diff options
Diffstat (limited to 'docroot/lib/scriptaculous')
-rwxr-xr-x | docroot/lib/scriptaculous/builder.js | 136 | ||||
-rwxr-xr-x | docroot/lib/scriptaculous/controls.js | 875 | ||||
-rwxr-xr-x | docroot/lib/scriptaculous/dragdrop.js | 970 | ||||
-rwxr-xr-x | docroot/lib/scriptaculous/effects.js | 1094 | ||||
-rwxr-xr-x | docroot/lib/scriptaculous/scriptaculous.js | 58 | ||||
-rwxr-xr-x | docroot/lib/scriptaculous/slider.js | 277 | ||||
-rwxr-xr-x | docroot/lib/scriptaculous/sound.js | 60 | ||||
-rwxr-xr-x | docroot/lib/scriptaculous/unittest.js | 564 |
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 | |||
8 | var 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(/"/,'"') + '"'); | ||
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 | |||
39 | if(typeof Effect == 'undefined') | ||
40 | throw("controls.js requires including script.aculo.us' effects.js library"); | ||
41 | |||
42 | var Autocompleter = {} | ||
43 | Autocompleter.Base = function() {}; | ||
44 | Autocompleter.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 | |||
336 | Ajax.Autocompleter = Class.create(); | ||
337 | Object.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 | |||
402 | Autocompleter.Local = Class.create(); | ||
403 | Autocompleter.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 | ||
470 | Field.scrollFreeActivate = function(field) { | ||
471 | setTimeout(function() { | ||
472 | Field.activate(field); | ||
473 | }, 1); | ||
474 | } | ||
475 | |||
476 | Ajax.InPlaceEditor = Class.create(); | ||
477 | Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; | ||
478 | Ajax.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 | |||
822 | Ajax.InPlaceCollectionEditor = Class.create(); | ||
823 | Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); | ||
824 | Object.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 | |||
855 | Form.Element.DelayedObserver = Class.create(); | ||
856 | Form.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 | |||
9 | if(typeof Effect == 'undefined') | ||
10 | throw("dragdrop.js requires including script.aculo.us' effects.js library"); | ||
11 | |||
12 | var 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 | |||
127 | var 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 | |||
226 | var Draggable = Class.create(); | ||
227 | Draggable._dragging = {}; | ||
228 | |||
229 | Draggable.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 | |||
571 | var SortableObserver = Class.create(); | ||
572 | SortableObserver.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 | |||
590 | var 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 | ||
944 | Element.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 | |||
950 | Element.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 | |||
968 | Element.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 | ||
14 | String.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 | |||
30 | Element.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 | |||
37 | Element.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 | |||
45 | Element.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 | |||
52 | Element.getInlineOpacity = function(element){ | ||
53 | return $(element).style.opacity || ''; | ||
54 | } | ||
55 | |||
56 | Element.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 | |||
67 | Array.prototype.call = function() { | ||
68 | var args = arguments; | ||
69 | this.each(function(f){ f.apply(this, args) }); | ||
70 | } | ||
71 | |||
72 | /*--------------------------------------------------------------------------*/ | ||
73 | |||
74 | var 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 | |||
134 | var Effect2 = Effect; // deprecated | ||
135 | |||
136 | /* ------------- transitions ------------- */ | ||
137 | |||
138 | Effect.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 | |||
171 | Effect.ScopedQueue = Class.create(); | ||
172 | Object.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 | |||
226 | Effect.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 | } | ||
237 | Effect.Queue = Effect.Queues.get('global'); | ||
238 | |||
239 | Effect.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 | |||
250 | Effect.Base = function() {}; | ||
251 | Effect.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 | |||
324 | Effect.Parallel = Class.create(); | ||
325 | Object.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 | |||
344 | Effect.Event = Class.create(); | ||
345 | Object.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 | |||
355 | Effect.Opacity = Class.create(); | ||
356 | Object.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 | |||
374 | Effect.Move = Class.create(); | ||
375 | Object.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 | ||
409 | Effect.MoveBy = function(element, toTop, toLeft) { | ||
410 | return new Effect.Move(element, | ||
411 | Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); | ||
412 | }; | ||
413 | |||
414 | Effect.Scale = Class.create(); | ||
415 | Object.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 | |||
489 | Effect.Highlight = Class.create(); | ||
490 | Object.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 | |||
525 | Effect.ScrollTo = Class.create(); | ||
526 | Object.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 | |||
552 | Effect.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 | |||
565 | Effect.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 | |||
580 | Effect.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 | |||
604 | Effect.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 | |||
618 | Effect.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 | |||
636 | Effect.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 | |||
658 | Effect.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 | |||
678 | Effect.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 | |||
699 | Effect.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 | |||
727 | Effect.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 | ||
755 | Effect.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 | |||
767 | Effect.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 | |||
841 | Effect.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 | |||
895 | Effect.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 | |||
908 | Effect.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 | |||
929 | Effect.Morph = Class.create(); | ||
930 | Object.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 | |||
1021 | Effect.Transform = Class.create(); | ||
1022 | Object.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 | |||
1050 | Element.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 | |||
1061 | Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; | ||
1062 | |||
1063 | String.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 | |||
1077 | Element.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 | |||
1087 | Element.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 | |||
1094 | Element.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 | |||
26 | var 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 | |||
58 | Scriptaculous.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 | |||
8 | if(!Control) var Control = {}; | ||
9 | Control.Slider = Class.create(); | ||
10 | |||
11 | // options: | ||
12 | // axis: 'vertical', or 'horizontal' (default) | ||
13 | // | ||
14 | // callbacks: | ||
15 | // onChange(value) | ||
16 | // onSlide(value) | ||
17 | Control.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 | |||
10 | Sound = { | ||
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 | |||
55 | if(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 | ||
11 | Event.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 | ||
47 | Event.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 | |||
64 | Event.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 | |||
70 | var Test = {} | ||
71 | Test.Unit = {}; | ||
72 | |||
73 | // security exception workaround | ||
74 | Test.Unit.inspect = Object.inspect; | ||
75 | |||
76 | Test.Unit.Logger = Class.create(); | ||
77 | Test.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 | |||
138 | Test.Unit.Runner = Class.create(); | ||
139 | Test.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 | |||
251 | Test.Unit.Assertions = Class.create(); | ||
252 | Test.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 | |||
466 | Test.Unit.Testcase = Class.create(); | ||
467 | Object.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 | |||
511 | Test.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 | |||
540 | Test.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 | ||