actionpack 1.5.1 → 1.6.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 (39) hide show
  1. data/CHANGELOG +94 -0
  2. data/README +24 -0
  3. data/lib/action_controller.rb +2 -0
  4. data/lib/action_controller/assertions/action_pack_assertions.rb +1 -1
  5. data/lib/action_controller/base.rb +15 -2
  6. data/lib/action_controller/caching.rb +6 -16
  7. data/lib/action_controller/components.rb +1 -1
  8. data/lib/action_controller/flash.rb +125 -29
  9. data/lib/action_controller/pagination.rb +378 -0
  10. data/lib/action_controller/request.rb +13 -6
  11. data/lib/action_controller/routing.rb +37 -3
  12. data/lib/action_controller/test_process.rb +7 -3
  13. data/lib/action_controller/url_rewriter.rb +5 -4
  14. data/lib/action_view/helpers/asset_tag_helper.rb +35 -4
  15. data/lib/action_view/helpers/capture_helper.rb +95 -0
  16. data/lib/action_view/helpers/form_helper.rb +1 -1
  17. data/lib/action_view/helpers/form_options_helper.rb +2 -0
  18. data/lib/action_view/helpers/form_tag_helper.rb +28 -10
  19. data/lib/action_view/helpers/javascript_helper.rb +192 -0
  20. data/lib/action_view/helpers/javascripts/prototype.js +336 -0
  21. data/lib/action_view/helpers/pagination_helper.rb +71 -0
  22. data/lib/action_view/helpers/tag_helper.rb +2 -1
  23. data/lib/action_view/helpers/text_helper.rb +15 -2
  24. data/lib/action_view/helpers/url_helper.rb +3 -20
  25. data/lib/action_view/partials.rb +4 -2
  26. data/rakefile +2 -2
  27. data/test/controller/action_pack_assertions_test.rb +1 -2
  28. data/test/controller/flash_test.rb +30 -5
  29. data/test/controller/request_test.rb +33 -10
  30. data/test/controller/routing_tests.rb +26 -0
  31. data/test/template/asset_tag_helper_test.rb +87 -2
  32. data/test/template/form_helper_test.rb +1 -0
  33. data/test/template/form_options_helper_test.rb +11 -0
  34. data/test/template/form_tag_helper_test.rb +84 -16
  35. data/test/template/tag_helper_test.rb +2 -15
  36. data/test/template/text_helper_test.rb +6 -0
  37. data/test/template/url_helper_test.rb +13 -18
  38. metadata +10 -5
  39. data/test/controller/url_obsolete.rb.rej +0 -747
@@ -0,0 +1,336 @@
1
+ /* Prototype: an object-oriented Javascript library, version 1.0.1
2
+ * (c) 2005 Sam Stephenson <sam@conio.net>
3
+ *
4
+ * Prototype is freely distributable under the terms of an MIT-style license.
5
+ * For details, see http://prototype.conio.net/
6
+ */
7
+
8
+ Prototype = {
9
+ Version: '1.0.1'
10
+ }
11
+
12
+ Class = {
13
+ create: function() {
14
+ return function() {
15
+ this.initialize.apply(this, arguments);
16
+ }
17
+ }
18
+ }
19
+
20
+ Abstract = new Object();
21
+
22
+ Object.prototype.extend = function(object) {
23
+ for (property in object) {
24
+ this[property] = object[property];
25
+ }
26
+ return this;
27
+ }
28
+
29
+ Function.prototype.bind = function(object) {
30
+ var method = this;
31
+ return function() {
32
+ method.apply(object, arguments);
33
+ }
34
+ }
35
+
36
+ Function.prototype.bindAsEventListener = function(object) {
37
+ var method = this;
38
+ return function(event) {
39
+ method.call(object, event || window.event);
40
+ }
41
+ }
42
+
43
+ Try = {
44
+ these: function() {
45
+ var returnValue;
46
+
47
+ for (var i = 0; i < arguments.length; i++) {
48
+ var lambda = arguments[i];
49
+ try {
50
+ returnValue = lambda();
51
+ break;
52
+ } catch (e) {}
53
+ }
54
+
55
+ return returnValue;
56
+ }
57
+ }
58
+
59
+ Toggle = {
60
+ display: function() {
61
+ for (var i = 0; i < elements.length; i++) {
62
+ var element = $(elements[i]);
63
+ element.style.display =
64
+ (element.style.display == 'none' ? '' : 'none');
65
+ }
66
+ }
67
+ }
68
+
69
+ /*--------------------------------------------------------------------------*/
70
+
71
+ function $() {
72
+ var elements = new Array();
73
+
74
+ for (var i = 0; i < arguments.length; i++) {
75
+ var element = arguments[i];
76
+ if (typeof element == 'string')
77
+ element = document.getElementById(element);
78
+
79
+ if (arguments.length == 1)
80
+ return element;
81
+
82
+ elements.push(element);
83
+ }
84
+
85
+ return elements;
86
+ }
87
+
88
+ function getElementsByClassName(className, element) {
89
+ var children = (element || document).getElementsByTagName('*');
90
+ var elements = new Array();
91
+
92
+ for (var i = 0; i < children.length; i++) {
93
+ var child = children[i];
94
+ var classNames = child.className.split(' ');
95
+ for (var j = 0; j < classNames.length; j++) {
96
+ if (classNames[j] == className) {
97
+ elements.push(child);
98
+ break;
99
+ }
100
+ }
101
+ }
102
+
103
+ return elements;
104
+ }
105
+
106
+ /*--------------------------------------------------------------------------*/
107
+
108
+ Ajax = {
109
+ getTransport: function() {
110
+ return Try.these(
111
+ function() {return new ActiveXObject('Msxml2.XMLHTTP')},
112
+ function() {return new ActiveXObject('Microsoft.XMLHTTP')},
113
+ function() {return new XMLHttpRequest()}
114
+ ) || false;
115
+ },
116
+
117
+ emptyFunction: function() {}
118
+ }
119
+
120
+ Ajax.Base = function() {};
121
+ Ajax.Base.prototype = {
122
+ setOptions: function(options) {
123
+ this.options = {
124
+ method: 'post',
125
+ asynchronous: true,
126
+ parameters: ''
127
+ }.extend(options || {});
128
+ }
129
+ }
130
+
131
+ Ajax.Request = Class.create();
132
+ Ajax.Request.Events =
133
+ ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
134
+
135
+ Ajax.Request.prototype = (new Ajax.Base()).extend({
136
+ initialize: function(url, options) {
137
+ this.transport = Ajax.getTransport();
138
+ this.setOptions(options);
139
+
140
+ try {
141
+ if (this.options.method == 'get')
142
+ url += '?' + this.options.parameters + '&_=';
143
+
144
+ this.transport.open(this.options.method, url, true);
145
+
146
+ if (this.options.asynchronous) {
147
+ this.transport.onreadystatechange = this.onStateChange.bind(this);
148
+ setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
149
+ }
150
+
151
+ if (this.options.method == 'post') {
152
+ this.transport.setRequestHeader('Connection', 'close');
153
+ this.transport.setRequestHeader('Content-type',
154
+ 'application/x-www-form-urlencoded');
155
+ }
156
+
157
+ this.transport.send(this.options.method == 'post' ?
158
+ this.options.parameters + '&_=' : null);
159
+
160
+ } catch (e) {
161
+ }
162
+ },
163
+
164
+ onStateChange: function() {
165
+ var readyState = this.transport.readyState;
166
+ if (readyState != 1)
167
+ this.respondToReadyState(this.transport.readyState);
168
+ },
169
+
170
+ respondToReadyState: function(readyState) {
171
+ var event = Ajax.Request.Events[readyState];
172
+ (this.options['on' + event] || Ajax.emptyFunction)(this.transport);
173
+ }
174
+ });
175
+
176
+ Ajax.Updater = Class.create();
177
+ Ajax.Updater.prototype = (new Ajax.Base()).extend({
178
+ initialize: function(container, url, options) {
179
+ this.container = $(container);
180
+ this.setOptions(options);
181
+
182
+ if (this.options.asynchronous) {
183
+ this.onComplete = this.options.onComplete;
184
+ this.options.onComplete = this.updateContent.bind(this);
185
+ }
186
+
187
+ this.request = new Ajax.Request(url, this.options);
188
+
189
+ if (!this.options.asynchronous)
190
+ this.updateContent();
191
+ },
192
+
193
+ updateContent: function() {
194
+ this.container.innerHTML = this.request.transport.responseText;
195
+ if (this.onComplete) this.onComplete(this.request);
196
+ }
197
+ });
198
+
199
+ /*--------------------------------------------------------------------------*/
200
+
201
+ Field = {
202
+ clear: function() {
203
+ for (var i = 0; i < arguments.length; i++)
204
+ $(arguments[i]).value = '';
205
+ },
206
+
207
+ focus: function(element) {
208
+ $(element).focus();
209
+ },
210
+
211
+ present: function() {
212
+ for (var i = 0; i < arguments.length; i++)
213
+ if ($(arguments[i]).value == '') return false;
214
+ return true;
215
+ }
216
+ }
217
+
218
+ /*--------------------------------------------------------------------------*/
219
+
220
+ Form = {
221
+ serialize: function(form) {
222
+ var elements = Form.getElements($(form));
223
+ var queryComponents = new Array();
224
+
225
+ for (var i = 0; i < elements.length; i++) {
226
+ var queryComponent = Form.Element.serialize(elements[i]);
227
+ if (queryComponent)
228
+ queryComponents.push(queryComponent);
229
+ }
230
+
231
+ return queryComponents.join('&');
232
+ },
233
+
234
+ getElements: function(form) {
235
+ form = $(form);
236
+ var elements = new Array();
237
+
238
+ for (tagName in Form.Element.Serializers) {
239
+ var tagElements = form.getElementsByTagName(tagName);
240
+ for (var j = 0; j < tagElements.length; j++)
241
+ elements.push(tagElements[j]);
242
+ }
243
+ return elements;
244
+ }
245
+ }
246
+
247
+ Form.Element = {
248
+ serialize: function(element) {
249
+ element = $(element);
250
+ var method = element.tagName.toLowerCase();
251
+ var parameter = Form.Element.Serializers[method](element);
252
+
253
+ if (parameter)
254
+ return encodeURIComponent(parameter[0]) + '=' +
255
+ encodeURIComponent(parameter[1]);
256
+ },
257
+
258
+ getValue: function(element) {
259
+ element = $(element);
260
+ var method = element.tagName.toLowerCase();
261
+ var parameter = Form.Element.Serializers[method](element);
262
+
263
+ if (parameter)
264
+ return parameter[1];
265
+ }
266
+ }
267
+
268
+ Form.Element.Serializers = {
269
+ input: function(element) {
270
+ switch (element.type.toLowerCase()) {
271
+ case 'hidden':
272
+ case 'text':
273
+ return Form.Element.Serializers.textarea(element);
274
+ case 'checkbox':
275
+ case 'radio':
276
+ return Form.Element.Serializers.inputSelector(element);
277
+ }
278
+ },
279
+
280
+ inputSelector: function(element) {
281
+ if (element.checked)
282
+ return [element.name, element.value];
283
+ },
284
+
285
+ textarea: function(element) {
286
+ return [element.name, element.value];
287
+ },
288
+
289
+ select: function(element) {
290
+ var index = element.selectedIndex;
291
+ return [element.name, (index >= 0) ? element.options[index].value : ''];
292
+ }
293
+ }
294
+
295
+ /*--------------------------------------------------------------------------*/
296
+
297
+ Abstract.TimedObserver = function() {}
298
+ Abstract.TimedObserver.prototype = {
299
+ initialize: function(element, frequency, callback) {
300
+ this.frequency = frequency;
301
+ this.element = $(element);
302
+ this.callback = callback;
303
+
304
+ this.lastValue = this.getValue();
305
+ this.registerCallback();
306
+ },
307
+
308
+ registerCallback: function() {
309
+ setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000);
310
+ },
311
+
312
+ onTimerEvent: function() {
313
+ var value = this.getValue();
314
+ if (this.lastValue != value) {
315
+ this.callback(this.element, value);
316
+ this.lastValue = value;
317
+ }
318
+
319
+ this.registerCallback();
320
+ }
321
+ }
322
+
323
+ Form.Element.Observer = Class.create();
324
+ Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({
325
+ getValue: function() {
326
+ return Form.Element.getValue(this.element);
327
+ }
328
+ });
329
+
330
+ Form.Observer = Class.create();
331
+ Form.Observer.prototype = (new Abstract.TimedObserver()).extend({
332
+ getValue: function() {
333
+ return Form.serialize(this.element);
334
+ }
335
+ });
336
+
@@ -0,0 +1,71 @@
1
+ module ActionView
2
+ module Helpers
3
+ # Provides methods for linking to ActionController::Pagination objects.
4
+ #
5
+ # You can also build your links manually, like in this example:
6
+ #
7
+ # <%= link_to "Previous page", { :page => paginator.current.previous } if paginator.current.previous %>
8
+ #
9
+ # <%= link_to "Next page", { :page => paginator.current.next } of paginator.current.next =%>
10
+ module PaginationHelper
11
+ unless const_defined?(:DEFAULT_OPTIONS)
12
+ DEFAULT_OPTIONS = {
13
+ :name => :page,
14
+ :window_size => 2,
15
+ :always_show_anchors => true,
16
+ :link_to_current_page => false,
17
+ :params => {}
18
+ }
19
+ end
20
+
21
+ # Creates a basic HTML link bar for the given +paginator+.
22
+ #
23
+ # +options+ are:
24
+ # <tt>:name</tt>:: the routing name for this paginator
25
+ # (defaults to +page+)
26
+ # <tt>:window_size</tt>:: the number of pages to show around
27
+ # the current page (defaults to +2+)
28
+ # <tt>:always_show_anchors</tt>:: whether or not the first and last
29
+ # pages should always be shown
30
+ # (defaults to +true+)
31
+ # <tt>:link_to_current_page</tt>:: whether or not the current page
32
+ # should be linked to (defaults to
33
+ # +false+)
34
+ # <tt>:params</tt>:: any additional routing parameters
35
+ # for page URLs
36
+ def pagination_links(paginator, options={})
37
+ options.merge!(DEFAULT_OPTIONS) {|key, old, new| old}
38
+
39
+ window_pages = paginator.current.window(options[:window_size]).pages
40
+
41
+ return if window_pages.length <= 1 unless
42
+ options[:link_to_current_page]
43
+
44
+ first, last = paginator.first, paginator.last
45
+
46
+ returning html = '' do
47
+ if options[:always_show_anchors] and not window_pages[0].first?
48
+ html << link_to(first.number, options[:name] => first)
49
+ html << ' ... ' if window_pages[0].number - first.number > 1
50
+ html << ' '
51
+ end
52
+
53
+ window_pages.each do |page|
54
+ if paginator.current == page && !options[:link_to_current_page]
55
+ html << page.number.to_s
56
+ else
57
+ html << link_to(page.number, options[:name] => page)
58
+ end
59
+ html << ' '
60
+ end
61
+
62
+ if options[:always_show_anchors] && !window_pages.last.last?
63
+ html << ' ... ' if last.number - window_pages[-1].number > 1
64
+ html << link_to(paginator.last.number, options[:name] => last)
65
+ end
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -25,6 +25,7 @@ module ActionView
25
25
  private
26
26
  def tag_options(options)
27
27
  unless options.empty?
28
+ options.symbolize_keys
28
29
  " " + options.map { |key, value|
29
30
  %(#{key}="#{html_escape(value.to_s)}")
30
31
  }.sort.join(" ")
@@ -32,4 +33,4 @@ module ActionView
32
33
  end
33
34
  end
34
35
  end
35
- end
36
+ end
@@ -69,7 +69,7 @@ module ActionView
69
69
  # Returns the text with all the Textile codes turned into HTML-tags.
70
70
  # <i>This method is only available if RedCloth can be required</i>.
71
71
  def textilize(text)
72
- text.empty? ? "" : RedCloth.new(text, [ :hard_breaks ]).to_html
72
+ text.blank? ? "" : RedCloth.new(text, [ :hard_breaks ]).to_html
73
73
  end
74
74
 
75
75
  # Returns the text with all the Textile codes turned into HTML-tags, but without the regular bounding <p> tag.
@@ -90,11 +90,24 @@ module ActionView
90
90
  # Returns the text with all the Markdown codes turned into HTML-tags.
91
91
  # <i>This method is only available if BlueCloth can be required</i>.
92
92
  def markdown(text)
93
- text.empty? ? "" : BlueCloth.new(text).to_html
93
+ text.blank? ? "" : BlueCloth.new(text).to_html
94
94
  end
95
95
  rescue LoadError
96
96
  # We can't really help what's not there
97
97
  end
98
+
99
+ # Returns +text+ transformed into html using very simple formatting rules
100
+ # Surrounds paragraphs with <tt>&lt;p&gt;</tt> tags, and converts line breaks into <tt>&lt;br /&gt;</tt>
101
+ # Two consecutive newlines(<tt>\n\n</tt>) are considered as a paragraph, one newline (<tt>\n</tt>) is
102
+ # considered a linebreak, three or more consecutive newlines are turned into two newlines
103
+ def simple_format(text)
104
+ text.gsub!(/(\r\n|\n|\r)/, "\n") # lets make them newlines crossplatform
105
+ text.gsub!(/\n\n+/, "\n\n") # zap dupes
106
+ text.gsub!(/\n\n/, '</p>\0<p>') # turn two newlines into paragraph
107
+ text.gsub!(/([^\n])(\n)([^\n])/, '\1\2<br />\3') # turn single newline into <br />
108
+
109
+ return '<p>' + text + '</p>' # wrap the first and last line in paragraphs before we're done
110
+ end
98
111
 
99
112
  # Turns all urls and email addresses into clickable links. The +link+ parameter can limit what should be linked.
100
113
  # Options are :all (default), :email_addresses, and :urls.