actionpack 1.7.0 → 1.8.0

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 (65) hide show
  1. data/CHANGELOG +109 -0
  2. data/README +2 -2
  3. data/RUNNING_UNIT_TESTS +1 -1
  4. data/install.rb +8 -77
  5. data/lib/action_controller/assertions.rb +203 -0
  6. data/lib/action_controller/base.rb +15 -7
  7. data/lib/action_controller/benchmarking.rb +10 -4
  8. data/lib/action_controller/caching.rb +28 -17
  9. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +5 -9
  10. data/lib/action_controller/cgi_process.rb +5 -1
  11. data/lib/action_controller/cookies.rb +3 -2
  12. data/lib/action_controller/deprecated_assertions.rb +204 -0
  13. data/lib/action_controller/flash.rb +30 -36
  14. data/lib/action_controller/pagination.rb +4 -4
  15. data/lib/action_controller/request.rb +18 -2
  16. data/lib/action_controller/routing.rb +6 -2
  17. data/lib/action_controller/scaffolding.rb +1 -1
  18. data/lib/action_controller/templates/rescues/diagnostics.rhtml +1 -1
  19. data/lib/action_controller/templates/rescues/routing_error.rhtml +4 -2
  20. data/lib/action_controller/templates/rescues/template_error.rhtml +5 -4
  21. data/lib/action_controller/templates/scaffolds/list.rhtml +3 -0
  22. data/lib/action_controller/test_process.rb +60 -17
  23. data/lib/action_controller/url_rewriter.rb +3 -3
  24. data/lib/action_controller/vendor/html-scanner/html/document.rb +63 -0
  25. data/lib/action_controller/vendor/html-scanner/html/node.rb +431 -0
  26. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +95 -0
  27. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  28. data/lib/action_controller/verification.rb +13 -4
  29. data/lib/action_view/base.rb +3 -2
  30. data/lib/action_view/helpers/active_record_helper.rb +1 -1
  31. data/lib/action_view/helpers/asset_tag_helper.rb +31 -9
  32. data/lib/action_view/helpers/date_helper.rb +25 -22
  33. data/lib/action_view/helpers/form_helper.rb +6 -5
  34. data/lib/action_view/helpers/form_options_helper.rb +4 -4
  35. data/lib/action_view/helpers/javascript_helper.rb +28 -6
  36. data/lib/action_view/helpers/javascripts/prototype.js +340 -30
  37. data/lib/action_view/helpers/number_helper.rb +110 -0
  38. data/lib/action_view/helpers/pagination_helper.rb +1 -1
  39. data/lib/action_view/helpers/text_helper.rb +8 -21
  40. data/lib/action_view/helpers/url_helper.rb +21 -4
  41. data/lib/action_view/partials.rb +39 -9
  42. data/rakefile +29 -5
  43. data/test/abstract_unit.rb +1 -0
  44. data/test/controller/action_pack_assertions_test.rb +1 -2
  45. data/test/controller/active_record_assertions_test.rb +1 -1
  46. data/test/controller/cgi_test.rb +0 -1
  47. data/test/controller/cookie_test.rb +11 -1
  48. data/test/controller/helper_test.rb +0 -12
  49. data/test/controller/render_test.rb +9 -0
  50. data/test/controller/request_test.rb +44 -1
  51. data/test/controller/routing_tests.rb +4 -1
  52. data/test/controller/test_test.rb +62 -0
  53. data/test/controller/verification_test.rb +21 -0
  54. data/test/fixtures/test/_partial_only.rhtml +1 -0
  55. data/test/template/active_record_helper_test.rb +2 -2
  56. data/test/template/asset_tag_helper_test.rb +52 -4
  57. data/test/template/date_helper_test.rb +163 -32
  58. data/test/template/form_helper_test.rb +9 -6
  59. data/test/template/form_options_helper_test.rb +18 -15
  60. data/test/template/number_helper_test.rb +51 -0
  61. data/test/template/text_helper_test.rb +17 -20
  62. data/test/template/url_helper_test.rb +7 -1
  63. metadata +15 -6
  64. data/lib/action_controller/assertions/action_pack_assertions.rb +0 -260
  65. data/lib/action_controller/assertions/active_record_assertions.rb +0 -65
@@ -29,7 +29,7 @@ module ActionView
29
29
  def link_to_function(name, function, html_options = {})
30
30
  content_tag(
31
31
  "a", name,
32
- html_options.symbolize_keys.merge(:href => "#", :onclick => "#{function}; return false;")
32
+ {:href => "#", :onclick => "#{function}; return false;"}.merge(html_options.symbolize_keys)
33
33
  )
34
34
  end
35
35
 
@@ -71,6 +71,15 @@ module ActionView
71
71
  link_to_function(name, remote_function(options), html_options)
72
72
  end
73
73
 
74
+ # Periodically calls the specified url (<tt>options[:url]</tt>) every <tt>options[:frequency]</tt> seconds (default is 10).
75
+ # Usually used to update a specified div (<tt>options[:update]</tt>) with the results of the remote call.
76
+ # The options for specifying the target with :url and defining callbacks is the same as link_to_remote.
77
+ def periodically_call_remote(options = {})
78
+ frequency = options[:frequency] || 10 # every ten seconds by default
79
+ code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
80
+ content_tag("script", code, options[:html_options] || {})
81
+ end
82
+
74
83
  # Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular
75
84
  # reloading POST arrangement. Even though it's using Javascript to serialize the form elements, the form submission
76
85
  # will work just like a regular submission as viewed by the receiving side (all elements available in @params).
@@ -78,11 +87,25 @@ module ActionView
78
87
  def form_remote_tag(options = {})
79
88
  options[:form] = true
80
89
 
81
- options[:html] ||= { }
90
+ options[:html] ||= {}
82
91
  options[:html][:onsubmit] = "#{remote_function(options)}; return false;"
83
92
 
84
93
  tag("form", options[:html], true)
85
94
  end
95
+
96
+ # Returns a button input tag that will submit form using XMLHttpRequest in the background instead of regular
97
+ # reloading POST arrangement. <tt>options</tt> argument is the same as in <tt>form_remote_tag</tt>
98
+ def submit_to_remote(name, value, options = {})
99
+ options[:with] = 'Form.serialize(this.form)'
100
+
101
+ options[:html] ||= {}
102
+ options[:html][:type] = 'button'
103
+ options[:html][:onclick] = "#{remote_function(options)}; return false;"
104
+ options[:html][:name] = name
105
+ options[:html][:value] = value
106
+
107
+ tag("input", options[:html], false)
108
+ end
86
109
 
87
110
  def remote_function(options) #:nodoc: for now
88
111
  javascript_options = options_for_ajax(options)
@@ -97,7 +120,8 @@ module ActionView
97
120
  function = "#{options[:before]}; #{function}" if options[:before]
98
121
  function = "#{function}; #{options[:after]}" if options[:after]
99
122
  function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
100
-
123
+ function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
124
+
101
125
  return function
102
126
  end
103
127
 
@@ -110,9 +134,7 @@ module ActionView
110
134
  # create remote <script> links.
111
135
  def define_javascript_functions
112
136
  javascript = '<script type="text/javascript">'
113
- Dir.glob(File.join(JAVASCRIPT_PATH, '*')).each do |filename|
114
- javascript << "\n" << IO.read(filename)
115
- end
137
+ Dir.glob(File.join(JAVASCRIPT_PATH, '*')).each { |filename| javascript << "\n" << IO.read(filename) }
116
138
  javascript << '</script>'
117
139
  end
118
140
 
@@ -1,12 +1,17 @@
1
- /* Prototype: an object-oriented Javascript library, version 1.0.1
1
+ /* Prototype: an object-oriented Javascript library, version 1.2.0
2
2
  * (c) 2005 Sam Stephenson <sam@conio.net>
3
3
  *
4
- * Prototype is freely distributable under the terms of an MIT-style license.
5
- * For details, see http://prototype.conio.net/
6
- */
4
+ * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
5
+ * against the source tree, available from the Prototype darcs repository.
6
+ *
7
+ * Prototype is freely distributable under the terms of an MIT-style license.
8
+ *
9
+ * For details, see the Prototype web site: http://prototype.conio.net/
10
+ *
11
+ /*--------------------------------------------------------------------------*/
7
12
 
8
13
  var Prototype = {
9
- Version: '1.1.0'
14
+ Version: '1.2.0'
10
15
  }
11
16
 
12
17
  var Class = {
@@ -62,13 +67,33 @@ var Try = {
62
67
  }
63
68
  }
64
69
 
65
- var Toggle = {
66
- display: function() {
67
- for (var i = 0; i < arguments.length; i++) {
68
- var element = $(arguments[i]);
69
- element.style.display =
70
- (element.style.display == 'none' ? '' : 'none');
70
+ /*--------------------------------------------------------------------------*/
71
+
72
+ var PeriodicalExecuter = Class.create();
73
+ PeriodicalExecuter.prototype = {
74
+ initialize: function(callback, frequency) {
75
+ this.callback = callback;
76
+ this.frequency = frequency;
77
+ this.currentlyExecuting = false;
78
+
79
+ this.registerCallback();
80
+ },
81
+
82
+ registerCallback: function() {
83
+ setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000);
84
+ },
85
+
86
+ onTimerEvent: function() {
87
+ if (!this.currentlyExecuting) {
88
+ try {
89
+ this.currentlyExecuting = true;
90
+ this.callback();
91
+ } finally {
92
+ this.currentlyExecuting = false;
93
+ }
71
94
  }
95
+
96
+ this.registerCallback();
72
97
  }
73
98
  }
74
99
 
@@ -91,22 +116,34 @@ function $() {
91
116
  return elements;
92
117
  }
93
118
 
94
- function getElementsByClassName(className) {
95
- var children = document.getElementsByTagName('*') || document.all;
96
- var elements = new Array();
97
-
98
- for (var i = 0; i < children.length; i++) {
99
- var child = children[i];
100
- var classNames = child.className.split(' ');
101
- for (var j = 0; j < classNames.length; j++) {
102
- if (classNames[j] == className) {
103
- elements.push(child);
104
- break;
105
- }
106
- }
119
+ /*--------------------------------------------------------------------------*/
120
+
121
+ if (!Array.prototype.push) {
122
+ Array.prototype.push = function() {
123
+ var startLength = this.length;
124
+ for (var i = 0; i < arguments.length; i++)
125
+ this[startLength + i] = arguments[i];
126
+ return this.length;
127
+ }
128
+ }
129
+
130
+ if (!Function.prototype.apply) {
131
+ // Based on code from http://www.youngpup.net/
132
+ Function.prototype.apply = function(object, parameters) {
133
+ var parameterStrings = new Array();
134
+ if (!object) object = window;
135
+ if (!parameters) parameters = new Array();
136
+
137
+ for (var i = 0; i < parameters.length; i++)
138
+ parameterStrings[i] = 'x[' + i + ']';
139
+
140
+ object.__apply__ = this;
141
+ var result = eval('obj.__apply__(' +
142
+ parameterStrings[i].join(', ') + ')');
143
+ object.__apply__ = null;
144
+
145
+ return result;
107
146
  }
108
-
109
- return elements;
110
147
  }
111
148
 
112
149
  /*--------------------------------------------------------------------------*/
@@ -154,6 +191,9 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({
154
191
  setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
155
192
  }
156
193
 
194
+ this.transport.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
195
+ this.transport.setRequestHeader('X-Prototype-Version', Prototype.Version);
196
+
157
197
  if (this.options.method == 'post') {
158
198
  this.transport.setRequestHeader('Connection', 'close');
159
199
  this.transport.setRequestHeader('Content-type',
@@ -197,11 +237,11 @@ Ajax.Updater.prototype = (new Ajax.Base()).extend({
197
237
  },
198
238
 
199
239
  updateContent: function() {
200
- if (!this.options.insertion) {
201
- this.container.innerHTML = this.request.transport.responseText;
202
- } else {
240
+ if (this.options.insertion) {
203
241
  new this.options.insertion(this.container,
204
242
  this.request.transport.responseText);
243
+ } else {
244
+ this.container.innerHTML = this.request.transport.responseText;
205
245
  }
206
246
 
207
247
  if (this.onComplete) {
@@ -226,6 +266,15 @@ var Field = {
226
266
  for (var i = 0; i < arguments.length; i++)
227
267
  if ($(arguments[i]).value == '') return false;
228
268
  return true;
269
+ },
270
+
271
+ select: function(element) {
272
+ $(element).select();
273
+ },
274
+
275
+ activate: function(element) {
276
+ $(element).focus();
277
+ $(element).select();
229
278
  }
230
279
  }
231
280
 
@@ -255,6 +304,31 @@ var Form = {
255
304
  elements.push(tagElements[j]);
256
305
  }
257
306
  return elements;
307
+ },
308
+
309
+ disable: function(form) {
310
+ var elements = Form.getElements(form);
311
+ for (var i = 0; i < elements.length; i++) {
312
+ var element = elements[i];
313
+ element.blur();
314
+ element.disable = 'true';
315
+ }
316
+ },
317
+
318
+ focusFirstElement: function(form) {
319
+ form = $(form);
320
+ var elements = Form.getElements(form);
321
+ for (var i = 0; i < elements.length; i++) {
322
+ var element = elements[i];
323
+ if (element.type != 'hidden' && !element.disabled) {
324
+ Field.activate(element);
325
+ break;
326
+ }
327
+ }
328
+ },
329
+
330
+ reset: function(form) {
331
+ $(form).reset();
258
332
  }
259
333
  }
260
334
 
@@ -304,12 +378,17 @@ Form.Element.Serializers = {
304
378
 
305
379
  select: function(element) {
306
380
  var index = element.selectedIndex;
307
- return [element.name, (index >= 0) ? element.options[index].value : ''];
381
+ var value = element.options[index].value || element.options[index].text;
382
+ return [element.name, (index >= 0) ? value : ''];
308
383
  }
309
384
  }
310
385
 
311
386
  /*--------------------------------------------------------------------------*/
312
387
 
388
+ var $F = Form.Element.getValue;
389
+
390
+ /*--------------------------------------------------------------------------*/
391
+
313
392
  Abstract.TimedObserver = function() {}
314
393
  Abstract.TimedObserver.prototype = {
315
394
  initialize: function(element, frequency, callback) {
@@ -351,6 +430,65 @@ Form.Observer.prototype = (new Abstract.TimedObserver()).extend({
351
430
  });
352
431
 
353
432
 
433
+ /*--------------------------------------------------------------------------*/
434
+
435
+ document.getElementsByClassName = function(className) {
436
+ var children = document.getElementsByTagName('*') || document.all;
437
+ var elements = new Array();
438
+
439
+ for (var i = 0; i < children.length; i++) {
440
+ var child = children[i];
441
+ var classNames = child.className.split(' ');
442
+ for (var j = 0; j < classNames.length; j++) {
443
+ if (classNames[j] == className) {
444
+ elements.push(child);
445
+ break;
446
+ }
447
+ }
448
+ }
449
+
450
+ return elements;
451
+ }
452
+
453
+ /*--------------------------------------------------------------------------*/
454
+
455
+ var Element = {
456
+ toggle: function() {
457
+ for (var i = 0; i < arguments.length; i++) {
458
+ var element = $(arguments[i]);
459
+ element.style.display =
460
+ (element.style.display == 'none' ? '' : 'none');
461
+ }
462
+ },
463
+
464
+ hide: function() {
465
+ for (var i = 0; i < arguments.length; i++) {
466
+ var element = $(arguments[i]);
467
+ element.style.display = 'none';
468
+ }
469
+ },
470
+
471
+ show: function() {
472
+ for (var i = 0; i < arguments.length; i++) {
473
+ var element = $(arguments[i]);
474
+ element.style.display = '';
475
+ }
476
+ },
477
+
478
+ remove: function(element) {
479
+ element = $(element);
480
+ element.parentNode.removeChild(element);
481
+ },
482
+
483
+ getHeight: function(element) {
484
+ element = $(element);
485
+ return element.offsetHeight;
486
+ }
487
+ }
488
+
489
+ var Toggle = new Object();
490
+ Toggle.display = Element.toggle;
491
+
354
492
  /*--------------------------------------------------------------------------*/
355
493
 
356
494
  Abstract.Insertion = function(adjacency) {
@@ -452,3 +590,175 @@ Effect.Highlight.prototype = {
452
590
  element.style.backgroundColor = "#ffff" + current.toColorPart();
453
591
  }
454
592
  }
593
+
594
+
595
+ Effect.Fade = Class.create();
596
+ Effect.Fade.prototype = {
597
+ initialize: function(element) {
598
+ this.element = $(element);
599
+ this.start = 100;
600
+ this.finish = 0;
601
+ this.current = this.start;
602
+ this.fade();
603
+ },
604
+
605
+ fade: function() {
606
+ if (this.isFinished()) { this.element.style.display = 'none'; return; }
607
+ if (this.timer) clearTimeout(this.timer);
608
+ this.setOpacity(this.element, this.current);
609
+ this.current -= 10;
610
+ this.timer = setTimeout(this.fade.bind(this), 50);
611
+ },
612
+
613
+ isFinished: function() {
614
+ return this.current <= this.finish;
615
+ },
616
+
617
+ setOpacity: function(element, opacity) {
618
+ opacity = (opacity == 100) ? 99.999 : opacity;
619
+ element.style.filter = "alpha(opacity:"+opacity+")";
620
+ element.style.opacity = opacity/100 /*//*/;
621
+ }
622
+ }
623
+
624
+ Effect.Scale = Class.create();
625
+ Effect.Scale.prototype = {
626
+ initialize: function(element, percent) {
627
+ this.element = $(element);
628
+ this.startScale = 1.0;
629
+ this.startHeight = this.element.offsetHeight;
630
+ this.startWidth = this.element.offsetWidth;
631
+ this.currentHeight = this.startHeight;
632
+ this.currentWidth = this.startWidth;
633
+ this.finishScale = (percent/100) /*//*/;
634
+ if (this.element.style.fontSize=="") this.sizeEm = 1.0;
635
+ if (this.element.style.fontSize.indexOf("em")>0)
636
+ this.sizeEm = parseFloat(this.element.style.fontSize);
637
+ if(this.element.effect_scale) {
638
+ clearTimeout(this.element.effect_scale.timer);
639
+ this.startScale = this.element.effect_scale.currentScale;
640
+ this.startHeight = this.element.effect_scale.startHeight;
641
+ this.startWidth = this.element.effect_scale.startWidth;
642
+ if(this.element.effect_scale.sizeEm)
643
+ this.sizeEm = this.element.effect_scale.sizeEm;
644
+ }
645
+ this.element.effect_scale = this;
646
+ this.currentScale = this.startScale;
647
+ this.factor = this.finishScale - this.startScale;
648
+ this.options = arguments[2] || {};
649
+ this.scale();
650
+ },
651
+
652
+ scale: function() {
653
+ if (this.isFinished()) {
654
+ this.setDimensions(this.element, this.startWidth*this.finishScale, this.startHeight*this.finishScale);
655
+ if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.finishScale + "em";
656
+ if(this.options.complete) this.options.complete(this);
657
+ return;
658
+ }
659
+ if (this.timer) clearTimeout(this.timer);
660
+ if (this.options.step) this.options.step(this);
661
+ this.setDimensions(this.element, this.currentWidth, this.currentHeight);
662
+ if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.currentScale + "em";
663
+ this.currentScale += (this.factor/10) /*//*/;
664
+ this.currentWidth = this.startWidth * this.currentScale;
665
+ this.currentHeight = this.startHeight * this.currentScale;
666
+ this.timer = setTimeout(this.scale.bind(this), 50);
667
+ },
668
+
669
+ isFinished: function() {
670
+ return (this.factor < 0) ?
671
+ this.currentScale <= this.finishScale : this.currentScale >= this.finishScale;
672
+ },
673
+
674
+ setDimensions: function(element, width, height) {
675
+ element.style.width = width + 'px';
676
+ element.style.height = height + 'px';
677
+ }
678
+ }
679
+
680
+ Effect.Squish = Class.create();
681
+ Effect.Squish.prototype = {
682
+ initialize: function(element) {
683
+ this.element = $(element);
684
+ new Effect.Scale(this.element, 1, { complete: this.hide.bind(this) } );
685
+ },
686
+ hide: function() {
687
+ this.element.style.display = 'none';
688
+ }
689
+ }
690
+
691
+ Effect.Puff = Class.create();
692
+ Effect.Puff.prototype = {
693
+ initialize: function(element) {
694
+ this.element = $(element);
695
+ this.opacity = 100;
696
+ this.startTop = this.element.top || this.element.offsetTop;
697
+ this.startLeft = this.element.left || this.element.offsetLeft;
698
+ new Effect.Scale(this.element, 200, { step: this.fade.bind(this), complete: this.hide.bind(this) } );
699
+ },
700
+ fade: function(effect) {
701
+ topd = (((effect.currentScale)*effect.startHeight) - effect.startHeight)/2;
702
+ leftd = (((effect.currentScale)*effect.startWidth) - effect.startWidth)/2;
703
+ this.element.style.position='absolute';
704
+ this.element.style.top = this.startTop-topd + "px";
705
+ this.element.style.left = this.startLeft-leftd + "px";
706
+ this.opacity -= 10;
707
+ this.setOpacity(this.element, this.opacity);
708
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) this.element.innerHTML += ''; //force redraw on safari
709
+ },
710
+ hide: function() {
711
+ this.element.style.display = 'none';
712
+ },
713
+ setOpacity: function(element, opacity) {
714
+ opacity = (opacity == 100) ? 99.999 : opacity;
715
+ element.style.filter = "alpha(opacity:"+opacity+")";
716
+ element.style.opacity = opacity/100 /*//*/;
717
+ }
718
+ }
719
+
720
+ Effect.Appear = Class.create();
721
+ Effect.Appear.prototype = {
722
+ initialize: function(element) {
723
+ this.element = $(element);
724
+ this.start = 0;
725
+ this.finish = 100;
726
+ this.current = this.start;
727
+ this.fade();
728
+ },
729
+
730
+ fade: function() {
731
+ if (this.isFinished()) return;
732
+ if (this.timer) clearTimeout(this.timer);
733
+ this.setOpacity(this.element, this.current);
734
+ this.current += 10;
735
+ this.timer = setTimeout(this.fade.bind(this), 50);
736
+ },
737
+
738
+ isFinished: function() {
739
+ return this.current > this.finish;
740
+ },
741
+
742
+ setOpacity: function(element, opacity) {
743
+ opacity = (opacity == 100) ? 99.999 : opacity;
744
+ element.style.filter = "alpha(opacity:"+opacity+")";
745
+ element.style.opacity = opacity/100 /*//*/;
746
+ element.style.display = '';
747
+ }
748
+ }
749
+
750
+ Effect.ContentZoom = Class.create();
751
+ Effect.ContentZoom.prototype = {
752
+ initialize: function(element, percent) {
753
+ this.element = $(element);
754
+ if (this.element.style.fontSize=="") this.sizeEm = 1.0;
755
+ if (this.element.style.fontSize.indexOf("em")>0)
756
+ this.sizeEm = parseFloat(this.element.style.fontSize);
757
+ if(this.element.effect_contentzoom) {
758
+ this.sizeEm = this.element.effect_contentzoom.sizeEm;
759
+ }
760
+ this.element.effect_contentzoom = this;
761
+ this.element.style.fontSize = this.sizeEm*(percent/100) + "em" /*//*/;
762
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) { this.element.scrollTop -= 1; };
763
+ }
764
+ }