Motivation and Problem
There are several libraries for generating html markup strings using ruby (erb, haml, builder, markaby, tagz, ...), but I am not satisfied with any of them. The reason is that, except for erb, they take the nesting style rather than the chain style. And erb is a way to embed ruby within html rather than generating html with ruby.
To my understanding, one beauty of ruby lies in encouraging the use of chain style:
receiver.method1(args1).method2(args2). ... method_n(args_n)
instead of doing a nesting style:
method_n(...method2(method1(receiver, args1), args2), ... args_n)
But libraries mentioned above (except for erb) take the nesting style (sometimes with the help of block arguments).
My Idea
For my own purpose, I wrote a method dom
so that I can do html markup in a chain style. When applied to a string, this example
"This is a link to SO".dom(:a, href: "http://stackoverflow.com").dom(:body).dom(:html)
will generate:
<html><body><a href="http://stackoverflow.com";>This is a link to SO</a></body></html>
When applied to an array, this:
[
["a".dom(:td), "b".dom(:td)].dom(:tr),
["c".dom(:td), "d".dom(:td)].dom(:tr)
].dom(:table, border: 1)
will generate
<table border="1";>
<tr>
<td>"a"</td>
<td>"b"</td>
</tr>
<tr>
<td>"c"</td>
<td>"d"</td>
</tr>
<table>
And, when applied without an explicit receiver (outside of a domain of strings and arrays),
dom(:img, src: "picture.jpg", width: 48, height: 48)
will generate
<img src="picture.jpg";width="48";height="48";/>
Note that all is done with just one method dom
. This is much simpler than using other libraries. It is also flexible in that it is not affected by a change in the inventory of html tags; you just specify that with a symbol argument. In other libraries, there are classes and/or methods for each tag. Furthuremore, unlike erb, it is pure ruby. It is not a DSL that needs to be convertred.
My Implementation
The implementation is as follows:
class Hash
def attribute
map{|k, v| %Q{#{k}#{
case v
when TrueClass; ''
when Hash; %Q{="#{v.subattribute}"}
else %Q{="#{v}"}
end
;}}}.join
end
def subattribute
map{|k, v| %Q{#{k}:#{v};}}.join
end
end
class Array
def dom type, hash = {}
"<#{type} #{hash.attribute}>\n#{join("\n").gsub(/^/, " ")}\n</#{type}>"
end
end
class String
def dom type, hash = {}
"<#{type} #{hash.attribute}>#{self}</#{type}>"
end
end
class Object
def dom type, hash = {}
"<#{type} #{hash.attribute}/>"
end
end
Questions
<input type="text";readonly>
instead of <input type="text";readonly="true">
. In my present implementation, I can do that by passing true
(which will not be used in the end) as the value for such attribute like dom(:input, type: "text", readonly: true)
, but that seems redundant and is also part of the reason that I have case
statement in the code, making it slower. Is there a better way to do this?The main reason of haml and most other nested stuff is that it is basically easy to look how your HTML is nested. I am assuming this but do you actually code HTML or do you do more backend stuff? The reason for it is that in nested style, you will see how your elements are nested(which is important to you if you are also writing the styles)
While it is quite easier to write
.dom(:body).dom(:html)
it is difficult for a designer to see how the HTML flows without mapping and trying it to visualize in his head, whereas:
%html
%body
does that already with just one look.
Take not of a longer example:
"This is a link to SO".dom(:a, href: "http://stackoverflow.com").dom(:body).dom(:html)
Would it be easier for you if say the client or you needed to add a clickable image to the link? How would you have done it? In haml it is easy as:
%html
%body
%a{:href => "blah"}
= image_tag("image.png")
Also, IMHO, writing dom(:)
for each html tag is just as tedious as writing the closing tag for it(which HAML and the others fixed)
Again, these are just my opinions from an XHTML/CSS programmer(as opposed to a ruby programmer perspective)
And lastly, I would also think that this can be turned into a community wiki or something as this doesn't merit an exact answer and will probably spawn a lot of subjective ones like this one.