aurita-gui 0.3.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.txt ADDED
@@ -0,0 +1,10 @@
1
+
2
+ Current documentation is online at
3
+
4
+ http://intra.wortundform.de/doc/aurita-gui
5
+
6
+ Mail your requests to twh.fuchs@gmail.com.
7
+ i'll respond within 24 hours.
8
+
9
+ have fun!
10
+ Tobi
data/aurita-gui.gemspec CHANGED
@@ -14,7 +14,7 @@ as stand-alone library in any context (such as rails).
14
14
  As there seems to be a lack of ruby form generators, i decided to release this
15
15
  part of Aurita in a single gem with no dependencies on aurita itself.
16
16
  EOF
17
- s.version = '0.3.7'
17
+ s.version = '0.5.0'
18
18
  s.author = 'Tobias Fuchs'
19
19
  s.email = 'fuchs@wortundform.de'
20
20
  s.date = Time.now
data/lib/aurita-gui.rb CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
2
  require('aurita-gui/element')
3
+ require('aurita-gui/widget')
3
4
  require('aurita-gui/html')
4
5
  require('aurita-gui/javascript')
5
6
  require('aurita-gui/form')
@@ -148,9 +148,30 @@ module GUI
148
148
  class Element < DelegateClass(Array)
149
149
 
150
150
  @@element_count = 0
151
+ @@render_count = 0
151
152
 
152
- attr_accessor :attrib, :parent, :force_closing_tag, :tag
153
+ attr_accessor :attrib, :parent, :force_closing_tag, :tag, :gui_element_id
154
+
155
+ def force_closing_tag=(value)
156
+ touch()
157
+ @force_closing_tag = value
158
+ end
153
159
 
160
+ def parent=(value)
161
+ touch()
162
+ @parent = value
163
+ end
164
+
165
+ def attrib
166
+ touch()
167
+ @attrib
168
+ end
169
+
170
+ def tag=(value)
171
+ touch()
172
+ @tag = value
173
+ end
174
+
154
175
  def initialize(*args, &block)
155
176
 
156
177
  case args[0]
@@ -162,7 +183,10 @@ module GUI
162
183
  params[:content] = args[0]
163
184
  end
164
185
 
186
+ @touched = false
187
+
165
188
  @@element_count += 1
189
+ @gui_element_id = @@element_count
166
190
  @id = @@element_count
167
191
  @parent ||= params[:parent]
168
192
  @force_closing_tag = params[:force_closing_tag]
@@ -176,9 +200,13 @@ module GUI
176
200
  if block_given? then
177
201
  @content = yield
178
202
  else
179
- @content = params[:content]
203
+ @content = params[:content] unless @content
180
204
  end
181
- @content = [ @content ] unless (@content.kind_of? Array or @content.to_s.length == 0)
205
+ # DON'T EVER USE @content.string UNLESS FOR RENDERING!!
206
+ # @content = nil if @content.to_s.length == 0
207
+ # instead, do:
208
+ @content = nil if !@content.is_a?(Element) && ((@content.respond_to?(:length) && @content.length == 0))
209
+ @content = [ @content ] unless (@content.kind_of? Array or @content.nil?)
182
210
  @content ||= []
183
211
 
184
212
  @content.each { |c|
@@ -192,7 +220,35 @@ module GUI
192
220
  @attrib = params
193
221
 
194
222
  super(@content)
223
+ end
224
+
225
+ # To definitely tell if a class is
226
+ # anyhow derived from Element, use
227
+ #
228
+ # something.respond_to? :aurita_gui_element
229
+ #
230
+ def aurita_gui_element
231
+ end
232
+
233
+ def length
234
+ __getobj__.length
235
+ end
195
236
 
237
+ # Tell object tree instance to rebuild
238
+ # object tree on next call of #string
239
+ # as this element instance has been
240
+ # changed.
241
+ def touch
242
+ @touched = true
243
+ @string = nil
244
+ @parent.touch() if @parent
245
+ end
246
+ alias touch! touch
247
+ def touched?
248
+ (@touched == true)
249
+ end
250
+ def untouch
251
+ @touched = false
196
252
  end
197
253
 
198
254
  def has_content?
@@ -227,6 +283,7 @@ module GUI
227
283
  o.parent = @parent if @parent
228
284
  end
229
285
  }
286
+ touch # ADDED
230
287
  return [ self ] + other
231
288
  end
232
289
 
@@ -239,6 +296,7 @@ module GUI
239
296
  if other.is_a?(Element) then
240
297
  other.parent = self
241
298
  end
299
+ touch # ADDED
242
300
  __getobj__().push(other)
243
301
  end
244
302
  alias add_child <<
@@ -284,6 +342,7 @@ module GUI
284
342
  # <div class="highlighted">content</div>
285
343
  #
286
344
  def method_missing(meth, value=nil, &block)
345
+ touch()
287
346
  if block_given? then
288
347
  @attrib[:class] = meth
289
348
  @attrib.update(value) if value.is_a? Hash
@@ -312,6 +371,7 @@ module GUI
312
371
  # Set enclosed content of this element.
313
372
  # Will be automatically wrapped in an array.
314
373
  def set_content(obj)
374
+ touch()
315
375
  if obj.is_a?(Array) then
316
376
  obj.each { |o| o.parent = self if o.is_a?(Element) }
317
377
  __setobj__(obj)
@@ -350,6 +410,7 @@ module GUI
350
410
 
351
411
  # Do not redirect random access operators.
352
412
  def []=(index,element)
413
+ touch()
353
414
  super(index,element) if (index.is_a? Numeric)
354
415
  e = find_by_dom_id(index)
355
416
  e.swap(element)
@@ -358,6 +419,7 @@ module GUI
358
419
  # Copy constructor. Replace self with
359
420
  # other element.
360
421
  def swap(other)
422
+ touch()
361
423
  save_own_id = dom_id()
362
424
  @tag = other.tag
363
425
  @attrib = other.attrib
@@ -374,12 +436,24 @@ module GUI
374
436
 
375
437
  # Render this element to a string.
376
438
  def string
377
- return get_content.to_s if @tag == :pseudo
439
+
440
+ return @string if @string
441
+ if @tag == :pseudo then
442
+ @string = get_content
443
+ if @string.is_a?(Array) then
444
+ @string = @string.map { |e| e.to_s; e }.join('')
445
+ else
446
+ @string = @string.to_s
447
+ end
448
+ return @string
449
+ end
450
+
451
+ @@render_count += 1
378
452
 
379
453
  attrib_string = ''
380
454
  @attrib.each_pair { |name,value|
381
455
  if value.instance_of?(Array) then
382
- value = value.join(' ')
456
+ value = value.reject { |e| e.to_s == '' }.join(' ')
383
457
  elsif value.instance_of?(TrueClass) then
384
458
  value = name
385
459
  end
@@ -388,19 +462,32 @@ module GUI
388
462
  attrib_string << " #{name}=\"#{value}\""
389
463
  end
390
464
  }
391
-
465
+
392
466
  if (!(@force_closing_tag.instance_of?(FalseClass)) &&
393
- [ :div, :label, :button, :textarea, :ol, :ul ].include?(@tag)) then
467
+ ![ :hr, :br, :input ].include?(@tag)) then
394
468
  @force_closing_tag = true
395
469
  end
396
470
  if @force_closing_tag || has_content? then
397
- return "<#{@tag}#{attrib_string}>#{__getobj__}</#{@tag}>"
471
+ # Compatible to ruby 1.9 but SLOW:
472
+ tmp = __getobj__
473
+ tmp = tmp.map { |e| e.to_s; e }.join('') if tmp.is_a?(Array)
474
+ # return "<#{@tag}#{attrib_string}>#{tmp}</#{@tag}>"
475
+ #
476
+ # Ruby 1.8 only:
477
+ # inner = __getobj__.to_s
478
+ @string = "<#{@tag}#{attrib_string}>#{tmp}</#{@tag}>"
479
+ untouch()
480
+ return @string
398
481
  else
399
- return "<#{@tag.to_s}#{attrib_string} />"
482
+ untouch()
483
+ @string = "<#{@tag}#{attrib_string} />"
484
+ return @string
400
485
  end
401
486
  end
402
487
  alias to_s string
403
488
  alias to_str string
489
+ alias to_string string
490
+
404
491
 
405
492
  # Return CSS classes as array. Note that
406
493
  # Element#class is not redefined to return
@@ -425,10 +512,17 @@ module GUI
425
512
  # e.to_s
426
513
  # -->
427
514
  # <div class="first second"></div>
428
- def add_class(css_class_name)
429
- @attrib[:class] = (css_classes << css_class_name.to_sym)
515
+ def add_class(*css_class_names)
516
+ touch()
517
+ if css_class_names.first.is_a?(Array) then
518
+ css_class_names = css_class_names.first
519
+ end
520
+ css_class_names.map! { |c| c.to_sym }
521
+ @attrib[:class] = (css_classes + css_class_names)
430
522
  end
431
523
  alias add_css_class add_class
524
+ alias add_css_classes add_class
525
+ alias add_classes add_class
432
526
 
433
527
  # Remove CSS class from this Element instance.
434
528
  # Add CSS class to this Element instance.
@@ -442,6 +536,7 @@ module GUI
442
536
  # -->
443
537
  # <div class="first"></div>
444
538
  def remove_class(css_class_name)
539
+ touch()
445
540
  classes = css_classes
446
541
  classes.delete(css_class_name.to_sym)
447
542
  @attrib[:class] = classes
@@ -0,0 +1,609 @@
1
+
2
+ require('delegate')
3
+
4
+ module Aurita
5
+ module GUI
6
+
7
+ # GUI::Element is the base class for any rendering
8
+ # implementation.
9
+ # It consists of the following members:
10
+ #
11
+ # * @tag: The HTML tag to render.
12
+ # * @attrib: A hash storing tag attributes, like
13
+ # { :href => '/link/to/somewhere' }
14
+ # * @content: Content this element is wrapping.
15
+ # Content can be set in the constructor
16
+ # via parameter :content or using a
17
+ # block or by #content and #content=.
18
+ #
19
+ # == Usage as container
20
+ #
21
+ # Element implements all features expected from a
22
+ # container class.
23
+ # It delegates access to @content to class Array,
24
+ # so an element can be used as Array instance, too:
25
+ #
26
+ # e = Element.new { Element.new { 'first' } + Element.new { 'second' } }
27
+ # puts e.join(' -- ')
28
+ # -->
29
+ # 'first -- second'
30
+ #
31
+ # You can also push elements into an element:
32
+ #
33
+ # e1 = HTML.div { 'Foo' }
34
+ # e2 = HTML.div { 'Bar' }
35
+ #
36
+ # assert_equal(e[0], 'Foo')
37
+ # assert_equal(e[1], e2)
38
+ #
39
+ # It also keeps track of parent classes:
40
+ #
41
+ # assert_equal(e1[1].parent, e1)
42
+ #
43
+ # Random access operators are redefined, so you
44
+ # can either access elements by array index, as usual,
45
+ # as well as by their DOM id:
46
+ #
47
+ # e = Element.new { Element.new(:tag => :p, :id => :foo) { 'nested element' } }
48
+ # puts e[:foo].to_s
49
+ # -->
50
+ # '<p id="foo">nested element</p>'
51
+ #
52
+ # == Builder
53
+ #
54
+ # Most methods invoked on an Element instance are
55
+ # redirected to return or set a tag attribute.
56
+ # Example:
57
+ #
58
+ # link = Element.new(:tag => :a) { 'click me' }
59
+ # link.href = '/link/to/somewhere'
60
+ #
61
+ # Same as
62
+ #
63
+ # link = Element(:tag => :a,
64
+ # :content => 'click me',
65
+ # :href => '/link/to/somewhere')
66
+ #
67
+ # An Element instance can wrap one or more other
68
+ # elements:
69
+ #
70
+ # image_link = Element.new(:tag => :a, :href => '/link/') {
71
+ # Element.new(:tag => :img, :src => '/an_image.png')
72
+ # }
73
+ #
74
+ # In case an element has no content, it will render
75
+ # a self-closing tag, like <img ... />.
76
+ #
77
+ # In most cases you won't use class Element directly,
78
+ # but by using a factory like Aurita::GUI::HTML or
79
+ # by any derived class like Aurita::GUI::Form or
80
+ # Aurita::GUI::Table.
81
+ #
82
+ # == Markaby style
83
+ #
84
+ # A syntax similar to markaby is also provided:
85
+ #
86
+ # HTML.build {
87
+ # div.outer {
88
+ # p.inner 'click me'
89
+ # } +
90
+ # div.footer 'text at the bottom'
91
+ # }.to_s
92
+ #
93
+ # -->
94
+ #
95
+ # <div class="outer">
96
+ # <p class="inner">paragraph</p>
97
+ # </div>
98
+ # <div class="footer">text at the bottom</div>
99
+ #
100
+ # == Javascript convenience
101
+ #
102
+ # When including the Javascript helper (aurita-gui/javascript),
103
+ # class HTML is extended by method .js, which provides
104
+ # building Javascript snippets in ruby:
105
+ #
106
+ # e = HTML.build {
107
+ # div.outer(:onclick => js.Wombat.alert('message')) {
108
+ # p.inner 'click me'
109
+ # }
110
+ # }
111
+ # e.to_s
112
+ # -->
113
+ # <div class="outer" onclick="Wombat.alert(\'message\'); ">
114
+ # <p class="inner">click me</p>
115
+ # </div>
116
+ #
117
+ # But watch out for operator precedence! This won't work, as
118
+ # .js() catches the block first:
119
+ #
120
+ # HTML.build {
121
+ # div :header, :onclick => js.funcall { 'i will not be passed to div' }
122
+ # }
123
+ # -->
124
+ # <div class="header" onclick="funcall();"></div>
125
+ #
126
+ #
127
+ # So be explicit, use parentheses:
128
+ #
129
+ # HTML.build {
130
+ # div(:header, :onclick => js.funcall) { 'aaah, much better' }
131
+ # }
132
+ # -->
133
+ # <div class="header" onclick="funcall();">aaah, much better</div>
134
+ #
135
+ # == Notes
136
+ #
137
+ # Double-quotes in tag parameters will be escaped
138
+ # when rendering to string.
139
+ #
140
+ # e = Element.new(:onclick => 'alert("message");')
141
+ #
142
+ # The value of parameter :onclick does not change,
143
+ # but will be escaped when rendering:
144
+ #
145
+ # e.onclick == 'alert("message");'
146
+ # e.to_s == '<div onclick="alert(\"message\");"></div>'
147
+ #
148
+ class Element < DelegateClass(Array)
149
+
150
+ @@element_count = 0
151
+ @@render_count = 0
152
+
153
+ attr_accessor :attrib, :parent, :force_closing_tag, :tag, :gui_element_id
154
+
155
+ def initialize(*args, &block)
156
+
157
+ case args[0]
158
+ when Hash
159
+ params = args[0]
160
+ else
161
+ params = args[1]
162
+ params ||= {}
163
+ params[:content] = args[0]
164
+ end
165
+
166
+ @touched = false
167
+
168
+ @@element_count += 1
169
+ @gui_element_id = @@element_count
170
+ @id = @@element_count
171
+ @parent ||= params[:parent]
172
+ @force_closing_tag = params[:force_closing_tag]
173
+
174
+ params[:tag] = :div if params[:tag].nil?
175
+ @tag = params[:tag]
176
+
177
+ params.delete(:parent)
178
+ params.delete(:force_closing_tag)
179
+
180
+ if block_given? then
181
+ @content = yield
182
+ else
183
+ @content = params[:content] unless @content
184
+ end
185
+ # DON'T EVER USE @content.string UNLESS FOR RENDERING!!
186
+ # @content = nil if @content.to_s.length == 0
187
+ # instead, do:
188
+ @content = nil if @content.respond_to?(:length) && @content.length == 0
189
+ @content = [ @content ] unless (@content.kind_of? Array or @content.nil?)
190
+ @content ||= []
191
+
192
+ @content.each { |c|
193
+ if c.is_a?(Element) then
194
+ c.parent = self
195
+ end
196
+ }
197
+ params.delete(:content)
198
+ params.delete(:tag)
199
+
200
+ @attrib = params
201
+
202
+ super(@content)
203
+ end
204
+
205
+ # To definitely tell if a class is
206
+ # anyhow derived from Element, use
207
+ #
208
+ # something.respond_to? :aurita_gui_element
209
+ #
210
+ def aurita_gui_element
211
+ end
212
+
213
+ # Tell object tree instance to rebuild
214
+ # object tree on next call of #string
215
+ # as this element instance has been
216
+ # changed.
217
+ def touch
218
+ @touched = true
219
+ @string = nil
220
+ @parent.touch() if @parent
221
+ end
222
+ alias touch! touch
223
+ def touched?
224
+ (@touched == true)
225
+ end
226
+ def untouch
227
+ @touched = false
228
+ end
229
+
230
+ def has_content?
231
+ (length > 0)
232
+ end
233
+
234
+ # Alias definition for #dom_id=(value)
235
+ # Define explicitly so built-in method #id
236
+ # is not invoked instead
237
+ def id=(value)
238
+ @attrib[:id] = value if @attrib
239
+ end
240
+ alias dom_id= id=
241
+ # Alias definition for #dom_id()
242
+ def id
243
+ @attrib[:id] if @attrib
244
+ end
245
+ alias dom_id id
246
+
247
+ # Return [ self, other ] so concatenation of
248
+ # Element instances works as expected;
249
+ #
250
+ # HTML.build {
251
+ # div { 'first' } + div { 'second' }
252
+ # }
253
+ # --> <Element [ <Element 'first'>, <Element 'second'> ] >
254
+ #
255
+ def +(other)
256
+ other = [other] unless other.is_a?(Array)
257
+ other.each { |o|
258
+ if o.is_a?(Element) then
259
+ o.parent = @parent if @parent
260
+ end
261
+ }
262
+ touch # ADDED
263
+ return [ self ] + other
264
+ end
265
+
266
+ # Append object to array of nested elements.
267
+ # Object to append (other) does not have to
268
+ # be an Element instance.
269
+ # If so, however, other#parent will be set
270
+ # to this instance.
271
+ def <<(other)
272
+ if other.is_a?(Element) then
273
+ other.parent = self
274
+ end
275
+ touch # ADDED
276
+ __getobj__().push(other)
277
+ end
278
+ alias add_child <<
279
+ alias add_content <<
280
+
281
+ # Returns [ self ], so concatenation with
282
+ # Arrays and other Element instances works
283
+ # as expected (see #<<(other).
284
+ def to_ary
285
+ [ self ]
286
+ end
287
+ alias to_a to_ary
288
+
289
+ # Returns nested content as array.
290
+ def get_content
291
+ __getobj__()
292
+ end
293
+
294
+ # Redirect methods to setting or retreiving tag
295
+ # attributes.
296
+ # There are several possible routings for method_missing:
297
+ #
298
+ # 1. Setting an attribute (no block, method ends in '=') Example:
299
+ # my_div = HTML.div 'content'
300
+ # my_div.onlick = "alert('foo');"
301
+ # puts my_div.to_s
302
+ # -->
303
+ # <div onclick="alert('foo');">content</div>
304
+ #
305
+ # 2. Retreiving an attribute (no block, method does not end in '='). Example:
306
+ #
307
+ # puts my_div.onlick
308
+ # -->
309
+ # 'alert(\'foo\');'
310
+ #
311
+ # 3. Setting the css class (block or value passed, method does not end in '='). Example:
312
+ #
313
+ # my_div.highlighted { 'content' }
314
+ # or
315
+ # my_div.highlighted 'content'
316
+ #
317
+ # -->
318
+ # <div class="highlighted">content</div>
319
+ #
320
+ def method_missing(meth, value=nil, &block)
321
+ touch()
322
+ if block_given? then
323
+ @attrib[:class] = meth
324
+ @attrib.update(value) if value.is_a? Hash
325
+ c = yield
326
+ c = [ c ] unless c.is_a?(Array)
327
+ __setobj__(c)
328
+ return self
329
+ elsif !value.nil? && !meth.to_s.include?('=') then
330
+ @attrib[:class] = meth
331
+ case value
332
+ when Hash then
333
+ @attrib.update(value)
334
+ c = value[:content]
335
+ c = [ c ] if (c && !c.is_a?(Array))
336
+ __setobj__(c) if c
337
+ when String then
338
+ __setobj__([value])
339
+ end
340
+ return self
341
+ else
342
+ return @attrib[meth] unless value or meth.to_s.include? '='
343
+ @attrib[meth.to_s.gsub('=','').intern] = value
344
+ end
345
+ end
346
+
347
+ # Set enclosed content of this element.
348
+ # Will be automatically wrapped in an array.
349
+ def set_content(obj)
350
+ touch()
351
+ if obj.is_a?(Array) then
352
+ obj.each { |o| o.parent = self if o.is_a?(Element) }
353
+ __setobj__(obj)
354
+ else
355
+ obj.parent = self if obj.is_a?(Element)
356
+ return __setobj__([ obj ])
357
+ end
358
+ end
359
+ alias content= set_content
360
+
361
+ # Define explicitly so built-in method #type
362
+ # is not invoked instead
363
+ def type=(type)
364
+ @attrib[:type] = type
365
+ end
366
+
367
+ # Do not redirect random access operators.
368
+ def [](index)
369
+ return super(index) if (index.is_a?(Fixnum))
370
+ return find_by_dom_id(index)
371
+ end
372
+
373
+ # Retreive an element from object tree by
374
+ # its dom_id
375
+ def find_by_dom_id(dom_id)
376
+ dom_id = dom_id.to_sym
377
+ each { |c|
378
+ if c.is_a? Element then
379
+ return c if (c.dom_id == dom_id)
380
+ sub = c.find_by_dom_id(dom_id)
381
+ return sub if sub
382
+ end
383
+ }
384
+ return nil
385
+ end
386
+
387
+ # Do not redirect random access operators.
388
+ def []=(index,element)
389
+ touch()
390
+ super(index,element) if (index.is_a? Numeric)
391
+ e = find_by_dom_id(index)
392
+ e.swap(element)
393
+ end
394
+
395
+ # Copy constructor. Replace self with
396
+ # other element.
397
+ def swap(other)
398
+ touch()
399
+ save_own_id = dom_id()
400
+ @tag = other.tag
401
+ @attrib = other.attrib
402
+ @attrib[:id] = save_own_id
403
+ __setobj__(other.get_content)
404
+ end
405
+ alias copy swap
406
+
407
+ # Static helper definition for clearing
408
+ # CSS floats.
409
+ def clear_floating
410
+ '<div style="clear: both;" />'
411
+ end
412
+
413
+ # Render this element to a string.
414
+ def string
415
+
416
+ # return @string if @string
417
+ if @tag == :pseudo then
418
+ # @string = get_content.to_s
419
+ # return @string
420
+ return get_content.to_s
421
+ end
422
+
423
+ @@render_count += 1
424
+
425
+ attrib_string = ''
426
+ @attrib.each_pair { |name,value|
427
+ if value.instance_of?(Array) then
428
+ value = value.reject { |e| e.to_s == '' }.join(' ')
429
+ elsif value.instance_of?(TrueClass) then
430
+ value = name
431
+ end
432
+ if !value.nil? then
433
+ value = value.to_s.gsub('"','\"')
434
+ attrib_string << " #{name}=\"#{value}\""
435
+ end
436
+ }
437
+
438
+ if (!(@force_closing_tag.instance_of?(FalseClass)) &&
439
+ ![ :hr, :br, :input ].include?(@tag)) then
440
+ @force_closing_tag = true
441
+ end
442
+ if @force_closing_tag || has_content? then
443
+ # Compatible to ruby 1.9 but SLOW:
444
+ # tmp = __getobj__
445
+ # tmp = tmp.map { |e| e.to_s; e }.join('') if tmp.is_a?(Array)
446
+ # return "<#{@tag}#{attrib_string}>#{tmp}</#{@tag}>"
447
+ inner = __getobj__.to_s
448
+ @string = "<#{@tag}#{attrib_string}>#{inner}</#{@tag}>"
449
+ untouch()
450
+ return @string
451
+ else
452
+ untouch()
453
+ @string = "<#{@tag}#{attrib_string} />"
454
+ return @string
455
+ end
456
+ end
457
+ alias to_s string
458
+ alias to_str string
459
+ alias to_string string
460
+
461
+
462
+ # Return CSS classes as array. Note that
463
+ # Element#class is not redefined to return
464
+ # attribute :class, for obvious reasons.
465
+ def css_classes
466
+ css_classes = @attrib[:class]
467
+ if css_classes.kind_of? Array
468
+ css_classes.flatten!
469
+ elsif css_classes.kind_of? String
470
+ css_classes = css_classes.split(' ')
471
+ else # e.g. Symbol
472
+ css_classes = [ css_classes ]
473
+ end
474
+ css_classes.map! { |c| c.to_sym if c }
475
+ return css_classes
476
+ end
477
+ alias css_class css_classes
478
+
479
+ # Add CSS class to this Element instance.
480
+ # e = Element.new(:class => :first)
481
+ # e.add_class(:second
482
+ # e.to_s
483
+ # -->
484
+ # <div class="first second"></div>
485
+ def add_class(*css_class_names)
486
+ touch()
487
+ if css_class_names.first.is_a?(Array) then
488
+ css_class_names = css_class_names.first
489
+ end
490
+ css_class_names.map! { |c| c.to_sym }
491
+ @attrib[:class] = (css_classes + css_class_names)
492
+ end
493
+ alias add_css_class add_class
494
+ alias add_css_classes add_class
495
+ alias add_classes add_class
496
+
497
+ # Remove CSS class from this Element instance.
498
+ # Add CSS class to this Element instance.
499
+ # e = Element.new(:class => [ :first, :second ])
500
+ # e.to_s
501
+ # -->
502
+ # <div class="first second"></div>
503
+ #
504
+ # e.remove_class(:second)
505
+ # e.to_s
506
+ # -->
507
+ # <div class="first"></div>
508
+ def remove_class(css_class_name)
509
+ touch()
510
+ classes = css_classes
511
+ classes.delete(css_class_name.to_sym)
512
+ @attrib[:class] = classes
513
+ end
514
+ alias remove_css_class remove_class
515
+
516
+ # Iterates over all Elements in this
517
+ # instances object tree (depth first).
518
+ #
519
+ # x = HTML.build {
520
+ # div.main {
521
+ # h2.header { 'Title' } +
522
+ # div.lead { 'Intro here' } +
523
+ # div.body {
524
+ # p.section { 'First' } +
525
+ # p.section { 'Second' }
526
+ # }
527
+ # }
528
+ # }
529
+ #
530
+ # x.recurse { |element|
531
+ # p element.css_class
532
+ # }
533
+ #
534
+ # -->
535
+ #
536
+ # :main
537
+ # :header
538
+ # :lead
539
+ # :body
540
+ # :section
541
+ # :section
542
+ #
543
+ def recurse(&block)
544
+ each { |c|
545
+ if c.is_a?(Element) then
546
+ yield(c)
547
+ c.recurse(&block)
548
+ end
549
+ }
550
+ end
551
+
552
+ def js_init_code()
553
+ code = js_initialize() if self.respond_to?(:js_initialize)
554
+ code ||= ''
555
+ recurse { |e|
556
+ code << e.js_initialize if e.respond_to?(:js_initialize)
557
+ }
558
+ code
559
+ end
560
+
561
+ end # class
562
+
563
+ class Buffered_Element < Element
564
+
565
+ def initialize(buffer, *args, &block)
566
+ @output_buffer = buffer
567
+ super(*args, &block)
568
+ end
569
+
570
+ def method_missing(meth, value=nil, &block)
571
+ if block_given? then
572
+ @attrib[:class] = meth
573
+ @attrib.update(value) if value.is_a? Hash
574
+ c = yield
575
+ c = [ c ] unless c.is_a?(Array)
576
+ __setobj__(c)
577
+
578
+ return string()
579
+ elsif !value.nil? && !meth.to_s.include?('=') then
580
+ @attrib[:class] = meth
581
+ case value
582
+ when Hash then
583
+ @attrib.update(value)
584
+ c = value[:content]
585
+ c = [ c ] if (c && !c.is_a?(Array))
586
+ __setobj__(c) if c
587
+ when String then
588
+ __setobj__([value])
589
+ end
590
+
591
+ return string()
592
+ else
593
+ return @attrib[meth] unless value or meth.to_s.include? '='
594
+ @attrib[meth.to_s.gsub('=','').intern] = value
595
+ end
596
+ end
597
+
598
+ end
599
+
600
+ class PseudoElement < Element
601
+ def initialize(params={}, &block)
602
+ params[:tag] = :pseudo
603
+ super()
604
+ end
605
+ end
606
+
607
+ end # module
608
+ end # module
609
+