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.
- data/CHANGELOG +237 -0
- data/README +12 -12
- data/lib/action_controller.rb +17 -12
- data/lib/action_controller/assertions.rb +119 -67
- data/lib/action_controller/base.rb +184 -102
- data/lib/action_controller/benchmarking.rb +35 -6
- data/lib/action_controller/caching.rb +115 -58
- data/lib/action_controller/cgi_ext/cgi_methods.rb +54 -21
- data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +39 -35
- data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +34 -21
- data/lib/action_controller/cgi_process.rb +23 -20
- data/lib/action_controller/components.rb +11 -2
- data/lib/action_controller/dependencies.rb +0 -5
- data/lib/action_controller/deprecated_redirects.rb +17 -0
- data/lib/action_controller/filters.rb +13 -9
- data/lib/action_controller/flash.rb +7 -7
- data/lib/action_controller/helpers.rb +1 -14
- data/lib/action_controller/layout.rb +40 -29
- data/lib/action_controller/macros/auto_complete.rb +52 -0
- data/lib/action_controller/macros/in_place_editing.rb +32 -0
- data/lib/action_controller/pagination.rb +44 -28
- data/lib/action_controller/request.rb +54 -40
- data/lib/action_controller/rescue.rb +8 -6
- data/lib/action_controller/routing.rb +77 -28
- data/lib/action_controller/scaffolding.rb +10 -14
- data/lib/action_controller/session/active_record_store.rb +36 -7
- data/lib/action_controller/session_management.rb +126 -0
- data/lib/action_controller/streaming.rb +14 -5
- data/lib/action_controller/templates/rescues/_request_and_response.rhtml +1 -1
- data/lib/action_controller/templates/rescues/_trace.rhtml +24 -0
- data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -13
- data/lib/action_controller/templates/rescues/template_error.rhtml +4 -2
- data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
- data/lib/action_controller/test_process.rb +35 -17
- data/lib/action_controller/upload_progress.rb +52 -0
- data/lib/action_controller/url_rewriter.rb +21 -16
- data/lib/action_controller/vendor/html-scanner/html/document.rb +2 -2
- data/lib/action_controller/vendor/html-scanner/html/node.rb +30 -3
- data/lib/action_pack/version.rb +9 -0
- data/lib/action_view.rb +1 -1
- data/lib/action_view/base.rb +204 -60
- data/lib/action_view/compiled_templates.rb +70 -0
- data/lib/action_view/helpers/active_record_helper.rb +7 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +22 -12
- data/lib/action_view/helpers/capture_helper.rb +2 -10
- data/lib/action_view/helpers/date_helper.rb +21 -13
- data/lib/action_view/helpers/form_helper.rb +14 -10
- data/lib/action_view/helpers/form_options_helper.rb +4 -4
- data/lib/action_view/helpers/form_tag_helper.rb +59 -25
- data/lib/action_view/helpers/java_script_macros_helper.rb +188 -0
- data/lib/action_view/helpers/javascript_helper.rb +68 -133
- data/lib/action_view/helpers/javascripts/controls.js +427 -165
- data/lib/action_view/helpers/javascripts/dragdrop.js +256 -277
- data/lib/action_view/helpers/javascripts/effects.js +766 -277
- data/lib/action_view/helpers/javascripts/prototype.js +906 -218
- data/lib/action_view/helpers/javascripts/slider.js +258 -0
- data/lib/action_view/helpers/number_helper.rb +4 -3
- data/lib/action_view/helpers/pagination_helper.rb +42 -27
- data/lib/action_view/helpers/tag_helper.rb +25 -11
- data/lib/action_view/helpers/text_helper.rb +119 -13
- data/lib/action_view/helpers/upload_progress_helper.rb +2 -2
- data/lib/action_view/helpers/url_helper.rb +68 -21
- data/lib/action_view/partials.rb +17 -6
- data/lib/action_view/template_error.rb +19 -24
- data/rakefile +4 -3
- data/test/abstract_unit.rb +2 -1
- data/test/controller/action_pack_assertions_test.rb +62 -2
- data/test/controller/active_record_assertions_test.rb +5 -6
- data/test/controller/active_record_store_test.rb +23 -1
- data/test/controller/addresses_render_test.rb +4 -0
- data/test/controller/{base_tests.rb → base_test.rb} +4 -3
- data/test/controller/benchmark_test.rb +36 -0
- data/test/controller/caching_filestore.rb +22 -40
- data/test/controller/capture_test.rb +10 -1
- data/test/controller/cgi_test.rb +145 -23
- data/test/controller/components_test.rb +50 -0
- data/test/controller/custom_handler_test.rb +3 -3
- data/test/controller/fake_controllers.rb +24 -0
- data/test/controller/filters_test.rb +6 -6
- data/test/controller/flash_test.rb +6 -6
- data/test/controller/fragment_store_setting_test.rb +45 -0
- data/test/controller/helper_test.rb +1 -3
- data/test/controller/new_render_test.rb +119 -7
- data/test/controller/redirect_test.rb +11 -1
- data/test/controller/render_test.rb +34 -1
- data/test/controller/request_test.rb +14 -5
- data/test/controller/routing_test.rb +238 -42
- data/test/controller/send_file_test.rb +11 -10
- data/test/controller/session_management_test.rb +94 -0
- data/test/controller/test_test.rb +194 -5
- data/test/controller/url_rewriter_test.rb +46 -0
- data/test/fixtures/layouts/talk_from_action.rhtml +2 -0
- data/test/fixtures/layouts/yield.rhtml +2 -0
- data/test/fixtures/multipart/binary_file +0 -0
- data/test/fixtures/multipart/large_text_file +10 -0
- data/test/fixtures/multipart/mixed_files +0 -0
- data/test/fixtures/multipart/single_parameter +5 -0
- data/test/fixtures/multipart/text_file +10 -0
- data/test/fixtures/test/_customer_greeting.rhtml +1 -0
- data/test/fixtures/test/_hash_object.rhtml +1 -0
- data/test/fixtures/test/_person.rhtml +2 -0
- data/test/fixtures/test/action_talk_to_layout.rhtml +2 -0
- data/test/fixtures/test/content_for.rhtml +2 -0
- data/test/fixtures/test/potential_conflicts.rhtml +4 -0
- data/test/template/active_record_helper_test.rb +15 -8
- data/test/template/asset_tag_helper_test.rb +40 -16
- data/test/template/compiled_templates_tests.rb +63 -0
- data/test/template/date_helper_test.rb +80 -4
- data/test/template/form_helper_test.rb +48 -42
- data/test/template/form_options_helper_test.rb +40 -40
- data/test/template/form_tag_helper_test.rb +21 -15
- data/test/template/java_script_macros_helper_test.rb +56 -0
- data/test/template/javascript_helper_test.rb +70 -47
- data/test/template/number_helper_test.rb +2 -0
- data/test/template/tag_helper_test.rb +9 -0
- data/test/template/text_helper_test.rb +146 -1
- data/test/template/upload_progress_helper_testx.rb +11 -147
- data/test/template/url_helper_test.rb +90 -22
- data/test/testing_sandbox.rb +26 -0
- metadata +37 -7
- data/lib/action_controller/auto_complete.rb +0 -47
- data/lib/action_controller/deprecated_renders_and_redirects.rb +0 -76
- data/lib/action_controller/session.rb +0 -14
@@ -0,0 +1,258 @@
|
|
1
|
+
// Copyright (c) 2005 Marty Haught
|
2
|
+
//
|
3
|
+
// See scriptaculous.js for full license.
|
4
|
+
|
5
|
+
if(!Control) var Control = {};
|
6
|
+
Control.Slider = Class.create();
|
7
|
+
|
8
|
+
// options:
|
9
|
+
// axis: 'vertical', or 'horizontal' (default)
|
10
|
+
// increment: (default: 1)
|
11
|
+
// step: (default: 1)
|
12
|
+
//
|
13
|
+
// callbacks:
|
14
|
+
// onChange(value)
|
15
|
+
// onSlide(value)
|
16
|
+
Control.Slider.prototype = {
|
17
|
+
initialize: function(handle, track, options) {
|
18
|
+
this.handle = $(handle);
|
19
|
+
this.track = $(track);
|
20
|
+
|
21
|
+
this.options = options || {};
|
22
|
+
|
23
|
+
this.axis = this.options.axis || 'horizontal';
|
24
|
+
this.increment = this.options.increment || 1;
|
25
|
+
this.step = parseInt(this.options.step) || 1;
|
26
|
+
this.value = 0;
|
27
|
+
|
28
|
+
var defaultMaximum = Math.round(this.track.offsetWidth / this.increment);
|
29
|
+
if(this.isVertical()) defaultMaximum = Math.round(this.track.offsetHeight / this.increment);
|
30
|
+
|
31
|
+
this.maximum = this.options.maximum || defaultMaximum;
|
32
|
+
this.minimum = this.options.minimum || 0;
|
33
|
+
|
34
|
+
// Will be used to align the handle onto the track, if necessary
|
35
|
+
this.alignX = parseInt (this.options.alignX) || 0;
|
36
|
+
this.alignY = parseInt (this.options.alignY) || 0;
|
37
|
+
|
38
|
+
// Zero out the slider position
|
39
|
+
this.setCurrentLeft(Position.cumulativeOffset(this.track)[0] - Position.cumulativeOffset(this.handle)[0] + this.alignX);
|
40
|
+
this.setCurrentTop(this.trackTop() - Position.cumulativeOffset(this.handle)[1] + this.alignY);
|
41
|
+
|
42
|
+
this.offsetX = 0;
|
43
|
+
this.offsetY = 0;
|
44
|
+
|
45
|
+
this.originalLeft = this.currentLeft();
|
46
|
+
this.originalTop = this.currentTop();
|
47
|
+
this.originalZ = parseInt(this.handle.style.zIndex || "0");
|
48
|
+
|
49
|
+
// Prepopulate Slider value
|
50
|
+
this.setSliderValue(parseInt(this.options.sliderValue) || 0);
|
51
|
+
|
52
|
+
this.active = false;
|
53
|
+
this.dragging = false;
|
54
|
+
this.disabled = false;
|
55
|
+
|
56
|
+
// FIXME: use css
|
57
|
+
this.handleImage = $(this.options.handleImage) || false;
|
58
|
+
this.handleDisabled = this.options.handleDisabled || false;
|
59
|
+
this.handleEnabled = false;
|
60
|
+
if(this.handleImage)
|
61
|
+
this.handleEnabled = this.handleImage.src || false;
|
62
|
+
|
63
|
+
if(this.options.disabled)
|
64
|
+
this.setDisabled();
|
65
|
+
|
66
|
+
// Value Array
|
67
|
+
this.values = this.options.values || false; // Add method to validate and sort??
|
68
|
+
|
69
|
+
Element.makePositioned(this.handle); // fix IE
|
70
|
+
|
71
|
+
this.eventMouseDown = this.startDrag.bindAsEventListener(this);
|
72
|
+
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
73
|
+
this.eventMouseMove = this.update.bindAsEventListener(this);
|
74
|
+
this.eventKeypress = this.keyPress.bindAsEventListener(this);
|
75
|
+
|
76
|
+
Event.observe(this.handle, "mousedown", this.eventMouseDown);
|
77
|
+
Event.observe(document, "mouseup", this.eventMouseUp);
|
78
|
+
Event.observe(document, "mousemove", this.eventMouseMove);
|
79
|
+
Event.observe(document, "keypress", this.eventKeypress);
|
80
|
+
},
|
81
|
+
dispose: function() {
|
82
|
+
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
|
83
|
+
Event.stopObserving(document, "mouseup", this.eventMouseUp);
|
84
|
+
Event.stopObserving(document, "mousemove", this.eventMouseMove);
|
85
|
+
Event.stopObserving(document, "keypress", this.eventKeypress);
|
86
|
+
},
|
87
|
+
setDisabled: function(){
|
88
|
+
this.disabled = true;
|
89
|
+
if(this.handleDisabled)
|
90
|
+
this.handleImage.src = this.handleDisabled;
|
91
|
+
},
|
92
|
+
setEnabled: function(){
|
93
|
+
this.disabled = false;
|
94
|
+
if(this.handleEnabled)
|
95
|
+
this.handleImage.src = this.handleEnabled;
|
96
|
+
},
|
97
|
+
currentLeft: function() {
|
98
|
+
return parseInt(this.handle.style.left || '0');
|
99
|
+
},
|
100
|
+
currentTop: function() {
|
101
|
+
return parseInt(this.handle.style.top || '0');
|
102
|
+
},
|
103
|
+
setCurrentLeft: function(left) {
|
104
|
+
this.handle.style.left = left +"px";
|
105
|
+
},
|
106
|
+
setCurrentTop: function(top) {
|
107
|
+
this.handle.style.top = top +"px";
|
108
|
+
},
|
109
|
+
trackLeft: function(){
|
110
|
+
return Position.cumulativeOffset(this.track)[0];
|
111
|
+
},
|
112
|
+
trackTop: function(){
|
113
|
+
return Position.cumulativeOffset(this.track)[1];
|
114
|
+
},
|
115
|
+
getNearestValue: function(value){
|
116
|
+
if(this.values){
|
117
|
+
var i = 0;
|
118
|
+
var offset = Math.abs(this.values[0] - value);
|
119
|
+
var newValue = this.values[0];
|
120
|
+
|
121
|
+
for(i=0; i < this.values.length; i++){
|
122
|
+
var currentOffset = Math.abs(this.values[i] - value);
|
123
|
+
if(currentOffset < offset){
|
124
|
+
newValue = this.values[i];
|
125
|
+
offset = currentOffset;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
return newValue;
|
129
|
+
}
|
130
|
+
return value;
|
131
|
+
},
|
132
|
+
setSliderValue: function(sliderValue){
|
133
|
+
// First check our max and minimum and nearest values
|
134
|
+
sliderValue = this.getNearestValue(sliderValue);
|
135
|
+
if(sliderValue > this.maximum) sliderValue = this.maximum;
|
136
|
+
if(sliderValue < this.minimum) sliderValue = this.minimum;
|
137
|
+
var offsetDiff = (sliderValue - (this.value||this.minimum)) * this.increment;
|
138
|
+
|
139
|
+
if(this.isVertical()){
|
140
|
+
this.setCurrentTop(offsetDiff + this.currentTop());
|
141
|
+
} else {
|
142
|
+
this.setCurrentLeft(offsetDiff + this.currentLeft());
|
143
|
+
}
|
144
|
+
this.value = sliderValue;
|
145
|
+
this.updateFinished();
|
146
|
+
},
|
147
|
+
minimumOffset: function(){
|
148
|
+
return(this.isVertical() ?
|
149
|
+
this.trackTop() + this.alignY :
|
150
|
+
this.trackLeft() + this.alignX);
|
151
|
+
},
|
152
|
+
maximumOffset: function(){
|
153
|
+
return(this.isVertical() ?
|
154
|
+
this.trackTop() + this.alignY + (this.maximum - this.minimum) * this.increment :
|
155
|
+
this.trackLeft() + this.alignX + (this.maximum - this.minimum) * this.increment);
|
156
|
+
},
|
157
|
+
isVertical: function(){
|
158
|
+
return (this.axis == 'vertical');
|
159
|
+
},
|
160
|
+
startDrag: function(event) {
|
161
|
+
if(Event.isLeftClick(event)) {
|
162
|
+
if(!this.disabled){
|
163
|
+
this.active = true;
|
164
|
+
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
165
|
+
var offsets = Position.cumulativeOffset(this.handle);
|
166
|
+
this.offsetX = (pointer[0] - offsets[0]);
|
167
|
+
this.offsetY = (pointer[1] - offsets[1]);
|
168
|
+
this.originalLeft = this.currentLeft();
|
169
|
+
this.originalTop = this.currentTop();
|
170
|
+
}
|
171
|
+
Event.stop(event);
|
172
|
+
}
|
173
|
+
},
|
174
|
+
update: function(event) {
|
175
|
+
if(this.active) {
|
176
|
+
if(!this.dragging) {
|
177
|
+
var style = this.handle.style;
|
178
|
+
this.dragging = true;
|
179
|
+
if(style.position=="") style.position = "relative";
|
180
|
+
style.zIndex = this.options.zindex;
|
181
|
+
}
|
182
|
+
this.draw(event);
|
183
|
+
// fix AppleWebKit rendering
|
184
|
+
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
185
|
+
Event.stop(event);
|
186
|
+
}
|
187
|
+
},
|
188
|
+
draw: function(event) {
|
189
|
+
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
190
|
+
var offsets = Position.cumulativeOffset(this.handle);
|
191
|
+
|
192
|
+
offsets[0] -= this.currentLeft();
|
193
|
+
offsets[1] -= this.currentTop();
|
194
|
+
|
195
|
+
// Adjust for the pointer's position on the handle
|
196
|
+
pointer[0] -= this.offsetX;
|
197
|
+
pointer[1] -= this.offsetY;
|
198
|
+
var style = this.handle.style;
|
199
|
+
|
200
|
+
if(this.isVertical()){
|
201
|
+
if(pointer[1] > this.maximumOffset())
|
202
|
+
pointer[1] = this.maximumOffset();
|
203
|
+
if(pointer[1] < this.minimumOffset())
|
204
|
+
pointer[1] = this.minimumOffset();
|
205
|
+
|
206
|
+
// Increment by values
|
207
|
+
if(this.values){
|
208
|
+
this.value = this.getNearestValue(Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum);
|
209
|
+
pointer[1] = this.trackTop() + this.alignY + (this.value - this.minimum) * this.increment;
|
210
|
+
} else {
|
211
|
+
this.value = Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum;
|
212
|
+
}
|
213
|
+
style.top = pointer[1] - offsets[1] + "px";
|
214
|
+
} else {
|
215
|
+
if(pointer[0] > this.maximumOffset()) pointer[0] = this.maximumOffset();
|
216
|
+
if(pointer[0] < this.minimumOffset()) pointer[0] = this.minimumOffset();
|
217
|
+
// Increment by values
|
218
|
+
if(this.values){
|
219
|
+
this.value = this.getNearestValue(Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum);
|
220
|
+
pointer[0] = this.trackLeft() + this.alignX + (this.value - this.minimum) * this.increment;
|
221
|
+
} else {
|
222
|
+
this.value = Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum;
|
223
|
+
}
|
224
|
+
style.left = (pointer[0] - offsets[0]) + "px";
|
225
|
+
}
|
226
|
+
if(this.options.onSlide) this.options.onSlide(this.value);
|
227
|
+
},
|
228
|
+
endDrag: function(event) {
|
229
|
+
if(this.active && this.dragging) {
|
230
|
+
this.finishDrag(event, true);
|
231
|
+
Event.stop(event);
|
232
|
+
}
|
233
|
+
this.active = false;
|
234
|
+
this.dragging = false;
|
235
|
+
},
|
236
|
+
finishDrag: function(event, success) {
|
237
|
+
this.active = false;
|
238
|
+
this.dragging = false;
|
239
|
+
this.handle.style.zIndex = this.originalZ;
|
240
|
+
this.originalLeft = this.currentLeft();
|
241
|
+
this.originalTop = this.currentTop();
|
242
|
+
this.updateFinished();
|
243
|
+
},
|
244
|
+
updateFinished: function() {
|
245
|
+
if(this.options.onChange) this.options.onChange(this.value);
|
246
|
+
},
|
247
|
+
keyPress: function(event) {
|
248
|
+
if(this.active && !this.disabled) {
|
249
|
+
switch(event.keyCode) {
|
250
|
+
case Event.KEY_ESC:
|
251
|
+
this.finishDrag(event, false);
|
252
|
+
Event.stop(event);
|
253
|
+
break;
|
254
|
+
}
|
255
|
+
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
|
256
|
+
}
|
257
|
+
}
|
258
|
+
}
|
@@ -4,8 +4,8 @@ module ActionView
|
|
4
4
|
# one of the following forms: phone number, percentage, money, or precision level.
|
5
5
|
module NumberHelper
|
6
6
|
|
7
|
-
# Formats a +number+ into a US phone number string. The +options+ can be hash used to customize the format of the output.
|
8
|
-
# The area code can be surrounded by
|
7
|
+
# Formats a +number+ into a US phone number string. The +options+ can be a hash used to customize the format of the output.
|
8
|
+
# The area code can be surrounded by parentheses by setting +:area_code+ to true; default is false
|
9
9
|
# The delimiter can be set using +:delimiter+; default is "-"
|
10
10
|
# Examples:
|
11
11
|
# number_to_phone(1235551234) => 123-555-1234
|
@@ -25,7 +25,7 @@ module ActionView
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
#
|
28
|
+
# Formats a +number+ into a currency string. The +options+ hash can be used to customize the format of the output.
|
29
29
|
# The +number+ can contain a level of precision using the +precision+ key; default is 2
|
30
30
|
# The currency type can be set using the +unit+ key; default is "$"
|
31
31
|
# The unit separator can be set using the +separator+ key; default is "."
|
@@ -37,6 +37,7 @@ module ActionView
|
|
37
37
|
def number_to_currency(number, options = {})
|
38
38
|
options = options.stringify_keys
|
39
39
|
precision, unit, separator, delimiter = options.delete("precision") { 2 }, options.delete("unit") { "$" }, options.delete("separator") { "." }, options.delete("delimiter") { "," }
|
40
|
+
separator = "" unless precision > 0
|
40
41
|
begin
|
41
42
|
parts = number_with_precision(number, precision).split('.')
|
42
43
|
unit + number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s
|
@@ -18,7 +18,8 @@ module ActionView
|
|
18
18
|
}
|
19
19
|
end
|
20
20
|
|
21
|
-
# Creates a basic HTML link bar for the given +paginator+.
|
21
|
+
# Creates a basic HTML link bar for the given +paginator+.
|
22
|
+
# +html_options+ are passed to +link_to+.
|
22
23
|
#
|
23
24
|
# +options+ are:
|
24
25
|
# <tt>:name</tt>:: the routing name for this paginator
|
@@ -33,39 +34,53 @@ module ActionView
|
|
33
34
|
# +false+)
|
34
35
|
# <tt>:params</tt>:: any additional routing parameters
|
35
36
|
# for page URLs
|
36
|
-
def pagination_links(paginator, options={})
|
37
|
-
|
37
|
+
def pagination_links(paginator, options={}, html_options={})
|
38
|
+
name = options[:name] || DEFAULT_OPTIONS[:name]
|
39
|
+
params = (options[:params] || DEFAULT_OPTIONS[:params]).clone
|
38
40
|
|
39
|
-
|
41
|
+
pagination_links_each(paginator, options) do |n|
|
42
|
+
params[name] = n
|
43
|
+
link_to(n.to_s, params, html_options)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Iterate through the pages of a given +paginator+, invoking a
|
48
|
+
# block for each page number that needs to be rendered as a link.
|
49
|
+
def pagination_links_each(paginator, options)
|
50
|
+
options = DEFAULT_OPTIONS.merge(options)
|
51
|
+
link_to_current_page = options[:link_to_current_page]
|
52
|
+
always_show_anchors = options[:always_show_anchors]
|
40
53
|
|
41
|
-
|
42
|
-
|
54
|
+
current_page = paginator.current_page
|
55
|
+
window_pages = current_page.window(options[:window_size]).pages
|
56
|
+
return if window_pages.length <= 1 unless link_to_current_page
|
43
57
|
|
44
58
|
first, last = paginator.first, paginator.last
|
45
59
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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 }.update( options[:params] ))
|
58
|
-
end
|
59
|
-
html << ' '
|
60
|
-
end
|
60
|
+
html = ''
|
61
|
+
if always_show_anchors and not (wp_first = window_pages[0]).first?
|
62
|
+
html << yield(first.number)
|
63
|
+
html << ' ... ' if wp_first.number - first.number > 1
|
64
|
+
html << ' '
|
65
|
+
end
|
61
66
|
|
62
|
-
|
63
|
-
|
64
|
-
html <<
|
67
|
+
window_pages.each do |page|
|
68
|
+
if current_page == page && !link_to_current_page
|
69
|
+
html << page.number.to_s
|
70
|
+
else
|
71
|
+
html << yield(page.number)
|
65
72
|
end
|
73
|
+
html << ' '
|
66
74
|
end
|
75
|
+
|
76
|
+
if always_show_anchors and not (wp_last = window_pages[-1]).last?
|
77
|
+
html << ' ... ' if last.number - wp_last.number > 1
|
78
|
+
html << yield(last.number)
|
79
|
+
end
|
80
|
+
|
81
|
+
html
|
67
82
|
end
|
68
83
|
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
84
|
+
end # PaginationHelper
|
85
|
+
end # Helpers
|
86
|
+
end # ActionView
|
@@ -10,27 +10,41 @@ module ActionView
|
|
10
10
|
# Examples:
|
11
11
|
# * <tt>tag("br") => <br /></tt>
|
12
12
|
# * <tt>tag("input", { "type" => "text"}) => <input type="text" /></tt>
|
13
|
-
def tag(name, options =
|
14
|
-
"<#{name}#{tag_options(options)}" + (open ? ">" : " />")
|
13
|
+
def tag(name, options = nil, open = false)
|
14
|
+
"<#{name}#{tag_options(options.stringify_keys) if options}" + (open ? ">" : " />")
|
15
15
|
end
|
16
16
|
|
17
17
|
# Examples:
|
18
18
|
# * <tt>content_tag("p", "Hello world!") => <p>Hello world!</p></tt>
|
19
19
|
# * <tt>content_tag("div", content_tag("p", "Hello world!"), "class" => "strong") => </tt>
|
20
20
|
# <tt><div class="strong"><p>Hello world!</p></div></tt>
|
21
|
-
def content_tag(name, content, options =
|
22
|
-
"<#{name}#{tag_options(options)}>#{content}</#{name}>"
|
21
|
+
def content_tag(name, content, options = nil)
|
22
|
+
"<#{name}#{tag_options(options.stringify_keys) if options}>#{content}</#{name}>"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a CDATA section for the given +content+. CDATA sections
|
26
|
+
# are used to escape blocks of text containing characters which would
|
27
|
+
# otherwise be recognized as markup. CDATA sections begin with the string
|
28
|
+
# <tt><![CDATA[</tt> and end with (and may not contain) the string
|
29
|
+
# <tt>]]></tt>.
|
30
|
+
def cdata_section(content)
|
31
|
+
"<![CDATA[#{content}]]>"
|
23
32
|
end
|
24
33
|
|
25
34
|
private
|
26
35
|
def tag_options(options)
|
27
|
-
cleaned_options = options.reject {
|
28
|
-
unless cleaned_options.empty?
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
36
|
+
cleaned_options = convert_booleans(options.stringify_keys.reject {|key, value| value.nil?})
|
37
|
+
' ' + cleaned_options.map {|key, value| %(#{key}="#{html_escape(value.to_s)}")}.sort * ' ' unless cleaned_options.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def convert_booleans(options)
|
41
|
+
%w( disabled readonly multiple ).each { |a| boolean_attribute(options, a) }
|
42
|
+
options
|
43
|
+
end
|
44
|
+
|
45
|
+
def boolean_attribute(options, attribute)
|
46
|
+
options[attribute] ? options[attribute] = attribute : options.delete(attribute)
|
33
47
|
end
|
34
48
|
end
|
35
49
|
end
|
36
|
-
end
|
50
|
+
end
|
@@ -3,14 +3,14 @@ require File.dirname(__FILE__) + '/tag_helper'
|
|
3
3
|
module ActionView
|
4
4
|
module Helpers #:nodoc:
|
5
5
|
# Provides a set of methods for working with text strings that can help unburden the level of inline Ruby code in the
|
6
|
-
# templates. In the example below we iterate over a collection of posts provided to the template and
|
6
|
+
# templates. In the example below we iterate over a collection of posts provided to the template and print each title
|
7
7
|
# after making sure it doesn't run longer than 20 characters:
|
8
8
|
# <% for post in @posts %>
|
9
9
|
# Title: <%= truncate(post.title, 20) %>
|
10
10
|
# <% end %>
|
11
|
-
module TextHelper
|
11
|
+
module TextHelper
|
12
12
|
# The regular puts and print are outlawed in eRuby. It's recommended to use the <%= "hello" %> form instead of print "hello".
|
13
|
-
# If you absolutely must use a method-based output, you can use concat. It's
|
13
|
+
# If you absolutely must use a method-based output, you can use concat. It's used like this: <% concat "hello", binding %>. Notice that
|
14
14
|
# it doesn't have an equal sign in front. Using <%= concat "hello" %> would result in a double hello.
|
15
15
|
def concat(string, binding)
|
16
16
|
eval("_erbout", binding).concat(string)
|
@@ -20,7 +20,13 @@ module ActionView
|
|
20
20
|
# if the +text+ is longer than +length+.
|
21
21
|
def truncate(text, length = 30, truncate_string = "...")
|
22
22
|
if text.nil? then return end
|
23
|
-
|
23
|
+
|
24
|
+
if $KCODE == "NONE"
|
25
|
+
text.length > length ? text[0..(length - 3)] + truncate_string : text
|
26
|
+
else
|
27
|
+
chars = text.split(//)
|
28
|
+
chars.length > length ? chars[0..(length-3)].join + truncate_string : text
|
29
|
+
end
|
24
30
|
end
|
25
31
|
|
26
32
|
# Highlights the +phrase+ where it is found in the +text+ by surrounding it like
|
@@ -29,7 +35,7 @@ module ActionView
|
|
29
35
|
# N.B.: The +phrase+ is sanitized to include only letters, digits, and spaces before use.
|
30
36
|
def highlight(text, phrase, highlighter = '<strong class="highlight">\1</strong>')
|
31
37
|
if phrase.blank? then return text end
|
32
|
-
text.gsub(/(#{
|
38
|
+
text.gsub(/(#{Regexp.escape(phrase)})/i, highlighter) unless text.nil?
|
33
39
|
end
|
34
40
|
|
35
41
|
# Extracts an excerpt from the +text+ surrounding the +phrase+ with a number of characters on each side determined
|
@@ -37,7 +43,7 @@ module ActionView
|
|
37
43
|
# excerpt("hello my world", "my", 3) => "...lo my wo..."
|
38
44
|
def excerpt(text, phrase, radius = 100, excerpt_string = "...")
|
39
45
|
if text.nil? || phrase.nil? then return end
|
40
|
-
phrase =
|
46
|
+
phrase = Regexp.escape(phrase)
|
41
47
|
|
42
48
|
if found_pos = text =~ /(#{phrase})/i
|
43
49
|
start_pos = [ found_pos - radius, 0 ].max
|
@@ -76,7 +82,13 @@ module ActionView
|
|
76
82
|
# Returns the text with all the Textile codes turned into HTML-tags.
|
77
83
|
# <i>This method is only available if RedCloth can be required</i>.
|
78
84
|
def textilize(text)
|
79
|
-
text.blank?
|
85
|
+
if text.blank?
|
86
|
+
""
|
87
|
+
else
|
88
|
+
textilized = RedCloth.new(text, [ :hard_breaks ])
|
89
|
+
textilized.hard_breaks = true if textilized.respond_to?("hard_breaks=")
|
90
|
+
textilized.to_html
|
91
|
+
end
|
80
92
|
end
|
81
93
|
|
82
94
|
# Returns the text with all the Textile codes turned into HTML-tags, but without the regular bounding <p> tag.
|
@@ -103,7 +115,7 @@ module ActionView
|
|
103
115
|
# We can't really help what's not there
|
104
116
|
end
|
105
117
|
|
106
|
-
# Returns +text+ transformed into
|
118
|
+
# Returns +text+ transformed into HTML using very simple formatting rules
|
107
119
|
# Surrounds paragraphs with <tt><p></tt> tags, and converts line breaks into <tt><br /></tt>
|
108
120
|
# Two consecutive newlines(<tt>\n\n</tt>) are considered as a paragraph, one newline (<tt>\n</tt>) is
|
109
121
|
# considered a linebreak, three or more consecutive newlines are turned into two newlines
|
@@ -189,18 +201,112 @@ module ActionView
|
|
189
201
|
|
190
202
|
html
|
191
203
|
end
|
204
|
+
|
205
|
+
# Returns a Cycle object whose to_s value cycles through items of an
|
206
|
+
# array every time it is called. This can be used to alternate classes
|
207
|
+
# for table rows:
|
208
|
+
#
|
209
|
+
# <%- for item in @items do -%>
|
210
|
+
# <tr class="<%= cycle("even", "odd") %>">
|
211
|
+
# ... use item ...
|
212
|
+
# </tr>
|
213
|
+
# <%- end -%>
|
214
|
+
#
|
215
|
+
# You can use named cycles to prevent clashes in nested loops. You'll
|
216
|
+
# have to reset the inner cycle, manually:
|
217
|
+
#
|
218
|
+
# <%- for item in @items do -%>
|
219
|
+
# <tr class="<%= cycle("even", "odd", :name => "row_class")
|
220
|
+
# <td>
|
221
|
+
# <%- for value in item.values do -%>
|
222
|
+
# <span style="color:'<%= cycle("red", "green", "blue"
|
223
|
+
# :name => "colors") %>'">
|
224
|
+
# item
|
225
|
+
# </span>
|
226
|
+
# <%- end -%>
|
227
|
+
# <%- reset_cycle("colors") -%>
|
228
|
+
# </td>
|
229
|
+
# </tr>
|
230
|
+
# <%- end -%>
|
231
|
+
def cycle(first_value, *values)
|
232
|
+
if (values.last.instance_of? Hash)
|
233
|
+
params = values.pop
|
234
|
+
name = params[:name]
|
235
|
+
else
|
236
|
+
name = "default"
|
237
|
+
end
|
238
|
+
values.unshift(first_value)
|
192
239
|
|
240
|
+
cycle = get_cycle(name)
|
241
|
+
if (cycle.nil? || cycle.values != values)
|
242
|
+
cycle = set_cycle(name, Cycle.new(*values))
|
243
|
+
end
|
244
|
+
return cycle.to_s
|
245
|
+
end
|
246
|
+
|
247
|
+
# Resets a cycle so that it starts from the first element in the array
|
248
|
+
# the next time it is used.
|
249
|
+
def reset_cycle(name = "default")
|
250
|
+
cycle = get_cycle(name)
|
251
|
+
return if cycle.nil?
|
252
|
+
cycle.reset
|
253
|
+
end
|
254
|
+
|
255
|
+
class Cycle #:nodoc:
|
256
|
+
attr_reader :values
|
257
|
+
|
258
|
+
def initialize(first_value, *values)
|
259
|
+
@values = values.unshift(first_value)
|
260
|
+
reset
|
261
|
+
end
|
262
|
+
|
263
|
+
def reset
|
264
|
+
@index = 0
|
265
|
+
end
|
266
|
+
|
267
|
+
def to_s
|
268
|
+
value = @values[@index].to_s
|
269
|
+
@index = (@index + 1) % @values.size
|
270
|
+
return value
|
271
|
+
end
|
272
|
+
end
|
193
273
|
|
194
274
|
private
|
195
|
-
#
|
196
|
-
|
197
|
-
|
275
|
+
# The cycle helpers need to store the cycles in a place that is
|
276
|
+
# guaranteed to be reset every time a page is rendered, so it
|
277
|
+
# uses an instance variable of ActionView::Base.
|
278
|
+
def get_cycle(name)
|
279
|
+
@_cycles = Hash.new if @_cycles.nil?
|
280
|
+
return @_cycles[name]
|
198
281
|
end
|
282
|
+
|
283
|
+
def set_cycle(name, cycle_object)
|
284
|
+
@_cycles = Hash.new if @_cycles.nil?
|
285
|
+
@_cycles[name] = cycle_object
|
286
|
+
end
|
287
|
+
|
288
|
+
AUTO_LINK_RE = /
|
289
|
+
( # leading text
|
290
|
+
<\w+.*?>| # leading HTML tag, or
|
291
|
+
[^=!:'"\/]| # leading punctuation, or
|
292
|
+
^ # beginning of line
|
293
|
+
)
|
294
|
+
(
|
295
|
+
(?:http[s]?:\/\/)| # protocol spec, or
|
296
|
+
(?:www\.) # www.*
|
297
|
+
)
|
298
|
+
(
|
299
|
+
([\w]+[=?&\/.-]?)* # url segment
|
300
|
+
\w+[\/]? # url tail
|
301
|
+
(?:\#\w*)? # trailing anchor
|
302
|
+
)
|
303
|
+
([[:punct:]]|\s|<|$) # trailing text
|
304
|
+
/x unless const_defined?(:AUTO_LINK_RE)
|
199
305
|
|
200
306
|
# Turns all urls into clickable links.
|
201
307
|
def auto_link_urls(text, href_options = {})
|
202
|
-
text.gsub(
|
203
|
-
all, a, b, c, d = $&, $1, $2, $3, $
|
308
|
+
text.gsub(AUTO_LINK_RE) do
|
309
|
+
all, a, b, c, d = $&, $1, $2, $3, $5
|
204
310
|
if a =~ /<a\s/i # don't replace URL's that are already linked
|
205
311
|
all
|
206
312
|
else
|