aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb82
-rw-r--r--actionpack/test/template/text_helper_test.rb89
3 files changed, 173 insertions, 0 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 0e0b47f584..16d70e5bf7 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Added TextHelper#cycle to cycle over an array of values on each hit (useful for alternating row colors etc) #2154 [dave-ml@dribin.org]
+
* Ensure that request.path never returns nil. Closes #1675 [Nicholas Seckar]
* Add ability to specify Route Regexps for controllers. Closes #1917. [Sebastian Kanthak]
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 217cf6c9f8..413f32532d 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -189,9 +189,91 @@ module ActionView
html
end
+
+ # Returns a Cycle object whose to_s value cycles through items of an
+ # array every time it is called. This can be used to alternate classes
+ # for table rows:
+ #
+ # <%- for item in @items do -%>
+ # <tr class="<%= cycle("even", "odd") %>">
+ # ... use item ...
+ # </tr>
+ # <%- end -%>
+ #
+ # You can use named cycles to prevent clashes in nested loops. You'll
+ # have to reset the inner cycle, manually:
+ #
+ # <%- for item in @items do -%>
+ # <tr class="<%= cycle("even", "odd", :name => "row_class")
+ # <td>
+ # <%- for value in item.values do -%>
+ # <span style="color:'<%= cycle("red", "green", "blue"
+ # :name => "colors") %>'">
+ # item
+ # </span>
+ # <%- end -%>
+ # <%- reset_cycle("colors") -%>
+ # </td>
+ # </tr>
+ # <%- end -%>
+ def cycle(first_value, *values)
+ if (values.last.instance_of? Hash)
+ params = values.pop
+ name = params[:name]
+ else
+ name = "default"
+ end
+ values.unshift(first_value)
+
+ cycle = get_cycle(name)
+ if (cycle.nil? || cycle.values != values)
+ cycle = set_cycle(name, Cycle.new(*values))
+ end
+ return cycle.to_s
+ end
+
+ # Resets a cycle so that it starts from the first element in the array
+ # the next time it is used.
+ def reset_cycle(name = "default")
+ cycle = get_cycle(name)
+ return if cycle.nil?
+ cycle.reset
+ end
+
+ class Cycle
+ attr_reader :values
+
+ def initialize(first_value, *values)
+ @values = values.unshift(first_value)
+ reset
+ end
+
+ def reset
+ @index = 0
+ end
+
+ def to_s
+ value = @values[@index].to_s
+ @index = (@index + 1) % @values.size
+ return value
+ end
+ end
private
+ # The cycle helpers need to store the cycles in a place that is
+ # guaranteed to be reset every time a page is rendered, so it
+ # uses an instance variable of ActionView::Base.
+ def get_cycle(name)
+ @_cycles = Hash.new if @_cycles.nil?
+ return @_cycles[name]
+ end
+
+ def set_cycle(name, cycle_object)
+ @_cycles = Hash.new if @_cycles.nil?
+ @_cycles[name] = cycle_object
+ end
+
# Returns a version of the text that's safe to use in a regular expression without triggering engine features.
def escape_regexp(text)
text.gsub(/([\\|?+*\/\)\(])/) { |m| "\\#{$1}" }
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index 9b704711e1..ed0f3ce91c 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -8,6 +8,12 @@ class TextHelperTest < Test::Unit::TestCase
include ActionView::Helpers::TextHelper
include ActionView::Helpers::TagHelper
+ def setup
+ # This simulates the fact that instance variables are reset every time
+ # a view is rendered. The cycle helper depends on this behavior.
+ @_cycles = nil if (defined? @_cycles)
+ end
+
def test_simple_format
assert_equal "<p>crazy\n<br /> cross\n<br /> platform linebreaks</p>", simple_format("crazy\r\n cross\r platform linebreaks")
assert_equal "<p>A paragraph</p>\n\n<p>and another one!</p>", simple_format("A paragraph\n\nand another one!")
@@ -137,4 +143,87 @@ class TextHelperTest < Test::Unit::TestCase
assert_equal %{href="javascript:bang" <a name='hello'>foo</a>, <span>bar</span>}, result
end
+ def test_cycle_class
+ value = Cycle.new("one", 2, "3")
+ assert_equal("one", value.to_s)
+ assert_equal("2", value.to_s)
+ assert_equal("3", value.to_s)
+ assert_equal("one", value.to_s)
+ value.reset
+ assert_equal("one", value.to_s)
+ assert_equal("2", value.to_s)
+ assert_equal("3", value.to_s)
+ end
+
+ def test_cycle_class_with_no_arguments
+ assert_raise(ArgumentError) { value = Cycle.new() }
+ end
+
+ def test_cycle
+ assert_equal("one", cycle("one", 2, "3"))
+ assert_equal("2", cycle("one", 2, "3"))
+ assert_equal("3", cycle("one", 2, "3"))
+ assert_equal("one", cycle("one", 2, "3"))
+ assert_equal("2", cycle("one", 2, "3"))
+ assert_equal("3", cycle("one", 2, "3"))
+ end
+
+ def test_cycle_with_no_arguments
+ assert_raise(ArgumentError) { value = cycle() }
+ end
+
+ def test_cycle_resets_with_new_values
+ assert_equal("even", cycle("even", "odd"))
+ assert_equal("odd", cycle("even", "odd"))
+ assert_equal("even", cycle("even", "odd"))
+ assert_equal("1", cycle(1, 2, 3))
+ assert_equal("2", cycle(1, 2, 3))
+ assert_equal("3", cycle(1, 2, 3))
+ assert_equal("1", cycle(1, 2, 3))
+ end
+
+ def test_named_cycles
+ assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ assert_equal("2", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("blue", cycle("red", "blue", :name => "colors"))
+ assert_equal("3", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ end
+
+ def test_default_named_cycle
+ assert_equal("1", cycle(1, 2, 3))
+ assert_equal("2", cycle(1, 2, 3, :name => "default"))
+ assert_equal("3", cycle(1, 2, 3))
+ end
+
+ def test_reset_cycle
+ assert_equal("1", cycle(1, 2, 3))
+ assert_equal("2", cycle(1, 2, 3))
+ reset_cycle
+ assert_equal("1", cycle(1, 2, 3))
+ end
+
+ def test_reset_unknown_cycle
+ reset_cycle("colors")
+ end
+
+ def test_recet_named_cycle
+ assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ reset_cycle("numbers")
+ assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("blue", cycle("red", "blue", :name => "colors"))
+ assert_equal("2", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ end
+
+ def test_cycle_no_instance_variable_clashes
+ @cycles = %w{Specialized Fuji Giant}
+ assert_equal("red", cycle("red", "blue"))
+ assert_equal("blue", cycle("red", "blue"))
+ assert_equal("red", cycle("red", "blue"))
+ assert_equal(%w{Specialized Fuji Giant}, @cycles)
+ end
+
end