glimmer-dsl-opal 0.7.0 → 0.7.5

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +290 -53
  4. data/VERSION +1 -1
  5. data/lib/display.rb +31 -0
  6. data/lib/file.rb +29 -0
  7. data/lib/glimmer-dsl-opal.rb +30 -1
  8. data/lib/glimmer-dsl-opal/ext/date.rb +1 -1
  9. data/lib/glimmer-dsl-opal/ext/struct.rb +37 -0
  10. data/lib/glimmer-dsl-opal/samples/elaborate/contact_manager.rb +50 -23
  11. data/lib/glimmer-dsl-opal/samples/elaborate/login.rb +22 -5
  12. data/lib/glimmer-dsl-opal/samples/hello/hello_browser.rb +1 -1
  13. data/lib/glimmer-dsl-opal/samples/hello/hello_button.rb +46 -0
  14. data/lib/glimmer-dsl-opal/samples/hello/hello_custom_shell.rb +7 -7
  15. data/lib/glimmer-dsl-opal/samples/hello/hello_table.rb +5 -5
  16. data/lib/glimmer-dsl-swt.rb +20 -35
  17. data/lib/glimmer/data_binding/table_items_binding.rb +6 -4
  18. data/lib/glimmer/dsl/opal/custom_widget_expression.rb +6 -0
  19. data/lib/glimmer/dsl/opal/widget_expression.rb +6 -2
  20. data/lib/glimmer/swt/combo_proxy.rb +40 -1
  21. data/lib/glimmer/swt/composite_proxy.rb +5 -1
  22. data/lib/glimmer/swt/control_editor.rb +54 -0
  23. data/lib/glimmer/swt/date_time_proxy.rb +66 -1
  24. data/lib/glimmer/swt/font_proxy.rb +4 -4
  25. data/lib/glimmer/swt/grid_layout_proxy.rb +20 -12
  26. data/lib/glimmer/swt/label_proxy.rb +11 -3
  27. data/lib/glimmer/swt/layout_data_proxy.rb +10 -7
  28. data/lib/glimmer/swt/layout_proxy.rb +1 -1
  29. data/lib/glimmer/swt/message_box_proxy.rb +2 -10
  30. data/lib/glimmer/swt/table_column_proxy.rb +9 -0
  31. data/lib/glimmer/swt/table_editor.rb +65 -0
  32. data/lib/glimmer/swt/table_item_proxy.rb +36 -0
  33. data/lib/glimmer/swt/table_proxy.rb +375 -17
  34. data/lib/glimmer/swt/text_proxy.rb +1 -1
  35. data/lib/glimmer/swt/widget_proxy.rb +99 -21
  36. data/lib/glimmer/ui/custom_shell.rb +9 -7
  37. data/lib/os.rb +36 -0
  38. metadata +25 -3
@@ -4,11 +4,10 @@ require 'glimmer/swt/widget_proxy'
4
4
  module Glimmer
5
5
  module SWT
6
6
  class LabelProxy < WidgetProxy
7
- attr_reader :text, :background_image, :image, :alignment
7
+ attr_reader :text, :background_image, :image
8
8
 
9
9
  def initialize(parent, args, block)
10
10
  super(parent, args, block)
11
- self.alignment = [:left, :center, :right].detect {|align| args.detect { |arg| SWTProxy[align] == arg } }
12
11
  end
13
12
 
14
13
  def text=(value)
@@ -32,6 +31,15 @@ module Glimmer
32
31
  def element
33
32
  'label'
34
33
  end
34
+
35
+ def alignment
36
+ if @alignment.nil?
37
+ found_arg = nil
38
+ @alignment = [:left, :center, :right].detect {|align| found_arg = args.detect { |arg| SWTProxy[align] == SWTProxy[arg] } }
39
+ args.delete(found_arg)
40
+ end
41
+ @alignment
42
+ end
35
43
 
36
44
  def alignment=(value)
37
45
  # TODO consider storing swt value in the future instead
@@ -44,7 +52,7 @@ module Glimmer
44
52
  label_id = id
45
53
  label_class = name
46
54
  @dom ||= html {
47
- label(id: label_id, class: label_class) {
55
+ label(id: label_id, class: label_class, style: "text-align: #{alignment};") {
48
56
  label_text
49
57
  }
50
58
  }.to_s
@@ -3,6 +3,7 @@ require 'glimmer/swt/property_owner'
3
3
  module Glimmer
4
4
  module SWT
5
5
  class LayoutDataProxy
6
+ # TODO make this polymorphic as GridData or RowData subclasses
6
7
  include Glimmer::SWT::PropertyOwner
7
8
  attr_reader :parent,
8
9
  :args,
@@ -44,10 +45,11 @@ module Glimmer
44
45
  def horizontal_alignment=(horizontal_alignment)
45
46
  @horizontal_alignment = horizontal_alignment
46
47
  return if @horizontal_alignment.nil?
47
- if @horizontal_alignment == 'fill'
48
- @parent.dom_element.css('width', '100%') if width_hint.nil?
49
- else
50
- @parent.dom_element.css('text-align', @horizontal_alignment)
48
+ if @horizontal_alignment != 'fill'
49
+ @parent.dom_element.css('text-align', @horizontal_alignment.to_s)
50
+ @parent.dom_element.css('place-self', @horizontal_alignment.to_s)
51
+ @parent.dom_element.css('margin-left', 'auto') if ['right', 'center'].include?(@horizontal_alignment.to_s)
52
+ @parent.dom_element.css('margin-right', 'auto') if ['left', 'center'].include?(@horizontal_alignment.to_s)
51
53
  end
52
54
  # TODO
53
55
  # reapply
@@ -85,18 +87,19 @@ module Glimmer
85
87
 
86
88
  def grab_excess_horizontal_space=(grab_excess_horizontal_space)
87
89
  @grab_excess_horizontal_space = grab_excess_horizontal_space
88
- @parent.dom_element.css('width', "100%") if @grab_excess_horizontal_space && width_hint.nil?
90
+ @parent.dom_element.css('justify-self', @horizontal_alignment) if @grab_excess_horizontal_space && @horizontal_alignment != 'fill' && width_hint.nil?
91
+ @parent.parent.dom_element.css('justify-content', "normal") if @grab_excess_horizontal_space
89
92
  # reapply
90
93
  end
91
94
 
92
95
  def grab_excess_vertical_space=(grab_excess_vertical_space)
93
96
  @grab_excess_vertical_space = grab_excess_vertical_space
94
- @parent.dom_element.css('height', "100%") if @grab_excess_vertical_space && height_hint.nil?
95
- # TODO
97
+ @parent.dom_element.css('height', "100%") if @grab_excess_vertical_space && @vertical_alignment == 'fill' && height_hint.nil?
96
98
  # reapply
97
99
  end
98
100
 
99
101
  def reapply
102
+ # TODO remove reapply method
100
103
  # @parent.css = <<~CSS
101
104
  # CSS
102
105
  end
@@ -18,7 +18,7 @@ module Glimmer
18
18
  a_layout_class = Glimmer::SWT.const_get(class_name_main.to_sym) rescue Glimmer::SWT.const_get(class_name_alternative.to_sym)
19
19
  a_layout_class if a_layout_class.ancestors.include?(Glimmer::SWT::LayoutProxy)
20
20
  rescue => e
21
- puts "Layout #{keyword} was not found!"
21
+ Glimmer::Config.logger.debug "Layout #{keyword} was not found!"
22
22
  nil
23
23
  end
24
24
 
@@ -8,7 +8,7 @@ module Glimmer
8
8
 
9
9
  def initialize(parent, args, block)
10
10
  i = 0
11
- @parent = parent || DisplayProxy.instance.shells.first
11
+ @parent = parent || DisplayProxy.instance.shells.last
12
12
  @args = args
13
13
  @block = block
14
14
  @children = Set.new
@@ -30,16 +30,8 @@ module Glimmer
30
30
  dom_element.find('.modal-content .message').html(@text)
31
31
  end
32
32
 
33
- def document
34
- element = self
35
- begin
36
- element = element.parent
37
- end while(element.parent)
38
- element
39
- end
40
-
41
33
  def open
42
- document.post_initialize_child(self)
34
+ parent.post_initialize_child(self)
43
35
  end
44
36
 
45
37
  def hide
@@ -74,6 +74,15 @@ module Glimmer
74
74
  dom_element.find('.sort-direction')
75
75
  end
76
76
 
77
+ # Sets editor (e.g. combo)
78
+ def editor=(*args)
79
+ @editor = args
80
+ end
81
+
82
+ def editable?
83
+ !@editor&.include?(:none)
84
+ end
85
+
77
86
  def observation_request_to_event_mapping
78
87
  {
79
88
  'on_widget_selected' => {
@@ -0,0 +1,65 @@
1
+ # Copyright (c) 2020 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/swt/control_editor'
23
+
24
+ module Glimmer
25
+ module SWT
26
+ # Emulates SWT's native org.eclipse.swt.custom.TableEditor
27
+ class TableEditor < ControlEditor
28
+ alias table composite
29
+
30
+ def editor=(editor_widget, table_item, table_column_index)
31
+ # TODO consider making editor not gain an ID or gain a separate set of IDs to avoid clashing with standard widget predictability of ID
32
+ @table_item = table_item
33
+ @table_column_index = table_column_index
34
+ @editor_widget = editor_widget
35
+ @old_value = table_item.cell_dom_element(table_column_index).html
36
+ table_item.cell_dom_element(table_column_index).html('')
37
+ editor_widget.render(table_item.cell_dom_element(table_column_index))
38
+ # TODO tweak the width perfectly so it doesn't expand the table cell
39
+ # editor_widget.dom_element.css('width', 'calc(100% - 20px)')
40
+ editor_widget.dom_element.css('width', "#{minimumWidth}%") # TODO implement property with pixels (and perhaps derive percentage separately from pixels)
41
+ editor_widget.dom_element.css('height', "#{minimumHeight}px")
42
+ editor_widget.dom_element.add_class('table-editor')
43
+ # TODO consider relying on autofocus instead
44
+ editor_widget.dom_element.focus
45
+ # TODO consider doing the following line only for :text editor
46
+ editor_widget.dom_element.select
47
+ end
48
+ alias set_editor editor=
49
+ alias setEditor editor=
50
+
51
+ def cancel!
52
+ done!
53
+ end
54
+
55
+ def save!
56
+ done!
57
+ end
58
+
59
+ def done!
60
+ @table_item.cell_dom_element(@table_column_index).html(@old_value) unless @old_value.nil?
61
+ @old_value = nil
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,9 +1,33 @@
1
+ # Copyright (c) 2020 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
1
22
  require 'glimmer/swt/widget_proxy'
2
23
 
3
24
  module Glimmer
4
25
  module SWT
5
26
  class TableItemProxy < WidgetProxy
6
27
  STYLE = <<~CSS
28
+ tr.table-item td {
29
+ padding-bottom: 0;
30
+ }
7
31
  tr.table-item:nth-child(even):not(.selected) {
8
32
  background: rgb(243, 244, 246);
9
33
  }
@@ -60,6 +84,10 @@ module Glimmer
60
84
  'tr'
61
85
  end
62
86
 
87
+ def cell_dom_element(column_index)
88
+ dom_element.find("td:nth-child(#{column_index + 1})")
89
+ end
90
+
63
91
  def redraw
64
92
  super() #TODO re-enable and remove below lines
65
93
 
@@ -111,6 +139,14 @@ module Glimmer
111
139
  redraw
112
140
  end
113
141
 
142
+ def redraw_selection
143
+ if parent.selection.include?(self)
144
+ dom_element.add_class('selected')
145
+ else
146
+ dom_element.remove_class('selected')
147
+ end
148
+ end
149
+
114
150
  def on_widget_selected(&block)
115
151
  event = 'click'
116
152
  delegate = $document.on(event, selector, &block)
@@ -1,20 +1,251 @@
1
+ # Copyright (c) 2020 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
1
22
  require 'glimmer/swt/widget_proxy'
2
23
  require 'glimmer/swt/table_column_proxy'
24
+ require 'glimmer/swt/table_item_proxy'
25
+ require 'glimmer/swt/table_editor'
3
26
 
4
27
  module Glimmer
5
28
  module SWT
6
- class TableProxy < WidgetProxy
29
+ class TableProxy < CompositeProxy
7
30
  attr_reader :columns, :selection,
8
- :sort_type, :sort_column, :sort_property, :sort_block, :sort_by_block, :additional_sort_properties
31
+ :sort_type, :sort_column, :sort_property, :sort_block, :sort_by_block, :additional_sort_properties,
32
+ :editor, :table_editor
9
33
  attr_accessor :column_properties, :item_count, :data
10
34
  alias items children
11
35
  alias model_binding data
12
36
 
37
+ class << self
38
+ include Glimmer
39
+
40
+ def editors
41
+ @editors ||= {
42
+ # ensure editor can work with string keys not just symbols (leave one string in for testing)
43
+ text: {
44
+ widget_value_property: :text,
45
+ editor_gui: lambda do |args, model, property, table_proxy|
46
+ table_proxy.table_editor.minimumWidth = 90
47
+ table_proxy.table_editor.minimumHeight = 10
48
+ table_editor_widget_proxy = text(*args) {
49
+ text model.send(property)
50
+ focus true
51
+ on_focus_lost {
52
+ table_proxy.finish_edit!
53
+ }
54
+ on_key_pressed { |key_event|
55
+ if key_event.keyCode == swt(:cr)
56
+ table_proxy.finish_edit!
57
+ elsif key_event.keyCode == swt(:esc)
58
+ table_proxy.cancel_edit!
59
+ end
60
+ }
61
+ }
62
+ # table_editor_widget_proxy.swt_widget.selectAll # TODO select all
63
+ table_editor_widget_proxy
64
+ end,
65
+ },
66
+ combo: {
67
+ widget_value_property: :text,
68
+ editor_gui: lambda do |args, model, property, table_proxy|
69
+ first_time = true
70
+ table_proxy.table_editor.minimumWidth = 90
71
+ table_proxy.table_editor.minimumHeight = 18
72
+ table_editor_widget_proxy = combo(*args) {
73
+ items model.send("#{property}_options")
74
+ text model.send(property)
75
+ focus true
76
+ on_focus_lost {
77
+ table_proxy.finish_edit!
78
+ }
79
+ on_key_pressed { |key_event|
80
+ if key_event.keyCode == swt(:cr)
81
+ table_proxy.finish_edit!
82
+ elsif key_event.keyCode == swt(:esc)
83
+ table_proxy.cancel_edit!
84
+ end
85
+ }
86
+ on_widget_selected {
87
+ if !OS.windows? || !first_time || first_time && model.send(property) != table_editor_widget_proxy.text
88
+ table_proxy.finish_edit!
89
+ end
90
+ }
91
+ }
92
+ table_editor_widget_proxy
93
+ end,
94
+ },
95
+ checkbox: {
96
+ widget_value_property: :selection,
97
+ editor_gui: lambda do |args, model, property, table_proxy|
98
+ first_time = true
99
+ table_proxy.table_editor.minimumHeight = 25
100
+ checkbox(*args) {
101
+ selection model.send(property)
102
+ focus true
103
+ on_widget_selected {
104
+ table_proxy.finish_edit!
105
+ }
106
+ on_focus_lost {
107
+ table_proxy.finish_edit!
108
+ }
109
+ on_key_pressed { |key_event|
110
+ if key_event.keyCode == swt(:cr)
111
+ table_proxy.finish_edit!
112
+ elsif key_event.keyCode == swt(:esc)
113
+ table_proxy.cancel_edit!
114
+ end
115
+ }
116
+ }
117
+ end,
118
+ },
119
+ date: {
120
+ widget_value_property: :date_time,
121
+ editor_gui: lambda do |args, model, property, table_proxy|
122
+ first_time = true
123
+ table_proxy.table_editor.minimumWidth = 90
124
+ table_proxy.table_editor.minimumHeight = 15
125
+ date(*args) {
126
+ date_time model.send(property)
127
+ focus true
128
+ on_widget_selected {
129
+ table_proxy.finish_edit!
130
+ }
131
+ on_key_pressed { |key_event|
132
+ if key_event.keyCode == swt(:cr)
133
+ table_proxy.finish_edit!
134
+ elsif key_event.keyCode == swt(:esc)
135
+ table_proxy.cancel_edit!
136
+ end
137
+ }
138
+ }
139
+ end,
140
+ },
141
+ date_drop_down: {
142
+ widget_value_property: :date_time,
143
+ editor_gui: lambda do |args, model, property, table_proxy|
144
+ first_time = true
145
+ table_proxy.table_editor.minimumWidth = 80
146
+ table_proxy.table_editor.minimumHeight = 15
147
+ date_drop_down(*args) {
148
+ date_time model.send(property)
149
+ focus true
150
+ on_widget_selected {
151
+ table_proxy.finish_edit!
152
+ }
153
+ on_key_pressed { |key_event|
154
+ if key_event.keyCode == swt(:cr)
155
+ table_proxy.finish_edit!
156
+ elsif key_event.keyCode == swt(:esc)
157
+ table_proxy.cancel_edit!
158
+ end
159
+ }
160
+ }
161
+ end,
162
+ },
163
+ time: {
164
+ widget_value_property: :date_time,
165
+ editor_gui: lambda do |args, model, property, table_proxy|
166
+ first_time = true
167
+ table_proxy.table_editor.minimumWidth = 80
168
+ table_proxy.table_editor.minimumHeight = 15
169
+ time(*args) {
170
+ date_time model.send(property)
171
+ focus true
172
+ on_widget_selected {
173
+ table_proxy.finish_edit!
174
+ }
175
+ on_focus_lost {
176
+ table_proxy.finish_edit!
177
+ }
178
+ on_key_pressed { |key_event|
179
+ if key_event.keyCode == swt(:cr)
180
+ table_proxy.finish_edit!
181
+ elsif key_event.keyCode == swt(:esc)
182
+ table_proxy.cancel_edit!
183
+ end
184
+ }
185
+ }
186
+ end,
187
+ },
188
+ radio: {
189
+ widget_value_property: :selection,
190
+ editor_gui: lambda do |args, model, property, table_proxy|
191
+ first_time = true
192
+ table_proxy.table_editor.minimumHeight = 25
193
+ radio(*args) {
194
+ selection model.send(property)
195
+ focus true
196
+ on_widget_selected {
197
+ table_proxy.finish_edit!
198
+ }
199
+ on_focus_lost {
200
+ table_proxy.finish_edit!
201
+ }
202
+ on_key_pressed { |key_event|
203
+ if key_event.keyCode == swt(:cr)
204
+ table_proxy.finish_edit!
205
+ elsif key_event.keyCode == swt(:esc)
206
+ table_proxy.cancel_edit!
207
+ end
208
+ }
209
+ }
210
+ end,
211
+ },
212
+ spinner: {
213
+ widget_value_property: :selection,
214
+ editor_gui: lambda do |args, model, property, table_proxy|
215
+ first_time = true
216
+ table_proxy.table_editor.minimumHeight = 25
217
+ table_editor_widget_proxy = spinner(*args) {
218
+ selection model.send(property)
219
+ focus true
220
+ on_focus_lost {
221
+ table_proxy.finish_edit!
222
+ }
223
+ on_key_pressed { |key_event|
224
+ if key_event.keyCode == swt(:cr)
225
+ table_proxy.finish_edit!
226
+ elsif key_event.keyCode == swt(:esc)
227
+ table_proxy.cancel_edit!
228
+ end
229
+ }
230
+ }
231
+ table_editor_widget_proxy
232
+ end,
233
+ },
234
+ }
235
+ end
236
+ end
237
+
13
238
  def initialize(parent, args, block)
14
239
  super(parent, args, block)
15
240
  @columns = []
16
241
  @children = []
242
+ @editors = []
17
243
  @selection = []
244
+ @table_editor = TableEditor.new(self)
245
+ @table_editor.horizontalAlignment = SWTProxy[:left]
246
+ @table_editor.grabHorizontal = true
247
+ @table_editor.minimumWidth = 90
248
+ @table_editor.minimumHeight = 20
18
249
  if editable?
19
250
  on_mouse_up { |event|
20
251
  edit_table_item(event.table_item, event.column_index)
@@ -26,10 +257,24 @@ module Glimmer
26
257
  def post_initialize_child(child)
27
258
  if child.is_a?(TableColumnProxy)
28
259
  @columns << child
29
- else
260
+ child.render
261
+ elsif child.is_a?(TableItemProxy)
30
262
  @children << child
263
+ child.render
264
+ else
265
+ @editors << child
266
+ end
267
+ end
268
+
269
+ # Executes for the parent of a child that just got disposed
270
+ def post_dispose_child(child)
271
+ if child.is_a?(TableColumnProxy)
272
+ @columns&.delete(child)
273
+ elsif child.is_a?(TableItemProxy)
274
+ @children&.delete(child)
275
+ else
276
+ @editors&.delete(child)
31
277
  end
32
- child.redraw
33
278
  end
34
279
 
35
280
  def post_add_content
@@ -38,6 +283,10 @@ module Glimmer
38
283
  @initially_sorted = true
39
284
  end
40
285
 
286
+ def default_layout
287
+ nil
288
+ end
289
+
41
290
  def get_data(key=nil)
42
291
  data
43
292
  end
@@ -60,11 +309,12 @@ module Glimmer
60
309
  new_selection = new_selection.to_a
61
310
  changed = (selection + new_selection) - (selection & new_selection)
62
311
  @selection = new_selection
63
- changed.each(&:redraw)
312
+ changed.each(&:redraw_selection)
64
313
  end
65
314
 
66
315
  def items=(new_items)
67
316
  @children = new_items
317
+ # TODO optimize in the future by sorting elements in DOM directly when no change to elements occur other than sort
68
318
  redraw
69
319
  end
70
320
 
@@ -97,10 +347,6 @@ module Glimmer
97
347
  self.selection = new_selection
98
348
  end
99
349
 
100
- def search(&condition)
101
- items.select {|item| condition.nil? || condition.call(item)}
102
- end
103
-
104
350
  def sort_block=(comparator)
105
351
  @sort_block = comparator
106
352
  end
@@ -110,7 +356,7 @@ module Glimmer
110
356
  end
111
357
 
112
358
  def sort_property=(new_sort_property)
113
- @sort_property = [new_sort_property].flatten.compact
359
+ @sort_property = new_sort_property.to_collection
114
360
  end
115
361
 
116
362
  def detect_sort_type
@@ -131,7 +377,7 @@ module Glimmer
131
377
 
132
378
  def column_sort_properties
133
379
  column_properties.zip(columns.map(&:sort_property)).map do |pair|
134
- [pair.compact.last].flatten.compact
380
+ pair.compact.last.to_collection
135
381
  end
136
382
  end
137
383
 
@@ -162,7 +408,7 @@ module Glimmer
162
408
  end
163
409
  end
164
410
 
165
- new_sort_property = [new_sort_property].flatten.compact unless new_sort_property.is_a?(Array)
411
+ new_sort_property = new_sort_property.to_collection unless new_sort_property.is_a?(Array)
166
412
  @sort_direction = @sort_direction.nil? || @sort_property.first != new_sort_property.first || @sort_direction == :descending ? :ascending : :descending
167
413
 
168
414
  @sort_property = new_sort_property
@@ -223,9 +469,118 @@ module Glimmer
223
469
  @additional_sort_properties = args unless args.empty?
224
470
  end
225
471
 
226
- def edit_table_item(table_item, column_index)
227
- table_item&.edit(column_index) unless column_index.nil?
472
+ def editor=(args)
473
+ @editor = args
474
+ end
475
+
476
+ # Indicates if table is in edit mode, thus displaying a text widget for a table item cell
477
+ def edit_mode?
478
+ !!@edit_mode
479
+ end
480
+
481
+ def cancel_edit!
482
+ @cancel_edit&.call if @edit_mode
483
+ end
484
+
485
+ def finish_edit!
486
+ @finish_edit&.call if @edit_mode
487
+ end
488
+
489
+ # Indicates if table is editing a table item because the user hit ENTER or focused out after making a change in edit mode to a table item cell.
490
+ # It is set to false once change is saved to model
491
+ def edit_in_progress?
492
+ !!@edit_in_progress
493
+ end
494
+
495
+ def edit_selected_table_item(column_index, before_write: nil, after_write: nil, after_cancel: nil)
496
+ edit_table_item(selection.first, column_index, before_write: before_write, after_write: after_write, after_cancel: after_cancel)
228
497
  end
498
+
499
+ # TODO migrate the following to the next method
500
+ # def edit_table_item(table_item, column_index)
501
+ # table_item&.edit(column_index) unless column_index.nil?
502
+ # end
503
+
504
+ def edit_table_item(table_item, column_index, before_write: nil, after_write: nil, after_cancel: nil)
505
+ return if table_item.nil? || (@edit_mode && @edit_table_item == table_item && @edit_column_index == column_index)
506
+ @edit_column_index = column_index
507
+ @edit_table_item = table_item
508
+ column_index = column_index.to_i
509
+ model = table_item.data
510
+ property = column_properties[column_index]
511
+ cancel_edit!
512
+ return unless columns[column_index].editable?
513
+ action_taken = false
514
+ @edit_mode = true
515
+
516
+ editor_config = columns[column_index].editor || editor
517
+ editor_config = editor_config.to_collection
518
+ editor_widget_options = editor_config.last.is_a?(Hash) ? editor_config.last : {}
519
+ editor_widget_arg_last_index = editor_config.last.is_a?(Hash) ? -2 : -1
520
+ editor_widget = (editor_config[0] || :text).to_sym
521
+ editor_widget_args = editor_config[1..editor_widget_arg_last_index]
522
+ model_editing_property = editor_widget_options[:property] || property
523
+ widget_value_property = TableProxy::editors.symbolize_keys[editor_widget][:widget_value_property]
524
+
525
+ @cancel_edit = lambda do |event=nil|
526
+ @cancel_in_progress = true
527
+ @table_editor.cancel!
528
+ @table_editor_widget_proxy&.dispose
529
+ @table_editor_widget_proxy = nil
530
+ after_cancel&.call
531
+ @edit_in_progress = false
532
+ @cancel_in_progress = false
533
+ @cancel_edit = nil
534
+ @edit_table_item = @edit_column_index = nil if (@edit_mode && @edit_table_item == table_item && @edit_column_index == column_index)
535
+ @edit_mode = false
536
+ end
537
+
538
+ @finish_edit = lambda do |event=nil|
539
+ new_value = @table_editor_widget_proxy&.send(widget_value_property)
540
+ if table_item.disposed?
541
+ @cancel_edit.call
542
+ elsif !new_value.nil? && !action_taken && !@edit_in_progress && !@cancel_in_progress
543
+ action_taken = true
544
+ @edit_in_progress = true
545
+ if new_value == model.send(model_editing_property)
546
+ @cancel_edit.call
547
+ else
548
+ before_write&.call
549
+ @table_editor.save!(widget_value_property: widget_value_property)
550
+ model.send("#{model_editing_property}=", new_value) # makes table update itself, so must search for selected table item again
551
+ # Table refresh happens here because of model update triggering observers, so must retrieve table item again
552
+ edited_table_item = search { |ti| ti.data == model }.first
553
+ show_item(edited_table_item)
554
+ @table_editor_widget_proxy&.dispose
555
+ @table_editor_widget_proxy = nil
556
+ after_write&.call(edited_table_item)
557
+ @edit_in_progress = false
558
+ @edit_table_item = @edit_column_index = nil
559
+ end
560
+ end
561
+ end
562
+
563
+ content {
564
+ @table_editor_widget_proxy = TableProxy::editors.symbolize_keys[editor_widget][:editor_gui].call(editor_widget_args, model, model_editing_property, self)
565
+ }
566
+ @table_editor.set_editor(@table_editor_widget_proxy, table_item, column_index)
567
+ rescue => e
568
+ Glimmer::Config.logger.error {e.full_message}
569
+ raise e
570
+ end
571
+
572
+ def show_item(table_item)
573
+ table_item.dom_element.focus
574
+ end
575
+
576
+ def add_listener(underscored_listener_name, &block)
577
+ enhanced_block = lambda do |event|
578
+ event.extend(TableListenerEvent)
579
+ block.call(event)
580
+ end
581
+ super(underscored_listener_name, &enhanced_block)
582
+ end
583
+
229
584
 
230
585
  def header_visible=(value)
231
586
  @header_visible = value
@@ -259,7 +614,8 @@ module Glimmer
259
614
  event.singleton_class.send(:define_method, :column_index) do
260
615
  (table_data || event.target).attr('data-column-index')
261
616
  end
262
- event_listener.call(event)
617
+
618
+ event_listener.call(event) unless event.table_item.nil? && event.column_index.nil?
263
619
  }
264
620
  }
265
621
 
@@ -274,7 +630,8 @@ module Glimmer
274
630
  },
275
631
  'on_widget_selected' => {
276
632
  event: 'mouseup',
277
- }
633
+ event_handler: mouse_handler,
634
+ },
278
635
  }
279
636
  end
280
637
 
@@ -337,7 +694,8 @@ module Glimmer
337
694
  table_id = id
338
695
  table_id_style = css
339
696
  table_id_css_classes = css_classes
340
- table_id_css_classes << 'table'
697
+ table_id_css_classes << 'table' unless table_id_css_classes.include?('table')
698
+ table_id_css_classes << 'editable' if editable? && !table_id_css_classes.include?('editable')
341
699
  table_id_css_classes_string = table_id_css_classes.to_a.join(' ')
342
700
  @dom ||= html {
343
701
  table(id: table_id, style: table_id_style, class: table_id_css_classes_string) {