##// END OF EJS Templates
updated prototype (1.50) and script.aculo.us javascripts...
Jean-Philippe Lang -
r238:2e72f2eca8f4
parent child
Show More
@@ -1,750 +1,833
1 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 // (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3 // (c) 2005 Jon Tirsen (http://www.tirsen.com)
1 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 // (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3 // (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com)
4 4 // Contributors:
5 5 // Richard Livsey
6 6 // Rahul Bhargava
7 7 // Rob Wills
8 8 //
9 // See scriptaculous.js for full license.
9 // script.aculo.us is freely distributable under the terms of an MIT-style license.
10 // For details, see the script.aculo.us web site: http://script.aculo.us/
10 11
11 12 // Autocompleter.Base handles all the autocompletion functionality
12 13 // that's independent of the data source for autocompletion. This
13 14 // includes drawing the autocompletion menu, observing keyboard
14 15 // and mouse events, and similar.
15 16 //
16 17 // Specific autocompleters need to provide, at the very least,
17 18 // a getUpdatedChoices function that will be invoked every time
18 19 // the text inside the monitored textbox changes. This method
19 20 // should get the text for which to provide autocompletion by
20 21 // invoking this.getToken(), NOT by directly accessing
21 22 // this.element.value. This is to allow incremental tokenized
22 23 // autocompletion. Specific auto-completion logic (AJAX, etc)
23 24 // belongs in getUpdatedChoices.
24 25 //
25 26 // Tokenized incremental autocompletion is enabled automatically
26 27 // when an autocompleter is instantiated with the 'tokens' option
27 28 // in the options parameter, e.g.:
28 29 // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
29 30 // will incrementally autocomplete with a comma as the token.
30 31 // Additionally, ',' in the above example can be replaced with
31 32 // a token array, e.g. { tokens: [',', '\n'] } which
32 33 // enables autocompletion on multiple tokens. This is most
33 34 // useful when one of the tokens is \n (a newline), as it
34 35 // allows smart autocompletion after linebreaks.
35 36
37 if(typeof Effect == 'undefined')
38 throw("controls.js requires including script.aculo.us' effects.js library");
39
36 40 var Autocompleter = {}
37 41 Autocompleter.Base = function() {};
38 42 Autocompleter.Base.prototype = {
39 43 baseInitialize: function(element, update, options) {
40 44 this.element = $(element);
41 45 this.update = $(update);
42 46 this.hasFocus = false;
43 47 this.changed = false;
44 48 this.active = false;
45 49 this.index = 0;
46 50 this.entryCount = 0;
47 51
48 if (this.setOptions)
52 if(this.setOptions)
49 53 this.setOptions(options);
50 54 else
51 55 this.options = options || {};
52 56
53 57 this.options.paramName = this.options.paramName || this.element.name;
54 58 this.options.tokens = this.options.tokens || [];
55 59 this.options.frequency = this.options.frequency || 0.4;
56 60 this.options.minChars = this.options.minChars || 1;
57 61 this.options.onShow = this.options.onShow ||
58 function(element, update){
59 if(!update.style.position || update.style.position=='absolute') {
60 update.style.position = 'absolute';
61 Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
62 }
63 Effect.Appear(update,{duration:0.15});
64 };
62 function(element, update){
63 if(!update.style.position || update.style.position=='absolute') {
64 update.style.position = 'absolute';
65 Position.clone(element, update, {
66 setHeight: false,
67 offsetTop: element.offsetHeight
68 });
69 }
70 Effect.Appear(update,{duration:0.15});
71 };
65 72 this.options.onHide = this.options.onHide ||
66 function(element, update){ new Effect.Fade(update,{duration:0.15}) };
73 function(element, update){ new Effect.Fade(update,{duration:0.15}) };
67 74
68 if (typeof(this.options.tokens) == 'string')
75 if(typeof(this.options.tokens) == 'string')
69 76 this.options.tokens = new Array(this.options.tokens);
70 77
71 78 this.observer = null;
72 79
73 80 this.element.setAttribute('autocomplete','off');
74 81
75 82 Element.hide(this.update);
76 83
77 84 Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
78 85 Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
79 86 },
80 87
81 88 show: function() {
82 89 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
83 90 if(!this.iefix &&
84 91 (navigator.appVersion.indexOf('MSIE')>0) &&
85 92 (navigator.userAgent.indexOf('Opera')<0) &&
86 93 (Element.getStyle(this.update, 'position')=='absolute')) {
87 94 new Insertion.After(this.update,
88 95 '<iframe id="' + this.update.id + '_iefix" '+
89 96 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
90 97 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
91 98 this.iefix = $(this.update.id+'_iefix');
92 99 }
93 100 if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
94 101 },
95 102
96 103 fixIEOverlapping: function() {
97 Position.clone(this.update, this.iefix);
104 Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
98 105 this.iefix.style.zIndex = 1;
99 106 this.update.style.zIndex = 2;
100 107 Element.show(this.iefix);
101 108 },
102 109
103 110 hide: function() {
104 111 this.stopIndicator();
105 112 if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
106 113 if(this.iefix) Element.hide(this.iefix);
107 114 },
108 115
109 116 startIndicator: function() {
110 117 if(this.options.indicator) Element.show(this.options.indicator);
111 118 },
112 119
113 120 stopIndicator: function() {
114 121 if(this.options.indicator) Element.hide(this.options.indicator);
115 122 },
116 123
117 124 onKeyPress: function(event) {
118 125 if(this.active)
119 126 switch(event.keyCode) {
120 127 case Event.KEY_TAB:
121 128 case Event.KEY_RETURN:
122 129 this.selectEntry();
123 130 Event.stop(event);
124 131 case Event.KEY_ESC:
125 132 this.hide();
126 133 this.active = false;
127 134 Event.stop(event);
128 135 return;
129 136 case Event.KEY_LEFT:
130 137 case Event.KEY_RIGHT:
131 138 return;
132 139 case Event.KEY_UP:
133 140 this.markPrevious();
134 141 this.render();
135 142 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
136 143 return;
137 144 case Event.KEY_DOWN:
138 145 this.markNext();
139 146 this.render();
140 147 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
141 148 return;
142 149 }
143 150 else
144 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
145 return;
151 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
152 (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
146 153
147 154 this.changed = true;
148 155 this.hasFocus = true;
149 156
150 157 if(this.observer) clearTimeout(this.observer);
151 158 this.observer =
152 159 setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
153 160 },
154 161
162 activate: function() {
163 this.changed = false;
164 this.hasFocus = true;
165 this.getUpdatedChoices();
166 },
167
155 168 onHover: function(event) {
156 169 var element = Event.findElement(event, 'LI');
157 170 if(this.index != element.autocompleteIndex)
158 171 {
159 172 this.index = element.autocompleteIndex;
160 173 this.render();
161 174 }
162 175 Event.stop(event);
163 176 },
164 177
165 178 onClick: function(event) {
166 179 var element = Event.findElement(event, 'LI');
167 180 this.index = element.autocompleteIndex;
168 181 this.selectEntry();
169 182 this.hide();
170 183 },
171 184
172 185 onBlur: function(event) {
173 186 // needed to make click events working
174 187 setTimeout(this.hide.bind(this), 250);
175 188 this.hasFocus = false;
176 189 this.active = false;
177 190 },
178 191
179 192 render: function() {
180 193 if(this.entryCount > 0) {
181 194 for (var i = 0; i < this.entryCount; i++)
182 195 this.index==i ?
183 196 Element.addClassName(this.getEntry(i),"selected") :
184 197 Element.removeClassName(this.getEntry(i),"selected");
185 198
186 199 if(this.hasFocus) {
187 200 this.show();
188 201 this.active = true;
189 202 }
190 203 } else {
191 204 this.active = false;
192 205 this.hide();
193 206 }
194 207 },
195 208
196 209 markPrevious: function() {
197 210 if(this.index > 0) this.index--
198 211 else this.index = this.entryCount-1;
212 this.getEntry(this.index).scrollIntoView(true);
199 213 },
200 214
201 215 markNext: function() {
202 216 if(this.index < this.entryCount-1) this.index++
203 217 else this.index = 0;
218 this.getEntry(this.index).scrollIntoView(false);
204 219 },
205 220
206 221 getEntry: function(index) {
207 222 return this.update.firstChild.childNodes[index];
208 223 },
209 224
210 225 getCurrentEntry: function() {
211 226 return this.getEntry(this.index);
212 227 },
213 228
214 229 selectEntry: function() {
215 230 this.active = false;
216 231 this.updateElement(this.getCurrentEntry());
217 232 },
218 233
219 234 updateElement: function(selectedElement) {
220 235 if (this.options.updateElement) {
221 236 this.options.updateElement(selectedElement);
222 237 return;
223 238 }
224
225 var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
239 var value = '';
240 if (this.options.select) {
241 var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
242 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
243 } else
244 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
245
226 246 var lastTokenPos = this.findLastToken();
227 247 if (lastTokenPos != -1) {
228 248 var newValue = this.element.value.substr(0, lastTokenPos + 1);
229 249 var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
230 250 if (whitespace)
231 251 newValue += whitespace[0];
232 252 this.element.value = newValue + value;
233 253 } else {
234 254 this.element.value = value;
235 255 }
236 256 this.element.focus();
237 257
238 258 if (this.options.afterUpdateElement)
239 259 this.options.afterUpdateElement(this.element, selectedElement);
240 260 },
241 261
242 262 updateChoices: function(choices) {
243 263 if(!this.changed && this.hasFocus) {
244 264 this.update.innerHTML = choices;
245 265 Element.cleanWhitespace(this.update);
246 Element.cleanWhitespace(this.update.firstChild);
266 Element.cleanWhitespace(this.update.down());
247 267
248 if(this.update.firstChild && this.update.firstChild.childNodes) {
268 if(this.update.firstChild && this.update.down().childNodes) {
249 269 this.entryCount =
250 this.update.firstChild.childNodes.length;
270 this.update.down().childNodes.length;
251 271 for (var i = 0; i < this.entryCount; i++) {
252 272 var entry = this.getEntry(i);
253 273 entry.autocompleteIndex = i;
254 274 this.addObservers(entry);
255 275 }
256 276 } else {
257 277 this.entryCount = 0;
258 278 }
259 279
260 280 this.stopIndicator();
261
262 281 this.index = 0;
263 this.render();
282
283 if(this.entryCount==1 && this.options.autoSelect) {
284 this.selectEntry();
285 this.hide();
286 } else {
287 this.render();
288 }
264 289 }
265 290 },
266 291
267 292 addObservers: function(element) {
268 293 Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
269 294 Event.observe(element, "click", this.onClick.bindAsEventListener(this));
270 295 },
271 296
272 297 onObserverEvent: function() {
273 298 this.changed = false;
274 299 if(this.getToken().length>=this.options.minChars) {
275 300 this.startIndicator();
276 301 this.getUpdatedChoices();
277 302 } else {
278 303 this.active = false;
279 304 this.hide();
280 305 }
281 306 },
282 307
283 308 getToken: function() {
284 309 var tokenPos = this.findLastToken();
285 310 if (tokenPos != -1)
286 311 var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
287 312 else
288 313 var ret = this.element.value;
289 314
290 315 return /\n/.test(ret) ? '' : ret;
291 316 },
292 317
293 318 findLastToken: function() {
294 319 var lastTokenPos = -1;
295 320
296 321 for (var i=0; i<this.options.tokens.length; i++) {
297 322 var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
298 323 if (thisTokenPos > lastTokenPos)
299 324 lastTokenPos = thisTokenPos;
300 325 }
301 326 return lastTokenPos;
302 327 }
303 328 }
304 329
305 330 Ajax.Autocompleter = Class.create();
306 331 Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
307 332 initialize: function(element, update, url, options) {
308 this.baseInitialize(element, update, options);
333 this.baseInitialize(element, update, options);
309 334 this.options.asynchronous = true;
310 335 this.options.onComplete = this.onComplete.bind(this);
311 336 this.options.defaultParams = this.options.parameters || null;
312 337 this.url = url;
313 338 },
314 339
315 340 getUpdatedChoices: function() {
316 341 entry = encodeURIComponent(this.options.paramName) + '=' +
317 342 encodeURIComponent(this.getToken());
318 343
319 344 this.options.parameters = this.options.callback ?
320 345 this.options.callback(this.element, entry) : entry;
321 346
322 347 if(this.options.defaultParams)
323 348 this.options.parameters += '&' + this.options.defaultParams;
324 349
325 350 new Ajax.Request(this.url, this.options);
326 351 },
327 352
328 353 onComplete: function(request) {
329 354 this.updateChoices(request.responseText);
330 355 }
331 356
332 357 });
333 358
334 359 // The local array autocompleter. Used when you'd prefer to
335 360 // inject an array of autocompletion options into the page, rather
336 361 // than sending out Ajax queries, which can be quite slow sometimes.
337 362 //
338 363 // The constructor takes four parameters. The first two are, as usual,
339 364 // the id of the monitored textbox, and id of the autocompletion menu.
340 365 // The third is the array you want to autocomplete from, and the fourth
341 366 // is the options block.
342 367 //
343 368 // Extra local autocompletion options:
344 369 // - choices - How many autocompletion choices to offer
345 370 //
346 371 // - partialSearch - If false, the autocompleter will match entered
347 372 // text only at the beginning of strings in the
348 373 // autocomplete array. Defaults to true, which will
349 374 // match text at the beginning of any *word* in the
350 375 // strings in the autocomplete array. If you want to
351 376 // search anywhere in the string, additionally set
352 377 // the option fullSearch to true (default: off).
353 378 //
354 379 // - fullSsearch - Search anywhere in autocomplete array strings.
355 380 //
356 381 // - partialChars - How many characters to enter before triggering
357 382 // a partial match (unlike minChars, which defines
358 383 // how many characters are required to do any match
359 384 // at all). Defaults to 2.
360 385 //
361 386 // - ignoreCase - Whether to ignore case when autocompleting.
362 387 // Defaults to true.
363 388 //
364 389 // It's possible to pass in a custom function as the 'selector'
365 390 // option, if you prefer to write your own autocompletion logic.
366 391 // In that case, the other options above will not apply unless
367 392 // you support them.
368 393
369 394 Autocompleter.Local = Class.create();
370 395 Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
371 396 initialize: function(element, update, array, options) {
372 397 this.baseInitialize(element, update, options);
373 398 this.options.array = array;
374 399 },
375 400
376 401 getUpdatedChoices: function() {
377 402 this.updateChoices(this.options.selector(this));
378 403 },
379 404
380 405 setOptions: function(options) {
381 406 this.options = Object.extend({
382 407 choices: 10,
383 408 partialSearch: true,
384 409 partialChars: 2,
385 410 ignoreCase: true,
386 411 fullSearch: false,
387 412 selector: function(instance) {
388 413 var ret = []; // Beginning matches
389 414 var partial = []; // Inside matches
390 415 var entry = instance.getToken();
391 416 var count = 0;
392 417
393 418 for (var i = 0; i < instance.options.array.length &&
394 419 ret.length < instance.options.choices ; i++) {
395 420
396 421 var elem = instance.options.array[i];
397 422 var foundPos = instance.options.ignoreCase ?
398 423 elem.toLowerCase().indexOf(entry.toLowerCase()) :
399 424 elem.indexOf(entry);
400 425
401 426 while (foundPos != -1) {
402 427 if (foundPos == 0 && elem.length != entry.length) {
403 428 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
404 429 elem.substr(entry.length) + "</li>");
405 430 break;
406 431 } else if (entry.length >= instance.options.partialChars &&
407 432 instance.options.partialSearch && foundPos != -1) {
408 433 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
409 434 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
410 435 elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
411 436 foundPos + entry.length) + "</li>");
412 437 break;
413 438 }
414 439 }
415 440
416 441 foundPos = instance.options.ignoreCase ?
417 442 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
418 443 elem.indexOf(entry, foundPos + 1);
419 444
420 445 }
421 446 }
422 447 if (partial.length)
423 448 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
424 449 return "<ul>" + ret.join('') + "</ul>";
425 450 }
426 451 }, options || {});
427 452 }
428 453 });
429 454
430 455 // AJAX in-place editor
431 456 //
432 457 // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
433 458
434 459 // Use this if you notice weird scrolling problems on some browsers,
435 460 // the DOM might be a bit confused when this gets called so do this
436 461 // waits 1 ms (with setTimeout) until it does the activation
437 462 Field.scrollFreeActivate = function(field) {
438 463 setTimeout(function() {
439 464 Field.activate(field);
440 465 }, 1);
441 466 }
442 467
443 468 Ajax.InPlaceEditor = Class.create();
444 469 Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
445 470 Ajax.InPlaceEditor.prototype = {
446 471 initialize: function(element, url, options) {
447 472 this.url = url;
448 473 this.element = $(element);
449 474
450 475 this.options = Object.extend({
476 paramName: "value",
477 okButton: true,
451 478 okText: "ok",
479 cancelLink: true,
452 480 cancelText: "cancel",
453 481 savingText: "Saving...",
454 482 clickToEditText: "Click to edit",
455 483 okText: "ok",
456 484 rows: 1,
457 485 onComplete: function(transport, element) {
458 486 new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
459 487 },
460 488 onFailure: function(transport) {
461 489 alert("Error communicating with the server: " + transport.responseText.stripTags());
462 490 },
463 491 callback: function(form) {
464 492 return Form.serialize(form);
465 493 },
466 494 handleLineBreaks: true,
467 495 loadingText: 'Loading...',
468 496 savingClassName: 'inplaceeditor-saving',
469 497 loadingClassName: 'inplaceeditor-loading',
470 498 formClassName: 'inplaceeditor-form',
471 499 highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
472 500 highlightendcolor: "#FFFFFF",
473 externalControl: null,
474 ajaxOptions: {}
501 externalControl: null,
502 submitOnBlur: false,
503 ajaxOptions: {},
504 evalScripts: false
475 505 }, options || {});
476 506
477 507 if(!this.options.formId && this.element.id) {
478 508 this.options.formId = this.element.id + "-inplaceeditor";
479 509 if ($(this.options.formId)) {
480 510 // there's already a form with that name, don't specify an id
481 511 this.options.formId = null;
482 512 }
483 513 }
484 514
485 515 if (this.options.externalControl) {
486 516 this.options.externalControl = $(this.options.externalControl);
487 517 }
488 518
489 519 this.originalBackground = Element.getStyle(this.element, 'background-color');
490 520 if (!this.originalBackground) {
491 521 this.originalBackground = "transparent";
492 522 }
493 523
494 524 this.element.title = this.options.clickToEditText;
495 525
496 526 this.onclickListener = this.enterEditMode.bindAsEventListener(this);
497 527 this.mouseoverListener = this.enterHover.bindAsEventListener(this);
498 528 this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
499 529 Event.observe(this.element, 'click', this.onclickListener);
500 530 Event.observe(this.element, 'mouseover', this.mouseoverListener);
501 531 Event.observe(this.element, 'mouseout', this.mouseoutListener);
502 532 if (this.options.externalControl) {
503 533 Event.observe(this.options.externalControl, 'click', this.onclickListener);
504 534 Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
505 535 Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
506 536 }
507 537 },
508 538 enterEditMode: function(evt) {
509 539 if (this.saving) return;
510 540 if (this.editing) return;
511 541 this.editing = true;
512 542 this.onEnterEditMode();
513 543 if (this.options.externalControl) {
514 544 Element.hide(this.options.externalControl);
515 545 }
516 546 Element.hide(this.element);
517 547 this.createForm();
518 548 this.element.parentNode.insertBefore(this.form, this.element);
519 Field.scrollFreeActivate(this.editField);
549 if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
520 550 // stop the event to avoid a page refresh in Safari
521 551 if (evt) {
522 552 Event.stop(evt);
523 553 }
524 554 return false;
525 555 },
526 556 createForm: function() {
527 557 this.form = document.createElement("form");
528 558 this.form.id = this.options.formId;
529 559 Element.addClassName(this.form, this.options.formClassName)
530 560 this.form.onsubmit = this.onSubmit.bind(this);
531 561
532 562 this.createEditField();
533 563
534 564 if (this.options.textarea) {
535 565 var br = document.createElement("br");
536 566 this.form.appendChild(br);
537 567 }
538 568
539 okButton = document.createElement("input");
540 okButton.type = "submit";
541 okButton.value = this.options.okText;
542 this.form.appendChild(okButton);
569 if (this.options.okButton) {
570 okButton = document.createElement("input");
571 okButton.type = "submit";
572 okButton.value = this.options.okText;
573 okButton.className = 'editor_ok_button';
574 this.form.appendChild(okButton);
575 }
543 576
544 cancelLink = document.createElement("a");
545 cancelLink.href = "#";
546 cancelLink.appendChild(document.createTextNode(this.options.cancelText));
547 cancelLink.onclick = this.onclickCancel.bind(this);
548 this.form.appendChild(cancelLink);
577 if (this.options.cancelLink) {
578 cancelLink = document.createElement("a");
579 cancelLink.href = "#";
580 cancelLink.appendChild(document.createTextNode(this.options.cancelText));
581 cancelLink.onclick = this.onclickCancel.bind(this);
582 cancelLink.className = 'editor_cancel';
583 this.form.appendChild(cancelLink);
584 }
549 585 },
550 586 hasHTMLLineBreaks: function(string) {
551 587 if (!this.options.handleLineBreaks) return false;
552 588 return string.match(/<br/i) || string.match(/<p>/i);
553 589 },
554 590 convertHTMLLineBreaks: function(string) {
555 591 return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
556 592 },
557 593 createEditField: function() {
558 594 var text;
559 595 if(this.options.loadTextURL) {
560 596 text = this.options.loadingText;
561 597 } else {
562 598 text = this.getText();
563 599 }
600
601 var obj = this;
564 602
565 603 if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
566 604 this.options.textarea = false;
567 605 var textField = document.createElement("input");
606 textField.obj = this;
568 607 textField.type = "text";
569 textField.name = "value";
608 textField.name = this.options.paramName;
570 609 textField.value = text;
571 610 textField.style.backgroundColor = this.options.highlightcolor;
611 textField.className = 'editor_field';
572 612 var size = this.options.size || this.options.cols || 0;
573 613 if (size != 0) textField.size = size;
614 if (this.options.submitOnBlur)
615 textField.onblur = this.onSubmit.bind(this);
574 616 this.editField = textField;
575 617 } else {
576 618 this.options.textarea = true;
577 619 var textArea = document.createElement("textarea");
578 textArea.name = "value";
620 textArea.obj = this;
621 textArea.name = this.options.paramName;
579 622 textArea.value = this.convertHTMLLineBreaks(text);
580 623 textArea.rows = this.options.rows;
581 624 textArea.cols = this.options.cols || 40;
625 textArea.className = 'editor_field';
626 if (this.options.submitOnBlur)
627 textArea.onblur = this.onSubmit.bind(this);
582 628 this.editField = textArea;
583 629 }
584 630
585 631 if(this.options.loadTextURL) {
586 632 this.loadExternalText();
587 633 }
588 634 this.form.appendChild(this.editField);
589 635 },
590 636 getText: function() {
591 637 return this.element.innerHTML;
592 638 },
593 639 loadExternalText: function() {
594 640 Element.addClassName(this.form, this.options.loadingClassName);
595 641 this.editField.disabled = true;
596 642 new Ajax.Request(
597 643 this.options.loadTextURL,
598 644 Object.extend({
599 645 asynchronous: true,
600 646 onComplete: this.onLoadedExternalText.bind(this)
601 647 }, this.options.ajaxOptions)
602 648 );
603 649 },
604 650 onLoadedExternalText: function(transport) {
605 651 Element.removeClassName(this.form, this.options.loadingClassName);
606 652 this.editField.disabled = false;
607 653 this.editField.value = transport.responseText.stripTags();
654 Field.scrollFreeActivate(this.editField);
608 655 },
609 656 onclickCancel: function() {
610 657 this.onComplete();
611 658 this.leaveEditMode();
612 659 return false;
613 660 },
614 661 onFailure: function(transport) {
615 662 this.options.onFailure(transport);
616 663 if (this.oldInnerHTML) {
617 664 this.element.innerHTML = this.oldInnerHTML;
618 665 this.oldInnerHTML = null;
619 666 }
620 667 return false;
621 668 },
622 669 onSubmit: function() {
623 670 // onLoading resets these so we need to save them away for the Ajax call
624 671 var form = this.form;
625 672 var value = this.editField.value;
626 673
627 674 // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
628 675 // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
629 676 // to be displayed indefinitely
630 677 this.onLoading();
631 678
632 new Ajax.Updater(
633 {
634 success: this.element,
635 // don't update on failure (this could be an option)
636 failure: null
637 },
638 this.url,
639 Object.extend({
640 parameters: this.options.callback(form, value),
641 onComplete: this.onComplete.bind(this),
642 onFailure: this.onFailure.bind(this)
643 }, this.options.ajaxOptions)
644 );
679 if (this.options.evalScripts) {
680 new Ajax.Request(
681 this.url, Object.extend({
682 parameters: this.options.callback(form, value),
683 onComplete: this.onComplete.bind(this),
684 onFailure: this.onFailure.bind(this),
685 asynchronous:true,
686 evalScripts:true
687 }, this.options.ajaxOptions));
688 } else {
689 new Ajax.Updater(
690 { success: this.element,
691 // don't update on failure (this could be an option)
692 failure: null },
693 this.url, Object.extend({
694 parameters: this.options.callback(form, value),
695 onComplete: this.onComplete.bind(this),
696 onFailure: this.onFailure.bind(this)
697 }, this.options.ajaxOptions));
698 }
645 699 // stop the event to avoid a page refresh in Safari
646 700 if (arguments.length > 1) {
647 701 Event.stop(arguments[0]);
648 702 }
649 703 return false;
650 704 },
651 705 onLoading: function() {
652 706 this.saving = true;
653 707 this.removeForm();
654 708 this.leaveHover();
655 709 this.showSaving();
656 710 },
657 711 showSaving: function() {
658 712 this.oldInnerHTML = this.element.innerHTML;
659 713 this.element.innerHTML = this.options.savingText;
660 714 Element.addClassName(this.element, this.options.savingClassName);
661 715 this.element.style.backgroundColor = this.originalBackground;
662 716 Element.show(this.element);
663 717 },
664 718 removeForm: function() {
665 719 if(this.form) {
666 720 if (this.form.parentNode) Element.remove(this.form);
667 721 this.form = null;
668 722 }
669 723 },
670 724 enterHover: function() {
671 725 if (this.saving) return;
672 726 this.element.style.backgroundColor = this.options.highlightcolor;
673 727 if (this.effect) {
674 728 this.effect.cancel();
675 729 }
676 730 Element.addClassName(this.element, this.options.hoverClassName)
677 731 },
678 732 leaveHover: function() {
679 733 if (this.options.backgroundColor) {
680 734 this.element.style.backgroundColor = this.oldBackground;
681 735 }
682 736 Element.removeClassName(this.element, this.options.hoverClassName)
683 737 if (this.saving) return;
684 738 this.effect = new Effect.Highlight(this.element, {
685 739 startcolor: this.options.highlightcolor,
686 740 endcolor: this.options.highlightendcolor,
687 741 restorecolor: this.originalBackground
688 742 });
689 743 },
690 744 leaveEditMode: function() {
691 745 Element.removeClassName(this.element, this.options.savingClassName);
692 746 this.removeForm();
693 747 this.leaveHover();
694 748 this.element.style.backgroundColor = this.originalBackground;
695 749 Element.show(this.element);
696 750 if (this.options.externalControl) {
697 751 Element.show(this.options.externalControl);
698 752 }
699 753 this.editing = false;
700 754 this.saving = false;
701 755 this.oldInnerHTML = null;
702 756 this.onLeaveEditMode();
703 757 },
704 758 onComplete: function(transport) {
705 759 this.leaveEditMode();
706 760 this.options.onComplete.bind(this)(transport, this.element);
707 761 },
708 762 onEnterEditMode: function() {},
709 763 onLeaveEditMode: function() {},
710 764 dispose: function() {
711 765 if (this.oldInnerHTML) {
712 766 this.element.innerHTML = this.oldInnerHTML;
713 767 }
714 768 this.leaveEditMode();
715 769 Event.stopObserving(this.element, 'click', this.onclickListener);
716 770 Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
717 771 Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
718 772 if (this.options.externalControl) {
719 773 Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
720 774 Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
721 775 Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
722 776 }
723 777 }
724 778 };
725 779
780 Ajax.InPlaceCollectionEditor = Class.create();
781 Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
782 Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
783 createEditField: function() {
784 if (!this.cached_selectTag) {
785 var selectTag = document.createElement("select");
786 var collection = this.options.collection || [];
787 var optionTag;
788 collection.each(function(e,i) {
789 optionTag = document.createElement("option");
790 optionTag.value = (e instanceof Array) ? e[0] : e;
791 if((typeof this.options.value == 'undefined') &&
792 ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
793 if(this.options.value==optionTag.value) optionTag.selected = true;
794 optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
795 selectTag.appendChild(optionTag);
796 }.bind(this));
797 this.cached_selectTag = selectTag;
798 }
799
800 this.editField = this.cached_selectTag;
801 if(this.options.loadTextURL) this.loadExternalText();
802 this.form.appendChild(this.editField);
803 this.options.callback = function(form, value) {
804 return "value=" + encodeURIComponent(value);
805 }
806 }
807 });
808
726 809 // Delayed observer, like Form.Element.Observer,
727 810 // but waits for delay after last key input
728 811 // Ideal for live-search fields
729 812
730 813 Form.Element.DelayedObserver = Class.create();
731 814 Form.Element.DelayedObserver.prototype = {
732 815 initialize: function(element, delay, callback) {
733 816 this.delay = delay || 0.5;
734 817 this.element = $(element);
735 818 this.callback = callback;
736 819 this.timer = null;
737 820 this.lastValue = $F(this.element);
738 821 Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
739 822 },
740 823 delayedListener: function(event) {
741 824 if(this.lastValue == $F(this.element)) return;
742 825 if(this.timer) clearTimeout(this.timer);
743 826 this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
744 827 this.lastValue = $F(this.element);
745 828 },
746 829 onTimerEvent: function() {
747 830 this.timer = null;
748 831 this.callback(this.element, $F(this.element));
749 832 }
750 }; No newline at end of file
833 };
This diff has been collapsed as it changes many lines, (550 lines changed) Show them Hide them
@@ -1,584 +1,942
1 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
1 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 // (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
2 3 //
3 // See scriptaculous.js for full license.
4 // script.aculo.us is freely distributable under the terms of an MIT-style license.
5 // For details, see the script.aculo.us web site: http://script.aculo.us/
4 6
5 /*--------------------------------------------------------------------------*/
7 if(typeof Effect == 'undefined')
8 throw("dragdrop.js requires including script.aculo.us' effects.js library");
6 9
7 10 var Droppables = {
8 11 drops: [],
9 12
10 13 remove: function(element) {
11 14 this.drops = this.drops.reject(function(d) { return d.element==$(element) });
12 15 },
13 16
14 17 add: function(element) {
15 18 element = $(element);
16 19 var options = Object.extend({
17 20 greedy: true,
18 hoverclass: null
21 hoverclass: null,
22 tree: false
19 23 }, arguments[1] || {});
20 24
21 25 // cache containers
22 26 if(options.containment) {
23 27 options._containers = [];
24 28 var containment = options.containment;
25 29 if((typeof containment == 'object') &&
26 30 (containment.constructor == Array)) {
27 31 containment.each( function(c) { options._containers.push($(c)) });
28 32 } else {
29 33 options._containers.push($(containment));
30 34 }
31 35 }
32 36
33 37 if(options.accept) options.accept = [options.accept].flatten();
34 38
35 39 Element.makePositioned(element); // fix IE
36 40 options.element = element;
37 41
38 42 this.drops.push(options);
39 43 },
44
45 findDeepestChild: function(drops) {
46 deepest = drops[0];
47
48 for (i = 1; i < drops.length; ++i)
49 if (Element.isParent(drops[i].element, deepest.element))
50 deepest = drops[i];
51
52 return deepest;
53 },
40 54
41 55 isContained: function(element, drop) {
42 var parentNode = element.parentNode;
43 return drop._containers.detect(function(c) { return parentNode == c });
56 var containmentNode;
57 if(drop.tree) {
58 containmentNode = element.treeNode;
59 } else {
60 containmentNode = element.parentNode;
61 }
62 return drop._containers.detect(function(c) { return containmentNode == c });
44 63 },
45
64
46 65 isAffected: function(point, element, drop) {
47 66 return (
48 67 (drop.element!=element) &&
49 68 ((!drop._containers) ||
50 69 this.isContained(element, drop)) &&
51 70 ((!drop.accept) ||
52 71 (Element.classNames(element).detect(
53 72 function(v) { return drop.accept.include(v) } ) )) &&
54 73 Position.within(drop.element, point[0], point[1]) );
55 74 },
56 75
57 76 deactivate: function(drop) {
58 77 if(drop.hoverclass)
59 78 Element.removeClassName(drop.element, drop.hoverclass);
60 79 this.last_active = null;
61 80 },
62 81
63 82 activate: function(drop) {
64 83 if(drop.hoverclass)
65 84 Element.addClassName(drop.element, drop.hoverclass);
66 85 this.last_active = drop;
67 86 },
68 87
69 88 show: function(point, element) {
70 89 if(!this.drops.length) return;
90 var affected = [];
71 91
72 92 if(this.last_active) this.deactivate(this.last_active);
73 93 this.drops.each( function(drop) {
74 if(Droppables.isAffected(point, element, drop)) {
75 if(drop.onHover)
76 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
77 if(drop.greedy) {
78 Droppables.activate(drop);
79 throw $break;
80 }
81 }
94 if(Droppables.isAffected(point, element, drop))
95 affected.push(drop);
82 96 });
97
98 if(affected.length>0) {
99 drop = Droppables.findDeepestChild(affected);
100 Position.within(drop.element, point[0], point[1]);
101 if(drop.onHover)
102 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
103
104 Droppables.activate(drop);
105 }
83 106 },
84 107
85 108 fire: function(event, element) {
86 109 if(!this.last_active) return;
87 110 Position.prepare();
88 111
89 112 if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
90 113 if (this.last_active.onDrop)
91 114 this.last_active.onDrop(element, this.last_active.element, event);
92 115 },
93 116
94 117 reset: function() {
95 118 if(this.last_active)
96 119 this.deactivate(this.last_active);
97 120 }
98 121 }
99 122
100 123 var Draggables = {
101 124 drags: [],
102 125 observers: [],
103 126
104 127 register: function(draggable) {
105 128 if(this.drags.length == 0) {
106 129 this.eventMouseUp = this.endDrag.bindAsEventListener(this);
107 130 this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
108 131 this.eventKeypress = this.keyPress.bindAsEventListener(this);
109 132
110 133 Event.observe(document, "mouseup", this.eventMouseUp);
111 134 Event.observe(document, "mousemove", this.eventMouseMove);
112 135 Event.observe(document, "keypress", this.eventKeypress);
113 136 }
114 137 this.drags.push(draggable);
115 138 },
116 139
117 140 unregister: function(draggable) {
118 141 this.drags = this.drags.reject(function(d) { return d==draggable });
119 142 if(this.drags.length == 0) {
120 143 Event.stopObserving(document, "mouseup", this.eventMouseUp);
121 144 Event.stopObserving(document, "mousemove", this.eventMouseMove);
122 145 Event.stopObserving(document, "keypress", this.eventKeypress);
123 146 }
124 147 },
125 148
126 149 activate: function(draggable) {
127 window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
128 this.activeDraggable = draggable;
150 if(draggable.options.delay) {
151 this._timeout = setTimeout(function() {
152 Draggables._timeout = null;
153 window.focus();
154 Draggables.activeDraggable = draggable;
155 }.bind(this), draggable.options.delay);
156 } else {
157 window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
158 this.activeDraggable = draggable;
159 }
129 160 },
130 161
131 deactivate: function(draggbale) {
162 deactivate: function() {
132 163 this.activeDraggable = null;
133 164 },
134 165
135 166 updateDrag: function(event) {
136 167 if(!this.activeDraggable) return;
137 168 var pointer = [Event.pointerX(event), Event.pointerY(event)];
138 169 // Mozilla-based browsers fire successive mousemove events with
139 170 // the same coordinates, prevent needless redrawing (moz bug?)
140 171 if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
141 172 this._lastPointer = pointer;
173
142 174 this.activeDraggable.updateDrag(event, pointer);
143 175 },
144 176
145 177 endDrag: function(event) {
178 if(this._timeout) {
179 clearTimeout(this._timeout);
180 this._timeout = null;
181 }
146 182 if(!this.activeDraggable) return;
147 183 this._lastPointer = null;
148 184 this.activeDraggable.endDrag(event);
185 this.activeDraggable = null;
149 186 },
150 187
151 188 keyPress: function(event) {
152 189 if(this.activeDraggable)
153 190 this.activeDraggable.keyPress(event);
154 191 },
155 192
156 193 addObserver: function(observer) {
157 194 this.observers.push(observer);
158 195 this._cacheObserverCallbacks();
159 196 },
160 197
161 198 removeObserver: function(element) { // element instead of observer fixes mem leaks
162 199 this.observers = this.observers.reject( function(o) { return o.element==element });
163 200 this._cacheObserverCallbacks();
164 201 },
165 202
166 203 notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
167 204 if(this[eventName+'Count'] > 0)
168 205 this.observers.each( function(o) {
169 206 if(o[eventName]) o[eventName](eventName, draggable, event);
170 207 });
208 if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
171 209 },
172 210
173 211 _cacheObserverCallbacks: function() {
174 212 ['onStart','onEnd','onDrag'].each( function(eventName) {
175 213 Draggables[eventName+'Count'] = Draggables.observers.select(
176 214 function(o) { return o[eventName]; }
177 215 ).length;
178 216 });
179 217 }
180 218 }
181 219
182 220 /*--------------------------------------------------------------------------*/
183 221
184 222 var Draggable = Class.create();
223 Draggable._dragging = {};
224
185 225 Draggable.prototype = {
186 226 initialize: function(element) {
187 var options = Object.extend({
227 var defaults = {
188 228 handle: false,
189 starteffect: function(element) {
190 new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
191 },
192 229 reverteffect: function(element, top_offset, left_offset) {
193 230 var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
194 element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
231 new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
232 queue: {scope:'_draggable', position:'end'}
233 });
195 234 },
196 endeffect: function(element) {
197 new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
235 endeffect: function(element) {
236 var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
237 new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
238 queue: {scope:'_draggable', position:'end'},
239 afterFinish: function(){
240 Draggable._dragging[element] = false
241 }
242 });
198 243 },
199 244 zindex: 1000,
200 245 revert: false,
201 snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
202 }, arguments[1] || {});
246 scroll: false,
247 scrollSensitivity: 20,
248 scrollSpeed: 15,
249 snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
250 delay: 0
251 };
252
253 if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
254 Object.extend(defaults, {
255 starteffect: function(element) {
256 element._opacity = Element.getOpacity(element);
257 Draggable._dragging[element] = true;
258 new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
259 }
260 });
261
262 var options = Object.extend(defaults, arguments[1] || {});
203 263
204 264 this.element = $(element);
205 265
206 266 if(options.handle && (typeof options.handle == 'string'))
207 this.handle = Element.childrenWithClassName(this.element, options.handle)[0];
267 this.handle = this.element.down('.'+options.handle, 0);
268
208 269 if(!this.handle) this.handle = $(options.handle);
209 270 if(!this.handle) this.handle = this.element;
271
272 if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
273 options.scroll = $(options.scroll);
274 this._isScrollChild = Element.childOf(this.element, options.scroll);
275 }
210 276
211 277 Element.makePositioned(this.element); // fix IE
212 278
213 279 this.delta = this.currentDelta();
214 280 this.options = options;
215 281 this.dragging = false;
216 282
217 283 this.eventMouseDown = this.initDrag.bindAsEventListener(this);
218 284 Event.observe(this.handle, "mousedown", this.eventMouseDown);
219 285
220 286 Draggables.register(this);
221 287 },
222 288
223 289 destroy: function() {
224 290 Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
225 291 Draggables.unregister(this);
226 292 },
227 293
228 294 currentDelta: function() {
229 295 return([
230 parseInt(this.element.style.left || '0'),
231 parseInt(this.element.style.top || '0')]);
296 parseInt(Element.getStyle(this.element,'left') || '0'),
297 parseInt(Element.getStyle(this.element,'top') || '0')]);
232 298 },
233 299
234 300 initDrag: function(event) {
301 if(typeof Draggable._dragging[this.element] != 'undefined' &&
302 Draggable._dragging[this.element]) return;
235 303 if(Event.isLeftClick(event)) {
236 304 // abort on form elements, fixes a Firefox issue
237 305 var src = Event.element(event);
238 306 if(src.tagName && (
239 307 src.tagName=='INPUT' ||
240 308 src.tagName=='SELECT' ||
309 src.tagName=='OPTION' ||
241 310 src.tagName=='BUTTON' ||
242 311 src.tagName=='TEXTAREA')) return;
243 312
244 if(this.element._revert) {
245 this.element._revert.cancel();
246 this.element._revert = null;
247 }
248
249 313 var pointer = [Event.pointerX(event), Event.pointerY(event)];
250 314 var pos = Position.cumulativeOffset(this.element);
251 315 this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
252 316
253 317 Draggables.activate(this);
254 318 Event.stop(event);
255 319 }
256 320 },
257 321
258 322 startDrag: function(event) {
259 323 this.dragging = true;
260 324
261 325 if(this.options.zindex) {
262 326 this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
263 327 this.element.style.zIndex = this.options.zindex;
264 328 }
265 329
266 330 if(this.options.ghosting) {
267 331 this._clone = this.element.cloneNode(true);
268 332 Position.absolutize(this.element);
269 333 this.element.parentNode.insertBefore(this._clone, this.element);
270 334 }
271 335
336 if(this.options.scroll) {
337 if (this.options.scroll == window) {
338 var where = this._getWindowScroll(this.options.scroll);
339 this.originalScrollLeft = where.left;
340 this.originalScrollTop = where.top;
341 } else {
342 this.originalScrollLeft = this.options.scroll.scrollLeft;
343 this.originalScrollTop = this.options.scroll.scrollTop;
344 }
345 }
346
272 347 Draggables.notify('onStart', this, event);
348
273 349 if(this.options.starteffect) this.options.starteffect(this.element);
274 350 },
275 351
276 352 updateDrag: function(event, pointer) {
277 353 if(!this.dragging) this.startDrag(event);
278 354 Position.prepare();
279 355 Droppables.show(pointer, this.element);
280 356 Draggables.notify('onDrag', this, event);
357
281 358 this.draw(pointer);
282 359 if(this.options.change) this.options.change(this);
283 360
361 if(this.options.scroll) {
362 this.stopScrolling();
363
364 var p;
365 if (this.options.scroll == window) {
366 with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
367 } else {
368 p = Position.page(this.options.scroll);
369 p[0] += this.options.scroll.scrollLeft + Position.deltaX;
370 p[1] += this.options.scroll.scrollTop + Position.deltaY;
371 p.push(p[0]+this.options.scroll.offsetWidth);
372 p.push(p[1]+this.options.scroll.offsetHeight);
373 }
374 var speed = [0,0];
375 if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
376 if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
377 if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
378 if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
379 this.startScrolling(speed);
380 }
381
284 382 // fix AppleWebKit rendering
285 383 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
384
286 385 Event.stop(event);
287 386 },
288 387
289 388 finishDrag: function(event, success) {
290 389 this.dragging = false;
291 390
292 391 if(this.options.ghosting) {
293 392 Position.relativize(this.element);
294 393 Element.remove(this._clone);
295 394 this._clone = null;
296 395 }
297 396
298 397 if(success) Droppables.fire(event, this.element);
299 398 Draggables.notify('onEnd', this, event);
300 399
301 400 var revert = this.options.revert;
302 401 if(revert && typeof revert == 'function') revert = revert(this.element);
303 402
304 403 var d = this.currentDelta();
305 404 if(revert && this.options.reverteffect) {
306 405 this.options.reverteffect(this.element,
307 406 d[1]-this.delta[1], d[0]-this.delta[0]);
308 407 } else {
309 408 this.delta = d;
310 409 }
311 410
312 411 if(this.options.zindex)
313 412 this.element.style.zIndex = this.originalZ;
314 413
315 414 if(this.options.endeffect)
316 415 this.options.endeffect(this.element);
317
416
318 417 Draggables.deactivate(this);
319 418 Droppables.reset();
320 419 },
321 420
322 421 keyPress: function(event) {
323 if(!event.keyCode==Event.KEY_ESC) return;
422 if(event.keyCode!=Event.KEY_ESC) return;
324 423 this.finishDrag(event, false);
325 424 Event.stop(event);
326 425 },
327 426
328 427 endDrag: function(event) {
329 428 if(!this.dragging) return;
429 this.stopScrolling();
330 430 this.finishDrag(event, true);
331 431 Event.stop(event);
332 432 },
333 433
334 434 draw: function(point) {
335 435 var pos = Position.cumulativeOffset(this.element);
436 if(this.options.ghosting) {
437 var r = Position.realOffset(this.element);
438 pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
439 }
440
336 441 var d = this.currentDelta();
337 442 pos[0] -= d[0]; pos[1] -= d[1];
338 443
339 var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this));
444 if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
445 pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
446 pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
447 }
448
449 var p = [0,1].map(function(i){
450 return (point[i]-pos[i]-this.offset[i])
451 }.bind(this));
340 452
341 453 if(this.options.snap) {
342 454 if(typeof this.options.snap == 'function') {
343 p = this.options.snap(p[0],p[1]);
455 p = this.options.snap(p[0],p[1],this);
344 456 } else {
345 457 if(this.options.snap instanceof Array) {
346 458 p = p.map( function(v, i) {
347 459 return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
348 460 } else {
349 461 p = p.map( function(v) {
350 462 return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
351 463 }
352 464 }}
353 465
354 466 var style = this.element.style;
355 467 if((!this.options.constraint) || (this.options.constraint=='horizontal'))
356 468 style.left = p[0] + "px";
357 469 if((!this.options.constraint) || (this.options.constraint=='vertical'))
358 470 style.top = p[1] + "px";
471
359 472 if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
473 },
474
475 stopScrolling: function() {
476 if(this.scrollInterval) {
477 clearInterval(this.scrollInterval);
478 this.scrollInterval = null;
479 Draggables._lastScrollPointer = null;
480 }
481 },
482
483 startScrolling: function(speed) {
484 if(!(speed[0] || speed[1])) return;
485 this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
486 this.lastScrolled = new Date();
487 this.scrollInterval = setInterval(this.scroll.bind(this), 10);
488 },
489
490 scroll: function() {
491 var current = new Date();
492 var delta = current - this.lastScrolled;
493 this.lastScrolled = current;
494 if(this.options.scroll == window) {
495 with (this._getWindowScroll(this.options.scroll)) {
496 if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
497 var d = delta / 1000;
498 this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
499 }
500 }
501 } else {
502 this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
503 this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
504 }
505
506 Position.prepare();
507 Droppables.show(Draggables._lastPointer, this.element);
508 Draggables.notify('onDrag', this);
509 if (this._isScrollChild) {
510 Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
511 Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
512 Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
513 if (Draggables._lastScrollPointer[0] < 0)
514 Draggables._lastScrollPointer[0] = 0;
515 if (Draggables._lastScrollPointer[1] < 0)
516 Draggables._lastScrollPointer[1] = 0;
517 this.draw(Draggables._lastScrollPointer);
518 }
519
520 if(this.options.change) this.options.change(this);
521 },
522
523 _getWindowScroll: function(w) {
524 var T, L, W, H;
525 with (w.document) {
526 if (w.document.documentElement && documentElement.scrollTop) {
527 T = documentElement.scrollTop;
528 L = documentElement.scrollLeft;
529 } else if (w.document.body) {
530 T = body.scrollTop;
531 L = body.scrollLeft;
532 }
533 if (w.innerWidth) {
534 W = w.innerWidth;
535 H = w.innerHeight;
536 } else if (w.document.documentElement && documentElement.clientWidth) {
537 W = documentElement.clientWidth;
538 H = documentElement.clientHeight;
539 } else {
540 W = body.offsetWidth;
541 H = body.offsetHeight
542 }
543 }
544 return { top: T, left: L, width: W, height: H };
360 545 }
361 546 }
362 547
363 548 /*--------------------------------------------------------------------------*/
364 549
365 550 var SortableObserver = Class.create();
366 551 SortableObserver.prototype = {
367 552 initialize: function(element, observer) {
368 553 this.element = $(element);
369 554 this.observer = observer;
370 555 this.lastValue = Sortable.serialize(this.element);
371 556 },
372 557
373 558 onStart: function() {
374 559 this.lastValue = Sortable.serialize(this.element);
375 560 },
376 561
377 562 onEnd: function() {
378 563 Sortable.unmark();
379 564 if(this.lastValue != Sortable.serialize(this.element))
380 565 this.observer(this.element)
381 566 }
382 567 }
383 568
384 569 var Sortable = {
385 sortables: new Array(),
570 SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
386 571
387 options: function(element){
388 element = $(element);
389 return this.sortables.detect(function(s) { return s.element == element });
572 sortables: {},
573
574 _findRootElement: function(element) {
575 while (element.tagName != "BODY") {
576 if(element.id && Sortable.sortables[element.id]) return element;
577 element = element.parentNode;
578 }
579 },
580
581 options: function(element) {
582 element = Sortable._findRootElement($(element));
583 if(!element) return;
584 return Sortable.sortables[element.id];
390 585 },
391 586
392 587 destroy: function(element){
393 element = $(element);
394 this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
588 var s = Sortable.options(element);
589
590 if(s) {
395 591 Draggables.removeObserver(s.element);
396 592 s.droppables.each(function(d){ Droppables.remove(d) });
397 593 s.draggables.invoke('destroy');
398 });
399 this.sortables = this.sortables.reject(function(s) { return s.element == element });
594
595 delete Sortable.sortables[s.element.id];
596 }
400 597 },
401
598
402 599 create: function(element) {
403 600 element = $(element);
404 601 var options = Object.extend({
405 602 element: element,
406 603 tag: 'li', // assumes li children, override with tag: 'tagname'
407 604 dropOnEmpty: false,
408 tree: false, // fixme: unimplemented
605 tree: false,
606 treeTag: 'ul',
409 607 overlap: 'vertical', // one of 'vertical', 'horizontal'
410 608 constraint: 'vertical', // one of 'vertical', 'horizontal', false
411 609 containment: element, // also takes array of elements (or id's); or false
412 610 handle: false, // or a CSS class
413 611 only: false,
612 delay: 0,
414 613 hoverclass: null,
415 614 ghosting: false,
416 format: null,
615 scroll: false,
616 scrollSensitivity: 20,
617 scrollSpeed: 15,
618 format: this.SERIALIZE_RULE,
417 619 onChange: Prototype.emptyFunction,
418 620 onUpdate: Prototype.emptyFunction
419 621 }, arguments[1] || {});
420 622
421 623 // clear any old sortable with same element
422 624 this.destroy(element);
423 625
424 626 // build options for the draggables
425 627 var options_for_draggable = {
426 628 revert: true,
629 scroll: options.scroll,
630 scrollSpeed: options.scrollSpeed,
631 scrollSensitivity: options.scrollSensitivity,
632 delay: options.delay,
427 633 ghosting: options.ghosting,
428 634 constraint: options.constraint,
429 635 handle: options.handle };
430 636
431 637 if(options.starteffect)
432 638 options_for_draggable.starteffect = options.starteffect;
433 639
434 640 if(options.reverteffect)
435 641 options_for_draggable.reverteffect = options.reverteffect;
436 642 else
437 643 if(options.ghosting) options_for_draggable.reverteffect = function(element) {
438 644 element.style.top = 0;
439 645 element.style.left = 0;
440 646 };
441 647
442 648 if(options.endeffect)
443 649 options_for_draggable.endeffect = options.endeffect;
444 650
445 651 if(options.zindex)
446 652 options_for_draggable.zindex = options.zindex;
447 653
448 654 // build options for the droppables
449 655 var options_for_droppable = {
450 656 overlap: options.overlap,
451 657 containment: options.containment,
658 tree: options.tree,
452 659 hoverclass: options.hoverclass,
453 onHover: Sortable.onHover,
454 greedy: !options.dropOnEmpty
660 onHover: Sortable.onHover
661 }
662
663 var options_for_tree = {
664 onHover: Sortable.onEmptyHover,
665 overlap: options.overlap,
666 containment: options.containment,
667 hoverclass: options.hoverclass
455 668 }
456 669
457 670 // fix for gecko engine
458 671 Element.cleanWhitespace(element);
459 672
460 673 options.draggables = [];
461 674 options.droppables = [];
462 675
463 // make it so
464
465 676 // drop on empty handling
466 if(options.dropOnEmpty) {
467 Droppables.add(element,
468 {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
677 if(options.dropOnEmpty || options.tree) {
678 Droppables.add(element, options_for_tree);
469 679 options.droppables.push(element);
470 680 }
471 681
472 682 (this.findElements(element, options) || []).each( function(e) {
473 683 // handles are per-draggable
474 684 var handle = options.handle ?
475 Element.childrenWithClassName(e, options.handle)[0] : e;
685 $(e).down('.'+options.handle,0) : e;
476 686 options.draggables.push(
477 687 new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
478 688 Droppables.add(e, options_for_droppable);
689 if(options.tree) e.treeNode = element;
479 690 options.droppables.push(e);
480 691 });
692
693 if(options.tree) {
694 (Sortable.findTreeElements(element, options) || []).each( function(e) {
695 Droppables.add(e, options_for_tree);
696 e.treeNode = element;
697 options.droppables.push(e);
698 });
699 }
481 700
482 701 // keep reference
483 this.sortables.push(options);
702 this.sortables[element.id] = options;
484 703
485 704 // for onupdate
486 705 Draggables.addObserver(new SortableObserver(element, options.onUpdate));
487 706
488 707 },
489 708
490 709 // return all suitable-for-sortable elements in a guaranteed order
491 710 findElements: function(element, options) {
492 if(!element.hasChildNodes()) return null;
493 var elements = [];
494 $A(element.childNodes).each( function(e) {
495 if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() &&
496 (!options.only || (Element.hasClassName(e, options.only))))
497 elements.push(e);
498 if(options.tree) {
499 var grandchildren = this.findElements(e, options);
500 if(grandchildren) elements.push(grandchildren);
501 }
502 });
503
504 return (elements.length>0 ? elements.flatten() : null);
711 return Element.findChildren(
712 element, options.only, options.tree ? true : false, options.tag);
713 },
714
715 findTreeElements: function(element, options) {
716 return Element.findChildren(
717 element, options.only, options.tree ? true : false, options.treeTag);
505 718 },
506 719
507 720 onHover: function(element, dropon, overlap) {
508 if(overlap>0.5) {
721 if(Element.isParent(dropon, element)) return;
722
723 if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
724 return;
725 } else if(overlap>0.5) {
509 726 Sortable.mark(dropon, 'before');
510 727 if(dropon.previousSibling != element) {
511 728 var oldParentNode = element.parentNode;
512 729 element.style.visibility = "hidden"; // fix gecko rendering
513 730 dropon.parentNode.insertBefore(element, dropon);
514 731 if(dropon.parentNode!=oldParentNode)
515 732 Sortable.options(oldParentNode).onChange(element);
516 733 Sortable.options(dropon.parentNode).onChange(element);
517 734 }
518 735 } else {
519 736 Sortable.mark(dropon, 'after');
520 737 var nextElement = dropon.nextSibling || null;
521 738 if(nextElement != element) {
522 739 var oldParentNode = element.parentNode;
523 740 element.style.visibility = "hidden"; // fix gecko rendering
524 741 dropon.parentNode.insertBefore(element, nextElement);
525 742 if(dropon.parentNode!=oldParentNode)
526 743 Sortable.options(oldParentNode).onChange(element);
527 744 Sortable.options(dropon.parentNode).onChange(element);
528 745 }
529 746 }
530 747 },
531
532 onEmptyHover: function(element, dropon) {
533 if(element.parentNode!=dropon) {
534 var oldParentNode = element.parentNode;
535 dropon.appendChild(element);
748
749 onEmptyHover: function(element, dropon, overlap) {
750 var oldParentNode = element.parentNode;
751 var droponOptions = Sortable.options(dropon);
752
753 if(!Element.isParent(dropon, element)) {
754 var index;
755
756 var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
757 var child = null;
758
759 if(children) {
760 var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
761
762 for (index = 0; index < children.length; index += 1) {
763 if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
764 offset -= Element.offsetSize (children[index], droponOptions.overlap);
765 } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
766 child = index + 1 < children.length ? children[index + 1] : null;
767 break;
768 } else {
769 child = children[index];
770 break;
771 }
772 }
773 }
774
775 dropon.insertBefore(element, child);
776
536 777 Sortable.options(oldParentNode).onChange(element);
537 Sortable.options(dropon).onChange(element);
778 droponOptions.onChange(element);
538 779 }
539 780 },
540 781
541 782 unmark: function() {
542 if(Sortable._marker) Element.hide(Sortable._marker);
783 if(Sortable._marker) Sortable._marker.hide();
543 784 },
544 785
545 786 mark: function(dropon, position) {
546 787 // mark on ghosting only
547 788 var sortable = Sortable.options(dropon.parentNode);
548 789 if(sortable && !sortable.ghosting) return;
549 790
550 791 if(!Sortable._marker) {
551 Sortable._marker = $('dropmarker') || document.createElement('DIV');
552 Element.hide(Sortable._marker);
553 Element.addClassName(Sortable._marker, 'dropmarker');
554 Sortable._marker.style.position = 'absolute';
792 Sortable._marker =
793 ($('dropmarker') || Element.extend(document.createElement('DIV'))).
794 hide().addClassName('dropmarker').setStyle({position:'absolute'});
555 795 document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
556 796 }
557 797 var offsets = Position.cumulativeOffset(dropon);
558 Sortable._marker.style.left = offsets[0] + 'px';
559 Sortable._marker.style.top = offsets[1] + 'px';
798 Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
560 799
561 800 if(position=='after')
562 801 if(sortable.overlap == 'horizontal')
563 Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
802 Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
564 803 else
565 Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
804 Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
566 805
567 Element.show(Sortable._marker);
806 Sortable._marker.show();
568 807 },
808
809 _tree: function(element, options, parent) {
810 var children = Sortable.findElements(element, options) || [];
811
812 for (var i = 0; i < children.length; ++i) {
813 var match = children[i].id.match(options.format);
569 814
570 serialize: function(element) {
815 if (!match) continue;
816
817 var child = {
818 id: encodeURIComponent(match ? match[1] : null),
819 element: element,
820 parent: parent,
821 children: [],
822 position: parent.children.length,
823 container: $(children[i]).down(options.treeTag)
824 }
825
826 /* Get the element containing the children and recurse over it */
827 if (child.container)
828 this._tree(child.container, options, child)
829
830 parent.children.push (child);
831 }
832
833 return parent;
834 },
835
836 tree: function(element) {
571 837 element = $(element);
572 838 var sortableOptions = this.options(element);
573 839 var options = Object.extend({
574 tag: sortableOptions.tag,
840 tag: sortableOptions.tag,
841 treeTag: sortableOptions.treeTag,
575 842 only: sortableOptions.only,
576 843 name: element.id,
577 format: sortableOptions.format || /^[^_]*_(.*)$/
844 format: sortableOptions.format
578 845 }, arguments[1] || {});
846
847 var root = {
848 id: null,
849 parent: null,
850 children: [],
851 container: element,
852 position: 0
853 }
854
855 return Sortable._tree(element, options, root);
856 },
857
858 /* Construct a [i] index for a particular node */
859 _constructIndex: function(node) {
860 var index = '';
861 do {
862 if (node.id) index = '[' + node.position + ']' + index;
863 } while ((node = node.parent) != null);
864 return index;
865 },
866
867 sequence: function(element) {
868 element = $(element);
869 var options = Object.extend(this.options(element), arguments[1] || {});
870
579 871 return $(this.findElements(element, options) || []).map( function(item) {
580 return (encodeURIComponent(options.name) + "[]=" +
581 encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
582 }).join("&");
872 return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
873 });
874 },
875
876 setSequence: function(element, new_sequence) {
877 element = $(element);
878 var options = Object.extend(this.options(element), arguments[2] || {});
879
880 var nodeMap = {};
881 this.findElements(element, options).each( function(n) {
882 if (n.id.match(options.format))
883 nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
884 n.parentNode.removeChild(n);
885 });
886
887 new_sequence.each(function(ident) {
888 var n = nodeMap[ident];
889 if (n) {
890 n[1].appendChild(n[0]);
891 delete nodeMap[ident];
892 }
893 });
894 },
895
896 serialize: function(element) {
897 element = $(element);
898 var options = Object.extend(Sortable.options(element), arguments[1] || {});
899 var name = encodeURIComponent(
900 (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
901
902 if (options.tree) {
903 return Sortable.tree(element, arguments[1]).children.map( function (item) {
904 return [name + Sortable._constructIndex(item) + "[id]=" +
905 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
906 }).flatten().join('&');
907 } else {
908 return Sortable.sequence(element, arguments[1]).map( function(item) {
909 return name + "[]=" + encodeURIComponent(item);
910 }).join('&');
911 }
583 912 }
584 } No newline at end of file
913 }
914
915 // Returns true if child is contained within element
916 Element.isParent = function(child, element) {
917 if (!child.parentNode || child == element) return false;
918 if (child.parentNode == element) return true;
919 return Element.isParent(child.parentNode, element);
920 }
921
922 Element.findChildren = function(element, only, recursive, tagName) {
923 if(!element.hasChildNodes()) return null;
924 tagName = tagName.toUpperCase();
925 if(only) only = [only].flatten();
926 var elements = [];
927 $A(element.childNodes).each( function(e) {
928 if(e.tagName && e.tagName.toUpperCase()==tagName &&
929 (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
930 elements.push(e);
931 if(recursive) {
932 var grandchildren = Element.findChildren(e, only, recursive, tagName);
933 if(grandchildren) elements.push(grandchildren);
934 }
935 });
936
937 return (elements.length>0 ? elements.flatten() : []);
938 }
939
940 Element.offsetSize = function (element, type) {
941 return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
942 }
This diff has been collapsed as it changes many lines, (800 lines changed) Show them Hide them
@@ -1,854 +1,1088
1 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
1 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 2 // Contributors:
3 3 // Justin Palmer (http://encytemedia.com/)
4 4 // Mark Pilgrim (http://diveintomark.org/)
5 5 // Martin Bialasinki
6 6 //
7 // See scriptaculous.js for full license.
7 // script.aculo.us is freely distributable under the terms of an MIT-style license.
8 // For details, see the script.aculo.us web site: http://script.aculo.us/
8 9
9 /* ------------- element ext -------------- */
10
11 10 // converts rgb() and #xxx to #xxxxxx format,
12 11 // returns self (or first argument) if not convertable
13 12 String.prototype.parseColor = function() {
14 var color = '#';
13 var color = '#';
15 14 if(this.slice(0,4) == 'rgb(') {
16 15 var cols = this.slice(4,this.length-1).split(',');
17 16 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
18 17 } else {
19 18 if(this.slice(0,1) == '#') {
20 19 if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
21 20 if(this.length==7) color = this.toLowerCase();
22 21 }
23 22 }
24 23 return(color.length==7 ? color : (arguments[0] || this));
25 }
24 }
26 25
27 Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
28 var children = $(element).childNodes;
29 var text = '';
30 var classtest = new RegExp('^([^ ]+ )*' + ignoreclass+ '( [^ ]+)*$','i');
31
32 for (var i = 0; i < children.length; i++) {
33 if(children[i].nodeType==3) {
34 text+=children[i].nodeValue;
35 } else {
36 if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
37 text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
38 }
39 }
40
41 return text;
26 /*--------------------------------------------------------------------------*/
27
28 Element.collectTextNodes = function(element) {
29 return $A($(element).childNodes).collect( function(node) {
30 return (node.nodeType==3 ? node.nodeValue :
31 (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
32 }).flatten().join('');
42 33 }
43 34
44 Element.setStyle = function(element, style) {
45 element = $(element);
46 for(k in style) element.style[k.camelize()] = style[k];
35 Element.collectTextNodesIgnoreClass = function(element, className) {
36 return $A($(element).childNodes).collect( function(node) {
37 return (node.nodeType==3 ? node.nodeValue :
38 ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
39 Element.collectTextNodesIgnoreClass(node, className) : ''));
40 }).flatten().join('');
47 41 }
48 42
49 Element.setContentZoom = function(element, percent) {
50 Element.setStyle(element, {fontSize: (percent/100) + 'em'});
51 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
43 Element.setContentZoom = function(element, percent) {
44 element = $(element);
45 element.setStyle({fontSize: (percent/100) + 'em'});
46 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
47 return element;
52 48 }
53 49
54 Element.getOpacity = function(element){
50 Element.getOpacity = function(element){
51 element = $(element);
55 52 var opacity;
56 if (opacity = Element.getStyle(element, 'opacity'))
53 if (opacity = element.getStyle('opacity'))
57 54 return parseFloat(opacity);
58 if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))
55 if (opacity = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
59 56 if(opacity[1]) return parseFloat(opacity[1]) / 100;
60 57 return 1.0;
61 58 }
62 59
63 60 Element.setOpacity = function(element, value){
64 61 element= $(element);
65 62 if (value == 1){
66 Element.setStyle(element, { opacity:
63 element.setStyle({ opacity:
67 64 (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
68 0.999999 : null });
69 if(/MSIE/.test(navigator.userAgent))
70 Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
65 0.999999 : 1.0 });
66 if(/MSIE/.test(navigator.userAgent) && !window.opera)
67 element.setStyle({filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
71 68 } else {
72 69 if(value < 0.00001) value = 0;
73 Element.setStyle(element, {opacity: value});
74 if(/MSIE/.test(navigator.userAgent))
75 Element.setStyle(element,
76 { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
77 'alpha(opacity='+value*100+')' });
78 }
70 element.setStyle({opacity: value});
71 if(/MSIE/.test(navigator.userAgent) && !window.opera)
72 element.setStyle(
73 { filter: element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
74 'alpha(opacity='+value*100+')' });
75 }
76 return element;
79 77 }
80 78
81 79 Element.getInlineOpacity = function(element){
82 80 return $(element).style.opacity || '';
83 81 }
84 82
85 Element.childrenWithClassName = function(element, className) {
86 return $A($(element).getElementsByTagName('*')).select(
87 function(c) { return Element.hasClassName(c, className) });
88 }
83 Element.forceRerendering = function(element) {
84 try {
85 element = $(element);
86 var n = document.createTextNode(' ');
87 element.appendChild(n);
88 element.removeChild(n);
89 } catch(e) { }
90 };
91
92 /*--------------------------------------------------------------------------*/
89 93
90 94 Array.prototype.call = function() {
91 95 var args = arguments;
92 96 this.each(function(f){ f.apply(this, args) });
93 97 }
94 98
95 99 /*--------------------------------------------------------------------------*/
96 100
97 101 var Effect = {
102 _elementDoesNotExistError: {
103 name: 'ElementDoesNotExistError',
104 message: 'The specified DOM element does not exist, but is required for this effect to operate'
105 },
98 106 tagifyText: function(element) {
107 if(typeof Builder == 'undefined')
108 throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
109
99 110 var tagifyStyle = 'position:relative';
100 if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
111 if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1';
112
101 113 element = $(element);
102 114 $A(element.childNodes).each( function(child) {
103 115 if(child.nodeType==3) {
104 116 child.nodeValue.toArray().each( function(character) {
105 117 element.insertBefore(
106 118 Builder.node('span',{style: tagifyStyle},
107 119 character == ' ' ? String.fromCharCode(160) : character),
108 120 child);
109 121 });
110 122 Element.remove(child);
111 123 }
112 124 });
113 125 },
114 126 multiple: function(element, effect) {
115 127 var elements;
116 128 if(((typeof element == 'object') ||
117 129 (typeof element == 'function')) &&
118 130 (element.length))
119 131 elements = element;
120 132 else
121 133 elements = $(element).childNodes;
122 134
123 135 var options = Object.extend({
124 136 speed: 0.1,
125 137 delay: 0.0
126 138 }, arguments[2] || {});
127 139 var masterDelay = options.delay;
128 140
129 141 $A(elements).each( function(element, index) {
130 142 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
131 143 });
144 },
145 PAIRS: {
146 'slide': ['SlideDown','SlideUp'],
147 'blind': ['BlindDown','BlindUp'],
148 'appear': ['Appear','Fade']
149 },
150 toggle: function(element, effect) {
151 element = $(element);
152 effect = (effect || 'appear').toLowerCase();
153 var options = Object.extend({
154 queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
155 }, arguments[2] || {});
156 Effect[element.visible() ?
157 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
132 158 }
133 159 };
134 160
135 161 var Effect2 = Effect; // deprecated
136 162
137 163 /* ------------- transitions ------------- */
138 164
139 Effect.Transitions = {}
140
141 Effect.Transitions.linear = function(pos) {
142 return pos;
143 }
144 Effect.Transitions.sinoidal = function(pos) {
145 return (-Math.cos(pos*Math.PI)/2) + 0.5;
146 }
147 Effect.Transitions.reverse = function(pos) {
148 return 1-pos;
149 }
150 Effect.Transitions.flicker = function(pos) {
151 return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
152 }
153 Effect.Transitions.wobble = function(pos) {
154 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
155 }
156 Effect.Transitions.pulse = function(pos) {
157 return (Math.floor(pos*10) % 2 == 0 ?
158 (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
159 }
160 Effect.Transitions.none = function(pos) {
161 return 0;
162 }
163 Effect.Transitions.full = function(pos) {
164 return 1;
165 }
165 Effect.Transitions = {
166 linear: Prototype.K,
167 sinoidal: function(pos) {
168 return (-Math.cos(pos*Math.PI)/2) + 0.5;
169 },
170 reverse: function(pos) {
171 return 1-pos;
172 },
173 flicker: function(pos) {
174 return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
175 },
176 wobble: function(pos) {
177 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
178 },
179 pulse: function(pos, pulses) {
180 pulses = pulses || 5;
181 return (
182 Math.round((pos % (1/pulses)) * pulses) == 0 ?
183 ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) :
184 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
185 );
186 },
187 none: function(pos) {
188 return 0;
189 },
190 full: function(pos) {
191 return 1;
192 }
193 };
166 194
167 195 /* ------------- core effects ------------- */
168 196
169 Effect.Queue = {
170 effects: [],
197 Effect.ScopedQueue = Class.create();
198 Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
199 initialize: function() {
200 this.effects = [];
201 this.interval = null;
202 },
171 203 _each: function(iterator) {
172 204 this.effects._each(iterator);
173 205 },
174 interval: null,
175 206 add: function(effect) {
176 207 var timestamp = new Date().getTime();
177 208
178 switch(effect.options.queue) {
209 var position = (typeof effect.options.queue == 'string') ?
210 effect.options.queue : effect.options.queue.position;
211
212 switch(position) {
179 213 case 'front':
180 214 // move unstarted effects after this effect
181 215 this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
182 216 e.startOn += effect.finishOn;
183 217 e.finishOn += effect.finishOn;
184 218 });
185 219 break;
220 case 'with-last':
221 timestamp = this.effects.pluck('startOn').max() || timestamp;
222 break;
186 223 case 'end':
187 224 // start effect after last queued effect has finished
188 225 timestamp = this.effects.pluck('finishOn').max() || timestamp;
189 226 break;
190 227 }
191 228
192 229 effect.startOn += timestamp;
193 230 effect.finishOn += timestamp;
194 this.effects.push(effect);
231
232 if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
233 this.effects.push(effect);
234
195 235 if(!this.interval)
196 236 this.interval = setInterval(this.loop.bind(this), 40);
197 237 },
198 238 remove: function(effect) {
199 239 this.effects = this.effects.reject(function(e) { return e==effect });
200 240 if(this.effects.length == 0) {
201 241 clearInterval(this.interval);
202 242 this.interval = null;
203 243 }
204 244 },
205 245 loop: function() {
206 246 var timePos = new Date().getTime();
207 247 this.effects.invoke('loop', timePos);
208 248 }
249 });
250
251 Effect.Queues = {
252 instances: $H(),
253 get: function(queueName) {
254 if(typeof queueName != 'string') return queueName;
255
256 if(!this.instances[queueName])
257 this.instances[queueName] = new Effect.ScopedQueue();
258
259 return this.instances[queueName];
260 }
261 }
262 Effect.Queue = Effect.Queues.get('global');
263
264 Effect.DefaultOptions = {
265 transition: Effect.Transitions.sinoidal,
266 duration: 1.0, // seconds
267 fps: 25.0, // max. 25fps due to Effect.Queue implementation
268 sync: false, // true for combining
269 from: 0.0,
270 to: 1.0,
271 delay: 0.0,
272 queue: 'parallel'
209 273 }
210 Object.extend(Effect.Queue, Enumerable);
211 274
212 275 Effect.Base = function() {};
213 276 Effect.Base.prototype = {
214 277 position: null,
215 setOptions: function(options) {
216 this.options = Object.extend({
217 transition: Effect.Transitions.sinoidal,
218 duration: 1.0, // seconds
219 fps: 25.0, // max. 25fps due to Effect.Queue implementation
220 sync: false, // true for combining
221 from: 0.0,
222 to: 1.0,
223 delay: 0.0,
224 queue: 'parallel'
225 }, options || {});
226 },
227 278 start: function(options) {
228 this.setOptions(options || {});
279 this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
229 280 this.currentFrame = 0;
230 281 this.state = 'idle';
231 282 this.startOn = this.options.delay*1000;
232 283 this.finishOn = this.startOn + (this.options.duration*1000);
233 284 this.event('beforeStart');
234 if(!this.options.sync) Effect.Queue.add(this);
285 if(!this.options.sync)
286 Effect.Queues.get(typeof this.options.queue == 'string' ?
287 'global' : this.options.queue.scope).add(this);
235 288 },
236 289 loop: function(timePos) {
237 290 if(timePos >= this.startOn) {
238 291 if(timePos >= this.finishOn) {
239 292 this.render(1.0);
240 293 this.cancel();
241 294 this.event('beforeFinish');
242 295 if(this.finish) this.finish();
243 296 this.event('afterFinish');
244 297 return;
245 298 }
246 299 var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
247 300 var frame = Math.round(pos * this.options.fps * this.options.duration);
248 301 if(frame > this.currentFrame) {
249 302 this.render(pos);
250 303 this.currentFrame = frame;
251 304 }
252 305 }
253 306 },
254 307 render: function(pos) {
255 308 if(this.state == 'idle') {
256 309 this.state = 'running';
257 310 this.event('beforeSetup');
258 311 if(this.setup) this.setup();
259 312 this.event('afterSetup');
260 313 }
261 314 if(this.state == 'running') {
262 315 if(this.options.transition) pos = this.options.transition(pos);
263 316 pos *= (this.options.to-this.options.from);
264 317 pos += this.options.from;
265 318 this.position = pos;
266 319 this.event('beforeUpdate');
267 320 if(this.update) this.update(pos);
268 321 this.event('afterUpdate');
269 322 }
270 323 },
271 324 cancel: function() {
272 if(!this.options.sync) Effect.Queue.remove(this);
325 if(!this.options.sync)
326 Effect.Queues.get(typeof this.options.queue == 'string' ?
327 'global' : this.options.queue.scope).remove(this);
273 328 this.state = 'finished';
274 329 },
275 330 event: function(eventName) {
276 331 if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
277 332 if(this.options[eventName]) this.options[eventName](this);
278 333 },
279 334 inspect: function() {
280 335 return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
281 336 }
282 337 }
283 338
284 339 Effect.Parallel = Class.create();
285 340 Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
286 341 initialize: function(effects) {
287 342 this.effects = effects || [];
288 343 this.start(arguments[1]);
289 344 },
290 345 update: function(position) {
291 346 this.effects.invoke('render', position);
292 347 },
293 348 finish: function(position) {
294 349 this.effects.each( function(effect) {
295 350 effect.render(1.0);
296 351 effect.cancel();
297 352 effect.event('beforeFinish');
298 353 if(effect.finish) effect.finish(position);
299 354 effect.event('afterFinish');
300 355 });
301 356 }
302 357 });
303 358
359 Effect.Event = Class.create();
360 Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
361 initialize: function() {
362 var options = Object.extend({
363 duration: 0
364 }, arguments[0] || {});
365 this.start(options);
366 },
367 update: Prototype.emptyFunction
368 });
369
304 370 Effect.Opacity = Class.create();
305 371 Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
306 372 initialize: function(element) {
307 373 this.element = $(element);
374 if(!this.element) throw(Effect._elementDoesNotExistError);
308 375 // make this work on IE on elements without 'layout'
309 if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
310 Element.setStyle(this.element, {zoom: 1});
376 if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout))
377 this.element.setStyle({zoom: 1});
311 378 var options = Object.extend({
312 from: Element.getOpacity(this.element) || 0.0,
379 from: this.element.getOpacity() || 0.0,
313 380 to: 1.0
314 381 }, arguments[1] || {});
315 382 this.start(options);
316 383 },
317 384 update: function(position) {
318 Element.setOpacity(this.element, position);
385 this.element.setOpacity(position);
319 386 }
320 387 });
321 388
322 Effect.MoveBy = Class.create();
323 Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
324 initialize: function(element, toTop, toLeft) {
325 this.element = $(element);
326 this.toTop = toTop;
327 this.toLeft = toLeft;
328 this.start(arguments[3]);
389 Effect.Move = Class.create();
390 Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
391 initialize: function(element) {
392 this.element = $(element);
393 if(!this.element) throw(Effect._elementDoesNotExistError);
394 var options = Object.extend({
395 x: 0,
396 y: 0,
397 mode: 'relative'
398 }, arguments[1] || {});
399 this.start(options);
329 400 },
330 401 setup: function() {
331 402 // Bug in Opera: Opera returns the "real" position of a static element or
332 403 // relative element that does not have top/left explicitly set.
333 404 // ==> Always set top and left for position relative elements in your stylesheets
334 405 // (to 0 if you do not need them)
335 Element.makePositioned(this.element);
336 this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0');
337 this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
406 this.element.makePositioned();
407 this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
408 this.originalTop = parseFloat(this.element.getStyle('top') || '0');
409 if(this.options.mode == 'absolute') {
410 // absolute movement, so we need to calc deltaX and deltaY
411 this.options.x = this.options.x - this.originalLeft;
412 this.options.y = this.options.y - this.originalTop;
413 }
338 414 },
339 415 update: function(position) {
340 Element.setStyle(this.element, {
341 top: this.toTop * position + this.originalTop + 'px',
342 left: this.toLeft * position + this.originalLeft + 'px'
416 this.element.setStyle({
417 left: Math.round(this.options.x * position + this.originalLeft) + 'px',
418 top: Math.round(this.options.y * position + this.originalTop) + 'px'
343 419 });
344 420 }
345 421 });
346 422
423 // for backwards compatibility
424 Effect.MoveBy = function(element, toTop, toLeft) {
425 return new Effect.Move(element,
426 Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
427 };
428
347 429 Effect.Scale = Class.create();
348 430 Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
349 431 initialize: function(element, percent) {
350 this.element = $(element)
432 this.element = $(element);
433 if(!this.element) throw(Effect._elementDoesNotExistError);
351 434 var options = Object.extend({
352 435 scaleX: true,
353 436 scaleY: true,
354 437 scaleContent: true,
355 438 scaleFromCenter: false,
356 439 scaleMode: 'box', // 'box' or 'contents' or {} with provided values
357 440 scaleFrom: 100.0,
358 441 scaleTo: percent
359 442 }, arguments[2] || {});
360 443 this.start(options);
361 444 },
362 445 setup: function() {
363 446 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
364 this.elementPositioning = Element.getStyle(this.element,'position');
447 this.elementPositioning = this.element.getStyle('position');
365 448
366 449 this.originalStyle = {};
367 450 ['top','left','width','height','fontSize'].each( function(k) {
368 451 this.originalStyle[k] = this.element.style[k];
369 452 }.bind(this));
370 453
371 454 this.originalTop = this.element.offsetTop;
372 455 this.originalLeft = this.element.offsetLeft;
373 456
374 var fontSize = Element.getStyle(this.element,'font-size') || '100%';
375 ['em','px','%'].each( function(fontSizeType) {
457 var fontSize = this.element.getStyle('font-size') || '100%';
458 ['em','px','%','pt'].each( function(fontSizeType) {
376 459 if(fontSize.indexOf(fontSizeType)>0) {
377 460 this.fontSize = parseFloat(fontSize);
378 461 this.fontSizeType = fontSizeType;
379 462 }
380 463 }.bind(this));
381 464
382 465 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
383 466
384 467 this.dims = null;
385 468 if(this.options.scaleMode=='box')
386 469 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
387 470 if(/^content/.test(this.options.scaleMode))
388 471 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
389 472 if(!this.dims)
390 473 this.dims = [this.options.scaleMode.originalHeight,
391 474 this.options.scaleMode.originalWidth];
392 475 },
393 476 update: function(position) {
394 477 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
395 478 if(this.options.scaleContent && this.fontSize)
396 Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType });
479 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
397 480 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
398 481 },
399 482 finish: function(position) {
400 if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle);
483 if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
401 484 },
402 485 setDimensions: function(height, width) {
403 486 var d = {};
404 if(this.options.scaleX) d.width = width + 'px';
405 if(this.options.scaleY) d.height = height + 'px';
487 if(this.options.scaleX) d.width = Math.round(width) + 'px';
488 if(this.options.scaleY) d.height = Math.round(height) + 'px';
406 489 if(this.options.scaleFromCenter) {
407 490 var topd = (height - this.dims[0])/2;
408 491 var leftd = (width - this.dims[1])/2;
409 492 if(this.elementPositioning == 'absolute') {
410 493 if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
411 494 if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
412 495 } else {
413 496 if(this.options.scaleY) d.top = -topd + 'px';
414 497 if(this.options.scaleX) d.left = -leftd + 'px';
415 498 }
416 499 }
417 Element.setStyle(this.element, d);
500 this.element.setStyle(d);
418 501 }
419 502 });
420 503
421 504 Effect.Highlight = Class.create();
422 505 Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
423 506 initialize: function(element) {
424 507 this.element = $(element);
508 if(!this.element) throw(Effect._elementDoesNotExistError);
425 509 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
426 510 this.start(options);
427 511 },
428 512 setup: function() {
429 513 // Prevent executing on elements not in the layout flow
430 if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; }
514 if(this.element.getStyle('display')=='none') { this.cancel(); return; }
431 515 // Disable background image during the effect
432 516 this.oldStyle = {
433 backgroundImage: Element.getStyle(this.element, 'background-image') };
434 Element.setStyle(this.element, {backgroundImage: 'none'});
517 backgroundImage: this.element.getStyle('background-image') };
518 this.element.setStyle({backgroundImage: 'none'});
435 519 if(!this.options.endcolor)
436 this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
520 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
437 521 if(!this.options.restorecolor)
438 this.options.restorecolor = Element.getStyle(this.element, 'background-color');
522 this.options.restorecolor = this.element.getStyle('background-color');
439 523 // init color calculations
440 524 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
441 525 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));
442 526 },
443 527 update: function(position) {
444 Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){
528 this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
445 529 return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
446 530 },
447 531 finish: function() {
448 Element.setStyle(this.element, Object.extend(this.oldStyle, {
532 this.element.setStyle(Object.extend(this.oldStyle, {
449 533 backgroundColor: this.options.restorecolor
450 534 }));
451 535 }
452 536 });
453 537
454 538 Effect.ScrollTo = Class.create();
455 539 Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
456 540 initialize: function(element) {
457 541 this.element = $(element);
458 542 this.start(arguments[1] || {});
459 543 },
460 544 setup: function() {
461 545 Position.prepare();
462 546 var offsets = Position.cumulativeOffset(this.element);
463 547 if(this.options.offset) offsets[1] += this.options.offset;
464 548 var max = window.innerHeight ?
465 549 window.height - window.innerHeight :
466 550 document.body.scrollHeight -
467 551 (document.documentElement.clientHeight ?
468 552 document.documentElement.clientHeight : document.body.clientHeight);
469 553 this.scrollStart = Position.deltaY;
470 554 this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
471 555 },
472 556 update: function(position) {
473 557 Position.prepare();
474 558 window.scrollTo(Position.deltaX,
475 559 this.scrollStart + (position*this.delta));
476 560 }
477 561 });
478 562
479 563 /* ------------- combination effects ------------- */
480 564
481 565 Effect.Fade = function(element) {
482 var oldOpacity = Element.getInlineOpacity(element);
566 element = $(element);
567 var oldOpacity = element.getInlineOpacity();
483 568 var options = Object.extend({
484 from: Element.getOpacity(element) || 1.0,
569 from: element.getOpacity() || 1.0,
485 570 to: 0.0,
486 afterFinishInternal: function(effect) { with(Element) {
571 afterFinishInternal: function(effect) {
487 572 if(effect.options.to!=0) return;
488 hide(effect.element);
489 setStyle(effect.element, {opacity: oldOpacity}); }}
490 }, arguments[1] || {});
573 effect.element.hide().setStyle({opacity: oldOpacity});
574 }}, arguments[1] || {});
491 575 return new Effect.Opacity(element,options);
492 576 }
493 577
494 578 Effect.Appear = function(element) {
579 element = $(element);
495 580 var options = Object.extend({
496 from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0),
581 from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
497 582 to: 1.0,
498 beforeSetup: function(effect) { with(Element) {
499 setOpacity(effect.element, effect.options.from);
500 show(effect.element); }}
501 }, arguments[1] || {});
583 // force Safari to render floated elements properly
584 afterFinishInternal: function(effect) {
585 effect.element.forceRerendering();
586 },
587 beforeSetup: function(effect) {
588 effect.element.setOpacity(effect.options.from).show();
589 }}, arguments[1] || {});
502 590 return new Effect.Opacity(element,options);
503 591 }
504 592
505 593 Effect.Puff = function(element) {
506 594 element = $(element);
507 var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') };
595 var oldStyle = {
596 opacity: element.getInlineOpacity(),
597 position: element.getStyle('position'),
598 top: element.style.top,
599 left: element.style.left,
600 width: element.style.width,
601 height: element.style.height
602 };
508 603 return new Effect.Parallel(
509 604 [ new Effect.Scale(element, 200,
510 605 { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
511 606 new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
512 607 Object.extend({ duration: 1.0,
513 beforeSetupInternal: function(effect) { with(Element) {
514 setStyle(effect.effects[0].element, {position: 'absolute'}); }},
515 afterFinishInternal: function(effect) { with(Element) {
516 hide(effect.effects[0].element);
517 setStyle(effect.effects[0].element, oldStyle); }}
608 beforeSetupInternal: function(effect) {
609 Position.absolutize(effect.effects[0].element)
610 },
611 afterFinishInternal: function(effect) {
612 effect.effects[0].element.hide().setStyle(oldStyle); }
518 613 }, arguments[1] || {})
519 614 );
520 615 }
521 616
522 617 Effect.BlindUp = function(element) {
523 618 element = $(element);
524 Element.makeClipping(element);
525 return new Effect.Scale(element, 0,
619 element.makeClipping();
620 return new Effect.Scale(element, 0,
526 621 Object.extend({ scaleContent: false,
527 622 scaleX: false,
528 623 restoreAfterFinish: true,
529 afterFinishInternal: function(effect) { with(Element) {
530 [hide, undoClipping].call(effect.element); }}
624 afterFinishInternal: function(effect) {
625 effect.element.hide().undoClipping();
626 }
531 627 }, arguments[1] || {})
532 628 );
533 629 }
534 630
535 631 Effect.BlindDown = function(element) {
536 632 element = $(element);
537 var oldHeight = Element.getStyle(element, 'height');
538 var elementDimensions = Element.getDimensions(element);
539 return new Effect.Scale(element, 100,
540 Object.extend({ scaleContent: false,
541 scaleX: false,
542 scaleFrom: 0,
543 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
544 restoreAfterFinish: true,
545 afterSetup: function(effect) { with(Element) {
546 makeClipping(effect.element);
547 setStyle(effect.element, {height: '0px'});
548 show(effect.element);
549 }},
550 afterFinishInternal: function(effect) { with(Element) {
551 undoClipping(effect.element);
552 setStyle(effect.element, {height: oldHeight});
553 }}
554 }, arguments[1] || {})
555 );
633 var elementDimensions = element.getDimensions();
634 return new Effect.Scale(element, 100, Object.extend({
635 scaleContent: false,
636 scaleX: false,
637 scaleFrom: 0,
638 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
639 restoreAfterFinish: true,
640 afterSetup: function(effect) {
641 effect.element.makeClipping().setStyle({height: '0px'}).show();
642 },
643 afterFinishInternal: function(effect) {
644 effect.element.undoClipping();
645 }
646 }, arguments[1] || {}));
556 647 }
557 648
558 649 Effect.SwitchOff = function(element) {
559 650 element = $(element);
560 var oldOpacity = Element.getInlineOpacity(element);
561 return new Effect.Appear(element, {
651 var oldOpacity = element.getInlineOpacity();
652 return new Effect.Appear(element, Object.extend({
562 653 duration: 0.4,
563 654 from: 0,
564 655 transition: Effect.Transitions.flicker,
565 656 afterFinishInternal: function(effect) {
566 657 new Effect.Scale(effect.element, 1, {
567 658 duration: 0.3, scaleFromCenter: true,
568 659 scaleX: false, scaleContent: false, restoreAfterFinish: true,
569 beforeSetup: function(effect) { with(Element) {
570 [makePositioned,makeClipping].call(effect.element);
571 }},
572 afterFinishInternal: function(effect) { with(Element) {
573 [hide,undoClipping,undoPositioned].call(effect.element);
574 setStyle(effect.element, {opacity: oldOpacity});
575 }}
660 beforeSetup: function(effect) {
661 effect.element.makePositioned().makeClipping();
662 },
663 afterFinishInternal: function(effect) {
664 effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
665 }
576 666 })
577 667 }
578 });
668 }, arguments[1] || {}));
579 669 }
580 670
581 671 Effect.DropOut = function(element) {
582 672 element = $(element);
583 673 var oldStyle = {
584 top: Element.getStyle(element, 'top'),
585 left: Element.getStyle(element, 'left'),
586 opacity: Element.getInlineOpacity(element) };
674 top: element.getStyle('top'),
675 left: element.getStyle('left'),
676 opacity: element.getInlineOpacity() };
587 677 return new Effect.Parallel(
588 [ new Effect.MoveBy(element, 100, 0, { sync: true }),
678 [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
589 679 new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
590 680 Object.extend(
591 681 { duration: 0.5,
592 beforeSetup: function(effect) { with(Element) {
593 makePositioned(effect.effects[0].element); }},
594 afterFinishInternal: function(effect) { with(Element) {
595 [hide, undoPositioned].call(effect.effects[0].element);
596 setStyle(effect.effects[0].element, oldStyle); }}
682 beforeSetup: function(effect) {
683 effect.effects[0].element.makePositioned();
684 },
685 afterFinishInternal: function(effect) {
686 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
687 }
597 688 }, arguments[1] || {}));
598 689 }
599 690
600 691 Effect.Shake = function(element) {
601 692 element = $(element);
602 693 var oldStyle = {
603 top: Element.getStyle(element, 'top'),
604 left: Element.getStyle(element, 'left') };
605 return new Effect.MoveBy(element, 0, 20,
606 { duration: 0.05, afterFinishInternal: function(effect) {
607 new Effect.MoveBy(effect.element, 0, -40,
608 { duration: 0.1, afterFinishInternal: function(effect) {
609 new Effect.MoveBy(effect.element, 0, 40,
610 { duration: 0.1, afterFinishInternal: function(effect) {
611 new Effect.MoveBy(effect.element, 0, -40,
612 { duration: 0.1, afterFinishInternal: function(effect) {
613 new Effect.MoveBy(effect.element, 0, 40,
614 { duration: 0.1, afterFinishInternal: function(effect) {
615 new Effect.MoveBy(effect.element, 0, -20,
616 { duration: 0.05, afterFinishInternal: function(effect) { with(Element) {
617 undoPositioned(effect.element);
618 setStyle(effect.element, oldStyle);
619 }}}) }}) }}) }}) }}) }});
694 top: element.getStyle('top'),
695 left: element.getStyle('left') };
696 return new Effect.Move(element,
697 { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
698 new Effect.Move(effect.element,
699 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
700 new Effect.Move(effect.element,
701 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
702 new Effect.Move(effect.element,
703 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
704 new Effect.Move(effect.element,
705 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
706 new Effect.Move(effect.element,
707 { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
708 effect.element.undoPositioned().setStyle(oldStyle);
709 }}) }}) }}) }}) }}) }});
620 710 }
621 711
622 712 Effect.SlideDown = function(element) {
623 element = $(element);
624 Element.cleanWhitespace(element);
713 element = $(element).cleanWhitespace();
625 714 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
626 var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
627 var elementDimensions = Element.getDimensions(element);
715 var oldInnerBottom = element.down().getStyle('bottom');
716 var elementDimensions = element.getDimensions();
628 717 return new Effect.Scale(element, 100, Object.extend({
629 718 scaleContent: false,
630 719 scaleX: false,
631 scaleFrom: 0,
720 scaleFrom: window.opera ? 0 : 1,
632 721 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
633 722 restoreAfterFinish: true,
634 afterSetup: function(effect) { with(Element) {
635 makePositioned(effect.element);
636 makePositioned(effect.element.firstChild);
637 if(window.opera) setStyle(effect.element, {top: ''});
638 makeClipping(effect.element);
639 setStyle(effect.element, {height: '0px'});
640 show(element); }},
641 afterUpdateInternal: function(effect) { with(Element) {
642 setStyle(effect.element.firstChild, {bottom:
643 (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
644 afterFinishInternal: function(effect) { with(Element) {
645 undoClipping(effect.element);
646 undoPositioned(effect.element.firstChild);
647 undoPositioned(effect.element);
648 setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
723 afterSetup: function(effect) {
724 effect.element.makePositioned();
725 effect.element.down().makePositioned();
726 if(window.opera) effect.element.setStyle({top: ''});
727 effect.element.makeClipping().setStyle({height: '0px'}).show();
728 },
729 afterUpdateInternal: function(effect) {
730 effect.element.down().setStyle({bottom:
731 (effect.dims[0] - effect.element.clientHeight) + 'px' });
732 },
733 afterFinishInternal: function(effect) {
734 effect.element.undoClipping().undoPositioned();
735 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
649 736 }, arguments[1] || {})
650 737 );
651 738 }
652
739
653 740 Effect.SlideUp = function(element) {
654 element = $(element);
655 Element.cleanWhitespace(element);
656 var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
657 return new Effect.Scale(element, 0,
741 element = $(element).cleanWhitespace();
742 var oldInnerBottom = element.down().getStyle('bottom');
743 return new Effect.Scale(element, window.opera ? 0 : 1,
658 744 Object.extend({ scaleContent: false,
659 745 scaleX: false,
660 746 scaleMode: 'box',
661 747 scaleFrom: 100,
662 748 restoreAfterFinish: true,
663 beforeStartInternal: function(effect) { with(Element) {
664 makePositioned(effect.element);
665 makePositioned(effect.element.firstChild);
666 if(window.opera) setStyle(effect.element, {top: ''});
667 makeClipping(effect.element);
668 show(element); }},
669 afterUpdateInternal: function(effect) { with(Element) {
670 setStyle(effect.element.firstChild, {bottom:
671 (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
672 afterFinishInternal: function(effect) { with(Element) {
673 [hide, undoClipping].call(effect.element);
674 undoPositioned(effect.element.firstChild);
675 undoPositioned(effect.element);
676 setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
749 beforeStartInternal: function(effect) {
750 effect.element.makePositioned();
751 effect.element.down().makePositioned();
752 if(window.opera) effect.element.setStyle({top: ''});
753 effect.element.makeClipping().show();
754 },
755 afterUpdateInternal: function(effect) {
756 effect.element.down().setStyle({bottom:
757 (effect.dims[0] - effect.element.clientHeight) + 'px' });
758 },
759 afterFinishInternal: function(effect) {
760 effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
761 effect.element.down().undoPositioned();
762 }
677 763 }, arguments[1] || {})
678 764 );
679 765 }
680 766
681 767 // Bug in opera makes the TD containing this element expand for a instance after finish
682 768 Effect.Squish = function(element) {
683 return new Effect.Scale(element, window.opera ? 1 : 0,
684 { restoreAfterFinish: true,
685 beforeSetup: function(effect) { with(Element) {
686 makeClipping(effect.element); }},
687 afterFinishInternal: function(effect) { with(Element) {
688 hide(effect.element);
689 undoClipping(effect.element); }}
769 return new Effect.Scale(element, window.opera ? 1 : 0, {
770 restoreAfterFinish: true,
771 beforeSetup: function(effect) {
772 effect.element.makeClipping();
773 },
774 afterFinishInternal: function(effect) {
775 effect.element.hide().undoClipping();
776 }
690 777 });
691 778 }
692 779
693 780 Effect.Grow = function(element) {
694 781 element = $(element);
695 782 var options = Object.extend({
696 783 direction: 'center',
697 moveTransistion: Effect.Transitions.sinoidal,
784 moveTransition: Effect.Transitions.sinoidal,
698 785 scaleTransition: Effect.Transitions.sinoidal,
699 786 opacityTransition: Effect.Transitions.full
700 787 }, arguments[1] || {});
701 788 var oldStyle = {
702 789 top: element.style.top,
703 790 left: element.style.left,
704 791 height: element.style.height,
705 792 width: element.style.width,
706 opacity: Element.getInlineOpacity(element) };
793 opacity: element.getInlineOpacity() };
707 794
708 var dims = Element.getDimensions(element);
795 var dims = element.getDimensions();
709 796 var initialMoveX, initialMoveY;
710 797 var moveX, moveY;
711 798
712 799 switch (options.direction) {
713 800 case 'top-left':
714 801 initialMoveX = initialMoveY = moveX = moveY = 0;
715 802 break;
716 803 case 'top-right':
717 804 initialMoveX = dims.width;
718 805 initialMoveY = moveY = 0;
719 806 moveX = -dims.width;
720 807 break;
721 808 case 'bottom-left':
722 809 initialMoveX = moveX = 0;
723 810 initialMoveY = dims.height;
724 811 moveY = -dims.height;
725 812 break;
726 813 case 'bottom-right':
727 814 initialMoveX = dims.width;
728 815 initialMoveY = dims.height;
729 816 moveX = -dims.width;
730 817 moveY = -dims.height;
731 818 break;
732 819 case 'center':
733 820 initialMoveX = dims.width / 2;
734 821 initialMoveY = dims.height / 2;
735 822 moveX = -dims.width / 2;
736 823 moveY = -dims.height / 2;
737 824 break;
738 825 }
739 826
740 return new Effect.MoveBy(element, initialMoveY, initialMoveX, {
827 return new Effect.Move(element, {
828 x: initialMoveX,
829 y: initialMoveY,
741 830 duration: 0.01,
742 beforeSetup: function(effect) { with(Element) {
743 hide(effect.element);
744 makeClipping(effect.element);
745 makePositioned(effect.element);
746 }},
831 beforeSetup: function(effect) {
832 effect.element.hide().makeClipping().makePositioned();
833 },
747 834 afterFinishInternal: function(effect) {
748 835 new Effect.Parallel(
749 836 [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
750 new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: options.moveTransition }),
837 new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
751 838 new Effect.Scale(effect.element, 100, {
752 839 scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
753 840 sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
754 841 ], Object.extend({
755 beforeSetup: function(effect) { with(Element) {
756 setStyle(effect.effects[0].element, {height: '0px'});
757 show(effect.effects[0].element); }},
758 afterFinishInternal: function(effect) { with(Element) {
759 [undoClipping, undoPositioned].call(effect.effects[0].element);
760 setStyle(effect.effects[0].element, oldStyle); }}
842 beforeSetup: function(effect) {
843 effect.effects[0].element.setStyle({height: '0px'}).show();
844 },
845 afterFinishInternal: function(effect) {
846 effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
847 }
761 848 }, options)
762 849 )
763 850 }
764 851 });
765 852 }
766 853
767 854 Effect.Shrink = function(element) {
768 855 element = $(element);
769 856 var options = Object.extend({
770 857 direction: 'center',
771 moveTransistion: Effect.Transitions.sinoidal,
858 moveTransition: Effect.Transitions.sinoidal,
772 859 scaleTransition: Effect.Transitions.sinoidal,
773 860 opacityTransition: Effect.Transitions.none
774 861 }, arguments[1] || {});
775 862 var oldStyle = {
776 863 top: element.style.top,
777 864 left: element.style.left,
778 865 height: element.style.height,
779 866 width: element.style.width,
780 opacity: Element.getInlineOpacity(element) };
867 opacity: element.getInlineOpacity() };
781 868
782 var dims = Element.getDimensions(element);
869 var dims = element.getDimensions();
783 870 var moveX, moveY;
784 871
785 872 switch (options.direction) {
786 873 case 'top-left':
787 874 moveX = moveY = 0;
788 875 break;
789 876 case 'top-right':
790 877 moveX = dims.width;
791 878 moveY = 0;
792 879 break;
793 880 case 'bottom-left':
794 881 moveX = 0;
795 882 moveY = dims.height;
796 883 break;
797 884 case 'bottom-right':
798 885 moveX = dims.width;
799 886 moveY = dims.height;
800 887 break;
801 888 case 'center':
802 889 moveX = dims.width / 2;
803 890 moveY = dims.height / 2;
804 891 break;
805 892 }
806 893
807 894 return new Effect.Parallel(
808 895 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
809 896 new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
810 new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: options.moveTransition })
897 new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
811 898 ], Object.extend({
812 beforeStartInternal: function(effect) { with(Element) {
813 [makePositioned, makeClipping].call(effect.effects[0].element) }},
814 afterFinishInternal: function(effect) { with(Element) {
815 [hide, undoClipping, undoPositioned].call(effect.effects[0].element);
816 setStyle(effect.effects[0].element, oldStyle); }}
899 beforeStartInternal: function(effect) {
900 effect.effects[0].element.makePositioned().makeClipping();
901 },
902 afterFinishInternal: function(effect) {
903 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
817 904 }, options)
818 905 );
819 906 }
820 907
821 908 Effect.Pulsate = function(element) {
822 909 element = $(element);
823 910 var options = arguments[1] || {};
824 var oldOpacity = Element.getInlineOpacity(element);
911 var oldOpacity = element.getInlineOpacity();
825 912 var transition = options.transition || Effect.Transitions.sinoidal;
826 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
913 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
827 914 reverser.bind(transition);
828 915 return new Effect.Opacity(element,
829 Object.extend(Object.extend({ duration: 3.0, from: 0,
830 afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); }
916 Object.extend(Object.extend({ duration: 2.0, from: 0,
917 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
831 918 }, options), {transition: reverser}));
832 919 }
833 920
834 921 Effect.Fold = function(element) {
835 922 element = $(element);
836 923 var oldStyle = {
837 924 top: element.style.top,
838 925 left: element.style.left,
839 926 width: element.style.width,
840 927 height: element.style.height };
841 Element.makeClipping(element);
928 element.makeClipping();
842 929 return new Effect.Scale(element, 5, Object.extend({
843 930 scaleContent: false,
844 931 scaleX: false,
845 932 afterFinishInternal: function(effect) {
846 933 new Effect.Scale(element, 1, {
847 934 scaleContent: false,
848 935 scaleY: false,
849 afterFinishInternal: function(effect) { with(Element) {
850 [hide, undoClipping].call(effect.element);
851 setStyle(effect.element, oldStyle);
852 }} });
936 afterFinishInternal: function(effect) {
937 effect.element.hide().undoClipping().setStyle(oldStyle);
938 } });
853 939 }}, arguments[1] || {}));
854 }
940 };
941
942 Effect.Morph = Class.create();
943 Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
944 initialize: function(element) {
945 this.element = $(element);
946 if(!this.element) throw(Effect._elementDoesNotExistError);
947 var options = Object.extend({
948 style: ''
949 }, arguments[1] || {});
950 this.start(options);
951 },
952 setup: function(){
953 function parseColor(color){
954 if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
955 color = color.parseColor();
956 return $R(0,2).map(function(i){
957 return parseInt( color.slice(i*2+1,i*2+3), 16 )
958 });
959 }
960 this.transforms = this.options.style.parseStyle().map(function(property){
961 var originalValue = this.element.getStyle(property[0]);
962 return $H({
963 style: property[0],
964 originalValue: property[1].unit=='color' ?
965 parseColor(originalValue) : parseFloat(originalValue || 0),
966 targetValue: property[1].unit=='color' ?
967 parseColor(property[1].value) : property[1].value,
968 unit: property[1].unit
969 });
970 }.bind(this)).reject(function(transform){
971 return (
972 (transform.originalValue == transform.targetValue) ||
973 (
974 transform.unit != 'color' &&
975 (isNaN(transform.originalValue) || isNaN(transform.targetValue))
976 )
977 )
978 });
979 },
980 update: function(position) {
981 var style = $H(), value = null;
982 this.transforms.each(function(transform){
983 value = transform.unit=='color' ?
984 $R(0,2).inject('#',function(m,v,i){
985 return m+(Math.round(transform.originalValue[i]+
986 (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) :
987 transform.originalValue + Math.round(
988 ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
989 style[transform.style] = value;
990 });
991 this.element.setStyle(style);
992 }
993 });
994
995 Effect.Transform = Class.create();
996 Object.extend(Effect.Transform.prototype, {
997 initialize: function(tracks){
998 this.tracks = [];
999 this.options = arguments[1] || {};
1000 this.addTracks(tracks);
1001 },
1002 addTracks: function(tracks){
1003 tracks.each(function(track){
1004 var data = $H(track).values().first();
1005 this.tracks.push($H({
1006 ids: $H(track).keys().first(),
1007 effect: Effect.Morph,
1008 options: { style: data }
1009 }));
1010 }.bind(this));
1011 return this;
1012 },
1013 play: function(){
1014 return new Effect.Parallel(
1015 this.tracks.map(function(track){
1016 var elements = [$(track.ids) || $$(track.ids)].flatten();
1017 return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
1018 }).flatten(),
1019 this.options
1020 );
1021 }
1022 });
1023
1024 Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage',
1025 'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle',
1026 'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth',
1027 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor',
1028 'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content',
1029 'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction',
1030 'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch',
1031 'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight',
1032 'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight',
1033 'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity',
1034 'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY',
1035 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore',
1036 'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes',
1037 'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress',
1038 'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top',
1039 'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows',
1040 'width', 'wordSpacing', 'zIndex'];
1041
1042 Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1043
1044 String.prototype.parseStyle = function(){
1045 var element = Element.extend(document.createElement('div'));
1046 element.innerHTML = '<div style="' + this + '"></div>';
1047 var style = element.down().style, styleRules = $H();
1048
1049 Element.CSS_PROPERTIES.each(function(property){
1050 if(style[property]) styleRules[property] = style[property];
1051 });
1052
1053 var result = $H();
1054
1055 styleRules.each(function(pair){
1056 var property = pair[0], value = pair[1], unit = null;
1057
1058 if(value.parseColor('#zzzzzz') != '#zzzzzz') {
1059 value = value.parseColor();
1060 unit = 'color';
1061 } else if(Element.CSS_LENGTH.test(value))
1062 var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/),
1063 value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null;
1064
1065 result[property.underscore().dasherize()] = $H({ value:value, unit:unit });
1066 }.bind(this));
1067
1068 return result;
1069 };
1070
1071 Element.morph = function(element, style) {
1072 new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
1073 return element;
1074 };
1075
1076 ['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
1077 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
1078 function(f) { Element.Methods[f] = Element[f]; }
1079 );
1080
1081 Element.Methods.visualEffect = function(element, effect, options) {
1082 s = effect.gsub(/_/, '-').camelize();
1083 effect_class = s.charAt(0).toUpperCase() + s.substring(1);
1084 new Effect[effect_class](element, options);
1085 return $(element);
1086 };
1087
1088 Element.addMethods(); No newline at end of file
This diff has been collapsed as it changes many lines, (558 lines changed) Show them Hide them
@@ -1,2319 +1,2515
1 /* Prototype JavaScript framework, version 1.5.0_rc1
2 * (c) 2005 Sam Stephenson <sam@conio.net>
1 /* Prototype JavaScript framework, version 1.5.0
2 * (c) 2005-2007 Sam Stephenson
3 3 *
4 4 * Prototype is freely distributable under the terms of an MIT-style license.
5 5 * For details, see the Prototype web site: http://prototype.conio.net/
6 6 *
7 7 /*--------------------------------------------------------------------------*/
8 8
9 9 var Prototype = {
10 Version: '1.5.0_rc1',
10 Version: '1.5.0',
11 11 BrowserFeatures: {
12 12 XPath: !!document.evaluate
13 13 },
14 14
15 15 ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
16 16 emptyFunction: function() {},
17 17 K: function(x) { return x }
18 18 }
19 19
20 20 var Class = {
21 21 create: function() {
22 22 return function() {
23 23 this.initialize.apply(this, arguments);
24 24 }
25 25 }
26 26 }
27 27
28 28 var Abstract = new Object();
29 29
30 30 Object.extend = function(destination, source) {
31 31 for (var property in source) {
32 32 destination[property] = source[property];
33 33 }
34 34 return destination;
35 35 }
36 36
37 37 Object.extend(Object, {
38 38 inspect: function(object) {
39 39 try {
40 40 if (object === undefined) return 'undefined';
41 41 if (object === null) return 'null';
42 42 return object.inspect ? object.inspect() : object.toString();
43 43 } catch (e) {
44 44 if (e instanceof RangeError) return '...';
45 45 throw e;
46 46 }
47 47 },
48 48
49 49 keys: function(object) {
50 50 var keys = [];
51 51 for (var property in object)
52 52 keys.push(property);
53 53 return keys;
54 54 },
55 55
56 56 values: function(object) {
57 57 var values = [];
58 58 for (var property in object)
59 59 values.push(object[property]);
60 60 return values;
61 61 },
62 62
63 63 clone: function(object) {
64 64 return Object.extend({}, object);
65 65 }
66 66 });
67 67
68 68 Function.prototype.bind = function() {
69 69 var __method = this, args = $A(arguments), object = args.shift();
70 70 return function() {
71 71 return __method.apply(object, args.concat($A(arguments)));
72 72 }
73 73 }
74 74
75 75 Function.prototype.bindAsEventListener = function(object) {
76 76 var __method = this, args = $A(arguments), object = args.shift();
77 77 return function(event) {
78 78 return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments)));
79 79 }
80 80 }
81 81
82 82 Object.extend(Number.prototype, {
83 83 toColorPart: function() {
84 84 var digits = this.toString(16);
85 85 if (this < 16) return '0' + digits;
86 86 return digits;
87 87 },
88 88
89 89 succ: function() {
90 90 return this + 1;
91 91 },
92 92
93 93 times: function(iterator) {
94 94 $R(0, this, true).each(iterator);
95 95 return this;
96 96 }
97 97 });
98 98
99 99 var Try = {
100 100 these: function() {
101 101 var returnValue;
102 102
103 for (var i = 0; i < arguments.length; i++) {
103 for (var i = 0, length = arguments.length; i < length; i++) {
104 104 var lambda = arguments[i];
105 105 try {
106 106 returnValue = lambda();
107 107 break;
108 108 } catch (e) {}
109 109 }
110 110
111 111 return returnValue;
112 112 }
113 113 }
114 114
115 115 /*--------------------------------------------------------------------------*/
116 116
117 117 var PeriodicalExecuter = Class.create();
118 118 PeriodicalExecuter.prototype = {
119 119 initialize: function(callback, frequency) {
120 120 this.callback = callback;
121 121 this.frequency = frequency;
122 122 this.currentlyExecuting = false;
123 123
124 124 this.registerCallback();
125 125 },
126 126
127 127 registerCallback: function() {
128 128 this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
129 129 },
130 130
131 131 stop: function() {
132 132 if (!this.timer) return;
133 133 clearInterval(this.timer);
134 134 this.timer = null;
135 135 },
136 136
137 137 onTimerEvent: function() {
138 138 if (!this.currentlyExecuting) {
139 139 try {
140 140 this.currentlyExecuting = true;
141 141 this.callback(this);
142 142 } finally {
143 143 this.currentlyExecuting = false;
144 144 }
145 145 }
146 146 }
147 147 }
148 String.interpret = function(value){
149 return value == null ? '' : String(value);
150 }
151
148 152 Object.extend(String.prototype, {
149 153 gsub: function(pattern, replacement) {
150 154 var result = '', source = this, match;
151 155 replacement = arguments.callee.prepareReplacement(replacement);
152 156
153 157 while (source.length > 0) {
154 158 if (match = source.match(pattern)) {
155 159 result += source.slice(0, match.index);
156 result += (replacement(match) || '').toString();
160 result += String.interpret(replacement(match));
157 161 source = source.slice(match.index + match[0].length);
158 162 } else {
159 163 result += source, source = '';
160 164 }
161 165 }
162 166 return result;
163 167 },
164 168
165 169 sub: function(pattern, replacement, count) {
166 170 replacement = this.gsub.prepareReplacement(replacement);
167 171 count = count === undefined ? 1 : count;
168 172
169 173 return this.gsub(pattern, function(match) {
170 174 if (--count < 0) return match[0];
171 175 return replacement(match);
172 176 });
173 177 },
174 178
175 179 scan: function(pattern, iterator) {
176 180 this.gsub(pattern, iterator);
177 181 return this;
178 182 },
179 183
180 184 truncate: function(length, truncation) {
181 185 length = length || 30;
182 186 truncation = truncation === undefined ? '...' : truncation;
183 187 return this.length > length ?
184 188 this.slice(0, length - truncation.length) + truncation : this;
185 189 },
186 190
187 191 strip: function() {
188 192 return this.replace(/^\s+/, '').replace(/\s+$/, '');
189 193 },
190 194
191 195 stripTags: function() {
192 196 return this.replace(/<\/?[^>]+>/gi, '');
193 197 },
194 198
195 199 stripScripts: function() {
196 200 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
197 201 },
198 202
199 203 extractScripts: function() {
200 204 var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
201 205 var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
202 206 return (this.match(matchAll) || []).map(function(scriptTag) {
203 207 return (scriptTag.match(matchOne) || ['', ''])[1];
204 208 });
205 209 },
206 210
207 211 evalScripts: function() {
208 212 return this.extractScripts().map(function(script) { return eval(script) });
209 213 },
210 214
211 215 escapeHTML: function() {
212 216 var div = document.createElement('div');
213 217 var text = document.createTextNode(this);
214 218 div.appendChild(text);
215 219 return div.innerHTML;
216 220 },
217 221
218 222 unescapeHTML: function() {
219 223 var div = document.createElement('div');
220 224 div.innerHTML = this.stripTags();
221 return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
225 return div.childNodes[0] ? (div.childNodes.length > 1 ?
226 $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) :
227 div.childNodes[0].nodeValue) : '';
222 228 },
223 229
224 toQueryParams: function() {
225 var match = this.strip().match(/[^?]*$/)[0];
230 toQueryParams: function(separator) {
231 var match = this.strip().match(/([^?#]*)(#.*)?$/);
226 232 if (!match) return {};
227 var pairs = match.split('&');
228 return pairs.inject({}, function(params, pairString) {
229 var pair = pairString.split('=');
230 var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
231 params[decodeURIComponent(pair[0])] = value;
232 return params;
233
234 return match[1].split(separator || '&').inject({}, function(hash, pair) {
235 if ((pair = pair.split('='))[0]) {
236 var name = decodeURIComponent(pair[0]);
237 var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
238
239 if (hash[name] !== undefined) {
240 if (hash[name].constructor != Array)
241 hash[name] = [hash[name]];
242 if (value) hash[name].push(value);
243 }
244 else hash[name] = value;
245 }
246 return hash;
233 247 });
234 248 },
235 249
236 250 toArray: function() {
237 251 return this.split('');
238 252 },
239 253
254 succ: function() {
255 return this.slice(0, this.length - 1) +
256 String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
257 },
258
240 259 camelize: function() {
241 var oStringList = this.split('-');
242 if (oStringList.length == 1) return oStringList[0];
260 var parts = this.split('-'), len = parts.length;
261 if (len == 1) return parts[0];
243 262
244 var camelizedString = this.indexOf('-') == 0
245 ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
246 : oStringList[0];
263 var camelized = this.charAt(0) == '-'
264 ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
265 : parts[0];
247 266
248 for (var i = 1, length = oStringList.length; i < length; i++) {
249 var s = oStringList[i];
250 camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
251 }
267 for (var i = 1; i < len; i++)
268 camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
269
270 return camelized;
271 },
272
273 capitalize: function(){
274 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
275 },
252 276
253 return camelizedString;
277 underscore: function() {
278 return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
279 },
280
281 dasherize: function() {
282 return this.gsub(/_/,'-');
254 283 },
255 284
256 285 inspect: function(useDoubleQuotes) {
257 286 var escapedString = this.replace(/\\/g, '\\\\');
258 287 if (useDoubleQuotes)
259 288 return '"' + escapedString.replace(/"/g, '\\"') + '"';
260 289 else
261 290 return "'" + escapedString.replace(/'/g, '\\\'') + "'";
262 291 }
263 292 });
264 293
265 294 String.prototype.gsub.prepareReplacement = function(replacement) {
266 295 if (typeof replacement == 'function') return replacement;
267 296 var template = new Template(replacement);
268 297 return function(match) { return template.evaluate(match) };
269 298 }
270 299
271 300 String.prototype.parseQuery = String.prototype.toQueryParams;
272 301
273 302 var Template = Class.create();
274 303 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
275 304 Template.prototype = {
276 305 initialize: function(template, pattern) {
277 306 this.template = template.toString();
278 307 this.pattern = pattern || Template.Pattern;
279 308 },
280 309
281 310 evaluate: function(object) {
282 311 return this.template.gsub(this.pattern, function(match) {
283 312 var before = match[1];
284 313 if (before == '\\') return match[2];
285 return before + (object[match[3]] || '').toString();
314 return before + String.interpret(object[match[3]]);
286 315 });
287 316 }
288 317 }
289 318
290 319 var $break = new Object();
291 320 var $continue = new Object();
292 321
293 322 var Enumerable = {
294 323 each: function(iterator) {
295 324 var index = 0;
296 325 try {
297 326 this._each(function(value) {
298 327 try {
299 328 iterator(value, index++);
300 329 } catch (e) {
301 330 if (e != $continue) throw e;
302 331 }
303 332 });
304 333 } catch (e) {
305 334 if (e != $break) throw e;
306 335 }
307 336 return this;
308 337 },
309 338
310 339 eachSlice: function(number, iterator) {
311 340 var index = -number, slices = [], array = this.toArray();
312 341 while ((index += number) < array.length)
313 342 slices.push(array.slice(index, index+number));
314 return slices.collect(iterator || Prototype.K);
343 return slices.map(iterator);
315 344 },
316 345
317 346 all: function(iterator) {
318 347 var result = true;
319 348 this.each(function(value, index) {
320 349 result = result && !!(iterator || Prototype.K)(value, index);
321 350 if (!result) throw $break;
322 351 });
323 352 return result;
324 353 },
325 354
326 355 any: function(iterator) {
327 356 var result = false;
328 357 this.each(function(value, index) {
329 358 if (result = !!(iterator || Prototype.K)(value, index))
330 359 throw $break;
331 360 });
332 361 return result;
333 362 },
334 363
335 364 collect: function(iterator) {
336 365 var results = [];
337 366 this.each(function(value, index) {
338 results.push(iterator(value, index));
367 results.push((iterator || Prototype.K)(value, index));
339 368 });
340 369 return results;
341 370 },
342 371
343 372 detect: function(iterator) {
344 373 var result;
345 374 this.each(function(value, index) {
346 375 if (iterator(value, index)) {
347 376 result = value;
348 377 throw $break;
349 378 }
350 379 });
351 380 return result;
352 381 },
353 382
354 383 findAll: function(iterator) {
355 384 var results = [];
356 385 this.each(function(value, index) {
357 386 if (iterator(value, index))
358 387 results.push(value);
359 388 });
360 389 return results;
361 390 },
362 391
363 392 grep: function(pattern, iterator) {
364 393 var results = [];
365 394 this.each(function(value, index) {
366 395 var stringValue = value.toString();
367 396 if (stringValue.match(pattern))
368 397 results.push((iterator || Prototype.K)(value, index));
369 398 })
370 399 return results;
371 400 },
372 401
373 402 include: function(object) {
374 403 var found = false;
375 404 this.each(function(value) {
376 405 if (value == object) {
377 406 found = true;
378 407 throw $break;
379 408 }
380 409 });
381 410 return found;
382 411 },
383 412
384 413 inGroupsOf: function(number, fillWith) {
385 fillWith = fillWith || null;
386 var results = this.eachSlice(number);
387 if (results.length > 0) (number - results.last().length).times(function() {
388 results.last().push(fillWith)
414 fillWith = fillWith === undefined ? null : fillWith;
415 return this.eachSlice(number, function(slice) {
416 while(slice.length < number) slice.push(fillWith);
417 return slice;
389 418 });
390 return results;
391 419 },
392 420
393 421 inject: function(memo, iterator) {
394 422 this.each(function(value, index) {
395 423 memo = iterator(memo, value, index);
396 424 });
397 425 return memo;
398 426 },
399 427
400 428 invoke: function(method) {
401 429 var args = $A(arguments).slice(1);
402 return this.collect(function(value) {
430 return this.map(function(value) {
403 431 return value[method].apply(value, args);
404 432 });
405 433 },
406 434
407 435 max: function(iterator) {
408 436 var result;
409 437 this.each(function(value, index) {
410 438 value = (iterator || Prototype.K)(value, index);
411 439 if (result == undefined || value >= result)
412 440 result = value;
413 441 });
414 442 return result;
415 443 },
416 444
417 445 min: function(iterator) {
418 446 var result;
419 447 this.each(function(value, index) {
420 448 value = (iterator || Prototype.K)(value, index);
421 449 if (result == undefined || value < result)
422 450 result = value;
423 451 });
424 452 return result;
425 453 },
426 454
427 455 partition: function(iterator) {
428 456 var trues = [], falses = [];
429 457 this.each(function(value, index) {
430 458 ((iterator || Prototype.K)(value, index) ?
431 459 trues : falses).push(value);
432 460 });
433 461 return [trues, falses];
434 462 },
435 463
436 464 pluck: function(property) {
437 465 var results = [];
438 466 this.each(function(value, index) {
439 467 results.push(value[property]);
440 468 });
441 469 return results;
442 470 },
443 471
444 472 reject: function(iterator) {
445 473 var results = [];
446 474 this.each(function(value, index) {
447 475 if (!iterator(value, index))
448 476 results.push(value);
449 477 });
450 478 return results;
451 479 },
452 480
453 481 sortBy: function(iterator) {
454 return this.collect(function(value, index) {
482 return this.map(function(value, index) {
455 483 return {value: value, criteria: iterator(value, index)};
456 484 }).sort(function(left, right) {
457 485 var a = left.criteria, b = right.criteria;
458 486 return a < b ? -1 : a > b ? 1 : 0;
459 487 }).pluck('value');
460 488 },
461 489
462 490 toArray: function() {
463 return this.collect(Prototype.K);
491 return this.map();
464 492 },
465 493
466 494 zip: function() {
467 495 var iterator = Prototype.K, args = $A(arguments);
468 496 if (typeof args.last() == 'function')
469 497 iterator = args.pop();
470 498
471 499 var collections = [this].concat(args).map($A);
472 500 return this.map(function(value, index) {
473 501 return iterator(collections.pluck(index));
474 502 });
475 503 },
476 504
505 size: function() {
506 return this.toArray().length;
507 },
508
477 509 inspect: function() {
478 510 return '#<Enumerable:' + this.toArray().inspect() + '>';
479 511 }
480 512 }
481 513
482 514 Object.extend(Enumerable, {
483 515 map: Enumerable.collect,
484 516 find: Enumerable.detect,
485 517 select: Enumerable.findAll,
486 518 member: Enumerable.include,
487 519 entries: Enumerable.toArray
488 520 });
489 521 var $A = Array.from = function(iterable) {
490 522 if (!iterable) return [];
491 523 if (iterable.toArray) {
492 524 return iterable.toArray();
493 525 } else {
494 526 var results = [];
495 527 for (var i = 0, length = iterable.length; i < length; i++)
496 528 results.push(iterable[i]);
497 529 return results;
498 530 }
499 531 }
500 532
501 533 Object.extend(Array.prototype, Enumerable);
502 534
503 535 if (!Array.prototype._reverse)
504 536 Array.prototype._reverse = Array.prototype.reverse;
505 537
506 538 Object.extend(Array.prototype, {
507 539 _each: function(iterator) {
508 540 for (var i = 0, length = this.length; i < length; i++)
509 541 iterator(this[i]);
510 542 },
511 543
512 544 clear: function() {
513 545 this.length = 0;
514 546 return this;
515 547 },
516 548
517 549 first: function() {
518 550 return this[0];
519 551 },
520 552
521 553 last: function() {
522 554 return this[this.length - 1];
523 555 },
524 556
525 557 compact: function() {
526 558 return this.select(function(value) {
527 return value != undefined || value != null;
559 return value != null;
528 560 });
529 561 },
530 562
531 563 flatten: function() {
532 564 return this.inject([], function(array, value) {
533 565 return array.concat(value && value.constructor == Array ?
534 566 value.flatten() : [value]);
535 567 });
536 568 },
537 569
538 570 without: function() {
539 571 var values = $A(arguments);
540 572 return this.select(function(value) {
541 573 return !values.include(value);
542 574 });
543 575 },
544 576
545 577 indexOf: function(object) {
546 578 for (var i = 0, length = this.length; i < length; i++)
547 579 if (this[i] == object) return i;
548 580 return -1;
549 581 },
550 582
551 583 reverse: function(inline) {
552 584 return (inline !== false ? this : this.toArray())._reverse();
553 585 },
554 586
555 587 reduce: function() {
556 588 return this.length > 1 ? this : this[0];
557 589 },
558 590
559 591 uniq: function() {
560 592 return this.inject([], function(array, value) {
561 593 return array.include(value) ? array : array.concat([value]);
562 594 });
563 595 },
564 596
565 597 clone: function() {
566 598 return [].concat(this);
567 599 },
568 600
601 size: function() {
602 return this.length;
603 },
604
569 605 inspect: function() {
570 606 return '[' + this.map(Object.inspect).join(', ') + ']';
571 607 }
572 608 });
573 609
574 610 Array.prototype.toArray = Array.prototype.clone;
575 var Hash = {
611
612 function $w(string){
613 string = string.strip();
614 return string ? string.split(/\s+/) : [];
615 }
616
617 if(window.opera){
618 Array.prototype.concat = function(){
619 var array = [];
620 for(var i = 0, length = this.length; i < length; i++) array.push(this[i]);
621 for(var i = 0, length = arguments.length; i < length; i++) {
622 if(arguments[i].constructor == Array) {
623 for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
624 array.push(arguments[i][j]);
625 } else {
626 array.push(arguments[i]);
627 }
628 }
629 return array;
630 }
631 }
632 var Hash = function(obj) {
633 Object.extend(this, obj || {});
634 };
635
636 Object.extend(Hash, {
637 toQueryString: function(obj) {
638 var parts = [];
639
640 this.prototype._each.call(obj, function(pair) {
641 if (!pair.key) return;
642
643 if (pair.value && pair.value.constructor == Array) {
644 var values = pair.value.compact();
645 if (values.length < 2) pair.value = values.reduce();
646 else {
647 key = encodeURIComponent(pair.key);
648 values.each(function(value) {
649 value = value != undefined ? encodeURIComponent(value) : '';
650 parts.push(key + '=' + encodeURIComponent(value));
651 });
652 return;
653 }
654 }
655 if (pair.value == undefined) pair[1] = '';
656 parts.push(pair.map(encodeURIComponent).join('='));
657 });
658
659 return parts.join('&');
660 }
661 });
662
663 Object.extend(Hash.prototype, Enumerable);
664 Object.extend(Hash.prototype, {
576 665 _each: function(iterator) {
577 666 for (var key in this) {
578 667 var value = this[key];
579 if (typeof value == 'function') continue;
668 if (value && value == Hash.prototype[key]) continue;
580 669
581 670 var pair = [key, value];
582 671 pair.key = key;
583 672 pair.value = value;
584 673 iterator(pair);
585 674 }
586 675 },
587 676
588 677 keys: function() {
589 678 return this.pluck('key');
590 679 },
591 680
592 681 values: function() {
593 682 return this.pluck('value');
594 683 },
595 684
596 685 merge: function(hash) {
597 686 return $H(hash).inject(this, function(mergedHash, pair) {
598 687 mergedHash[pair.key] = pair.value;
599 688 return mergedHash;
600 689 });
601 690 },
602 691
692 remove: function() {
693 var result;
694 for(var i = 0, length = arguments.length; i < length; i++) {
695 var value = this[arguments[i]];
696 if (value !== undefined){
697 if (result === undefined) result = value;
698 else {
699 if (result.constructor != Array) result = [result];
700 result.push(value)
701 }
702 }
703 delete this[arguments[i]];
704 }
705 return result;
706 },
707
603 708 toQueryString: function() {
604 return this.map(function(pair) {
605 if (!pair.value && pair.value !== 0) pair[1] = '';
606 if (!pair.key) return;
607 return pair.map(encodeURIComponent).join('=');
608 }).join('&');
709 return Hash.toQueryString(this);
609 710 },
610 711
611 712 inspect: function() {
612 713 return '#<Hash:{' + this.map(function(pair) {
613 714 return pair.map(Object.inspect).join(': ');
614 715 }).join(', ') + '}>';
615 716 }
616 }
717 });
617 718
618 719 function $H(object) {
619 var hash = Object.extend({}, object || {});
620 Object.extend(hash, Enumerable);
621 Object.extend(hash, Hash);
622 return hash;
623 }
720 if (object && object.constructor == Hash) return object;
721 return new Hash(object);
722 };
624 723 ObjectRange = Class.create();
625 724 Object.extend(ObjectRange.prototype, Enumerable);
626 725 Object.extend(ObjectRange.prototype, {
627 726 initialize: function(start, end, exclusive) {
628 727 this.start = start;
629 728 this.end = end;
630 729 this.exclusive = exclusive;
631 730 },
632 731
633 732 _each: function(iterator) {
634 733 var value = this.start;
635 734 while (this.include(value)) {
636 735 iterator(value);
637 736 value = value.succ();
638 737 }
639 738 },
640 739
641 740 include: function(value) {
642 741 if (value < this.start)
643 742 return false;
644 743 if (this.exclusive)
645 744 return value < this.end;
646 745 return value <= this.end;
647 746 }
648 747 });
649 748
650 749 var $R = function(start, end, exclusive) {
651 750 return new ObjectRange(start, end, exclusive);
652 751 }
653 752
654 753 var Ajax = {
655 754 getTransport: function() {
656 755 return Try.these(
657 756 function() {return new XMLHttpRequest()},
658 757 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
659 758 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
660 759 ) || false;
661 760 },
662 761
663 762 activeRequestCount: 0
664 763 }
665 764
666 765 Ajax.Responders = {
667 766 responders: [],
668 767
669 768 _each: function(iterator) {
670 769 this.responders._each(iterator);
671 770 },
672 771
673 772 register: function(responder) {
674 773 if (!this.include(responder))
675 774 this.responders.push(responder);
676 775 },
677 776
678 777 unregister: function(responder) {
679 778 this.responders = this.responders.without(responder);
680 779 },
681 780
682 781 dispatch: function(callback, request, transport, json) {
683 782 this.each(function(responder) {
684 783 if (typeof responder[callback] == 'function') {
685 784 try {
686 785 responder[callback].apply(responder, [request, transport, json]);
687 786 } catch (e) {}
688 787 }
689 788 });
690 789 }
691 790 };
692 791
693 792 Object.extend(Ajax.Responders, Enumerable);
694 793
695 794 Ajax.Responders.register({
696 795 onCreate: function() {
697 796 Ajax.activeRequestCount++;
698 797 },
699 798 onComplete: function() {
700 799 Ajax.activeRequestCount--;
701 800 }
702 801 });
703 802
704 803 Ajax.Base = function() {};
705 804 Ajax.Base.prototype = {
706 805 setOptions: function(options) {
707 806 this.options = {
708 807 method: 'post',
709 808 asynchronous: true,
710 809 contentType: 'application/x-www-form-urlencoded',
711 810 encoding: 'UTF-8',
712 811 parameters: ''
713 812 }
714 813 Object.extend(this.options, options || {});
715 814
716 815 this.options.method = this.options.method.toLowerCase();
717 this.options.parameters = $H(typeof this.options.parameters == 'string' ?
718 this.options.parameters.toQueryParams() : this.options.parameters);
816 if (typeof this.options.parameters == 'string')
817 this.options.parameters = this.options.parameters.toQueryParams();
719 818 }
720 819 }
721 820
722 821 Ajax.Request = Class.create();
723 822 Ajax.Request.Events =
724 823 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
725 824
726 825 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
826 _complete: false,
827
727 828 initialize: function(url, options) {
728 829 this.transport = Ajax.getTransport();
729 830 this.setOptions(options);
730 831 this.request(url);
731 832 },
732 833
733 834 request: function(url) {
835 this.url = url;
836 this.method = this.options.method;
734 837 var params = this.options.parameters;
735 if (params.any()) params['_'] = '';
736 838
737 if (!['get', 'post'].include(this.options.method)) {
839 if (!['get', 'post'].include(this.method)) {
738 840 // simulate other verbs over post
739 params['_method'] = this.options.method;
740 this.options.method = 'post';
841 params['_method'] = this.method;
842 this.method = 'post';
741 843 }
742 844
743 this.url = url;
845 params = Hash.toQueryString(params);
846 if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='
744 847
745 848 // when GET, append parameters to URL
746 if (this.options.method == 'get' && params.any())
747 this.url += (this.url.indexOf('?') >= 0 ? '&' : '?') +
748 params.toQueryString();
849 if (this.method == 'get' && params)
850 this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params;
749 851
750 852 try {
751 853 Ajax.Responders.dispatch('onCreate', this, this.transport);
752 854
753 this.transport.open(this.options.method.toUpperCase(), this.url,
754 this.options.asynchronous, this.options.username,
755 this.options.password);
855 this.transport.open(this.method.toUpperCase(), this.url,
856 this.options.asynchronous);
756 857
757 858 if (this.options.asynchronous)
758 859 setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
759 860
760 861 this.transport.onreadystatechange = this.onStateChange.bind(this);
761 862 this.setRequestHeaders();
762 863
763 var body = this.options.method == 'post' ?
764 (this.options.postBody || params.toQueryString()) : null;
864 var body = this.method == 'post' ? (this.options.postBody || params) : null;
765 865
766 866 this.transport.send(body);
767 867
768 868 /* Force Firefox to handle ready state 4 for synchronous requests */
769 869 if (!this.options.asynchronous && this.transport.overrideMimeType)
770 870 this.onStateChange();
871
771 872 }
772 873 catch (e) {
773 874 this.dispatchException(e);
774 875 }
775 876 },
776 877
777 878 onStateChange: function() {
778 879 var readyState = this.transport.readyState;
779 if (readyState > 1)
880 if (readyState > 1 && !((readyState == 4) && this._complete))
780 881 this.respondToReadyState(this.transport.readyState);
781 882 },
782 883
783 884 setRequestHeaders: function() {
784 885 var headers = {
785 886 'X-Requested-With': 'XMLHttpRequest',
786 887 'X-Prototype-Version': Prototype.Version,
787 888 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
788 889 };
789 890
790 if (this.options.method == 'post') {
891 if (this.method == 'post') {
791 892 headers['Content-type'] = this.options.contentType +
792 893 (this.options.encoding ? '; charset=' + this.options.encoding : '');
793 894
794 895 /* Force "Connection: close" for older Mozilla browsers to work
795 896 * around a bug where XMLHttpRequest sends an incorrect
796 897 * Content-length header. See Mozilla Bugzilla #246651.
797 898 */
798 899 if (this.transport.overrideMimeType &&
799 900 (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
800 901 headers['Connection'] = 'close';
801 902 }
802 903
803 904 // user-defined headers
804 905 if (typeof this.options.requestHeaders == 'object') {
805 906 var extras = this.options.requestHeaders;
806 907
807 908 if (typeof extras.push == 'function')
808 for (var i = 0; i < extras.length; i += 2)
909 for (var i = 0, length = extras.length; i < length; i += 2)
809 910 headers[extras[i]] = extras[i+1];
810 911 else
811 912 $H(extras).each(function(pair) { headers[pair.key] = pair.value });
812 913 }
813 914
814 915 for (var name in headers)
815 916 this.transport.setRequestHeader(name, headers[name]);
816 917 },
817 918
818 919 success: function() {
819 920 return !this.transport.status
820 921 || (this.transport.status >= 200 && this.transport.status < 300);
821 922 },
822 923
823 924 respondToReadyState: function(readyState) {
824 925 var state = Ajax.Request.Events[readyState];
825 926 var transport = this.transport, json = this.evalJSON();
826 927
827 928 if (state == 'Complete') {
828 929 try {
930 this._complete = true;
829 931 (this.options['on' + this.transport.status]
830 932 || this.options['on' + (this.success() ? 'Success' : 'Failure')]
831 933 || Prototype.emptyFunction)(transport, json);
832 934 } catch (e) {
833 935 this.dispatchException(e);
834 936 }
937
938 if ((this.getHeader('Content-type') || 'text/javascript').strip().
939 match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
940 this.evalResponse();
835 941 }
836 942
837 943 try {
838 944 (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
839 945 Ajax.Responders.dispatch('on' + state, this, transport, json);
840 946 } catch (e) {
841 947 this.dispatchException(e);
842 948 }
843 949
844 950 if (state == 'Complete') {
845 if ((this.getHeader('Content-type') || '').strip().
846 match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
847 this.evalResponse();
848
849 951 // avoid memory leak in MSIE: clean up
850 952 this.transport.onreadystatechange = Prototype.emptyFunction;
851 953 }
852 954 },
853 955
854 956 getHeader: function(name) {
855 957 try {
856 958 return this.transport.getResponseHeader(name);
857 959 } catch (e) { return null }
858 960 },
859 961
860 962 evalJSON: function() {
861 963 try {
862 964 var json = this.getHeader('X-JSON');
863 965 return json ? eval('(' + json + ')') : null;
864 966 } catch (e) { return null }
865 967 },
866 968
867 969 evalResponse: function() {
868 970 try {
869 971 return eval(this.transport.responseText);
870 972 } catch (e) {
871 973 this.dispatchException(e);
872 974 }
873 975 },
874 976
875 977 dispatchException: function(exception) {
876 978 (this.options.onException || Prototype.emptyFunction)(this, exception);
877 979 Ajax.Responders.dispatch('onException', this, exception);
878 980 }
879 981 });
880 982
881 983 Ajax.Updater = Class.create();
882 984
883 985 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
884 986 initialize: function(container, url, options) {
885 987 this.container = {
886 988 success: (container.success || container),
887 989 failure: (container.failure || (container.success ? null : container))
888 990 }
889 991
890 992 this.transport = Ajax.getTransport();
891 993 this.setOptions(options);
892 994
893 995 var onComplete = this.options.onComplete || Prototype.emptyFunction;
894 996 this.options.onComplete = (function(transport, param) {
895 997 this.updateContent();
896 998 onComplete(transport, param);
897 999 }).bind(this);
898 1000
899 1001 this.request(url);
900 1002 },
901 1003
902 1004 updateContent: function() {
903 1005 var receiver = this.container[this.success() ? 'success' : 'failure'];
904 1006 var response = this.transport.responseText;
905 1007
906 1008 if (!this.options.evalScripts) response = response.stripScripts();
907 1009
908 1010 if (receiver = $(receiver)) {
909 1011 if (this.options.insertion)
910 1012 new this.options.insertion(receiver, response);
911 1013 else
912 1014 receiver.update(response);
913 1015 }
914 1016
915 1017 if (this.success()) {
916 1018 if (this.onComplete)
917 1019 setTimeout(this.onComplete.bind(this), 10);
918 1020 }
919 1021 }
920 1022 });
921 1023
922 1024 Ajax.PeriodicalUpdater = Class.create();
923 1025 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
924 1026 initialize: function(container, url, options) {
925 1027 this.setOptions(options);
926 1028 this.onComplete = this.options.onComplete;
927 1029
928 1030 this.frequency = (this.options.frequency || 2);
929 1031 this.decay = (this.options.decay || 1);
930 1032
931 1033 this.updater = {};
932 1034 this.container = container;
933 1035 this.url = url;
934 1036
935 1037 this.start();
936 1038 },
937 1039
938 1040 start: function() {
939 1041 this.options.onComplete = this.updateComplete.bind(this);
940 1042 this.onTimerEvent();
941 1043 },
942 1044
943 1045 stop: function() {
944 1046 this.updater.options.onComplete = undefined;
945 1047 clearTimeout(this.timer);
946 1048 (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
947 1049 },
948 1050
949 1051 updateComplete: function(request) {
950 1052 if (this.options.decay) {
951 1053 this.decay = (request.responseText == this.lastText ?
952 1054 this.decay * this.options.decay : 1);
953 1055
954 1056 this.lastText = request.responseText;
955 1057 }
956 1058 this.timer = setTimeout(this.onTimerEvent.bind(this),
957 1059 this.decay * this.frequency * 1000);
958 1060 },
959 1061
960 1062 onTimerEvent: function() {
961 1063 this.updater = new Ajax.Updater(this.container, this.url, this.options);
962 1064 }
963 1065 });
964 1066 function $(element) {
965 1067 if (arguments.length > 1) {
966 1068 for (var i = 0, elements = [], length = arguments.length; i < length; i++)
967 1069 elements.push($(arguments[i]));
968 1070 return elements;
969 1071 }
970 1072 if (typeof element == 'string')
971 1073 element = document.getElementById(element);
972 1074 return Element.extend(element);
973 1075 }
974 1076
975 1077 if (Prototype.BrowserFeatures.XPath) {
976 1078 document._getElementsByXPath = function(expression, parentElement) {
977 1079 var results = [];
978 1080 var query = document.evaluate(expression, $(parentElement) || document,
979 1081 null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
980 for (var i = 0, len = query.snapshotLength; i < len; i++)
1082 for (var i = 0, length = query.snapshotLength; i < length; i++)
981 1083 results.push(query.snapshotItem(i));
982 1084 return results;
983 }
1085 };
984 1086 }
985 1087
986 1088 document.getElementsByClassName = function(className, parentElement) {
987 1089 if (Prototype.BrowserFeatures.XPath) {
988 1090 var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
989 1091 return document._getElementsByXPath(q, parentElement);
990 1092 } else {
991 1093 var children = ($(parentElement) || document.body).getElementsByTagName('*');
992 1094 var elements = [], child;
993 1095 for (var i = 0, length = children.length; i < length; i++) {
994 1096 child = children[i];
995 1097 if (Element.hasClassName(child, className))
996 1098 elements.push(Element.extend(child));
997 1099 }
998 1100 return elements;
999 1101 }
1000 }
1102 };
1001 1103
1002 1104 /*--------------------------------------------------------------------------*/
1003 1105
1004 1106 if (!window.Element)
1005 1107 var Element = new Object();
1006 1108
1007 1109 Element.extend = function(element) {
1008 if (!element) return;
1009 if (_nativeExtensions || element.nodeType == 3) return element;
1110 if (!element || _nativeExtensions || element.nodeType == 3) return element;
1010 1111
1011 1112 if (!element._extended && element.tagName && element != window) {
1012 1113 var methods = Object.clone(Element.Methods), cache = Element.extend.cache;
1013 1114
1014 1115 if (element.tagName == 'FORM')
1015 1116 Object.extend(methods, Form.Methods);
1016 1117 if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName))
1017 1118 Object.extend(methods, Form.Element.Methods);
1018 1119
1019 for (var property in methods) {
1020 var value = methods[property];
1021 if (typeof value == 'function')
1022 element[property] = cache.findOrStore(value);
1023 }
1120 Object.extend(methods, Element.Methods.Simulated);
1024 1121
1025 var methods = Object.clone(Element.Methods.Simulated), cache = Element.extend.cache;
1026 1122 for (var property in methods) {
1027 1123 var value = methods[property];
1028 if ('function' == typeof value && !(property in element))
1124 if (typeof value == 'function' && !(property in element))
1029 1125 element[property] = cache.findOrStore(value);
1030 1126 }
1031 1127 }
1032 1128
1033 1129 element._extended = true;
1034 1130 return element;
1035 }
1131 };
1036 1132
1037 1133 Element.extend.cache = {
1038 1134 findOrStore: function(value) {
1039 1135 return this[value] = this[value] || function() {
1040 1136 return value.apply(null, [this].concat($A(arguments)));
1041 1137 }
1042 1138 }
1043 }
1139 };
1044 1140
1045 1141 Element.Methods = {
1046 1142 visible: function(element) {
1047 1143 return $(element).style.display != 'none';
1048 1144 },
1049 1145
1050 1146 toggle: function(element) {
1051 1147 element = $(element);
1052 1148 Element[Element.visible(element) ? 'hide' : 'show'](element);
1053 1149 return element;
1054 1150 },
1055 1151
1056 1152 hide: function(element) {
1057 1153 $(element).style.display = 'none';
1058 1154 return element;
1059 1155 },
1060 1156
1061 1157 show: function(element) {
1062 1158 $(element).style.display = '';
1063 1159 return element;
1064 1160 },
1065 1161
1066 1162 remove: function(element) {
1067 1163 element = $(element);
1068 1164 element.parentNode.removeChild(element);
1069 1165 return element;
1070 1166 },
1071 1167
1072 1168 update: function(element, html) {
1073 1169 html = typeof html == 'undefined' ? '' : html.toString();
1074 1170 $(element).innerHTML = html.stripScripts();
1075 1171 setTimeout(function() {html.evalScripts()}, 10);
1076 1172 return element;
1077 1173 },
1078 1174
1079 1175 replace: function(element, html) {
1080 1176 element = $(element);
1177 html = typeof html == 'undefined' ? '' : html.toString();
1081 1178 if (element.outerHTML) {
1082 1179 element.outerHTML = html.stripScripts();
1083 1180 } else {
1084 1181 var range = element.ownerDocument.createRange();
1085 1182 range.selectNodeContents(element);
1086 1183 element.parentNode.replaceChild(
1087 1184 range.createContextualFragment(html.stripScripts()), element);
1088 1185 }
1089 1186 setTimeout(function() {html.evalScripts()}, 10);
1090 1187 return element;
1091 1188 },
1092 1189
1093 1190 inspect: function(element) {
1094 1191 element = $(element);
1095 1192 var result = '<' + element.tagName.toLowerCase();
1096 1193 $H({'id': 'id', 'className': 'class'}).each(function(pair) {
1097 1194 var property = pair.first(), attribute = pair.last();
1098 1195 var value = (element[property] || '').toString();
1099 1196 if (value) result += ' ' + attribute + '=' + value.inspect(true);
1100 1197 });
1101 1198 return result + '>';
1102 1199 },
1103 1200
1104 1201 recursivelyCollect: function(element, property) {
1105 1202 element = $(element);
1106 1203 var elements = [];
1107 1204 while (element = element[property])
1108 1205 if (element.nodeType == 1)
1109 1206 elements.push(Element.extend(element));
1110 1207 return elements;
1111 1208 },
1112 1209
1113 1210 ancestors: function(element) {
1114 1211 return $(element).recursivelyCollect('parentNode');
1115 1212 },
1116 1213
1117 1214 descendants: function(element) {
1118 element = $(element);
1119 return $A(element.getElementsByTagName('*'));
1215 return $A($(element).getElementsByTagName('*'));
1216 },
1217
1218 immediateDescendants: function(element) {
1219 if (!(element = $(element).firstChild)) return [];
1220 while (element && element.nodeType != 1) element = element.nextSibling;
1221 if (element) return [element].concat($(element).nextSiblings());
1222 return [];
1120 1223 },
1121 1224
1122 1225 previousSiblings: function(element) {
1123 1226 return $(element).recursivelyCollect('previousSibling');
1124 1227 },
1125 1228
1126 1229 nextSiblings: function(element) {
1127 1230 return $(element).recursivelyCollect('nextSibling');
1128 1231 },
1129 1232
1130 1233 siblings: function(element) {
1131 1234 element = $(element);
1132 1235 return element.previousSiblings().reverse().concat(element.nextSiblings());
1133 1236 },
1134 1237
1135 1238 match: function(element, selector) {
1136 element = $(element);
1137 1239 if (typeof selector == 'string')
1138 1240 selector = new Selector(selector);
1139 return selector.match(element);
1241 return selector.match($(element));
1140 1242 },
1141 1243
1142 1244 up: function(element, expression, index) {
1143 1245 return Selector.findElement($(element).ancestors(), expression, index);
1144 1246 },
1145 1247
1146 1248 down: function(element, expression, index) {
1147 1249 return Selector.findElement($(element).descendants(), expression, index);
1148 1250 },
1149 1251
1150 1252 previous: function(element, expression, index) {
1151 1253 return Selector.findElement($(element).previousSiblings(), expression, index);
1152 1254 },
1153 1255
1154 1256 next: function(element, expression, index) {
1155 1257 return Selector.findElement($(element).nextSiblings(), expression, index);
1156 1258 },
1157 1259
1158 1260 getElementsBySelector: function() {
1159 1261 var args = $A(arguments), element = $(args.shift());
1160 1262 return Selector.findChildElements(element, args);
1161 1263 },
1162 1264
1163 1265 getElementsByClassName: function(element, className) {
1164 element = $(element);
1165 1266 return document.getElementsByClassName(className, element);
1166 1267 },
1167 1268
1168 getHeight: function(element) {
1269 readAttribute: function(element, name) {
1169 1270 element = $(element);
1170 return element.offsetHeight;
1271 if (document.all && !window.opera) {
1272 var t = Element._attributeTranslations;
1273 if (t.values[name]) return t.values[name](element, name);
1274 if (t.names[name]) name = t.names[name];
1275 var attribute = element.attributes[name];
1276 if(attribute) return attribute.nodeValue;
1277 }
1278 return element.getAttribute(name);
1279 },
1280
1281 getHeight: function(element) {
1282 return $(element).getDimensions().height;
1283 },
1284
1285 getWidth: function(element) {
1286 return $(element).getDimensions().width;
1171 1287 },
1172 1288
1173 1289 classNames: function(element) {
1174 1290 return new Element.ClassNames(element);
1175 1291 },
1176 1292
1177 1293 hasClassName: function(element, className) {
1178 1294 if (!(element = $(element))) return;
1179 1295 var elementClassName = element.className;
1180 1296 if (elementClassName.length == 0) return false;
1181 1297 if (elementClassName == className ||
1182 1298 elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
1183 1299 return true;
1184 1300 return false;
1185 1301 },
1186 1302
1187 1303 addClassName: function(element, className) {
1188 1304 if (!(element = $(element))) return;
1189 1305 Element.classNames(element).add(className);
1190 1306 return element;
1191 1307 },
1192 1308
1193 1309 removeClassName: function(element, className) {
1194 1310 if (!(element = $(element))) return;
1195 1311 Element.classNames(element).remove(className);
1196 1312 return element;
1197 1313 },
1198 1314
1315 toggleClassName: function(element, className) {
1316 if (!(element = $(element))) return;
1317 Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
1318 return element;
1319 },
1320
1199 1321 observe: function() {
1200 1322 Event.observe.apply(Event, arguments);
1201 1323 return $A(arguments).first();
1202 1324 },
1203 1325
1204 1326 stopObserving: function() {
1205 1327 Event.stopObserving.apply(Event, arguments);
1206 1328 return $A(arguments).first();
1207 1329 },
1208 1330
1209 1331 // removes whitespace-only text node children
1210 1332 cleanWhitespace: function(element) {
1211 1333 element = $(element);
1212 1334 var node = element.firstChild;
1213 1335 while (node) {
1214 1336 var nextNode = node.nextSibling;
1215 1337 if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
1216 1338 element.removeChild(node);
1217 1339 node = nextNode;
1218 1340 }
1219 1341 return element;
1220 1342 },
1221 1343
1222 1344 empty: function(element) {
1223 1345 return $(element).innerHTML.match(/^\s*$/);
1224 1346 },
1225 1347
1226 childOf: function(element, ancestor) {
1348 descendantOf: function(element, ancestor) {
1227 1349 element = $(element), ancestor = $(ancestor);
1228 1350 while (element = element.parentNode)
1229 1351 if (element == ancestor) return true;
1230 1352 return false;
1231 1353 },
1232 1354
1233 1355 scrollTo: function(element) {
1234 1356 element = $(element);
1235 var x = element.x ? element.x : element.offsetLeft,
1236 y = element.y ? element.y : element.offsetTop;
1237 window.scrollTo(x, y);
1357 var pos = Position.cumulativeOffset(element);
1358 window.scrollTo(pos[0], pos[1]);
1238 1359 return element;
1239 1360 },
1240 1361
1241 1362 getStyle: function(element, style) {
1242 1363 element = $(element);
1243 var value = element.style[style.camelize()];
1364 if (['float','cssFloat'].include(style))
1365 style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat');
1366 style = style.camelize();
1367 var value = element.style[style];
1244 1368 if (!value) {
1245 1369 if (document.defaultView && document.defaultView.getComputedStyle) {
1246 1370 var css = document.defaultView.getComputedStyle(element, null);
1247 value = css ? css.getPropertyValue(style) : null;
1371 value = css ? css[style] : null;
1248 1372 } else if (element.currentStyle) {
1249 value = element.currentStyle[style.camelize()];
1373 value = element.currentStyle[style];
1250 1374 }
1251 1375 }
1252 1376
1377 if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none'))
1378 value = element['offset'+style.capitalize()] + 'px';
1379
1253 1380 if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
1254 1381 if (Element.getStyle(element, 'position') == 'static') value = 'auto';
1255
1382 if(style == 'opacity') {
1383 if(value) return parseFloat(value);
1384 if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
1385 if(value[1]) return parseFloat(value[1]) / 100;
1386 return 1.0;
1387 }
1256 1388 return value == 'auto' ? null : value;
1257 1389 },
1258 1390
1259 1391 setStyle: function(element, style) {
1260 1392 element = $(element);
1261 for (var name in style)
1262 element.style[name.camelize()] = style[name];
1393 for (var name in style) {
1394 var value = style[name];
1395 if(name == 'opacity') {
1396 if (value == 1) {
1397 value = (/Gecko/.test(navigator.userAgent) &&
1398 !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0;
1399 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1400 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
1401 } else if(value == '') {
1402 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1403 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
1404 } else {
1405 if(value < 0.00001) value = 0;
1406 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1407 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
1408 'alpha(opacity='+value*100+')';
1409 }
1410 } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat';
1411 element.style[name.camelize()] = value;
1412 }
1263 1413 return element;
1264 1414 },
1265 1415
1266 1416 getDimensions: function(element) {
1267 1417 element = $(element);
1268 if (Element.getStyle(element, 'display') != 'none')
1418 var display = $(element).getStyle('display');
1419 if (display != 'none' && display != null) // Safari bug
1269 1420 return {width: element.offsetWidth, height: element.offsetHeight};
1270 1421
1271 1422 // All *Width and *Height properties give 0 on elements with display none,
1272 1423 // so enable the element temporarily
1273 1424 var els = element.style;
1274 1425 var originalVisibility = els.visibility;
1275 1426 var originalPosition = els.position;
1427 var originalDisplay = els.display;
1276 1428 els.visibility = 'hidden';
1277 1429 els.position = 'absolute';
1278 els.display = '';
1430 els.display = 'block';
1279 1431 var originalWidth = element.clientWidth;
1280 1432 var originalHeight = element.clientHeight;
1281 els.display = 'none';
1433 els.display = originalDisplay;
1282 1434 els.position = originalPosition;
1283 1435 els.visibility = originalVisibility;
1284 1436 return {width: originalWidth, height: originalHeight};
1285 1437 },
1286 1438
1287 1439 makePositioned: function(element) {
1288 1440 element = $(element);
1289 1441 var pos = Element.getStyle(element, 'position');
1290 1442 if (pos == 'static' || !pos) {
1291 1443 element._madePositioned = true;
1292 1444 element.style.position = 'relative';
1293 1445 // Opera returns the offset relative to the positioning context, when an
1294 1446 // element is position relative but top and left have not been defined
1295 1447 if (window.opera) {
1296 1448 element.style.top = 0;
1297 1449 element.style.left = 0;
1298 1450 }
1299 1451 }
1300 1452 return element;
1301 1453 },
1302 1454
1303 1455 undoPositioned: function(element) {
1304 1456 element = $(element);
1305 1457 if (element._madePositioned) {
1306 1458 element._madePositioned = undefined;
1307 1459 element.style.position =
1308 1460 element.style.top =
1309 1461 element.style.left =
1310 1462 element.style.bottom =
1311 1463 element.style.right = '';
1312 1464 }
1313 1465 return element;
1314 1466 },
1315 1467
1316 1468 makeClipping: function(element) {
1317 1469 element = $(element);
1318 1470 if (element._overflow) return element;
1319 1471 element._overflow = element.style.overflow || 'auto';
1320 1472 if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1321 1473 element.style.overflow = 'hidden';
1322 1474 return element;
1323 1475 },
1324 1476
1325 1477 undoClipping: function(element) {
1326 1478 element = $(element);
1327 1479 if (!element._overflow) return element;
1328 1480 element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
1329 1481 element._overflow = null;
1330 1482 return element;
1331 1483 }
1332 }
1484 };
1485
1486 Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf});
1487
1488 Element._attributeTranslations = {};
1489
1490 Element._attributeTranslations.names = {
1491 colspan: "colSpan",
1492 rowspan: "rowSpan",
1493 valign: "vAlign",
1494 datetime: "dateTime",
1495 accesskey: "accessKey",
1496 tabindex: "tabIndex",
1497 enctype: "encType",
1498 maxlength: "maxLength",
1499 readonly: "readOnly",
1500 longdesc: "longDesc"
1501 };
1502
1503 Element._attributeTranslations.values = {
1504 _getAttr: function(element, attribute) {
1505 return element.getAttribute(attribute, 2);
1506 },
1507
1508 _flag: function(element, attribute) {
1509 return $(element).hasAttribute(attribute) ? attribute : null;
1510 },
1511
1512 style: function(element) {
1513 return element.style.cssText.toLowerCase();
1514 },
1515
1516 title: function(element) {
1517 var node = element.getAttributeNode('title');
1518 return node.specified ? node.nodeValue : null;
1519 }
1520 };
1521
1522 Object.extend(Element._attributeTranslations.values, {
1523 href: Element._attributeTranslations.values._getAttr,
1524 src: Element._attributeTranslations.values._getAttr,
1525 disabled: Element._attributeTranslations.values._flag,
1526 checked: Element._attributeTranslations.values._flag,
1527 readonly: Element._attributeTranslations.values._flag,
1528 multiple: Element._attributeTranslations.values._flag
1529 });
1333 1530
1334 1531 Element.Methods.Simulated = {
1335 1532 hasAttribute: function(element, attribute) {
1533 var t = Element._attributeTranslations;
1534 attribute = t.names[attribute] || attribute;
1336 1535 return $(element).getAttributeNode(attribute).specified;
1337 1536 }
1338 }
1537 };
1339 1538
1340 1539 // IE is missing .innerHTML support for TABLE-related elements
1341 if(document.all){
1540 if (document.all && !window.opera){
1342 1541 Element.Methods.update = function(element, html) {
1343 1542 element = $(element);
1344 1543 html = typeof html == 'undefined' ? '' : html.toString();
1345 1544 var tagName = element.tagName.toUpperCase();
1346 if (['THEAD','TBODY','TR','TD'].indexOf(tagName) > -1) {
1545 if (['THEAD','TBODY','TR','TD'].include(tagName)) {
1347 1546 var div = document.createElement('div');
1348 1547 switch (tagName) {
1349 1548 case 'THEAD':
1350 1549 case 'TBODY':
1351 1550 div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>';
1352 1551 depth = 2;
1353 1552 break;
1354 1553 case 'TR':
1355 1554 div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>';
1356 1555 depth = 3;
1357 1556 break;
1358 1557 case 'TD':
1359 1558 div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>';
1360 1559 depth = 4;
1361 1560 }
1362 1561 $A(element.childNodes).each(function(node){
1363 1562 element.removeChild(node)
1364 1563 });
1365 1564 depth.times(function(){ div = div.firstChild });
1366 1565
1367 1566 $A(div.childNodes).each(
1368 1567 function(node){ element.appendChild(node) });
1369 1568 } else {
1370 1569 element.innerHTML = html.stripScripts();
1371 1570 }
1372 1571 setTimeout(function() {html.evalScripts()}, 10);
1373 1572 return element;
1374 1573 }
1375 }
1574 };
1376 1575
1377 1576 Object.extend(Element, Element.Methods);
1378 1577
1379 1578 var _nativeExtensions = false;
1380 1579
1381 1580 if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1382 1581 ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {
1383 1582 var className = 'HTML' + tag + 'Element';
1384 1583 if(window[className]) return;
1385 1584 var klass = window[className] = {};
1386 1585 klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__;
1387 1586 });
1388 1587
1389 1588 Element.addMethods = function(methods) {
1390 1589 Object.extend(Element.Methods, methods || {});
1391 1590
1392 1591 function copy(methods, destination, onlyIfAbsent) {
1393 1592 onlyIfAbsent = onlyIfAbsent || false;
1394 1593 var cache = Element.extend.cache;
1395 1594 for (var property in methods) {
1396 1595 var value = methods[property];
1397 1596 if (!onlyIfAbsent || !(property in destination))
1398 1597 destination[property] = cache.findOrStore(value);
1399 1598 }
1400 1599 }
1401 1600
1402 1601 if (typeof HTMLElement != 'undefined') {
1403 1602 copy(Element.Methods, HTMLElement.prototype);
1404 1603 copy(Element.Methods.Simulated, HTMLElement.prototype, true);
1405 1604 copy(Form.Methods, HTMLFormElement.prototype);
1406 1605 [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {
1407 1606 copy(Form.Element.Methods, klass.prototype);
1408 1607 });
1409 1608 _nativeExtensions = true;
1410 1609 }
1411 1610 }
1412 1611
1413 1612 var Toggle = new Object();
1414 1613 Toggle.display = Element.toggle;
1415 1614
1416 1615 /*--------------------------------------------------------------------------*/
1417 1616
1418 1617 Abstract.Insertion = function(adjacency) {
1419 1618 this.adjacency = adjacency;
1420 1619 }
1421 1620
1422 1621 Abstract.Insertion.prototype = {
1423 1622 initialize: function(element, content) {
1424 1623 this.element = $(element);
1425 1624 this.content = content.stripScripts();
1426 1625
1427 1626 if (this.adjacency && this.element.insertAdjacentHTML) {
1428 1627 try {
1429 1628 this.element.insertAdjacentHTML(this.adjacency, this.content);
1430 1629 } catch (e) {
1431 var tagName = this.element.tagName.toLowerCase();
1432 if (tagName == 'tbody' || tagName == 'tr') {
1630 var tagName = this.element.tagName.toUpperCase();
1631 if (['TBODY', 'TR'].include(tagName)) {
1433 1632 this.insertContent(this.contentFromAnonymousTable());
1434 1633 } else {
1435 1634 throw e;
1436 1635 }
1437 1636 }
1438 1637 } else {
1439 1638 this.range = this.element.ownerDocument.createRange();
1440 1639 if (this.initializeRange) this.initializeRange();
1441 1640 this.insertContent([this.range.createContextualFragment(this.content)]);
1442 1641 }
1443 1642
1444 1643 setTimeout(function() {content.evalScripts()}, 10);
1445 1644 },
1446 1645
1447 1646 contentFromAnonymousTable: function() {
1448 1647 var div = document.createElement('div');
1449 1648 div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
1450 1649 return $A(div.childNodes[0].childNodes[0].childNodes);
1451 1650 }
1452 1651 }
1453 1652
1454 1653 var Insertion = new Object();
1455 1654
1456 1655 Insertion.Before = Class.create();
1457 1656 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
1458 1657 initializeRange: function() {
1459 1658 this.range.setStartBefore(this.element);
1460 1659 },
1461 1660
1462 1661 insertContent: function(fragments) {
1463 1662 fragments.each((function(fragment) {
1464 1663 this.element.parentNode.insertBefore(fragment, this.element);
1465 1664 }).bind(this));
1466 1665 }
1467 1666 });
1468 1667
1469 1668 Insertion.Top = Class.create();
1470 1669 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
1471 1670 initializeRange: function() {
1472 1671 this.range.selectNodeContents(this.element);
1473 1672 this.range.collapse(true);
1474 1673 },
1475 1674
1476 1675 insertContent: function(fragments) {
1477 1676 fragments.reverse(false).each((function(fragment) {
1478 1677 this.element.insertBefore(fragment, this.element.firstChild);
1479 1678 }).bind(this));
1480 1679 }
1481 1680 });
1482 1681
1483 1682 Insertion.Bottom = Class.create();
1484 1683 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
1485 1684 initializeRange: function() {
1486 1685 this.range.selectNodeContents(this.element);
1487 1686 this.range.collapse(this.element);
1488 1687 },
1489 1688
1490 1689 insertContent: function(fragments) {
1491 1690 fragments.each((function(fragment) {
1492 1691 this.element.appendChild(fragment);
1493 1692 }).bind(this));
1494 1693 }
1495 1694 });
1496 1695
1497 1696 Insertion.After = Class.create();
1498 1697 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
1499 1698 initializeRange: function() {
1500 1699 this.range.setStartAfter(this.element);
1501 1700 },
1502 1701
1503 1702 insertContent: function(fragments) {
1504 1703 fragments.each((function(fragment) {
1505 1704 this.element.parentNode.insertBefore(fragment,
1506 1705 this.element.nextSibling);
1507 1706 }).bind(this));
1508 1707 }
1509 1708 });
1510 1709
1511 1710 /*--------------------------------------------------------------------------*/
1512 1711
1513 1712 Element.ClassNames = Class.create();
1514 1713 Element.ClassNames.prototype = {
1515 1714 initialize: function(element) {
1516 1715 this.element = $(element);
1517 1716 },
1518 1717
1519 1718 _each: function(iterator) {
1520 1719 this.element.className.split(/\s+/).select(function(name) {
1521 1720 return name.length > 0;
1522 1721 })._each(iterator);
1523 1722 },
1524 1723
1525 1724 set: function(className) {
1526 1725 this.element.className = className;
1527 1726 },
1528 1727
1529 1728 add: function(classNameToAdd) {
1530 1729 if (this.include(classNameToAdd)) return;
1531 1730 this.set($A(this).concat(classNameToAdd).join(' '));
1532 1731 },
1533 1732
1534 1733 remove: function(classNameToRemove) {
1535 1734 if (!this.include(classNameToRemove)) return;
1536 1735 this.set($A(this).without(classNameToRemove).join(' '));
1537 1736 },
1538 1737
1539 1738 toString: function() {
1540 1739 return $A(this).join(' ');
1541 1740 }
1542 }
1741 };
1543 1742
1544 1743 Object.extend(Element.ClassNames.prototype, Enumerable);
1545 1744 var Selector = Class.create();
1546 1745 Selector.prototype = {
1547 1746 initialize: function(expression) {
1548 1747 this.params = {classNames: []};
1549 1748 this.expression = expression.toString().strip();
1550 1749 this.parseExpression();
1551 1750 this.compileMatcher();
1552 1751 },
1553 1752
1554 1753 parseExpression: function() {
1555 1754 function abort(message) { throw 'Parse error in selector: ' + message; }
1556 1755
1557 1756 if (this.expression == '') abort('empty expression');
1558 1757
1559 1758 var params = this.params, expr = this.expression, match, modifier, clause, rest;
1560 1759 while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
1561 1760 params.attributes = params.attributes || [];
1562 1761 params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
1563 1762 expr = match[1];
1564 1763 }
1565 1764
1566 1765 if (expr == '*') return this.params.wildcard = true;
1567 1766
1568 1767 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
1569 1768 modifier = match[1], clause = match[2], rest = match[3];
1570 1769 switch (modifier) {
1571 1770 case '#': params.id = clause; break;
1572 1771 case '.': params.classNames.push(clause); break;
1573 1772 case '':
1574 1773 case undefined: params.tagName = clause.toUpperCase(); break;
1575 1774 default: abort(expr.inspect());
1576 1775 }
1577 1776 expr = rest;
1578 1777 }
1579 1778
1580 1779 if (expr.length > 0) abort(expr.inspect());
1581 1780 },
1582 1781
1583 1782 buildMatchExpression: function() {
1584 1783 var params = this.params, conditions = [], clause;
1585 1784
1586 1785 if (params.wildcard)
1587 1786 conditions.push('true');
1588 1787 if (clause = params.id)
1589 conditions.push('element.id == ' + clause.inspect());
1788 conditions.push('element.readAttribute("id") == ' + clause.inspect());
1590 1789 if (clause = params.tagName)
1591 1790 conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
1592 1791 if ((clause = params.classNames).length > 0)
1593 for (var i = 0; i < clause.length; i++)
1594 conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
1792 for (var i = 0, length = clause.length; i < length; i++)
1793 conditions.push('element.hasClassName(' + clause[i].inspect() + ')');
1595 1794 if (clause = params.attributes) {
1596 1795 clause.each(function(attribute) {
1597 var value = 'element.getAttribute(' + attribute.name.inspect() + ')';
1796 var value = 'element.readAttribute(' + attribute.name.inspect() + ')';
1598 1797 var splitValueBy = function(delimiter) {
1599 1798 return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
1600 1799 }
1601 1800
1602 1801 switch (attribute.operator) {
1603 1802 case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
1604 1803 case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
1605 1804 case '|=': conditions.push(
1606 1805 splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
1607 1806 ); break;
1608 1807 case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
1609 1808 case '':
1610 case undefined: conditions.push(value + ' != null'); break;
1809 case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break;
1611 1810 default: throw 'Unknown operator ' + attribute.operator + ' in selector';
1612 1811 }
1613 1812 });
1614 1813 }
1615 1814
1616 1815 return conditions.join(' && ');
1617 1816 },
1618 1817
1619 1818 compileMatcher: function() {
1620 1819 this.match = new Function('element', 'if (!element.tagName) return false; \
1820 element = $(element); \
1621 1821 return ' + this.buildMatchExpression());
1622 1822 },
1623 1823
1624 1824 findElements: function(scope) {
1625 1825 var element;
1626 1826
1627 1827 if (element = $(this.params.id))
1628 1828 if (this.match(element))
1629 1829 if (!scope || Element.childOf(element, scope))
1630 1830 return [element];
1631 1831
1632 1832 scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
1633 1833
1634 1834 var results = [];
1635 1835 for (var i = 0, length = scope.length; i < length; i++)
1636 1836 if (this.match(element = scope[i]))
1637 1837 results.push(Element.extend(element));
1638 1838
1639 1839 return results;
1640 1840 },
1641 1841
1642 1842 toString: function() {
1643 1843 return this.expression;
1644 1844 }
1645 1845 }
1646 1846
1647 1847 Object.extend(Selector, {
1648 1848 matchElements: function(elements, expression) {
1649 1849 var selector = new Selector(expression);
1650 return elements.select(selector.match.bind(selector)).collect(Element.extend);
1850 return elements.select(selector.match.bind(selector)).map(Element.extend);
1651 1851 },
1652 1852
1653 1853 findElement: function(elements, expression, index) {
1654 1854 if (typeof expression == 'number') index = expression, expression = false;
1655 1855 return Selector.matchElements(elements, expression || '*')[index || 0];
1656 1856 },
1657 1857
1658 1858 findChildElements: function(element, expressions) {
1659 1859 return expressions.map(function(expression) {
1660 return expression.strip().split(/\s+/).inject([null], function(results, expr) {
1860 return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {
1661 1861 var selector = new Selector(expr);
1662 1862 return results.inject([], function(elements, result) {
1663 1863 return elements.concat(selector.findElements(result || element));
1664 1864 });
1665 1865 });
1666 1866 }).flatten();
1667 1867 }
1668 1868 });
1669 1869
1670 1870 function $$() {
1671 1871 return Selector.findChildElements(document, $A(arguments));
1672 1872 }
1673 1873 var Form = {
1674 1874 reset: function(form) {
1675 1875 $(form).reset();
1676 1876 return form;
1677 1877 },
1678 1878
1679 serializeElements: function(elements) {
1680 return elements.inject([], function(queryComponents, element) {
1681 var queryComponent = Form.Element.serialize(element);
1682 if (queryComponent) queryComponents.push(queryComponent);
1683 return queryComponents;
1684 }).join('&');
1879 serializeElements: function(elements, getHash) {
1880 var data = elements.inject({}, function(result, element) {
1881 if (!element.disabled && element.name) {
1882 var key = element.name, value = $(element).getValue();
1883 if (value != undefined) {
1884 if (result[key]) {
1885 if (result[key].constructor != Array) result[key] = [result[key]];
1886 result[key].push(value);
1887 }
1888 else result[key] = value;
1889 }
1890 }
1891 return result;
1892 });
1893
1894 return getHash ? data : Hash.toQueryString(data);
1685 1895 }
1686 1896 };
1687 1897
1688 1898 Form.Methods = {
1689 serialize: function(form) {
1690 return Form.serializeElements($(form).getElements());
1899 serialize: function(form, getHash) {
1900 return Form.serializeElements(Form.getElements(form), getHash);
1691 1901 },
1692 1902
1693 1903 getElements: function(form) {
1694 1904 return $A($(form).getElementsByTagName('*')).inject([],
1695 1905 function(elements, child) {
1696 1906 if (Form.Element.Serializers[child.tagName.toLowerCase()])
1697 1907 elements.push(Element.extend(child));
1698 1908 return elements;
1699 1909 }
1700 1910 );
1701 1911 },
1702 1912
1703 1913 getInputs: function(form, typeName, name) {
1704 1914 form = $(form);
1705 1915 var inputs = form.getElementsByTagName('input');
1706 1916
1707 if (!typeName && !name)
1708 return inputs;
1917 if (!typeName && !name) return $A(inputs).map(Element.extend);
1709 1918
1710 var matchingInputs = new Array();
1711 for (var i = 0, length = inputs.length; i < length; i++) {
1919 for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
1712 1920 var input = inputs[i];
1713 if ((typeName && input.type != typeName) ||
1714 (name && input.name != name))
1921 if ((typeName && input.type != typeName) || (name && input.name != name))
1715 1922 continue;
1716 1923 matchingInputs.push(Element.extend(input));
1717 1924 }
1718 1925
1719 1926 return matchingInputs;
1720 1927 },
1721 1928
1722 1929 disable: function(form) {
1723 1930 form = $(form);
1724 1931 form.getElements().each(function(element) {
1725 1932 element.blur();
1726 1933 element.disabled = 'true';
1727 1934 });
1728 1935 return form;
1729 1936 },
1730 1937
1731 1938 enable: function(form) {
1732 1939 form = $(form);
1733 1940 form.getElements().each(function(element) {
1734 1941 element.disabled = '';
1735 1942 });
1736 1943 return form;
1737 1944 },
1738 1945
1739 1946 findFirstElement: function(form) {
1740 1947 return $(form).getElements().find(function(element) {
1741 1948 return element.type != 'hidden' && !element.disabled &&
1742 1949 ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
1743 1950 });
1744 1951 },
1745 1952
1746 1953 focusFirstElement: function(form) {
1747 1954 form = $(form);
1748 1955 form.findFirstElement().activate();
1749 1956 return form;
1750 1957 }
1751 1958 }
1752 1959
1753 1960 Object.extend(Form, Form.Methods);
1754 1961
1755 1962 /*--------------------------------------------------------------------------*/
1756 1963
1757 1964 Form.Element = {
1758 1965 focus: function(element) {
1759 1966 $(element).focus();
1760 1967 return element;
1761 1968 },
1762 1969
1763 1970 select: function(element) {
1764 1971 $(element).select();
1765 1972 return element;
1766 1973 }
1767 1974 }
1768 1975
1769 1976 Form.Element.Methods = {
1770 1977 serialize: function(element) {
1771 1978 element = $(element);
1772 if (element.disabled) return '';
1773 var method = element.tagName.toLowerCase();
1774 var parameter = Form.Element.Serializers[method](element);
1775
1776 if (parameter) {
1777 var key = encodeURIComponent(parameter[0]);
1778 if (key.length == 0) return;
1779
1780 if (parameter[1].constructor != Array)
1781 parameter[1] = [parameter[1]];
1782
1783 return parameter[1].map(function(value) {
1784 return key + '=' + encodeURIComponent(value);
1785 }).join('&');
1979 if (!element.disabled && element.name) {
1980 var value = element.getValue();
1981 if (value != undefined) {
1982 var pair = {};
1983 pair[element.name] = value;
1984 return Hash.toQueryString(pair);
1985 }
1786 1986 }
1987 return '';
1787 1988 },
1788 1989
1789 1990 getValue: function(element) {
1790 1991 element = $(element);
1791 1992 var method = element.tagName.toLowerCase();
1792 var parameter = Form.Element.Serializers[method](element);
1793
1794 if (parameter)
1795 return parameter[1];
1993 return Form.Element.Serializers[method](element);
1796 1994 },
1797 1995
1798 1996 clear: function(element) {
1799 1997 $(element).value = '';
1800 1998 return element;
1801 1999 },
1802 2000
1803 2001 present: function(element) {
1804 2002 return $(element).value != '';
1805 2003 },
1806 2004
1807 2005 activate: function(element) {
1808 2006 element = $(element);
1809 2007 element.focus();
1810 if (element.select)
2008 if (element.select && ( element.tagName.toLowerCase() != 'input' ||
2009 !['button', 'reset', 'submit'].include(element.type) ) )
1811 2010 element.select();
1812 2011 return element;
1813 2012 },
1814 2013
1815 2014 disable: function(element) {
1816 2015 element = $(element);
1817 2016 element.disabled = true;
1818 2017 return element;
1819 2018 },
1820 2019
1821 2020 enable: function(element) {
1822 2021 element = $(element);
1823 2022 element.blur();
1824 2023 element.disabled = false;
1825 2024 return element;
1826 2025 }
1827 2026 }
1828 2027
1829 2028 Object.extend(Form.Element, Form.Element.Methods);
1830 2029 var Field = Form.Element;
2030 var $F = Form.Element.getValue;
1831 2031
1832 2032 /*--------------------------------------------------------------------------*/
1833 2033
1834 2034 Form.Element.Serializers = {
1835 2035 input: function(element) {
1836 2036 switch (element.type.toLowerCase()) {
1837 2037 case 'checkbox':
1838 2038 case 'radio':
1839 2039 return Form.Element.Serializers.inputSelector(element);
1840 2040 default:
1841 2041 return Form.Element.Serializers.textarea(element);
1842 2042 }
1843 return false;
1844 2043 },
1845 2044
1846 2045 inputSelector: function(element) {
1847 if (element.checked)
1848 return [element.name, element.value];
2046 return element.checked ? element.value : null;
1849 2047 },
1850 2048
1851 2049 textarea: function(element) {
1852 return [element.name, element.value];
2050 return element.value;
1853 2051 },
1854 2052
1855 2053 select: function(element) {
1856 return Form.Element.Serializers[element.type == 'select-one' ?
2054 return this[element.type == 'select-one' ?
1857 2055 'selectOne' : 'selectMany'](element);
1858 2056 },
1859 2057
1860 2058 selectOne: function(element) {
1861 var value = '', opt, index = element.selectedIndex;
1862 if (index >= 0) {
1863 opt = Element.extend(element.options[index]);
1864 // Uses the new potential extension if hasAttribute isn't native.
1865 value = opt.hasAttribute('value') ? opt.value : opt.text;
1866 }
1867 return [element.name, value];
2059 var index = element.selectedIndex;
2060 return index >= 0 ? this.optionValue(element.options[index]) : null;
1868 2061 },
1869 2062
1870 2063 selectMany: function(element) {
1871 var value = [];
1872 for (var i = 0; i < element.length; i++) {
1873 var opt = Element.extend(element.options[i]);
1874 if (opt.selected)
1875 // Uses the new potential extension if hasAttribute isn't native.
1876 value.push(opt.hasAttribute('value') ? opt.value : opt.text);
2064 var values, length = element.length;
2065 if (!length) return null;
2066
2067 for (var i = 0, values = []; i < length; i++) {
2068 var opt = element.options[i];
2069 if (opt.selected) values.push(this.optionValue(opt));
1877 2070 }
1878 return [element.name, value];
2071 return values;
2072 },
2073
2074 optionValue: function(opt) {
2075 // extend element because hasAttribute may not be native
2076 return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
1879 2077 }
1880 2078 }
1881 2079
1882 2080 /*--------------------------------------------------------------------------*/
1883 2081
1884 var $F = Form.Element.getValue;
1885
1886 /*--------------------------------------------------------------------------*/
1887
1888 2082 Abstract.TimedObserver = function() {}
1889 2083 Abstract.TimedObserver.prototype = {
1890 2084 initialize: function(element, frequency, callback) {
1891 2085 this.frequency = frequency;
1892 2086 this.element = $(element);
1893 2087 this.callback = callback;
1894 2088
1895 2089 this.lastValue = this.getValue();
1896 2090 this.registerCallback();
1897 2091 },
1898 2092
1899 2093 registerCallback: function() {
1900 2094 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
1901 2095 },
1902 2096
1903 2097 onTimerEvent: function() {
1904 2098 var value = this.getValue();
1905 if (this.lastValue != value) {
2099 var changed = ('string' == typeof this.lastValue && 'string' == typeof value
2100 ? this.lastValue != value : String(this.lastValue) != String(value));
2101 if (changed) {
1906 2102 this.callback(this.element, value);
1907 2103 this.lastValue = value;
1908 2104 }
1909 2105 }
1910 2106 }
1911 2107
1912 2108 Form.Element.Observer = Class.create();
1913 2109 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
1914 2110 getValue: function() {
1915 2111 return Form.Element.getValue(this.element);
1916 2112 }
1917 2113 });
1918 2114
1919 2115 Form.Observer = Class.create();
1920 2116 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
1921 2117 getValue: function() {
1922 2118 return Form.serialize(this.element);
1923 2119 }
1924 2120 });
1925 2121
1926 2122 /*--------------------------------------------------------------------------*/
1927 2123
1928 2124 Abstract.EventObserver = function() {}
1929 2125 Abstract.EventObserver.prototype = {
1930 2126 initialize: function(element, callback) {
1931 2127 this.element = $(element);
1932 2128 this.callback = callback;
1933 2129
1934 2130 this.lastValue = this.getValue();
1935 2131 if (this.element.tagName.toLowerCase() == 'form')
1936 2132 this.registerFormCallbacks();
1937 2133 else
1938 2134 this.registerCallback(this.element);
1939 2135 },
1940 2136
1941 2137 onElementEvent: function() {
1942 2138 var value = this.getValue();
1943 2139 if (this.lastValue != value) {
1944 2140 this.callback(this.element, value);
1945 2141 this.lastValue = value;
1946 2142 }
1947 2143 },
1948 2144
1949 2145 registerFormCallbacks: function() {
1950 2146 Form.getElements(this.element).each(this.registerCallback.bind(this));
1951 2147 },
1952 2148
1953 2149 registerCallback: function(element) {
1954 2150 if (element.type) {
1955 2151 switch (element.type.toLowerCase()) {
1956 2152 case 'checkbox':
1957 2153 case 'radio':
1958 2154 Event.observe(element, 'click', this.onElementEvent.bind(this));
1959 2155 break;
1960 2156 default:
1961 2157 Event.observe(element, 'change', this.onElementEvent.bind(this));
1962 2158 break;
1963 2159 }
1964 2160 }
1965 2161 }
1966 2162 }
1967 2163
1968 2164 Form.Element.EventObserver = Class.create();
1969 2165 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
1970 2166 getValue: function() {
1971 2167 return Form.Element.getValue(this.element);
1972 2168 }
1973 2169 });
1974 2170
1975 2171 Form.EventObserver = Class.create();
1976 2172 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
1977 2173 getValue: function() {
1978 2174 return Form.serialize(this.element);
1979 2175 }
1980 2176 });
1981 2177 if (!window.Event) {
1982 2178 var Event = new Object();
1983 2179 }
1984 2180
1985 2181 Object.extend(Event, {
1986 2182 KEY_BACKSPACE: 8,
1987 2183 KEY_TAB: 9,
1988 2184 KEY_RETURN: 13,
1989 2185 KEY_ESC: 27,
1990 2186 KEY_LEFT: 37,
1991 2187 KEY_UP: 38,
1992 2188 KEY_RIGHT: 39,
1993 2189 KEY_DOWN: 40,
1994 2190 KEY_DELETE: 46,
1995 2191 KEY_HOME: 36,
1996 2192 KEY_END: 35,
1997 2193 KEY_PAGEUP: 33,
1998 2194 KEY_PAGEDOWN: 34,
1999 2195
2000 2196 element: function(event) {
2001 2197 return event.target || event.srcElement;
2002 2198 },
2003 2199
2004 2200 isLeftClick: function(event) {
2005 2201 return (((event.which) && (event.which == 1)) ||
2006 2202 ((event.button) && (event.button == 1)));
2007 2203 },
2008 2204
2009 2205 pointerX: function(event) {
2010 2206 return event.pageX || (event.clientX +
2011 2207 (document.documentElement.scrollLeft || document.body.scrollLeft));
2012 2208 },
2013 2209
2014 2210 pointerY: function(event) {
2015 2211 return event.pageY || (event.clientY +
2016 2212 (document.documentElement.scrollTop || document.body.scrollTop));
2017 2213 },
2018 2214
2019 2215 stop: function(event) {
2020 2216 if (event.preventDefault) {
2021 2217 event.preventDefault();
2022 2218 event.stopPropagation();
2023 2219 } else {
2024 2220 event.returnValue = false;
2025 2221 event.cancelBubble = true;
2026 2222 }
2027 2223 },
2028 2224
2029 2225 // find the first node with the given tagName, starting from the
2030 2226 // node the event was triggered on; traverses the DOM upwards
2031 2227 findElement: function(event, tagName) {
2032 2228 var element = Event.element(event);
2033 2229 while (element.parentNode && (!element.tagName ||
2034 2230 (element.tagName.toUpperCase() != tagName.toUpperCase())))
2035 2231 element = element.parentNode;
2036 2232 return element;
2037 2233 },
2038 2234
2039 2235 observers: false,
2040 2236
2041 2237 _observeAndCache: function(element, name, observer, useCapture) {
2042 2238 if (!this.observers) this.observers = [];
2043 2239 if (element.addEventListener) {
2044 2240 this.observers.push([element, name, observer, useCapture]);
2045 2241 element.addEventListener(name, observer, useCapture);
2046 2242 } else if (element.attachEvent) {
2047 2243 this.observers.push([element, name, observer, useCapture]);
2048 2244 element.attachEvent('on' + name, observer);
2049 2245 }
2050 2246 },
2051 2247
2052 2248 unloadCache: function() {
2053 2249 if (!Event.observers) return;
2054 2250 for (var i = 0, length = Event.observers.length; i < length; i++) {
2055 2251 Event.stopObserving.apply(this, Event.observers[i]);
2056 2252 Event.observers[i][0] = null;
2057 2253 }
2058 2254 Event.observers = false;
2059 2255 },
2060 2256
2061 2257 observe: function(element, name, observer, useCapture) {
2062 2258 element = $(element);
2063 2259 useCapture = useCapture || false;
2064 2260
2065 2261 if (name == 'keypress' &&
2066 2262 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
2067 2263 || element.attachEvent))
2068 2264 name = 'keydown';
2069 2265
2070 2266 Event._observeAndCache(element, name, observer, useCapture);
2071 2267 },
2072 2268
2073 2269 stopObserving: function(element, name, observer, useCapture) {
2074 2270 element = $(element);
2075 2271 useCapture = useCapture || false;
2076 2272
2077 2273 if (name == 'keypress' &&
2078 2274 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
2079 2275 || element.detachEvent))
2080 2276 name = 'keydown';
2081 2277
2082 2278 if (element.removeEventListener) {
2083 2279 element.removeEventListener(name, observer, useCapture);
2084 2280 } else if (element.detachEvent) {
2085 2281 try {
2086 2282 element.detachEvent('on' + name, observer);
2087 2283 } catch (e) {}
2088 2284 }
2089 2285 }
2090 2286 });
2091 2287
2092 2288 /* prevent memory leaks in IE */
2093 2289 if (navigator.appVersion.match(/\bMSIE\b/))
2094 2290 Event.observe(window, 'unload', Event.unloadCache, false);
2095 2291 var Position = {
2096 2292 // set to true if needed, warning: firefox performance problems
2097 2293 // NOT neeeded for page scrolling, only if draggable contained in
2098 2294 // scrollable elements
2099 2295 includeScrollOffsets: false,
2100 2296
2101 2297 // must be called before calling withinIncludingScrolloffset, every time the
2102 2298 // page is scrolled
2103 2299 prepare: function() {
2104 2300 this.deltaX = window.pageXOffset
2105 2301 || document.documentElement.scrollLeft
2106 2302 || document.body.scrollLeft
2107 2303 || 0;
2108 2304 this.deltaY = window.pageYOffset
2109 2305 || document.documentElement.scrollTop
2110 2306 || document.body.scrollTop
2111 2307 || 0;
2112 2308 },
2113 2309
2114 2310 realOffset: function(element) {
2115 2311 var valueT = 0, valueL = 0;
2116 2312 do {
2117 2313 valueT += element.scrollTop || 0;
2118 2314 valueL += element.scrollLeft || 0;
2119 2315 element = element.parentNode;
2120 2316 } while (element);
2121 2317 return [valueL, valueT];
2122 2318 },
2123 2319
2124 2320 cumulativeOffset: function(element) {
2125 2321 var valueT = 0, valueL = 0;
2126 2322 do {
2127 2323 valueT += element.offsetTop || 0;
2128 2324 valueL += element.offsetLeft || 0;
2129 2325 element = element.offsetParent;
2130 2326 } while (element);
2131 2327 return [valueL, valueT];
2132 2328 },
2133 2329
2134 2330 positionedOffset: function(element) {
2135 2331 var valueT = 0, valueL = 0;
2136 2332 do {
2137 2333 valueT += element.offsetTop || 0;
2138 2334 valueL += element.offsetLeft || 0;
2139 2335 element = element.offsetParent;
2140 2336 if (element) {
2141 2337 if(element.tagName=='BODY') break;
2142 2338 var p = Element.getStyle(element, 'position');
2143 2339 if (p == 'relative' || p == 'absolute') break;
2144 2340 }
2145 2341 } while (element);
2146 2342 return [valueL, valueT];
2147 2343 },
2148 2344
2149 2345 offsetParent: function(element) {
2150 2346 if (element.offsetParent) return element.offsetParent;
2151 2347 if (element == document.body) return element;
2152 2348
2153 2349 while ((element = element.parentNode) && element != document.body)
2154 2350 if (Element.getStyle(element, 'position') != 'static')
2155 2351 return element;
2156 2352
2157 2353 return document.body;
2158 2354 },
2159 2355
2160 2356 // caches x/y coordinate pair to use with overlap
2161 2357 within: function(element, x, y) {
2162 2358 if (this.includeScrollOffsets)
2163 2359 return this.withinIncludingScrolloffsets(element, x, y);
2164 2360 this.xcomp = x;
2165 2361 this.ycomp = y;
2166 2362 this.offset = this.cumulativeOffset(element);
2167 2363
2168 2364 return (y >= this.offset[1] &&
2169 2365 y < this.offset[1] + element.offsetHeight &&
2170 2366 x >= this.offset[0] &&
2171 2367 x < this.offset[0] + element.offsetWidth);
2172 2368 },
2173 2369
2174 2370 withinIncludingScrolloffsets: function(element, x, y) {
2175 2371 var offsetcache = this.realOffset(element);
2176 2372
2177 2373 this.xcomp = x + offsetcache[0] - this.deltaX;
2178 2374 this.ycomp = y + offsetcache[1] - this.deltaY;
2179 2375 this.offset = this.cumulativeOffset(element);
2180 2376
2181 2377 return (this.ycomp >= this.offset[1] &&
2182 2378 this.ycomp < this.offset[1] + element.offsetHeight &&
2183 2379 this.xcomp >= this.offset[0] &&
2184 2380 this.xcomp < this.offset[0] + element.offsetWidth);
2185 2381 },
2186 2382
2187 2383 // within must be called directly before
2188 2384 overlap: function(mode, element) {
2189 2385 if (!mode) return 0;
2190 2386 if (mode == 'vertical')
2191 2387 return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
2192 2388 element.offsetHeight;
2193 2389 if (mode == 'horizontal')
2194 2390 return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
2195 2391 element.offsetWidth;
2196 2392 },
2197 2393
2198 2394 page: function(forElement) {
2199 2395 var valueT = 0, valueL = 0;
2200 2396
2201 2397 var element = forElement;
2202 2398 do {
2203 2399 valueT += element.offsetTop || 0;
2204 2400 valueL += element.offsetLeft || 0;
2205 2401
2206 2402 // Safari fix
2207 2403 if (element.offsetParent==document.body)
2208 2404 if (Element.getStyle(element,'position')=='absolute') break;
2209 2405
2210 2406 } while (element = element.offsetParent);
2211 2407
2212 2408 element = forElement;
2213 2409 do {
2214 2410 if (!window.opera || element.tagName=='BODY') {
2215 2411 valueT -= element.scrollTop || 0;
2216 2412 valueL -= element.scrollLeft || 0;
2217 2413 }
2218 2414 } while (element = element.parentNode);
2219 2415
2220 2416 return [valueL, valueT];
2221 2417 },
2222 2418
2223 2419 clone: function(source, target) {
2224 2420 var options = Object.extend({
2225 2421 setLeft: true,
2226 2422 setTop: true,
2227 2423 setWidth: true,
2228 2424 setHeight: true,
2229 2425 offsetTop: 0,
2230 2426 offsetLeft: 0
2231 2427 }, arguments[2] || {})
2232 2428
2233 2429 // find page position of source
2234 2430 source = $(source);
2235 2431 var p = Position.page(source);
2236 2432
2237 2433 // find coordinate system to use
2238 2434 target = $(target);
2239 2435 var delta = [0, 0];
2240 2436 var parent = null;
2241 2437 // delta [0,0] will do fine with position: fixed elements,
2242 2438 // position:absolute needs offsetParent deltas
2243 2439 if (Element.getStyle(target,'position') == 'absolute') {
2244 2440 parent = Position.offsetParent(target);
2245 2441 delta = Position.page(parent);
2246 2442 }
2247 2443
2248 2444 // correct by body offsets (fixes Safari)
2249 2445 if (parent == document.body) {
2250 2446 delta[0] -= document.body.offsetLeft;
2251 2447 delta[1] -= document.body.offsetTop;
2252 2448 }
2253 2449
2254 2450 // set position
2255 2451 if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
2256 2452 if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
2257 2453 if(options.setWidth) target.style.width = source.offsetWidth + 'px';
2258 2454 if(options.setHeight) target.style.height = source.offsetHeight + 'px';
2259 2455 },
2260 2456
2261 2457 absolutize: function(element) {
2262 2458 element = $(element);
2263 2459 if (element.style.position == 'absolute') return;
2264 2460 Position.prepare();
2265 2461
2266 2462 var offsets = Position.positionedOffset(element);
2267 2463 var top = offsets[1];
2268 2464 var left = offsets[0];
2269 2465 var width = element.clientWidth;
2270 2466 var height = element.clientHeight;
2271 2467
2272 2468 element._originalLeft = left - parseFloat(element.style.left || 0);
2273 2469 element._originalTop = top - parseFloat(element.style.top || 0);
2274 2470 element._originalWidth = element.style.width;
2275 2471 element._originalHeight = element.style.height;
2276 2472
2277 2473 element.style.position = 'absolute';
2278 element.style.top = top + 'px';;
2279 element.style.left = left + 'px';;
2280 element.style.width = width + 'px';;
2281 element.style.height = height + 'px';;
2474 element.style.top = top + 'px';
2475 element.style.left = left + 'px';
2476 element.style.width = width + 'px';
2477 element.style.height = height + 'px';
2282 2478 },
2283 2479
2284 2480 relativize: function(element) {
2285 2481 element = $(element);
2286 2482 if (element.style.position == 'relative') return;
2287 2483 Position.prepare();
2288 2484
2289 2485 element.style.position = 'relative';
2290 2486 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
2291 2487 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
2292 2488
2293 2489 element.style.top = top + 'px';
2294 2490 element.style.left = left + 'px';
2295 2491 element.style.height = element._originalHeight;
2296 2492 element.style.width = element._originalWidth;
2297 2493 }
2298 2494 }
2299 2495
2300 2496 // Safari returns margins on body which is incorrect if the child is absolutely
2301 2497 // positioned. For performance reasons, redefine Position.cumulativeOffset for
2302 2498 // KHTML/WebKit only.
2303 2499 if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
2304 2500 Position.cumulativeOffset = function(element) {
2305 2501 var valueT = 0, valueL = 0;
2306 2502 do {
2307 2503 valueT += element.offsetTop || 0;
2308 2504 valueL += element.offsetLeft || 0;
2309 2505 if (element.offsetParent == document.body)
2310 2506 if (Element.getStyle(element, 'position') == 'absolute') break;
2311 2507
2312 2508 element = element.offsetParent;
2313 2509 } while (element);
2314 2510
2315 2511 return [valueL, valueT];
2316 2512 }
2317 2513 }
2318 2514
2319 2515 Element.addMethods(); No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now