aurita-gui 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,4 +1,44 @@
1
- === 0.3.3 / 2009-01-24
1
+
2
+ === 0.3.4 / 2009-01-26
3
+
4
+ * Added retreiving elements from object tree
5
+ via DOM id:
6
+
7
+ x = HTML.build {
8
+ div {
9
+ p {
10
+ span(:id => :nested) { 'content before' }
11
+ }
12
+ }
13
+ }
14
+
15
+ x[:nested].content = 'content after'
16
+
17
+ Also added replacing elements in tree:
18
+
19
+ x[:nested] = HTML.div { 'other element' }
20
+
21
+ * Added marshalling of element hierarchies:
22
+
23
+ e = HTML.build { div.outer(:onclick => js.alert('message')) { p.inner 'nested content' } }
24
+ e.to_s == Element.marshal_load(e.marshal_dump)
25
+
26
+ and thus
27
+
28
+ Element.marshal_load(e.marshal_dump)[0].onclick
29
+ --> "alert('message');"
30
+ Element.marshal_load(e.marshal_dump)[0][0].class
31
+ --> 'inner'
32
+
33
+ This is useful for caching object hierarchies.
34
+
35
+
36
+ * Extended API by convenience shortcuts:
37
+ Content for elements can be set markaby-like now:
38
+
39
+ HTML.div 'content here' :class => :highlighted
40
+
41
+ === 0.3.3 / 2009-01-25
2
42
 
3
43
  * Added form helpers for template rendering.
4
44
  * Extended API by convenience shortcuts:
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.
16
16
  EOF
17
- s.version = '0.3.3'
17
+ s.version = '0.3.4'
18
18
  s.author = 'Tobias Fuchs'
19
19
  s.email = 'fuchs@atomnode.net'
20
20
  s.date = Time.now
@@ -42,6 +42,59 @@ module GUI
42
42
  # by any derived class like Aurita::GUI::Form or
43
43
  # Aurita::GUI::Table.
44
44
  #
45
+ # == Markaby style
46
+ #
47
+ # A syntax similar to markaby is also provided:
48
+ #
49
+ # HTML.build {
50
+ # div.outer {
51
+ # p.inner 'click me'
52
+ # } +
53
+ # div.footer 'text at the bottom'
54
+ # }.to_s
55
+ #
56
+ # -->
57
+ #
58
+ # <div class="outer">
59
+ # <p class="inner">paragraph</p>
60
+ # </div>
61
+ # <div class="footer">text at the bottom</div>
62
+ #
63
+ # == Javascript convenience
64
+ #
65
+ # When including the Javascript helper (aurita-gui/javascript),
66
+ # class HTML is extended by method .js, which provides
67
+ # building Javascript snippets in ruby:
68
+ #
69
+ # e = HTML.build {
70
+ # div.outer(:onclick => js.Wombat.alert('message')) {
71
+ # p.inner 'click me'
72
+ # }
73
+ # }
74
+ # e.to_s
75
+ # -->
76
+ # <div class="outer" onclick="Wombat.alert(\'message\'); ">
77
+ # <p class="inner">click me</p>
78
+ # </div>
79
+ #
80
+ # But watch out for operator precedence! This won't work, as
81
+ # .js() catches the block first:
82
+ #
83
+ # HTML.build {
84
+ # div :header, :onclick => js.funcall { 'i will not be passed to div' }
85
+ # }
86
+ # -->
87
+ # <div class="header" onclick="funcall();"></div>
88
+ #
89
+ #
90
+ # So be explicit, use parentheses:
91
+ #
92
+ # HTML.build {
93
+ # div(:header, :onclick => js.funcall) { 'aaah, much better' }
94
+ # }
95
+ # -->
96
+ # <div class="header" onclick="funcall();">aaah, much better</div>
97
+ #
45
98
  # == Notes
46
99
  #
47
100
  # Double-quotes in tag parameters will be escaped
@@ -139,11 +192,54 @@ module GUI
139
192
 
140
193
  # Redirect methods to setting or retreiving tag
141
194
  # attributes.
142
- def method_missing(meth, value=nil)
143
- return @attrib[meth] unless value or meth.to_s.include? '='
144
- @attrib[meth.to_s.gsub('=','').intern] = value
195
+ # There are several possible routings for method_missing:
196
+ #
197
+ # 1. Setting an attribute (no block, method ends in '=') Example:
198
+ # my_div = HTML.div 'content'
199
+ # my_div.onlick = "alert('foo');"
200
+ # puts my_div.to_s
201
+ # -->
202
+ # <div onclick="alert('foo');">content</div>
203
+ #
204
+ # 2. Retreiving an attribute (no block, method does not end in '='). Example:
205
+ #
206
+ # puts my_div.onlick
207
+ # -->
208
+ # 'alert(\'foo\');'
209
+ #
210
+ # 3. Setting the css class (block or value passed, method does not end in '='). Example:
211
+ #
212
+ # my_div.highlighted { 'content' }
213
+ # or
214
+ # my_div.highlighted 'content'
215
+ #
216
+ # -->
217
+ # <div class="highlighted">content</div>
218
+ #
219
+ def method_missing(meth, value=nil, &block)
220
+ if block_given? then
221
+ @attrib[:class] = meth
222
+ @attrib.update(value) if value.is_a? Hash
223
+ @content = yield
224
+ return self
225
+ elsif !value.nil? && !meth.to_s.include?('=') then
226
+ @attrib[:class] = meth
227
+ case value
228
+ when Hash then
229
+ @attrib.update(value)
230
+ @content = value[:content]
231
+ when String then
232
+ @content = value
233
+ end
234
+ return self
235
+ else
236
+ return @attrib[meth] unless value or meth.to_s.include? '='
237
+ @attrib[meth.to_s.gsub('=','').intern] = value
238
+ end
145
239
  end
146
240
 
241
+ # Set enclosed content of this element.
242
+ # Will be automatically wrapped in an array.
147
243
  def content=(obj)
148
244
  @content = [ obj ]
149
245
  end
@@ -156,13 +252,29 @@ module GUI
156
252
 
157
253
  # Do not redirect random access operators.
158
254
  def [](index)
159
- return @content[index]
160
- # raise ::Exception.new('Undefined method [] for ' << self.class.to_s)
255
+ return @content[index] if (index.is_a? Numeric)
256
+ return find_by_dom_id(index)
161
257
  end
258
+
259
+ # Retreive an element from object tree by
260
+ # its dom_id
261
+ def find_by_dom_id(dom_id)
262
+ dom_id = dom_id.to_sym
263
+ @content.each { |c|
264
+ if c.is_a? Element then
265
+ return c if (c.dom_id == dom_id)
266
+ sub = c.find_by_dom_id(dom_id)
267
+ return sub if sub
268
+ end
269
+ }
270
+ return nil
271
+ end
272
+
162
273
  # Do not redirect random access operators.
163
274
  def []=(index,element)
164
- @content[index] = element
165
- # raise ::Exception.new('Undefined method []= for ' << self.class.to_s)
275
+ @content[index] = element if (index.is_a? Numeric)
276
+ e = find_by_dom_id(index)
277
+ e.swap(element)
166
278
  end
167
279
  def length
168
280
  @content.length
@@ -171,6 +283,17 @@ module GUI
171
283
  @content.length == 0
172
284
  end
173
285
 
286
+ # Copy constructor. Replace self with
287
+ # other element.
288
+ def swap(other)
289
+ save_own_id = dom_id()
290
+ @tag = other.tag
291
+ @attrib = other.attrib
292
+ @attrib[:id] = save_own_id
293
+ @content = other.content
294
+ end
295
+ alias copy swap
296
+
174
297
  # Static helper definition for clearing
175
298
  # CSS floats.
176
299
  def clear_floating
@@ -179,6 +302,8 @@ module GUI
179
302
 
180
303
  # Render this element to a string.
181
304
  def string
305
+ return content.to_s if @tag == :pseudo
306
+
182
307
  attrib_string = ''
183
308
  @attrib.each_pair { |name,value|
184
309
  if value.instance_of?(Array) then
@@ -226,6 +351,7 @@ module GUI
226
351
  css_classes.collect { |c| c.to_sym if c }
227
352
  return css_classes
228
353
  end
354
+ alias css_class css_classes
229
355
 
230
356
  def add_class(css_class_name)
231
357
  @attrib[:class] = (css_classes << css_class_name.to_sym)
@@ -14,7 +14,55 @@ module GUI
14
14
  # is used to support design of OO-accessible GUI
15
15
  # elements.
16
16
  #
17
- # Other than other builder implementations (there
17
+ # Aurita::GUI is not intended to be used just for
18
+ # HTML rendering, that is: It is not just a builder.
19
+ # But it includes a builder syntax, extremely
20
+ # similar to Markaby, but also including Javascript:
21
+ #
22
+ # x = HTML.build {
23
+ # div.main(:onclick => js.funcall({ :foo => 23, :bar => 'batz' })) {
24
+ # h2.title 'This is what you already now from markaby'
25
+ # } +
26
+ # div.footer('Footer content' :id => :footer)
27
+ # }
28
+ # -->
29
+ # <div class="main" onclick="funcall({ foo: 23, bar: 'batz' }); ">
30
+ # <h2 class="title">
31
+ # This is what you already now from markaby
32
+ # </h2>
33
+ # <div class="footer" id="footer">Footer content</div>
34
+ # </div>
35
+ #
36
+ # But HTML.build is not just rendering text. It
37
+ # maintains an object tree. After rendering, you can
38
+ # access elements by their index, like in an multi-
39
+ # dimensional array:
40
+ #
41
+ # x[0][0].content = "But you didn't know this"
42
+ # x[0][0].onmouseover = Javascript.alert("or that")
43
+ #
44
+ # Or, more convenient, by their DOM id:
45
+ #
46
+ # x[:footer].content = 'Or finding an element by DOM id'
47
+ #
48
+ # You can even completely replace an element after
49
+ # rendering, but you won't be able to change it's DOM id
50
+ # (for obvious reasons):
51
+ #
52
+ # x[:footer] = HTML.span { 'i changed my mind' }
53
+ #
54
+ # -->
55
+ # <div class="main" onclick="funcall({ foo: 23, bar: 'batz' }); ">
56
+ # <h2 class="title" onmouseover="alert('or that'); ">
57
+ # But you didn&apos;t know this
58
+ # </h2>
59
+ # <span id="footer">I changed my mind</span>
60
+ # </div>
61
+ #
62
+ # And *this* is the reason there is an own builder
63
+ # implementation for Aurita::GUI in the first place.
64
+ #
65
+ # So, other than other builder implementations (there
18
66
  # are many), Aurita::GUI is not just a collection
19
67
  # of template helpers. It is data persistent and
20
68
  # maintains object hierarchies.
@@ -22,6 +70,10 @@ module GUI
22
70
  # rendered to a string - you can access and modify a
23
71
  # generated HTML hierarchy before rendering it.
24
72
  #
73
+ # Altogether, Aurita::GUI implements a flexible and
74
+ # convenient toolkit for GUI element (e.g. Widgets)
75
+ # libraries.
76
+ #
25
77
  # This is especially useful (and necessary) when
26
78
  # generating forms.
27
79
  #
@@ -53,6 +105,10 @@ module GUI
53
105
  # HTML.hr(:class => 'divide')
54
106
  # # --> '<hr class="divide" />'
55
107
  #
108
+ # Same as
109
+ #
110
+ # HTML.hr.divide
111
+ #
56
112
  # This is effectively a wrapper for
57
113
  #
58
114
  # Element.new(:tag => :hr, :class => 'divide')
@@ -72,11 +128,16 @@ module GUI
72
128
  #
73
129
  # So the following statements are equivalent:
74
130
  #
131
+ # e = HTML.div.highlight 'hello'
75
132
  # e = HTML.div 'hello', :class => 'highlight'
76
133
  # e = HTML.div(:class => :highlight) { 'hello' }
77
134
  # e = HTML.div(:class => :highlight, :content => 'hello')
78
135
  #
79
- # In all cases, e.to_s renders:
136
+ # In all cases, e is an Element instance:
137
+ #
138
+ # e = Element.new(:tag => :div, :class => :highlight, :content => 'hello')
139
+ #
140
+ # That again renders to a String:
80
141
  #
81
142
  # <div class="highlight">hello</div>
82
143
  #
@@ -103,9 +164,9 @@ module GUI
103
164
  # div(:class => :css_class,
104
165
  # :onmouseover => "do_something_with(this);") {
105
166
  # ul(:id => :the_list) {
106
- # li(:class => :first) { 'foo' } +
107
- # li(:class => :second) { 'bar' } +
108
- # li(:class => :third) { 'batz' }
167
+ # li.first { 'foo' } +
168
+ # li.second { 'bar' } +
169
+ # li.third { 'batz' }
109
170
  # }
110
171
  # }
111
172
  # }
@@ -124,14 +185,17 @@ module GUI
124
185
  #
125
186
  # This is due to a class_eval restriction every
126
187
  # builder struggles with at the moment.
127
- # (There is mixico, but it is not portable).
188
+ # (There is mixico, but it is not portable), as
189
+ # a price you pay for using class_eval on the
190
+ # build block, which enables the concise syntax.
128
191
  #
129
- # To come by this inconvenience, use, for
130
- # example:
192
+ # To come by this inconvenience, use puts, as you'd
193
+ # do in regular ruby code.
194
+ # In the example:
131
195
  #
132
196
  # HTML.build {
133
197
  # div {
134
- # HTML.h2 { compute_string() }
198
+ # h2 { puts compute_string() }
135
199
  # }
136
200
  # }.to_s
137
201
  #
@@ -140,18 +204,6 @@ module GUI
140
204
  # This works, as explicit calls to class methods
141
205
  # of HTML are not rendered using class_eval.
142
206
  #
143
- # The previous example effectively does the
144
- # following:
145
- #
146
- # t2 = HTML.div(:class => :css_class,
147
- # :onmouseover => "do_something_with(this);") {
148
- # HTML.ul(:id => :the_list) {
149
- # HTML.li(:class => :first) { 'foo' } +
150
- # HTML.li(:class => :second) { 'bar' } +
151
- # HTML.li(:class => :third) { 'batz' }
152
- # }
153
- # }
154
- # assert_equal(t1.to_s, t2.to_s)
155
207
  #
156
208
  # Element is not a full Enumerable implementation (yet),
157
209
  # but it offers random access operators ...
@@ -233,7 +285,6 @@ module GUI
233
285
 
234
286
  def self.render(meth_name, *args, &block)
235
287
  raise ::Exception.new('Missing attributes for HTML.' << meth_name.inspect) unless args
236
-
237
288
  e = Element.new(*args, &block)
238
289
  e.tag = meth_name
239
290
  e
@@ -241,7 +292,7 @@ module GUI
241
292
 
242
293
  def self.build(&block)
243
294
  raise ::Exception.new('Missing block for HTML.render') unless block_given?
244
- self.class_eval(&block)
295
+ Element.new(:tag => :pseudo) { self.class_eval(&block) }
245
296
  end
246
297
 
247
298
 
@@ -260,6 +311,10 @@ module GUI
260
311
  render(meth_name, *attribs, &block)
261
312
  end
262
313
 
314
+ def self.puts(arg)
315
+ arg
316
+ end
317
+
263
318
  =begin
264
319
  XHTML_TAGS = [ :html, :div, :p, :input, :select, :option, :ul, :ol, :li ]
265
320
  for t in XHTML_TAGS do
@@ -153,6 +153,18 @@ module GUI
153
153
 
154
154
  end # class
155
155
 
156
+ class HTML
157
+
158
+ def self.js(&block)
159
+ if block_given? then
160
+ Javascript.build(&block)
161
+ else
162
+ Javascript
163
+ end
164
+ end
165
+
166
+ end
167
+
156
168
  end
157
169
  end
158
170
 
@@ -0,0 +1,25 @@
1
+
2
+ module Aurita
3
+ module GUI
4
+
5
+ module Marshal_Helper
6
+ def marshal_dump
7
+ c = { :tag => @tag, :content => @content }
8
+ @attrib.update(c)
9
+ end
10
+ end
11
+
12
+ module Marshal_Helper_Class_Methods
13
+ def marshal_load(dump)
14
+ self.new(dump)
15
+ end
16
+ end
17
+
18
+ class Element
19
+ include Marshal_Helper
20
+ extend Marshal_Helper_Class_Methods
21
+ end
22
+
23
+ end
24
+ end
25
+
data/spec/html.rb CHANGED
@@ -35,18 +35,37 @@ describe Aurita::GUI::HTML, "basic rendering" do
35
35
  end
36
36
 
37
37
  it "should still provide an object tree" do
38
- @e[0].id.should == :header
39
- @e[1].id.should == :content
40
- @e[1].content.should == [ 'Content' ]
41
- @e[1].content = 'Altered'
42
- @e[1].content.should == [ 'Altered' ]
38
+ @e[0].css_class.first.should == :outer
39
+ @e[0][0].id.should == :header
40
+ @e[0][1].id.should == :content
41
+ @e[0][1].content.should == [ 'Content' ]
42
+ @e[0][1].content = 'Altered'
43
+ @e[0][1].content.should == [ 'Altered' ]
43
44
  @e.to_s.should == '<div class="outer"><h2 id="header">Header</h2><p id="content">Altered</p></div>'
44
45
  end
45
46
 
47
+ it "should be possible to retreive elements just by DOM id" do
48
+ e = @e[:header]
49
+ e.content.first.should == 'Header'
50
+ end
51
+
46
52
  it "should escape double-quotes in tag parameters" do
47
53
  e = Element.new(:onclick => 'alert("message");')
48
54
  e.onclick.should == 'alert("message");'
49
55
  e.to_s.should == '<div onclick="alert(\"message\");"></div>'
50
56
  end
51
57
 
58
+ it "should provide a shortcut for setting css classes" do
59
+
60
+ e = HTML.build {
61
+ div.wrapper {
62
+ h2.highlight {
63
+ 'content here'
64
+ } +
65
+ p.footer('footer content')
66
+ }
67
+ }
68
+ e.to_s.should == '<div class="wrapper"><h2 class="highlight">content here</h2><p class="footer">footer content</p></div>'
69
+ end
70
+
52
71
  end
data/spec/javascript.rb CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
2
  require('rubygems')
3
3
  require('aurita-gui/javascript')
4
+ require('aurita-gui/html')
4
5
 
5
6
  include Aurita::GUI
6
7
 
@@ -21,5 +22,14 @@ describe Aurita::GUI::Javascript, "basic rendering" do
21
22
  Javascript.Some.Namespace.do_stuff(23, 'wombat').to_s.should == "Some.Namespace.do_stuff(23,'wombat'); "
22
23
  end
23
24
 
25
+ it "should extend class HTML by a helper method '.js'" do
26
+ e = HTML.build {
27
+ div.outer(:onclick => js.Wombat.alert('message')) {
28
+ p.inner 'click me'
29
+ }
30
+ }
31
+ e.to_s.should == '<div class="outer" onclick="Wombat.alert(\'message\'); "><p class="inner">click me</p></div>'
32
+ end
33
+
24
34
  end
25
35
 
data/spec/marshal.rb ADDED
@@ -0,0 +1,33 @@
1
+
2
+ require('rubygems')
3
+ require('aurita-gui/javascript')
4
+ require('aurita-gui/html')
5
+ require('aurita-gui/marshal')
6
+
7
+ include Aurita::GUI
8
+
9
+ describe Aurita::GUI::Marshal_Helper, "dump and load" do
10
+ before do
11
+ end
12
+
13
+ it "should provide marshal dumping of Element instances" do
14
+ e = HTML.div 'content here', :id => 'dump_test'
15
+ l = Element.marshal_load(e.marshal_dump)
16
+ e.to_s.should == l.to_s
17
+ end
18
+
19
+ it "should also work for object hierarchies" do
20
+ e = HTML.build {
21
+ div.outer(:id => :outer_div) {
22
+ p.inner(:id => :inner_p) {
23
+ 'nested content'
24
+ } +
25
+ button.confirm(:onclick => js.alert('clicked')) { 'click me' }
26
+ }
27
+ }
28
+ e.to_s.should == Element.marshal_load(e.marshal_dump).to_s
29
+ e[0][1].onclick.to_s.should == "alert('clicked'); "
30
+ end
31
+
32
+ end
33
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aurita-gui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Fuchs
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-25 00:00:00 +01:00
12
+ date: 2009-01-26 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -22,7 +22,6 @@ extensions: []
22
22
  extra_rdoc_files: []
23
23
 
24
24
  files:
25
- - fieldtest.rb
26
25
  - cheatsheet.rb
27
26
  - spec
28
27
  - History.txt
@@ -35,6 +34,7 @@ files:
35
34
  - LICENSE
36
35
  - lib/aurita-gui
37
36
  - lib/aurita-gui.rb
37
+ - lib/aurita-gui/marshal.rb
38
38
  - lib/aurita-gui/javascript.rb
39
39
  - lib/aurita-gui/button.rb
40
40
  - lib/aurita-gui/form.rb
@@ -59,6 +59,7 @@ files:
59
59
  - lib/aurita-gui/form/template_helper.rb
60
60
  - lib/aurita-gui/form/input_field.rb
61
61
  - lib/aurita-gui/form/date_field.rb
62
+ - spec/marshal.rb
62
63
  - spec/javascript.rb
63
64
  - spec/form.rb
64
65
  - spec/html.rb
data/fieldtest.rb DELETED
@@ -1,64 +0,0 @@
1
-
2
- require('rubygems')
3
- require('aurita-gui')
4
- require('test/unit/assertions')
5
-
6
- include Test::Unit::Assertions
7
-
8
- include Aurita::GUI
9
-
10
- form = Form.new(:name => :the_form,
11
- :id => :the_form_id,
12
- :action => :where_to_send)
13
- # You can either set all attributes in the
14
- # constructor call ...
15
- text = Input_Field.new(:name => :description,
16
- :class => :the_css_class,
17
- :label => 'Enter description',
18
- :onfocus => "alert('input focussed');",
19
- :value => 'some text')
20
- # Or set them afterwards:
21
- text.onblur = "alert('i lost focus :(');"
22
-
23
- puts "\nDefault ---------------------------------\n"
24
- puts text.to_s
25
- puts "\nDisabled --------------------------------\n"
26
- text.disable!
27
- puts text.to_s
28
- puts "\nEnabled ---------------------------------\n"
29
- text.enable!
30
- puts text.to_s
31
- puts "\nReadonly --------------------------------\n"
32
- text.readonly!
33
- puts text.to_s
34
- puts "\nEditable --------------------------------\n"
35
- text.editable!
36
- puts text.to_s
37
-
38
- puts "\nTo hidden -------------------------------\n"
39
- puts text.to_hidden_field.to_s
40
-
41
- # Add it to the form:
42
- form.add(text)
43
- puts "\nThe Form --------------------------------\n"
44
-
45
- # Access it again, via name:
46
- assert_equal(form[:description], text)
47
- # Or by using its index:
48
- assert_equal(form[0], text)
49
-
50
- # This is useful!
51
- form[:description].value = 'change value'
52
-
53
- checkbox = Checkbox_Field.new(:name => :enable_me,
54
- :value => :foo,
55
- :label => 'Check me',
56
- :options => [ :foo, :bar ] )
57
- form.add(checkbox)
58
- form[:description].required = true
59
- checkbox.required = true
60
- form.fields = [ :description, :enable_me ]
61
- form.delete_field(:enable_me)
62
- puts form.to_s
63
-
64
-