Class: Aurita::GUI::Element

Inherits:
Array
  • Object
show all
Extended by:
Marshal_Helper_Class_Methods
Includes:
Marshal_Helper
Defined in:
lib/aurita-gui/element.rb,
lib/aurita-gui/marshal.rb

Overview

GUI::Element is the base class for any rendering implementation. It consists of the following members:

* @tag: The HTML tag to render. 
* @attrib: A hash storing tag attributes, like 
           { :href => '/link/to/somewhere' }
* @content: Content this element is wrapping. 
            Content can be set in the constructor 
            via parameter :content or using a 
            block or by #content and #content=.

Usage as container

Element implements all features expected from a container class. It delegates access to @content to class Array, so an element can be used as Array instance, too:

e = Element.new { Element.new { 'first' } + Element.new { 'second' } }
puts e.join(' -- ')
-->
'first -- second'

You can also push elements into an element:

e1 = HTML.div { 'Foo' }
e2 = HTML.div { 'Bar' }

assert_equal(e[0], 'Foo')
assert_equal(e[1], e2)

It also keeps track of parent classes:

assert_equal(e1[1].parent, e1)

Random access operators are redefined, so you can either access elements by array index, as usual, as well as by their DOM id:

e = Element.new { Element.new(:tag => :p, :id => :foo) { 'nested element' } }
puts e[:foo].to_s
-->
'<p id="foo">nested element</p>'

Builder

Most methods invoked on an Element instance are redirected to return or set a tag attribute. Example:

link = Element.new(:tag => :a) { 'click me' }
link.href = '/link/to/somewhere'

Same as

link = Element(:tag => :a, 
               :content => 'click me', 
               :href => '/link/to/somewhere')

An Element instance can wrap one or more other elements:

image_link = Element.new(:tag => :a, :href => '/link/') { 
               Element.new(:tag => :img, :src => '/an_image.png')
             }

In case an element has no content, it will render a self-closing tag, like <img … />.

In most cases you won’t use class Element directly, but by using a factory like Aurita::GUI::HTML or by any derived class like Aurita::GUI::Form or Aurita::GUI::Table.

Markaby style

A syntax similar to markaby is also provided:

HTML.build { 
  div.outer { 
    p.inner 'click me'
  } + 
  div.footer 'text at the bottom'
}.to_s

–>

<div class="outer">
 <p class="inner">paragraph</p>
</div>
<div class="footer">text at the bottom</div>

Javascript convenience

When including the Javascript helper (aurita-gui/javascript), class HTML is extended by method .js, which provides building Javascript snippets in ruby:

e = HTML.build { 
  div.outer(:onclick => js.Wombat.alert('message')) { 
    p.inner 'click me'
  }
}
e.to_s

–>

<div class="outer" onclick="Wombat.alert(\'message\'); ">
  <p class="inner">click me</p>
</div>

But watch out for operator precedence! This won’t work, as .js() catches the block first:

HTML.build { 
  div :header, :onclick => js.funcall { 'i will not be passed to div' }
}

–>

<div class="header" onclick="funcall();"></div>

So be explicit, use parentheses:

HTML.build { 
  div(:header, :onclick => js.funcall) { 'aaah, much better' }
}

–>

<div class="header" onclick="funcall();">aaah, much better</div>

Notes

Double-quotes in tag parameters will be escaped when rendering to string.

e = Element.new(:onclick => 'alert("message");')

The value of parameter :onclick does not change, but will be escaped when rendering:

e.onclick == 'alert("message");'
e.to_s    == '<div onclick="alert(\"message\");"></div>'

Constant Summary collapse

@@element_count =
0

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Marshal_Helper_Class_Methods

marshal_load

Methods included from Marshal_Helper

#marshal_dump

Constructor Details

#initialize(*args, &block) ⇒ Element

Returns a new instance of Element.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/aurita-gui/element.rb', line 154

def initialize(*args, &block) 

  params = {}
  case args[0]
  when Hash 
    params = args[0]
  else
    params = args[1] 
    params ||= {}
    params[:content] = args[0]
  end

  @@element_count += 1
  @id          = @@element_count
  @parent      = params[:parent]
  @force_closing_tag = params[:force_closing_tag]

  params[:tag] = :div if params[:tag].nil?
  @tag = params[:tag]

  params.delete(:parent)
  params.delete(:force_closing_tag)

  if block_given? then
    @content = yield
  else
    @content = params[:content]
  end
  @content   = [ @content ] unless (@content.kind_of? Array or @content.to_s.length == 0)
  @content ||= []

  @content.each { |c|
    if c.is_a?(Element) then
      c.parent = self
    end
  }
  params.delete(:content)
  params.delete(:tag)
  
  @attrib = params

  super(@content)

end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, value = nil, &block) ⇒ Object

Redirect methods to setting or retreiving tag attributes. There are several possible routings for method_missing:

  1. Setting an attribute (no block, method ends in ‘=’) Example:

my_div = HTML.div 'content'
my_div.onlick = "alert('foo');"
puts my_div.to_s

–>

<div onclick="alert('foo');">content</div>
  1. Retreiving an attribute (no block, method does not end in ‘=’). Example:

puts my_div.onlick

–>

'alert(\'foo\');'
  1. Setting the css class (block or value passed, method does not end in ‘=’). Example:

my_div.highlighted { 'content' }

or

my_div.highlighted 'content'

–>

<div class="highlighted">content</div>


277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/aurita-gui/element.rb', line 277

def method_missing(meth, value=nil, &block)
  if block_given? then
    @attrib[:class] = meth
    @attrib.update(value) if value.is_a? Hash
    c = yield
    c = [ c ] unless c.is_a?(Array)
    __setobj__(c)
    return self
  elsif !value.nil? && !meth.to_s.include?('=') then
    @attrib[:class] = meth
    case value
    when Hash then
      @attrib.update(value)
      c = value[:content] 
      c = [ c ] if (c && !c.is_a?(Array))
      __setobj__(c) if c
    when String then
      __setobj__([value])
    end
    return self
  else
    return @attrib[meth] unless value or meth.to_s.include? '='
    @attrib[meth.to_s.gsub('=','').intern] = value
  end
end

Instance Attribute Details

#attribObject

Returns the value of attribute attrib.



152
153
154
# File 'lib/aurita-gui/element.rb', line 152

def attrib
  @attrib
end

#force_closing_tagObject

Returns the value of attribute force_closing_tag.



152
153
154
# File 'lib/aurita-gui/element.rb', line 152

def force_closing_tag
  @force_closing_tag
end

#parentObject

Returns the value of attribute parent.



152
153
154
# File 'lib/aurita-gui/element.rb', line 152

def parent
  @parent
end

#tagObject

Returns the value of attribute tag.



152
153
154
# File 'lib/aurita-gui/element.rb', line 152

def tag
  @tag
end

Instance Method Details

#+(other) ⇒ Object

Return [ self, other ] so concatenation of Element instances works as expected;

HTML.build { 
  div { 'first' } + div { 'second' } 
}
--> <Element [ <Element 'first'>, <Element 'second'> ] >


224
225
226
# File 'lib/aurita-gui/element.rb', line 224

def +(other)
  return [ self, other ]
end

#<<(other) ⇒ Object

Append object to array of nested elements. Object to append (other) does not have to be an Element instance. If so, however, other#parent will be set to this instance.



233
234
235
236
# File 'lib/aurita-gui/element.rb', line 233

def <<(other)
  other.parent = self if other.is_a?(Element)
  __getobj__().push(other)
end

#[](index) ⇒ Object

Do not redirect random access operators.



318
319
320
321
# File 'lib/aurita-gui/element.rb', line 318

def [](index)
  return super(index) if (index.is_a?(Fixnum))
  return find_by_dom_id(index) 
end

#[]=(index, element) ⇒ Object

Do not redirect random access operators.



338
339
340
341
342
# File 'lib/aurita-gui/element.rb', line 338

def []=(index,element)
  super(index,element) if (index.is_a? Numeric)
  e = find_by_dom_id(index) 
  e.swap(element)
end

#add_class(css_class_name) ⇒ Object Also known as: add_css_class

Add CSS class to this Element instance.

e = Element.new(:class => :first)
e.add_class(:second
e.to_s

–>

<div class="first second"></div>


414
415
416
# File 'lib/aurita-gui/element.rb', line 414

def add_class(css_class_name)
  @attrib[:class] = (css_classes << css_class_name.to_sym)
end

#clear_floatingObject

Static helper definition for clearing CSS floats.



357
358
359
# File 'lib/aurita-gui/element.rb', line 357

def clear_floating
  '<div style="clear: both;" />'
end

#css_classesObject Also known as: css_class

Return CSS classes as array. Note that Element#class is not redefined to return attribute :class, for obvious reasons.



394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/aurita-gui/element.rb', line 394

def css_classes
  css_classes = @attrib[:class]
  if css_classes.kind_of? Array
    css_classes.flatten! 
  elsif css_classes.kind_of? String
    css_classes = css_classes.split(' ') 
  else # e.g. Symbol
    css_classes = [ css_classes ]
  end
  css_classes.map! { |c| c.to_sym if c }
  return css_classes
end

#find_by_dom_id(dom_id) ⇒ Object

Retreive an element from object tree by its dom_id



325
326
327
328
329
330
331
332
333
334
335
# File 'lib/aurita-gui/element.rb', line 325

def find_by_dom_id(dom_id)
  dom_id = dom_id.to_sym
  each { |c|
    if c.is_a? Element then
      return c if (c.dom_id == dom_id)
      sub = c.find_by_dom_id(dom_id)
      return sub if sub
    end
  }
  return nil
end

#get_contentObject

Returns nested content as array.



247
248
249
# File 'lib/aurita-gui/element.rb', line 247

def get_content
  __getobj__()
end

#has_content?Boolean

Returns:

  • (Boolean)


199
200
201
# File 'lib/aurita-gui/element.rb', line 199

def has_content? 
  (length > 0)
end

#idObject Also known as: dom_id

Alias definition for #dom_id()



211
212
213
# File 'lib/aurita-gui/element.rb', line 211

def id
  @attrib[:id] if @attrib
end

#id=(value) ⇒ Object Also known as: dom_id=

Alias definition for #dom_id=(value) Define explicitly so built-in method #id is not invoked instead



206
207
208
# File 'lib/aurita-gui/element.rb', line 206

def id=(value)
  @attrib[:id] = value if @attrib
end

#recurse(&block) ⇒ Object

Iterates over all Elements in this instances object tree (depth first).

x = HTML.build { 
     div.main { 
       h2.header { 'Title' } + 
       div.lead { 'Intro here' } + 
       div.body { 
         p.section { 'First' } + 
         p.section { 'Second' } 
       }
     }
    }

x.recurse { |element| 
  p element.css_class
}

–>

:main
:header
:lead
:body
:section
:section


464
465
466
467
468
469
470
471
# File 'lib/aurita-gui/element.rb', line 464

def recurse(&block)
  each { |c| 
    if c.is_a?(Element) then
      yield(c)
      c.recurse(&block) 
    end
  }
end

#remove_class(css_class_name) ⇒ Object Also known as: remove_css_class

Remove CSS class from this Element instance. Add CSS class to this Element instance.

e = Element.new(:class => [ :first, :second ])
e.to_s

–>

<div class="first second"></div>

e.remove_class(:second)
e.to_s

–>

<div class="first"></div>


430
431
432
433
434
# File 'lib/aurita-gui/element.rb', line 430

def remove_class(css_class_name)
  classes = css_classes
  classes.delete(css_class_name.to_sym)
  @attrib[:class] = classes
end

#set_content(obj) ⇒ Object Also known as: content=

Set enclosed content of this element. Will be automatically wrapped in an array.



305
306
307
308
# File 'lib/aurita-gui/element.rb', line 305

def set_content(obj)
  return __setobj__([ obj ]) unless (obj.is_a?(Array))
  __setobj__(obj)
end

#stringObject Also known as: to_s, to_str

Render this element to a string.



362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/aurita-gui/element.rb', line 362

def string
  return get_content.to_s if @tag == :pseudo

  attrib_string = ''
  @attrib.each_pair { |name,value|
    if value.instance_of?(Array) then
      value = value.join(' ')
    elsif value.instance_of?(TrueClass) then
      value = name
    end
    if !value.nil? then
      value = value.to_s.gsub('"','\"')
      attrib_string << " #{name}=\"#{value}\""
    end
  }
  
  if (!(@force_closing_tag.instance_of?(FalseClass)) && 
      [ :div, :label, :button, :textarea ].include?(@tag)) then
    @force_closing_tag = true
  end
  if @force_closing_tag || has_content? then
    return "<#{@tag}#{attrib_string}>#{__getobj__}</#{@tag}>"
  else
    return "<#{@tag.to_s}#{attrib_string} />" 
  end
end

#swap(other) ⇒ Object Also known as: copy

Copy constructor. Replace self with other element.



346
347
348
349
350
351
352
# File 'lib/aurita-gui/element.rb', line 346

def swap(other)
  save_own_id = dom_id()
  @tag = other.tag
  @attrib = other.attrib 
  @attrib[:id] = save_own_id
  __setobj__(other.get_content)
end

#to_aryObject Also known as: to_a

Returns [ self ], so concatenation with Arrays and other Element instances works as expected (see #<<(other).



241
242
243
# File 'lib/aurita-gui/element.rb', line 241

def to_ary
  [ self ]
end

#type=(type) ⇒ Object

Define explicitly so built-in method #type is not invoked instead



313
314
315
# File 'lib/aurita-gui/element.rb', line 313

def type=(type)
  @attrib[:type] = type
end