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 +10 -0
- data/aurita-gui.gemspec +1 -1
- data/lib/aurita-gui.rb +1 -0
- data/lib/aurita-gui/element.rb +106 -11
- data/lib/aurita-gui/element_fixed.rb +609 -0
- data/lib/aurita-gui/form.rb +4 -0
- data/lib/aurita-gui/form/checkbox_field.rb +4 -1
- data/lib/aurita-gui/form/options_field.rb +4 -1
- data/lib/aurita-gui/form/selection_list.rb +32 -10
- data/lib/aurita-gui/javascript.rb +6 -6
- data/lib/aurita-gui/table.rb +109 -55
- data/lib/aurita-gui/widget.rb +27 -0
- data/spec/element.rb +4 -4
- data/spec/table.rb +53 -0
- metadata +36 -35
- data/History.txt +0 -85
- data/README +0 -10
- data/lib/aurita-gui/builtest.rb +0 -15
- data/lib/aurita-gui/selection_list_test.rb +0 -12
data/README.txt
ADDED
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.
|
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
data/lib/aurita-gui/element.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
[ :
|
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
|
-
|
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
|
-
|
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(
|
429
|
-
|
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
|
+
|