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.
- data/CHANGELOG +94 -0
- data/README +24 -0
- data/lib/action_controller.rb +2 -0
- data/lib/action_controller/assertions/action_pack_assertions.rb +1 -1
- data/lib/action_controller/base.rb +15 -2
- data/lib/action_controller/caching.rb +6 -16
- data/lib/action_controller/components.rb +1 -1
- data/lib/action_controller/flash.rb +125 -29
- data/lib/action_controller/pagination.rb +378 -0
- data/lib/action_controller/request.rb +13 -6
- data/lib/action_controller/routing.rb +37 -3
- data/lib/action_controller/test_process.rb +7 -3
- data/lib/action_controller/url_rewriter.rb +5 -4
- data/lib/action_view/helpers/asset_tag_helper.rb +35 -4
- data/lib/action_view/helpers/capture_helper.rb +95 -0
- data/lib/action_view/helpers/form_helper.rb +1 -1
- data/lib/action_view/helpers/form_options_helper.rb +2 -0
- data/lib/action_view/helpers/form_tag_helper.rb +28 -10
- data/lib/action_view/helpers/javascript_helper.rb +192 -0
- data/lib/action_view/helpers/javascripts/prototype.js +336 -0
- data/lib/action_view/helpers/pagination_helper.rb +71 -0
- data/lib/action_view/helpers/tag_helper.rb +2 -1
- data/lib/action_view/helpers/text_helper.rb +15 -2
- data/lib/action_view/helpers/url_helper.rb +3 -20
- data/lib/action_view/partials.rb +4 -2
- data/rakefile +2 -2
- data/test/controller/action_pack_assertions_test.rb +1 -2
- data/test/controller/flash_test.rb +30 -5
- data/test/controller/request_test.rb +33 -10
- data/test/controller/routing_tests.rb +26 -0
- data/test/template/asset_tag_helper_test.rb +87 -2
- data/test/template/form_helper_test.rb +1 -0
- data/test/template/form_options_helper_test.rb +11 -0
- data/test/template/form_tag_helper_test.rb +84 -16
- data/test/template/tag_helper_test.rb +2 -15
- data/test/template/text_helper_test.rb +6 -0
- data/test/template/url_helper_test.rb +13 -18
- metadata +10 -5
- 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.
|
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.
|
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><p></tt> tags, and converts line breaks into <tt><br /></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.
|