vdom-rb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/vdom-rb.rb +5 -0
- data/lib/vdom/version.rb +3 -0
- data/opal/vdom.rb +4 -0
- data/opal/vdom/component.rb +151 -0
- data/opal/vdom/content.rb +240 -0
- data/opal/vdom/instance.rb +112 -0
- data/opal/vdom/registry.rb +60 -0
- data/opal/vdom/renderer.rb +164 -0
- data/opal/vdom/table.rb +500 -0
- data/opal/vdom/table_column.rb +81 -0
- data/opal/vdom/util.rb +12 -0
- metadata +84 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
puts "#{__FILE__}[#{__LINE__}] requiring vdom stuff"
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require 'vdom/instance'
|
5
|
+
|
6
|
+
module VDOM
|
7
|
+
class Registry
|
8
|
+
|
9
|
+
include Singleton
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@instances = Array.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(instance)
|
17
|
+
# `console.log(#{"#{__FILE__}:#{__LINE__}:#{self.class.name}##{__method__}(instance=#{instance})"})`
|
18
|
+
if @instances.find_index(instance)
|
19
|
+
fail "#{__FILE__}:#{__LINE__}:#{self.class.name}##{__method__}: virtual DOM instance with id #{instance.id} already registered"
|
20
|
+
end
|
21
|
+
@instances << instance
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(instance)
|
25
|
+
@instances ? @instances.delete(instance) : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def each(&block)
|
29
|
+
@instances.each(&block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def size
|
33
|
+
@instances.size
|
34
|
+
end
|
35
|
+
|
36
|
+
# Render the DOM with given id, or
|
37
|
+
# all DOMs if dom id is nil.
|
38
|
+
def render(dom_id = nil)
|
39
|
+
result = false
|
40
|
+
# `console.log(#{"#{__FILE__}:#{__LINE__}:#{self.class.name}##{__method__}(dom_id=#{dom_id}): @instances.size=#{@instances.size} "})`
|
41
|
+
each { |e|
|
42
|
+
if dom_id.nil? || e.id == dom_id
|
43
|
+
# `console.log(#{"#{__FILE__}:#{__LINE__}:#{self.class.name}##{__method__}(dom_id=#{dom_id}): calling #{e.class.name}#render"})`
|
44
|
+
e.render
|
45
|
+
result = true
|
46
|
+
end
|
47
|
+
}
|
48
|
+
# `console.log(#{"#{__FILE__}:#{__LINE__}:#{self.class.name}##{__method__}(dom_id=#{dom_id}): @instances.size=#{@instances.size} result=#{result} "})`
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
def shutdown
|
53
|
+
each { |e| e.shutdown }
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
Instances = VDOM::Registry.instance
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'vdom/component'
|
2
|
+
|
3
|
+
module VDOM
|
4
|
+
module Renderer
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def node(tag_name, attributes: nil, content: nil, model: nil, dom_id: nil)
|
9
|
+
VDOM::Node.new(tag_name, attributes: attributes, content: content, model: model, dom_id: dom_id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def state_component(content_or_state)
|
13
|
+
VDOM::StateComponent.new(content_or_state)
|
14
|
+
end
|
15
|
+
|
16
|
+
def icon(name, callback: nil, css: nil, float: nil, margins: nil, style: nil)
|
17
|
+
_style = {
|
18
|
+
text_align: 'center',
|
19
|
+
vertical_align: 'middle',
|
20
|
+
color: 'inherit',
|
21
|
+
background_color: 'inherit',
|
22
|
+
cursor: 'pointer',
|
23
|
+
}
|
24
|
+
if margins
|
25
|
+
_style =_style.merge(margins)
|
26
|
+
elsif float
|
27
|
+
_style = _style.merge(
|
28
|
+
case float.to_sym
|
29
|
+
when :left
|
30
|
+
{ margin_right: '0.5em' } # { margin_top: '0.2em', margin_right: '0.5em' }
|
31
|
+
when :right
|
32
|
+
{ margin_left: '0.5em' } # { margin_top: '0.2em', margin_right: '0.5em' }
|
33
|
+
else
|
34
|
+
{ } # { margin_top: '0.2em', margin_right: '0.5em' }
|
35
|
+
end
|
36
|
+
)
|
37
|
+
end
|
38
|
+
# arg style overrides any defaults
|
39
|
+
_style = _style.merge(style) if style
|
40
|
+
_class = iconify(name)
|
41
|
+
_class = "#{_class} #{css}" if css
|
42
|
+
_class = "#{_class} pull-#{float}" if float
|
43
|
+
attributes = {
|
44
|
+
class: _class,
|
45
|
+
style: _style
|
46
|
+
}.merge(
|
47
|
+
callback ? { onclick: callback } : {}
|
48
|
+
)
|
49
|
+
node(:span, attributes: attributes)
|
50
|
+
end
|
51
|
+
|
52
|
+
def icon_with_anchor(icon_name, href, float: nil, margins: nil, icon_css: nil, anchor_css: nil, icon_style: nil, anchor_style: nil)
|
53
|
+
_icon = icon(icon_name, css: icon_css, float: float, margins: margins, style: icon_style)
|
54
|
+
node(:a,
|
55
|
+
attributes: {
|
56
|
+
href: href,
|
57
|
+
style: anchor_style,
|
58
|
+
class: anchor_css || '',
|
59
|
+
},
|
60
|
+
content: _icon
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_icon(href)
|
65
|
+
icon_with_anchor(
|
66
|
+
:plus_sign,
|
67
|
+
href,
|
68
|
+
icon_style: { color: 'lightgreen'}
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def delete_icon(callback)
|
73
|
+
icon(
|
74
|
+
:remove_sign,
|
75
|
+
callback: callback,
|
76
|
+
style: {color: 'red'}
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def plain_anchor(content, href)
|
81
|
+
node(:a,
|
82
|
+
attributes: {
|
83
|
+
href: href,
|
84
|
+
style: 'color: inherit; background-color: inherit'
|
85
|
+
},
|
86
|
+
content: content
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def sortable(callback, direction: 0, content: nil)
|
91
|
+
if direction != 0
|
92
|
+
node(:div,
|
93
|
+
attributes: {
|
94
|
+
onclick: callback,
|
95
|
+
style: { cursor: 'pointer' }
|
96
|
+
},
|
97
|
+
content: arrify(content) + [
|
98
|
+
node(:span,
|
99
|
+
attributes: {
|
100
|
+
class: "glyphicon glyphicon-triangle-#{direction > 0 ? 'top' : 'bottom'}",
|
101
|
+
style: {
|
102
|
+
font_size: 'smaller',
|
103
|
+
margin_left: '0.5em',
|
104
|
+
vertical_align: 'middle',
|
105
|
+
color: 'inherit',
|
106
|
+
background_color: 'inherit',
|
107
|
+
}
|
108
|
+
}
|
109
|
+
)
|
110
|
+
]
|
111
|
+
)
|
112
|
+
else
|
113
|
+
node(:div,
|
114
|
+
attributes: {
|
115
|
+
onclick: callback,
|
116
|
+
style: { cursor: 'pointer' }
|
117
|
+
},
|
118
|
+
content: content
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def collapsible(callback, collapsed: false, content: nil)
|
124
|
+
node(:div,
|
125
|
+
attributes: {
|
126
|
+
onclick: callback,
|
127
|
+
style: { cursor: 'pointer' }
|
128
|
+
},
|
129
|
+
content: [
|
130
|
+
node(:span,
|
131
|
+
attributes: {
|
132
|
+
class: "glyphicon glyphicon-menu-#{collapsed ? 'down' : 'up'} pull-left",
|
133
|
+
style: {
|
134
|
+
font_size: 'smaller',
|
135
|
+
margin_right: '0.5em',
|
136
|
+
vertical_align: 'middle',
|
137
|
+
color: 'inherit',
|
138
|
+
background_color: 'inherit',
|
139
|
+
}
|
140
|
+
}
|
141
|
+
)
|
142
|
+
] + arrify(content)
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
def arrify(obj)
|
147
|
+
if obj
|
148
|
+
if Enumerable === obj
|
149
|
+
obj.to_a
|
150
|
+
else
|
151
|
+
[obj]
|
152
|
+
end
|
153
|
+
else
|
154
|
+
[]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# TODO: generalize from bootstrap
|
159
|
+
def iconify(icon_name)
|
160
|
+
"glyphicon glyphicon-#{icon_name.to_s.gsub('_', '-')}"
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
data/opal/vdom/table.rb
ADDED
@@ -0,0 +1,500 @@
|
|
1
|
+
require 'vdom/component'
|
2
|
+
require 'vdom/renderer'
|
3
|
+
require 'vdom/content'
|
4
|
+
require 'vdom/util'
|
5
|
+
|
6
|
+
module VDOM
|
7
|
+
|
8
|
+
class TableRow < VDOM::StateComponent
|
9
|
+
include VDOM::Renderer
|
10
|
+
include VDOM::Util
|
11
|
+
|
12
|
+
def initialize(state)
|
13
|
+
super(state)
|
14
|
+
end
|
15
|
+
|
16
|
+
def table; state[:table] end
|
17
|
+
def section; state[:section] end
|
18
|
+
def index; state[:index] end
|
19
|
+
|
20
|
+
def render
|
21
|
+
# debug __FILE__, __LINE__, __method__, "section=#{section} model_index [#{index}]"
|
22
|
+
node(
|
23
|
+
:tr,
|
24
|
+
attributes: table.row_attributes(section, index),
|
25
|
+
content: cells
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def cells
|
30
|
+
_table = table
|
31
|
+
_section = section
|
32
|
+
_is_head = _section == :head
|
33
|
+
# debug __FILE__, __LINE__, __method__, "section=#{section} index=#{index} "
|
34
|
+
_row_model = _table.row_model(section, index)
|
35
|
+
# debug __FILE__, __LINE__, __method__, "section=#{section} index=#{index} cust=#{_row_model.class} #{_row_model.to_h}"
|
36
|
+
_table.visible_columns.map do |column|
|
37
|
+
content = column.section_content(_section)
|
38
|
+
if Content === content
|
39
|
+
content, attributes = content.value_and_attributes(context: _row_model)
|
40
|
+
# debug __FILE__, __LINE__, __method__, "section=#{section} column=#{column.id} content=#{content}"
|
41
|
+
end
|
42
|
+
if _is_head && column.sort?
|
43
|
+
content = Renderer.sortable(
|
44
|
+
column.sort_callback,
|
45
|
+
direction: _table.sort_column_id == column.id ? _table.sort_order : 0,
|
46
|
+
content: content
|
47
|
+
)
|
48
|
+
end
|
49
|
+
tag_name = _is_head ? 'th' : 'td'
|
50
|
+
node(tag_name, attributes: attributes, content: content)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
class TableCaption < VDOM::StateComponent
|
57
|
+
include VDOM::Renderer
|
58
|
+
include VDOM::Util
|
59
|
+
|
60
|
+
def initialize(state)
|
61
|
+
super(state)
|
62
|
+
end
|
63
|
+
|
64
|
+
def table
|
65
|
+
state[:table]
|
66
|
+
end
|
67
|
+
|
68
|
+
def render
|
69
|
+
# debug __FILE__, __LINE__, __method__, "CAPTION"
|
70
|
+
content = table.caption_content
|
71
|
+
content = if VDOM::Content === content
|
72
|
+
content.node(:caption, attributes: attributes, context: state[:table])
|
73
|
+
else
|
74
|
+
node(
|
75
|
+
:h3,
|
76
|
+
attributes: table.caption_attributes,
|
77
|
+
content: content
|
78
|
+
)
|
79
|
+
end
|
80
|
+
node(
|
81
|
+
:caption,
|
82
|
+
attributes: table.caption_attributes,
|
83
|
+
content: content
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class TableSection < VDOM::StateComponent
|
89
|
+
include VDOM::Renderer
|
90
|
+
|
91
|
+
def initialize(state)
|
92
|
+
super(state)
|
93
|
+
end
|
94
|
+
|
95
|
+
def table; state[:table] end
|
96
|
+
def section; state[:section] end
|
97
|
+
def row_states; state[:row_states] end
|
98
|
+
|
99
|
+
def render
|
100
|
+
# debug __FILE__, __LINE__, __method__, "@section=#{section}"
|
101
|
+
node(
|
102
|
+
"t#{section}",
|
103
|
+
attributes: table.section_attributes(section),
|
104
|
+
content: rows
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
def rows
|
109
|
+
row_states.map do |row_state|
|
110
|
+
TableRow.new(row_state)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class Table < VDOM::StateComponent
|
116
|
+
include VDOM::Util
|
117
|
+
|
118
|
+
# TODO: other than bootstrap, ...
|
119
|
+
DEFAULT_CSS = 'table table-condensed table-bordered table-hover'
|
120
|
+
DEFAULT_STYLE = {}
|
121
|
+
|
122
|
+
# :css, # table css class string or nil
|
123
|
+
# :style, # table style hash or nil
|
124
|
+
# :caption_content, # string or ComponentSpec with tag => 'caption'
|
125
|
+
# :head_rows, # Proc or array of objects or nil
|
126
|
+
# :body_rows, # Proc or array of objects or nil
|
127
|
+
# :foot_rows, # Proc or array of objects or nil
|
128
|
+
# :sort_column_id, # default sort column or nil
|
129
|
+
# :sort_order, # nil, 1 or -1
|
130
|
+
# :accordion, # whether table has master column which can be collapsed
|
131
|
+
# :context, # something meaningful to column content procs or nil
|
132
|
+
# :reactive # is the table reactive, default false
|
133
|
+
|
134
|
+
attr_reader :sort_column_id, :sort_column, :sort_orders, :sort_order
|
135
|
+
attr_reader :accordion
|
136
|
+
attr_reader :css
|
137
|
+
attr_reader :style
|
138
|
+
attr_reader :caption_content
|
139
|
+
attr_reader :context
|
140
|
+
attr_reader :section_ids
|
141
|
+
attr_reader :render_count
|
142
|
+
|
143
|
+
def initialize(**options)
|
144
|
+
# debug __FILE__, __LINE__, __method__, "options: #{options.class.name}"
|
145
|
+
|
146
|
+
init_sections(options)
|
147
|
+
|
148
|
+
# debug __FILE__, __LINE__, __method__
|
149
|
+
@raw_columns = options[:columns]
|
150
|
+
@context = options[:context]
|
151
|
+
|
152
|
+
# debug __FILE__, __LINE__, __method__
|
153
|
+
init_caption(options)
|
154
|
+
|
155
|
+
# debug __FILE__, __LINE__, __method__
|
156
|
+
init_css_style(options)
|
157
|
+
|
158
|
+
# debug __FILE__, __LINE__, __method__
|
159
|
+
init_row_sources(options)
|
160
|
+
init_row_models
|
161
|
+
|
162
|
+
# debug __FILE__, __LINE__, __method__
|
163
|
+
@initial_sort_column_id = options[:sort_column_id]
|
164
|
+
@initial_sort_order = options[:sort_order]
|
165
|
+
|
166
|
+
# debug __FILE__, __LINE__, __method__
|
167
|
+
init_visibility
|
168
|
+
|
169
|
+
# debug __FILE__, __LINE__, __method__
|
170
|
+
init_sorting
|
171
|
+
|
172
|
+
# debug __FILE__, __LINE__, __method__
|
173
|
+
init_accordion(options)
|
174
|
+
|
175
|
+
# debug __FILE__, __LINE__, __method__
|
176
|
+
init_states
|
177
|
+
|
178
|
+
# debug __FILE__, __LINE__, __method__, "_state=#{_state}"
|
179
|
+
super(table_state)
|
180
|
+
|
181
|
+
@render_count = 0
|
182
|
+
end
|
183
|
+
|
184
|
+
def table_state
|
185
|
+
State.new(table: self)
|
186
|
+
end
|
187
|
+
|
188
|
+
def columns
|
189
|
+
unless @columns
|
190
|
+
@columns = if Proc === @raw_columns
|
191
|
+
@raw_columns.call
|
192
|
+
else
|
193
|
+
@raw_columns
|
194
|
+
end
|
195
|
+
end
|
196
|
+
@columns
|
197
|
+
end
|
198
|
+
|
199
|
+
def render
|
200
|
+
@render_count += 1
|
201
|
+
node(
|
202
|
+
:table,
|
203
|
+
attributes: { class: css, style: style },
|
204
|
+
content: [caption_component] + section_components
|
205
|
+
)
|
206
|
+
end
|
207
|
+
|
208
|
+
def caption_component
|
209
|
+
TableCaption.new(caption_state)
|
210
|
+
end
|
211
|
+
|
212
|
+
def section_components
|
213
|
+
section_ids.map do |id|
|
214
|
+
TableSection.new(section_state(id))
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def visible_columns
|
219
|
+
@visible_columns ||= visible_column_indexes.map {|i| columns[i]}
|
220
|
+
end
|
221
|
+
|
222
|
+
def visible_column_indexes
|
223
|
+
@visible_column_indexes
|
224
|
+
end
|
225
|
+
|
226
|
+
def column_ids
|
227
|
+
columns.map(&:id)
|
228
|
+
end
|
229
|
+
|
230
|
+
def caption_attributes
|
231
|
+
# TODO:
|
232
|
+
nil
|
233
|
+
end
|
234
|
+
|
235
|
+
def cell_attributes(section)
|
236
|
+
# TODO:
|
237
|
+
nil
|
238
|
+
end
|
239
|
+
|
240
|
+
def section_attributes(section)
|
241
|
+
# TODO:
|
242
|
+
nil
|
243
|
+
end
|
244
|
+
|
245
|
+
def row_attributes(section, index)
|
246
|
+
# TODO:
|
247
|
+
nil
|
248
|
+
end
|
249
|
+
|
250
|
+
def sorted?
|
251
|
+
!!@sort_column_id
|
252
|
+
end
|
253
|
+
|
254
|
+
# set or toggle the sort order to/of the given column
|
255
|
+
def sort!(column_id)
|
256
|
+
if @sort_column_id == column_id
|
257
|
+
sort_orders[@sort_column_id] *= -1
|
258
|
+
else
|
259
|
+
@sort_column_id = column_id
|
260
|
+
@sort_column = columns.detect {|c| c.id == @sort_column_id}
|
261
|
+
end
|
262
|
+
@sort_order = sort_orders[@sort_column_id]
|
263
|
+
@model_indexes_sorted[:body] = nil
|
264
|
+
invalidate_sections
|
265
|
+
render_dom!
|
266
|
+
end
|
267
|
+
|
268
|
+
def row_models(section)
|
269
|
+
@row_models[section]
|
270
|
+
end
|
271
|
+
|
272
|
+
def row_model(section, row_index)
|
273
|
+
models = row_models(section)
|
274
|
+
models ? models[row_index] : nil
|
275
|
+
end
|
276
|
+
|
277
|
+
def invalidate
|
278
|
+
init_row_models
|
279
|
+
init_sorting
|
280
|
+
invalidate_caption
|
281
|
+
invalidate_sections
|
282
|
+
render_dom!
|
283
|
+
end
|
284
|
+
|
285
|
+
def invalidate_caption
|
286
|
+
@caption_state = nil
|
287
|
+
end
|
288
|
+
|
289
|
+
def invalidate_sections(arg_ids = nil)
|
290
|
+
ids = arg_ids || section_ids
|
291
|
+
ids.each do |id|
|
292
|
+
# debug __FILE__, __LINE__, __method__, "id=#{id} "
|
293
|
+
invalidate_section(id, row_states: true)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def invalidate_section(section, row_states: true)
|
298
|
+
# debug __FILE__, __LINE__, __method__, "section=#{section} row_states=#{row_states}"
|
299
|
+
@section_states[section] = nil
|
300
|
+
@row_states[section] = nil if row_states
|
301
|
+
end
|
302
|
+
|
303
|
+
def invalidate_row(section, model, columns = nil)
|
304
|
+
# debug __FILE__, __LINE__, __method__, "section=#{section} model=#{model} columns=#{columns}"
|
305
|
+
index = state_index(section, model)
|
306
|
+
if index
|
307
|
+
if section == :body && (columns.nil? || columns.include?(@sort_column))
|
308
|
+
# debug __FILE__, __LINE__, __method__, "section=#{section} model=#{model.name} invalidating whole section"
|
309
|
+
@model_indexes_sorted[section] = nil
|
310
|
+
invalidate_section(section, row_states: true)
|
311
|
+
else
|
312
|
+
# debug __FILE__, __LINE__, __method__, "section=#{section} invalidating row [#{index}] only for #{model.name}"
|
313
|
+
invalidate_section(section, row_states: false)
|
314
|
+
invalidate_row_state(section, index, columns)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def invalidate_cell(section, row, column)
|
320
|
+
invalidate_row(section, row, [column])
|
321
|
+
end
|
322
|
+
|
323
|
+
private
|
324
|
+
|
325
|
+
def init_sections(options)
|
326
|
+
# TODO: from options
|
327
|
+
@section_ids = [:head, :body, :foot]
|
328
|
+
end
|
329
|
+
|
330
|
+
|
331
|
+
def init_row_sources(options)
|
332
|
+
@row_sources = {}
|
333
|
+
section_ids.each do |section|
|
334
|
+
@row_sources[section] = options[:"#{section}_rows"]
|
335
|
+
end
|
336
|
+
# debug __FILE__, __LINE__, __method__, "@row_sources=#{@row_sources}"
|
337
|
+
end
|
338
|
+
|
339
|
+
def init_row_models
|
340
|
+
# debug __FILE__, __LINE__, __method__
|
341
|
+
@row_models = {}
|
342
|
+
empty_sections = []
|
343
|
+
@section_ids.each do |section|
|
344
|
+
source = @row_sources[section]
|
345
|
+
@row_models[section] = case source
|
346
|
+
when Fixnum
|
347
|
+
source.times.to_a
|
348
|
+
when Proc
|
349
|
+
source.call
|
350
|
+
else
|
351
|
+
if source.nil? || source.size == 0
|
352
|
+
empty_sections << section
|
353
|
+
end
|
354
|
+
source
|
355
|
+
end
|
356
|
+
end
|
357
|
+
empty_sections.each do |section|
|
358
|
+
@section_ids.delete(section)
|
359
|
+
@row_models[section]
|
360
|
+
end
|
361
|
+
# debug __FILE__, __LINE__, __method__, '@section_ids=#{@section_ids}'
|
362
|
+
end
|
363
|
+
|
364
|
+
def init_accordion(options)
|
365
|
+
@accordion = !!options[:accordion]
|
366
|
+
end
|
367
|
+
|
368
|
+
def init_css_style(options)
|
369
|
+
@css = options[:css] || DEFAULT_CSS
|
370
|
+
@style = options[:style] || DEFAULT_STYLE
|
371
|
+
end
|
372
|
+
|
373
|
+
def init_caption(options)
|
374
|
+
@caption_content = options[:caption]
|
375
|
+
end
|
376
|
+
|
377
|
+
def init_sorting
|
378
|
+
@sort_column_id = @initial_sort_column_id unless @sort_column_id
|
379
|
+
@sort_column = @sort_column_id ? columns.detect {|c| c.id == @sort_column_id} : columns.detect {|c| c.sort?}
|
380
|
+
@sort_column_id = @sort_column ? @sort_column.id : nil
|
381
|
+
@sort_order = @initial_sort_order || 1 unless @sort_order
|
382
|
+
@sort_orders = {}
|
383
|
+
if @sort_column_id
|
384
|
+
columns.each { |c| sort_orders[c.id] = c.id == sort_column_id ? @sort_order : 1 }
|
385
|
+
end
|
386
|
+
@model_indexes_sorted = {}
|
387
|
+
# debug __FILE__, __LINE__, __method__, "@sort_column_id=#{@sort_column_id}"
|
388
|
+
end
|
389
|
+
|
390
|
+
def init_states
|
391
|
+
@caption_state = nil
|
392
|
+
@section_states = {}
|
393
|
+
@row_states = {}
|
394
|
+
end
|
395
|
+
|
396
|
+
def init_visibility
|
397
|
+
@visible_column_indexes = columns.size.times.to_a
|
398
|
+
end
|
399
|
+
|
400
|
+
def caption_state
|
401
|
+
@caption_state ||= State.new(table: self)
|
402
|
+
end
|
403
|
+
|
404
|
+
def section_state(section)
|
405
|
+
unless @section_states[section]
|
406
|
+
# debug __FILE__, __LINE__, __method__, "#{section} creating new section state"
|
407
|
+
@section_states[section] = State.new(
|
408
|
+
table: self,
|
409
|
+
section: section,
|
410
|
+
row_states: row_states(section)
|
411
|
+
)
|
412
|
+
end
|
413
|
+
@section_states[section]
|
414
|
+
end
|
415
|
+
|
416
|
+
def row_states(section)
|
417
|
+
sorted_indexes = model_indexes_sorted(section)
|
418
|
+
states = (@row_states[section] ||= Array.new(sorted_indexes.size))
|
419
|
+
if section == :body
|
420
|
+
# debug __FILE__, __LINE__, __method__, "#{section} sorted_indexes=#{sorted_indexes.to_a}"
|
421
|
+
# debug __FILE__, __LINE__, __method__, "#{section} sorted_models=#{sorted_indexes.map{|i|row_models(section)[i]}.map(&:code)}"
|
422
|
+
end
|
423
|
+
sorted_indexes.each_with_index do |model_index, row_index|
|
424
|
+
unless states[row_index]
|
425
|
+
if section == :body
|
426
|
+
# debug __FILE__, __LINE__, __method__, "#{section} creating new row state[#{row_index}] model_index=#{model_index} #{section == :body ? row_models(section)[model_index].name : ''}"
|
427
|
+
end
|
428
|
+
states[row_index] = State.new(table: self, section: section, index: model_index)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
states
|
432
|
+
end
|
433
|
+
|
434
|
+
def invalidate_row_state(section, index, columns)
|
435
|
+
if @row_states[section]
|
436
|
+
# debug __FILE__, __LINE__, __method__, "#{section} index=#{index} #{row_models(section)[index].name}"
|
437
|
+
@row_states[section][index] = nil
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def state_index(section, model)
|
442
|
+
models = row_models(section)
|
443
|
+
model_indexes_sorted(section).each_with_index do |model_index, state_index|
|
444
|
+
if model == models[model_index]
|
445
|
+
return state_index
|
446
|
+
end
|
447
|
+
end
|
448
|
+
nil
|
449
|
+
end
|
450
|
+
|
451
|
+
def model_indexes_sorted(section)
|
452
|
+
unless @model_indexes_sorted[section]
|
453
|
+
models = row_models(section)
|
454
|
+
@model_indexes_sorted[section] = models.size.times.to_a
|
455
|
+
if section == :body && sorted?
|
456
|
+
order = sort_order
|
457
|
+
@model_indexes_sorted[section] = @model_indexes_sorted[section].sort do |a, b|
|
458
|
+
val_a = sort_value(sort_column, models[a])
|
459
|
+
val_b = sort_value(sort_column, models[b])
|
460
|
+
compare(val_a, val_b) * order
|
461
|
+
end
|
462
|
+
sorted_models = @model_indexes_sorted[section].map{|i|models[i]}
|
463
|
+
sorted_models = sorted_models.map(&:code)
|
464
|
+
# debug __FILE__, __LINE__, __method__, "sorted models = #{sorted_models}"
|
465
|
+
# debug __FILE__, __LINE__, __method__, "sort_column=#{sort_column.id} model_indexes_sorted=#{@model_indexes_sorted}"
|
466
|
+
end
|
467
|
+
end
|
468
|
+
# debug __FILE__, __LINE__, __method__, "sort_column=#{sort_column.id} model_indexes_sorted=#{@model_indexes_sorted}"
|
469
|
+
@model_indexes_sorted[section]
|
470
|
+
end
|
471
|
+
|
472
|
+
def sort_value(column, row_model)
|
473
|
+
content = column.section_content(:body)
|
474
|
+
result = if VDOM::Content === content
|
475
|
+
content.sort_value(context: row_model)
|
476
|
+
else
|
477
|
+
content
|
478
|
+
end
|
479
|
+
result
|
480
|
+
end
|
481
|
+
|
482
|
+
def compare(a, b)
|
483
|
+
if a && b
|
484
|
+
a <=> b
|
485
|
+
elsif a
|
486
|
+
1
|
487
|
+
elsif b
|
488
|
+
-1
|
489
|
+
else
|
490
|
+
0
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
|
495
|
+
end
|
496
|
+
|
497
|
+
end # module VDOM
|
498
|
+
|
499
|
+
require 'vdom/table_column'
|
500
|
+
|