# = HTML Element Class # # Used with the Html class to generate html content for a single tag/element. Represents # a single element with attributes and optional contents (including other elements). Generally, you # won't use this by itself. Check out Html.build() instead. # # Simple useage: # >> Html::Element.new('span','some text', :id => 'title-text').render # => 'some text' # # Complex usage: # span = Html::Element.new('span') # Creates a span element for customization # span.id = 'title-text' # Set some attributes # span.style = 'color: #f00;' # span.html = 'some text' # Adds some content # span.render # Converts to html string # => 'some text # class Html class Element # Remove everything that would normally come from Object and Kernel etc. so our attrs can be anything instance_methods.each { |m| undef_method m if m =~ /^[a-z0-9]+=?$/ } # Add back in our accessors attr_accessor :tag, :attrs # Commonly empty tags SINGLETON_SET = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source'].freeze # Inline formatting tags INLINE_SET = ['a','abbr','b','button','em','i','input','img','label','li','option','span','strong','title','textarea','u'].freeze # Attributes that should render even if blank BLANK_ATTRS = ['value','alt'].freeze # One-stop shop for building content def self.build(*args) el = self.new(*args) yield el if block_given? el.render end def initialize(tag, text=nil, attrs={}) @tag = tag.to_s @force_end = !SINGLETON_SET.include?(@tag) @skip_newline = INLINE_SET.include?(@tag) if text.is_a?(Hash) @attrs = text text = nil else @attrs = attrs || {} end if text.is_a?(String) html << ((!text.respond_to?(:html_safe?) || !text.html_safe?) ? Html.escape_once(text) : text) elsif text.is_a?(Html::Element) html << text elsif text.is_a?(Html) @html = text end yield self if block_given? self end def force_end! @force_end = true end def skip_newline! @skip_newline = true end def html @html ||= Html.new @html end def html=(arg) if arg.is_a? String @html = Html.new @html.text! arg elsif arg.is_a?(Html) @html = arg elsif arg.is_a?(Array) @html = Html.new arg.each do |el| @html << el end else raise 'Invalid input' end end # Set/get attrs on any method missing calls def method_missing(method, *args) parts = method.to_s.match(/^([a-z0-9_]+)(=?)$/i) if parts key = parts[1].to_sym if parts[2] && args.length == 1 # We have an attempt to set a missing field... @attrs[key] = args[0] return args[0] else raise "I think you meant <#{@tag}>.html.#{method} instead of <#{@tag}>.#{method}" if block_given? return @attrs[key] end else # There really is no method... super end end def [](key) @attrs[key.to_sym] end def []=(key, value) @attrs[key.to_sym] = value end def to_s render end def inspect render end def is_a?(other) return other == Html::Element end def render(depth = 0, inblock = false) # Convert attrs to strings attrStr = @attrs.collect do |k,v| if v.nil? nil elsif v.blank? && ! BLANK_ATTRS.include?(k.to_s) " #{k}" else v = Html.escape_once(v) unless v.html_safe? " #{k}=\"#{v}\"" end end.compact.join('') # Build start tag val = '' val += "\n" if !inblock && !@skip_newline val += ' ' * depth unless !inblock && @skip_newline val += "<#{@tag}#{attrStr}>" unless @html.nil? val += "\n" unless @skip_newline val += @html.render(depth+1, !@skip_newline) val += "\n" unless @skip_newline || val.ends_with?("\n") val += ' ' * depth unless @skip_newline end val += "" if (@force_end || !@html.blank?) val += "\n" unless @skip_newline val end end end