sawa sawa - 7 months ago 18
Ruby Question

Chain style html markup

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


  • Are there already stable libraries that do a similar thing?

  • What will be the potential problems to this approach (particularly to my implementation or to doing this in chain approach)?

  • Some attributes take boolean values, which are often encouraged to be omitted. For example,
    <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?

  • Are there any possible improvements to the implementation?


Answer

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.