aurita-gui 0.3.7 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
+