actionpack 1.9.1 → 1.10.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (123) hide show
  1. data/CHANGELOG +237 -0
  2. data/README +12 -12
  3. data/lib/action_controller.rb +17 -12
  4. data/lib/action_controller/assertions.rb +119 -67
  5. data/lib/action_controller/base.rb +184 -102
  6. data/lib/action_controller/benchmarking.rb +35 -6
  7. data/lib/action_controller/caching.rb +115 -58
  8. data/lib/action_controller/cgi_ext/cgi_methods.rb +54 -21
  9. data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +39 -35
  10. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +34 -21
  11. data/lib/action_controller/cgi_process.rb +23 -20
  12. data/lib/action_controller/components.rb +11 -2
  13. data/lib/action_controller/dependencies.rb +0 -5
  14. data/lib/action_controller/deprecated_redirects.rb +17 -0
  15. data/lib/action_controller/filters.rb +13 -9
  16. data/lib/action_controller/flash.rb +7 -7
  17. data/lib/action_controller/helpers.rb +1 -14
  18. data/lib/action_controller/layout.rb +40 -29
  19. data/lib/action_controller/macros/auto_complete.rb +52 -0
  20. data/lib/action_controller/macros/in_place_editing.rb +32 -0
  21. data/lib/action_controller/pagination.rb +44 -28
  22. data/lib/action_controller/request.rb +54 -40
  23. data/lib/action_controller/rescue.rb +8 -6
  24. data/lib/action_controller/routing.rb +77 -28
  25. data/lib/action_controller/scaffolding.rb +10 -14
  26. data/lib/action_controller/session/active_record_store.rb +36 -7
  27. data/lib/action_controller/session_management.rb +126 -0
  28. data/lib/action_controller/streaming.rb +14 -5
  29. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +1 -1
  30. data/lib/action_controller/templates/rescues/_trace.rhtml +24 -0
  31. data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -13
  32. data/lib/action_controller/templates/rescues/template_error.rhtml +4 -2
  33. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  34. data/lib/action_controller/test_process.rb +35 -17
  35. data/lib/action_controller/upload_progress.rb +52 -0
  36. data/lib/action_controller/url_rewriter.rb +21 -16
  37. data/lib/action_controller/vendor/html-scanner/html/document.rb +2 -2
  38. data/lib/action_controller/vendor/html-scanner/html/node.rb +30 -3
  39. data/lib/action_pack/version.rb +9 -0
  40. data/lib/action_view.rb +1 -1
  41. data/lib/action_view/base.rb +204 -60
  42. data/lib/action_view/compiled_templates.rb +70 -0
  43. data/lib/action_view/helpers/active_record_helper.rb +7 -3
  44. data/lib/action_view/helpers/asset_tag_helper.rb +22 -12
  45. data/lib/action_view/helpers/capture_helper.rb +2 -10
  46. data/lib/action_view/helpers/date_helper.rb +21 -13
  47. data/lib/action_view/helpers/form_helper.rb +14 -10
  48. data/lib/action_view/helpers/form_options_helper.rb +4 -4
  49. data/lib/action_view/helpers/form_tag_helper.rb +59 -25
  50. data/lib/action_view/helpers/java_script_macros_helper.rb +188 -0
  51. data/lib/action_view/helpers/javascript_helper.rb +68 -133
  52. data/lib/action_view/helpers/javascripts/controls.js +427 -165
  53. data/lib/action_view/helpers/javascripts/dragdrop.js +256 -277
  54. data/lib/action_view/helpers/javascripts/effects.js +766 -277
  55. data/lib/action_view/helpers/javascripts/prototype.js +906 -218
  56. data/lib/action_view/helpers/javascripts/slider.js +258 -0
  57. data/lib/action_view/helpers/number_helper.rb +4 -3
  58. data/lib/action_view/helpers/pagination_helper.rb +42 -27
  59. data/lib/action_view/helpers/tag_helper.rb +25 -11
  60. data/lib/action_view/helpers/text_helper.rb +119 -13
  61. data/lib/action_view/helpers/upload_progress_helper.rb +2 -2
  62. data/lib/action_view/helpers/url_helper.rb +68 -21
  63. data/lib/action_view/partials.rb +17 -6
  64. data/lib/action_view/template_error.rb +19 -24
  65. data/rakefile +4 -3
  66. data/test/abstract_unit.rb +2 -1
  67. data/test/controller/action_pack_assertions_test.rb +62 -2
  68. data/test/controller/active_record_assertions_test.rb +5 -6
  69. data/test/controller/active_record_store_test.rb +23 -1
  70. data/test/controller/addresses_render_test.rb +4 -0
  71. data/test/controller/{base_tests.rb → base_test.rb} +4 -3
  72. data/test/controller/benchmark_test.rb +36 -0
  73. data/test/controller/caching_filestore.rb +22 -40
  74. data/test/controller/capture_test.rb +10 -1
  75. data/test/controller/cgi_test.rb +145 -23
  76. data/test/controller/components_test.rb +50 -0
  77. data/test/controller/custom_handler_test.rb +3 -3
  78. data/test/controller/fake_controllers.rb +24 -0
  79. data/test/controller/filters_test.rb +6 -6
  80. data/test/controller/flash_test.rb +6 -6
  81. data/test/controller/fragment_store_setting_test.rb +45 -0
  82. data/test/controller/helper_test.rb +1 -3
  83. data/test/controller/new_render_test.rb +119 -7
  84. data/test/controller/redirect_test.rb +11 -1
  85. data/test/controller/render_test.rb +34 -1
  86. data/test/controller/request_test.rb +14 -5
  87. data/test/controller/routing_test.rb +238 -42
  88. data/test/controller/send_file_test.rb +11 -10
  89. data/test/controller/session_management_test.rb +94 -0
  90. data/test/controller/test_test.rb +194 -5
  91. data/test/controller/url_rewriter_test.rb +46 -0
  92. data/test/fixtures/layouts/talk_from_action.rhtml +2 -0
  93. data/test/fixtures/layouts/yield.rhtml +2 -0
  94. data/test/fixtures/multipart/binary_file +0 -0
  95. data/test/fixtures/multipart/large_text_file +10 -0
  96. data/test/fixtures/multipart/mixed_files +0 -0
  97. data/test/fixtures/multipart/single_parameter +5 -0
  98. data/test/fixtures/multipart/text_file +10 -0
  99. data/test/fixtures/test/_customer_greeting.rhtml +1 -0
  100. data/test/fixtures/test/_hash_object.rhtml +1 -0
  101. data/test/fixtures/test/_person.rhtml +2 -0
  102. data/test/fixtures/test/action_talk_to_layout.rhtml +2 -0
  103. data/test/fixtures/test/content_for.rhtml +2 -0
  104. data/test/fixtures/test/potential_conflicts.rhtml +4 -0
  105. data/test/template/active_record_helper_test.rb +15 -8
  106. data/test/template/asset_tag_helper_test.rb +40 -16
  107. data/test/template/compiled_templates_tests.rb +63 -0
  108. data/test/template/date_helper_test.rb +80 -4
  109. data/test/template/form_helper_test.rb +48 -42
  110. data/test/template/form_options_helper_test.rb +40 -40
  111. data/test/template/form_tag_helper_test.rb +21 -15
  112. data/test/template/java_script_macros_helper_test.rb +56 -0
  113. data/test/template/javascript_helper_test.rb +70 -47
  114. data/test/template/number_helper_test.rb +2 -0
  115. data/test/template/tag_helper_test.rb +9 -0
  116. data/test/template/text_helper_test.rb +146 -1
  117. data/test/template/upload_progress_helper_testx.rb +11 -147
  118. data/test/template/url_helper_test.rb +90 -22
  119. data/test/testing_sandbox.rb +26 -0
  120. metadata +37 -7
  121. data/lib/action_controller/auto_complete.rb +0 -47
  122. data/lib/action_controller/deprecated_renders_and_redirects.rb +0 -76
  123. data/lib/action_controller/session.rb +0 -14
@@ -1,30 +1,341 @@
1
1
  // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
- //
3
- // Parts (c) 2005 Justin Palmer (http://encytemedia.com/)
4
- // Parts (c) 2005 Mark Pilgrim (http://diveintomark.org/)
2
+ // Contributors:
3
+ // Justin Palmer (http://encytemedia.com/)
4
+ // Mark Pilgrim (http://diveintomark.org/)
5
+ // Martin Bialasinki
5
6
  //
6
- // Permission is hereby granted, free of charge, to any person obtaining
7
- // a copy of this software and associated documentation files (the
8
- // "Software"), to deal in the Software without restriction, including
9
- // without limitation the rights to use, copy, modify, merge, publish,
10
- // distribute, sublicense, and/or sell copies of the Software, and to
11
- // permit persons to whom the Software is furnished to do so, subject to
12
- // the following conditions:
13
- //
14
- // The above copyright notice and this permission notice shall be
15
- // included in all copies or substantial portions of the Software.
16
- //
17
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
- // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
- // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
- // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7
+ // See scriptaculous.js for full license.
8
+
9
+ Object.debug = function(obj) {
10
+ var info = [];
11
+
12
+ if(typeof obj in ["string","number"]) {
13
+ return obj;
14
+ } else {
15
+ for(property in obj)
16
+ if(typeof obj[property]!="function")
17
+ info.push(property + ' => ' +
18
+ (typeof obj[property] == "string" ?
19
+ '"' + obj[property] + '"' :
20
+ obj[property]));
21
+ }
22
+
23
+ return ("'" + obj + "' #" + typeof obj +
24
+ ": {" + info.join(", ") + "}");
25
+ }
26
+
27
+
28
+ /*--------------------------------------------------------------------------*/
29
+
30
+ var Builder = {
31
+ NODEMAP: {
32
+ AREA: 'map',
33
+ CAPTION: 'table',
34
+ COL: 'table',
35
+ COLGROUP: 'table',
36
+ LEGEND: 'fieldset',
37
+ OPTGROUP: 'select',
38
+ OPTION: 'select',
39
+ PARAM: 'object',
40
+ TBODY: 'table',
41
+ TD: 'table',
42
+ TFOOT: 'table',
43
+ TH: 'table',
44
+ THEAD: 'table',
45
+ TR: 'table'
46
+ },
47
+ // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
48
+ // due to a Firefox bug
49
+ node: function(elementName) {
50
+ elementName = elementName.toUpperCase();
51
+
52
+ // try innerHTML approach
53
+ var parentTag = this.NODEMAP[elementName] || 'div';
54
+ var parentElement = document.createElement(parentTag);
55
+ parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
56
+ var element = parentElement.firstChild || null;
57
+
58
+ // see if browser added wrapping tags
59
+ if(element && (element.tagName != elementName))
60
+ element = element.getElementsByTagName(elementName)[0];
61
+
62
+ // fallback to createElement approach
63
+ if(!element) element = document.createElement(elementName);
64
+
65
+ // abort if nothing could be created
66
+ if(!element) return;
67
+
68
+ // attributes (or text)
69
+ if(arguments[1])
70
+ if(this._isStringOrNumber(arguments[1]) ||
71
+ (arguments[1] instanceof Array)) {
72
+ this._children(element, arguments[1]);
73
+ } else {
74
+ var attrs = this._attributes(arguments[1]);
75
+ if(attrs.length) {
76
+ parentElement.innerHTML = "<" +elementName + " " +
77
+ attrs + "></" + elementName + ">";
78
+ element = parentElement.firstChild || null;
79
+ // workaround firefox 1.0.X bug
80
+ if(!element) {
81
+ element = document.createElement(elementName);
82
+ for(attr in arguments[1])
83
+ element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
84
+ }
85
+ if(element.tagName != elementName)
86
+ element = parentElement.getElementsByTagName(elementName)[0];
87
+ }
88
+ }
89
+
90
+ // text, or array of children
91
+ if(arguments[2])
92
+ this._children(element, arguments[2]);
93
+
94
+ return element;
95
+ },
96
+ _text: function(text) {
97
+ return document.createTextNode(text);
98
+ },
99
+ _attributes: function(attributes) {
100
+ var attrs = [];
101
+ for(attribute in attributes)
102
+ attrs.push((attribute=='className' ? 'class' : attribute) +
103
+ '="' + attributes[attribute].toString().escapeHTML() + '"');
104
+ return attrs.join(" ");
105
+ },
106
+ _children: function(element, children) {
107
+ if(typeof children=='object') { // array can hold nodes and text
108
+ children.flatten().each( function(e) {
109
+ if(typeof e=='object')
110
+ element.appendChild(e)
111
+ else
112
+ if(Builder._isStringOrNumber(e))
113
+ element.appendChild(Builder._text(e));
114
+ });
115
+ } else
116
+ if(Builder._isStringOrNumber(children))
117
+ element.appendChild(Builder._text(children));
118
+ },
119
+ _isStringOrNumber: function(param) {
120
+ return(typeof param=='string' || typeof param=='number');
121
+ }
122
+ }
123
+
124
+ /* ------------- element ext -------------- */
125
+
126
+ // converts rgb() and #xxx to #xxxxxx format,
127
+ // returns self (or first argument) if not convertable
128
+ String.prototype.parseColor = function() {
129
+ color = "#";
130
+ if(this.slice(0,4) == "rgb(") {
131
+ var cols = this.slice(4,this.length-1).split(',');
132
+ var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
133
+ } else {
134
+ if(this.slice(0,1) == '#') {
135
+ if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
136
+ if(this.length==7) color = this.toLowerCase();
137
+ }
138
+ }
139
+ return(color.length==7 ? color : (arguments[0] || this));
140
+ }
141
+
142
+ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
143
+ var children = $(element).childNodes;
144
+ var text = "";
145
+ var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
146
+
147
+ for (var i = 0; i < children.length; i++) {
148
+ if(children[i].nodeType==3) {
149
+ text+=children[i].nodeValue;
150
+ } else {
151
+ if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
152
+ text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
153
+ }
154
+ }
155
+
156
+ return text;
157
+ }
158
+
159
+ Element.setContentZoom = function(element, percent) {
160
+ element = $(element);
161
+ element.style.fontSize = (percent/100) + "em";
162
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
163
+ }
164
+
165
+ Element.getOpacity = function(element){
166
+ var opacity;
167
+ if (opacity = Element.getStyle(element, "opacity"))
168
+ return parseFloat(opacity);
169
+ if (opacity = (Element.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/))
170
+ if(opacity[1]) return parseFloat(opacity[1]) / 100;
171
+ return 1.0;
172
+ }
173
+
174
+ Element.setOpacity = function(element, value){
175
+ element= $(element);
176
+ var els = element.style;
177
+ if (value == 1){
178
+ els.opacity = '0.999999';
179
+ if(/MSIE/.test(navigator.userAgent))
180
+ els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'');
181
+ } else {
182
+ if(value < 0.00001) value = 0;
183
+ els.opacity = value;
184
+ if(/MSIE/.test(navigator.userAgent))
185
+ els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
186
+ "alpha(opacity="+value*100+")";
187
+ }
188
+ }
189
+
190
+ Element.getInlineOpacity = function(element){
191
+ element= $(element);
192
+ var op;
193
+ op = element.style.opacity;
194
+ if (typeof op != "undefined" && op != "") return op;
195
+ return "";
196
+ }
197
+
198
+ Element.setInlineOpacity = function(element, value){
199
+ element= $(element);
200
+ var els = element.style;
201
+ els.opacity = value;
202
+ }
203
+
204
+ /*--------------------------------------------------------------------------*/
205
+
206
+ Element.Class = {
207
+ // Element.toggleClass(element, className) toggles the class being on/off
208
+ // Element.toggleClass(element, className1, className2) toggles between both classes,
209
+ // defaulting to className1 if neither exist
210
+ toggle: function(element, className) {
211
+ if(Element.Class.has(element, className)) {
212
+ Element.Class.remove(element, className);
213
+ if(arguments.length == 3) Element.Class.add(element, arguments[2]);
214
+ } else {
215
+ Element.Class.add(element, className);
216
+ if(arguments.length == 3) Element.Class.remove(element, arguments[2]);
217
+ }
218
+ },
219
+
220
+ // gets space-delimited classnames of an element as an array
221
+ get: function(element) {
222
+ return $(element).className.split(' ');
223
+ },
24
224
 
225
+ // functions adapted from original functions by Gavin Kistner
226
+ remove: function(element) {
227
+ element = $(element);
228
+ var removeClasses = arguments;
229
+ $R(1,arguments.length-1).each( function(index) {
230
+ element.className =
231
+ element.className.split(' ').reject(
232
+ function(klass) { return (klass == removeClasses[index]) } ).join(' ');
233
+ });
234
+ },
25
235
 
26
- Effect = {}
27
- Effect2 = Effect; // deprecated
236
+ add: function(element) {
237
+ element = $(element);
238
+ for(var i = 1; i < arguments.length; i++) {
239
+ Element.Class.remove(element, arguments[i]);
240
+ element.className += (element.className.length > 0 ? ' ' : '') + arguments[i];
241
+ }
242
+ },
243
+
244
+ // returns true if all given classes exist in said element
245
+ has: function(element) {
246
+ element = $(element);
247
+ if(!element || !element.className) return false;
248
+ var regEx;
249
+ for(var i = 1; i < arguments.length; i++) {
250
+ if((typeof arguments[i] == 'object') &&
251
+ (arguments[i].constructor == Array)) {
252
+ for(var j = 0; j < arguments[i].length; j++) {
253
+ regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
254
+ if(!regEx.test(element.className)) return false;
255
+ }
256
+ } else {
257
+ regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
258
+ if(!regEx.test(element.className)) return false;
259
+ }
260
+ }
261
+ return true;
262
+ },
263
+
264
+ // expects arrays of strings and/or strings as optional paramters
265
+ // Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
266
+ has_any: function(element) {
267
+ element = $(element);
268
+ if(!element || !element.className) return false;
269
+ var regEx;
270
+ for(var i = 1; i < arguments.length; i++) {
271
+ if((typeof arguments[i] == 'object') &&
272
+ (arguments[i].constructor == Array)) {
273
+ for(var j = 0; j < arguments[i].length; j++) {
274
+ regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
275
+ if(regEx.test(element.className)) return true;
276
+ }
277
+ } else {
278
+ regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
279
+ if(regEx.test(element.className)) return true;
280
+ }
281
+ }
282
+ return false;
283
+ },
284
+
285
+ childrenWith: function(element, className) {
286
+ var children = $(element).getElementsByTagName('*');
287
+ var elements = new Array();
288
+
289
+ for (var i = 0; i < children.length; i++)
290
+ if (Element.Class.has(children[i], className))
291
+ elements.push(children[i]);
292
+
293
+ return elements;
294
+ }
295
+ }
296
+
297
+ /*--------------------------------------------------------------------------*/
298
+
299
+ var Effect = {
300
+ tagifyText: function(element) {
301
+ var tagifyStyle = "position:relative";
302
+ if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ";zoom:1";
303
+ element = $(element);
304
+ $A(element.childNodes).each( function(child) {
305
+ if(child.nodeType==3) {
306
+ child.nodeValue.toArray().each( function(character) {
307
+ element.insertBefore(
308
+ Builder.node('span',{style: tagifyStyle},
309
+ character == " " ? String.fromCharCode(160) : character),
310
+ child);
311
+ });
312
+ Element.remove(child);
313
+ }
314
+ });
315
+ },
316
+ multiple: function(element, effect) {
317
+ var elements;
318
+ if(((typeof element == 'object') ||
319
+ (typeof element == 'function')) &&
320
+ (element.length))
321
+ elements = element;
322
+ else
323
+ elements = $(element).childNodes;
324
+
325
+ var options = Object.extend({
326
+ speed: 0.1,
327
+ delay: 0.0
328
+ }, arguments[2] || {});
329
+ var speed = options.speed;
330
+ var delay = options.delay;
331
+
332
+ $A(elements).each( function(element, index) {
333
+ new effect(element, Object.extend(options, { delay: delay + index * speed }));
334
+ });
335
+ }
336
+ };
337
+
338
+ var Effect2 = Effect; // deprecated
28
339
 
29
340
  /* ------------- transitions ------------- */
30
341
 
@@ -40,7 +351,7 @@ Effect.Transitions.reverse = function(pos) {
40
351
  return 1-pos;
41
352
  }
42
353
  Effect.Transitions.flicker = function(pos) {
43
- return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25);
354
+ return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
44
355
  }
45
356
  Effect.Transitions.wobble = function(pos) {
46
357
  return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
@@ -56,73 +367,111 @@ Effect.Transitions.full = function(pos) {
56
367
  return 1;
57
368
  }
58
369
 
59
- /* ------------- element ext -------------- */
60
-
61
- Element.makePositioned = function(element) {
62
- element = $(element);
63
- if(element.style.position == "")
64
- element.style.position = "relative";
65
- }
66
-
67
- Element.makeClipping = function(element) {
68
- element = $(element);
69
- element._overflow = element.style.overflow || 'visible';
70
- if(element._overflow!='hidden') element.style.overflow = 'hidden';
71
- }
370
+ /* ------------- core effects ------------- */
72
371
 
73
- Element.undoClipping = function(element) {
74
- element = $(element);
75
- if(element._overflow!='hidden') element.style.overflow = element._overflow;
372
+ Effect.Queue = {
373
+ effects: [],
374
+ interval: null,
375
+ add: function(effect) {
376
+ var timestamp = new Date().getTime();
377
+
378
+ switch(effect.options.queue) {
379
+ case 'front':
380
+ // move unstarted effects after this effect
381
+ this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
382
+ e.startOn += effect.finishOn;
383
+ e.finishOn += effect.finishOn;
384
+ });
385
+ break;
386
+ case 'end':
387
+ // start effect after last queued effect has finished
388
+ timestamp = this.effects.pluck('finishOn').max() || timestamp;
389
+ break;
390
+ }
391
+
392
+ effect.startOn += timestamp;
393
+ effect.finishOn += timestamp;
394
+ this.effects.push(effect);
395
+ if(!this.interval)
396
+ this.interval = setInterval(this.loop.bind(this), 40);
397
+ },
398
+ remove: function(effect) {
399
+ this.effects = this.effects.reject(function(e) { return e==effect });
400
+ if(this.effects.length == 0) {
401
+ clearInterval(this.interval);
402
+ this.interval = null;
403
+ }
404
+ },
405
+ loop: function() {
406
+ var timePos = new Date().getTime();
407
+ this.effects.invoke('loop', timePos);
408
+ }
76
409
  }
77
410
 
78
- /* ------------- core effects ------------- */
79
-
80
411
  Effect.Base = function() {};
81
412
  Effect.Base.prototype = {
413
+ position: null,
82
414
  setOptions: function(options) {
83
415
  this.options = Object.extend({
84
416
  transition: Effect.Transitions.sinoidal,
85
417
  duration: 1.0, // seconds
86
- fps: 25.0, // max. 100fps
418
+ fps: 25.0, // max. 25fps due to Effect.Queue implementation
87
419
  sync: false, // true for combining
88
420
  from: 0.0,
89
- to: 1.0
421
+ to: 1.0,
422
+ delay: 0.0,
423
+ queue: 'parallel'
90
424
  }, options || {});
91
425
  },
92
426
  start: function(options) {
93
427
  this.setOptions(options || {});
94
428
  this.currentFrame = 0;
95
- this.startOn = new Date().getTime();
429
+ this.state = 'idle';
430
+ this.startOn = this.options.delay*1000;
96
431
  this.finishOn = this.startOn + (this.options.duration*1000);
97
- if(this.options.beforeStart) this.options.beforeStart(this);
98
- if(!this.options.sync) this.loop();
432
+ this.event('beforeStart');
433
+ if(!this.options.sync) Effect.Queue.add(this);
99
434
  },
100
- loop: function() {
101
- var timePos = new Date().getTime();
102
- if(timePos >= this.finishOn) {
103
- this.render(this.options.to);
104
- if(this.finish) this.finish();
105
- if(this.options.afterFinish) this.options.afterFinish(this);
106
- return;
107
- }
108
- var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
109
- var frame = Math.round(pos * this.options.fps * this.options.duration);
110
- if(frame > this.currentFrame) {
111
- this.render(pos);
112
- this.currentFrame = frame;
435
+ loop: function(timePos) {
436
+ if(timePos >= this.startOn) {
437
+ if(timePos >= this.finishOn) {
438
+ this.render(1.0);
439
+ this.cancel();
440
+ this.event('beforeFinish');
441
+ if(this.finish) this.finish();
442
+ this.event('afterFinish');
443
+ return;
444
+ }
445
+ var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
446
+ var frame = Math.round(pos * this.options.fps * this.options.duration);
447
+ if(frame > this.currentFrame) {
448
+ this.render(pos);
449
+ this.currentFrame = frame;
450
+ }
113
451
  }
114
- this.timeout = setTimeout(this.loop.bind(this), 10);
115
452
  },
116
453
  render: function(pos) {
454
+ if(this.state == 'idle') {
455
+ this.state = 'running';
456
+ this.event('beforeSetup');
457
+ if(this.setup) this.setup();
458
+ this.event('afterSetup');
459
+ }
117
460
  if(this.options.transition) pos = this.options.transition(pos);
118
461
  pos *= (this.options.to-this.options.from);
119
- pos += this.options.from;
120
- if(this.options.beforeUpdate) this.options.beforeUpdate(this);
462
+ pos += this.options.from;
463
+ this.position = pos;
464
+ this.event('beforeUpdate');
121
465
  if(this.update) this.update(pos);
122
- if(this.options.afterUpdate) this.options.afterUpdate(this);
466
+ this.event('afterUpdate');
123
467
  },
124
468
  cancel: function() {
125
- if(this.timeout) clearTimeout(this.timeout);
469
+ if(!this.options.sync) Effect.Queue.remove(this);
470
+ this.state = 'finished';
471
+ },
472
+ event: function(eventName) {
473
+ if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
474
+ if(this.options[eventName]) this.options[eventName](this);
126
475
  }
127
476
  }
128
477
 
@@ -133,35 +482,34 @@ Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
133
482
  this.start(arguments[1]);
134
483
  },
135
484
  update: function(position) {
136
- for (var i = 0; i < this.effects.length; i++)
137
- this.effects[i].render(position);
485
+ this.effects.invoke('render', position);
138
486
  },
139
487
  finish: function(position) {
140
- for (var i = 0; i < this.effects.length; i++)
141
- if(this.effects[i].finish) this.effects[i].finish(position);
488
+ this.effects.each( function(effect) {
489
+ effect.render(1.0);
490
+ effect.cancel();
491
+ effect.event('beforeFinish');
492
+ if(effect.finish) effect.finish(position);
493
+ effect.event('afterFinish');
494
+ });
142
495
  }
143
496
  });
144
497
 
145
- // Internet Explorer caveat: works only on elements the have
146
- // a 'layout', meaning having a given width or height.
147
- // There is no way to safely set this automatically.
148
498
  Effect.Opacity = Class.create();
149
499
  Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
150
500
  initialize: function(element) {
151
501
  this.element = $(element);
152
- options = Object.extend({
153
- from: 0.0,
502
+ // make this work on IE on elements without 'layout'
503
+ if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
504
+ this.element.style.zoom = 1;
505
+ var options = Object.extend({
506
+ from: Element.getOpacity(this.element) || 0.0,
154
507
  to: 1.0
155
508
  }, arguments[1] || {});
156
509
  this.start(options);
157
510
  },
158
511
  update: function(position) {
159
- this.setOpacity(position);
160
- },
161
- setOpacity: function(opacity) {
162
- opacity = (opacity == 1) ? 0.99999 : opacity;
163
- this.element.style.opacity = opacity;
164
- this.element.style.filter = "alpha(opacity:"+opacity*100+")";
512
+ Element.setOpacity(this.element, position);
165
513
  }
166
514
  });
167
515
 
@@ -169,16 +517,23 @@ Effect.MoveBy = Class.create();
169
517
  Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
170
518
  initialize: function(element, toTop, toLeft) {
171
519
  this.element = $(element);
172
- this.originalTop = parseFloat(this.element.style.top || '0');
173
- this.originalLeft = parseFloat(this.element.style.left || '0');
174
520
  this.toTop = toTop;
175
521
  this.toLeft = toLeft;
176
- Element.makePositioned(this.element);
177
522
  this.start(arguments[3]);
178
523
  },
524
+ setup: function() {
525
+ // Bug in Opera: Opera returns the "real" position of a static element or
526
+ // relative element that does not have top/left explicitly set.
527
+ // ==> Always set top and left for position relative elements in your stylesheets
528
+ // (to 0 if you do not need them)
529
+
530
+ Element.makePositioned(this.element);
531
+ this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0');
532
+ this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
533
+ },
179
534
  update: function(position) {
180
- topd = this.toTop * position + this.originalTop;
181
- leftd = this.toLeft * position + this.originalLeft;
535
+ var topd = this.toTop * position + this.originalTop;
536
+ var leftd = this.toLeft * position + this.originalLeft;
182
537
  this.setPosition(topd, leftd);
183
538
  },
184
539
  setPosition: function(topd, leftd) {
@@ -191,55 +546,77 @@ Effect.Scale = Class.create();
191
546
  Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
192
547
  initialize: function(element, percent) {
193
548
  this.element = $(element)
194
- options = Object.extend({
549
+ var options = Object.extend({
195
550
  scaleX: true,
196
551
  scaleY: true,
197
552
  scaleContent: true,
198
553
  scaleFromCenter: false,
199
554
  scaleMode: 'box', // 'box' or 'contents' or {} with provided values
200
- scaleFrom: 100.0
555
+ scaleFrom: 100.0,
556
+ scaleTo: percent
201
557
  }, arguments[2] || {});
202
- this.originalTop = this.element.offsetTop;
203
- this.originalLeft = this.element.offsetLeft;
204
- if(this.element.style.fontSize=="") this.sizeEm = 1.0;
205
- if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0)
206
- this.sizeEm = parseFloat(this.element.style.fontSize);
207
- this.factor = (percent/100.0) - (options.scaleFrom/100.0);
208
- if(options.scaleMode=='box') {
209
- this.originalHeight = this.element.clientHeight;
210
- this.originalWidth = this.element.clientWidth;
211
- } else
212
- if(options.scaleMode=='contents') {
213
- this.originalHeight = this.element.scrollHeight;
214
- this.originalWidth = this.element.scrollWidth;
215
- } else {
216
- this.originalHeight = options.scaleMode.originalHeight;
217
- this.originalWidth = options.scaleMode.originalWidth;
218
- }
219
558
  this.start(options);
220
559
  },
221
-
560
+ setup: function() {
561
+ var effect = this;
562
+
563
+ this.restoreAfterFinish = this.options.restoreAfterFinish || false;
564
+ this.elementPositioning = Element.getStyle(this.element,'position');
565
+
566
+ effect.originalStyle = {};
567
+ ['top','left','width','height','fontSize'].each( function(k) {
568
+ effect.originalStyle[k] = effect.element.style[k];
569
+ });
570
+
571
+ this.originalTop = this.element.offsetTop;
572
+ this.originalLeft = this.element.offsetLeft;
573
+
574
+ var fontSize = Element.getStyle(this.element,'font-size') || "100%";
575
+ ['em','px','%'].each( function(fontSizeType) {
576
+ if(fontSize.indexOf(fontSizeType)>0) {
577
+ effect.fontSize = parseFloat(fontSize);
578
+ effect.fontSizeType = fontSizeType;
579
+ }
580
+ });
581
+
582
+ this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
583
+
584
+ this.dims = null;
585
+ if(this.options.scaleMode=='box')
586
+ this.dims = [this.element.clientHeight, this.element.clientWidth];
587
+ if(this.options.scaleMode=='content')
588
+ this.dims = [this.element.scrollHeight, this.element.scrollWidth];
589
+ if(!this.dims)
590
+ this.dims = [this.options.scaleMode.originalHeight,
591
+ this.options.scaleMode.originalWidth];
592
+ },
222
593
  update: function(position) {
223
- currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
224
- if(this.options.scaleContent && this.sizeEm)
225
- this.element.style.fontSize = this.sizeEm*currentScale + "em";
226
- this.setDimensions(
227
- this.originalWidth * currentScale,
228
- this.originalHeight * currentScale);
594
+ var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
595
+ if(this.options.scaleContent && this.fontSize)
596
+ this.element.style.fontSize = this.fontSize*currentScale + this.fontSizeType;
597
+ this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
229
598
  },
230
-
231
- setDimensions: function(width, height) {
232
- if(this.options.scaleX) this.element.style.width = width + 'px';
233
- if(this.options.scaleY) this.element.style.height = height + 'px';
599
+ finish: function(position) {
600
+ if (this.restoreAfterFinish) {
601
+ var effect = this;
602
+ ['top','left','width','height','fontSize'].each( function(k) {
603
+ effect.element.style[k] = effect.originalStyle[k];
604
+ });
605
+ }
606
+ },
607
+ setDimensions: function(height, width) {
608
+ var els = this.element.style;
609
+ if(this.options.scaleX) els.width = width + 'px';
610
+ if(this.options.scaleY) els.height = height + 'px';
234
611
  if(this.options.scaleFromCenter) {
235
- topd = (height - this.originalHeight)/2;
236
- leftd = (width - this.originalWidth)/2;
237
- if(this.element.style.position=='absolute') {
238
- if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px";
239
- if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px";
612
+ var topd = (height - this.dims[0])/2;
613
+ var leftd = (width - this.dims[1])/2;
614
+ if(this.elementPositioning == 'absolute') {
615
+ if(this.options.scaleY) els.top = this.originalTop-topd + "px";
616
+ if(this.options.scaleX) els.left = this.originalLeft-leftd + "px";
240
617
  } else {
241
- if(this.options.scaleY) this.element.style.top = -topd + "px";
242
- if(this.options.scaleX) this.element.style.left = -leftd + "px";
618
+ if(this.options.scaleY) els.top = -topd + "px";
619
+ if(this.options.scaleX) els.left = -leftd + "px";
243
620
  }
244
621
  }
245
622
  }
@@ -249,44 +626,39 @@ Effect.Highlight = Class.create();
249
626
  Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
250
627
  initialize: function(element) {
251
628
  this.element = $(element);
252
-
253
- // try to parse current background color as default for endcolor
254
- // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format
255
- var endcolor = "#ffffff";
256
- var current = this.element.style.backgroundColor;
257
- if(current && current.slice(0,4) == "rgb(") {
258
- endcolor = "#";
259
- var cols = current.slice(4,current.length-1).split(',');
260
- var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); }
261
-
262
629
  var options = Object.extend({
263
- startcolor: "#ffff99",
264
- endcolor: endcolor,
265
- restorecolor: current
630
+ startcolor: "#ffff99"
266
631
  }, arguments[1] || {});
267
-
632
+ this.start(options);
633
+ },
634
+ setup: function() {
635
+ // Disable background image during the effect
636
+ this.oldBgImage = this.element.style.backgroundImage;
637
+ this.element.style.backgroundImage = "none";
638
+ if(!this.options.endcolor)
639
+ this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
640
+ if (typeof this.options.restorecolor == "undefined")
641
+ this.options.restorecolor = this.element.style.backgroundColor;
268
642
  // init color calculations
269
643
  this.colors_base = [
270
- parseInt(options.startcolor.slice(1,3),16),
271
- parseInt(options.startcolor.slice(3,5),16),
272
- parseInt(options.startcolor.slice(5),16) ];
644
+ parseInt(this.options.startcolor.slice(1,3),16),
645
+ parseInt(this.options.startcolor.slice(3,5),16),
646
+ parseInt(this.options.startcolor.slice(5),16) ];
273
647
  this.colors_delta = [
274
- parseInt(options.endcolor.slice(1,3),16)-this.colors_base[0],
275
- parseInt(options.endcolor.slice(3,5),16)-this.colors_base[1],
276
- parseInt(options.endcolor.slice(5),16)-this.colors_base[2] ];
277
-
278
- this.start(options);
648
+ parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0],
649
+ parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1],
650
+ parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]];
279
651
  },
280
652
  update: function(position) {
281
- var colors = [
282
- Math.round(this.colors_base[0]+(this.colors_delta[0]*position)),
283
- Math.round(this.colors_base[1]+(this.colors_delta[1]*position)),
284
- Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ];
653
+ var effect = this; var colors = $R(0,2).map( function(i){
654
+ return Math.round(effect.colors_base[i]+(effect.colors_delta[i]*position))
655
+ });
285
656
  this.element.style.backgroundColor = "#" +
286
657
  colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart();
287
658
  },
288
659
  finish: function() {
289
660
  this.element.style.backgroundColor = this.options.restorecolor;
661
+ this.element.style.backgroundImage = this.oldBgImage;
290
662
  }
291
663
  });
292
664
 
@@ -294,6 +666,9 @@ Effect.ScrollTo = Class.create();
294
666
  Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
295
667
  initialize: function(element) {
296
668
  this.element = $(element);
669
+ this.start(arguments[1] || {});
670
+ },
671
+ setup: function() {
297
672
  Position.prepare();
298
673
  var offsets = Position.cumulativeOffset(this.element);
299
674
  var max = window.innerHeight ?
@@ -302,8 +677,7 @@ Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
302
677
  (document.documentElement.clientHeight ?
303
678
  document.documentElement.clientHeight : document.body.clientHeight);
304
679
  this.scrollStart = Position.deltaY;
305
- this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
306
- this.start(arguments[1] || {});
680
+ this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
307
681
  },
308
682
  update: function(position) {
309
683
  Position.prepare();
@@ -312,51 +686,61 @@ Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
312
686
  }
313
687
  });
314
688
 
315
- /* ------------- prepackaged effects ------------- */
689
+ /* ------------- combination effects ------------- */
316
690
 
317
691
  Effect.Fade = function(element) {
318
- options = Object.extend({
319
- from: 1.0,
692
+ var oldOpacity = Element.getInlineOpacity(element);
693
+ var options = Object.extend({
694
+ from: Element.getOpacity(element) || 1.0,
320
695
  to: 0.0,
321
- afterFinish: function(effect)
322
- { Element.hide(effect.element);
323
- effect.setOpacity(1); }
696
+ afterFinishInternal: function(effect)
697
+ { if (effect.options.to == 0) {
698
+ Element.hide(effect.element);
699
+ Element.setInlineOpacity(effect.element, oldOpacity);
700
+ }
701
+ }
324
702
  }, arguments[1] || {});
325
- new Effect.Opacity(element,options);
703
+ return new Effect.Opacity(element,options);
326
704
  }
327
705
 
328
706
  Effect.Appear = function(element) {
329
- options = Object.extend({
330
- from: 0.0,
707
+ var options = Object.extend({
708
+ from: (Element.getStyle(element, "display") == "none" ? 0.0 : Element.getOpacity(element) || 0.0),
331
709
  to: 1.0,
332
- beforeStart: function(effect)
333
- { effect.setOpacity(0);
334
- Element.show(effect.element); },
335
- afterUpdate: function(effect)
336
- { Element.show(effect.element); }
710
+ beforeSetup: function(effect)
711
+ { Element.setOpacity(effect.element, effect.options.from);
712
+ Element.show(effect.element); }
337
713
  }, arguments[1] || {});
338
- new Effect.Opacity(element,options);
714
+ return new Effect.Opacity(element,options);
339
715
  }
340
716
 
341
717
  Effect.Puff = function(element) {
342
- new Effect.Parallel(
343
- [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }),
344
- new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],
345
- { duration: 1.0,
346
- afterUpdate: function(effect)
718
+ element = $(element);
719
+ var oldOpacity = Element.getInlineOpacity(element);
720
+ var oldPosition = element.style.position;
721
+ return new Effect.Parallel(
722
+ [ new Effect.Scale(element, 200,
723
+ { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
724
+ new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
725
+ Object.extend({ duration: 1.0,
726
+ beforeSetupInternal: function(effect)
347
727
  { effect.effects[0].element.style.position = 'absolute'; },
348
- afterFinish: function(effect)
349
- { Element.hide(effect.effects[0].element); }
350
- }
728
+ afterFinishInternal: function(effect)
729
+ { Element.hide(effect.effects[0].element);
730
+ effect.effects[0].element.style.position = oldPosition;
731
+ Element.setInlineOpacity(effect.effects[0].element, oldOpacity); }
732
+ }, arguments[1] || {})
351
733
  );
352
734
  }
353
735
 
354
736
  Effect.BlindUp = function(element) {
737
+ element = $(element);
355
738
  Element.makeClipping(element);
356
- new Effect.Scale(element, 0,
739
+ return new Effect.Scale(element, 0,
357
740
  Object.extend({ scaleContent: false,
358
741
  scaleX: false,
359
- afterFinish: function(effect)
742
+ restoreAfterFinish: true,
743
+ afterFinishInternal: function(effect)
360
744
  {
361
745
  Element.hide(effect.element);
362
746
  Element.undoClipping(effect.element);
@@ -366,120 +750,179 @@ Effect.BlindUp = function(element) {
366
750
  }
367
751
 
368
752
  Effect.BlindDown = function(element) {
369
- $(element).style.height = '0px';
370
- Element.makeClipping(element);
371
- Element.show(element);
372
- new Effect.Scale(element, 100,
753
+ element = $(element);
754
+ var oldHeight = element.style.height;
755
+ var elementDimensions = Element.getDimensions(element);
756
+ return new Effect.Scale(element, 100,
373
757
  Object.extend({ scaleContent: false,
374
- scaleX: false,
375
- scaleMode: 'contents',
758
+ scaleX: false,
376
759
  scaleFrom: 0,
377
- afterFinish: function(effect) {
760
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
761
+ restoreAfterFinish: true,
762
+ afterSetup: function(effect) {
763
+ Element.makeClipping(effect.element);
764
+ effect.element.style.height = "0px";
765
+ Element.show(effect.element);
766
+ },
767
+ afterFinishInternal: function(effect) {
378
768
  Element.undoClipping(effect.element);
769
+ effect.element.style.height = oldHeight;
379
770
  }
380
771
  }, arguments[1] || {})
381
772
  );
382
773
  }
383
774
 
384
775
  Effect.SwitchOff = function(element) {
385
- new Effect.Appear(element,
386
- { duration: 0.4,
387
- transition: Effect.Transitions.flicker,
388
- afterFinish: function(effect)
389
- { effect.element.style.overflow = 'hidden';
390
- new Effect.Scale(effect.element, 1,
391
- { duration: 0.3, scaleFromCenter: true,
392
- scaleX: false, scaleContent: false,
393
- afterUpdate: function(effect) {
394
- if(effect.element.style.position=="")
395
- effect.element.style.position = 'relative'; },
396
- afterFinish: function(effect) { Element.hide(effect.element); }
397
- } )
398
- }
399
- } );
776
+ element = $(element);
777
+ var oldOpacity = Element.getInlineOpacity(element);
778
+ return new Effect.Appear(element, {
779
+ duration: 0.4,
780
+ from: 0,
781
+ transition: Effect.Transitions.flicker,
782
+ afterFinishInternal: function(effect) {
783
+ new Effect.Scale(effect.element, 1, {
784
+ duration: 0.3, scaleFromCenter: true,
785
+ scaleX: false, scaleContent: false, restoreAfterFinish: true,
786
+ beforeSetup: function(effect) {
787
+ Element.makePositioned(effect.element);
788
+ Element.makeClipping(effect.element);
789
+ },
790
+ afterFinishInternal: function(effect) {
791
+ Element.hide(effect.element);
792
+ Element.undoClipping(effect.element);
793
+ Element.undoPositioned(effect.element);
794
+ Element.setInlineOpacity(effect.element, oldOpacity);
795
+ }
796
+ })
797
+ }
798
+ });
400
799
  }
401
800
 
402
801
  Effect.DropOut = function(element) {
403
- new Effect.Parallel(
802
+ element = $(element);
803
+ var oldTop = element.style.top;
804
+ var oldLeft = element.style.left;
805
+ var oldOpacity = Element.getInlineOpacity(element);
806
+ return new Effect.Parallel(
404
807
  [ new Effect.MoveBy(element, 100, 0, { sync: true }),
405
- new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],
406
- { duration: 0.5,
407
- afterFinish: function(effect)
408
- { Element.hide(effect.effects[0].element); }
409
- });
808
+ new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
809
+ Object.extend(
810
+ { duration: 0.5,
811
+ beforeSetup: function(effect) {
812
+ Element.makePositioned(effect.effects[0].element); },
813
+ afterFinishInternal: function(effect) {
814
+ Element.hide(effect.effects[0].element);
815
+ Element.undoPositioned(effect.effects[0].element);
816
+ effect.effects[0].element.style.left = oldLeft;
817
+ effect.effects[0].element.style.top = oldTop;
818
+ Element.setInlineOpacity(effect.effects[0].element, oldOpacity); }
819
+ }, arguments[1] || {}));
410
820
  }
411
821
 
412
822
  Effect.Shake = function(element) {
413
- new Effect.MoveBy(element, 0, 20,
414
- { duration: 0.05, afterFinish: function(effect) {
823
+ element = $(element);
824
+ var oldTop = element.style.top;
825
+ var oldLeft = element.style.left;
826
+ return new Effect.MoveBy(element, 0, 20,
827
+ { duration: 0.05, afterFinishInternal: function(effect) {
415
828
  new Effect.MoveBy(effect.element, 0, -40,
416
- { duration: 0.1, afterFinish: function(effect) {
829
+ { duration: 0.1, afterFinishInternal: function(effect) {
417
830
  new Effect.MoveBy(effect.element, 0, 40,
418
- { duration: 0.1, afterFinish: function(effect) {
831
+ { duration: 0.1, afterFinishInternal: function(effect) {
419
832
  new Effect.MoveBy(effect.element, 0, -40,
420
- { duration: 0.1, afterFinish: function(effect) {
833
+ { duration: 0.1, afterFinishInternal: function(effect) {
421
834
  new Effect.MoveBy(effect.element, 0, 40,
422
- { duration: 0.1, afterFinish: function(effect) {
835
+ { duration: 0.1, afterFinishInternal: function(effect) {
423
836
  new Effect.MoveBy(effect.element, 0, -20,
424
- { duration: 0.05, afterFinish: function(effect) {
837
+ { duration: 0.05, afterFinishInternal: function(effect) {
838
+ Element.undoPositioned(effect.element);
839
+ effect.element.style.left = oldLeft;
840
+ effect.element.style.top = oldTop;
425
841
  }}) }}) }}) }}) }}) }});
426
842
  }
427
843
 
428
844
  Effect.SlideDown = function(element) {
429
845
  element = $(element);
430
- element.style.height = '0px';
431
- Element.makeClipping(element);
432
846
  Element.cleanWhitespace(element);
433
- Element.makePositioned(element.firstChild);
434
- Element.show(element);
435
- new Effect.Scale(element, 100,
847
+ // SlideDown need to have the content of the element wrapped in a container element with fixed height!
848
+ var oldInnerBottom = element.firstChild.style.bottom;
849
+ var elementDimensions = Element.getDimensions(element);
850
+ return new Effect.Scale(element, 100,
436
851
  Object.extend({ scaleContent: false,
437
852
  scaleX: false,
438
- scaleMode: 'contents',
439
853
  scaleFrom: 0,
440
- afterUpdate: function(effect)
441
- { effect.element.firstChild.style.bottom =
442
- (effect.originalHeight - effect.element.clientHeight) + 'px'; },
443
- afterFinish: function(effect)
444
- { Element.undoClipping(effect.element); }
854
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
855
+ restoreAfterFinish: true,
856
+ afterSetup: function(effect) {
857
+ Element.makePositioned(effect.element.firstChild);
858
+ if (window.opera) effect.element.firstChild.style.top = "";
859
+ Element.makeClipping(effect.element);
860
+ element.style.height = '0';
861
+ Element.show(element);
862
+ },
863
+ afterUpdateInternal: function(effect) {
864
+ effect.element.firstChild.style.bottom =
865
+ (effect.originalHeight - effect.element.clientHeight) + 'px'; },
866
+ afterFinishInternal: function(effect) {
867
+ Element.undoClipping(effect.element);
868
+ Element.undoPositioned(effect.element.firstChild);
869
+ effect.element.firstChild.style.bottom = oldInnerBottom; }
445
870
  }, arguments[1] || {})
446
871
  );
447
872
  }
448
873
 
449
874
  Effect.SlideUp = function(element) {
450
875
  element = $(element);
451
- Element.makeClipping(element);
452
876
  Element.cleanWhitespace(element);
453
- Element.makePositioned(element.firstChild);
454
- Element.show(element);
455
- new Effect.Scale(element, 0,
877
+ var oldInnerBottom = element.firstChild.style.bottom;
878
+ return new Effect.Scale(element, 0,
456
879
  Object.extend({ scaleContent: false,
457
880
  scaleX: false,
458
- afterUpdate: function(effect)
459
- { effect.element.firstChild.style.bottom =
460
- (effect.originalHeight - effect.element.clientHeight) + 'px'; },
461
- afterFinish: function(effect)
462
- {
881
+ scaleMode: 'box',
882
+ scaleFrom: 100,
883
+ restoreAfterFinish: true,
884
+ beforeStartInternal: function(effect) {
885
+ Element.makePositioned(effect.element.firstChild);
886
+ if (window.opera) effect.element.firstChild.style.top = "";
887
+ Element.makeClipping(effect.element);
888
+ Element.show(element);
889
+ },
890
+ afterUpdateInternal: function(effect) {
891
+ effect.element.firstChild.style.bottom =
892
+ (effect.originalHeight - effect.element.clientHeight) + 'px'; },
893
+ afterFinishInternal: function(effect) {
463
894
  Element.hide(effect.element);
464
- Element.undoClipping(effect.element);
465
- }
895
+ Element.undoClipping(effect.element);
896
+ Element.undoPositioned(effect.element.firstChild);
897
+ effect.element.firstChild.style.bottom = oldInnerBottom; }
466
898
  }, arguments[1] || {})
467
899
  );
468
900
  }
469
901
 
470
902
  Effect.Squish = function(element) {
471
- new Effect.Scale(element, 0,
472
- { afterFinish: function(effect) { Element.hide(effect.element); } });
903
+ // Bug in opera makes the TD containing this element expand for a instance after finish
904
+ return new Effect.Scale(element, window.opera ? 1 : 0,
905
+ { restoreAfterFinish: true,
906
+ beforeSetup: function(effect) {
907
+ Element.makeClipping(effect.element); },
908
+ afterFinishInternal: function(effect) {
909
+ Element.hide(effect.element);
910
+ Element.undoClipping(effect.element); }
911
+ });
473
912
  }
474
913
 
475
914
  Effect.Grow = function(element) {
476
915
  element = $(element);
477
916
  var options = arguments[1] || {};
478
917
 
479
- var originalWidth = element.clientWidth;
480
- var originalHeight = element.clientHeight;
481
- element.style.overflow = 'hidden';
482
- Element.show(element);
918
+ var elementDimensions = Element.getDimensions(element);
919
+ var originalWidth = elementDimensions.width;
920
+ var originalHeight = elementDimensions.height;
921
+ var oldTop = element.style.top;
922
+ var oldLeft = element.style.left;
923
+ var oldHeight = element.style.height;
924
+ var oldWidth = element.style.width;
925
+ var oldOpacity = Element.getInlineOpacity(element);
483
926
 
484
927
  var direction = options.direction || 'center';
485
928
  var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
@@ -517,18 +960,40 @@ Effect.Grow = function(element) {
517
960
  break;
518
961
  }
519
962
 
520
- new Effect.MoveBy(element, initialMoveY, initialMoveX, {
963
+ return new Effect.MoveBy(element, initialMoveY, initialMoveX, {
521
964
  duration: 0.01,
522
- beforeUpdate: function(effect) { $(element).style.height = '0px'; },
523
- afterFinish: function(effect) {
965
+ beforeSetup: function(effect) {
966
+ Element.hide(effect.element);
967
+ Element.makeClipping(effect.element);
968
+ Element.makePositioned(effect.element);
969
+ },
970
+ afterFinishInternal: function(effect) {
524
971
  new Effect.Parallel(
525
- [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
526
- new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }),
527
- new Effect.Scale(element, 100, {
972
+ [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
973
+ new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: moveTransition }),
974
+ new Effect.Scale(effect.element, 100, {
528
975
  scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth },
529
- sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })],
530
- options); }
531
- });
976
+ sync: true, scaleFrom: window.opera ? 1 : 0, transition: scaleTransition, restoreAfterFinish: true})
977
+ ], Object.extend({
978
+ beforeSetup: function(effect) {
979
+ effect.effects[0].element.style.height = 0;
980
+ Element.show(effect.effects[0].element);
981
+ },
982
+ afterFinishInternal: function(effect) {
983
+ var el = effect.effects[0].element;
984
+ var els = el.style;
985
+ Element.undoClipping(el);
986
+ Element.undoPositioned(el);
987
+ els.top = oldTop;
988
+ els.left = oldLeft;
989
+ els.height = oldHeight;
990
+ els.width = originalWidth;
991
+ Element.setInlineOpacity(el, oldOpacity);
992
+ }
993
+ }, options)
994
+ )
995
+ }
996
+ });
532
997
  }
533
998
 
534
999
  Effect.Shrink = function(element) {
@@ -537,8 +1002,11 @@ Effect.Shrink = function(element) {
537
1002
 
538
1003
  var originalWidth = element.clientWidth;
539
1004
  var originalHeight = element.clientHeight;
540
- element.style.overflow = 'hidden';
541
- Element.show(element);
1005
+ var oldTop = element.style.top;
1006
+ var oldLeft = element.style.left;
1007
+ var oldHeight = element.style.height;
1008
+ var oldWidth = element.style.width;
1009
+ var oldOpacity = Element.getInlineOpacity(element);
542
1010
 
543
1011
  var direction = options.direction || 'center';
544
1012
  var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
@@ -569,44 +1037,65 @@ Effect.Shrink = function(element) {
569
1037
  break;
570
1038
  }
571
1039
 
572
- new Effect.Parallel(
1040
+ return new Effect.Parallel(
573
1041
  [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }),
574
- new Effect.Scale(element, 0, { sync: true, transition: moveTransition }),
575
- new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ],
576
- options);
1042
+ new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: scaleTransition, restoreAfterFinish: true}),
1043
+ new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition })
1044
+ ], Object.extend({
1045
+ beforeStartInternal: function(effect) {
1046
+ Element.makePositioned(effect.effects[0].element);
1047
+ Element.makeClipping(effect.effects[0].element);
1048
+ },
1049
+ afterFinishInternal: function(effect) {
1050
+ var el = effect.effects[0].element;
1051
+ var els = el.style;
1052
+ Element.hide(el);
1053
+ Element.undoClipping(el);
1054
+ Element.undoPositioned(el);
1055
+ els.top = oldTop;
1056
+ els.left = oldLeft;
1057
+ els.height = oldHeight;
1058
+ els.width = oldWidth;
1059
+ Element.setInlineOpacity(el, oldOpacity);
1060
+ }
1061
+ }, options)
1062
+ );
577
1063
  }
578
1064
 
579
1065
  Effect.Pulsate = function(element) {
1066
+ element = $(element);
580
1067
  var options = arguments[1] || {};
1068
+ var oldOpacity = Element.getInlineOpacity(element);
581
1069
  var transition = options.transition || Effect.Transitions.sinoidal;
582
1070
  var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
583
1071
  reverser.bind(transition);
584
- new Effect.Opacity(element,
585
- Object.extend(Object.extend({ duration: 3.0,
586
- afterFinish: function(effect) { Element.show(effect.element); }
1072
+ return new Effect.Opacity(element,
1073
+ Object.extend(Object.extend({ duration: 3.0, from: 0,
1074
+ afterFinishInternal: function(effect) { Element.setInlineOpacity(effect.element, oldOpacity); }
587
1075
  }, options), {transition: reverser}));
588
1076
  }
589
1077
 
590
1078
  Effect.Fold = function(element) {
591
- $(element).style.overflow = 'hidden';
592
- new Effect.Scale(element, 5, Object.extend({
593
- scaleContent: false,
594
- scaleTo: 100,
595
- scaleX: false,
596
- afterFinish: function(effect) {
597
- new Effect.Scale(element, 1, {
598
- scaleContent: false,
599
- scaleTo: 0,
600
- scaleY: false,
601
- afterFinish: function(effect) { Element.hide(effect.element) } });
602
- }}, arguments[1] || {}));
603
- }
604
-
605
- // old: new Effect.ContentZoom(element, percent)
606
- // new: Element.setContentZoom(element, percent)
607
-
608
- Element.setContentZoom = function(element, percent) {
609
- var element = $(element);
610
- element.style.fontSize = (percent/100) + "em";
611
- if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
1079
+ element = $(element);
1080
+ var originalTop = element.style.top;
1081
+ var originalLeft = element.style.left;
1082
+ var originalWidth = element.style.width;
1083
+ var originalHeight = element.style.height;
1084
+ Element.makeClipping(element);
1085
+ return new Effect.Scale(element, 5, Object.extend({
1086
+ scaleContent: false,
1087
+ scaleX: false,
1088
+ afterFinishInternal: function(effect) {
1089
+ new Effect.Scale(element, 1, {
1090
+ scaleContent: false,
1091
+ scaleY: false,
1092
+ afterFinishInternal: function(effect) {
1093
+ Element.hide(effect.element);
1094
+ Element.undoClipping(effect.element);
1095
+ effect.element.style.top = originalTop;
1096
+ effect.element.style.left = originalLeft;
1097
+ effect.element.style.width = originalWidth;
1098
+ effect.element.style.height = originalHeight;
1099
+ } });
1100
+ }}, arguments[1] || {}));
612
1101
  }