1// script.aculo.us scriptaculous.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007 2 3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 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// For details, see the script.aculo.us web site: http://script.aculo.us/ 25 26var Scriptaculous = { 27 Version: '1.8.0', 28 REQUIRED_PROTOTYPE: '1.6.0' 29} 30 31var Builder = { 32 NODEMAP: { 33 AREA: 'map', 34 CAPTION: 'table', 35 COL: 'table', 36 COLGROUP: 'table', 37 LEGEND: 'fieldset', 38 OPTGROUP: 'select', 39 OPTION: 'select', 40 PARAM: 'object', 41 TBODY: 'table', 42 TD: 'table', 43 TFOOT: 'table', 44 TH: 'table', 45 THEAD: 'table', 46 TR: 'table' 47 }, 48 // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken, 49 // due to a Firefox bug 50 node: function(elementName) { 51 elementName = elementName.toUpperCase(); 52 53 // try innerHTML approach 54 var parentTag = this.NODEMAP[elementName] || 'div'; 55 var parentElement = document.createElement(parentTag); 56 try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 57 parentElement.innerHTML = "<" + elementName + "></" + elementName + ">"; 58 } catch(e) {} 59 var element = parentElement.firstChild || null; 60 61 // see if browser added wrapping tags 62 if(element && (element.tagName.toUpperCase() != elementName)) 63 element = element.getElementsByTagName(elementName)[0]; 64 65 // fallback to createElement approach 66 if(!element) element = document.createElement(elementName); 67 68 // abort if nothing could be created 69 if(!element) return; 70 71 // attributes (or text) 72 if(arguments[1]) 73 if(this._isStringOrNumber(arguments[1]) || 74 (arguments[1] instanceof Array) || 75 arguments[1].tagName) { 76 this._children(element, arguments[1]); 77 } else { 78 var attrs = this._attributes(arguments[1]); 79 if(attrs.length) { 80 try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 81 parentElement.innerHTML = "<" +elementName + " " + 82 attrs + "></" + elementName + ">"; 83 } catch(e) {} 84 element = parentElement.firstChild || null; 85 // workaround firefox 1.0.X bug 86 if(!element) { 87 element = document.createElement(elementName); 88 for(attr in arguments[1]) 89 element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; 90 } 91 if(element.tagName.toUpperCase() != elementName) 92 element = parentElement.getElementsByTagName(elementName)[0]; 93 } 94 } 95 96 // text, or array of children 97 if(arguments[2]) 98 this._children(element, arguments[2]); 99 100 return element; 101 }, 102 _text: function(text) { 103 return document.createTextNode(text); 104 }, 105 106 ATTR_MAP: { 107 'className': 'class', 108 'htmlFor': 'for' 109 }, 110 111 _attributes: function(attributes) { 112 var attrs = []; 113 for(attribute in attributes) 114 attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) + 115 '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'"') + '"'); 116 return attrs.join(" "); 117 }, 118 _children: function(element, children) { 119 if(children.tagName) { 120 element.appendChild(children); 121 return; 122 } 123 if(typeof children=='object') { // array can hold nodes and text 124 children.flatten().each( function(e) { 125 if(typeof e=='object') 126 element.appendChild(e) 127 else 128 if(Builder._isStringOrNumber(e)) 129 element.appendChild(Builder._text(e)); 130 }); 131 } else 132 if(Builder._isStringOrNumber(children)) 133 element.appendChild(Builder._text(children)); 134 }, 135 _isStringOrNumber: function(param) {
136 return(typeof param=='string' || typeof param=='number'); 137 }, 138 build: function(html) { 139 var element = this.node('div'); 140 $(element).update(html.strip()); 141 return element.down(); 142 }, 143 dump: function(scope) { 144 if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 145 146 var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " + 147 "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " + 148 "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+ 149 "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+ 150 "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+ 151 "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/); 152 153 tags.each( function(tag){ 154 scope[tag] = function() { 155 return Builder.node.apply(Builder, [tag].concat($A(arguments))); 156 } 157 }); 158 } 159} 160 161String.prototype.parseColor = function() { 162 var color = '#'; 163 if (this.slice(0,4) == 'rgb(') { 164 var cols = this.slice(4,this.length-1).split(','); 165 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); 166 } else { 167 if (this.slice(0,1) == '#') { 168 if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); 169 if (this.length==7) color = this.toLowerCase(); 170 } 171 } 172 return (color.length==7 ? color : (arguments[0] || this)); 173}; 174 175/*--------------------------------------------------------------------------*/ 176 177Element.collectTextNodes = function(element) { 178 return $A($(element).childNodes).collect( function(node) { 179 return (node.nodeType==3 ? node.nodeValue : 180 (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); 181 }).flatten().join(''); 182}; 183 184Element.collectTextNodesIgnoreClass = function(element, className) { 185 return $A($(element).childNodes).collect( function(node) { 186 return (node.nodeType==3 ? node.nodeValue : 187 ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 188 Element.collectTextNodesIgnoreClass(node, className) : '')); 189 }).flatten().join(''); 190}; 191 192Element.setContentZoom = function(element, percent) { 193 element = $(element); 194 element.setStyle({fontSize: (percent/100) + 'em'}); 195 if (Prototype.Browser.WebKit) window.scrollBy(0,0); 196 return element; 197}; 198 199Element.getInlineOpacity = function(element){ 200 return $(element).style.opacity || ''; 201}; 202 203Element.forceRerendering = function(element) { 204 try { 205 element = $(element); 206 var n = document.createTextNode(' '); 207 element.appendChild(n); 208 element.removeChild(n); 209 } catch(e) { } 210}; 211 212/*--------------------------------------------------------------------------*/ 213 214var Effect = { 215 _elementDoesNotExistError: { 216 name: 'ElementDoesNotExistError', 217 message: 'The specified DOM element does not exist, but is required for this effect to operate' 218 }, 219 Transitions: { 220 linear: Prototype.K, 221 sinoidal: function(pos) { 222 return (-Math.cos(pos*Math.PI)/2) + 0.5; 223 }, 224 reverse: function(pos) { 225 return 1-pos; 226 }, 227 flicker: function(pos) { 228 var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; 229 return pos > 1 ? 1 : pos; 230 }, 231 wobble: function(pos) { 232 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; 233 }, 234 pulse: function(pos, pulses) { 235 pulses = pulses || 5; 236 return ( 237 ((pos % (1/pulses)) * pulses).round() == 0 ? 238 ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 239 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor()) 240 ); 241 }, 242 spring: function(pos) { 243 return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 244 }, 245 none: function(pos) { 246 return 0; 247 }, 248 full: function(pos) { 249 return 1; 250 } 251 }, 252 DefaultOptions: { 253 duration: 1.0, // seconds 254 fps: 100, // 100= assume 66fps max. 255 sync: false, // true for combining 256 from: 0.0, 257 to: 1.0, 258 delay: 0.0, 259 queue: 'parallel' 260 }, 261 tagifyText: function(element) { 262 var tagifyStyle = 'position:relative'; 263 if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; 264 265 element = $(element); 266 $A(element.childNodes).each( function(child) { 267 if (child.nodeType==3) {
268 child.nodeValue.toArray().each( function(character) { 269 element.insertBefore( 270 new Element('span', {style: tagifyStyle}).update( 271 character == ' ' ? String.fromCharCode(160) : character), 272 child); 273 }); 274 Element.remove(child); 275 } 276 }); 277 }, 278 multiple: function(element, effect) { 279 var elements; 280 if (((typeof element == 'object') || 281 Object.isFunction(element)) && 282 (element.length)) 283 elements = element; 284 else 285 elements = $(element).childNodes; 286 287 var options = Object.extend({ 288 speed: 0.1, 289 delay: 0.0 290 }, arguments[2] || { }); 291 var masterDelay = options.delay; 292 293 $A(elements).each( function(element, index) { 294 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); 295 }); 296 }, 297 PAIRS: { 298 'slide': ['SlideDown','SlideUp'], 299 'blind': ['BlindDown','BlindUp'], 300 'appear': ['Appear','Fade'] 301 }, 302 toggle: function(element, effect) { 303 element = $(element); 304 effect = (effect || 'appear').toLowerCase(); 305 var options = Object.extend({ 306 queue: { position:'end', scope:(element.id || 'global'), limit: 1 } 307 }, arguments[2] || { }); 308 Effect[element.visible() ? 309 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); 310 } 311}; 312 313Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; 314 315/* ------------- core effects ------------- */ 316 317Effect.ScopedQueue = Class.create(Enumerable, { 318 initialize: function() { 319 this.effects = []; 320 this.interval = null; 321 }, 322 _each: function(iterator) { 323 this.effects._each(iterator); 324 }, 325 add: function(effect) { 326 var timestamp = new Date().getTime(); 327 328 var position = Object.isString(effect.options.queue) ? 329 effect.options.queue : effect.options.queue.position; 330 331 switch(position) { 332 case 'front': 333 // move unstarted effects after this effect 334 this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { 335 e.startOn += effect.finishOn; 336 e.finishOn += effect.finishOn; 337 }); 338 break; 339 case 'with-last': 340 timestamp = this.effects.pluck('startOn').max() || timestamp; 341 break; 342 case 'end': 343 // start effect after last queued effect has finished 344 timestamp = this.effects.pluck('finishOn').max() || timestamp; 345 break; 346 } 347 348 effect.startOn += timestamp; 349 effect.finishOn += timestamp; 350 351 if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) 352 this.effects.push(effect); 353 354 if (!this.interval) 355 this.interval = setInterval(this.loop.bind(this), 15); 356 }, 357 remove: function(effect) { 358 this.effects = this.effects.reject(function(e) { return e==effect }); 359 if (this.effects.length == 0) { 360 clearInterval(this.interval); 361 this.interval = null; 362 } 363 }, 364 loop: function() { 365 var timePos = new Date().getTime(); 366 for(var i=0, len=this.effects.length;i<len;i++) 367 this.effects[i] && this.effects[i].loop(timePos); 368 } 369}); 370 371Effect.Queues = { 372 instances: $H(), 373 get: function(queueName) { 374 if (!Object.isString(queueName)) return queueName; 375 376 return this.instances.get(queueName) || 377 this.instances.set(queueName, new Effect.ScopedQueue()); 378 } 379}; 380Effect.Queue = Effect.Queues.get('global'); 381 382Effect.Base = Class.create({ 383 position: null, 384 start: function(options) { 385 function codeForEvent(options,eventName){ 386 return ( 387 (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') + 388 (options[eventName] ? 'this.options.'+eventName+'(this);' : '') 389 ); 390 } 391 if (options && options.transition === false) options.transition = Effect.Transitions.linear; 392 this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { }); 393 this.currentFrame = 0; 394 this.state = 'idle'; 395 this.startOn = this.options.delay*1000; 396 this.finishOn = this.startOn+(this.options.duration*1000); 397 this.fromToDelta = this.options.to-this.options.from; 398 this.totalTime = this.finishOn-this.startOn; 399 this.totalFrames = this.options.fps*this.options.duration; 400 401 eval('this.render = function(pos){ '+ 402 'if (this.state=="idle"){this.state="running";'+ 403 codeForEvent(this.options,'beforeSetup')+ 404 (this.setup ? 'this.setup();':'')+ 405 codeForEvent(this.options,'afterSetup')+ 406 '};if (this.state=="running"){'+ 407 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+ 408 'this.position=pos;'+ 409 codeForEvent(this.options,'beforeUpdate')+ 410 (this.update ? 'this.update(pos);':'')+ 411 codeForEvent(this.options,'afterUpdate')+ 412 '}}'); 413 414 this.event('beforeStart'); 415 if (!this.options.sync)
416 Effect.Queues.get(Object.isString(this.options.queue) ? 417 'global' : this.options.queue.scope).add(this); 418 }, 419 loop: function(timePos) { 420 if (timePos >= this.startOn) { 421 if (timePos >= this.finishOn) { 422 this.render(1.0); 423 this.cancel(); 424 this.event('beforeFinish'); 425 if (this.finish) this.finish(); 426 this.event('afterFinish'); 427 return; 428 } 429 var pos = (timePos - this.startOn) / this.totalTime, 430 frame = (pos * this.totalFrames).round(); 431 if (frame > this.currentFrame) { 432 this.render(pos); 433 this.currentFrame = frame; 434 } 435 } 436 }, 437 cancel: function() { 438 if (!this.options.sync) 439 Effect.Queues.get(Object.isString(this.options.queue) ? 440 'global' : this.options.queue.scope).remove(this); 441 this.state = 'finished'; 442 }, 443 event: function(eventName) { 444 if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); 445 if (this.options[eventName]) this.options[eventName](this); 446 }, 447 inspect: function() { 448 var data = $H(); 449 for(property in this) 450 if (!Object.isFunction(this[property])) data.set(property, this[property]); 451 return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>'; 452 } 453}); 454 455Effect.Parallel = Class.create(Effect.Base, { 456 initialize: function(effects) { 457 this.effects = effects || []; 458 this.start(arguments[1]); 459 }, 460 update: function(position) { 461 this.effects.invoke('render', position); 462 }, 463 finish: function(position) { 464 this.effects.each( function(effect) { 465 effect.render(1.0); 466 effect.cancel(); 467 effect.event('beforeFinish'); 468 if (effect.finish) effect.finish(position); 469 effect.event('afterFinish'); 470 }); 471 } 472}); 473 474Effect.Tween = Class.create(Effect.Base, { 475 initialize: function(object, from, to) { 476 object = Object.isString(object) ? $(object) : object; 477 var args = $A(arguments), method = args.last(), 478 options = args.length == 5 ? args[3] : null; 479 this.method = Object.isFunction(method) ? method.bind(object) : 480 Object.isFunction(object[method]) ? object[method].bind(object) : 481 function(value) { object[method] = value }; 482 this.start(Object.extend({ from: from, to: to }, options || { })); 483 }, 484 update: function(position) { 485 this.method(position); 486 } 487}); 488 489Effect.Event = Class.create(Effect.Base, { 490 initialize: function() { 491 this.start(Object.extend({ duration: 0 }, arguments[0] || { })); 492 }, 493 update: Prototype.emptyFunction 494}); 495 496Effect.Opacity = Class.create(Effect.Base, { 497 initialize: function(element) { 498 this.element = $(element); 499 if (!this.element) throw(Effect._elementDoesNotExistError); 500 // make this work on IE on elements without 'layout' 501 if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) 502 this.element.setStyle({zoom: 1}); 503 var options = Object.extend({ 504 from: this.element.getOpacity() || 0.0, 505 to: 1.0 506 }, arguments[1] || { }); 507 this.start(options); 508 }, 509 update: function(position) { 510 this.element.setOpacity(position); 511 } 512}); 513 514Effect.Move = Class.create(Effect.Base, { 515 initialize: function(element) { 516 this.element = $(element); 517 if (!this.element) throw(Effect._elementDoesNotExistError); 518 var options = Object.extend({ 519 x: 0, 520 y: 0, 521 mode: 'relative' 522 }, arguments[1] || { }); 523 this.start(options); 524 }, 525 setup: function() { 526 this.element.makePositioned(); 527 this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); 528 this.originalTop = parseFloat(this.element.getStyle('top') || '0'); 529 if (this.options.mode == 'absolute') { 530 this.options.x = this.options.x - this.originalLeft; 531 this.options.y = this.options.y - this.originalTop; 532 } 533 }, 534 update: function(position) { 535 this.element.setStyle({ 536 left: (this.options.x * position + this.originalLeft).round() + 'px', 537 top: (this.options.y * position + this.originalTop).round() + 'px' 538 }); 539 } 540}); 541 542// for backwards compatibility 543Effect.MoveBy = function(element, toTop, toLeft) { 544 return new Effect.Move(element, 545 Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); 546}; 547 548Effect.Scale = Class.create(Effect.Base, { 549 initialize: function(element, percent) { 550 this.element = $(element); 551 if (!this.element) throw(Effect._elementDoesNotExistError); 552 var options = Object.extend({ 553 scaleX: true, 554 scaleY: true, 555 scaleContent: true, 556 scaleFromCenter: false, 557 scaleMode: 'box', // 'box' or 'contents' or { } with provided values 558 scaleFrom: 100.0, 559 scaleTo: percent 560 }, arguments[2] || { }); 561 this.start(options); 562 }, 563 setup: function() { 564 this.restoreAfterFinish = this.options.restoreAfterFinish || false; 565 this.elementPositioning = this.element.getStyle('position'); 566 567 this.originalStyle = { }; 568 ['top','left','width','height','fontSize'].each( function(k) { 569 this.originalStyle[k] = this.element.style[k]; 570 }.bind(this)); 571 572 this.originalTop = this.element.offsetTop; 573 this.originalLeft = this.element.offsetLeft; 574 575 var fontSize = this.element.getStyle('font-size') || '100%'; 576 ['em','px','%','pt'].each( function(fontSizeType) { 577 if (fontSize.indexOf(fontSizeType)>0) { 578 this.fontSize = parseFloat(fontSize); 579 this.fontSizeType = fontSizeType; 580 } 581 }.bind(this)); 582 583 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; 584 585 this.dims = null; 586 if (this.options.scaleMode=='box') 587 this.dims = [this.element.offsetHeight, this.element.offsetWidth]; 588 if (/^content/.test(this.options.scaleMode)) 589 this.dims = [this.element.scrollHeight, this.element.scrollWidth]; 590 if (!this.dims) 591 this.dims = [this.options.scaleMode.originalHeight, 592 this.options.scaleMode.originalWidth]; 593 }, 594 update: function(position) { 595 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); 596 if (this.options.scaleContent && this.fontSize) 597 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); 598 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); 599 }, 600 finish: function(position) { 601 if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); 602 }, 603 setDimensions: function(height, width) { 604 var d = { }; 605 if (this.options.scaleX) d.width = width.round() + 'px'; 606 if (this.options.scaleY) d.height = height.round() + 'px'; 607 if (this.options.scaleFromCenter) { 608 var topd = (height - this.dims[0])/2; 609 var leftd = (width - this.dims[1])/2; 610 if (this.elementPositioning == 'absolute') { 611 if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; 612 if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; 613 } else { 614 if (this.options.scaleY) d.top = -topd + 'px'; 615 if (this.options.scaleX) d.left = -leftd + 'px'; 616 } 617 } 618 this.element.setStyle(d); 619 } 620}); 621 622Effect.Highlight = Class.create(Effect.Base, { 623 initialize: function(element) { 624 this.element = $(element); 625 if (!this.element) throw(Effect._elementDoesNotExistError); 626 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); 627 this.start(options); 628 }, 629 setup: function() { 630 // Prevent executing on elements not in the layout flow 631 if (this.element.getStyle('display')=='none') { this.cancel(); return; } 632 // Disable background image during the effect
633 this.oldStyle = { }; 634 if (!this.options.keepBackgroundImage) { 635 this.oldStyle.backgroundImage = this.element.getStyle('background-image'); 636 this.element.setStyle({backgroundImage: 'none'}); 637 } 638 if (!this.options.endcolor) 639 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); 640 if (!this.options.restorecolor) 641 this.options.restorecolor = this.element.getStyle('background-color'); 642 // init color calculations 643 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); 644 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)); 645 }, 646 update: function(position) { 647 this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ 648 return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); 649 }, 650 finish: function() { 651 this.element.setStyle(Object.extend(this.oldStyle, { 652 backgroundColor: this.options.restorecolor 653 })); 654 } 655}); 656 657Effect.ScrollTo = function(element) { 658 var options = arguments[1] || { }, 659 scrollOffsets = document.viewport.getScrollOffsets(), 660 elementOffsets = $(element).cumulativeOffset(), 661 max = (window.height || document.body.scrollHeight) - document.viewport.getHeight(); 662 663 if (options.offset) elementOffsets[1] += options.offset; 664 665 return new Effect.Tween(null, 666 scrollOffsets.top, 667 elementOffsets[1] > max ? max : elementOffsets[1], 668 options, 669 function(p){ scrollTo(scrollOffsets.left, p.round()) } 670 ); 671}; 672 673/* ------------- combination effects ------------- */ 674 675Effect.Fade = function(element) { 676 element = $(element); 677 var oldOpacity = element.getInlineOpacity(); 678 var options = Object.extend({ 679 from: element.getOpacity() || 1.0, 680 to: 0.0, 681 afterFinishInternal: function(effect) { 682 if (effect.options.to!=0) return; 683 effect.element.hide().setStyle({opacity: oldOpacity}); 684 } 685 }, arguments[1] || { }); 686 return new Effect.Opacity(element,options); 687}; 688 689Effect.Appear = function(element) { 690 element = $(element); 691 var options = Object.extend({ 692 from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), 693 to: 1.0, 694 // force Safari to render floated elements properly 695 afterFinishInternal: function(effect) { 696 effect.element.forceRerendering(); 697 }, 698 beforeSetup: function(effect) { 699 effect.element.setOpacity(effect.options.from).show(); 700 }}, arguments[1] || { }); 701 return new Effect.Opacity(element,options); 702}; 703 704Effect.Puff = function(element) { 705 element = $(element); 706 var oldStyle = { 707 opacity: element.getInlineOpacity(), 708 position: element.getStyle('position'), 709 top: element.style.top, 710 left: element.style.left, 711 width: element.style.width, 712 height: element.style.height 713 }; 714 return new Effect.Parallel( 715 [ new Effect.Scale(element, 200, 716 { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 717 new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 718 Object.extend({ duration: 1.0, 719 beforeSetupInternal: function(effect) { 720 Position.absolutize(effect.effects[0].element) 721 }, 722 afterFinishInternal: function(effect) { 723 effect.effects[0].element.hide().setStyle(oldStyle); } 724 }, arguments[1] || { }) 725 ); 726}; 727 728Effect.BlindUp = function(element) { 729 element = $(element); 730 element.makeClipping(); 731 return new Effect.Scale(element, 0, 732 Object.extend({ scaleContent: false, 733 scaleX: false, 734 restoreAfterFinish: true, 735 afterFinishInternal: function(effect) { 736 effect.element.hide().undoClipping(); 737 } 738 }, arguments[1] || { }) 739 ); 740}; 741 742Effect.BlindDown = function(element) { 743 element = $(element); 744 var elementDimensions = element.getDimensions(); 745 return new Effect.Scale(element, 100, Object.extend({ 746 scaleContent: false, 747 scaleX: false, 748 scaleFrom: 0, 749 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 750 restoreAfterFinish: true, 751 afterSetup: function(effect) { 752 effect.element.makeClipping().setStyle({height: '0px'}).show(); 753 }, 754 afterFinishInternal: function(effect) { 755 effect.element.undoClipping(); 756 } 757 }, arguments[1] || { })); 758}; 759 760Effect.SwitchOff = function(element) { 761 element = $(element); 762 var oldOpacity = element.getInlineOpacity(); 763 return new Effect.Appear(element, Object.extend({ 764 duration: 0.4, 765 from: 0, 766 transition: Effect.Transitions.flicker, 767 afterFinishInternal: function(effect) { 768 new Effect.Scale(effect.element, 1, { 769 duration: 0.3, scaleFromCenter: true, 770 scaleX: false, scaleContent: false, restoreAfterFinish: true, 771 beforeSetup: function(effect) { 772 effect.element.makePositioned().makeClipping(); 773 }, 774 afterFinishInternal: function(effect) { 775 effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); 776 } 777 }) 778 } 779 }, arguments[1] || { })); 780}; 781 782Effect.DropOut = function(element) { 783 element = $(element); 784 var oldStyle = { 785 top: element.getStyle('top'), 786 left: element.getStyle('left'), 787 opacity: element.getInlineOpacity() }; 788 return new Effect.Parallel( 789 [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 790 new Effect.Opacity(element, { sync: true, to: 0.0 }) ], 791 Object.extend( 792 { duration: 0.5, 793 beforeSetup: function(effect) { 794 effect.effects[0].element.makePositioned(); 795 }, 796 afterFinishInternal: function(effect) { 797 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); 798 } 799 }, arguments[1] || { })); 800}; 801 802Effect.Shake = function(element) { 803 element = $(element); 804 var options = Object.extend({ 805 distance: 20, 806 duration: 0.5 807 }, arguments[1] || {}); 808 var distance = parseFloat(options.distance); 809 var split = parseFloat(options.duration) / 10.0; 810 var oldStyle = { 811 top: element.getStyle('top'), 812 left: element.getStyle('left') }; 813 return new Effect.Move(element, 814 { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { 815 new Effect.Move(effect.element, 816 { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { 817 new Effect.Move(effect.element, 818 { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { 819 new Effect.Move(effect.element, 820 { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { 821 new Effect.Move(effect.element, 822 { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { 823 new Effect.Move(effect.element, 824 { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { 825 effect.element.undoPositioned().setStyle(oldStyle); 826 }}) }}) }}) }}) }}) }}); 827}; 828 829Effect.SlideDown = function(element) { 830 element = $(element).cleanWhitespace(); 831 // SlideDown need to have the content of the element wrapped in a container element with fixed height! 832 var oldInnerBottom = element.down().getStyle('bottom'); 833 var elementDimensions = element.getDimensions(); 834 return new Effect.Scale(element, 100, Object.extend({ 835 scaleContent: false, 836 scaleX: false, 837 scaleFrom: window.opera ? 0 : 1, 838 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 839 restoreAfterFinish: true, 840 afterSetup: function(effect) { 841 effect.element.makePositioned(); 842 effect.element.down().makePositioned(); 843 if (window.opera) effect.element.setStyle({top: ''}); 844 effect.element.makeClipping().setStyle({height: '0px'}).show(); 845 }, 846 afterUpdateInternal: function(effect) { 847 effect.element.down().setStyle({bottom: 848 (effect.dims[0] - effect.element.clientHeight) + 'px' }); 849 }, 850 afterFinishInternal: function(effect) { 851 effect.element.undoClipping().undoPositioned(); 852 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } 853 }, arguments[1] || { }) 854 ); 855}; 856 857Effect.SlideUp = function(element) { 858 element = $(element).cleanWhitespace(); 859 var oldInnerBottom = element.down().getStyle('bottom'); 860 var elementDimensions = element.getDimensions(); 861 return new Effect.Scale(element, window.opera ? 0 : 1, 862 Object.extend({ scaleContent: false, 863 scaleX: false, 864 scaleMode: 'box', 865 scaleFrom: 100, 866 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 867 restoreAfterFinish: true, 868 afterSetup: function(effect) { 869 effect.element.makePositioned(); 870 effect.element.down().makePositioned(); 871 if (window.opera) effect.element.setStyle({top: ''}); 872 effect.element.makeClipping().show(); 873 }, 874 afterUpdateInternal: function(effect) { 875 effect.element.down().setStyle({bottom: 876 (effect.dims[0] - effect.element.clientHeight) + 'px' }); 877 }, 878 afterFinishInternal: function(effect) { 879 effect.element.hide().undoClipping().undoPositioned(); 880 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); 881 } 882 }, arguments[1] || { }) 883 ); 884}; 885 886// Bug in opera makes the TD containing this element expand for a instance after finish 887Effect.Squish = function(element) { 888 return new Effect.Scale(element, window.opera ? 1 : 0, { 889 restoreAfterFinish: true, 890 beforeSetup: function(effect) { 891 effect.element.makeClipping(); 892 }, 893 afterFinishInternal: function(effect) { 894 effect.element.hide().undoClipping(); 895 } 896 }); 897}; 898 899Effect.Grow = function(element) { 900 element = $(element); 901 var options = Object.extend({ 902 direction: 'center', 903 moveTransition: Effect.Transitions.sinoidal, 904 scaleTransition: Effect.Transitions.sinoidal, 905 opacityTransition: Effect.Transitions.full 906 }, arguments[1] || { }); 907 var oldStyle = { 908 top: element.style.top, 909 left: element.style.left, 910 height: element.style.height, 911 width: element.style.width, 912 opacity: element.getInlineOpacity() }; 913 914 var dims = element.getDimensions(); 915 var initialMoveX, initialMoveY; 916 var moveX, moveY; 917 918 switch (options.direction) { 919 case 'top-left': 920 initialMoveX = initialMoveY = moveX = moveY = 0; 921 break; 922 case 'top-right': 923 initialMoveX = dims.width; 924 initialMoveY = moveY = 0; 925 moveX = -dims.width; 926 break; 927 case 'bottom-left': 928 initialMoveX = moveX = 0; 929 initialMoveY = dims.height; 930 moveY = -dims.height; 931 break; 932 case 'bottom-right': 933 initialMoveX = dims.width; 934 initialMoveY = dims.height; 935 moveX = -dims.width; 936 moveY = -dims.height; 937 break; 938 case 'center': 939 initialMoveX = dims.width / 2; 940 initialMoveY = dims.height / 2; 941 moveX = -dims.width / 2; 942 moveY = -dims.height / 2; 943 break; 944 } 945 946 return new Effect.Move(element, { 947 x: initialMoveX, 948 y: initialMoveY, 949 duration: 0.01, 950 beforeSetup: function(effect) { 951 effect.element.hide().makeClipping().makePositioned(); 952 }, 953 afterFinishInternal: function(effect) { 954 new Effect.Parallel( 955 [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), 956 new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), 957 new Effect.Scale(effect.element, 100, { 958 scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 959 sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) 960 ], Object.extend({ 961 beforeSetup: function(effect) { 962 effect.effects[0].element.setStyle({height: '0px'}).show(); 963 }, 964 afterFinishInternal: function(effect) { 965 effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 966 } 967 }, options) 968 ) 969 } 970 }); 971}; 972 973Effect.Shrink = function(element) { 974 element = $(element); 975 var options = Object.extend({ 976 direction: 'center', 977 moveTransition: Effect.Transitions.sinoidal, 978 scaleTransition: Effect.Transitions.sinoidal, 979 opacityTransition: Effect.Transitions.none 980 }, arguments[1] || { }); 981 var oldStyle = { 982 top: element.style.top, 983 left: element.style.left, 984 height: element.style.height, 985 width: element.style.width, 986 opacity: element.getInlineOpacity() }; 987 988 var dims = element.getDimensions(); 989 var moveX, moveY; 990 991 switch (options.direction) { 992 case 'top-left': 993 moveX = moveY = 0; 994 break; 995 case 'top-right': 996 moveX = dims.width; 997 moveY = 0; 998 break; 999 case 'bottom-left': 1000 moveX = 0; 1001 moveY = dims.height; 1002 break; 1003 case 'bottom-right': 1004 moveX = dims.width; 1005 moveY = dims.height; 1006 break; 1007 case 'center': 1008 moveX = dims.width / 2; 1009 moveY = dims.height / 2; 1010 break; 1011 } 1012 1013 return new Effect.Parallel( 1014 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), 1015 new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), 1016 new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) 1017 ], Object.extend({ 1018 beforeStartInternal: function(effect) { 1019 effect.effects[0].element.makePositioned().makeClipping(); 1020 }, 1021 afterFinishInternal: function(effect) { 1022 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } 1023 }, options) 1024 ); 1025}; 1026 1027Effect.Pulsate = function(element) { 1028 element = $(element); 1029 var options = arguments[1] || { }; 1030 var oldOpacity = element.getInlineOpacity();
1031 var transition = options.transition || Effect.Transitions.sinoidal; 1032 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; 1033 reverser.bind(transition); 1034 return new Effect.Opacity(element, 1035 Object.extend(Object.extend({ duration: 2.0, from: 0, 1036 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } 1037 }, options), {transition: reverser})); 1038}; 1039 1040Effect.Fold = function(element) { 1041 element = $(element); 1042 var oldStyle = { 1043 top: element.style.top, 1044 left: element.style.left, 1045 width: element.style.width, 1046 height: element.style.height }; 1047 element.makeClipping(); 1048 return new Effect.Scale(element, 5, Object.extend({ 1049 scaleContent: false, 1050 scaleX: false, 1051 afterFinishInternal: function(effect) { 1052 new Effect.Scale(element, 1, { 1053 scaleContent: false, 1054 scaleY: false, 1055 afterFinishInternal: function(effect) { 1056 effect.element.hide().undoClipping().setStyle(oldStyle); 1057 } }); 1058 }}, arguments[1] || { })); 1059}; 1060 1061Effect.Morph = Class.create(Effect.Base, { 1062 initialize: function(element) { 1063 this.element = $(element); 1064 if (!this.element) throw(Effect._elementDoesNotExistError); 1065 var options = Object.extend({ 1066 style: { } 1067 }, arguments[1] || { }); 1068 1069 if (!Object.isString(options.style)) this.style = $H(options.style); 1070 else { 1071 if (options.style.include(':')) 1072 this.style = options.style.parseStyle(); 1073 else { 1074 this.element.addClassName(options.style); 1075 this.style = $H(this.element.getStyles()); 1076 this.element.removeClassName(options.style); 1077 var css = this.element.getStyles(); 1078 this.style = this.style.reject(function(style) { 1079 return style.value == css[style.key]; 1080 }); 1081 options.afterFinishInternal = function(effect) { 1082 effect.element.addClassName(effect.options.style); 1083 effect.transforms.each(function(transform) { 1084 effect.element.style[transform.style] = ''; 1085 }); 1086 } 1087 } 1088 } 1089 this.start(options); 1090 }, 1091 1092 setup: function(){ 1093 function parseColor(color){ 1094 if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; 1095 color = color.parseColor(); 1096 return $R(0,2).map(function(i){ 1097 return parseInt( color.slice(i*2+1,i*2+3), 16 ) 1098 }); 1099 } 1100 this.transforms = this.style.map(function(pair){ 1101 var property = pair[0], value = pair[1], unit = null; 1102 1103 if (value.parseColor('#zzzzzz') != '#zzzzzz') { 1104 value = value.parseColor(); 1105 unit = 'color'; 1106 } else if (property == 'opacity') { 1107 value = parseFloat(value); 1108 if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) 1109 this.element.setStyle({zoom: 1}); 1110 } else if (Element.CSS_LENGTH.test(value)) { 1111 var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); 1112 value = parseFloat(components[1]); 1113 unit = (components.length == 3) ? components[2] : null; 1114 } 1115 1116 var originalValue = this.element.getStyle(property); 1117 return { 1118 style: property.camelize(), 1119 originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 1120 targetValue: unit=='color' ? parseColor(value) : value, 1121 unit: unit 1122 }; 1123 }.bind(this)).reject(function(transform){ 1124 return ( 1125 (transform.originalValue == transform.targetValue) || 1126 ( 1127 transform.unit != 'color' && 1128 (isNaN(transform.originalValue) || isNaN(transform.targetValue)) 1129 ) 1130 ) 1131 }); 1132 }, 1133 update: function(position) { 1134 var style = { }, transform, i = this.transforms.length; 1135 while(i--) 1136 style[(transform = this.transforms[i]).style] = 1137 transform.unit=='color' ? '#'+ 1138 (Math.round(transform.originalValue[0]+ 1139 (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + 1140 (Math.round(transform.originalValue[1]+ 1141 (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + 1142 (Math.round(transform.originalValue[2]+ 1143 (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : 1144 (transform.originalValue + 1145 (transform.targetValue - transform.originalValue) * position).toFixed(3) + 1146 (transform.unit === null ? '' : transform.unit); 1147 this.element.setStyle(style, true); 1148 } 1149}); 1150 1151Effect.Transform = Class.create({ 1152 initialize: function(tracks){ 1153 this.tracks = []; 1154 this.options = arguments[1] || { }; 1155 this.addTracks(tracks); 1156 }, 1157 addTracks: function(tracks){ 1158 tracks.each(function(track){ 1159 track = $H(track); 1160 var data = track.values().first(); 1161 this.tracks.push($H({ 1162 ids: track.keys().first(), 1163 effect: Effect.Morph, 1164 options: { style: data } 1165 })); 1166 }.bind(this)); 1167 return this; 1168 }, 1169 play: function(){ 1170 return new Effect.Parallel( 1171 this.tracks.map(function(track){ 1172 var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); 1173 var elements = [$(ids) || $$(ids)].flatten(); 1174 return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); 1175 }).flatten(), 1176 this.options 1177 ); 1178 } 1179}); 1180 1181Element.CSS_PROPERTIES = $w( 1182 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 1183 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + 1184 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + 1185 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + 1186 'fontSize fontWeight height left letterSpacing lineHeight ' + 1187 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ 1188 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + 1189 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + 1190 'right textIndent top width wordSpacing zIndex'); 1191 1192Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; 1193 1194String.__parseStyleElement = document.createElement('div'); 1195String.prototype.parseStyle = function(){ 1196 var style, styleRules = $H(); 1197 if (Prototype.Browser.WebKit) 1198 style = new Element('div',{style:this}).style; 1199 else { 1200 String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>'; 1201 style = String.__parseStyleElement.childNodes[0].style; 1202 } 1203 1204 Element.CSS_PROPERTIES.each(function(property){ 1205 if (style[property]) styleRules.set(property, style[property]); 1206 }); 1207 1208 if (Prototype.Browser.IE && this.include('opacity')) 1209 styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); 1210 1211 return styleRules; 1212}; 1213 1214if (document.defaultView && document.defaultView.getComputedStyle) { 1215 Element.getStyles = function(element) { 1216 var css = document.defaultView.getComputedStyle($(element), null); 1217 return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { 1218 styles[property] = css[property]; 1219 return styles; 1220 }); 1221 }; 1222} else { 1223 Element.getStyles = function(element) { 1224 element = $(element); 1225 var css = element.currentStyle, styles; 1226 styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) { 1227 hash.set(property, css[property]); 1228 return hash; 1229 }); 1230 if (!styles.opacity) styles.set('opacity', element.getOpacity()); 1231 return styles; 1232 }; 1233}; 1234 1235Effect.Methods = { 1236 morph: function(element, style) { 1237 element = $(element); 1238 new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); 1239 return element; 1240 }, 1241 visualEffect: function(element, effect, options) { 1242 element = $(element) 1243 var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); 1244 new Effect[klass](element, options); 1245 return element; 1246 }, 1247 highlight: function(element, options) { 1248 element = $(element); 1249 new Effect.Highlight(element, options); 1250 return element; 1251 } 1252}; 1253 1254$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ 1255 'pulsate shake puff squish switchOff dropOut').each( 1256 function(effect) { 1257 Effect.Methods[effect] = function(element, options){ 1258 element = $(element);
1259 Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); 1260 return element; 1261 } 1262 } 1263); 1264 1265$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 1266 function(f) { Effect.Methods[f] = Element[f]; } 1267); 1268 1269Element.addMethods(Effect.Methods); 1270 1271if(typeof Effect == 'undefined') 1272 throw("controls.js requires including script.aculo.us' effects.js library"); 1273 1274var Autocompleter = { } 1275Autocompleter.Base = Class.create({ 1276 baseInitialize: function(element, update, options) { 1277 element = $(element) 1278 this.element = element; 1279 this.update = $(update); 1280 this.hasFocus = false; 1281 this.changed = false; 1282 this.active = false; 1283 this.index = 0; 1284 this.entryCount = 0; 1285 this.oldElementValue = this.element.value; 1286 1287 if(this.setOptions) 1288 this.setOptions(options); 1289 else 1290 this.options = options || { }; 1291 1292 this.options.paramName = this.options.paramName || this.element.name; 1293 this.options.tokens = this.options.tokens || []; 1294 this.options.frequency = this.options.frequency || 0.4; 1295 this.options.minChars = this.options.minChars || 1; 1296 this.options.onShow = this.options.onShow || 1297 function(element, update){ 1298 if(!update.style.position || update.style.position=='absolute') { 1299 update.style.position = 'absolute'; 1300 Position.clone(element, update, { 1301 setHeight: false, 1302 offsetTop: element.offsetHeight 1303 }); 1304 } 1305 Effect.Appear(update,{duration:0.15}); 1306 }; 1307 this.options.onHide = this.options.onHide || 1308 function(element, update){ new Effect.Fade(update,{duration:0.15}) }; 1309 1310 if(typeof(this.options.tokens) == 'string') 1311 this.options.tokens = new Array(this.options.tokens); 1312 // Force carriage returns as token delimiters anyway 1313 if (!this.options.tokens.include('\n')) 1314 this.options.tokens.push('\n'); 1315 1316 this.observer = null; 1317 1318 this.element.setAttribute('autocomplete','off'); 1319 1320 Element.hide(this.update); 1321 1322 Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); 1323 Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this)); 1324 }, 1325 1326 show: function() { 1327 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); 1328 if(!this.iefix && 1329 (Prototype.Browser.IE) && 1330 (Element.getStyle(this.update, 'position')=='absolute')) { 1331 new Insertion.After(this.update, 1332 '<iframe id="' + this.update.id + '_iefix" '+ 1333 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 1334 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); 1335 this.iefix = $(this.update.id+'_iefix'); 1336 } 1337 if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); 1338 }, 1339 1340 fixIEOverlapping: function() { 1341 Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); 1342 this.iefix.style.zIndex = 1; 1343 this.update.style.zIndex = 2; 1344 Element.show(this.iefix); 1345 }, 1346 1347 hide: function() { 1348 this.stopIndicator(); 1349 if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); 1350 if(this.iefix) Element.hide(this.iefix); 1351 }, 1352 1353 startIndicator: function() { 1354 if(this.options.indicator) Element.show(this.options.indicator); 1355 }, 1356 1357 stopIndicator: function() { 1358 if(this.options.indicator) Element.hide(this.options.indicator); 1359 }, 1360 1361 onKeyPress: function(event) { 1362 if(this.active) 1363 switch(event.keyCode) { 1364 case Event.KEY_TAB: 1365 case Event.KEY_RETURN: 1366 this.selectEntry(); 1367 Event.stop(event); 1368 case Event.KEY_ESC: 1369 this.hide(); 1370 this.active = false; 1371 Event.stop(event); 1372 return; 1373 case Event.KEY_LEFT: 1374 case Event.KEY_RIGHT: 1375 return; 1376 case Event.KEY_UP: 1377 this.markPrevious(); 1378 this.render();
1379 if(Prototype.Browser.WebKit) Event.stop(event); 1380 return; 1381 case Event.KEY_DOWN: 1382 this.markNext(); 1383 this.render(); 1384 if(Prototype.Browser.WebKit) Event.stop(event); 1385 return; 1386 } 1387 else 1388 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 1389 (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; 1390 1391 this.changed = true; 1392 this.hasFocus = true; 1393 1394 if(this.observer) clearTimeout(this.observer); 1395 this.observer = 1396 setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); 1397 }, 1398 1399 activate: function() { 1400 this.changed = false; 1401 this.hasFocus = true; 1402 this.getUpdatedChoices(); 1403 }, 1404 1405 onHover: function(event) { 1406 var element = Event.findElement(event, 'LI'); 1407 if(this.index != element.autocompleteIndex) 1408 { 1409 this.index = element.autocompleteIndex; 1410 this.render(); 1411 } 1412 Event.stop(event); 1413 }, 1414 1415 onClick: function(event) { 1416 var element = Event.findElement(event, 'LI'); 1417 this.index = element.autocompleteIndex; 1418 this.selectEntry(); 1419 this.hide(); 1420 }, 1421 1422 onBlur: function(event) { 1423 // needed to make click events working 1424 setTimeout(this.hide.bind(this), 250); 1425 this.hasFocus = false; 1426 this.active = false; 1427 }, 1428 1429 render: function() { 1430 if(this.entryCount > 0) { 1431 for (var i = 0; i < this.entryCount; i++) 1432 this.index==i ? 1433 Element.addClassName(this.getEntry(i),"selected") : 1434 Element.removeClassName(this.getEntry(i),"selected"); 1435 if(this.hasFocus) { 1436 this.show(); 1437 this.active = true; 1438 } 1439 } else { 1440 this.active = false; 1441 this.hide(); 1442 } 1443 }, 1444 1445 markPrevious: function() { 1446 if(this.index > 0) this.index-- 1447 else this.index = this.entryCount-1; 1448 this.getEntry(this.index).scrollIntoView(true); 1449 }, 1450 1451 markNext: function() { 1452 if(this.index < this.entryCount-1) this.index++ 1453 else this.index = 0; 1454 this.getEntry(this.index).scrollIntoView(false); 1455 }, 1456 1457 getEntry: function(index) { 1458 return this.update.firstChild.childNodes[index]; 1459 }, 1460 1461 getCurrentEntry: function() { 1462 return this.getEntry(this.index); 1463 }, 1464 1465 selectEntry: function() { 1466 this.active = false; 1467 this.updateElement(this.getCurrentEntry()); 1468 }, 1469 1470 updateElement: function(selectedElement) { 1471 if (this.options.updateElement) { 1472 this.options.updateElement(selectedElement); 1473 return; 1474 } 1475 var value = ''; 1476 if (this.options.select) { 1477 var nodes = $(selectedElement).select('.' + this.options.select) || []; 1478 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); 1479 } else 1480 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); 1481 1482 var bounds = this.getTokenBounds(); 1483 if (bounds[0] != -1) { 1484 var newValue = this.element.value.substr(0, bounds[0]); 1485 var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); 1486 if (whitespace) 1487 newValue += whitespace[0]; 1488 this.element.value = newValue + value + this.element.value.substr(bounds[1]); 1489 } else { 1490 this.element.value = value; 1491 } 1492 this.oldElementValue = this.element.value; 1493 this.element.focus(); 1494 1495 if (this.options.afterUpdateElement) 1496 this.options.afterUpdateElement(this.element, selectedElement); 1497 }, 1498 1499 updateChoices: function(choices) { 1500 if(!this.changed && this.hasFocus) { 1501 this.update.innerHTML = choices; 1502 Element.cleanWhitespace(this.update); 1503 Element.cleanWhitespace(this.update.down()); 1504 1505 if(this.update.firstChild && this.update.down().childNodes) { 1506 this.entryCount = 1507 this.update.down().childNodes.length; 1508 for (var i = 0; i < this.entryCount; i++) { 1509 var entry = this.getEntry(i); 1510 entry.autocompleteIndex = i; 1511 this.addObservers(entry); 1512 } 1513 } else { 1514 this.entryCount = 0; 1515 } 1516 1517 this.stopIndicator(); 1518 this.index = 0; 1519 1520 if(this.entryCount==1 && this.options.autoSelect) { 1521 this.selectEntry(); 1522 this.hide(); 1523 } else { 1524 this.render(); 1525 } 1526 } 1527 }, 1528 1529 addObservers: function(element) { 1530 Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); 1531 Event.observe(element, "click", this.onClick.bindAsEventListener(this)); 1532 }, 1533 1534 onObserverEvent: function() { 1535 this.changed = false; 1536 this.tokenBounds = null; 1537 if(this.getToken().length>=this.options.minChars) { 1538 this.getUpdatedChoices(); 1539 } else { 1540 this.active = false;
1541 this.hide(); 1542 } 1543 this.oldElementValue = this.element.value; 1544 }, 1545 1546 getToken: function() { 1547 var bounds = this.getTokenBounds(); 1548 return this.element.value.substring(bounds[0], bounds[1]).strip(); 1549 }, 1550 1551 getTokenBounds: function() { 1552 if (null != this.tokenBounds) return this.tokenBounds; 1553 var value = this.element.value; 1554 if (value.strip().empty()) return [-1, 0]; 1555 var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); 1556 var offset = (diff == this.oldElementValue.length ? 1 : 0); 1557 var prevTokenPos = -1, nextTokenPos = value.length; 1558 var tp; 1559 for (var index = 0, l = this.options.tokens.length; index < l; ++index) { 1560 tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); 1561 if (tp > prevTokenPos) prevTokenPos = tp; 1562 tp = value.indexOf(this.options.tokens[index], diff + offset); 1563 if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; 1564 } 1565 return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); 1566 } 1567}); 1568 1569Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { 1570 var boundary = Math.min(newS.length, oldS.length); 1571 for (var index = 0; index < boundary; ++index) 1572 if (newS[index] != oldS[index]) 1573 return index; 1574 return boundary; 1575}; 1576 1577Ajax.Autocompleter = Class.create(Autocompleter.Base, { 1578 initialize: function(element, update, url, options) { 1579 this.baseInitialize(element, update, options); 1580 this.options.asynchronous = true; 1581 this.options.onComplete = this.onComplete.bind(this); 1582 this.options.defaultParams = this.options.parameters || null; 1583 this.url = url; 1584 }, 1585 1586 getUpdatedChoices: function() { 1587 this.startIndicator(); 1588 1589 var entry = encodeURIComponent(this.options.paramName) + '=' + 1590 encodeURIComponent(this.getToken()); 1591 1592 this.options.parameters = this.options.callback ? 1593 this.options.callback(this.element, entry) : entry; 1594 1595 if(this.options.defaultParams) 1596 this.options.parameters += '&' + this.options.defaultParams; 1597 1598 new Ajax.Request(this.url, this.options); 1599 }, 1600 1601 onComplete: function(request) { 1602 this.updateChoices(request.responseText); 1603 } 1604}); 1605 1606// The local array autocompleter. Used when you'd prefer to 1607// inject an array of autocompletion options into the page, rather 1608// than sending out Ajax queries, which can be quite slow sometimes. 1609// 1610// The constructor takes four parameters. The first two are, as usual, 1611// the id of the monitored textbox, and id of the autocompletion menu. 1612// The third is the array you want to autocomplete from, and the fourth 1613// is the options block. 1614// 1615// Extra local autocompletion options: 1616// - choices - How many autocompletion choices to offer 1617//
1618// - partialSearch - If false, the autocompleter will match entered 1619// text only at the beginning of strings in the 1620// autocomplete array. Defaults to true, which will 1621// match text at the beginning of any *word* in the 1622// strings in the autocomplete array. If you want to 1623// search anywhere in the string, additionally set 1624// the option fullSearch to true (default: off). 1625// 1626// - fullSsearch - Search anywhere in autocomplete array strings. 1627// 1628// - partialChars - How many characters to enter before triggering 1629// a partial match (unlike minChars, which defines 1630// how many characters are required to do any match 1631// at all). Defaults to 2. 1632// 1633// - ignoreCase - Whether to ignore case when autocompleting. 1634// Defaults to true. 1635// 1636// It's possible to pass in a custom function as the 'selector' 1637// option, if you prefer to write your own autocompletion logic. 1638// In that case, the other options above will not apply unless 1639// you support them. 1640 1641Autocompleter.Local = Class.create(Autocompleter.Base, { 1642 initialize: function(element, update, array, options) { 1643 this.baseInitialize(element, update, options); 1644 this.options.array = array; 1645 }, 1646 1647 getUpdatedChoices: function() { 1648 this.updateChoices(this.options.selector(this)); 1649 }, 1650 1651 setOptions: function(options) { 1652 this.options = Object.extend({ 1653 choices: 10, 1654 partialSearch: true, 1655 partialChars: 2, 1656 ignoreCase: true, 1657 fullSearch: false, 1658 selector: function(instance) { 1659 var ret = []; // Beginning matches 1660 var partial = []; // Inside matches 1661 var entry = instance.getToken(); 1662 var count = 0; 1663 1664 for (var i = 0; i < instance.options.array.length && 1665 ret.length < instance.options.choices ; i++) { 1666 1667 var elem = instance.options.array[i]; 1668 var foundPos = instance.options.ignoreCase ? 1669 elem.toLowerCase().indexOf(entry.toLowerCase()) : 1670 elem.indexOf(entry); 1671 1672 while (foundPos != -1) { 1673 if (foundPos == 0 && elem.length != entry.length) { 1674 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 1675 elem.substr(entry.length) + "</li>"); 1676 break; 1677 } else if (entry.length >= instance.options.partialChars && 1678 instance.options.partialSearch && foundPos != -1) { 1679 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { 1680 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" + 1681 elem.substr(foundPos, entry.length) + "</strong>" + elem.substr( 1682 foundPos + entry.length) + "</li>"); 1683 break; 1684 } 1685 } 1686 1687 foundPos = instance.options.ignoreCase ? 1688 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 1689 elem.indexOf(entry, foundPos + 1); 1690 1691 } 1692 } 1693 if (partial.length) 1694 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) 1695 return "<ul>" + ret.join('') + "</ul>"; 1696 } 1697 }, options || { }); 1698 } 1699}); 1700 1701// AJAX in-place editor and collection editor 1702// Full rewrite by Christophe Porteneuve <[email protected]> (April 2007). 1703 1704// Use this if you notice weird scrolling problems on some browsers, 1705// the DOM might be a bit confused when this gets called so do this 1706// waits 1 ms (with setTimeout) until it does the activation 1707Field.scrollFreeActivate = function(field) { 1708 setTimeout(function() { 1709 Field.activate(field); 1710 }, 1); 1711} 1712 1713Ajax.InPlaceEditor = Class.create({ 1714 initialize: function(element, url, options) { 1715 this.url = url; 1716 this.element = element = $(element); 1717 this.prepareOptions(); 1718 this._controls = { }; 1719 arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! 1720 Object.extend(this.options, options || { });
1721 if (!this.options.formId && this.element.id) { 1722 this.options.formId = this.element.id + '-inplaceeditor'; 1723 if ($(this.options.formId)) 1724 this.options.formId = ''; 1725 } 1726 if (this.options.externalControl) 1727 this.options.externalControl = $(this.options.externalControl); 1728 if (!this.options.externalControl) 1729 this.options.externalControlOnly = false; 1730 this._originalBackground = this.element.getStyle('background-color') || 'transparent'; 1731 this.element.title = this.options.clickToEditText; 1732 this._boundCancelHandler = this.handleFormCancellation.bind(this); 1733 this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); 1734 this._boundFailureHandler = this.handleAJAXFailure.bind(this); 1735 this._boundSubmitHandler = this.handleFormSubmission.bind(this); 1736 this._boundWrapperHandler = this.wrapUp.bind(this); 1737 this.registerListeners(); 1738 }, 1739 checkForEscapeOrReturn: function(e) { 1740 if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; 1741 if (Event.KEY_ESC == e.keyCode) 1742 this.handleFormCancellation(e); 1743 else if (Event.KEY_RETURN == e.keyCode) 1744 this.handleFormSubmission(e); 1745 }, 1746 createControl: function(mode, handler, extraClasses) { 1747 var control = this.options[mode + 'Control']; 1748 var text = this.options[mode + 'Text']; 1749 if ('button' == control) { 1750 var btn = document.createElement('input'); 1751 btn.type = 'submit'; 1752 btn.value = text; 1753 btn.className = 'editor_' + mode + '_button'; 1754 if ('cancel' == mode) 1755 btn.onclick = this._boundCancelHandler; 1756 this._form.appendChild(btn); 1757 this._controls[mode] = btn; 1758 } else if ('link' == control) { 1759 var link = document.createElement('a'); 1760 link.href = '#'; 1761 link.appendChild(document.createTextNode(text)); 1762 link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; 1763 link.className = 'editor_' + mode + '_link'; 1764 if (extraClasses) 1765 link.className += ' ' + extraClasses; 1766 this._form.appendChild(link); 1767 this._controls[mode] = link; 1768 } 1769 }, 1770 createEditField: function() { 1771 var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); 1772 var fld; 1773 if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { 1774 fld = document.createElement('input'); 1775 fld.type = 'text'; 1776 var size = this.options.size || this.options.cols || 0; 1777 if (0 < size) fld.size = size; 1778 } else { 1779 fld = document.createElement('textarea'); 1780 fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); 1781 fld.cols = this.options.cols || 40; 1782 } 1783 fld.name = this.options.paramName; 1784 fld.value = text; // No HTML breaks conversion anymore 1785 fld.className = 'editor_field'; 1786 if (this.options.submitOnBlur) 1787 fld.onblur = this._boundSubmitHandler; 1788 this._controls.editor = fld; 1789 if (this.options.loadTextURL) 1790 this.loadExternalText(); 1791 this._form.appendChild(this._controls.editor); 1792 }, 1793 createForm: function() { 1794 var ipe = this; 1795 function addText(mode, condition) { 1796 var text = ipe.options['text' + mode + 'Controls']; 1797 if (!text || condition === false) return; 1798 ipe._form.appendChild(document.createTextNode(text)); 1799 }; 1800 this._form = $(document.createElement('form')); 1801 this._form.id = this.options.formId; 1802 this._form.addClassName(this.options.formClassName); 1803 this._form.onsubmit = this._boundSubmitHandler; 1804 this.createEditField(); 1805 if ('textarea' == this._controls.editor.tagName.toLowerCase()) 1806 this._form.appendChild(document.createElement('br')); 1807 if (this.options.onFormCustomization) 1808 this.options.onFormCustomization(this, this._form); 1809 addText('Before', this.options.okControl || this.options.cancelControl); 1810 this.createControl('ok', this._boundSubmitHandler); 1811 addText('Between', this.options.okControl && this.options.cancelControl); 1812 this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); 1813 addText('After', this.options.okControl || this.options.cancelControl); 1814 }, 1815 destroy: function() { 1816 if (this._oldInnerHTML) 1817 this.element.innerHTML = this._oldInnerHTML; 1818 this.leaveEditMode(); 1819 this.unregisterListeners(); 1820 }, 1821 enterEditMode: function(e) { 1822 if (this._saving || this._editing) return; 1823 this._editing = true; 1824 this.triggerCallback('onEnterEditMode'); 1825 if (this.options.externalControl) 1826 this.options.externalControl.hide(); 1827 this.element.hide(); 1828 this.createForm(); 1829 this.element.parentNode.insertBefore(this._form, this.element); 1830 if (!this.options.loadTextURL) 1831 this.postProcessEditField();
1832 if (e) Event.stop(e); 1833 }, 1834 enterHover: function(e) { 1835 if (this.options.hoverClassName) 1836 this.element.addClassName(this.options.hoverClassName); 1837 if (this._saving) return; 1838 this.triggerCallback('onEnterHover'); 1839 }, 1840 getText: function() { 1841 return this.element.innerHTML; 1842 }, 1843 handleAJAXFailure: function(transport) { 1844 this.triggerCallback('onFailure', transport); 1845 if (this._oldInnerHTML) { 1846 this.element.innerHTML = this._oldInnerHTML; 1847 this._oldInnerHTML = null; 1848 } 1849 }, 1850 handleFormCancellation: function(e) { 1851 this.wrapUp(); 1852 if (e) Event.stop(e); 1853 }, 1854 handleFormSubmission: function(e) { 1855 var form = this._form; 1856 var value = $F(this._controls.editor); 1857 this.prepareSubmission(); 1858 var params = this.options.callback(form, value) || ''; 1859 if (Object.isString(params)) 1860 params = params.toQueryParams(); 1861 params.editorId = this.element.id; 1862 if (this.options.htmlResponse) { 1863 var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); 1864 Object.extend(options, { 1865 parameters: params, 1866 onComplete: this._boundWrapperHandler, 1867 onFailure: this._boundFailureHandler 1868 }); 1869 new Ajax.Updater({ success: this.element }, this.url, options); 1870 } else { 1871 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 1872 Object.extend(options, { 1873 parameters: params, 1874 onComplete: this._boundWrapperHandler, 1875 onFailure: this._boundFailureHandler 1876 }); 1877 new Ajax.Request(this.url, options); 1878 } 1879 if (e) Event.stop(e); 1880 }, 1881 leaveEditMode: function() { 1882 this.element.removeClassName(this.options.savingClassName); 1883 this.removeForm(); 1884 this.leaveHover(); 1885 this.element.style.backgroundColor = this._originalBackground; 1886 this.element.show(); 1887 if (this.options.externalControl) 1888 this.options.externalControl.show(); 1889 this._saving = false; 1890 this._editing = false; 1891 this._oldInnerHTML = null; 1892 this.triggerCallback('onLeaveEditMode'); 1893 }, 1894 leaveHover: function(e) { 1895 if (this.options.hoverClassName) 1896 this.element.removeClassName(this.options.hoverClassName); 1897 if (this._saving) return; 1898 this.triggerCallback('onLeaveHover'); 1899 }, 1900 loadExternalText: function() { 1901 this._form.addClassName(this.options.loadingClassName); 1902 this._controls.editor.disabled = true; 1903 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 1904 Object.extend(options, { 1905 parameters: 'editorId=' + encodeURIComponent(this.element.id), 1906 onComplete: Prototype.emptyFunction, 1907 onSuccess: function(transport) { 1908 this._form.removeClassName(this.options.loadingClassName); 1909 var text = transport.responseText; 1910 if (this.options.stripLoadedTextTags) 1911 text = text.stripTags(); 1912 this._controls.editor.value = text; 1913 this._controls.editor.disabled = false; 1914 this.postProcessEditField(); 1915 }.bind(this), 1916 onFailure: this._boundFailureHandler 1917 }); 1918 new Ajax.Request(this.options.loadTextURL, options); 1919 }, 1920 postProcessEditField: function() { 1921 var fpc = this.options.fieldPostCreation; 1922 if (fpc) 1923 $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); 1924 }, 1925 prepareOptions: function() { 1926 this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); 1927 Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); 1928 [this._extraDefaultOptions].flatten().compact().each(function(defs) { 1929 Object.extend(this.options, defs); 1930 }.bind(this)); 1931 }, 1932 prepareSubmission: function() { 1933 this._saving = true; 1934 this.removeForm(); 1935 this.leaveHover(); 1936 this.showSaving(); 1937 }, 1938 registerListeners: function() { 1939 this._listeners = { }; 1940 var listener; 1941 $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { 1942 listener = this[pair.value].bind(this); 1943 this._listeners[pair.key] = listener; 1944 if (!this.options.externalControlOnly) 1945 this.element.observe(pair.key, listener); 1946 if (this.options.externalControl) 1947 this.options.externalControl.observe(pair.key, listener); 1948 }.bind(this)); 1949 }, 1950 removeForm: function() { 1951 if (!this._form) return; 1952 this._form.remove(); 1953 this._form = null; 1954 this._controls = { }; 1955 }, 1956 showSaving: function() { 1957 this._oldInnerHTML = this.element.innerHTML; 1958 this.element.innerHTML = this.options.savingText; 1959 this.element.addClassName(this.options.savingClassName); 1960 this.element.style.backgroundColor = this._originalBackgroun
1960d; 1961 this.element.show(); 1962 }, 1963 triggerCallback: function(cbName, arg) { 1964 if ('function' == typeof this.options[cbName]) { 1965 this.options[cbName](this, arg); 1966 } 1967 }, 1968 unregisterListeners: function() { 1969 $H(this._listeners).each(function(pair) { 1970 if (!this.options.externalControlOnly) 1971 this.element.stopObserving(pair.key, pair.value); 1972 if (this.options.externalControl) 1973 this.options.externalControl.stopObserving(pair.key, pair.value); 1974 }.bind(this)); 1975 }, 1976 wrapUp: function(transport) { 1977 this.leaveEditMode(); 1978 // Can't use triggerCallback due to backward compatibility: requires 1979 // binding + direct element 1980 this._boundComplete(transport, this.element); 1981 } 1982}); 1983 1984Object.extend(Ajax.InPlaceEditor.prototype, { 1985 dispose: Ajax.InPlaceEditor.prototype.destroy 1986}); 1987 1988Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { 1989 initialize: function($super, element, url, options) { 1990 this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; 1991 $super(element, url, options); 1992 }, 1993 1994 createEditField: function() { 1995 var list = document.createElement('select'); 1996 list.name = this.options.paramName; 1997 list.size = 1; 1998 this._controls.editor = list; 1999 this._collection = this.options.collection || []; 2000 if (this.options.loadCollectionURL) 2001 this.loadCollection(); 2002 else 2003 this.checkForExternalText(); 2004 this._form.appendChild(this._controls.editor); 2005 }, 2006 2007 loadCollection: function() { 2008 this._form.addClassName(this.options.loadingClassName); 2009 this.showLoadingText(this.options.loadingCollectionText); 2010 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 2011 Object.extend(options, { 2012 parameters: 'editorId=' + encodeURIComponent(this.element.id), 2013 onComplete: Prototype.emptyFunction, 2014 onSuccess: function(transport) { 2015 var js = transport.responseText.strip(); 2016 if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check 2017 throw 'Server returned an invalid collection representation.'; 2018 this._collection = eval(js); 2019 this.checkForExternalText(); 2020 }.bind(this), 2021 onFailure: this.onFailure 2022 }); 2023 new Ajax.Request(this.options.loadCollectionURL, options); 2024 }, 2025 2026 showLoadingText: function(text) { 2027 this._controls.editor.disabled = true; 2028 var tempOption = this._controls.editor.firstChild; 2029 if (!tempOption) { 2030 tempOption = document.createElement('option'); 2031 tempOption.value = ''; 2032 this._controls.editor.appendChild(tempOption); 2033 tempOption.selected = true; 2034 } 2035 tempOption.update((text || '').stripScripts().stripTags()); 2036 }, 2037 2038 checkForExternalText: function() { 2039 this._text = this.getText(); 2040 if (this.options.loadTextURL) 2041 this.loadExternalText(); 2042 else 2043 this.buildOptionList(); 2044 }, 2045 2046 loadExternalText: function() { 2047 this.showLoadingText(this.options.loadingText); 2048 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 2049 Object.extend(options, { 2050 parameters: 'editorId=' + encodeURIComponent(this.element.id), 2051 onComplete: Prototype.emptyFunction, 2052 onSuccess: function(transport) { 2053 this._text = transport.responseText.strip(); 2054 this.buildOptionList(); 2055 }.bind(this), 2056 onFailure: this.onFailure 2057 }); 2058 new Ajax.Request(this.options.loadTextURL, options); 2059 }, 2060 2061 buildOptionList: function() { 2062 this._form.removeClassName(this.options.loadingClassName); 2063 this._collection = this._collection.map(function(entry) { 2064 return 2 === entry.length ? entry : [entry, entry].flatten(); 2065 }); 2066 var marker = ('value' in this.options) ? this.options.value : this._text; 2067 var textFound = this._collection.any(function(entry) { 2068 return entry[0] == marker; 2069 }.bind(this)); 2070 this._controls.editor.update(''); 2071 var option; 2072 this._collection.each(function(entry, index) { 2073 option = document.createElement('option'); 2074 option.value = entry[0]; 2075 option.selected = textFound ? entry[0] == marker : 0 == index; 2076 option.appendChild(document.createTextNode(entry[1])); 2077 this._controls.editor.appendChild(option); 2078 }.bind(this)); 2079 this._controls.editor.disabled = false;
2080 Field.scrollFreeActivate(this._controls.editor); 2081 } 2082}); 2083 2084//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** 2085//**** This only exists for a while, in order to let **** 2086//**** users adapt to the new API. Read up on the new **** 2087//**** API and convert your code to it ASAP! **** 2088 2089Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { 2090 if (!options) return; 2091 function fallback(name, expr) { 2092 if (name in options || expr === undefined) return; 2093 options[name] = expr; 2094 }; 2095 fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : 2096 options.cancelLink == options.cancelButton == false ? false : undefined))); 2097 fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : 2098 options.okLink == options.okButton == false ? false : undefined))); 2099 fallback('highlightColor', options.highlightcolor); 2100 fallback('highlightEndColor', options.highlightendcolor); 2101}; 2102 2103Object.extend(Ajax.InPlaceEditor, { 2104 DefaultOptions: { 2105 ajaxOptions: { }, 2106 autoRows: 3, // Use when multi-line w/ rows == 1 2107 cancelControl: 'link', // 'link'|'button'|false 2108 cancelText: 'cancel', 2109 clickToEditText: 'Click to edit', 2110 externalControl: null, // id|elt 2111 externalControlOnly: false, 2112 fieldPostCreation: 'activate', // 'activate'|'focus'|false 2113 formClassName: 'inplaceeditor-form', 2114 formId: null, // id|elt 2115 highlightColor: '#ffff99', 2116 highlightEndColor: '#ffffff', 2117 hoverClassName: '', 2118 htmlResponse: true, 2119 loadingClassName: 'inplaceeditor-loading', 2120 loadingText: 'Loading...', 2121 okControl: 'button', // 'link'|'button'|false 2122 okText: 'ok', 2123 paramName: 'value', 2124 rows: 1, // If 1 and multi-line, uses autoRows 2125 savingClassName: 'inplaceeditor-saving', 2126 savingText: 'Saving...', 2127 size: 0, 2128 stripLoadedTextTags: false, 2129 submitOnBlur: false, 2130 textAfterControls: '', 2131 textBeforeControls: '', 2132 textBetweenControls: '' 2133 }, 2134 DefaultCallbacks: { 2135 callback: function(form) { 2136 return Form.serialize(form); 2137 }, 2138 onComplete: function(transport, element) { 2139 // For backward compatibility, this one is bound to the IPE, and passes 2140 // the element directly. It was too often customized, so we don't break it. 2141 new Effect.Highlight(element, { 2142 startcolor: this.options.highlightColor, keepBackgroundImage: true }); 2143 }, 2144 onEnterEditMode: null, 2145 onEnterHover: function(ipe) { 2146 ipe.element.style.backgroundColor = ipe.options.highlightColor; 2147 if (ipe._effect) 2148 ipe._effect.cancel(); 2149 }, 2150 onFailure: function(transport, ipe) { 2151 alert('Error communication with the server: ' + transport.responseText.stripTags()); 2152 }, 2153 onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. 2154 onLeaveEditMode: null, 2155 onLeaveHover: function(ipe) { 2156 ipe._effect = new Effect.Highlight(ipe.element, { 2157 startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, 2158 restorecolor: ipe._originalBackground, keepBackgroundImage: true 2159 }); 2160 } 2161 }, 2162 Listeners: { 2163 click: 'enterEditMode', 2164 keydown: 'checkForEscapeOrReturn', 2165 mouseover: 'enterHover', 2166 mouseout: 'leaveHover' 2167 } 2168}); 2169 2170Ajax.InPlaceCollectionEditor.DefaultOptions = { 2171 loadingCollectionText: 'Loading options...' 2172}; 2173 2174// Delayed observer, like Form.Element.Observer, 2175// but waits for delay after last key input 2176// Ideal for live-search fields 2177 2178Form.Element.DelayedObserver = Class.create({ 2179 initialize: function(element, delay, callback) { 2180 this.delay = delay || 0.5; 2181 this.element = $(element); 2182 this.callback = callback; 2183 this.timer = null; 2184 this.lastValue = $F(this.element); 2185 Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); 2186 }, 2187 delayedListener: function(event) { 2188 if(this.lastValue == $F(this.element)) return; 2189 if(this.timer) clearTimeout(this.timer); 2190 this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); 2191 this.lastValue = $F(this.element); 2192 },
2193 onTimerEvent: function() { 2194 this.timer = null; 2195 this.callback(this.element, $F(this.element)); 2196 } 2197}); 2198 2199if (!Control) var Control = { }; 2200 2201// options: 2202// axis: 'vertical', or 'horizontal' (default) 2203// 2204// callbacks: 2205// onChange(value) 2206// onSlide(value) 2207Control.Slider = Class.create({ 2208 initialize: function(handle, track, options) { 2209 var slider = this; 2210 2211 if (Object.isArray(handle)) { 2212 this.handles = handle.collect( function(e) { return $(e) }); 2213 } else { 2214 this.handles = [$(handle)]; 2215 } 2216 2217 this.track = $(track); 2218 this.options = options || { }; 2219 2220 this.axis = this.options.axis || 'horizontal'; 2221 this.increment = this.options.increment || 1; 2222 this.step = parseInt(this.options.step || '1'); 2223 this.range = this.options.range || $R(0,1); 2224 2225 this.value = 0; // assure backwards compat 2226 this.values = this.handles.map( function() { return 0 }); 2227 this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false; 2228 this.options.startSpan = $(this.options.startSpan || null); 2229 this.options.endSpan = $(this.options.endSpan || null); 2230 2231 this.restricted = this.options.restricted || false; 2232 2233 this.maximum = this.options.maximum || this.range.end; 2234 this.minimum = this.options.minimum || this.range.start; 2235 2236 // Will be used to align the handle onto the track, if necessary 2237 this.alignX = parseInt(this.options.alignX || '0'); 2238 this.alignY = parseInt(this.options.alignY || '0'); 2239 2240 this.trackLength = this.maximumOffset() - this.minimumOffset(); 2241 2242 this.handleLength = this.isVertical() ? 2243 (this.handles[0].offsetHeight != 0 ? 2244 this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 2245 (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 2246 this.handles[0].style.width.replace(/px$/,"")); 2247 2248 this.active = false; 2249 this.dragging = false; 2250 this.disabled = false; 2251 2252 if (this.options.disabled) this.setDisabled(); 2253 2254 // Allowed values array 2255 this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false; 2256 if (this.allowedValues) { 2257 this.minimum = this.allowedValues.min(); 2258 this.maximum = this.allowedValues.max(); 2259 } 2260 2261 this.eventMouseDown = this.startDrag.bindAsEventListener(this); 2262 this.eventMouseUp = this.endDrag.bindAsEventListener(this); 2263 this.eventMouseMove = this.update.bindAsEventListener(this); 2264 2265 // Initialize handles in reverse (make sure first handle is active) 2266 this.handles.each( function(h,i) { 2267 i = slider.handles.length-1-i; 2268 slider.setValue(parseFloat( 2269 (Object.isArray(slider.options.sliderValue) ? 2270 slider.options.sliderValue[i] : slider.options.sliderValue) || 2271 slider.range.start), i); 2272 h.makePositioned().observe("mousedown", slider.eventMouseDown); 2273 }); 2274 2275 this.track.observe("mousedown", this.eventMouseDown); 2276 document.observe("mouseup", this.eventMouseUp); 2277 document.observe("mousemove", this.eventMouseMove); 2278 2279 this.initialized = true; 2280 }, 2281 dispose: function() { 2282 var slider = this; 2283 Event.stopObserving(this.track, "mousedown", this.eventMouseDown); 2284 Event.stopObserving(document, "mouseup", this.eventMouseUp); 2285 Event.stopObserving(document, "mousemove", this.eventMouseMove); 2286 this.handles.each( function(h) { 2287 Event.stopObserving(h, "mousedown", slider.eventMouseDown); 2288 }); 2289 }, 2290 setDisabled: function(){ 2291 this.disabled = true; 2292 }, 2293 setEnabled: function(){ 2294 this.disabled = false; 2295 }, 2296 getNearestValue: function(value){ 2297 if (this.allowedValues){ 2298 if (value >= this.allowedValues.max()) return(this.allowedValues.max()); 2299 if (value <= this.allowedValues.min()) return(this.allowedValues.min()); 2300 2301 var offset = Math.abs(this.allowedValues[0] - value); 2302 var newValue = this.allowedValues[0]; 2303 this.allowedValues.each( function(v) { 2304 var currentOffset = Math.abs(v - value); 2305 if (currentOffset <= offset){ 2306 newValue = v; 2307 offset = currentOffset; 2308 } 2309 }); 2310 return newValue; 2311 } 2312 if (value > this.range.end) return this.range.end; 2313 if (value < this.range.start) return this.range.start; 2314 return value; 2315 }, 2316 setValue: function(sliderValue, handleIdx){ 2317 if (!this.active) { 2318 this.activeHandleIdx = handleIdx || 0; 2319 this.activeHandle = this.handles[this.activeHandleIdx]; 2320 this.updateStyles(); 2321 } 2322 handleIdx = handleIdx || this.activeHandleIdx || 0; 2323 if (this.initialized && this.restricted) { 2324 if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1])) 2325 sliderValue = this.values[handleIdx-1]; 2326 if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1])) 2327 sliderValue = this.values[handleIdx+1]; 2328 } 2329 sliderValue = this.getNearestValue(sliderValue); 2330 this.values[handleIdx] = sliderValue; 2331 this.value = this.values[0]; // assure backwards compat 2332 2333 this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 2334 this.translateToPx(sliderValue); 2335 2336 this.drawSpans(); 2337 if (!this.dragging || !this.event) this.updateFinished(); 2338 }, 2339 setValueBy: function(delta, handleIdx) { 2340 this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 2341 handleIdx || this.activeHandleIdx || 0); 2342 }, 2343 translateToPx: function(value) { 2344 return Math.round( 2345 ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 2346 (value - this.range.start)) + "px"; 2347 }, 2348 translateToValue: function(offset) { 2349 return ((offset/(this.trackLength-this.handleLength) * 2350 (this.range.end-this.range.start)) + this.range.start); 2351 }, 2352 getRange: function(range) { 2353 var v = this.values.sortBy(Prototype.K); 2354 range = range || 0; 2355 return $R(v[range],v[range+1]); 2356 }, 2357 minimumOffset: function(){ 2358 return(this.isVertical() ? this.alignY : this.alignX); 2359 }, 2360 maximumOffset: function(){ 2361 return(this.isVertical() ? 2362 (this.track.offsetHeight != 0 ? this.track.offsetHeight : 2363 this.track.style.height.replace(/px$/,"")) - this.alignY : 2364 (this.track.offsetWidth != 0 ? this.track.offsetWidth : 2365 this.track.style.width.replace(/px$/,"")) - this.alignX); 2366 }, 2367 isVertical: function(){ 2368 return (this.axis == 'vertical'); 2369 }, 2370 drawSpans: function() { 2371 var slider = this; 2372 if (this.spans) 2373 $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) }); 2374 if (this.options.startSpan) 2375 this.setSpan(this.options.startSpan, 2376 $R(0, this.values.length>1 ? this.getRange(0).min() : this.value )); 2377 if (this.options.endSpan) 2378 this.setSpan(this.options.endSpan, 2379 $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum)); 2380 }, 2381 setSpan: function(span, range) { 2382 if (this.isVertical()) { 2383 span.style.top = this.translateToPx(range.start); 2384 span.style.height = this.translateToPx(range.end - range.start + this.range.start); 2385 } else { 2386 span.style.left = this.translateToPx(range.start); 2387 span.style.width = this.translateToPx(range.end - range.start + this.range.start); 2388 } 2389 }, 2390 updateStyles: function() { 2391 this.handles.each( function(h){ Element.removeClassName(h, 'selected') }); 2392 Element.addClassName(this.activeHandle, 'selected'); 2393 }, 2394 startDrag: function(event) { 2395 if (Event.isLeftClick(event)) { 2396 if (!this.disabled){ 2397 this.active = true; 2398 2399 var handle = Event.element(event); 2400 var pointer = [Event.pointerX(event), Event.pointerY(event)]; 2401 var track = handle; 2402 if (track==this.track) { 2403 var offsets = Position.cumulativeOffset(this.track); 2404 this.event = event; 2405 this.setValue(this.translateToValue( 2406 (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2) 2407 )); 2408 var offsets = Position.cumulativeOffset(this.activeHandle); 2409 this.offsetX = (pointer[0] - offsets[0]); 2410 this.offsetY = (pointer[1] - offsets[1]); 2411 } else { 2412 // find the handle (prevents issues with Safari) 2413 while((this.handles.indexOf(handle) == -1) && handle.parentNode) 2414 handle = handle.parentNode; 2415 2416 if (this.handles.indexOf(handle)!=-1) { 2417 this.activeHandle = handle; 2418 this.activeHandleIdx = this.handles.indexOf(this.activeHandle); 2419 this.updateStyles(); 2420 2421 var offsets = Position.cumulativeOffset(this.activeHandle); 2422 this.offsetX = (pointer[0] - offsets[0]); 2423 this.offsetY = (pointer[1] - offsets[1]); 2424 } 2425 } 2426 } 2427 Event.stop(event); 2428 } 2429 }, 2430 update: function(event) { 2431 if (this.active) { 2432 if (!this.dragging) this.dragging = true; 2433 this.draw(event); 2434 if (Prototype.Browser.WebKit) window.scrollBy(0,0); 2435 Event.stop(event); 2436 } 2437 }, 2438 draw: function(event) { 2439 var pointer = [Event.pointerX(event), Event.pointerY(event)]; 2440 var offsets = Position.cumulativeOffset(this.track); 2441 pointer[0] -= this.offsetX + offsets[0]; 2442 pointer[1] -= this.offsetY + offsets[1]; 2443 this.event = event; 2444 this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] )); 2445 if (this.initialized && this.options.onSlide) 2446 this.options.onSlide(this.values.length>1 ? this.values : this.value, this); 2447 }, 2448 endDrag: function(event) { 2449 if (this.active && this.dragging) { 2450 this.finishDrag(event, true); 2451 Event.stop(event); 2452 } 2453 this.active = false;
2454 this.dragging = false; 2455 }, 2456 finishDrag: function(event, success) { 2457 this.active = false; 2458 this.dragging = false; 2459 this.updateFinished(); 2460 }, 2461 updateFinished: function() { 2462 if (this.initialized && this.options.onChange) 2463 this.options.onChange(this.values.length>1 ? this.values : this.value, this); 2464 this.event = null; 2465 } 2466}); 2467 2468if(Object.isUndefined(Effect)) 2469 throw("dragdrop.js requires including script.aculo.us' effects.js library"); 2470 2471var Droppables = { 2472 drops: [], 2473 2474 remove: function(element) { 2475 this.drops = this.drops.reject(function(d) { return d.element==$(element) }); 2476 }, 2477 2478 add: function(element) { 2479 element = $(element); 2480 var options = Object.extend({ 2481 greedy: true, 2482 hoverclass: null, 2483 tree: false 2484 }, arguments[1] || { }); 2485 2486 // cache containers 2487 if(options.containment) { 2488 options._containers = []; 2489 var containment = options.containment; 2490 if(Object.isArray(containment)) { 2491 containment.each( function(c) { options._containers.push($(c)) }); 2492 } else { 2493 options._containers.push($(containment)); 2494 } 2495 } 2496 2497 if(options.accept) options.accept = [options.accept].flatten(); 2498 2499 Element.makePositioned(element); // fix IE 2500 options.element = element; 2501 2502 this.drops.push(options); 2503 }, 2504 2505 findDeepestChild: function(drops) { 2506 deepest = drops[0]; 2507 2508 for (i = 1; i < drops.length; ++i) 2509 if (Element.isParent(drops[i].element, deepest.element)) 2510 deepest = drops[i]; 2511 2512 return deepest; 2513 }, 2514 2515 isContained: function(element, drop) { 2516 var containmentNode; 2517 if(drop.tree) { 2518 containmentNode = element.treeNode; 2519 } else { 2520 containmentNode = element.parentNode; 2521 } 2522 return drop._containers.detect(function(c) { return containmentNode == c }); 2523 }, 2524 2525 isAffected: function(point, element, drop) { 2526 return ( 2527 (drop.element!=element) && 2528 ((!drop._containers) || 2529 this.isContained(element, drop)) && 2530 ((!drop.accept) || 2531 (Element.classNames(element).detect( 2532 function(v) { return drop.accept.include(v) } ) )) && 2533 Position.within(drop.element, point[0], point[1]) ); 2534 }, 2535 2536 deactivate: function(drop) { 2537 if(drop.hoverclass) 2538 Element.removeClassName(drop.element, drop.hoverclass); 2539 this.last_active = null; 2540 }, 2541 2542 activate: function(drop) { 2543 if(drop.hoverclass) 2544 Element.addClassName(drop.element, drop.hoverclass); 2545 this.last_active = drop; 2546 }, 2547 2548 show: function(point, element) { 2549 if(!this.drops.length) return; 2550 var drop, affected = []; 2551 2552 this.drops.each( function(drop) { 2553 if(Droppables.isAffected(point, element, drop)) 2554 affected.push(drop); 2555 }); 2556 2557 if(affected.length>0) 2558 drop = Droppables.findDeepestChild(affected); 2559 2560 if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); 2561 if (drop) { 2562 Position.within(drop.element, point[0], point[1]); 2563 if(drop.onHover) 2564 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 2565 2566 if (drop != this.last_active) Droppables.activate(drop); 2567 } 2568 }, 2569 2570 fire: function(event, element) { 2571 if(!this.last_active) return; 2572 Position.prepare(); 2573 2574 if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) 2575 if (this.last_active.onDrop) { 2576 this.last_active.onDrop(element, this.last_active.element, event); 2577 return true; 2578 } 2579 }, 2580 2581 reset: function() { 2582 if(this.last_active) 2583 this.deactivate(this.last_active); 2584 } 2585} 2586 2587var Draggables = { 2588 drags: [], 2589 observers: [], 2590 2591 register: function(draggable) { 2592 if(this.drags.length == 0) { 2593 this.eventMouseUp = this.endDrag.bindAsEventListener(this); 2594 this.eventMouseMove = this.updateDrag.bindAsEventListener(this); 2595 this.eventKeypress = this.keyPress.bindAsEventListener(this); 2596 2597 Event.observe(document, "mouseup", this.eventMouseUp); 2598 Event.observe(document, "mousemove", this.eventMouseMove); 2599 Event.observe(document, "keypress", this.eventKeypress); 2600 } 2601 this.drags.push(draggable); 2602 }, 2603
2604 unregister: function(draggable) { 2605 this.drags = this.drags.reject(function(d) { return d==draggable }); 2606 if(this.drags.length == 0) { 2607 Event.stopObserving(document, "mouseup", this.eventMouseUp); 2608 Event.stopObserving(document, "mousemove", this.eventMouseMove); 2609 Event.stopObserving(document, "keypress", this.eventKeypress); 2610 } 2611 }, 2612 2613 activate: function(draggable) { 2614 if(draggable.options.delay) { 2615 this._timeout = setTimeout(function() { 2616 Draggables._timeout = null; 2617 window.focus(); 2618 Draggables.activeDraggable = draggable; 2619 }.bind(this), draggable.options.delay); 2620 } else { 2621 window.focus(); // allows keypress events if window isn't currently focused, fails for Safari 2622 this.activeDraggable = draggable; 2623 } 2624 }, 2625 2626 deactivate: function() { 2627 this.activeDraggable = null; 2628 }, 2629 2630 updateDrag: function(event) { 2631 if(!this.activeDraggable) return; 2632 var pointer = [Event.pointerX(event), Event.pointerY(event)]; 2633 // Mozilla-based browsers fire successive mousemove events with 2634 // the same coordinates, prevent needless redrawing (moz bug?) 2635 if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; 2636 this._lastPointer = pointer; 2637 2638 this.activeDraggable.updateDrag(event, pointer); 2639 }, 2640
2641 endDrag: function(event) { 2642 if(this._timeout) { 2643 clearTimeout(this._timeout); 2644 this._timeout = null; 2645 } 2646 if(!this.activeDraggable) return; 2647 this._lastPointer = null; 2648 this.activeDraggable.endDrag(event); 2649 this.activeDraggable = null; 2650 }, 2651 2652 keyPress: function(event) { 2653 if(this.activeDraggable) 2654 this.activeDraggable.keyPress(event); 2655 }, 2656 2657 addObserver: function(observer) { 2658 this.observers.push(observer); 2659 this._cacheObserverCallbacks(); 2660 }, 2661 2662 removeObserver: function(element) { // element instead of observer fixes mem leaks 2663 this.observers = this.observers.reject( function(o) { return o.element==element }); 2664 this._cacheObserverCallbacks(); 2665 }, 2666 2667 notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' 2668 if(this[eventName+'Count'] > 0) 2669 this.observers.each( function(o) { 2670 if(o[eventName]) o[eventName](eventName, draggable, event); 2671 }); 2672 if(draggable.options[eventName]) draggable.options[eventName](draggable, event); 2673 }, 2674 2675 _cacheObserverCallbacks: function() { 2676 ['onStart','onEnd','onDrag'].each( function(eventName) { 2677 Draggables[eventName+'Count'] = Draggables.observers.select( 2678 function(o) { return o[eventName]; } 2679 ).length; 2680 }); 2681 } 2682} 2683 2684/*--------------------------------------------------------------------------*/ 2685 2686var Draggable = Class.create({ 2687 initialize: function(element) { 2688 var defaults = { 2689 handle: false, 2690 reverteffect: function(element, top_offset, left_offset) { 2691 var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; 2692 new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, 2693 queue: {scope:'_draggable', position:'end'} 2694 }); 2695 }, 2696 endeffect: function(element) { 2697 var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; 2698 new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 2699 queue: {scope:'_draggable', position:'end'}, 2700 afterFinish: function(){ 2701 Draggable._dragging[element] = false 2702 } 2703 }); 2704 }, 2705 zindex: 1000, 2706 revert: false, 2707 quiet: false, 2708 scroll: false, 2709 scrollSensitivity: 20, 2710 scrollSpeed: 15, 2711 snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } 2712 delay: 0 2713 }; 2714 2715 if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) 2716 Object.extend(defaults, { 2717 starteffect: function(element) { 2718 element._opacity = Element.getOpacity(element); 2719 Draggable._dragging[element] = true; 2720 new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 2721 } 2722 }); 2723 2724 var options = Object.extend(defaults, arguments[1] || { }); 2725 2726 this.element = $(element); 2727 2728 if(options.handle && Object.isString(options.handle)) 2729 this.handle = this.element.down('.'+options.handle, 0); 2730 2731 if(!this.handle) this.handle = $(options.handle); 2732 if(!this.handle) this.handle = this.element; 2733 2734 if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { 2735 options.scroll = $(options.scroll); 2736 this._isScrollChild = Element.childOf(this.element, options.scroll); 2737 } 2738 2739 Element.makePositioned(this.element); // fix IE 2740 2741 this.options = options; 2742 this.dragging = false; 2743 2744 this.eventMouseDown = this.initDrag.bindAsEventListener(this); 2745 Event.observe(this.handle, "mousedown", this.eventMouseDown); 2746 2747 Draggables.register(this); 2748 }, 2749 2750 destroy: function() { 2751 Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); 2752 Draggables.unregister(this); 2753 }, 2754 2755 currentDelta: function() { 2756 return([ 2757 parseInt(Element.getStyle(this.element,'left') || '0'), 2758 parseInt(Element.getStyle(this.element,'top') || '0')]); 2759 }, 2760 2761 initDrag: function(event) { 2762 if(!Object.isUndefined(Draggable._dragging[this.element]) && 2763 Draggable._dragging[this.element]) return; 2764 if(Event.isLeftClick(event)) { 2765 // abort on form elements, fixes a Firefox issue 2766 var src = Event.element(event); 2767 if((tag_name = src.tagName.toUpperCase()) && ( 2768 tag_name=='INPUT' || 2769 tag_name=='SELECT' || 2770 tag_name=='OPTION' || 2771 tag_name=='BUTTON' || 2772 tag_name=='TEXTAREA')) return; 2773 2774 var pointer = [Event.pointerX(event), Event.pointerY(event)]; 2775 var pos = Position.cumulativeOffset(this.element); 2776 this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); 2777 2778 Draggables.activate(this); 2779 Event.stop(event); 2780 } 2781 }, 2782 2783 startDrag: function(event) { 2784 this.dragging = true; 2785 if(!this.delta) 2786 this.delta = this.currentDelta(); 2787 2788 if(this.options.zindex) { 2789 this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); 2790 this.element.style.zIndex = this.options.zindex; 2791 } 2792 2793 if(this.options.ghosting) { 2794 this._clone = this.element.cloneNode(true); 2795 this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); 2796 if (!this.element._originallyAbsolute) 2797 Position.absolutize(this.element); 2798 this.element.parentNode.insertBefore(this._clone, this.element); 2799 } 2800 2801 if(this.options.scroll) { 2802 if (this.options.scroll == window) { 2803 var where = this._getWindowScroll(this.options.scroll); 2804 this.originalScrollLeft = where.left; 2805 this.originalScrollTop = where.top; 2806 } else {
2807 this.originalScrollLeft = this.options.scroll.scrollLeft; 2808 this.originalScrollTop = this.options.scroll.scrollTop; 2809 } 2810 } 2811 2812 Draggables.notify('onStart', this, event); 2813 2814 if(this.options.starteffect) this.options.starteffect(this.element); 2815 }, 2816 2817 updateDrag: function(event, pointer) { 2818 if(!this.dragging) this.startDrag(event); 2819 2820 if(!this.options.quiet){ 2821 Position.prepare(); 2822 Droppables.show(pointer, this.element); 2823 } 2824 2825 Draggables.notify('onDrag', this, event); 2826 2827 this.draw(pointer); 2828 if(this.options.change) this.options.change(this); 2829 2830 if(this.options.scroll) { 2831 this.stopScrolling(); 2832 2833 var p; 2834 if (this.options.scroll == window) { 2835 with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } 2836 } else { 2837 p = Position.page(this.options.scroll); 2838 p[0] += this.options.scroll.scrollLeft + Position.deltaX; 2839 p[1] += this.options.scroll.scrollTop + Position.deltaY; 2840 p.push(p[0]+this.options.scroll.offsetWidth); 2841 p.push(p[1]+this.options.scroll.offsetHeight); 2842 } 2843 var speed = [0,0]; 2844 if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); 2845 if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); 2846 if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); 2847 if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); 2848 this.startScrolling(speed); 2849 } 2850 2851 // fix AppleWebKit rendering 2852 if(Prototype.Browser.WebKit) window.scrollBy(0,0); 2853 2854 Event.stop(event); 2855 }, 2856 2857 finishDrag: function(event, success) { 2858 this.dragging = false; 2859 2860 if(this.options.quiet){ 2861 Position.prepare(); 2862 var pointer = [Event.pointerX(event), Event.pointerY(event)]; 2863 Droppables.show(pointer, this.element); 2864 } 2865 2866 if(this.options.ghosting) { 2867 if (!this.element._originallyAbsolute) 2868 Position.relativize(this.element); 2869 delete this.element._originallyAbsolute; 2870 Element.remove(this._clone); 2871 this._clone = null; 2872 } 2873 2874 var dropped = false; 2875 if(success) { 2876 dropped = Droppables.fire(event, this.element); 2877 if (!dropped) dropped = false; 2878 } 2879 if(dropped && this.options.onDropped) this.options.onDropped(this.element); 2880 Draggables.notify('onEnd', this, event); 2881 2882 var revert = this.options.revert; 2883 if(revert && Object.isFunction(revert)) revert = revert(this.element); 2884 2885 var d = this.currentDelta(); 2886 if(revert && this.options.reverteffect) { 2887 if (dropped == 0 || revert != 'failure') 2888 this.options.reverteffect(this.element, 2889 d[1]-this.delta[1], d[0]-this.delta[0]); 2890 } else { 2891 this.delta = d; 2892 } 2893 2894 if(this.options.zindex) 2895 this.element.style.zIndex = this.originalZ; 2896 2897 if(this.options.endeffect) 2898 this.options.endeffect(this.element); 2899 2900 Draggables.deactivate(this); 2901 Droppables.reset(); 2902 }, 2903 2904 keyPress: function(event) { 2905 if(event.keyCode!=Event.KEY_ESC) return; 2906 this.finishDrag(event, false); 2907 Event.stop(event); 2908 }, 2909 2910 endDrag: function(event) { 2911 if(!this.dragging) return; 2912 this.stopScrolling(); 2913 this.finishDrag(event, true); 2914 Event.stop(event); 2915 }, 2916 2917 draw: function(point) { 2918 var pos = Position.cumulativeOffset(this.element); 2919 if(this.options.ghosting) { 2920 var r = Position.realOffset(this.element); 2921 pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; 2922 } 2923 2924 var d = this.currentDelta(); 2925 pos[0] -= d[0]; pos[1] -= d[1]; 2926 2927 if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { 2928 pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; 2929 pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; 2930 } 2931 2932 var p = [0,1].map(function(i){ 2933 return (point[i]-pos[i]-this.offset[i]) 2934 }.bind(this)); 2935 2936 if(this.options.snap) { 2937 if(Object.isFunction(this.options.snap)) { 2938 p = this.options.snap(p[0],p[1],this); 2939 } else { 2940 if(Object.isArray(this.options.snap)) { 2941 p = p.map( function(v, i) { 2942 return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)) 2943 } else { 2944 p = p.map( function(v) { 2945 return (v/this.options.snap).round()*this.options.snap }.bind(this)) 2946 } 2947 }} 2948 2949 var style = this.element.style; 2950 if((!this.options.constraint) || (this.options.constraint=='horizontal')) 2951 style.left = p[0] + "px"; 2952 if((!this.options.constraint) || (this.options.constraint=='vertical')) 2953 style.top = p[1] + "px"; 2954 2955 if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering 2956 }, 2957
2958 stopScrolling: function() { 2959 if(this.scrollInterval) { 2960 clearInterval(this.scrollInterval); 2961 this.scrollInterval = null; 2962 Draggables._lastScrollPointer = null; 2963 } 2964 }, 2965 2966 startScrolling: function(speed) { 2967 if(!(speed[0] || speed[1])) return; 2968 this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; 2969 this.lastScrolled = new Date(); 2970 this.scrollInterval = setInterval(this.scroll.bind(this), 10); 2971 }, 2972 2973 scroll: function() { 2974 var current = new Date(); 2975 var delta = current - this.lastScrolled; 2976 this.lastScrolled = current; 2977 if(this.options.scroll == window) { 2978 with (this._getWindowScroll(this.options.scroll)) { 2979 if (this.scrollSpeed[0] || this.scrollSpeed[1]) { 2980 var d = delta / 1000; 2981 this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); 2982 } 2983 } 2984 } else { 2985 this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; 2986 this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; 2987 } 2988 2989 Position.prepare(); 2990 Droppables.show(Draggables._lastPointer, this.element); 2991 Draggables.notify('onDrag', this); 2992 if (this._isScrollChild) { 2993 Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); 2994 Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; 2995 Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; 2996 if (Draggables._lastScrollPointer[0] < 0) 2997 Draggables._lastScrollPointer[0] = 0; 2998 if (Draggables._lastScrollPointer[1] < 0) 2999 Draggables._lastScrollPointer[1] = 0; 3000 this.draw(Draggables._lastScrollPointer); 3001 } 3002 3003 if(this.options.change) this.options.change(this); 3004 }, 3005 3006 _getWindowScroll: function(w) { 3007 var T, L, W, H; 3008 with (w.document) { 3009 if (w.document.documentElement && documentElement.scrollTop) { 3010 T = documentElement.scrollTop; 3011 L = documentElement.scrollLeft; 3012 } else if (w.document.body) { 3013 T = body.scrollTop; 3014 L = body.scrollLeft; 3015 } 3016 if (w.innerWidth) { 3017 W = w.innerWidth; 3018 H = w.innerHeight; 3019 } else if (w.document.documentElement && documentElement.clientWidth) { 3020 W = documentElement.clientWidth; 3021 H = documentElement.clientHeight; 3022 } else { 3023 W = body.offsetWidth; 3024 H = body.offsetHeight 3025 } 3026 } 3027 return { top: T, left: L, width: W, height: H }; 3028 } 3029}); 3030 3031Draggable._dragging = { }; 3032 3033/*--------------------------------------------------------------------------*/ 3034 3035var SortableObserver = Class.create({ 3036 initialize: function(element, observer) { 3037 this.element = $(element); 3038 this.observer = observer; 3039 this.lastValue = Sortable.serialize(this.element); 3040 }, 3041 3042 onStart: function() { 3043 this.lastValue = Sortable.serialize(this.element); 3044 }, 3045 3046 onEnd: function() { 3047 Sortable.unmark(); 3048 if(this.lastValue != Sortable.serialize(this.element)) 3049 this.observer(this.element) 3050 } 3051}); 3052 3053var Sortable = { 3054 SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, 3055 3056 sortables: { }, 3057 3058 _findRootElement: function(element) { 3059 while (element.tagName.toUpperCase() != "BODY") { 3060 if(element.id && Sortable.sortables[element.id]) return element; 3061 element = element.parentNode; 3062 } 3063 }, 3064 3065 options: function(element) { 3066 element = Sortable._findRootElement($(element)); 3067 if(!element) return; 3068 return Sortable.sortables[element.id]; 3069 }, 3070 3071 destroy: function(element){ 3072 var s = Sortable.options(element); 3073 3074 if(s) { 3075 Draggables.removeObserver(s.element); 3076 s.droppables.each(function(d){ Droppables.remove(d) }); 3077 s.draggables.invoke('destroy'); 3078 3079 delete Sortable.sortables[s.element.id]; 3080 } 3081 }, 3082 3083 create: function(element) { 3084 element = $(element); 3085 var options = Object.extend({ 3086 element: element, 3087 tag: 'li', // assumes li children, override with tag: 'tagname' 3088 dropOnEmpty: false, 3089 tree: false, 3090 treeTag: 'ul', 3091 overlap: 'vertical', // one of 'vertical', 'horizontal' 3092 constraint: 'vertical', // one of 'vertical', 'horizontal', false 3093 containment: element, // also takes array of elements (or id's); or false 3094 handle: false, // or a CSS class 3095 only: false, 3096 delay: 0, 3097 hoverclass: null, 3098 ghosting: false, 3099 quiet: false, 3100 scroll: false, 3101 scrollSensitivity: 20, 3102 scrollSpeed: 15, 3103 format: this.SERIALIZE_RULE, 3104 3105 // these take arrays of elements or ids and can be 3106 // used for better initialization performance 3107 elements: false, 3108 handles: false, 3109 3110 onChange: Prototype.emptyFunction, 3111 onUpdate: Prototype.emptyFunction 3112 }, arguments[1] || { }); 3113 3114 // clear any old sortable with same element 3115 this.destroy(element); 3116 3117 // build options for the draggables 3118 var options_for_draggable = { 3119 revert: true, 3120 quiet: options.quiet, 3121 scroll: options.scroll, 3122 scrollSpeed: options.scrollSpeed,
3123 scrollSensitivity: options.scrollSensitivity, 3124 delay: options.delay, 3125 ghosting: options.ghosting, 3126 constraint: options.constraint, 3127 handle: options.handle }; 3128 3129 if(options.starteffect) 3130 options_for_draggable.starteffect = options.starteffect; 3131 3132 if(options.reverteffect) 3133 options_for_draggable.reverteffect = options.reverteffect; 3134 else 3135 if(options.ghosting) options_for_draggable.reverteffect = function(element) { 3136 element.style.top = 0; 3137 element.style.left = 0; 3138 }; 3139 3140 if(options.endeffect) 3141 options_for_draggable.endeffect = options.endeffect; 3142 3143 if(options.zindex) 3144 options_for_draggable.zindex = options.zindex; 3145 3146 // build options for the droppables 3147 var options_for_droppable = { 3148 overlap: options.overlap, 3149 containment: options.containment, 3150 tree: options.tree, 3151 hoverclass: options.hoverclass, 3152 onHover: Sortable.onHover 3153 } 3154 3155 var options_for_tree = { 3156 onHover: Sortable.onEmptyHover, 3157 overlap: options.overlap, 3158 containment: options.containment, 3159 hoverclass: options.hoverclass 3160 } 3161 3162 // fix for gecko engine 3163 Element.cleanWhitespace(element); 3164 3165 options.draggables = []; 3166 options.droppables = []; 3167 3168 // drop on empty handling 3169 if(options.dropOnEmpty || options.tree) { 3170 Droppables.add(element, options_for_tree); 3171 options.droppables.push(element); 3172 } 3173 3174 (options.elements || this.findElements(element, options) || []).each( function(e,i) { 3175 var handle = options.handles ? $(options.handles[i]) : 3176 (options.handle ? $(e).select('.' + options.handle)[0] : e); 3177 options.draggables.push( 3178 new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); 3179 Droppables.add(e, options_for_droppable); 3180 if(options.tree) e.treeNode = element; 3181 options.droppables.push(e); 3182 }); 3183 3184 if(options.tree) { 3185 (Sortable.findTreeElements(element, options) || []).each( function(e) { 3186 Droppables.add(e, options_for_tree); 3187 e.treeNode = element; 3188 options.droppables.push(e); 3189 }); 3190 } 3191 3192 // keep reference 3193 this.sortables[element.id] = options; 3194 3195 // for onupdate 3196 Draggables.addObserver(new SortableObserver(element, options.onUpdate)); 3197 3198 }, 3199 3200 // return all suitable-for-sortable elements in a guaranteed order 3201 findElements: function(element, options) { 3202 return Element.findChildren( 3203 element, options.only, options.tree ? true : false, options.tag); 3204 }, 3205 3206 findTreeElements: function(element, options) { 3207 return Element.findChildren( 3208 element, options.only, options.tree ? true : false, options.treeTag); 3209 }, 3210 3211 onHover: function(element, dropon, overlap) { 3212 if(Element.isParent(dropon, element)) return; 3213 3214 if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { 3215 return; 3216 } else if(overlap>0.5) { 3217 Sortable.mark(dropon, 'before'); 3218 if(dropon.previousSibling != element) { 3219 var oldParentNode = element.parentNode; 3220 element.style.visibility = "hidden"; // fix gecko rendering 3221 dropon.parentNode.insertBefore(element, dropon); 3222 if(dropon.parentNode!=oldParentNode) 3223 Sortable.options(oldParentNode).onChange(element); 3224 Sortable.options(dropon.parentNode).onChange(element); 3225 } 3226 } else { 3227 Sortable.mark(dropon, 'after'); 3228 var nextElement = dropon.nextSibling || null; 3229 if(nextElement != element) { 3230 var oldParentNode = element.parentNode; 3231 element.style.visibility = "hidden"; // fix gecko rendering 3232 dropon.parentNode.insertBefore(element, nextElement); 3233 if(dropon.parentNode!=oldParentNode) 3234 Sortable.options(oldParentNode).onChange(element); 3235 Sortable.options(dropon.parentNode).onChange(element); 3236 } 3237 } 3238 }, 3239 3240 onEmptyHover: function(element, dropon, overlap) { 3241 var oldParentNode = element.parentNode; 3242 var droponOptions = Sortable.options(dropon); 3243 3244 if(!Element.isParent(dropon, element)) { 3245 var index; 3246 3247 var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); 3248 var child = null; 3249 3250 if(children) { 3251 var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); 3252 3253 for (index = 0; index < children.length; index += 1) { 3254 if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { 3255 offset -= Element.offsetSize (children[index], droponOptions.overlap); 3256 } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { 3257 child = index + 1 < children.length ? children[index + 1] : null; 3258 break; 3259 } else { 3260 child = children[index]; 3261 break; 3262 } 3263 } 3264 } 3265 3266 dropon.insertBefore(element, child); 3267
3268 Sortable.options(oldParentNode).onChange(element); 3269 droponOptions.onChange(element); 3270 } 3271 }, 3272 3273 unmark: function() { 3274 if(Sortable._marker) Sortable._marker.hide(); 3275 }, 3276 3277 mark: function(dropon, position) { 3278 // mark on ghosting only 3279 var sortable = Sortable.options(dropon.parentNode); 3280 if(sortable && !sortable.ghosting) return; 3281 3282 if(!Sortable._marker) { 3283 Sortable._marker = 3284 ($('dropmarker') || Element.extend(document.createElement('DIV'))). 3285 hide().addClassName('dropmarker').setStyle({position:'absolute'}); 3286 document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); 3287 } 3288 var offsets = Position.cumulativeOffset(dropon); 3289 Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); 3290 3291 if(position=='after') 3292 if(sortable.overlap == 'horizontal') 3293 Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); 3294 else 3295 Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); 3296 3297 Sortable._marker.show(); 3298 }, 3299 3300 _tree: function(element, options, parent) { 3301 var children = Sortable.findElements(element, options) || []; 3302 3303 for (var i = 0; i < children.length; ++i) { 3304 var match = children[i].id.match(options.format); 3305 3306 if (!match) continue; 3307 3308 var child = { 3309 id: encodeURIComponent(match ? match[1] : null), 3310 element: element, 3311 parent: parent, 3312 children: [], 3313 position: parent.children.length, 3314 container: $(children[i]).down(options.treeTag) 3315 } 3316 3317 /* Get the element containing the children and recurse over it */ 3318 if (child.container) 3319 this._tree(child.container, options, child) 3320 3321 parent.children.push (child); 3322 } 3323 3324 return parent; 3325 }, 3326 3327 tree: function(element) { 3328 element = $(element); 3329 var sortableOptions = this.options(element); 3330 var options = Object.extend({ 3331 tag: sortableOptions.tag, 3332 treeTag: sortableOptions.treeTag, 3333 only: sortableOptions.only, 3334 name: element.id, 3335 format: sortableOptions.format 3336 }, arguments[1] || { }); 3337 3338 var root = { 3339 id: null, 3340 parent: null, 3341 children: [], 3342 container: element, 3343 position: 0 3344 } 3345 3346 return Sortable._tree(element, options, root); 3347 }, 3348 3349 /* Construct a [i] index for a particular node */ 3350 _constructIndex: function(node) { 3351 var index = ''; 3352 do { 3353 if (node.id) index = '[' + node.position + ']' + index; 3354 } while ((node = node.parent) != null); 3355 return index; 3356 }, 3357 3358 sequence: function(element) { 3359 element = $(element); 3360 var options = Object.extend(this.options(element), arguments[1] || { }); 3361 3362 return $(this.findElements(element, options) || []).map( function(item) { 3363 return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; 3364 }); 3365 }, 3366 3367 setSequence: function(element, new_sequence) { 3368 element = $(element); 3369 var options = Object.extend(this.options(element), arguments[2] || { }); 3370 3371 var nodeMap = { }; 3372 this.findElements(element, options).each( function(n) { 3373 if (n.id.match(options.format)) 3374 nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; 3375 n.parentNode.removeChild(n); 3376 }); 3377 3378 new_sequence.each(function(ident) { 3379 var n = nodeMap[ident]; 3380 if (n) { 3381 n[1].appendChild(n[0]); 3382 delete nodeMap[ident]; 3383 } 3384 }); 3385 }, 3386 3387 serialize: function(element) { 3388 element = $(element); 3389 var options = Object.extend(Sortable.options(element), arguments[1] || { }); 3390 var name = encodeURIComponent( 3391 (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); 3392 3393 if (options.tree) { 3394 return Sortable.tree(element, arguments[1]).children.map( function (item) { 3395 return [name + Sortable._constructIndex(item) + "[id]=" + 3396 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); 3397 }).flatten().join('&'); 3398 } else { 3399 return Sortable.sequence(element, arguments[1]).map( function(item) { 3400 return name + "[]=" + encodeURIComponent(item); 3401 }).join('&'); 3402 } 3403 } 3404} 3405 3406// Returns true if child is contained within element 3407Element.isParent = function(child, element) { 3408 if (!child.parentNode || child == element) return false;
3409 if (child.parentNode == element) return true; 3410 return Element.isParent(child.parentNode, element); 3411} 3412 3413Element.findChildren = function(element, only, recursive, tagName) { 3414 if(!element.hasChildNodes()) return null; 3415 tagName = tagName.toUpperCase(); 3416 if(only) only = [only].flatten(); 3417 var elements = []; 3418 $A(element.childNodes).each( function(e) { 3419 if(e.tagName && e.tagName.toUpperCase()==tagName && 3420 (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) 3421 elements.push(e); 3422 if(recursive) { 3423 var grandchildren = Element.findChildren(e, only, recursive, tagName); 3424 if(grandchildren) elements.push(grandchildren); 3425 } 3426 }); 3427 3428 return (elements.length>0 ? elements.flatten() : []); 3429} 3430 3431Element.offsetSize = function (element, type) { 3432 return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; 3433}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.