ezframe 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +11 -4
  4. data/app_template/asset/image/favicon.ico +0 -0
  5. data/app_template/asset/js/ezframe.js +288 -0
  6. data/app_template/config/generic.yml +3 -0
  7. data/app_template/config/materialize.yml +5 -0
  8. data/{example/chat → app_template}/config.ru +2 -10
  9. data/app_template/pages/basic.rb +5 -0
  10. data/exe/create_table.rb +1 -0
  11. data/exe/setup.rb +15 -0
  12. data/ezframe.gemspec +3 -4
  13. data/lib/ezframe/auth.rb +15 -12
  14. data/lib/ezframe/column_set.rb +68 -28
  15. data/lib/ezframe/column_type.rb +231 -68
  16. data/lib/ezframe/config.rb +4 -0
  17. data/lib/ezframe/controller.rb +20 -10
  18. data/lib/ezframe/database.rb +10 -3
  19. data/lib/ezframe/ht.rb +167 -0
  20. data/lib/ezframe/html.rb +28 -4
  21. data/lib/ezframe/japanese_utils.rb +40 -0
  22. data/lib/ezframe/{pages.rb → loader.rb} +3 -0
  23. data/lib/ezframe/materialize.rb +55 -20
  24. data/lib/ezframe/model.rb +0 -2
  25. data/lib/ezframe/page_base.rb +18 -12
  26. data/lib/ezframe/page_kit.rb +12 -33
  27. data/lib/ezframe/template.rb +20 -15
  28. data/lib/ezframe/util.rb +5 -0
  29. data/lib/ezframe/version.rb +1 -1
  30. data/lib/ezframe.rb +5 -4
  31. metadata +27 -70
  32. data/example/auth/Gemfile +0 -8
  33. data/example/auth/asset/css/materialize.min.css +0 -13
  34. data/example/auth/asset/js/common.js +0 -200
  35. data/example/auth/asset/js/htmlgen.js +0 -79
  36. data/example/auth/columns/user.yml +0 -12
  37. data/example/auth/config/view_conf.yml +0 -3
  38. data/example/auth/config.ru +0 -26
  39. data/example/auth/pages/app.rb +0 -61
  40. data/example/auth/template/base.html +0 -12
  41. data/example/chat/Gemfile +0 -9
  42. data/example/chat/asset/css/materialize.min.css +0 -13
  43. data/example/chat/asset/js/common.js +0 -200
  44. data/example/chat/asset/js/htmlgen.js +0 -79
  45. data/example/chat/columns/belong.yml +0 -6
  46. data/example/chat/columns/channel.yml +0 -3
  47. data/example/chat/columns/talk.yml +0 -6
  48. data/example/chat/columns/user.yml +0 -12
  49. data/example/chat/config/view_conf.yml +0 -3
  50. data/example/chat/pages/app.rb +0 -59
  51. data/example/chat/template/base.html +0 -12
  52. data/example/todo/Gemfile +0 -8
  53. data/example/todo/asset/css/datatable.css +0 -54
  54. data/example/todo/asset/css/materialize.min.css +0 -13
  55. data/example/todo/asset/js/common.js +0 -135
  56. data/example/todo/asset/js/datatable.js +0 -1814
  57. data/example/todo/asset/js/htmlgen.js +0 -79
  58. data/example/todo/asset/js/init.js +0 -3
  59. data/example/todo/asset/js/materialize.min.js +0 -6
  60. data/example/todo/asset/js/mydatatable.js +0 -9
  61. data/example/todo/asset/js/mymaterialize.js +0 -22
  62. data/example/todo/columns/todo.yml +0 -9
  63. data/example/todo/config/view_conf.yml +0 -3
  64. data/example/todo/config.ru +0 -15
  65. data/example/todo/pages/app.rb +0 -93
  66. data/example/todo/template/base.html +0 -12
  67. data/exe/myrackup +0 -5
  68. data/lib/ezframe/hthash.rb +0 -116
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
+ require "date"
2
3
 
3
4
  module Ezframe
4
5
  class TypeBase
5
- attr_accessor :attribute, :parent
6
- attr_writer :value
6
+ attr_accessor :attribute, :parent, :error
7
7
 
8
8
  def self.get_class(key)
9
9
  return nil unless key
@@ -25,13 +25,13 @@ module Ezframe
25
25
  end
26
26
 
27
27
  def initialize(attr = nil)
28
- @attribute = attr if attr
28
+ @attribute = attr || {}
29
29
  end
30
30
 
31
31
  def key
32
- @attribute[:key]
33
- end
34
-
32
+ @attribute[:key]
33
+ end
34
+
35
35
  def label
36
36
  return nil if @attribute[:hidden]
37
37
  @attribute[:label]
@@ -41,8 +41,8 @@ module Ezframe
41
41
  @value
42
42
  end
43
43
 
44
- def view
45
- @value
44
+ def value=(v)
45
+ @value = v
46
46
  end
47
47
 
48
48
  def db_type
@@ -53,134 +53,295 @@ module Ezframe
53
53
  value
54
54
  end
55
55
 
56
- def form
56
+ def form(opts = {})
57
57
  nil
58
58
  end
59
+
60
+ def form_html(opts = {})
61
+ form_h = form(opts)
62
+ return nil unless form_h
63
+ return Html.convert(form_h)
64
+ end
65
+
66
+ def view(opts = {})
67
+ return nil if no_view?
68
+ @value
69
+ end
70
+
71
+ def validate
72
+ if !@value || @value.empty?
73
+ if @attribute[:must]
74
+ @error = "必須項目です。"
75
+ return @error
76
+ end
77
+ end
78
+ return nil
79
+ end
80
+
81
+ def no_edit?
82
+ return ((@attribute[:hidden] || @attribute[:no_edit]) && !@attribute[:force])
83
+ end
84
+
85
+ def no_view?
86
+ return (@attribute[:hidden] && !@attribute[:force])
87
+ end
59
88
  end
60
89
 
61
- class StringType < TypeBase
90
+ class TextType < TypeBase
62
91
  def normalize
63
- @value.gsub!(/ /, ' ')
92
+ return unless @value
93
+ @value = @value.to_s
94
+ @value.gsub!(/ /, " ")
95
+ @value.gsub!(/\s+/, " ")
96
+ @value.strip!
64
97
  end
65
98
 
66
- def form
67
- return nil if @attribute[:hidden] && !@attribute[:force]
68
- { tag: 'input', type: 'text', name: @attribute[:key], key: @attribute[:key], label: @attribute[:label], value: @value }
99
+ def value=(v)
100
+ super(v)
101
+ normalize
102
+ end
103
+
104
+ def form(opts = {})
105
+ return nil if no_edit? && !opts[:force]
106
+ h = { tag: "input", type: "text", name: @attribute[:key], key: @attribute[:key], label: @attribute[:label], value: @value || "" }
107
+ h[:size] = @attribute[:size] if @attribute[:size]
108
+ h
69
109
  end
70
110
 
71
111
  def db_type
72
- "string"
112
+ "text"
73
113
  end
74
114
  end
75
115
 
76
- class IntType < StringType
77
- def view
78
- return nil if @attribute[:hidden]
79
- Util.add_comma(@value.to_i)
116
+ class IntType < TextType
117
+ def view(opts = {})
118
+ return nil if no_view? && !opts[:force]
119
+ return Util.add_comma(@value.to_i)
80
120
  end
81
121
 
82
- def form
83
- return nil if @attribute[:hidden]
84
- { tag: 'input', type: 'number', key: @attribute[:key], label: @attribute[:label], value: @value }
122
+ def value=(v)
123
+ if v.nil?
124
+ @value = nil
125
+ return
126
+ end
127
+ if v.is_a?(String)
128
+ v = v.tr("0-9", "0-9").strip
129
+ end
130
+ @value = v.to_i
131
+ end
132
+
133
+ def form(opts = {})
134
+ return nil if no_edit? && !opts[:force]
135
+ { tag: "input", type: "number", key: @attribute[:key], label: @attribute[:label], value: @value || "" }
85
136
  end
86
137
 
87
138
  def db_type
88
139
  "int"
89
140
  end
141
+
142
+
90
143
  end
91
144
 
92
145
  class ForeignType < IntType
93
- def view
146
+ def view(opts = {})
147
+ return nil if no_view? && !opts[:force]
94
148
  dataset = @parent.db.dataset[self.type.inner]
95
149
  data = dataset.get(id: @value)
96
- data[@attribute[:view]]
150
+ return data[@attribute[:view]]
151
+ end
152
+
153
+ def form
154
+ return nil
97
155
  end
98
156
  end
99
-
157
+
100
158
  class IdType < IntType
101
159
  def label
102
- return nil if @attribute[:hidden] && !@attribute[:force]
103
- "ID"
160
+ return nil if no_view?
161
+ return "ID"
162
+ end
163
+
164
+ def form(opts = {})
165
+ return nil
104
166
  end
105
167
  end
106
168
 
107
- class PasswordType < StringType
108
- def form
109
- { tag: "input", type: "password", label: @attribute[:label], value: @value}
169
+ class PasswordType < TextType
170
+ def form(opts = {})
171
+ return nil if no_edit? && !opts[:force]
172
+ return { tag: "input", type: "password", label: @attribute[:label], value: @value || "" }
110
173
  end
111
174
 
112
175
  def db_value
113
- value
176
+ return value
114
177
  end
115
178
  end
116
179
 
117
180
  class SelectType < TypeBase
118
- def form
119
- return nil if @attribute[:hidden]
120
- { tag: 'select', key: @attribute[:key], label: @attribute[:label], items: @attribute[:items], value: @value }
181
+ def form(opts = {})
182
+ return nil if no_edit? && !opts[:force]
183
+ # puts "selectType: #{@attribute[:items].inspect}"
184
+ return { tag: "select", key: @attribute[:key], label: @attribute[:label], items: @attribute[:items], value: @value }
121
185
  end
122
186
 
123
187
  def db_type
124
- "string"
188
+ return "text"
125
189
  end
126
190
  end
127
191
 
128
192
  class CheckboxType < TypeBase
129
- def form
130
- return nil if @attribute[:hidden]
131
- { tag: "checkbox", key: @attribute[:key], name: @attribute[:key], value: parent[:id].value, label: @attribute[:label] }
193
+ def form(opts = {})
194
+ return nil if no_edit? && !opts[:force]
195
+ return { tag: "checkbox", key: @attribute[:key], name: @attribute[:key], value: parent[:id].value, label: @attribute[:label] }
132
196
  end
133
197
 
134
198
  def db_type
135
- "int"
199
+ return "int"
136
200
  end
137
201
  end
138
202
 
139
- class DateType < StringType
140
- def form
203
+ class DateType < TextType
204
+ def form(opts = {})
205
+ return nil if no_edit? && !opts[:force]
141
206
  h = super
142
- h[:type] = 'date' if h
143
- h
207
+ if h
208
+ # h[:type] = 'date'
209
+ h[:type] = "text"
210
+ h[:class] = "datepicker"
211
+ h[:value] = value || ""
212
+ end
213
+ return h
144
214
  end
145
215
 
146
216
  def db_type
147
- "datetime"
217
+ "date"
218
+ end
219
+
220
+ def value
221
+ if @value.is_a?(Date) || @value.is_a?(Time)
222
+ return "%d-%02d-%02d" % [@value.year, @value.mon, @value.mday]
223
+ end
224
+ return @value
225
+ end
226
+
227
+ def value=(v)
228
+ if v.nil?
229
+ @value = nil
230
+ return
231
+ end
232
+ if v.is_a?(String)
233
+ if v.strip.empty?
234
+ @value = nil
235
+ return
236
+ end
237
+ y, m, d = v.split(/[\-\/]/)
238
+ # puts "date=#{v.inspect}"
239
+ @value = Date.new(y.to_i, m.to_i, d.to_i)
240
+ return
241
+ end
242
+ if v.is_a?(Date) || v.is_a?(Time)
243
+ @value = v
244
+ else
245
+ mylog "[WARN] illegal value for date type: #{v.inspect}"
246
+ end
247
+ end
248
+
249
+ def view(opts = {})
250
+ return nil if no_view? && !opts[:force]
251
+ if @value.is_a?(Time) || @value.is_a?(Date)
252
+ return "#{@value.year}/#{@value.mon}/#{@value.mday}"
253
+ else
254
+ return @value
255
+ end
148
256
  end
149
257
  end
150
-
151
- class EmailType < StringType
152
- def form
153
- h = super
154
- h[:type] = 'email' if h
155
- h
258
+
259
+ class TimeType < TextType
260
+ end
261
+
262
+ class DatetimeType < DateType
263
+ def db_type
264
+ "timestamp"
156
265
  end
157
266
  end
158
267
 
159
- class TelephoneType < StringType
160
- def form
268
+ class BirthdayType < DateType
269
+ def form(opts = {})
270
+ return nil if no_edit? && !opts[:force]
271
+ prefix = @attribute[:key]
272
+ now = Time.now
273
+ year_list = []
274
+ 110.times do |y|
275
+ year = now.year - y - 10
276
+ year_list.push [year, "#{year}年 (#{Japanese.convert_wareki(year)})"]
277
+ end
278
+ mon_list = (1..12).map { |m| [m, "#{m}月"] }
279
+ mon_list.unshift([0, "(月)"])
280
+ mday_list = (1..31).map { |d| [d, "#{d}日"] }
281
+ mday_list.unshift([0, "(日)"])
282
+ return [Ht.select(name: "#{prefix}_year", items: year_list),
283
+ Ht.select(name: "#{prefix}_mon", items: mon_list),
284
+ Ht.select(name: "#{prefix}_mday", items: mday_list)]
285
+ end
286
+ end
287
+
288
+ class EmailType < TextType
289
+ def form(opts = {})
290
+ return nil if no_edit? && !opts[:force]
161
291
  h = super
162
- h[:type] = 'tel' if h
163
- h
292
+ h[:type] = "email" if h
293
+ return h
294
+ end
295
+
296
+ def normalize
297
+ return unless @value
298
+ @value = NKF.nkf('-w -Z4', @value)
299
+ end
300
+
301
+ def validate
302
+ super
303
+ return @error if @error
304
+ if email_format?
305
+ @error = "形式が正しくありません"
306
+ return @error
307
+ end
308
+ return nil
309
+ end
310
+
311
+ def email_format?
312
+ return nil unless @value
313
+ return @value =~ /^[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
164
314
  end
165
315
  end
166
316
 
167
- class JpnameType < StringType
317
+ class TelType < TextType
318
+ def validate
319
+ super
320
+ return @error if @error
321
+ unless /^0\d{9,10}$/ =~ @value
322
+ @error = "形式が正しくありません"
323
+ return @error
324
+ end
325
+ end
168
326
  end
169
327
 
170
- class JpnameKanaType < StringType
171
- def set(val)
172
- val = val.tr('ァ-ン', 'ぁ-ん')
173
- super(val)
328
+ class JpnameType < TextType
329
+ end
330
+
331
+ class JpnameKanaType < TextType
332
+ def normalize
333
+ return unless @value
334
+ super
335
+ @value.tr!("ァ-ン", "ぁ-ん")
174
336
  end
175
337
 
176
338
  def validation
177
339
  unless /^[ぁ-ん ]+$/ =~ @value
178
- 'ひらがなのみで入力してください。'
340
+ "ひらがなのみで入力してください。"
179
341
  end
180
342
  end
181
343
  end
182
344
 
183
- #
184
345
  class PrefectureType < SelectType
185
346
  def initialize(attr)
186
347
  super(attr)
@@ -195,26 +356,28 @@ module Ezframe
195
356
  @pref_a.each_with_index { |p, i| @pref_h[i] = p }
196
357
  end
197
358
 
198
- def form
359
+ def form(opts = {})
360
+ return nil if no_edit? && !opts[:force]
199
361
  h = super
200
362
  h[:items] = @pref_h
201
- h
363
+ return h
202
364
  end
203
365
 
204
366
  def view
205
- @pref_h[@value.to_i]
367
+ return @pref_h[@value.to_i]
206
368
  end
207
369
  end
208
370
 
209
371
  # Japanese Zipcode type column
210
- class ZipcodeType < StringType
211
- def view
372
+ class ZipcodeType < TextType
373
+ def view(opts = {})
374
+ return nil if no_view? && !opts[:force]
212
375
  return "" unless @value
213
- @value.to_s.gsub(/(\d{3})(\d{4})/) { "#{$1}-#{$2}" }
376
+ return @value.to_s.gsub(/(\d{3})(\d{4})/) { "#{$1}-#{$2}" }
214
377
  end
215
378
 
216
379
  def db_type
217
- "string"
380
+ return "text"
218
381
  end
219
382
  end
220
383
  end
@@ -28,6 +28,10 @@ module Ezframe
28
28
  @value_h||={}
29
29
  @value_h[k]=v
30
30
  end
31
+
32
+ def inspect
33
+ @value_h.inspect
34
+ end
31
35
  end
32
36
  end
33
37
  end
@@ -9,22 +9,33 @@ module Ezframe
9
9
  Model.init
10
10
  model = Model.get_clone
11
11
  Auth.init_warden
12
- Auth.model = model
12
+ @request.env["model"] = model
13
+ # Auth.model = model
13
14
 
14
15
  mylog("exec: path=#{request.path_info} params=#{request.params}")
15
16
  klass, method = PageBase::decide_route(request.path_info)
16
- mylog "klass=#{klass}, method=#{method}"
17
+ unless klass
18
+ response.status = 404
19
+ response['Content-Type'] = 'text/html; charset=utf-8'
20
+ response.body = [ Html.convert(Ht.p("file not found")) ]
21
+ return
22
+ end
23
+ method = "default" if !method || method=="undefined"
17
24
  page = klass.new(request, model)
18
25
  if request.post?
19
26
  method_full_name = "public_#{method}_post"
20
27
  else
21
28
  method_full_name = "public_#{method}_page"
22
29
  end
23
- warden.authenticate! if page.auth
30
+ if page.auth
31
+ warden.authenticate!
32
+ end
24
33
  # request.env["rack.session"]["kamatest"]="usable"
25
- mylog "method: #{klass}.#{method_full_name}"
26
- mylog "rack.session.id=#{request.env['rack.session'].id}"
34
+ # mylog "method: #{klass}.#{method_full_name}"
35
+ #mylog "rack.session.id=#{request.env['rack.session'].id}"
27
36
  mylog "rack.session.keys=#{request.env['rack.session'].keys}"
37
+ #mylog "warden=#{request.env['warden'].inspect}"
38
+ mylog "klass=#{klass}, method=#{method_full_name}"
28
39
  body = if page.respond_to?(method_full_name)
29
40
  page.send(method_full_name)
30
41
  else
@@ -41,11 +52,10 @@ module Ezframe
41
52
  response.status = 200
42
53
  end
43
54
 
44
-
45
- def file_not_found(response)
46
- response.body = ['path not found']
47
- response.status = 404
48
- end
55
+ # def file_not_found(response)
56
+ # response.body = ['path not found']
57
+ # response.status = 404
58
+ # end
49
59
 
50
60
  def warden
51
61
  @request.env["warden"]
@@ -5,13 +5,15 @@ module Ezframe
5
5
  class Database
6
6
  attr_accessor :sequel
7
7
 
8
- def initialize(dbfile = "db/devel.sqlite")
8
+ def initialize(dbfile = nil)
9
9
  @dbfile = dbfile
10
10
  connect
11
11
  end
12
12
 
13
13
  def connect
14
- @sequel = Sequel.connect("sqlite://#{@dbfile}", loggers: [Logger.new($stdout)])
14
+ @dbfile ||= ENV["EZFRAME_DB"] || Config[:database] || "sqlite://db/devel.sqlite"
15
+ puts "Database.connect: dbfile=#{@dbfile}"
16
+ @sequel = Sequel.connect(@dbfile, loggers: [Logger.new($stdout)])
15
17
  end
16
18
 
17
19
  def exec(sql)
@@ -26,8 +28,13 @@ module Ezframe
26
28
  %w[id created_at updated_at].each do |key|
27
29
  dbtype_h.delete(key.to_sym)
28
30
  end
31
+ puts "create_table: #{table_name}"
29
32
  @sequel.create_table(table_name) do
30
- primary_key :id, auto_increment: true
33
+ # if @dbfile.index("postgresql")
34
+ serial :id
35
+ # else
36
+ #primary_key :id, auto_increment: true
37
+ #end
31
38
  dbtype_h.each do |key, dbtype|
32
39
  column(key, dbtype)
33
40
  end
data/lib/ezframe/ht.rb ADDED
@@ -0,0 +1,167 @@
1
+ # HTMLを中間表現としてのハッシュであるhthashを生成するためのクラス
2
+ module Ezframe
3
+ module Ht
4
+ class << self
5
+ # メソッド名の名前のタグのhthashを生成
6
+ def wrap_tag(opts = {})
7
+ if opts.is_a?(String) || opts.is_a?(Array)
8
+ h = { child: opts }
9
+ elsif opts.is_a?(Hash)
10
+ if opts[:tag] && !__callee__.to_s.index("wrap_tag")
11
+ h = { child: opts }
12
+ else
13
+ h = opts.dup
14
+ end
15
+ else
16
+ mylog("wrap_tag: unknown type: #{opts.inspect}")
17
+ return nil
18
+ end
19
+ h[:tag] ||= __callee__.to_s
20
+ raise "no tag" if h[:tag] == "wrap_tag"
21
+ return h
22
+ end
23
+
24
+ alias_method :script, :wrap_tag
25
+
26
+ alias_method :h1, :wrap_tag
27
+ alias_method :h2, :wrap_tag
28
+ alias_method :h3, :wrap_tag
29
+ alias_method :h4, :wrap_tag
30
+ alias_method :h5, :wrap_tag
31
+ alias_method :h6, :wrap_tag
32
+ alias_method :p, :wrap_tag
33
+ alias_method :br, :wrap_tag
34
+ alias_method :hr, :wrap_tag
35
+ alias_method :div, :wrap_tag
36
+ alias_method :span, :wrap_tag
37
+ alias_method :i, :wrap_tag
38
+ alias_method :strong, :wrap_tag
39
+ alias_method :ul, :wrap_tag
40
+ alias_method :ol, :wrap_tag
41
+ alias_method :li, :wrap_tag
42
+ alias_method :table, :wrap_tag
43
+ alias_method :thead, :wrap_tag
44
+ alias_method :tbody, :wrap_tag
45
+ alias_method :tr, :wrap_tag
46
+ alias_method :th, :wrap_tag
47
+ alias_method :td, :wrap_tag
48
+ alias_method :img, :wrap_tag
49
+ alias_method :a, :wrap_tag
50
+ alias_method :form, :wrap_tag
51
+ alias_method :input, :wrap_tag
52
+ alias_method :select, :wrap_tag
53
+ alias_method :textarea, :wrap_tag
54
+ alias_method :label, :wrap_tag
55
+ alias_method :fieldset, :wrap_tag
56
+ alias_method :nav, :wrap_tag
57
+ alias_method :footer, :wrap_tag
58
+
59
+ alias_method :small, :wrap_tag
60
+
61
+ alias_method :checkbox, :wrap_tag
62
+ alias_method :radio, :wrap_tag
63
+
64
+ # materialize用のiconメソッド
65
+ # 引数が文字列だったら、それをname属性とする
66
+ def icon(arg)
67
+ if arg.is_a?(Hash)
68
+ h = arg.clone
69
+ h[:tag] = "icon"
70
+ return wrap_tag(h)
71
+ elsif arg.is_a?(String)
72
+ return { tag: "icon", name: arg }
73
+ end
74
+ end
75
+
76
+ # buttonタグにはデフォルトでtype=button属性を付ける
77
+ def button(arg)
78
+ arg[:tag] = "button"
79
+ unless arg[:type]
80
+ arg[:type] = "button"
81
+ end
82
+ wrap_tag(arg)
83
+ end
84
+
85
+ def multi_div(class_a, child)
86
+ class_a.reverse.each do |klass|
87
+ child = Ht.div(class: klass, child: child)
88
+ end
89
+ return child
90
+ end
91
+ end
92
+
93
+ # 配列を<UL><OL>要素に変換するためのクラス
94
+ class List < Array
95
+ attr_accessor :tag
96
+ def to_h(opts = {})
97
+ return nil if self.empty?
98
+ child = self.map do |elem|
99
+ { tag: "li", child: elem }
100
+ end
101
+ h = { tag: @tag, child: child }
102
+ h.update(opts)
103
+ return h
104
+ end
105
+ end
106
+
107
+ # 配列を<UL>要素に変換するためのクラス
108
+ class Ul < List
109
+ def to_h(opts = {})
110
+ @tag = "ul"
111
+ return super(opts)
112
+ end
113
+ end
114
+
115
+ # 配列を<OL>要素に変換するためのクラス
116
+ class Ol < List
117
+ def to_h(opts = {})
118
+ @tag = "ol"
119
+ return super(opts)
120
+ end
121
+ end
122
+
123
+ # テーブルを生成するためのクラス
124
+ # @matrix ... テーブルの内容となる二次元配列
125
+ # @header ... テーブルの先頭に付ける項目名の配列
126
+ # @class_a ... <table><tr><td>の各ノードにそれぞれ設定したいclass属性を配列として定義
127
+ class Table
128
+ attr_accessor :class_a, :header, :matrix
129
+
130
+ def initialize(matrix = nil)
131
+ set(matrix) if matrix
132
+ @matrix ||= []
133
+ end
134
+
135
+ def set(matrix)
136
+ @matrix = matrix
137
+ end
138
+
139
+ def add_row(row)
140
+ @matrix.push(row)
141
+ end
142
+
143
+ def to_h
144
+ table_class, tr_class, td_class = @class_a
145
+ max_col = 0
146
+ @matrix.each { |row| max_col = row.length if max_col < row.length }
147
+ tr_a = @matrix.map do |row|
148
+ add_attr = nil
149
+ add_attr = { colspan: max_col - row.length + 1 } if row.length < max_col
150
+ td_a = row.map do |v|
151
+ td = Ht.td(child: v)
152
+ td.add_class(td_class) if td_class
153
+ td
154
+ end
155
+ td_a[0].update(add_attr) if add_attr
156
+ tr = Ht.tr(class: tr_class, child: td_a)
157
+ tr.add_class(tr_class) if tr_class
158
+ tr
159
+ end
160
+ tr_a.unshift( Ht.thead(child: Ht.tr(child: @header.map {|v| Ht.th(child: v) }) )) if @header
161
+ tb = Ht.table(child: tr_a)
162
+ tb.add_class(table_class) if table_class
163
+ tb
164
+ end
165
+ end
166
+ end
167
+ end