##
# Borges::HTMLBuilder is a generic class for outputting HTML.
class Borges::HtmlBuilder
SIMPLE_ELEMENTS = {
:bold => :b,
:code => :code,
:div => :div,
:emphasis => :em,
:italic => :i,
:paragraph => :p,
:preformatted => :pre,
:strong => :strong,
:small => :small,
:span => :span,
:sub => :sub,
:sup => :sup,
:table_data => :td,
:table_heading => :th,
:underline => :u,
}
HTML_CHARACTERS = {}
attr_accessor :document
attr_reader :attributes
SIMPLE_ELEMENTS.each do |method, element|
eval %^
def #{method}(obj = nil, &block)
return tag_do(:#{element}, obj) unless obj.nil?
return tag_do(:#{element}, block)
end
^
end
##
# Creates a line break.
def break
tag(:br)
end
##
# Close the last open element.
def close
@document.pop_element
end
##
# Adds a class attribute that contains +classes+
def css_class(classes)
@attributes[:class] = classes
end
##
# Creates a
that has an id attribute containing +name+.
def div_named(name, obj = nil, &block)
element_id(name)
div(obj, &block)
end
##
# Creates a
that has an class attribute containing
# +classes+.
def div_with_class(classes, obj = nil, &block)
css_class(classes)
div(obj, &block)
end
##
# Adds an id attribute containing +e_id+
def element_id(e_id)
@attributes[:id] = e_id
end
##
# Turns an +obj+ into an HTML encoded string
#
# encode "foo > bar" # => "foo > bar"
def encode(obj)
obj.to_s.gsub(/./) do |char|
HTML_CHARACTERS[char[0]]
end
end
##
# Adds +obj+ as an HTML encoded string.
def encode_text(obj)
text encode(obj)
end
##
# Creates a
labeled +legend+
def fieldset(legend, &block)
tag_do(:fieldset, proc do
tag_do(:legend, legend)
block.call
end)
end
##
# Creates a tag that goes in the of the document with +name+.
def head_tag(name)
element = Borges::HtmlElement.new name, @attributes
@document.head.add element
@attributes = Borges::HtmlAttributes.new
return element
end
##
# Creates a tag that goes in the of the document with
# +name+, that contains +child+ in its contents.
def head_tag_with(name, child)
head_tag(name).add(child)
end
##
# Creates a level 1 heading
#
# heading("My Heading")
#
# My Heading
def heading(str)
heading_level(str, 1)
end
##
# Creates a heading with level
#
# heading_level("My Heading", 2)
#
# My Heading
def heading_level(obj, level)
tag_do("h#{level}", obj)
end
##
# Adds an to the document.
def horizontal_rule
tag(:hr)
end
##
# Adds an with +src+ as its src attribute. If +alt+ is
# specified, it is used as the image's alternate text,
# otherwise the alt attribute is left empty.
def image(src, alt = "")
@attributes[:src] = src
@attributes[:alt] = alt
tag :img
end
##
# Add an anchor on an with +src+ as its src attribute. If +alt+ is
# specified, it is used as the image's alternate text,
# otherwise the alt attribute is left empty.
def image_url_anchor(url, src, alt='')
url_anchor(url) do
image(src, alt)
end
end
##
# Creates a new HtmlBuilder.
#
# +document+ must have the following interface:
# #head
# #add_element
# #pop_element
# #push_element
def initialize(document)
@attributes = Borges::HtmlAttributes.new
@document = document
end
##
# Creates an of type +input_type+ with an optional
# +name+ and +value+.
def input(input_type, name = nil, value = nil)
@attributes[:name] = name unless name.nil?
@attributes[:value] = encode value unless value.nil?
@attributes[:type] = input_type
tag :input
end
##
# Builds an unordered list of items from an Enumerable. #text
# will be called on each item.
#
# list ["x", 5, { :x => :y }]
#
#
def list(items)
list_do(items) do |x| text(x) end
end
##
# Builds an unordered list of items from an Enumerable. The
# given block will be called on each item.
#
# See also #list
def list_do(items, &block)
tag_do(:ul, proc do
items.each do |item|
tag_do(:li, proc do block.call(item) end)
end
end)
end
##
# Adds a new element +name+ to the document with all the
# attributes specified for it.
def open_tag(name)
@document.push_element Borges::HtmlElement.new(name, @attributes)
@attributes = Borges::HtmlAttributes.new
end
##
# Renders +obj+.
#--
# FIXME: better comment
def render(obj)
raise "Rendering nil object" if obj.nil? # XXX catch possible bugs
obj.render_on(self)
end
def set_attribute(attr, val)
@attributes[attr] = val
end
##
# Adds a non-breakin space ( ).
def space
text(' ')
end
##
# Adds a link to an external stylesheet with +url+.
def style_link(url)
@attributes[:rel] = 'stylesheet'
@attributes[:type] = 'text/css'
@attributes[:href] = url
@attributes[:media] = 'all'
head_tag(:link)
end
##
# Creates a element.
def table(&block)
tag_do(:table, block)
end
##
# Creates a row of table headings from +headings+
def table_headings(*headings)
table_row do
headings.each do |heading|
table_heading(heading)
end
end
end
##
# Creates a table row.
def table_row(&block)
tag_do(:tr, block)
end
##
# Creates a table row with a table cell that spans +span+
# columns.
#--
# FIXME: This has a funky name
def table_row_span(span, &block)
table_row do
@attributes[:colspan] = span
table_data(&block)
end
end
##
# Creates a table row containing a single data cell.
def table_row_with(obj = nil, &block)
table_row do table_data(obj || block) end
end
##
# Creates a table row with +label+ in the first cell.
def table_row_labeled(label, obj = nil, &block)
table_row do
css_class(:label)
table_data(label)
table_data(obj, &block)
end
end
##
# Creates an empty table row.
def table_spacer_row
table_row do space end
end
##
# Creates an empty element with +name+
def tag(name)
open_tag(name)
close
end
##
# Creates a +name+ element containing +obj+
def tag_do(name, obj)
open_tag(name)
render(obj)
close
end
##
# Turn an object into a string. It is _not_ recommened to use this method
# to dump raw HTML.
#
# text("hello world")
#
# hello world
def text(obj)
@document.add_element(obj.to_s)
end
##
# Sets the of the document to +str+
def title(str)
head_tag_with(:title, str)
end
##
# Creates an anchor that links to +url+.
def url_anchor(url, obj = nil, &block)
@attributes[:href] = url
return tag_do(:a, obj) unless obj.nil?
return tag_do(:a, block)
end
=begin
def anchorWithUrl_title_do(urlString, titleString, &block)
attributes.title(titleString)
anchorWithUrl_do(urlString, &block)
end
##
# XXX Same as Hash#update
def attributes=(attrs)
@attributes ||= Borges::HtmlAttributes.new
attrs.each do |key, val|
@attributes[key] = val
end
end
def buttonForUrl_withText(urlString, labelString)
buttonForUrl_withText_data(urlString, labelString, [])
end
def buttonForUrl_withText_data(urlString, labelString, assocCollection)
formWithMethod_action_do('GET', urlString, proc do
assocCollection.each do |each|
inputWithType_named_value('hidden', each.key, each.value)
end
submitButtonWithText(labelString)
end)
end
def doesNotUnderstand(aMessage)
argCount = aMessage.arguments.size
if argCount == 0 then
return tag(aMessage.selector)
end
if argCount == 1 then
return tag_do(aMessage.selector.allButLast, aMessage.argument)
end
return super.doesNotUnderstand(aMessage)
end
def formWithAction_do(actionUrl, &block)
formWithMethod_action_do('POST', actionUrl, &block)
end
def formWithMethod_action_do(methodString, actionUrl, &block)
attributes.method(methodString)
attributes.action(actionUrl)
openTag('form')
block.call
close
end
##
# HTML4 image must have alt attribute
def image_width_height(urlString, width, height)
attributeAt_put('width', width)
attributeAt_put('height', height)
image(urlString)
end
def image_width_height_altText(urlString, width, height, altString)
attributeAt_put('alt', altString)
image_width_height(urlString, width, height)
end
def inputWithType(input_type)
inputWithType_named(input_type, nil)
end
def inputWithType_named(input_type, name)
inputWithType_named_value(input_type, name, nil)
end
def layoutTable(&block)
attributes.border(0)
attributes.cellspacing(0)
attributes.cellpadding(0)
table(&block)
end
def layoutTableOfWidth_do(width, &block)
attributes.width(width)
layoutTable(&block)
end
def metaTagNamed_content(nameString, contentString)
attributes.name(nameString)
attributes.content(contentString)
headTag('meta')
end
def self.on(aDocument)
inst = self.new
inst.document = aDocument
return inst
end
def scriptWithUrl(urlString)
attributes.language('javascript')
attributes.src(urlString)
headTag('script')
end
def spanClass_with(aString, anObject)
cssClass(aString)
span(anObject)
end
def spanNamed_with(aString, anObject)
cssId(aString)
span(anObject)
end
def submitButton
inputWithType('submit')
end
def submitButtonWithText(aString)
attributes.value(aString)
inputWithType('submit')
end
def tableRowWith(&block)
tableRow do
tableData(&block)
end
end
def tableRowWith_with(aBlock, anotherBlock)
tableRow do
tableData(aBlock)
tableData(anotherBlock)
end
end
def tableRowWith_with_with(x, y, z)
tableRow do
tableData(x)
tableData(y)
tableData(z)
end
end
def tableRowWithLabel_column_column(anObject, aBlock, anotherBlock)
tableRow do
cssClass('label')
tableData(anObject)
tableData(aBlock)
tableData(anotherBlock)
end
end
=end
##
# HtmlBuilder initialize
(0..8).each do |i| HTML_CHARACTERS[i] = "#{i};" end
(9..13).each do |i| HTML_CHARACTERS[i] = i.chr end
(14..31).each do |i| HTML_CHARACTERS[i] = "#{i};" end
(32..126).each do |i| HTML_CHARACTERS[i] = i.chr end
(127..159).each do |i| HTML_CHARACTERS[i] = "#{i};" end
{'quot' => '"', 'lt' => '<', 'amp' => '&', 'gt' => '>'}.each do |s, c|
HTML_CHARACTERS[c[0]] = "{s};"
end
%w(nbsp iexcl cent pound curren yen brvbar sect uml copy ordf
laquo not shy reg macr deg plusmn sup2 sup3 acute micro para
middot cedil sup1 ordm raquo frac14 frac12 frac34 iquest
Agrave Aacute Acirc Atilde Auml Aring AElig Ccedil Egrave
Eacute Ecirc Euml Igrave Iacute Icirc Iuml ETH Ntilde Ograve
Oacute Ocirc Otilde Ouml times Oslash Ugrave Uacute Ucirc
Uuml Yacute THORN szlig agrave aacute acirc atilde auml aring
aelig ccedil egrave eacute ecirc euml igrave iacute icirc
iuml eth ntilde ograve oacute ocirc otilde ouml divide oslash
ugrave uacute ucirc uuml yacute thorn yuml).each_with_index do |s, i|
HTML_CHARACTERS[(i + 160)] = "{s};"
end
end