summaryrefslogtreecommitdiff
path: root/docroot/lib/scriptaculous
diff options
context:
space:
mode:
Diffstat (limited to 'docroot/lib/scriptaculous')
-rwxr-xr-xdocroot/lib/scriptaculous/builder.js136
-rwxr-xr-xdocroot/lib/scriptaculous/controls.js875
-rwxr-xr-xdocroot/lib/scriptaculous/dragdrop.js970
-rwxr-xr-xdocroot/lib/scriptaculous/effects.js1094
-rwxr-xr-xdocroot/lib/scriptaculous/scriptaculous.js58
-rwxr-xr-xdocroot/lib/scriptaculous/slider.js277
-rwxr-xr-xdocroot/lib/scriptaculous/sound.js60
-rwxr-xr-xdocroot/lib/scriptaculous/unittest.js564
8 files changed, 4034 insertions, 0 deletions
diff --git a/docroot/lib/scriptaculous/builder.js b/docroot/lib/scriptaculous/builder.js
new file mode 100755
index 0000000..5b4ce87
--- /dev/null
+++ b/docroot/lib/scriptaculous/builder.js
@@ -0,0 +1,136 @@
1// script.aculo.us builder.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4//
5// script.aculo.us is freely distributable under the terms of an MIT-style license.
6// For details, see the script.aculo.us web site: http://script.aculo.us/
7
8var Builder = {
9 NODEMAP: {
10 AREA: 'map',
11 CAPTION: 'table',
12 COL: 'table',
13 COLGROUP: 'table',
14 LEGEND: 'fieldset',
15 OPTGROUP: 'select',
16 OPTION: 'select',
17 PARAM: 'object',
18 TBODY: 'table',
19 TD: 'table',
20 TFOOT: 'table',
21 TH: 'table',
22 THEAD: 'table',
23 TR: 'table'
24 },
25 // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
26 // due to a Firefox bug
27 node: function(elementName) {
28 elementName = elementName.toUpperCase();
29
30 // try innerHTML approach
31 var parentTag = this.NODEMAP[elementName] || 'div';
32 var parentElement = document.createElement(parentTag);
33 try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
34 parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
35 } catch(e) {}
36 var element = parentElement.firstChild || null;
37
38 // see if browser added wrapping tags
39 if(element && (element.tagName.toUpperCase() != elementName))
40 element = element.getElementsByTagName(elementName)[0];
41
42 // fallback to createElement approach
43 if(!element) element = document.createElement(elementName);
44
45 // abort if nothing could be created
46 if(!element) return;
47
48 // attributes (or text)
49 if(arguments[1])
50 if(this._isStringOrNumber(arguments[1]) ||
51 (arguments[1] instanceof Array) ||
52 arguments[1].tagName) {
53 this._children(element, arguments[1]);
54 } else {
55 var attrs = this._attributes(arguments[1]);
56 if(attrs.length) {
57 try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
58 parentElement.innerHTML = "<" +elementName + " " +
59 attrs + "></" + elementName + ">";
60 } catch(e) {}
61 element = parentElement.firstChild || null;
62 // workaround firefox 1.0.X bug
63 if(!element) {
64 element = document.createElement(elementName);
65 for(attr in arguments[1])
66 element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
67 }
68 if(element.tagName.toUpperCase() != elementName)
69 element = parentElement.getElementsByTagName(elementName)[0];
70 }
71 }
72
73 // text, or array of children
74 if(arguments[2])
75 this._children(element, arguments[2]);
76
77 return element;
78 },
79 _text: function(text) {
80 return document.createTextNode(text);
81 },
82
83 ATTR_MAP: {
84 'className': 'class',
85 'htmlFor': 'for'
86 },
87
88 _attributes: function(attributes) {
89 var attrs = [];
90 for(attribute in attributes)
91 attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
92 '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
93 return attrs.join(" ");
94 },
95 _children: function(element, children) {
96 if(children.tagName) {
97 element.appendChild(children);
98 return;
99 }
100 if(typeof children=='object') { // array can hold nodes and text
101 children.flatten().each( function(e) {
102 if(typeof e=='object')
103 element.appendChild(e)
104 else
105 if(Builder._isStringOrNumber(e))
106 element.appendChild(Builder._text(e));
107 });
108 } else
109 if(Builder._isStringOrNumber(children))
110 element.appendChild(Builder._text(children));
111 },
112 _isStringOrNumber: function(param) {
113 return(typeof param=='string' || typeof param=='number');
114 },
115 build: function(html) {
116 var element = this.node('div');
117 $(element).update(html.strip());
118 return element.down();
119 },
120 dump: function(scope) {
121 if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
122
123 var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
124 "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
125 "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
126 "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
127 "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
128 "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
129
130 tags.each( function(tag){
131 scope[tag] = function() {
132 return Builder.node.apply(Builder, [tag].concat($A(arguments)));
133 }
134 });
135 }
136}
diff --git a/docroot/lib/scriptaculous/controls.js b/docroot/lib/scriptaculous/controls.js
new file mode 100755
index 0000000..6783bd0
--- /dev/null
+++ b/docroot/lib/scriptaculous/controls.js
@@ -0,0 +1,875 @@
1// script.aculo.us controls.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
5// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
6// Contributors:
7// Richard Livsey
8// Rahul Bhargava
9// Rob Wills
10//
11// script.aculo.us is freely distributable under the terms of an MIT-style license.
12// For details, see the script.aculo.us web site: http://script.aculo.us/
13
14// Autocompleter.Base handles all the autocompletion functionality
15// that's independent of the data source for autocompletion. This
16// includes drawing the autocompletion menu, observing keyboard
17// and mouse events, and similar.
18//
19// Specific autocompleters need to provide, at the very least,
20// a getUpdatedChoices function that will be invoked every time
21// the text inside the monitored textbox changes. This method
22// should get the text for which to provide autocompletion by
23// invoking this.getToken(), NOT by directly accessing
24// this.element.value. This is to allow incremental tokenized
25// autocompletion. Specific auto-completion logic (AJAX, etc)
26// belongs in getUpdatedChoices.
27//
28// Tokenized incremental autocompletion is enabled automatically
29// when an autocompleter is instantiated with the 'tokens' option
30// in the options parameter, e.g.:
31// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
32// will incrementally autocomplete with a comma as the token.
33// Additionally, ',' in the above example can be replaced with
34// a token array, e.g. { tokens: [',', '\n'] } which
35// enables autocompletion on multiple tokens. This is most
36// useful when one of the tokens is \n (a newline), as it
37// allows smart autocompletion after linebreaks.
38
39if(typeof Effect == 'undefined')
40 throw("controls.js requires including script.aculo.us' effects.js library");
41
42var Autocompleter = {}
43Autocompleter.Base = function() {};
44Autocompleter.Base.prototype = {
45 baseInitialize: function(element, update, options) {
46 element = $(element)
47 this.element = element;
48 this.update = $(update);
49 this.hasFocus = false;
50 this.changed = false;
51 this.active = false;
52 this.index = 0;
53 this.entryCount = 0;
54
55 if(this.setOptions)
56 this.setOptions(options);
57 else
58 this.options = options || {};
59
60 this.options.paramName = this.options.paramName || this.element.name;
61 this.options.tokens = this.options.tokens || [];
62 this.options.frequency = this.options.frequency || 0.4;
63 this.options.minChars = this.options.minChars || 1;
64 this.options.onShow = this.options.onShow ||
65 function(element, update){
66 if(!update.style.position || update.style.position=='absolute') {
67 update.style.position = 'absolute';
68 Position.clone(element, update, {
69 setHeight: false,
70 offsetTop: element.offsetHeight
71 });
72 }
73 Effect.Appear(update,{duration:0.15});
74 };
75 this.options.onHide = this.options.onHide ||
76 function(element, update){ new Effect.Fade(update,{duration:0.15}) };
77
78 if(typeof(this.options.tokens) == 'string')
79 this.options.tokens = new Array(this.options.tokens);
80
81 this.observer = null;
82
83 this.element.setAttribute('autocomplete','off');
84
85 Element.hide(this.update);
86
87 Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
88 Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this));
89
90 // Turn autocomplete back on when the user leaves the page, so that the
91 // field's value will be remembered on Mozilla-based browsers.
92 Event.observe(window, 'beforeunload', function(){
93 element.setAttribute('autocomplete', 'on');
94 });
95 },
96
97 show: function() {
98 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
99 if(!this.iefix &&
100 (Prototype.Browser.IE) &&
101 (Element.getStyle(this.update, 'position')=='absolute')) {
102 new Insertion.After(this.update,
103 '<iframe id="' + this.update.id + '_iefix" '+
104 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
105 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
106 this.iefix = $(this.update.id+'_iefix');
107 }
108 if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
109 },
110
111 fixIEOverlapping: function() {
112 Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
113 this.iefix.style.zIndex = 1;
114 this.update.style.zIndex = 2;
115 Element.show(this.iefix);
116 },
117
118 hide: function() {
119 this.stopIndicator();
120 if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
121 if(this.iefix) Element.hide(this.iefix);
122 },
123
124 startIndicator: function() {
125 if(this.options.indicator) Element.show(this.options.indicator);
126 },
127
128 stopIndicator: function() {
129 if(this.options.indicator) Element.hide(this.options.indicator);
130 },
131
132 onKeyPress: function(event) {
133 if(this.active)
134 switch(event.keyCode) {
135 case Event.KEY_TAB:
136 case Event.KEY_RETURN:
137 this.selectEntry();
138 Event.stop(event);
139 case Event.KEY_ESC:
140 this.hide();
141 this.active = false;
142 Event.stop(event);
143 return;
144 case Event.KEY_LEFT:
145 case Event.KEY_RIGHT:
146 return;
147 case Event.KEY_UP:
148 this.markPrevious();
149 this.render();
150 if(Prototype.Browser.WebKit) Event.stop(event);
151 return;
152 case Event.KEY_DOWN:
153 this.markNext();
154 this.render();
155 if(Prototype.Browser.WebKit) Event.stop(event);
156 return;
157 }
158 else
159 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
160 (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
161
162 this.changed = true;
163 this.hasFocus = true;
164
165 if(this.observer) clearTimeout(this.observer);
166 this.observer =
167 setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
168 },
169
170 activate: function() {
171 this.changed = false;
172 this.hasFocus = true;
173 this.getUpdatedChoices();
174 },
175
176 onHover: function(event) {
177 var element = Event.findElement(event, 'LI');
178 if(this.index != element.autocompleteIndex)
179 {
180 this.index = element.autocompleteIndex;
181 this.render();
182 }
183 Event.stop(event);
184 },
185
186 onClick: function(event) {
187 var element = Event.findElement(event, 'LI');
188 this.index = element.autocompleteIndex;
189 this.selectEntry();
190 this.hide();
191 },
192
193 onBlur: function(event) {
194 // needed to make click events working
195 setTimeout(this.hide.bind(this), 250);
196 this.hasFocus = false;
197 this.active = false;
198 },
199
200 render: function() {
201 if(this.entryCount > 0) {
202 for (var i = 0; i < this.entryCount; i++)
203 this.index==i ?
204 Element.addClassName(this.getEntry(i),"selected") :
205 Element.removeClassName(this.getEntry(i),"selected");
206 if(this.hasFocus) {
207 this.show();
208 this.active = true;
209 }
210 } else {
211 this.active = false;
212 this.hide();
213 }
214 },
215
216 markPrevious: function() {
217 if(this.index > 0) this.index--
218 else this.index = this.entryCount-1;
219 this.getEntry(this.index).scrollIntoView(true);
220 },
221
222 markNext: function() {
223 if(this.index < this.entryCount-1) this.index++
224 else this.index = 0;
225 this.getEntry(this.index).scrollIntoView(false);
226 },
227
228 getEntry: function(index) {
229 return this.update.firstChild.childNodes[index];
230 },
231
232 getCurrentEntry: function() {
233 return this.getEntry(this.index);
234 },
235
236 selectEntry: function() {
237 this.active = false;
238 this.updateElement(this.getCurrentEntry());
239 },
240
241 updateElement: function(selectedElement) {
242 if (this.options.updateElement) {
243 this.options.updateElement(selectedElement);
244 return;
245 }
246 var value = '';
247 if (this.options.select) {
248 var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
249 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
250 } else
251 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
252
253 var lastTokenPos = this.findLastToken();
254 if (lastTokenPos != -1) {
255 var newValue = this.element.value.substr(0, lastTokenPos + 1);
256 var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
257 if (whitespace)
258 newValue += whitespace[0];
259 this.element.value = newValue + value;
260 } else {
261 this.element.value = value;
262 }
263 this.element.focus();
264
265 if (this.options.afterUpdateElement)
266 this.options.afterUpdateElement(this.element, selectedElement);
267 },
268
269 updateChoices: function(choices) {
270 if(!this.changed && this.hasFocus) {
271 this.update.innerHTML = choices;
272 Element.cleanWhitespace(this.update);
273 Element.cleanWhitespace(this.update.down());
274
275 if(this.update.firstChild && this.update.down().childNodes) {
276 this.entryCount =
277 this.update.down().childNodes.length;
278 for (var i = 0; i < this.entryCount; i++) {
279 var entry = this.getEntry(i);
280 entry.autocompleteIndex = i;
281 this.addObservers(entry);
282 }
283 } else {
284 this.entryCount = 0;
285 }
286
287 this.stopIndicator();
288 this.index = 0;
289
290 if(this.entryCount==1 && this.options.autoSelect) {
291 this.selectEntry();
292 this.hide();
293 } else {
294 this.render();
295 }
296 }
297 },
298
299 addObservers: function(element) {
300 Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
301 Event.observe(element, "click", this.onClick.bindAsEventListener(this));
302 },
303
304 onObserverEvent: function() {
305 this.changed = false;
306 if(this.getToken().length>=this.options.minChars) {
307 this.getUpdatedChoices();
308 } else {
309 this.active = false;
310 this.hide();
311 }
312 },
313
314 getToken: function() {
315 var tokenPos = this.findLastToken();
316 if (tokenPos != -1)
317 var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
318 else
319 var ret = this.element.value;
320
321 return /\n/.test(ret) ? '' : ret;
322 },
323
324 findLastToken: function() {
325 var lastTokenPos = -1;
326
327 for (var i=0; i<this.options.tokens.length; i++) {
328 var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
329 if (thisTokenPos > lastTokenPos)
330 lastTokenPos = thisTokenPos;
331 }
332 return lastTokenPos;
333 }
334}
335
336Ajax.Autocompleter = Class.create();
337Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
338 initialize: function(element, update, url, options) {
339 this.baseInitialize(element, update, options);
340 this.options.asynchronous = true;
341 this.options.onComplete = this.onComplete.bind(this);
342 this.options.defaultParams = this.options.parameters || null;
343 this.url = url;
344 },
345
346 getUpdatedChoices: function() {
347 this.startIndicator();
348
349 var entry = encodeURIComponent(this.options.paramName) + '=' +
350 encodeURIComponent(this.getToken());
351
352 this.options.parameters = this.options.callback ?
353 this.options.callback(this.element, entry) : entry;
354
355 if(this.options.defaultParams)
356 this.options.parameters += '&' + this.options.defaultParams;
357
358 new Ajax.Request(this.url, this.options);
359 },
360
361 onComplete: function(request) {
362 this.updateChoices(request.responseText);
363 }
364
365});
366
367// The local array autocompleter. Used when you'd prefer to
368// inject an array of autocompletion options into the page, rather
369// than sending out Ajax queries, which can be quite slow sometimes.
370//
371// The constructor takes four parameters. The first two are, as usual,
372// the id of the monitored textbox, and id of the autocompletion menu.
373// The third is the array you want to autocomplete from, and the fourth
374// is the options block.
375//
376// Extra local autocompletion options:
377// - choices - How many autocompletion choices to offer
378//
379// - partialSearch - If false, the autocompleter will match entered
380// text only at the beginning of strings in the
381// autocomplete array. Defaults to true, which will
382// match text at the beginning of any *word* in the
383// strings in the autocomplete array. If you want to
384// search anywhere in the string, additionally set
385// the option fullSearch to true (default: off).
386//
387// - fullSsearch - Search anywhere in autocomplete array strings.
388//
389// - partialChars - How many characters to enter before triggering
390// a partial match (unlike minChars, which defines
391// how many characters are required to do any match
392// at all). Defaults to 2.
393//
394// - ignoreCase - Whether to ignore case when autocompleting.
395// Defaults to true.
396//
397// It's possible to pass in a custom function as the 'selector'
398// option, if you prefer to write your own autocompletion logic.
399// In that case, the other options above will not apply unless
400// you support them.
401
402Autocompleter.Local = Class.create();
403Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
404 initialize: function(element, update, array, options) {
405 this.baseInitialize(element, update, options);
406 this.options.array = array;
407 },
408
409 getUpdatedChoices: function() {
410 this.updateChoices(this.options.selector(this));
411 },
412
413 setOptions: function(options) {
414 this.options = Object.extend({
415 choices: 10,
416 partialSearch: true,
417 partialChars: 2,
418 ignoreCase: true,
419 fullSearch: false,
420 selector: function(instance) {
421 var ret = []; // Beginning matches
422 var partial = []; // Inside matches
423 var entry = instance.getToken();
424 var count = 0;
425
426 for (var i = 0; i < instance.options.array.length &&
427 ret.length < instance.options.choices ; i++) {
428
429 var elem = instance.options.array[i];
430 var foundPos = instance.options.ignoreCase ?
431 elem.toLowerCase().indexOf(entry.toLowerCase()) :
432 elem.indexOf(entry);
433
434 while (foundPos != -1) {
435 if (foundPos == 0 && elem.length != entry.length) {
436 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
437 elem.substr(entry.length) + "</li>");
438 break;
439 } else if (entry.length >= instance.options.partialChars &&
440 instance.options.partialSearch && foundPos != -1) {
441 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
442 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
443 elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
444 foundPos + entry.length) + "</li>");
445 break;
446 }
447 }
448
449 foundPos = instance.options.ignoreCase ?
450 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
451 elem.indexOf(entry, foundPos + 1);
452
453 }
454 }
455 if (partial.length)
456 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
457 return "<ul>" + ret.join('') + "</ul>";
458 }
459 }, options || {});
460 }
461});
462
463// AJAX in-place editor
464//
465// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
466
467// Use this if you notice weird scrolling problems on some browsers,
468// the DOM might be a bit confused when this gets called so do this
469// waits 1 ms (with setTimeout) until it does the activation
470Field.scrollFreeActivate = function(field) {
471 setTimeout(function() {
472 Field.activate(field);
473 }, 1);
474}
475
476Ajax.InPlaceEditor = Class.create();
477Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
478Ajax.InPlaceEditor.prototype = {
479 initialize: function(element, url, options) {
480 this.url = url;
481 this.element = $(element);
482
483 this.options = Object.extend({
484 paramName: "value",
485 okButton: true,
486 okLink: false,
487 okText: "ok",
488 cancelButton: false,
489 cancelLink: true,
490 cancelText: "cancel",
491 textBeforeControls: '',
492 textBetweenControls: '',
493 textAfterControls: '',
494 savingText: "Saving...",
495 clickToEditText: "Click to edit",
496 okText: "ok",
497 rows: 1,
498 onComplete: function(transport, element) {
499 new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
500 },
501 onFailure: function(transport) {
502 alert("Error communicating with the server: " + transport.responseText.stripTags());
503 },
504 callback: function(form) {
505 return Form.serialize(form);
506 },
507 handleLineBreaks: true,
508 loadingText: 'Loading...',
509 savingClassName: 'inplaceeditor-saving',
510 loadingClassName: 'inplaceeditor-loading',
511 formClassName: 'inplaceeditor-form',
512 highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
513 highlightendcolor: "#FFFFFF",
514 externalControl: null,
515 submitOnBlur: false,
516 ajaxOptions: {},
517 evalScripts: false
518 }, options || {});
519
520 if(!this.options.formId && this.element.id) {
521 this.options.formId = this.element.id + "-inplaceeditor";
522 if ($(this.options.formId)) {
523 // there's already a form with that name, don't specify an id
524 this.options.formId = null;
525 }
526 }
527
528 if (this.options.externalControl) {
529 this.options.externalControl = $(this.options.externalControl);
530 }
531
532 this.originalBackground = Element.getStyle(this.element, 'background-color');
533 if (!this.originalBackground) {
534 this.originalBackground = "transparent";
535 }
536
537 this.element.title = this.options.clickToEditText;
538
539 this.onclickListener = this.enterEditMode.bindAsEventListener(this);
540 this.mouseoverListener = this.enterHover.bindAsEventListener(this);
541 this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
542 Event.observe(this.element, 'click', this.onclickListener);
543 Event.observe(this.element, 'mouseover', this.mouseoverListener);
544 Event.observe(this.element, 'mouseout', this.mouseoutListener);
545 if (this.options.externalControl) {
546 Event.observe(this.options.externalControl, 'click', this.onclickListener);
547 Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
548 Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
549 }
550 },
551 enterEditMode: function(evt) {
552 if (this.saving) return;
553 if (this.editing) return;
554 this.editing = true;
555 this.onEnterEditMode();
556 if (this.options.externalControl) {
557 Element.hide(this.options.externalControl);
558 }
559 Element.hide(this.element);
560 this.createForm();
561 this.element.parentNode.insertBefore(this.form, this.element);
562 if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
563 // stop the event to avoid a page refresh in Safari
564 if (evt) {
565 Event.stop(evt);
566 }
567 return false;
568 },
569 createForm: function() {
570 this.form = document.createElement("form");
571 this.form.id = this.options.formId;
572 Element.addClassName(this.form, this.options.formClassName)
573 this.form.onsubmit = this.onSubmit.bind(this);
574
575 this.createEditField();
576
577 if (this.options.textarea) {
578 var br = document.createElement("br");
579 this.form.appendChild(br);
580 }
581
582 if (this.options.textBeforeControls)
583 this.form.appendChild(document.createTextNode(this.options.textBeforeControls));
584
585 if (this.options.okButton) {
586 var okButton = document.createElement("input");
587 okButton.type = "submit";
588 okButton.value = this.options.okText;
589 okButton.className = 'editor_ok_button';
590 this.form.appendChild(okButton);
591 }
592
593 if (this.options.okLink) {
594 var okLink = document.createElement("a");
595 okLink.href = "#";
596 okLink.appendChild(document.createTextNode(this.options.okText));
597 okLink.onclick = this.onSubmit.bind(this);
598 okLink.className = 'editor_ok_link';
599 this.form.appendChild(okLink);
600 }
601
602 if (this.options.textBetweenControls &&
603 (this.options.okLink || this.options.okButton) &&
604 (this.options.cancelLink || this.options.cancelButton))
605 this.form.appendChild(document.createTextNode(this.options.textBetweenControls));
606
607 if (this.options.cancelButton) {
608 var cancelButton = document.createElement("input");
609 cancelButton.type = "submit";
610 cancelButton.value = this.options.cancelText;
611 cancelButton.onclick = this.onclickCancel.bind(this);
612 cancelButton.className = 'editor_cancel_button';
613 this.form.appendChild(cancelButton);
614 }
615
616 if (this.options.cancelLink) {
617 var cancelLink = document.createElement("a");
618 cancelLink.href = "#";
619 cancelLink.appendChild(document.createTextNode(this.options.cancelText));
620 cancelLink.onclick = this.onclickCancel.bind(this);
621 cancelLink.className = 'editor_cancel editor_cancel_link';
622 this.form.appendChild(cancelLink);
623 }
624
625 if (this.options.textAfterControls)
626 this.form.appendChild(document.createTextNode(this.options.textAfterControls));
627 },
628 hasHTMLLineBreaks: function(string) {
629 if (!this.options.handleLineBreaks) return false;
630 return string.match(/<br/i) || string.match(/<p>/i);
631 },
632 convertHTMLLineBreaks: function(string) {
633 return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
634 },
635 createEditField: function() {
636 var text;
637 if(this.options.loadTextURL) {
638 text = this.options.loadingText;
639 } else {
640 text = this.getText();
641 }
642
643 var obj = this;
644
645 if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
646 this.options.textarea = false;
647 var textField = document.createElement("input");
648 textField.obj = this;
649 textField.type = "text";
650 textField.name = this.options.paramName;
651 textField.value = text;
652 textField.style.backgroundColor = this.options.highlightcolor;
653 textField.className = 'editor_field';
654 var size = this.options.size || this.options.cols || 0;
655 if (size != 0) textField.size = size;
656 if (this.options.submitOnBlur)
657 textField.onblur = this.onSubmit.bind(this);
658 this.editField = textField;
659 } else {
660 this.options.textarea = true;
661 var textArea = document.createElement("textarea");
662 textArea.obj = this;
663 textArea.name = this.options.paramName;
664 textArea.value = this.convertHTMLLineBreaks(text);
665 textArea.rows = this.options.rows;
666 textArea.cols = this.options.cols || 40;
667 textArea.className = 'editor_field';
668 if (this.options.submitOnBlur)
669 textArea.onblur = this.onSubmit.bind(this);
670 this.editField = textArea;
671 }
672
673 if(this.options.loadTextURL) {
674 this.loadExternalText();
675 }
676 this.form.appendChild(this.editField);
677 },
678 getText: function() {
679 return this.element.innerHTML;
680 },
681 loadExternalText: function() {
682 Element.addClassName(this.form, this.options.loadingClassName);
683 this.editField.disabled = true;
684 new Ajax.Request(
685 this.options.loadTextURL,
686 Object.extend({
687 asynchronous: true,
688 onComplete: this.onLoadedExternalText.bind(this)
689 }, this.options.ajaxOptions)
690 );
691 },
692 onLoadedExternalText: function(transport) {
693 Element.removeClassName(this.form, this.options.loadingClassName);
694 this.editField.disabled = false;
695 this.editField.value = transport.responseText.stripTags();
696 Field.scrollFreeActivate(this.editField);
697 },
698 onclickCancel: function() {
699 this.onComplete();
700 this.leaveEditMode();
701 return false;
702 },
703 onFailure: function(transport) {
704 this.options.onFailure(transport);
705 if (this.oldInnerHTML) {
706 this.element.innerHTML = this.oldInnerHTML;
707 this.oldInnerHTML = null;
708 }
709 return false;
710 },
711 onSubmit: function() {
712 // onLoading resets these so we need to save them away for the Ajax call
713 var form = this.form;
714 var value = this.editField.value;
715
716 // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
717 // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
718 // to be displayed indefinitely
719 this.onLoading();
720
721 if (this.options.evalScripts) {
722 new Ajax.Request(
723 this.url, Object.extend({
724 parameters: this.options.callback(form, value),
725 onComplete: this.onComplete.bind(this),
726 onFailure: this.onFailure.bind(this),
727 asynchronous:true,
728 evalScripts:true
729 }, this.options.ajaxOptions));
730 } else {
731 new Ajax.Updater(
732 { success: this.element,
733 // don't update on failure (this could be an option)
734 failure: null },
735 this.url, Object.extend({
736 parameters: this.options.callback(form, value),
737 onComplete: this.onComplete.bind(this),
738 onFailure: this.onFailure.bind(this)
739 }, this.options.ajaxOptions));
740 }
741 // stop the event to avoid a page refresh in Safari
742 if (arguments.length > 1) {
743 Event.stop(arguments[0]);
744 }
745 return false;
746 },
747 onLoading: function() {
748 this.saving = true;
749 this.removeForm();
750 this.leaveHover();
751 this.showSaving();
752 },
753 showSaving: function() {
754 this.oldInnerHTML = this.element.innerHTML;
755 this.element.innerHTML = this.options.savingText;
756 Element.addClassName(this.element, this.options.savingClassName);
757 this.element.style.backgroundColor = this.originalBackground;
758 Element.show(this.element);
759 },
760 removeForm: function() {
761 if(this.form) {
762 if (this.form.parentNode) Element.remove(this.form);
763 this.form = null;
764 }
765 },
766 enterHover: function() {
767 if (this.saving) return;
768 this.element.style.backgroundColor = this.options.highlightcolor;
769 if (this.effect) {
770 this.effect.cancel();
771 }
772 Element.addClassName(this.element, this.options.hoverClassName)
773 },
774 leaveHover: function() {
775 if (this.options.backgroundColor) {
776 this.element.style.backgroundColor = this.oldBackground;
777 }
778 Element.removeClassName(this.element, this.options.hoverClassName)
779 if (this.saving) return;
780 this.effect = new Effect.Highlight(this.element, {
781 startcolor: this.options.highlightcolor,
782 endcolor: this.options.highlightendcolor,
783 restorecolor: this.originalBackground
784 });
785 },
786 leaveEditMode: function() {
787 Element.removeClassName(this.element, this.options.savingClassName);
788 this.removeForm();
789 this.leaveHover();
790 this.element.style.backgroundColor = this.originalBackground;
791 Element.show(this.element);
792 if (this.options.externalControl) {
793 Element.show(this.options.externalControl);
794 }
795 this.editing = false;
796 this.saving = false;
797 this.oldInnerHTML = null;
798 this.onLeaveEditMode();
799 },
800 onComplete: function(transport) {
801 this.leaveEditMode();
802 this.options.onComplete.bind(this)(transport, this.element);
803 },
804 onEnterEditMode: function() {},
805 onLeaveEditMode: function() {},
806 dispose: function() {
807 if (this.oldInnerHTML) {
808 this.element.innerHTML = this.oldInnerHTML;
809 }
810 this.leaveEditMode();
811 Event.stopObserving(this.element, 'click', this.onclickListener);
812 Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
813 Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
814 if (this.options.externalControl) {
815 Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
816 Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
817 Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
818 }
819 }
820};
821
822Ajax.InPlaceCollectionEditor = Class.create();
823Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
824Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
825 createEditField: function() {
826 if (!this.cached_selectTag) {
827 var selectTag = document.createElement("select");
828 var collection = this.options.collection || [];
829 var optionTag;
830 collection.each(function(e,i) {
831 optionTag = document.createElement("option");
832 optionTag.value = (e instanceof Array) ? e[0] : e;
833 if((typeof this.options.value == 'undefined') &&
834 ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
835 if(this.options.value==optionTag.value) optionTag.selected = true;
836 optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
837 selectTag.appendChild(optionTag);
838 }.bind(this));
839 this.cached_selectTag = selectTag;
840 }
841
842 this.editField = this.cached_selectTag;
843 if(this.options.loadTextURL) this.loadExternalText();
844 this.form.appendChild(this.editField);
845 this.options.callback = function(form, value) {
846 return "value=" + encodeURIComponent(value);
847 }
848 }
849});
850
851// Delayed observer, like Form.Element.Observer,
852// but waits for delay after last key input
853// Ideal for live-search fields
854
855Form.Element.DelayedObserver = Class.create();
856Form.Element.DelayedObserver.prototype = {
857 initialize: function(element, delay, callback) {
858 this.delay = delay || 0.5;
859 this.element = $(element);
860 this.callback = callback;
861 this.timer = null;
862 this.lastValue = $F(this.element);
863 Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
864 },
865 delayedListener: function(event) {
866 if(this.lastValue == $F(this.element)) return;
867 if(this.timer) clearTimeout(this.timer);
868 this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
869 this.lastValue = $F(this.element);
870 },
871 onTimerEvent: function() {
872 this.timer = null;
873 this.callback(this.element, $F(this.element));
874 }
875};
diff --git a/docroot/lib/scriptaculous/dragdrop.js b/docroot/lib/scriptaculous/dragdrop.js
new file mode 100755
index 0000000..108dd66
--- /dev/null
+++ b/docroot/lib/scriptaculous/dragdrop.js
@@ -0,0 +1,970 @@
1// script.aculo.us dragdrop.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
5//
6// script.aculo.us is freely distributable under the terms of an MIT-style license.
7// For details, see the script.aculo.us web site: http://script.aculo.us/
8
9if(typeof Effect == 'undefined')
10 throw("dragdrop.js requires including script.aculo.us' effects.js library");
11
12var Droppables = {
13 drops: [],
14
15 remove: function(element) {
16 this.drops = this.drops.reject(function(d) { return d.element==$(element) });
17 },
18
19 add: function(element) {
20 element = $(element);
21 var options = Object.extend({
22 greedy: true,
23 hoverclass: null,
24 tree: false
25 }, arguments[1] || {});
26
27 // cache containers
28 if(options.containment) {
29 options._containers = [];
30 var containment = options.containment;
31 if((typeof containment == 'object') &&
32 (containment.constructor == Array)) {
33 containment.each( function(c) { options._containers.push($(c)) });
34 } else {
35 options._containers.push($(containment));
36 }
37 }
38
39 if(options.accept) options.accept = [options.accept].flatten();
40
41 Element.makePositioned(element); // fix IE
42 options.element = element;
43
44 this.drops.push(options);
45 },
46
47 findDeepestChild: function(drops) {
48 deepest = drops[0];
49
50 for (i = 1; i < drops.length; ++i)
51 if (Element.isParent(drops[i].element, deepest.element))
52 deepest = drops[i];
53
54 return deepest;
55 },
56
57 isContained: function(element, drop) {
58 var containmentNode;
59 if(drop.tree) {
60 containmentNode = element.treeNode;
61 } else {
62 containmentNode = element.parentNode;
63 }
64 return drop._containers.detect(function(c) { return containmentNode == c });
65 },
66
67 isAffected: function(point, element, drop) {
68 return (
69 (drop.element!=element) &&
70 ((!drop._containers) ||
71 this.isContained(element, drop)) &&
72 ((!drop.accept) ||
73 (Element.classNames(element).detect(
74 function(v) { return drop.accept.include(v) } ) )) &&
75 Position.within(drop.element, point[0], point[1]) );
76 },
77
78 deactivate: function(drop) {
79 if(drop.hoverclass)
80 Element.removeClassName(drop.element, drop.hoverclass);
81 this.last_active = null;
82 },
83
84 activate: function(drop) {
85 if(drop.hoverclass)
86 Element.addClassName(drop.element, drop.hoverclass);
87 this.last_active = drop;
88 },
89
90 show: function(point, element) {
91 if(!this.drops.length) return;
92 var affected = [];
93
94 if(this.last_active) this.deactivate(this.last_active);
95 this.drops.each( function(drop) {
96 if(Droppables.isAffected(point, element, drop))
97 affected.push(drop);
98 });
99
100 if(affected.length>0) {
101 drop = Droppables.findDeepestChild(affected);
102 Position.within(drop.element, point[0], point[1]);
103 if(drop.onHover)
104 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
105
106 Droppables.activate(drop);
107 }
108 },
109
110 fire: function(event, element) {
111 if(!this.last_active) return;
112 Position.prepare();
113
114 if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
115 if (this.last_active.onDrop) {
116 this.last_active.onDrop(element, this.last_active.element, event);
117 return true;
118 }
119 },
120
121 reset: function() {
122 if(this.last_active)
123 this.deactivate(this.last_active);
124 }
125}
126
127var Draggables = {
128 drags: [],
129 observers: [],
130
131 register: function(draggable) {
132 if(this.drags.length == 0) {
133 this.eventMouseUp = this.endDrag.bindAsEventListener(this);
134 this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
135 this.eventKeypress = this.keyPress.bindAsEventListener(this);
136
137 Event.observe(document, "mouseup", this.eventMouseUp);
138 Event.observe(document, "mousemove", this.eventMouseMove);
139 Event.observe(document, "keypress", this.eventKeypress);
140 }
141 this.drags.push(draggable);
142 },
143
144 unregister: function(draggable) {
145 this.drags = this.drags.reject(function(d) { return d==draggable });
146 if(this.drags.length == 0) {
147 Event.stopObserving(document, "mouseup", this.eventMouseUp);
148 Event.stopObserving(document, "mousemove", this.eventMouseMove);
149 Event.stopObserving(document, "keypress", this.eventKeypress);
150 }
151 },
152
153 activate: function(draggable) {
154 if(draggable.options.delay) {
155 this._timeout = setTimeout(function() {
156 Draggables._timeout = null;
157 window.focus();
158 Draggables.activeDraggable = draggable;
159 }.bind(this), draggable.options.delay);
160 } else {
161 window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
162 this.activeDraggable = draggable;
163 }
164 },
165
166 deactivate: function() {
167 this.activeDraggable = null;
168 },
169
170 updateDrag: function(event) {
171 if(!this.activeDraggable) return;
172 var pointer = [Event.pointerX(event), Event.pointerY(event)];
173 // Mozilla-based browsers fire successive mousemove events with
174 // the same coordinates, prevent needless redrawing (moz bug?)
175 if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
176 this._lastPointer = pointer;
177
178 this.activeDraggable.updateDrag(event, pointer);
179 },
180
181 endDrag: function(event) {
182 if(this._timeout) {
183 clearTimeout(this._timeout);
184 this._timeout = null;
185 }
186 if(!this.activeDraggable) return;
187 this._lastPointer = null;
188 this.activeDraggable.endDrag(event);
189 this.activeDraggable = null;
190 },
191
192 keyPress: function(event) {
193 if(this.activeDraggable)
194 this.activeDraggable.keyPress(event);
195 },
196
197 addObserver: function(observer) {
198 this.observers.push(observer);
199 this._cacheObserverCallbacks();
200 },
201
202 removeObserver: function(element) { // element instead of observer fixes mem leaks
203 this.observers = this.observers.reject( function(o) { return o.element==element });
204 this._cacheObserverCallbacks();
205 },
206
207 notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
208 if(this[eventName+'Count'] > 0)
209 this.observers.each( function(o) {
210 if(o[eventName]) o[eventName](eventName, draggable, event);
211 });
212 if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
213 },
214
215 _cacheObserverCallbacks: function() {
216 ['onStart','onEnd','onDrag'].each( function(eventName) {
217 Draggables[eventName+'Count'] = Draggables.observers.select(
218 function(o) { return o[eventName]; }
219 ).length;
220 });
221 }
222}
223
224/*--------------------------------------------------------------------------*/
225
226var Draggable = Class.create();
227Draggable._dragging = {};
228
229Draggable.prototype = {
230 initialize: function(element) {
231 var defaults = {
232 handle: false,
233 reverteffect: function(element, top_offset, left_offset) {
234 var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
235 new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
236 queue: {scope:'_draggable', position:'end'}
237 });
238 },
239 endeffect: function(element) {
240 var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
241 new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
242 queue: {scope:'_draggable', position:'end'},
243 afterFinish: function(){
244 Draggable._dragging[element] = false
245 }
246 });
247 },
248 zindex: 1000,
249 revert: false,
250 quiet: false,
251 scroll: false,
252 scrollSensitivity: 20,
253 scrollSpeed: 15,
254 snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
255 delay: 0
256 };
257
258 if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
259 Object.extend(defaults, {
260 starteffect: function(element) {
261 element._opacity = Element.getOpacity(element);
262 Draggable._dragging[element] = true;
263 new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
264 }
265 });
266
267 var options = Object.extend(defaults, arguments[1] || {});
268
269 this.element = $(element);
270
271 if(options.handle && (typeof options.handle == 'string'))
272 this.handle = this.element.down('.'+options.handle, 0);
273
274 if(!this.handle) this.handle = $(options.handle);
275 if(!this.handle) this.handle = this.element;
276
277 if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
278 options.scroll = $(options.scroll);
279 this._isScrollChild = Element.childOf(this.element, options.scroll);
280 }
281
282 Element.makePositioned(this.element); // fix IE
283
284 this.delta = this.currentDelta();
285 this.options = options;
286 this.dragging = false;
287
288 this.eventMouseDown = this.initDrag.bindAsEventListener(this);
289 Event.observe(this.handle, "mousedown", this.eventMouseDown);
290
291 Draggables.register(this);
292 },
293
294 destroy: function() {
295 Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
296 Draggables.unregister(this);
297 },
298
299 currentDelta: function() {
300 return([
301 parseInt(Element.getStyle(this.element,'left') || '0'),
302 parseInt(Element.getStyle(this.element,'top') || '0')]);
303 },
304
305 initDrag: function(event) {
306 if(typeof Draggable._dragging[this.element] != 'undefined' &&
307 Draggable._dragging[this.element]) return;
308 if(Event.isLeftClick(event)) {
309 // abort on form elements, fixes a Firefox issue
310 var src = Event.element(event);
311 if((tag_name = src.tagName.toUpperCase()) && (
312 tag_name=='INPUT' ||
313 tag_name=='SELECT' ||
314 tag_name=='OPTION' ||
315 tag_name=='BUTTON' ||
316 tag_name=='TEXTAREA')) return;
317
318 var pointer = [Event.pointerX(event), Event.pointerY(event)];
319 var pos = Position.cumulativeOffset(this.element);
320 this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
321
322 Draggables.activate(this);
323 Event.stop(event);
324 }
325 },
326
327 startDrag: function(event) {
328 this.dragging = true;
329
330 if(this.options.zindex) {
331 this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
332 this.element.style.zIndex = this.options.zindex;
333 }
334
335 if(this.options.ghosting) {
336 this._clone = this.element.cloneNode(true);
337 Position.absolutize(this.element);
338 this.element.parentNode.insertBefore(this._clone, this.element);
339 }
340
341 if(this.options.scroll) {
342 if (this.options.scroll == window) {
343 var where = this._getWindowScroll(this.options.scroll);
344 this.originalScrollLeft = where.left;
345 this.originalScrollTop = where.top;
346 } else {
347 this.originalScrollLeft = this.options.scroll.scrollLeft;
348 this.originalScrollTop = this.options.scroll.scrollTop;
349 }
350 }
351
352 Draggables.notify('onStart', this, event);
353
354 if(this.options.starteffect) this.options.starteffect(this.element);
355 },
356
357 updateDrag: function(event, pointer) {
358 if(!this.dragging) this.startDrag(event);
359
360 if(!this.options.quiet){
361 Position.prepare();
362 Droppables.show(pointer, this.element);
363 }
364
365 Draggables.notify('onDrag', this, event);
366
367 this.draw(pointer);
368 if(this.options.change) this.options.change(this);
369
370 if(this.options.scroll) {
371 this.stopScrolling();
372
373 var p;
374 if (this.options.scroll == window) {
375 with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
376 } else {
377 p = Position.page(this.options.scroll);
378 p[0] += this.options.scroll.scrollLeft + Position.deltaX;
379 p[1] += this.options.scroll.scrollTop + Position.deltaY;
380 p.push(p[0]+this.options.scroll.offsetWidth);
381 p.push(p[1]+this.options.scroll.offsetHeight);
382 }
383 var speed = [0,0];
384 if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
385 if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
386 if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
387 if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
388 this.startScrolling(speed);
389 }
390
391 // fix AppleWebKit rendering
392 if(Prototype.Browser.WebKit) window.scrollBy(0,0);
393
394 Event.stop(event);
395 },
396
397 finishDrag: function(event, success) {
398 this.dragging = false;
399
400 if(this.options.quiet){
401 Position.prepare();
402 var pointer = [Event.pointerX(event), Event.pointerY(event)];
403 Droppables.show(pointer, this.element);
404 }
405
406 if(this.options.ghosting) {
407 Position.relativize(this.element);
408 Element.remove(this._clone);
409 this._clone = null;
410 }
411
412 var dropped = false;
413 if(success) {
414 dropped = Droppables.fire(event, this.element);
415 if (!dropped) dropped = false;
416 }
417 if(dropped && this.options.onDropped) this.options.onDropped(this.element);
418 Draggables.notify('onEnd', this, event);
419
420 var revert = this.options.revert;
421 if(revert && typeof revert == 'function') revert = revert(this.element);
422
423 var d = this.currentDelta();
424 if(revert && this.options.reverteffect) {
425 if (dropped == 0 || revert != 'failure')
426 this.options.reverteffect(this.element,
427 d[1]-this.delta[1], d[0]-this.delta[0]);
428 } else {
429 this.delta = d;
430 }
431
432 if(this.options.zindex)
433 this.element.style.zIndex = this.originalZ;
434
435 if(this.options.endeffect)
436 this.options.endeffect(this.element);
437
438 Draggables.deactivate(this);
439 Droppables.reset();
440 },
441
442 keyPress: function(event) {
443 if(event.keyCode!=Event.KEY_ESC) return;
444 this.finishDrag(event, false);
445 Event.stop(event);
446 },
447
448 endDrag: function(event) {
449 if(!this.dragging) return;
450 this.stopScrolling();
451 this.finishDrag(event, true);
452 Event.stop(event);
453 },
454
455 draw: function(point) {
456 var pos = Position.cumulativeOffset(this.element);
457 if(this.options.ghosting) {
458 var r = Position.realOffset(this.element);
459 pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
460 }
461
462 var d = this.currentDelta();
463 pos[0] -= d[0]; pos[1] -= d[1];
464
465 if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
466 pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
467 pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
468 }
469
470 var p = [0,1].map(function(i){
471 return (point[i]-pos[i]-this.offset[i])
472 }.bind(this));
473
474 if(this.options.snap) {
475 if(typeof this.options.snap == 'function') {
476 p = this.options.snap(p[0],p[1],this);
477 } else {
478 if(this.options.snap instanceof Array) {
479 p = p.map( function(v, i) {
480 return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
481 } else {
482 p = p.map( function(v) {
483 return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
484 }
485 }}
486
487 var style = this.element.style;
488 if((!this.options.constraint) || (this.options.constraint=='horizontal'))
489 style.left = p[0] + "px";
490 if((!this.options.constraint) || (this.options.constraint=='vertical'))
491 style.top = p[1] + "px";
492
493 if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
494 },
495
496 stopScrolling: function() {
497 if(this.scrollInterval) {
498 clearInterval(this.scrollInterval);
499 this.scrollInterval = null;
500 Draggables._lastScrollPointer = null;
501 }
502 },
503
504 startScrolling: function(speed) {
505 if(!(speed[0] || speed[1])) return;
506 this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
507 this.lastScrolled = new Date();
508 this.scrollInterval = setInterval(this.scroll.bind(this), 10);
509 },
510
511 scroll: function() {
512 var current = new Date();
513 var delta = current - this.lastScrolled;
514 this.lastScrolled = current;
515 if(this.options.scroll == window) {
516 with (this._getWindowScroll(this.options.scroll)) {
517 if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
518 var d = delta / 1000;
519 this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
520 }
521 }
522 } else {
523 this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
524 this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
525 }
526
527 Position.prepare();
528 Droppables.show(Draggables._lastPointer, this.element);
529 Draggables.notify('onDrag', this);
530 if (this._isScrollChild) {
531 Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
532 Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
533 Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
534 if (Draggables._lastScrollPointer[0] < 0)
535 Draggables._lastScrollPointer[0] = 0;
536 if (Draggables._lastScrollPointer[1] < 0)
537 Draggables._lastScrollPointer[1] = 0;
538 this.draw(Draggables._lastScrollPointer);
539 }
540
541 if(this.options.change) this.options.change(this);
542 },
543
544 _getWindowScroll: function(w) {
545 var T, L, W, H;
546 with (w.document) {
547 if (w.document.documentElement && documentElement.scrollTop) {
548 T = documentElement.scrollTop;
549 L = documentElement.scrollLeft;
550 } else if (w.document.body) {
551 T = body.scrollTop;
552 L = body.scrollLeft;
553 }
554 if (w.innerWidth) {
555 W = w.innerWidth;
556 H = w.innerHeight;
557 } else if (w.document.documentElement && documentElement.clientWidth) {
558 W = documentElement.clientWidth;
559 H = documentElement.clientHeight;
560 } else {
561 W = body.offsetWidth;
562 H = body.offsetHeight
563 }
564 }
565 return { top: T, left: L, width: W, height: H };
566 }
567}
568
569/*--------------------------------------------------------------------------*/
570
571var SortableObserver = Class.create();
572SortableObserver.prototype = {
573 initialize: function(element, observer) {
574 this.element = $(element);
575 this.observer = observer;
576 this.lastValue = Sortable.serialize(this.element);
577 },
578
579 onStart: function() {
580 this.lastValue = Sortable.serialize(this.element);
581 },
582
583 onEnd: function() {
584 Sortable.unmark();
585 if(this.lastValue != Sortable.serialize(this.element))
586 this.observer(this.element)
587 }
588}
589
590var Sortable = {
591 SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
592
593 sortables: {},
594
595 _findRootElement: function(element) {
596 while (element.tagName.toUpperCase() != "BODY") {
597 if(element.id && Sortable.sortables[element.id]) return element;
598 element = element.parentNode;
599 }
600 },
601
602 options: function(element) {
603 element = Sortable._findRootElement($(element));
604 if(!element) return;
605 return Sortable.sortables[element.id];
606 },
607
608 destroy: function(element){
609 var s = Sortable.options(element);
610
611 if(s) {
612 Draggables.removeObserver(s.element);
613 s.droppables.each(function(d){ Droppables.remove(d) });
614 s.draggables.invoke('destroy');
615
616 delete Sortable.sortables[s.element.id];
617 }
618 },
619
620 create: function(element) {
621 element = $(element);
622 var options = Object.extend({
623 element: element,
624 tag: 'li', // assumes li children, override with tag: 'tagname'
625 dropOnEmpty: false,
626 tree: false,
627 treeTag: 'ul',
628 overlap: 'vertical', // one of 'vertical', 'horizontal'
629 constraint: 'vertical', // one of 'vertical', 'horizontal', false
630 containment: element, // also takes array of elements (or id's); or false
631 handle: false, // or a CSS class
632 only: false,
633 delay: 0,
634 hoverclass: null,
635 ghosting: false,
636 quiet: false,
637 scroll: false,
638 scrollSensitivity: 20,
639 scrollSpeed: 15,
640 format: this.SERIALIZE_RULE,
641
642 // these take arrays of elements or ids and can be
643 // used for better initialization performance
644 elements: false,
645 handles: false,
646
647 onChange: Prototype.emptyFunction,
648 onUpdate: Prototype.emptyFunction
649 }, arguments[1] || {});
650
651 // clear any old sortable with same element
652 this.destroy(element);
653
654 // build options for the draggables
655 var options_for_draggable = {
656 revert: true,
657 quiet: options.quiet,
658 scroll: options.scroll,
659 scrollSpeed: options.scrollSpeed,
660 scrollSensitivity: options.scrollSensitivity,
661 delay: options.delay,
662 ghosting: options.ghosting,
663 constraint: options.constraint,
664 handle: options.handle };
665
666 if(options.starteffect)
667 options_for_draggable.starteffect = options.starteffect;
668
669 if(options.reverteffect)
670 options_for_draggable.reverteffect = options.reverteffect;
671 else
672 if(options.ghosting) options_for_draggable.reverteffect = function(element) {
673 element.style.top = 0;
674 element.style.left = 0;
675 };
676
677 if(options.endeffect)
678 options_for_draggable.endeffect = options.endeffect;
679
680 if(options.zindex)
681 options_for_draggable.zindex = options.zindex;
682
683 // build options for the droppables
684 var options_for_droppable = {
685 overlap: options.overlap,
686 containment: options.containment,
687 tree: options.tree,
688 hoverclass: options.hoverclass,
689 onHover: Sortable.onHover
690 }
691
692 var options_for_tree = {
693 onHover: Sortable.onEmptyHover,
694 overlap: options.overlap,
695 containment: options.containment,
696 hoverclass: options.hoverclass
697 }
698
699 // fix for gecko engine
700 Element.cleanWhitespace(element);
701
702 options.draggables = [];
703 options.droppables = [];
704
705 // drop on empty handling
706 if(options.dropOnEmpty || options.tree) {
707 Droppables.add(element, options_for_tree);
708 options.droppables.push(element);
709 }
710
711 (options.elements || this.findElements(element, options) || []).each( function(e,i) {
712 var handle = options.handles ? $(options.handles[i]) :
713 (options.handle ? $(e).getElementsByClassName(options.handle)[0] : e);
714 options.draggables.push(
715 new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
716 Droppables.add(e, options_for_droppable);
717 if(options.tree) e.treeNode = element;
718 options.droppables.push(e);
719 });
720
721 if(options.tree) {
722 (Sortable.findTreeElements(element, options) || []).each( function(e) {
723 Droppables.add(e, options_for_tree);
724 e.treeNode = element;
725 options.droppables.push(e);
726 });
727 }
728
729 // keep reference
730 this.sortables[element.id] = options;
731
732 // for onupdate
733 Draggables.addObserver(new SortableObserver(element, options.onUpdate));
734
735 },
736
737 // return all suitable-for-sortable elements in a guaranteed order
738 findElements: function(element, options) {
739 return Element.findChildren(
740 element, options.only, options.tree ? true : false, options.tag);
741 },
742
743 findTreeElements: function(element, options) {
744 return Element.findChildren(
745 element, options.only, options.tree ? true : false, options.treeTag);
746 },
747
748 onHover: function(element, dropon, overlap) {
749 if(Element.isParent(dropon, element)) return;
750
751 if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
752 return;
753 } else if(overlap>0.5) {
754 Sortable.mark(dropon, 'before');
755 if(dropon.previousSibling != element) {
756 var oldParentNode = element.parentNode;
757 element.style.visibility = "hidden"; // fix gecko rendering
758 dropon.parentNode.insertBefore(element, dropon);
759 if(dropon.parentNode!=oldParentNode)
760 Sortable.options(oldParentNode).onChange(element);
761 Sortable.options(dropon.parentNode).onChange(element);
762 }
763 } else {
764 Sortable.mark(dropon, 'after');
765 var nextElement = dropon.nextSibling || null;
766 if(nextElement != element) {
767 var oldParentNode = element.parentNode;
768 element.style.visibility = "hidden"; // fix gecko rendering
769 dropon.parentNode.insertBefore(element, nextElement);
770 if(dropon.parentNode!=oldParentNode)
771 Sortable.options(oldParentNode).onChange(element);
772 Sortable.options(dropon.parentNode).onChange(element);
773 }
774 }
775 },
776
777 onEmptyHover: function(element, dropon, overlap) {
778 var oldParentNode = element.parentNode;
779 var droponOptions = Sortable.options(dropon);
780
781 if(!Element.isParent(dropon, element)) {
782 var index;
783
784 var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
785 var child = null;
786
787 if(children) {
788 var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
789
790 for (index = 0; index < children.length; index += 1) {
791 if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
792 offset -= Element.offsetSize (children[index], droponOptions.overlap);
793 } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
794 child = index + 1 < children.length ? children[index + 1] : null;
795 break;
796 } else {
797 child = children[index];
798 break;
799 }
800 }
801 }
802
803 dropon.insertBefore(element, child);
804
805 Sortable.options(oldParentNode).onChange(element);
806 droponOptions.onChange(element);
807 }
808 },
809
810 unmark: function() {
811 if(Sortable._marker) Sortable._marker.hide();
812 },
813
814 mark: function(dropon, position) {
815 // mark on ghosting only
816 var sortable = Sortable.options(dropon.parentNode);
817 if(sortable && !sortable.ghosting) return;
818
819 if(!Sortable._marker) {
820 Sortable._marker =
821 ($('dropmarker') || Element.extend(document.createElement('DIV'))).
822 hide().addClassName('dropmarker').setStyle({position:'absolute'});
823 document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
824 }
825 var offsets = Position.cumulativeOffset(dropon);
826 Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
827
828 if(position=='after')
829 if(sortable.overlap == 'horizontal')
830 Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
831 else
832 Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
833
834 Sortable._marker.show();
835 },
836
837 _tree: function(element, options, parent) {
838 var children = Sortable.findElements(element, options) || [];
839
840 for (var i = 0; i < children.length; ++i) {
841 var match = children[i].id.match(options.format);
842
843 if (!match) continue;
844
845 var child = {
846 id: encodeURIComponent(match ? match[1] : null),
847 element: element,
848 parent: parent,
849 children: [],
850 position: parent.children.length,
851 container: $(children[i]).down(options.treeTag)
852 }
853
854 /* Get the element containing the children and recurse over it */
855 if (child.container)
856 this._tree(child.container, options, child)
857
858 parent.children.push (child);
859 }
860
861 return parent;
862 },
863
864 tree: function(element) {
865 element = $(element);
866 var sortableOptions = this.options(element);
867 var options = Object.extend({
868 tag: sortableOptions.tag,
869 treeTag: sortableOptions.treeTag,
870 only: sortableOptions.only,
871 name: element.id,
872 format: sortableOptions.format
873 }, arguments[1] || {});
874
875 var root = {
876 id: null,
877 parent: null,
878 children: [],
879 container: element,
880 position: 0
881 }
882
883 return Sortable._tree(element, options, root);
884 },
885
886 /* Construct a [i] index for a particular node */
887 _constructIndex: function(node) {
888 var index = '';
889 do {
890 if (node.id) index = '[' + node.position + ']' + index;
891 } while ((node = node.parent) != null);
892 return index;
893 },
894
895 sequence: function(element) {
896 element = $(element);
897 var options = Object.extend(this.options(element), arguments[1] || {});
898
899 return $(this.findElements(element, options) || []).map( function(item) {
900 return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
901 });
902 },
903
904 setSequence: function(element, new_sequence) {
905 element = $(element);
906 var options = Object.extend(this.options(element), arguments[2] || {});
907
908 var nodeMap = {};
909 this.findElements(element, options).each( function(n) {
910 if (n.id.match(options.format))
911 nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
912 n.parentNode.removeChild(n);
913 });
914
915 new_sequence.each(function(ident) {
916 var n = nodeMap[ident];
917 if (n) {
918 n[1].appendChild(n[0]);
919 delete nodeMap[ident];
920 }
921 });
922 },
923
924 serialize: function(element) {
925 element = $(element);
926 var options = Object.extend(Sortable.options(element), arguments[1] || {});
927 var name = encodeURIComponent(
928 (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
929
930 if (options.tree) {
931 return Sortable.tree(element, arguments[1]).children.map( function (item) {
932 return [name + Sortable._constructIndex(item) + "[id]=" +
933 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
934 }).flatten().join('&');
935 } else {
936 return Sortable.sequence(element, arguments[1]).map( function(item) {
937 return name + "[]=" + encodeURIComponent(item);
938 }).join('&');
939 }
940 }
941}
942
943// Returns true if child is contained within element
944Element.isParent = function(child, element) {
945 if (!child.parentNode || child == element) return false;
946 if (child.parentNode == element) return true;
947 return Element.isParent(child.parentNode, element);
948}
949
950Element.findChildren = function(element, only, recursive, tagName) {
951 if(!element.hasChildNodes()) return null;
952 tagName = tagName.toUpperCase();
953 if(only) only = [only].flatten();
954 var elements = [];
955 $A(element.childNodes).each( function(e) {
956 if(e.tagName && e.tagName.toUpperCase()==tagName &&
957 (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
958 elements.push(e);
959 if(recursive) {
960 var grandchildren = Element.findChildren(e, only, recursive, tagName);
961 if(grandchildren) elements.push(grandchildren);
962 }
963 });
964
965 return (elements.length>0 ? elements.flatten() : []);
966}
967
968Element.offsetSize = function (element, type) {
969 return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
970}
diff --git a/docroot/lib/scriptaculous/effects.js b/docroot/lib/scriptaculous/effects.js
new file mode 100755
index 0000000..70d0752
--- /dev/null
+++ b/docroot/lib/scriptaculous/effects.js
@@ -0,0 +1,1094 @@
1// script.aculo.us effects.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4// Contributors:
5// Justin Palmer (http://encytemedia.com/)
6// Mark Pilgrim (http://diveintomark.org/)
7// Martin Bialasinki
8//
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/
11
12// converts rgb() and #xxx to #xxxxxx format,
13// returns self (or first argument) if not convertable
14String.prototype.parseColor = function() {
15 var color = '#';
16 if(this.slice(0,4) == 'rgb(') {
17 var cols = this.slice(4,this.length-1).split(',');
18 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
19 } else {
20 if(this.slice(0,1) == '#') {
21 if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
22 if(this.length==7) color = this.toLowerCase();
23 }
24 }
25 return(color.length==7 ? color : (arguments[0] || this));
26}
27
28/*--------------------------------------------------------------------------*/
29
30Element.collectTextNodes = function(element) {
31 return $A($(element).childNodes).collect( function(node) {
32 return (node.nodeType==3 ? node.nodeValue :
33 (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
34 }).flatten().join('');
35}
36
37Element.collectTextNodesIgnoreClass = function(element, className) {
38 return $A($(element).childNodes).collect( function(node) {
39 return (node.nodeType==3 ? node.nodeValue :
40 ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
41 Element.collectTextNodesIgnoreClass(node, className) : ''));
42 }).flatten().join('');
43}
44
45Element.setContentZoom = function(element, percent) {
46 element = $(element);
47 element.setStyle({fontSize: (percent/100) + 'em'});
48 if(Prototype.Browser.WebKit) window.scrollBy(0,0);
49 return element;
50}
51
52Element.getInlineOpacity = function(element){
53 return $(element).style.opacity || '';
54}
55
56Element.forceRerendering = function(element) {
57 try {
58 element = $(element);
59 var n = document.createTextNode(' ');
60 element.appendChild(n);
61 element.removeChild(n);
62 } catch(e) { }
63};
64
65/*--------------------------------------------------------------------------*/
66
67Array.prototype.call = function() {
68 var args = arguments;
69 this.each(function(f){ f.apply(this, args) });
70}
71
72/*--------------------------------------------------------------------------*/
73
74var Effect = {
75 _elementDoesNotExistError: {
76 name: 'ElementDoesNotExistError',
77 message: 'The specified DOM element does not exist, but is required for this effect to operate'
78 },
79 tagifyText: function(element) {
80 if(typeof Builder == 'undefined')
81 throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
82
83 var tagifyStyle = 'position:relative';
84 if(Prototype.Browser.IE) tagifyStyle += ';zoom:1';
85
86 element = $(element);
87 $A(element.childNodes).each( function(child) {
88 if(child.nodeType==3) {
89 child.nodeValue.toArray().each( function(character) {
90 element.insertBefore(
91 Builder.node('span',{style: tagifyStyle},
92 character == ' ' ? String.fromCharCode(160) : character),
93 child);
94 });
95 Element.remove(child);
96 }
97 });
98 },
99 multiple: function(element, effect) {
100 var elements;
101 if(((typeof element == 'object') ||
102 (typeof element == 'function')) &&
103 (element.length))
104 elements = element;
105 else
106 elements = $(element).childNodes;
107
108 var options = Object.extend({
109 speed: 0.1,
110 delay: 0.0
111 }, arguments[2] || {});
112 var masterDelay = options.delay;
113
114 $A(elements).each( function(element, index) {
115 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
116 });
117 },
118 PAIRS: {
119 'slide': ['SlideDown','SlideUp'],
120 'blind': ['BlindDown','BlindUp'],
121 'appear': ['Appear','Fade']
122 },
123 toggle: function(element, effect) {
124 element = $(element);
125 effect = (effect || 'appear').toLowerCase();
126 var options = Object.extend({
127 queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
128 }, arguments[2] || {});
129 Effect[element.visible() ?
130 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
131 }
132};
133
134var Effect2 = Effect; // deprecated
135
136/* ------------- transitions ------------- */
137
138Effect.Transitions = {
139 linear: Prototype.K,
140 sinoidal: function(pos) {
141 return (-Math.cos(pos*Math.PI)/2) + 0.5;
142 },
143 reverse: function(pos) {
144 return 1-pos;
145 },
146 flicker: function(pos) {
147 var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
148 return (pos > 1 ? 1 : pos);
149 },
150 wobble: function(pos) {
151 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
152 },
153 pulse: function(pos, pulses) {
154 pulses = pulses || 5;
155 return (
156 Math.round((pos % (1/pulses)) * pulses) == 0 ?
157 ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) :
158 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
159 );
160 },
161 none: function(pos) {
162 return 0;
163 },
164 full: function(pos) {
165 return 1;
166 }
167};
168
169/* ------------- core effects ------------- */
170
171Effect.ScopedQueue = Class.create();
172Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
173 initialize: function() {
174 this.effects = [];
175 this.interval = null;
176 },
177 _each: function(iterator) {
178 this.effects._each(iterator);
179 },
180 add: function(effect) {
181 var timestamp = new Date().getTime();
182
183 var position = (typeof effect.options.queue == 'string') ?
184 effect.options.queue : effect.options.queue.position;
185
186 switch(position) {
187 case 'front':
188 // move unstarted effects after this effect
189 this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
190 e.startOn += effect.finishOn;
191 e.finishOn += effect.finishOn;
192 });
193 break;
194 case 'with-last':
195 timestamp = this.effects.pluck('startOn').max() || timestamp;
196 break;
197 case 'end':
198 // start effect after last queued effect has finished
199 timestamp = this.effects.pluck('finishOn').max() || timestamp;
200 break;
201 }
202
203 effect.startOn += timestamp;
204 effect.finishOn += timestamp;
205
206 if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
207 this.effects.push(effect);
208
209 if(!this.interval)
210 this.interval = setInterval(this.loop.bind(this), 15);
211 },
212 remove: function(effect) {
213 this.effects = this.effects.reject(function(e) { return e==effect });
214 if(this.effects.length == 0) {
215 clearInterval(this.interval);
216 this.interval = null;
217 }
218 },
219 loop: function() {
220 var timePos = new Date().getTime();
221 for(var i=0, len=this.effects.length;i<len;i++)
222 this.effects[i] && this.effects[i].loop(timePos);
223 }
224});
225
226Effect.Queues = {
227 instances: $H(),
228 get: function(queueName) {
229 if(typeof queueName != 'string') return queueName;
230
231 if(!this.instances[queueName])
232 this.instances[queueName] = new Effect.ScopedQueue();
233
234 return this.instances[queueName];
235 }
236}
237Effect.Queue = Effect.Queues.get('global');
238
239Effect.DefaultOptions = {
240 transition: Effect.Transitions.sinoidal,
241 duration: 1.0, // seconds
242 fps: 100, // 100= assume 66fps max.
243 sync: false, // true for combining
244 from: 0.0,
245 to: 1.0,
246 delay: 0.0,
247 queue: 'parallel'
248}
249
250Effect.Base = function() {};
251Effect.Base.prototype = {
252 position: null,
253 start: function(options) {
254 function codeForEvent(options,eventName){
255 return (
256 (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
257 (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
258 );
259 }
260 if(options.transition === false) options.transition = Effect.Transitions.linear;
261 this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
262 this.currentFrame = 0;
263 this.state = 'idle';
264 this.startOn = this.options.delay*1000;
265 this.finishOn = this.startOn+(this.options.duration*1000);
266 this.fromToDelta = this.options.to-this.options.from;
267 this.totalTime = this.finishOn-this.startOn;
268 this.totalFrames = this.options.fps*this.options.duration;
269
270 eval('this.render = function(pos){ '+
271 'if(this.state=="idle"){this.state="running";'+
272 codeForEvent(options,'beforeSetup')+
273 (this.setup ? 'this.setup();':'')+
274 codeForEvent(options,'afterSetup')+
275 '};if(this.state=="running"){'+
276 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
277 'this.position=pos;'+
278 codeForEvent(options,'beforeUpdate')+
279 (this.update ? 'this.update(pos);':'')+
280 codeForEvent(options,'afterUpdate')+
281 '}}');
282
283 this.event('beforeStart');
284 if(!this.options.sync)
285 Effect.Queues.get(typeof this.options.queue == 'string' ?
286 'global' : this.options.queue.scope).add(this);
287 },
288 loop: function(timePos) {
289 if(timePos >= this.startOn) {
290 if(timePos >= this.finishOn) {
291 this.render(1.0);
292 this.cancel();
293 this.event('beforeFinish');
294 if(this.finish) this.finish();
295 this.event('afterFinish');
296 return;
297 }
298 var pos = (timePos - this.startOn) / this.totalTime,
299 frame = Math.round(pos * this.totalFrames);
300 if(frame > this.currentFrame) {
301 this.render(pos);
302 this.currentFrame = frame;
303 }
304 }
305 },
306 cancel: function() {
307 if(!this.options.sync)
308 Effect.Queues.get(typeof this.options.queue == 'string' ?
309 'global' : this.options.queue.scope).remove(this);
310 this.state = 'finished';
311 },
312 event: function(eventName) {
313 if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
314 if(this.options[eventName]) this.options[eventName](this);
315 },
316 inspect: function() {
317 var data = $H();
318 for(property in this)
319 if(typeof this[property] != 'function') data[property] = this[property];
320 return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
321 }
322}
323
324Effect.Parallel = Class.create();
325Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
326 initialize: function(effects) {
327 this.effects = effects || [];
328 this.start(arguments[1]);
329 },
330 update: function(position) {
331 this.effects.invoke('render', position);
332 },
333 finish: function(position) {
334 this.effects.each( function(effect) {
335 effect.render(1.0);
336 effect.cancel();
337 effect.event('beforeFinish');
338 if(effect.finish) effect.finish(position);
339 effect.event('afterFinish');
340 });
341 }
342});
343
344Effect.Event = Class.create();
345Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
346 initialize: function() {
347 var options = Object.extend({
348 duration: 0
349 }, arguments[0] || {});
350 this.start(options);
351 },
352 update: Prototype.emptyFunction
353});
354
355Effect.Opacity = Class.create();
356Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
357 initialize: function(element) {
358 this.element = $(element);
359 if(!this.element) throw(Effect._elementDoesNotExistError);
360 // make this work on IE on elements without 'layout'
361 if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
362 this.element.setStyle({zoom: 1});
363 var options = Object.extend({
364 from: this.element.getOpacity() || 0.0,
365 to: 1.0
366 }, arguments[1] || {});
367 this.start(options);
368 },
369 update: function(position) {
370 this.element.setOpacity(position);
371 }
372});
373
374Effect.Move = Class.create();
375Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
376 initialize: function(element) {
377 this.element = $(element);
378 if(!this.element) throw(Effect._elementDoesNotExistError);
379 var options = Object.extend({
380 x: 0,
381 y: 0,
382 mode: 'relative'
383 }, arguments[1] || {});
384 this.start(options);
385 },
386 setup: function() {
387 // Bug in Opera: Opera returns the "real" position of a static element or
388 // relative element that does not have top/left explicitly set.
389 // ==> Always set top and left for position relative elements in your stylesheets
390 // (to 0 if you do not need them)
391 this.element.makePositioned();
392 this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
393 this.originalTop = parseFloat(this.element.getStyle('top') || '0');
394 if(this.options.mode == 'absolute') {
395 // absolute movement, so we need to calc deltaX and deltaY
396 this.options.x = this.options.x - this.originalLeft;
397 this.options.y = this.options.y - this.originalTop;
398 }
399 },
400 update: function(position) {
401 this.element.setStyle({
402 left: Math.round(this.options.x * position + this.originalLeft) + 'px',
403 top: Math.round(this.options.y * position + this.originalTop) + 'px'
404 });
405 }
406});
407
408// for backwards compatibility
409Effect.MoveBy = function(element, toTop, toLeft) {
410 return new Effect.Move(element,
411 Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
412};
413
414Effect.Scale = Class.create();
415Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
416 initialize: function(element, percent) {
417 this.element = $(element);
418 if(!this.element) throw(Effect._elementDoesNotExistError);
419 var options = Object.extend({
420 scaleX: true,
421 scaleY: true,
422 scaleContent: true,
423 scaleFromCenter: false,
424 scaleMode: 'box', // 'box' or 'contents' or {} with provided values
425 scaleFrom: 100.0,
426 scaleTo: percent
427 }, arguments[2] || {});
428 this.start(options);
429 },
430 setup: function() {
431 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
432 this.elementPositioning = this.element.getStyle('position');
433
434 this.originalStyle = {};
435 ['top','left','width','height','fontSize'].each( function(k) {
436 this.originalStyle[k] = this.element.style[k];
437 }.bind(this));
438
439 this.originalTop = this.element.offsetTop;
440 this.originalLeft = this.element.offsetLeft;
441
442 var fontSize = this.element.getStyle('font-size') || '100%';
443 ['em','px','%','pt'].each( function(fontSizeType) {
444 if(fontSize.indexOf(fontSizeType)>0) {
445 this.fontSize = parseFloat(fontSize);
446 this.fontSizeType = fontSizeType;
447 }
448 }.bind(this));
449
450 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
451
452 this.dims = null;
453 if(this.options.scaleMode=='box')
454 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
455 if(/^content/.test(this.options.scaleMode))
456 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
457 if(!this.dims)
458 this.dims = [this.options.scaleMode.originalHeight,
459 this.options.scaleMode.originalWidth];
460 },
461 update: function(position) {
462 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
463 if(this.options.scaleContent && this.fontSize)
464 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
465 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
466 },
467 finish: function(position) {
468 if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
469 },
470 setDimensions: function(height, width) {
471 var d = {};
472 if(this.options.scaleX) d.width = Math.round(width) + 'px';
473 if(this.options.scaleY) d.height = Math.round(height) + 'px';
474 if(this.options.scaleFromCenter) {
475 var topd = (height - this.dims[0])/2;
476 var leftd = (width - this.dims[1])/2;
477 if(this.elementPositioning == 'absolute') {
478 if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
479 if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
480 } else {
481 if(this.options.scaleY) d.top = -topd + 'px';
482 if(this.options.scaleX) d.left = -leftd + 'px';
483 }
484 }
485 this.element.setStyle(d);
486 }
487});
488
489Effect.Highlight = Class.create();
490Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
491 initialize: function(element) {
492 this.element = $(element);
493 if(!this.element) throw(Effect._elementDoesNotExistError);
494 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
495 this.start(options);
496 },
497 setup: function() {
498 // Prevent executing on elements not in the layout flow
499 if(this.element.getStyle('display')=='none') { this.cancel(); return; }
500 // Disable background image during the effect
501 this.oldStyle = {};
502 if (!this.options.keepBackgroundImage) {
503 this.oldStyle.backgroundImage = this.element.getStyle('background-image');
504 this.element.setStyle({backgroundImage: 'none'});
505 }
506 if(!this.options.endcolor)
507 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
508 if(!this.options.restorecolor)
509 this.options.restorecolor = this.element.getStyle('background-color');
510 // init color calculations
511 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
512 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));
513 },
514 update: function(position) {
515 this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
516 return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
517 },
518 finish: function() {
519 this.element.setStyle(Object.extend(this.oldStyle, {
520 backgroundColor: this.options.restorecolor
521 }));
522 }
523});
524
525Effect.ScrollTo = Class.create();
526Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
527 initialize: function(element) {
528 this.element = $(element);
529 this.start(arguments[1] || {});
530 },
531 setup: function() {
532 Position.prepare();
533 var offsets = Position.cumulativeOffset(this.element);
534 if(this.options.offset) offsets[1] += this.options.offset;
535 var max = window.innerHeight ?
536 window.height - window.innerHeight :
537 document.body.scrollHeight -
538 (document.documentElement.clientHeight ?
539 document.documentElement.clientHeight : document.body.clientHeight);
540 this.scrollStart = Position.deltaY;
541 this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
542 },
543 update: function(position) {
544 Position.prepare();
545 window.scrollTo(Position.deltaX,
546 this.scrollStart + (position*this.delta));
547 }
548});
549
550/* ------------- combination effects ------------- */
551
552Effect.Fade = function(element) {
553 element = $(element);
554 var oldOpacity = element.getInlineOpacity();
555 var options = Object.extend({
556 from: element.getOpacity() || 1.0,
557 to: 0.0,
558 afterFinishInternal: function(effect) {
559 if(effect.options.to!=0) return;
560 effect.element.hide().setStyle({opacity: oldOpacity});
561 }}, arguments[1] || {});
562 return new Effect.Opacity(element,options);
563}
564
565Effect.Appear = function(element) {
566 element = $(element);
567 var options = Object.extend({
568 from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
569 to: 1.0,
570 // force Safari to render floated elements properly
571 afterFinishInternal: function(effect) {
572 effect.element.forceRerendering();
573 },
574 beforeSetup: function(effect) {
575 effect.element.setOpacity(effect.options.from).show();
576 }}, arguments[1] || {});
577 return new Effect.Opacity(element,options);
578}
579
580Effect.Puff = function(element) {
581 element = $(element);
582 var oldStyle = {
583 opacity: element.getInlineOpacity(),
584 position: element.getStyle('position'),
585 top: element.style.top,
586 left: element.style.left,
587 width: element.style.width,
588 height: element.style.height
589 };
590 return new Effect.Parallel(
591 [ new Effect.Scale(element, 200,
592 { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
593 new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
594 Object.extend({ duration: 1.0,
595 beforeSetupInternal: function(effect) {
596 Position.absolutize(effect.effects[0].element)
597 },
598 afterFinishInternal: function(effect) {
599 effect.effects[0].element.hide().setStyle(oldStyle); }
600 }, arguments[1] || {})
601 );
602}
603
604Effect.BlindUp = function(element) {
605 element = $(element);
606 element.makeClipping();
607 return new Effect.Scale(element, 0,
608 Object.extend({ scaleContent: false,
609 scaleX: false,
610 restoreAfterFinish: true,
611 afterFinishInternal: function(effect) {
612 effect.element.hide().undoClipping();
613 }
614 }, arguments[1] || {})
615 );
616}
617
618Effect.BlindDown = function(element) {
619 element = $(element);
620 var elementDimensions = element.getDimensions();
621 return new Effect.Scale(element, 100, Object.extend({
622 scaleContent: false,
623 scaleX: false,
624 scaleFrom: 0,
625 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
626 restoreAfterFinish: true,
627 afterSetup: function(effect) {
628 effect.element.makeClipping().setStyle({height: '0px'}).show();
629 },
630 afterFinishInternal: function(effect) {
631 effect.element.undoClipping();
632 }
633 }, arguments[1] || {}));
634}
635
636Effect.SwitchOff = function(element) {
637 element = $(element);
638 var oldOpacity = element.getInlineOpacity();
639 return new Effect.Appear(element, Object.extend({
640 duration: 0.4,
641 from: 0,
642 transition: Effect.Transitions.flicker,
643 afterFinishInternal: function(effect) {
644 new Effect.Scale(effect.element, 1, {
645 duration: 0.3, scaleFromCenter: true,
646 scaleX: false, scaleContent: false, restoreAfterFinish: true,
647 beforeSetup: function(effect) {
648 effect.element.makePositioned().makeClipping();
649 },
650 afterFinishInternal: function(effect) {
651 effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
652 }
653 })
654 }
655 }, arguments[1] || {}));
656}
657
658Effect.DropOut = function(element) {
659 element = $(element);
660 var oldStyle = {
661 top: element.getStyle('top'),
662 left: element.getStyle('left'),
663 opacity: element.getInlineOpacity() };
664 return new Effect.Parallel(
665 [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
666 new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
667 Object.extend(
668 { duration: 0.5,
669 beforeSetup: function(effect) {
670 effect.effects[0].element.makePositioned();
671 },
672 afterFinishInternal: function(effect) {
673 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
674 }
675 }, arguments[1] || {}));
676}
677
678Effect.Shake = function(element) {
679 element = $(element);
680 var oldStyle = {
681 top: element.getStyle('top'),
682 left: element.getStyle('left') };
683 return new Effect.Move(element,
684 { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
685 new Effect.Move(effect.element,
686 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
687 new Effect.Move(effect.element,
688 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
689 new Effect.Move(effect.element,
690 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
691 new Effect.Move(effect.element,
692 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
693 new Effect.Move(effect.element,
694 { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
695 effect.element.undoPositioned().setStyle(oldStyle);
696 }}) }}) }}) }}) }}) }});
697}
698
699Effect.SlideDown = function(element) {
700 element = $(element).cleanWhitespace();
701 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
702 var oldInnerBottom = element.down().getStyle('bottom');
703 var elementDimensions = element.getDimensions();
704 return new Effect.Scale(element, 100, Object.extend({
705 scaleContent: false,
706 scaleX: false,
707 scaleFrom: window.opera ? 0 : 1,
708 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
709 restoreAfterFinish: true,
710 afterSetup: function(effect) {
711 effect.element.makePositioned();
712 effect.element.down().makePositioned();
713 if(window.opera) effect.element.setStyle({top: ''});
714 effect.element.makeClipping().setStyle({height: '0px'}).show();
715 },
716 afterUpdateInternal: function(effect) {
717 effect.element.down().setStyle({bottom:
718 (effect.dims[0] - effect.element.clientHeight) + 'px' });
719 },
720 afterFinishInternal: function(effect) {
721 effect.element.undoClipping().undoPositioned();
722 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
723 }, arguments[1] || {})
724 );
725}
726
727Effect.SlideUp = function(element) {
728 element = $(element).cleanWhitespace();
729 var oldInnerBottom = element.down().getStyle('bottom');
730 return new Effect.Scale(element, window.opera ? 0 : 1,
731 Object.extend({ scaleContent: false,
732 scaleX: false,
733 scaleMode: 'box',
734 scaleFrom: 100,
735 restoreAfterFinish: true,
736 beforeStartInternal: function(effect) {
737 effect.element.makePositioned();
738 effect.element.down().makePositioned();
739 if(window.opera) effect.element.setStyle({top: ''});
740 effect.element.makeClipping().show();
741 },
742 afterUpdateInternal: function(effect) {
743 effect.element.down().setStyle({bottom:
744 (effect.dims[0] - effect.element.clientHeight) + 'px' });
745 },
746 afterFinishInternal: function(effect) {
747 effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
748 effect.element.down().undoPositioned();
749 }
750 }, arguments[1] || {})
751 );
752}
753
754// Bug in opera makes the TD containing this element expand for a instance after finish
755Effect.Squish = function(element) {
756 return new Effect.Scale(element, window.opera ? 1 : 0, {
757 restoreAfterFinish: true,
758 beforeSetup: function(effect) {
759 effect.element.makeClipping();
760 },
761 afterFinishInternal: function(effect) {
762 effect.element.hide().undoClipping();
763 }
764 });
765}
766
767Effect.Grow = function(element) {
768 element = $(element);
769 var options = Object.extend({
770 direction: 'center',
771 moveTransition: Effect.Transitions.sinoidal,
772 scaleTransition: Effect.Transitions.sinoidal,
773 opacityTransition: Effect.Transitions.full
774 }, arguments[1] || {});
775 var oldStyle = {
776 top: element.style.top,
777 left: element.style.left,
778 height: element.style.height,
779 width: element.style.width,
780 opacity: element.getInlineOpacity() };
781
782 var dims = element.getDimensions();
783 var initialMoveX, initialMoveY;
784 var moveX, moveY;
785
786 switch (options.direction) {
787 case 'top-left':
788 initialMoveX = initialMoveY = moveX = moveY = 0;
789 break;
790 case 'top-right':
791 initialMoveX = dims.width;
792 initialMoveY = moveY = 0;
793 moveX = -dims.width;
794 break;
795 case 'bottom-left':
796 initialMoveX = moveX = 0;
797 initialMoveY = dims.height;
798 moveY = -dims.height;
799 break;
800 case 'bottom-right':
801 initialMoveX = dims.width;
802 initialMoveY = dims.height;
803 moveX = -dims.width;
804 moveY = -dims.height;
805 break;
806 case 'center':
807 initialMoveX = dims.width / 2;
808 initialMoveY = dims.height / 2;
809 moveX = -dims.width / 2;
810 moveY = -dims.height / 2;
811 break;
812 }
813
814 return new Effect.Move(element, {
815 x: initialMoveX,
816 y: initialMoveY,
817 duration: 0.01,
818 beforeSetup: function(effect) {
819 effect.element.hide().makeClipping().makePositioned();
820 },
821 afterFinishInternal: function(effect) {
822 new Effect.Parallel(
823 [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
824 new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
825 new Effect.Scale(effect.element, 100, {
826 scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
827 sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
828 ], Object.extend({
829 beforeSetup: function(effect) {
830 effect.effects[0].element.setStyle({height: '0px'}).show();
831 },
832 afterFinishInternal: function(effect) {
833 effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
834 }
835 }, options)
836 )
837 }
838 });
839}
840
841Effect.Shrink = function(element) {
842 element = $(element);
843 var options = Object.extend({
844 direction: 'center',
845 moveTransition: Effect.Transitions.sinoidal,
846 scaleTransition: Effect.Transitions.sinoidal,
847 opacityTransition: Effect.Transitions.none
848 }, arguments[1] || {});
849 var oldStyle = {
850 top: element.style.top,
851 left: element.style.left,
852 height: element.style.height,
853 width: element.style.width,
854 opacity: element.getInlineOpacity() };
855
856 var dims = element.getDimensions();
857 var moveX, moveY;
858
859 switch (options.direction) {
860 case 'top-left':
861 moveX = moveY = 0;
862 break;
863 case 'top-right':
864 moveX = dims.width;
865 moveY = 0;
866 break;
867 case 'bottom-left':
868 moveX = 0;
869 moveY = dims.height;
870 break;
871 case 'bottom-right':
872 moveX = dims.width;
873 moveY = dims.height;
874 break;
875 case 'center':
876 moveX = dims.width / 2;
877 moveY = dims.height / 2;
878 break;
879 }
880
881 return new Effect.Parallel(
882 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
883 new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
884 new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
885 ], Object.extend({
886 beforeStartInternal: function(effect) {
887 effect.effects[0].element.makePositioned().makeClipping();
888 },
889 afterFinishInternal: function(effect) {
890 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
891 }, options)
892 );
893}
894
895Effect.Pulsate = function(element) {
896 element = $(element);
897 var options = arguments[1] || {};
898 var oldOpacity = element.getInlineOpacity();
899 var transition = options.transition || Effect.Transitions.sinoidal;
900 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
901 reverser.bind(transition);
902 return new Effect.Opacity(element,
903 Object.extend(Object.extend({ duration: 2.0, from: 0,
904 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
905 }, options), {transition: reverser}));
906}
907
908Effect.Fold = function(element) {
909 element = $(element);
910 var oldStyle = {
911 top: element.style.top,
912 left: element.style.left,
913 width: element.style.width,
914 height: element.style.height };
915 element.makeClipping();
916 return new Effect.Scale(element, 5, Object.extend({
917 scaleContent: false,
918 scaleX: false,
919 afterFinishInternal: function(effect) {
920 new Effect.Scale(element, 1, {
921 scaleContent: false,
922 scaleY: false,
923 afterFinishInternal: function(effect) {
924 effect.element.hide().undoClipping().setStyle(oldStyle);
925 } });
926 }}, arguments[1] || {}));
927};
928
929Effect.Morph = Class.create();
930Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
931 initialize: function(element) {
932 this.element = $(element);
933 if(!this.element) throw(Effect._elementDoesNotExistError);
934 var options = Object.extend({
935 style: {}
936 }, arguments[1] || {});
937 if (typeof options.style == 'string') {
938 if(options.style.indexOf(':') == -1) {
939 var cssText = '', selector = '.' + options.style;
940 $A(document.styleSheets).reverse().each(function(styleSheet) {
941 if (styleSheet.cssRules) cssRules = styleSheet.cssRules;
942 else if (styleSheet.rules) cssRules = styleSheet.rules;
943 $A(cssRules).reverse().each(function(rule) {
944 if (selector == rule.selectorText) {
945 cssText = rule.style.cssText;
946 throw $break;
947 }
948 });
949 if (cssText) throw $break;
950 });
951 this.style = cssText.parseStyle();
952 options.afterFinishInternal = function(effect){
953 effect.element.addClassName(effect.options.style);
954 effect.transforms.each(function(transform) {
955 if(transform.style != 'opacity')
956 effect.element.style[transform.style] = '';
957 });
958 }
959 } else this.style = options.style.parseStyle();
960 } else this.style = $H(options.style)
961 this.start(options);
962 },
963 setup: function(){
964 function parseColor(color){
965 if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
966 color = color.parseColor();
967 return $R(0,2).map(function(i){
968 return parseInt( color.slice(i*2+1,i*2+3), 16 )
969 });
970 }
971 this.transforms = this.style.map(function(pair){
972 var property = pair[0], value = pair[1], unit = null;
973
974 if(value.parseColor('#zzzzzz') != '#zzzzzz') {
975 value = value.parseColor();
976 unit = 'color';
977 } else if(property == 'opacity') {
978 value = parseFloat(value);
979 if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
980 this.element.setStyle({zoom: 1});
981 } else if(Element.CSS_LENGTH.test(value)) {
982 var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
983 value = parseFloat(components[1]);
984 unit = (components.length == 3) ? components[2] : null;
985 }
986
987 var originalValue = this.element.getStyle(property);
988 return {
989 style: property.camelize(),
990 originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
991 targetValue: unit=='color' ? parseColor(value) : value,
992 unit: unit
993 };
994 }.bind(this)).reject(function(transform){
995 return (
996 (transform.originalValue == transform.targetValue) ||
997 (
998 transform.unit != 'color' &&
999 (isNaN(transform.originalValue) || isNaN(transform.targetValue))
1000 )
1001 )
1002 });
1003 },
1004 update: function(position) {
1005 var style = {}, transform, i = this.transforms.length;
1006 while(i--)
1007 style[(transform = this.transforms[i]).style] =
1008 transform.unit=='color' ? '#'+
1009 (Math.round(transform.originalValue[0]+
1010 (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
1011 (Math.round(transform.originalValue[1]+
1012 (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
1013 (Math.round(transform.originalValue[2]+
1014 (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
1015 transform.originalValue + Math.round(
1016 ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
1017 this.element.setStyle(style, true);
1018 }
1019});
1020
1021Effect.Transform = Class.create();
1022Object.extend(Effect.Transform.prototype, {
1023 initialize: function(tracks){
1024 this.tracks = [];
1025 this.options = arguments[1] || {};
1026 this.addTracks(tracks);
1027 },
1028 addTracks: function(tracks){
1029 tracks.each(function(track){
1030 var data = $H(track).values().first();
1031 this.tracks.push($H({
1032 ids: $H(track).keys().first(),
1033 effect: Effect.Morph,
1034 options: { style: data }
1035 }));
1036 }.bind(this));
1037 return this;
1038 },
1039 play: function(){
1040 return new Effect.Parallel(
1041 this.tracks.map(function(track){
1042 var elements = [$(track.ids) || $$(track.ids)].flatten();
1043 return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
1044 }).flatten(),
1045 this.options
1046 );
1047 }
1048});
1049
1050Element.CSS_PROPERTIES = $w(
1051 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1052 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1053 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1054 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1055 'fontSize fontWeight height left letterSpacing lineHeight ' +
1056 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1057 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1058 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1059 'right textIndent top width wordSpacing zIndex');
1060
1061Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1062
1063String.prototype.parseStyle = function(){
1064 var element = document.createElement('div');
1065 element.innerHTML = '<div style="' + this + '"></div>';
1066 var style = element.childNodes[0].style, styleRules = $H();
1067
1068 Element.CSS_PROPERTIES.each(function(property){
1069 if(style[property]) styleRules[property] = style[property];
1070 });
1071 if(Prototype.Browser.IE && this.indexOf('opacity') > -1) {
1072 styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];
1073 }
1074 return styleRules;
1075};
1076
1077Element.morph = function(element, style) {
1078 new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
1079 return element;
1080};
1081
1082['getInlineOpacity','forceRerendering','setContentZoom',
1083 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
1084 function(f) { Element.Methods[f] = Element[f]; }
1085);
1086
1087Element.Methods.visualEffect = function(element, effect, options) {
1088 s = effect.dasherize().camelize();
1089 effect_class = s.charAt(0).toUpperCase() + s.substring(1);
1090 new Effect[effect_class](element, options);
1091 return $(element);
1092};
1093
1094Element.addMethods(); \ No newline at end of file
diff --git a/docroot/lib/scriptaculous/scriptaculous.js b/docroot/lib/scriptaculous/scriptaculous.js
new file mode 100755
index 0000000..7c472a6
--- /dev/null
+++ b/docroot/lib/scriptaculous/scriptaculous.js
@@ -0,0 +1,58 @@
1// script.aculo.us scriptaculous.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4//
5// Permission is hereby granted, free of charge, to any person obtaining
6// a copy of this software and associated documentation files (the
7// "Software"), to deal in the Software without restriction, including
8// without limitation the rights to use, copy, modify, merge, publish,
9// distribute, sublicense, and/or sell copies of the Software, and to
10// permit persons to whom the Software is furnished to do so, subject to
11// the following conditions:
12//
13// The above copyright notice and this permission notice shall be
14// included in all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23//
24// For details, see the script.aculo.us web site: http://script.aculo.us/
25
26var Scriptaculous = {
27 Version: '1.7.1_beta3',
28 require: function(libraryName) {
29 // inserting via DOM fails in Safari 2.0, so brute force approach
30 document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
31 },
32 REQUIRED_PROTOTYPE: '1.5.1',
33 load: function() {
34 function convertVersionString(versionString){
35 var r = versionString.split('.');
36 return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
37 }
38
39 if((typeof Prototype=='undefined') ||
40 (typeof Element == 'undefined') ||
41 (typeof Element.Methods=='undefined') ||
42 (convertVersionString(Prototype.Version) <
43 convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
44 throw("script.aculo.us requires the Prototype JavaScript framework >= " +
45 Scriptaculous.REQUIRED_PROTOTYPE);
46
47 $A(document.getElementsByTagName("script")).findAll( function(s) {
48 return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
49 }).each( function(s) {
50 var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
51 var includes = s.src.match(/\?.*load=([a-z,]*)/);
52 (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
53 function(include) { Scriptaculous.require(path+include+'.js') });
54 });
55 }
56}
57
58Scriptaculous.load(); \ No newline at end of file
diff --git a/docroot/lib/scriptaculous/slider.js b/docroot/lib/scriptaculous/slider.js
new file mode 100755
index 0000000..c1a84eb
--- /dev/null
+++ b/docroot/lib/scriptaculous/slider.js
@@ -0,0 +1,277 @@
1// script.aculo.us slider.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs
4//
5// script.aculo.us is freely distributable under the terms of an MIT-style license.
6// For details, see the script.aculo.us web site: http://script.aculo.us/
7
8if(!Control) var Control = {};
9Control.Slider = Class.create();
10
11// options:
12// axis: 'vertical', or 'horizontal' (default)
13//
14// callbacks:
15// onChange(value)
16// onSlide(value)
17Control.Slider.prototype = {
18 initialize: function(handle, track, options) {
19 var slider = this;
20
21 if(handle instanceof Array) {
22 this.handles = handle.collect( function(e) { return $(e) });
23 } else {
24 this.handles = [$(handle)];
25 }
26
27 this.track = $(track);
28 this.options = options || {};
29
30 this.axis = this.options.axis || 'horizontal';
31 this.increment = this.options.increment || 1;
32 this.step = parseInt(this.options.step || '1');
33 this.range = this.options.range || $R(0,1);
34
35 this.value = 0; // assure backwards compat
36 this.values = this.handles.map( function() { return 0 });
37 this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
38 this.options.startSpan = $(this.options.startSpan || null);
39 this.options.endSpan = $(this.options.endSpan || null);
40
41 this.restricted = this.options.restricted || false;
42
43 this.maximum = this.options.maximum || this.range.end;
44 this.minimum = this.options.minimum || this.range.start;
45
46 // Will be used to align the handle onto the track, if necessary
47 this.alignX = parseInt(this.options.alignX || '0');
48 this.alignY = parseInt(this.options.alignY || '0');
49
50 this.trackLength = this.maximumOffset() - this.minimumOffset();
51
52 this.handleLength = this.isVertical() ?
53 (this.handles[0].offsetHeight != 0 ?
54 this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
55 (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
56 this.handles[0].style.width.replace(/px$/,""));
57
58 this.active = false;
59 this.dragging = false;
60 this.disabled = false;
61
62 if(this.options.disabled) this.setDisabled();
63
64 // Allowed values array
65 this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
66 if(this.allowedValues) {
67 this.minimum = this.allowedValues.min();
68 this.maximum = this.allowedValues.max();
69 }
70
71 this.eventMouseDown = this.startDrag.bindAsEventListener(this);
72 this.eventMouseUp = this.endDrag.bindAsEventListener(this);
73 this.eventMouseMove = this.update.bindAsEventListener(this);
74
75 // Initialize handles in reverse (make sure first handle is active)
76 this.handles.each( function(h,i) {
77 i = slider.handles.length-1-i;
78 slider.setValue(parseFloat(
79 (slider.options.sliderValue instanceof Array ?
80 slider.options.sliderValue[i] : slider.options.sliderValue) ||
81 slider.range.start), i);
82 Element.makePositioned(h); // fix IE
83 Event.observe(h, "mousedown", slider.eventMouseDown);
84 });
85
86 Event.observe(this.track, "mousedown", this.eventMouseDown);
87 Event.observe(document, "mouseup", this.eventMouseUp);
88 Event.observe(document, "mousemove", this.eventMouseMove);
89
90 this.initialized = true;
91 },
92 dispose: function() {
93 var slider = this;
94 Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
95 Event.stopObserving(document, "mouseup", this.eventMouseUp);
96 Event.stopObserving(document, "mousemove", this.eventMouseMove);
97 this.handles.each( function(h) {
98 Event.stopObserving(h, "mousedown", slider.eventMouseDown);
99 });
100 },
101 setDisabled: function(){
102 this.disabled = true;
103 },
104 setEnabled: function(){
105 this.disabled = false;
106 },
107 getNearestValue: function(value){
108 if(this.allowedValues){
109 if(value >= this.allowedValues.max()) return(this.allowedValues.max());
110 if(value <= this.allowedValues.min()) return(this.allowedValues.min());
111
112 var offset = Math.abs(this.allowedValues[0] - value);
113 var newValue = this.allowedValues[0];
114 this.allowedValues.each( function(v) {
115 var currentOffset = Math.abs(v - value);
116 if(currentOffset <= offset){
117 newValue = v;
118 offset = currentOffset;
119 }
120 });
121 return newValue;
122 }
123 if(value > this.range.end) return this.range.end;
124 if(value < this.range.start) return this.range.start;
125 return value;
126 },
127 setValue: function(sliderValue, handleIdx){
128 if(!this.active) {
129 this.activeHandleIdx = handleIdx || 0;
130 this.activeHandle = this.handles[this.activeHandleIdx];
131 this.updateStyles();
132 }
133 handleIdx = handleIdx || this.activeHandleIdx || 0;
134 if(this.initialized && this.restricted) {
135 if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
136 sliderValue = this.values[handleIdx-1];
137 if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
138 sliderValue = this.values[handleIdx+1];
139 }
140 sliderValue = this.getNearestValue(sliderValue);
141 this.values[handleIdx] = sliderValue;
142 this.value = this.values[0]; // assure backwards compat
143
144 this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
145 this.translateToPx(sliderValue);
146
147 this.drawSpans();
148 if(!this.dragging || !this.event) this.updateFinished();
149 },
150 setValueBy: function(delta, handleIdx) {
151 this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
152 handleIdx || this.activeHandleIdx || 0);
153 },
154 translateToPx: function(value) {
155 return Math.round(
156 ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
157 (value - this.range.start)) + "px";
158 },
159 translateToValue: function(offset) {
160 return ((offset/(this.trackLength-this.handleLength) *
161 (this.range.end-this.range.start)) + this.range.start);
162 },
163 getRange: function(range) {
164 var v = this.values.sortBy(Prototype.K);
165 range = range || 0;
166 return $R(v[range],v[range+1]);
167 },
168 minimumOffset: function(){
169 return(this.isVertical() ? this.alignY : this.alignX);
170 },
171 maximumOffset: function(){
172 return(this.isVertical() ?
173 (this.track.offsetHeight != 0 ? this.track.offsetHeight :
174 this.track.style.height.replace(/px$/,"")) - this.alignY :
175 (this.track.offsetWidth != 0 ? this.track.offsetWidth :
176 this.track.style.width.replace(/px$/,"")) - this.alignY);
177 },
178 isVertical: function(){
179 return (this.axis == 'vertical');
180 },
181 drawSpans: function() {
182 var slider = this;
183 if(this.spans)
184 $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
185 if(this.options.startSpan)
186 this.setSpan(this.options.startSpan,
187 $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
188 if(this.options.endSpan)
189 this.setSpan(this.options.endSpan,
190 $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
191 },
192 setSpan: function(span, range) {
193 if(this.isVertical()) {
194 span.style.top = this.translateToPx(range.start);
195 span.style.height = this.translateToPx(range.end - range.start + this.range.start);
196 } else {
197 span.style.left = this.translateToPx(range.start);
198 span.style.width = this.translateToPx(range.end - range.start + this.range.start);
199 }
200 },
201 updateStyles: function() {
202 this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
203 Element.addClassName(this.activeHandle, 'selected');
204 },
205 startDrag: function(event) {
206 if(Event.isLeftClick(event)) {
207 if(!this.disabled){
208 this.active = true;
209
210 var handle = Event.element(event);
211 var pointer = [Event.pointerX(event), Event.pointerY(event)];
212 var track = handle;
213 if(track==this.track) {
214 var offsets = Position.cumulativeOffset(this.track);
215 this.event = event;
216 this.setValue(this.translateToValue(
217 (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
218 ));
219 var offsets = Position.cumulativeOffset(this.activeHandle);
220 this.offsetX = (pointer[0] - offsets[0]);
221 this.offsetY = (pointer[1] - offsets[1]);
222 } else {
223 // find the handle (prevents issues with Safari)
224 while((this.handles.indexOf(handle) == -1) && handle.parentNode)
225 handle = handle.parentNode;
226
227 if(this.handles.indexOf(handle)!=-1) {
228 this.activeHandle = handle;
229 this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
230 this.updateStyles();
231
232 var offsets = Position.cumulativeOffset(this.activeHandle);
233 this.offsetX = (pointer[0] - offsets[0]);
234 this.offsetY = (pointer[1] - offsets[1]);
235 }
236 }
237 }
238 Event.stop(event);
239 }
240 },
241 update: function(event) {
242 if(this.active) {
243 if(!this.dragging) this.dragging = true;
244 this.draw(event);
245 if(Prototype.Browser.WebKit) window.scrollBy(0,0);
246 Event.stop(event);
247 }
248 },
249 draw: function(event) {
250 var pointer = [Event.pointerX(event), Event.pointerY(event)];
251 var offsets = Position.cumulativeOffset(this.track);
252 pointer[0] -= this.offsetX + offsets[0];
253 pointer[1] -= this.offsetY + offsets[1];
254 this.event = event;
255 this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
256 if(this.initialized && this.options.onSlide)
257 this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
258 },
259 endDrag: function(event) {
260 if(this.active && this.dragging) {
261 this.finishDrag(event, true);
262 Event.stop(event);
263 }
264 this.active = false;
265 this.dragging = false;
266 },
267 finishDrag: function(event, success) {
268 this.active = false;
269 this.dragging = false;
270 this.updateFinished();
271 },
272 updateFinished: function() {
273 if(this.initialized && this.options.onChange)
274 this.options.onChange(this.values.length>1 ? this.values : this.value, this);
275 this.event = null;
276 }
277} \ No newline at end of file
diff --git a/docroot/lib/scriptaculous/sound.js b/docroot/lib/scriptaculous/sound.js
new file mode 100755
index 0000000..164c79a
--- /dev/null
+++ b/docroot/lib/scriptaculous/sound.js
@@ -0,0 +1,60 @@
1// script.aculo.us sound.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4//
5// Based on code created by Jules Gravinese (http://www.webveteran.com/)
6//
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/
9
10Sound = {
11 tracks: {},
12 _enabled: true,
13 template:
14 new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
15 enable: function(){
16 Sound._enabled = true;
17 },
18 disable: function(){
19 Sound._enabled = false;
20 },
21 play: function(url){
22 if(!Sound._enabled) return;
23 var options = Object.extend({
24 track: 'global', url: url, replace: false
25 }, arguments[1] || {});
26
27 if(options.replace && this.tracks[options.track]) {
28 $R(0, this.tracks[options.track].id).each(function(id){
29 var sound = $('sound_'+options.track+'_'+id);
30 sound.Stop && sound.Stop();
31 sound.remove();
32 })
33 this.tracks[options.track] = null;
34 }
35
36 if(!this.tracks[options.track])
37 this.tracks[options.track] = { id: 0 }
38 else
39 this.tracks[options.track].id++;
40
41 options.id = this.tracks[options.track].id;
42 if (Prototype.Browser.IE) {
43 var sound = document.createElement('bgsound');
44 sound.setAttribute('id','sound_'+options.track+'_'+options.id);
45 sound.setAttribute('src',options.url);
46 sound.setAttribute('loop','1');
47 sound.setAttribute('autostart','true');
48 $$('body')[0].appendChild(sound);
49 }
50 else
51 new Insertion.Bottom($$('body')[0], Sound.template.evaluate(options));
52 }
53};
54
55if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
56 if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
57 Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')
58 else
59 Sound.play = function(){}
60}
diff --git a/docroot/lib/scriptaculous/unittest.js b/docroot/lib/scriptaculous/unittest.js
new file mode 100755
index 0000000..d2dff8b
--- /dev/null
+++ b/docroot/lib/scriptaculous/unittest.js
@@ -0,0 +1,564 @@
1// script.aculo.us unittest.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
5// (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
6//
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/
9
10// experimental, Firefox-only
11Event.simulateMouse = function(element, eventName) {
12 var options = Object.extend({
13 pointerX: 0,
14 pointerY: 0,
15 buttons: 0,
16 ctrlKey: false,
17 altKey: false,
18 shiftKey: false,
19 metaKey: false
20 }, arguments[2] || {});
21 var oEvent = document.createEvent("MouseEvents");
22 oEvent.initMouseEvent(eventName, true, true, document.defaultView,
23 options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
24 options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
25
26 if(this.mark) Element.remove(this.mark);
27 this.mark = document.createElement('div');
28 this.mark.appendChild(document.createTextNode(" "));
29 document.body.appendChild(this.mark);
30 this.mark.style.position = 'absolute';
31 this.mark.style.top = options.pointerY + "px";
32 this.mark.style.left = options.pointerX + "px";
33 this.mark.style.width = "5px";
34 this.mark.style.height = "5px;";
35 this.mark.style.borderTop = "1px solid red;"
36 this.mark.style.borderLeft = "1px solid red;"
37
38 if(this.step)
39 alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
40
41 $(element).dispatchEvent(oEvent);
42};
43
44// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
45// You need to downgrade to 1.0.4 for now to get this working
46// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
47Event.simulateKey = function(element, eventName) {
48 var options = Object.extend({
49 ctrlKey: false,
50 altKey: false,
51 shiftKey: false,
52 metaKey: false,
53 keyCode: 0,
54 charCode: 0
55 }, arguments[2] || {});
56
57 var oEvent = document.createEvent("KeyEvents");
58 oEvent.initKeyEvent(eventName, true, true, window,
59 options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
60 options.keyCode, options.charCode );
61 $(element).dispatchEvent(oEvent);
62};
63
64Event.simulateKeys = function(element, command) {
65 for(var i=0; i<command.length; i++) {
66 Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
67 }
68};
69
70var Test = {}
71Test.Unit = {};
72
73// security exception workaround
74Test.Unit.inspect = Object.inspect;
75
76Test.Unit.Logger = Class.create();
77Test.Unit.Logger.prototype = {
78 initialize: function(log) {
79 this.log = $(log);
80 if (this.log) {
81 this._createLogTable();
82 }
83 },
84 start: function(testName) {
85 if (!this.log) return;
86 this.testName = testName;
87 this.lastLogLine = document.createElement('tr');
88 this.statusCell = document.createElement('td');
89 this.nameCell = document.createElement('td');
90 this.nameCell.className = "nameCell";
91 this.nameCell.appendChild(document.createTextNode(testName));
92 this.messageCell = document.createElement('td');
93 this.lastLogLine.appendChild(this.statusCell);
94 this.lastLogLine.appendChild(this.nameCell);
95 this.lastLogLine.appendChild(this.messageCell);
96 this.loglines.appendChild(this.lastLogLine);
97 },
98 finish: function(status, summary) {
99 if (!this.log) return;
100 this.lastLogLine.className = status;
101 this.statusCell.innerHTML = status;
102 this.messageCell.innerHTML = this._toHTML(summary);
103 this.addLinksToResults();
104 },
105 message: function(message) {
106 if (!this.log) return;
107 this.messageCell.innerHTML = this._toHTML(message);
108 },
109 summary: function(summary) {
110 if (!this.log) return;
111 this.logsummary.innerHTML = this._toHTML(summary);
112 },
113 _createLogTable: function() {
114 this.log.innerHTML =
115 '<div id="logsummary"></div>' +
116 '<table id="logtable">' +
117 '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
118 '<tbody id="loglines"></tbody>' +
119 '</table>';
120 this.logsummary = $('logsummary')
121 this.loglines = $('loglines');
122 },
123 _toHTML: function(txt) {
124 return txt.escapeHTML().replace(/\n/g,"<br/>");
125 },
126 addLinksToResults: function(){
127 $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
128 td.title = "Run only this test"
129 Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
130 });
131 $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
132 td.title = "Run all tests"
133 Event.observe(td, 'click', function(){ window.location.search = "";});
134 });
135 }
136}
137
138Test.Unit.Runner = Class.create();
139Test.Unit.Runner.prototype = {
140 initialize: function(testcases) {
141 this.options = Object.extend({
142 testLog: 'testlog'
143 }, arguments[1] || {});
144 this.options.resultsURL = this.parseResultsURLQueryParameter();
145 this.options.tests = this.parseTestsQueryParameter();
146 if (this.options.testLog) {
147 this.options.testLog = $(this.options.testLog) || null;
148 }
149 if(this.options.tests) {
150 this.tests = [];
151 for(var i = 0; i < this.options.tests.length; i++) {
152 if(/^test/.test(this.options.tests[i])) {
153 this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
154 }
155 }
156 } else {
157 if (this.options.test) {
158 this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
159 } else {
160 this.tests = [];
161 for(var testcase in testcases) {
162 if(/^test/.test(testcase)) {
163 this.tests.push(
164 new Test.Unit.Testcase(
165 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase,
166 testcases[testcase], testcases["setup"], testcases["teardown"]
167 ));
168 }
169 }
170 }
171 }
172 this.currentTest = 0;
173 this.logger = new Test.Unit.Logger(this.options.testLog);
174 setTimeout(this.runTests.bind(this), 1000);
175 },
176 parseResultsURLQueryParameter: function() {
177 return window.location.search.parseQuery()["resultsURL"];
178 },
179 parseTestsQueryParameter: function(){
180 if (window.location.search.parseQuery()["tests"]){
181 return window.location.search.parseQuery()["tests"].split(',');
182 };
183 },
184 // Returns:
185 // "ERROR" if there was an error,
186 // "FAILURE" if there was a failure, or
187 // "SUCCESS" if there was neither
188 getResult: function() {
189 var hasFailure = false;
190 for(var i=0;i<this.tests.length;i++) {
191 if (this.tests[i].errors > 0) {
192 return "ERROR";
193 }
194 if (this.tests[i].failures > 0) {
195 hasFailure = true;
196 }
197 }
198 if (hasFailure) {
199 return "FAILURE";
200 } else {
201 return "SUCCESS";
202 }
203 },
204 postResults: function() {
205 if (this.options.resultsURL) {
206 new Ajax.Request(this.options.resultsURL,
207 { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
208 }
209 },
210 runTests: function() {
211 var test = this.tests[this.currentTest];
212 if (!test) {
213 // finished!
214 this.postResults();
215 this.logger.summary(this.summary());
216 return;
217 }
218 if(!test.isWaiting) {
219 this.logger.start(test.name);
220 }
221 test.run();
222 if(test.isWaiting) {
223 this.logger.message("Waiting for " + test.timeToWait + "ms");
224 setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
225 } else {
226 this.logger.finish(test.status(), test.summary());
227 this.currentTest++;
228 // tail recursive, hopefully the browser will skip the stackframe
229 this.runTests();
230 }
231 },
232 summary: function() {
233 var assertions = 0;
234 var failures = 0;
235 var errors = 0;
236 var messages = [];
237 for(var i=0;i<this.tests.length;i++) {
238 assertions += this.tests[i].assertions;
239 failures += this.tests[i].failures;
240 errors += this.tests[i].errors;
241 }
242 return (
243 (this.options.context ? this.options.context + ': ': '') +
244 this.tests.length + " tests, " +
245 assertions + " assertions, " +
246 failures + " failures, " +
247 errors + " errors");
248 }
249}
250
251Test.Unit.Assertions = Class.create();
252Test.Unit.Assertions.prototype = {
253 initialize: function() {
254 this.assertions = 0;
255 this.failures = 0;
256 this.errors = 0;
257 this.messages = [];
258 },
259 summary: function() {
260 return (
261 this.assertions + " assertions, " +
262 this.failures + " failures, " +
263 this.errors + " errors" + "\n" +
264 this.messages.join("\n"));
265 },
266 pass: function() {
267 this.assertions++;
268 },
269 fail: function(message) {
270 this.failures++;
271 this.messages.push("Failure: " + message);
272 },
273 info: function(message) {
274 this.messages.push("Info: " + message);
275 },
276 error: function(error) {
277 this.errors++;
278 this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
279 },
280 status: function() {
281 if (this.failures > 0) return 'failed';
282 if (this.errors > 0) return 'error';
283 return 'passed';
284 },
285 assert: function(expression) {
286 var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
287 try { expression ? this.pass() :
288 this.fail(message); }
289 catch(e) { this.error(e); }
290 },
291 assertEqual: function(expected, actual) {
292 var message = arguments[2] || "assertEqual";
293 try { (expected == actual) ? this.pass() :
294 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
295 '", actual "' + Test.Unit.inspect(actual) + '"'); }
296 catch(e) { this.error(e); }
297 },
298 assertInspect: function(expected, actual) {
299 var message = arguments[2] || "assertInspect";
300 try { (expected == actual.inspect()) ? this.pass() :
301 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
302 '", actual "' + Test.Unit.inspect(actual) + '"'); }
303 catch(e) { this.error(e); }
304 },
305 assertEnumEqual: function(expected, actual) {
306 var message = arguments[2] || "assertEnumEqual";
307 try { $A(expected).length == $A(actual).length &&
308 expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
309 this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +
310 ', actual ' + Test.Unit.inspect(actual)); }
311 catch(e) { this.error(e); }
312 },
313 assertNotEqual: function(expected, actual) {
314 var message = arguments[2] || "assertNotEqual";
315 try { (expected != actual) ? this.pass() :
316 this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
317 catch(e) { this.error(e); }
318 },
319 assertIdentical: function(expected, actual) {
320 var message = arguments[2] || "assertIdentical";
321 try { (expected === actual) ? this.pass() :
322 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
323 '", actual "' + Test.Unit.inspect(actual) + '"'); }
324 catch(e) { this.error(e); }
325 },
326 assertNotIdentical: function(expected, actual) {
327 var message = arguments[2] || "assertNotIdentical";
328 try { !(expected === actual) ? this.pass() :
329 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
330 '", actual "' + Test.Unit.inspect(actual) + '"'); }
331 catch(e) { this.error(e); }
332 },
333 assertNull: function(obj) {
334 var message = arguments[1] || 'assertNull'
335 try { (obj==null) ? this.pass() :
336 this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
337 catch(e) { this.error(e); }
338 },
339 assertMatch: function(expected, actual) {
340 var message = arguments[2] || 'assertMatch';
341 var regex = new RegExp(expected);
342 try { (regex.exec(actual)) ? this.pass() :
343 this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
344 catch(e) { this.error(e); }
345 },
346 assertHidden: function(element) {
347 var message = arguments[1] || 'assertHidden';
348 this.assertEqual("none", element.style.display, message);
349 },
350 assertNotNull: function(object) {
351 var message = arguments[1] || 'assertNotNull';
352 this.assert(object != null, message);
353 },
354 assertType: function(expected, actual) {
355 var message = arguments[2] || 'assertType';
356 try {
357 (actual.constructor == expected) ? this.pass() :
358 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
359 '", actual "' + (actual.constructor) + '"'); }
360 catch(e) { this.error(e); }
361 },
362 assertNotOfType: function(expected, actual) {
363 var message = arguments[2] || 'assertNotOfType';
364 try {
365 (actual.constructor != expected) ? this.pass() :
366 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
367 '", actual "' + (actual.constructor) + '"'); }
368 catch(e) { this.error(e); }
369 },
370 assertInstanceOf: function(expected, actual) {
371 var message = arguments[2] || 'assertInstanceOf';
372 try {
373 (actual instanceof expected) ? this.pass() :
374 this.fail(message + ": object was not an instance of the expected type"); }
375 catch(e) { this.error(e); }
376 },
377 assertNotInstanceOf: function(expected, actual) {
378 var message = arguments[2] || 'assertNotInstanceOf';
379 try {
380 !(actual instanceof expected) ? this.pass() :
381 this.fail(message + ": object was an instance of the not expected type"); }
382 catch(e) { this.error(e); }
383 },
384 assertRespondsTo: function(method, obj) {
385 var message = arguments[2] || 'assertRespondsTo';
386 try {
387 (obj[method] && typeof obj[method] == 'function') ? this.pass() :
388 this.fail(message + ": object doesn't respond to [" + method + "]"); }
389 catch(e) { this.error(e); }
390 },
391 assertReturnsTrue: function(method, obj) {
392 var message = arguments[2] || 'assertReturnsTrue';
393 try {
394 var m = obj[method];
395 if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
396 m() ? this.pass() :
397 this.fail(message + ": method returned false"); }
398 catch(e) { this.error(e); }
399 },
400 assertReturnsFalse: function(method, obj) {
401 var message = arguments[2] || 'assertReturnsFalse';
402 try {
403 var m = obj[method];
404 if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
405 !m() ? this.pass() :
406 this.fail(message + ": method returned true"); }
407 catch(e) { this.error(e); }
408 },
409 assertRaise: function(exceptionName, method) {
410 var message = arguments[2] || 'assertRaise';
411 try {
412 method();
413 this.fail(message + ": exception expected but none was raised"); }
414 catch(e) {
415 ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e);
416 }
417 },
418 assertElementsMatch: function() {
419 var expressions = $A(arguments), elements = $A(expressions.shift());
420 if (elements.length != expressions.length) {
421 this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
422 return false;
423 }
424 elements.zip(expressions).all(function(pair, index) {
425 var element = $(pair.first()), expression = pair.last();
426 if (element.match(expression)) return true;
427 this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
428 }.bind(this)) && this.pass();
429 },
430 assertElementMatches: function(element, expression) {
431 this.assertElementsMatch([element], expression);
432 },
433 benchmark: function(operation, iterations) {
434 var startAt = new Date();
435 (iterations || 1).times(operation);
436 var timeTaken = ((new Date())-startAt);
437 this.info((arguments[2] || 'Operation') + ' finished ' +
438 iterations + ' iterations in ' + (timeTaken/1000)+'s' );
439 return timeTaken;
440 },
441 _isVisible: function(element) {
442 element = $(element);
443 if(!element.parentNode) return true;
444 this.assertNotNull(element);
445 if(element.style && Element.getStyle(element, 'display') == 'none')
446 return false;
447
448 return this._isVisible(element.parentNode);
449 },
450 assertNotVisible: function(element) {
451 this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
452 },
453 assertVisible: function(element) {
454 this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
455 },
456 benchmark: function(operation, iterations) {
457 var startAt = new Date();
458 (iterations || 1).times(operation);
459 var timeTaken = ((new Date())-startAt);
460 this.info((arguments[2] || 'Operation') + ' finished ' +
461 iterations + ' iterations in ' + (timeTaken/1000)+'s' );
462 return timeTaken;
463 }
464}
465
466Test.Unit.Testcase = Class.create();
467Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
468 initialize: function(name, test, setup, teardown) {
469 Test.Unit.Assertions.prototype.initialize.bind(this)();
470 this.name = name;
471
472 if(typeof test == 'string') {
473 test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
474 test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
475 this.test = function() {
476 eval('with(this){'+test+'}');
477 }
478 } else {
479 this.test = test || function() {};
480 }
481
482 this.setup = setup || function() {};
483 this.teardown = teardown || function() {};
484 this.isWaiting = false;
485 this.timeToWait = 1000;
486 },
487 wait: function(time, nextPart) {
488 this.isWaiting = true;
489 this.test = nextPart;
490 this.timeToWait = time;
491 },
492 run: function() {
493 try {
494 try {
495 if (!this.isWaiting) this.setup.bind(this)();
496 this.isWaiting = false;
497 this.test.bind(this)();
498 } finally {
499 if(!this.isWaiting) {
500 this.teardown.bind(this)();
501 }
502 }
503 }
504 catch(e) { this.error(e); }
505 }
506});
507
508// *EXPERIMENTAL* BDD-style testing to please non-technical folk
509// This draws many ideas from RSpec http://rspec.rubyforge.org/
510
511Test.setupBDDExtensionMethods = function(){
512 var METHODMAP = {
513 shouldEqual: 'assertEqual',
514 shouldNotEqual: 'assertNotEqual',
515 shouldEqualEnum: 'assertEnumEqual',
516 shouldBeA: 'assertType',
517 shouldNotBeA: 'assertNotOfType',
518 shouldBeAn: 'assertType',
519 shouldNotBeAn: 'assertNotOfType',
520 shouldBeNull: 'assertNull',
521 shouldNotBeNull: 'assertNotNull',
522
523 shouldBe: 'assertReturnsTrue',
524 shouldNotBe: 'assertReturnsFalse',
525 shouldRespondTo: 'assertRespondsTo'
526 };
527 Test.BDDMethods = {};
528 for(m in METHODMAP) {
529 Test.BDDMethods[m] = eval(
530 'function(){'+
531 'var args = $A(arguments);'+
532 'var scope = args.shift();'+
533 'scope.'+METHODMAP[m]+'.apply(scope,(args || []).concat([this])); }');
534 }
535 [Array.prototype, String.prototype, Number.prototype].each(
536 function(p){ Object.extend(p, Test.BDDMethods) }
537 );
538}
539
540Test.context = function(name, spec, log){
541 Test.setupBDDExtensionMethods();
542
543 var compiledSpec = {};
544 var titles = {};
545 for(specName in spec) {
546 switch(specName){
547 case "setup":
548 case "teardown":
549 compiledSpec[specName] = spec[specName];
550 break;
551 default:
552 var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
553 var body = spec[specName].toString().split('\n').slice(1);
554 if(/^\{/.test(body[0])) body = body.slice(1);
555 body.pop();
556 body = body.map(function(statement){
557 return statement.strip()
558 });
559 compiledSpec[testName] = body.join('\n');
560 titles[testName] = specName;
561 }
562 }
563 new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
564}; \ No newline at end of file