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,
lib/aurita-gui/element_fixed.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
@@render_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.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/aurita-gui/element.rb', line 175

def initialize(*args, &block) 

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

  @touched = false

  @@element_count   += 1
  @gui_element_id    = @@element_count
  @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] unless @content
  end
# DON'T EVER USE @content.string UNLESS FOR RENDERING!!
#     @content   = nil if @content.to_s.length == 0    # <--- NOOOOooo!
# instead, do: 
  @content   = nil if !@content.is_a?(Element) && ((@content.respond_to?(:length) && @content.length == 0))
  @content   = [ @content ] unless (@content.kind_of? Array or @content.nil?)
  @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>


349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/aurita-gui/element.rb', line 349

def method_missing(meth, value=nil, &block)
  touch()
  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.



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

def attrib
  @attrib
end

#force_closing_tagObject

Returns the value of attribute force_closing_tag.



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

def force_closing_tag
  @force_closing_tag
end

#gui_element_idObject

Returns the value of attribute gui_element_id.



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

def gui_element_id
  @gui_element_id
end

#parentObject

Returns the value of attribute parent.



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

def parent
  @parent
end

#tagObject

Returns the value of attribute tag.



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

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'> ] >


284
285
286
287
288
289
290
291
292
293
# File 'lib/aurita-gui/element.rb', line 284

def +(other)
  other = [other] unless other.is_a?(Array) 
  other.each { |o| 
    if o.is_a?(Element) then
      o.parent = @parent if @parent
    end
  }
  touch # ADDED
  return [ self ] + other 
end

#<<(other) ⇒ Object Also known as: add_child, add_content

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.



300
301
302
303
304
305
306
# File 'lib/aurita-gui/element.rb', line 300

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

#[](index) ⇒ Object

Do not redirect random access operators.



397
398
399
400
# File 'lib/aurita-gui/element.rb', line 397

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.



417
418
419
420
421
422
# File 'lib/aurita-gui/element.rb', line 417

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

#add_class(*css_class_names) ⇒ Object Also known as: add_css_class, add_css_classes, add_classes

Add CSS class to this Element instance.

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

–>

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


520
521
522
523
524
525
526
527
# File 'lib/aurita-gui/element.rb', line 520

def add_class(*css_class_names)
  touch()
  if css_class_names.first.is_a?(Array) then
    css_class_names = css_class_names.first
  end
  css_class_names.map! { |c| c.to_sym }
  @attrib[:class] = (css_classes + css_class_names) 
end

#aurita_gui_elementObject

To definitely tell if a class is anyhow derived from Element, use

something.respond_to? :aurita_gui_element


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

def aurita_gui_element
end

#clear_floatingObject

Static helper definition for clearing CSS floats.



438
439
440
# File 'lib/aurita-gui/element.rb', line 438

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.



500
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/aurita-gui/element.rb', line 500

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



404
405
406
407
408
409
410
411
412
413
414
# File 'lib/aurita-gui/element.rb', line 404

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.



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

def get_content
  __getobj__()
end

#has_content?Boolean

Returns:

  • (Boolean)


259
260
261
# File 'lib/aurita-gui/element.rb', line 259

def has_content? 
  (length > 0)
end

#idObject Also known as: dom_id

Alias definition for #dom_id()



271
272
273
# File 'lib/aurita-gui/element.rb', line 271

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



266
267
268
# File 'lib/aurita-gui/element.rb', line 266

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

#inspectObject



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

def inspect
  "#{self.class.to_s}: <#{@tag}>, #{@attrib.inspect} { #{@content.inspect} }"
end

#js_init_codeObject



587
588
589
590
591
592
593
594
# File 'lib/aurita-gui/element.rb', line 587

def js_init_code()
  code = js_initialize() if self.respond_to?(:js_initialize)
  code ||= ''
  recurse { |e|
    code << e.js_initialize if e.respond_to?(:js_initialize)
  }
  code
end

#lengthObject



237
238
239
# File 'lib/aurita-gui/element.rb', line 237

def length
  __getobj__.length
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


578
579
580
581
582
583
584
585
# File 'lib/aurita-gui/element.rb', line 578

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>


543
544
545
546
547
548
# File 'lib/aurita-gui/element.rb', line 543

def remove_class(css_class_name)
  touch()
  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.



378
379
380
381
382
383
384
385
386
387
# File 'lib/aurita-gui/element.rb', line 378

def set_content(obj)
  touch()
  if obj.is_a?(Array) then
    obj.each { |o| o.parent = self if o.is_a?(Element) } 
    __setobj__(obj)
  else
    obj.parent = self if obj.is_a?(Element)
    return __setobj__([ obj ]) 
  end
end

#stringObject Also known as: to_s, to_str, to_string

Render this element to a string.



443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'lib/aurita-gui/element.rb', line 443

def string

  return @string if @string
  if @tag == :pseudo then
    @string = get_content
    if @string.is_a?(Array) then
      @string = @string.map { |e| e.to_s; e }.join('') 
    else 
      @string = @string.to_s
    end
    return @string
  end

  @@render_count += 1

  attrib_string = ''
  @attrib.each_pair { |name,value|
    if value.instance_of?(Array) then
      value = value.reject { |e| e.to_s == '' }.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)) && 
      ![ :hr, :br, :input ].include?(@tag)) then
    @force_closing_tag = true
  end
  if @force_closing_tag || has_content? then
# Compatible to ruby 1.9 but SLOW: 
    tmp = __getobj__
    tmp = tmp.map { |e| e.to_s; e }.join('') if tmp.is_a?(Array)
#       return "<#{@tag}#{attrib_string}>#{tmp}</#{@tag}>"
#
# Ruby 1.8 only: 
#       inner = __getobj__.to_s
    @string = "<#{@tag}#{attrib_string}>#{tmp}</#{@tag}>"
    untouch()
    return @string
  else
    untouch()
    @string = "<#{@tag}#{attrib_string} />" 
    return @string
  end
end

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

Copy constructor. Replace self with other element.



426
427
428
429
430
431
432
433
# File 'lib/aurita-gui/element.rb', line 426

def swap(other)
  touch()
  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).



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

def to_ary
  [ self ]
end

#touchObject Also known as: touch!

Tell object tree instance to rebuild object tree on next call of #string as this element instance has been changed.



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

def touch
  @touched = true
  @string  = nil
  # Don't re-touch! Parent could have been caller!
  @parent.touch() if (@parent && !@parent.touched?) 
end

#touched?Boolean

Returns:

  • (Boolean)


252
253
254
# File 'lib/aurita-gui/element.rb', line 252

def touched? 
  (@touched == true)
end

#type=(type) ⇒ Object

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



392
393
394
# File 'lib/aurita-gui/element.rb', line 392

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

#untouchObject



255
256
257
# File 'lib/aurita-gui/element.rb', line 255

def untouch
  @touched = false
end