vdom-rb 0.1.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.
- 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
|
+
|