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