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 +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
|
+
|