[Haml] Allow :class and :id attributes to accept arrays, which will be joined.

This commit is contained in:
ronen barzel 2010-02-27 07:52:09 -06:00 committed by Nathan Weizenbaum
parent 6324cfe058
commit 782f44f553
8 changed files with 141 additions and 0 deletions

View File

@ -66,6 +66,40 @@ won't do any indentation of their arguments.
Their `#to_s` method will be called to convert them to strings.
Previously, this only worked for attributes other than `class`.
### `:class` and `:id` Attributes Accept Ruby Arrays
In an attribute hash, the `:class` attribute now accepts an Array
whose elements will be converted to strings and joined with `" "`.
Likewise, the `:id` attribute now accepts an Array
whose elements will be converted to strings and joined with `"_"`.
The array will first be flattened and any elements that do not test as true
will be stripped out. For example:
.column{:class => [@item.type, @item == @sortcol && [:sort, @sortdir]] }
could render as any of:
class="column numeric sort ascending"
class="column numeric"
class="column sort descending"
class="column"
depending on whether `@item.type` is `"numeric"` or `nil`,
whether `@item == @sortcol`,
and whether `@sortdir` is `"ascending"` or `"descending"`.
A single value can still be specified.
If that value evaluates to false it is ignored;
otherwise it gets converted to a string.
For example:
.item{:class => @item.is_empty? && "empty"}
could render as either of:
class="item"
class="item empty"
### More Powerful `:autoclose` Option
The {file:HAML_REFERENCE.md#attributes_option `:attributes`} option

View File

@ -310,6 +310,50 @@ is compiled to:
<script src='javascripts/script_9' type='text/javascript'></script>
#### `:class` and `:id` Attributes
{#class-and-id-attributes}
The `:class` and `:id` attributes can also be specified as a Ruby array
whose elements will be joined together.
A `:class` array is joined with `" "`
and an `:id` array is joined with `"_"`.
For example:
%div{:id => [@item.type, @item.number], :class => [@item.type, @item.urgency]}
is equivalent to:
%div{:id => "#{@item.type}_#{@item.number}", :class => "#{@item.type} #{@item.urgency}"}
The array will first be flattened
and any elements that do not test as true will be removed.
The remaining elements will be converted to strings.
For example:
%div{:class => [@item.type, @item == @sortcol && [:sort, @sortdir]] } Contents
could render as any of:
<div class="column numeric sort ascending">Contents</div>
<div class="column numeric">Contents</div>
<div class="column sort descending">Contents</div>
<div class="column">Contents</div>
depending on whether `@item.type` is `"numeric"` or `nil`,
whether `@item == @sortcol`,
and whether `@sortdir` is `"ascending"` or `"descending"`.
If a single value is specified and it evaluates to false it is ignored;
otherwise it gets converted to a string.
For example:
.item{:class => @item.is_empty? && "empty"}
could render as either of:
class="item"
class="item empty"
#### HTML-style Attributes: `()`
Haml also supports a terser, less Ruby-specific attribute syntax
@ -472,6 +516,22 @@ is compiled to:
</div>
</div>
These shortcuts can be combined with long-hand attributes;
the two values will be merged together
as though they were all placed in an array
(see [the documentation on `:class` and `:id` attributes](#class-and-id-attributes)).
For example:
%div#Article.article.entry{:id => @article.number, :class => @article.visibility}
is equivalent to
%div{:id => ['Article', @article.number], :class => ['article', 'entry', @article.visibility]} Gabba Hey
and could compile to:
<div class="article entry visible" id="Article_27">Gabba Hey</div>
#### Implicit Div Elements
Because divs are used so often, they're the default elements.

View File

@ -244,12 +244,14 @@ RUBY
# @param from [{String => #to_s}] The attribute hash to merge from
# @return [{String => String}] `to`, after being merged
def self.merge_attrs(to, from)
from['id'] = Precompiler.filter_and_join(from['id'], '_') if from['id']
if to['id'] && from['id']
to['id'] << '_' << from.delete('id').to_s
elsif to['id'] || from['id']
from['id'] ||= to['id']
end
from['class'] = Precompiler.filter_and_join(from['class'], ' ') if from['class']
if to['class'] && from['class']
# Make sure we don't duplicate class names
from['class'] = (from['class'].to_s.split(' ') | to['class'].split(' ')).sort.join(' ')

View File

@ -545,6 +545,9 @@ END
result = attributes.collect do |attr, value|
next if value.nil?
value = filter_and_join(value, ' ') if attr == :class
value = filter_and_join(value, '_') if attr == :id
if value == true
next " #{attr}" if is_html
next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
@ -568,6 +571,11 @@ END
result.compact.sort.join
end
def self.filter_and_join(value, separator)
value = [value] unless value.is_a?(Array)
return value.flatten.collect {|item| item ? item.to_s : nil}.compact.join(separator)
end
def prerender_tag(name, self_close, attributes)
attributes_string = Precompiler.build_attributes(html?, @options[:attr_wrapper], attributes)
"<#{name}#{attributes_string}#{self_close && xhtml? ? ' /' : ''}>"

View File

@ -137,6 +137,29 @@ MESSAGE
assert_equal("<p class='3'>foo</p>", render("%p{:class => 1+2} foo").chomp)
end
def test_class_attr_with_array
assert_equal("<p class='a b'>foo</p>\n", render("%p{:class => %w[a b]} foo")) # basic
assert_equal("<p class='a b css'>foo</p>\n", render("%p.css{:class => %w[a b]} foo")) # merge with css
assert_equal("<p class='b css'>foo</p>\n", render("%p.css{:class => %w[css b]} foo")) # merge uniquely
assert_equal("<p class='a b c d'>foo</p>\n", render("%p{:class => [%w[a b], %w[c d]]} foo")) # flatten
assert_equal("<p class='a b'>foo</p>\n", render("%p{:class => [:a, :b] } foo")) # stringify
assert_equal("<p class=''>foo</p>\n", render("%p{:class => [nil, false] } foo")) # strip falsey
assert_equal("<p class='a'>foo</p>\n", render("%p{:class => :a} foo")) # single stringify
assert_equal("<p>foo</p>\n", render("%p{:class => false} foo")) # single falsey
assert_equal("<p class='a b html'>foo</p>\n", render("%p(class='html'){:class => %w[a b]} foo")) # html attrs
end
def test_id_attr_with_array
assert_equal("<p id='a_b'>foo</p>\n", render("%p{:id => %w[a b]} foo")) # basic
assert_equal("<p id='css_a_b'>foo</p>\n", render("%p#css{:id => %w[a b]} foo")) # merge with css
assert_equal("<p id='a_b_c_d'>foo</p>\n", render("%p{:id => [%w[a b], %w[c d]]} foo")) # flatten
assert_equal("<p id='a_b'>foo</p>\n", render("%p{:id => [:a, :b] } foo")) # stringify
assert_equal("<p id=''>foo</p>\n", render("%p{:id => [nil, false] } foo")) # strip falsey
assert_equal("<p id='a'>foo</p>\n", render("%p{:id => :a} foo")) # single stringify
assert_equal("<p>foo</p>\n", render("%p{:id => false} foo")) # single falsey
assert_equal("<p id='html_a_b'>foo</p>\n", render("%p(id='html'){:id => %w[a b]} foo")) # html attrs
end
def test_dynamic_attributes_with_no_content
assert_equal(<<HTML, render(<<HAML))
<p>

View File

@ -198,6 +198,16 @@ HAML
assert_equal("<br class='foo' />\n", render("- haml_tag :br, :class => 'foo'"))
end
def test_haml_tag_with_class_array
assert_equal("<p class='a b'>foo</p>\n", render("- haml_tag :p, 'foo', :class => %w[a b]"))
assert_equal("<p class='a b c d'>foo</p>\n", render("- haml_tag 'p.c.d', 'foo', :class => %w[a b]"))
end
def test_haml_tag_with_id_array
assert_equal("<p id='a_b'>foo</p>\n", render("- haml_tag :p, 'foo', :id => %w[a b]"))
assert_equal("<p id='c_a_b'>foo</p>\n", render("- haml_tag 'p#c', 'foo', :id => %w[a b]"))
end
def test_haml_tag_non_autoclosed_tags_arent_closed
assert_equal("<p></p>\n", render("- haml_tag :p"))
end

View File

@ -56,7 +56,9 @@ testtest
</br>
<p class='article bar foo' id='article_1'>Blah</p>
<p class='article foo' id='article_1'>Blah</p>
<p class='article bar baz foo' id='article_1'>Blah</p>
<p class='article quux qux' id='article_1'>Blump</p>
<p class='article' id='foo_bar_baz_article_1'>Whee</p>
Woah inner quotes
<p class='dynamic_quote' dyn='3' quotes="single '"></p>
<p class='dynamic_self_closing' dyn='3' />

View File

@ -70,7 +70,9 @@
Nested content
%p.foo{:class => true ? 'bar' : 'baz'}[@article] Blah
%p.foo{:class => false ? 'bar' : ''}[@article] Blah
%p.foo{:class => %w[bar baz]}[@article] Blah
%p.qux{:class => 'quux'}[@article] Blump
%p#foo{:id => %w[bar baz]}[@article] Whee
== #{"Woah inner quotes"}
%p.dynamic_quote{:quotes => "single '", :dyn => 1 + 2}
%p.dynamic_self_closing{:dyn => 1 + 2}/