glimmer-dsl-opal 0.6.1 → 0.7.4

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -0
  3. data/README.md +489 -16
  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 +31 -3
  8. data/lib/glimmer-dsl-opal/ext/date.rb +11 -0
  9. data/lib/glimmer-dsl-opal/ext/struct.rb +37 -0
  10. data/lib/glimmer-dsl-opal/samples/hello/hello_browser.rb +1 -1
  11. data/lib/glimmer-dsl-opal/samples/hello/hello_button.rb +46 -0
  12. data/lib/glimmer-dsl-opal/samples/hello/hello_custom_shell.rb +7 -7
  13. data/lib/glimmer-dsl-opal/samples/hello/hello_table.rb +283 -0
  14. data/lib/glimmer-dsl-swt.rb +20 -35
  15. data/lib/glimmer/data_binding/table_items_binding.rb +32 -19
  16. data/lib/glimmer/dsl/opal/block_property_expression.rb +41 -0
  17. data/lib/glimmer/dsl/opal/custom_widget_expression.rb +5 -0
  18. data/lib/glimmer/dsl/opal/dsl.rb +2 -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/display_proxy.rb +4 -0
  25. data/lib/glimmer/swt/font_proxy.rb +4 -4
  26. data/lib/glimmer/swt/grid_layout_proxy.rb +7 -8
  27. data/lib/glimmer/swt/label_proxy.rb +11 -3
  28. data/lib/glimmer/swt/layout_data_proxy.rb +9 -3
  29. data/lib/glimmer/swt/layout_proxy.rb +1 -1
  30. data/lib/glimmer/swt/message_box_proxy.rb +3 -10
  31. data/lib/glimmer/swt/property_owner.rb +2 -2
  32. data/lib/glimmer/swt/shell_proxy.rb +8 -0
  33. data/lib/glimmer/swt/table_column_proxy.rb +71 -12
  34. data/lib/glimmer/swt/table_editor.rb +65 -0
  35. data/lib/glimmer/swt/table_item_proxy.rb +44 -1
  36. data/lib/glimmer/swt/table_proxy.rb +579 -12
  37. data/lib/glimmer/swt/text_proxy.rb +49 -1
  38. data/lib/glimmer/swt/widget_proxy.rb +106 -17
  39. data/lib/glimmer/ui/custom_shell.rb +9 -7
  40. data/lib/net/http.rb +1 -5
  41. data/lib/os.rb +36 -0
  42. data/lib/uri.rb +3 -3
  43. metadata +31 -9
  44. data/lib/glimmer/data_binding/ext/observable_model.rb +0 -40
@@ -1,28 +1,294 @@
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
7
- attr_reader :columns, :selection
8
- attr_accessor :column_properties
29
+ class TableProxy < CompositeProxy
30
+ attr_reader :columns, :selection,
31
+ :sort_type, :sort_column, :sort_property, :sort_block, :sort_by_block, :additional_sort_properties,
32
+ :editor, :table_editor
33
+ attr_accessor :column_properties, :item_count, :data
9
34
  alias items children
35
+ alias model_binding data
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
10
237
 
11
238
  def initialize(parent, args, block)
12
239
  super(parent, args, block)
13
240
  @columns = []
14
241
  @children = []
242
+ @editors = []
15
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
249
+ if editable?
250
+ on_mouse_up { |event|
251
+ edit_table_item(event.table_item, event.column_index)
252
+ }
253
+ end
16
254
  end
17
255
 
18
256
  # Only table_columns may be added as children
19
257
  def post_initialize_child(child)
20
258
  if child.is_a?(TableColumnProxy)
21
259
  @columns << child
22
- else
260
+ child.render
261
+ elsif child.is_a?(TableItemProxy)
23
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)
24
277
  end
25
- child.redraw
278
+ end
279
+
280
+ def post_add_content
281
+ return if @initially_sorted
282
+ initial_sort!
283
+ @initially_sorted = true
284
+ end
285
+
286
+ def default_layout
287
+ nil
288
+ end
289
+
290
+ def get_data(key=nil)
291
+ data
26
292
  end
27
293
 
28
294
  def remove_all
@@ -30,17 +296,37 @@ module Glimmer
30
296
  redraw
31
297
  end
32
298
 
299
+ def editable?
300
+ args.include?(:editable)
301
+ end
302
+ alias editable editable?
303
+
304
+ def selection
305
+ @selection.to_a
306
+ end
307
+
33
308
  def selection=(new_selection)
34
- changed = (@selection + new_selection) - (@selection & new_selection)
309
+ new_selection = new_selection.to_a
310
+ changed = (selection + new_selection) - (selection & new_selection)
35
311
  @selection = new_selection
36
- changed.each(&:redraw)
312
+ changed.each(&:redraw_selection)
37
313
  end
38
314
 
39
315
  def items=(new_items)
40
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
41
318
  redraw
42
319
  end
43
320
 
321
+ def item_count=(value)
322
+ @item_count = value
323
+ redraw_empty_items
324
+ end
325
+
326
+ def cells_for(model)
327
+ column_properties.map {|property| model.send(property)}
328
+ end
329
+
44
330
  def search(&condition)
45
331
  items.select {|item| condition.nil? || condition.call(item)}
46
332
  end
@@ -61,8 +347,252 @@ module Glimmer
61
347
  self.selection = new_selection
62
348
  end
63
349
 
64
- def edit_table_item(table_item, column_index)
65
- table_item.edit(column_index)
350
+ def sort_block=(comparator)
351
+ @sort_block = comparator
352
+ end
353
+
354
+ def sort_by_block=(property_picker)
355
+ @sort_by_block = property_picker
356
+ end
357
+
358
+ def sort_property=(new_sort_property)
359
+ @sort_property = new_sort_property.to_collection
360
+ end
361
+
362
+ def detect_sort_type
363
+ @sort_type = sort_property.size.times.map { String }
364
+ array = model_binding.evaluate_property
365
+ sort_property.each_with_index do |a_sort_property, i|
366
+ values = array.map { |object| object.send(a_sort_property) }
367
+ value_classes = values.map(&:class).uniq
368
+ if value_classes.size == 1
369
+ @sort_type[i] = value_classes.first
370
+ elsif value_classes.include?(Integer)
371
+ @sort_type[i] = Integer
372
+ elsif value_classes.include?(Float)
373
+ @sort_type[i] = Float
374
+ end
375
+ end
376
+ end
377
+
378
+ def column_sort_properties
379
+ column_properties.zip(columns.map(&:sort_property)).map do |pair|
380
+ pair.compact.last.to_collection
381
+ end
382
+ end
383
+
384
+ def sort_direction
385
+ @sort_direction == :ascending ? SWTProxy[:up] : SWTProxy[:down]
386
+ end
387
+
388
+ def sort_direction=(value)
389
+ @sort_direction = value == SWTProxy[:up] ? :ascending : :descending
390
+ end
391
+
392
+ # Sorts by specified TableColumnProxy object. If nil, it uses the table default sort instead.
393
+ def sort_by_column!(table_column_proxy=nil)
394
+ index = columns.to_a.index(table_column_proxy) unless table_column_proxy.nil?
395
+ new_sort_property = table_column_proxy.nil? ? @sort_property : table_column_proxy.sort_property || [column_properties[index]]
396
+
397
+ return if table_column_proxy.nil? && new_sort_property.nil? && @sort_block.nil? && @sort_by_block.nil?
398
+ if new_sort_property && table_column_proxy.nil? && new_sort_property.size == 1 && (index = column_sort_properties.index(new_sort_property))
399
+ table_column_proxy = columns[index]
400
+ end
401
+ if new_sort_property && new_sort_property.size == 1 && !additional_sort_properties.to_a.empty?
402
+ selected_additional_sort_properties = additional_sort_properties.clone
403
+ if selected_additional_sort_properties.include?(new_sort_property.first)
404
+ selected_additional_sort_properties.delete(new_sort_property.first)
405
+ new_sort_property += selected_additional_sort_properties
406
+ else
407
+ new_sort_property += additional_sort_properties
408
+ end
409
+ end
410
+
411
+ new_sort_property = new_sort_property.to_collection unless new_sort_property.is_a?(Array)
412
+ @sort_direction = @sort_direction.nil? || @sort_property.first != new_sort_property.first || @sort_direction == :descending ? :ascending : :descending
413
+
414
+ @sort_property = new_sort_property
415
+ table_column_index = column_properties.index(new_sort_property.to_s.to_sym)
416
+ table_column_proxy ||= columns[table_column_index] if table_column_index
417
+ @sort_column = table_column_proxy if table_column_proxy
418
+
419
+ if table_column_proxy
420
+ @sort_by_block = nil
421
+ @sort_block = nil
422
+ end
423
+ @sort_type = nil
424
+ if table_column_proxy&.sort_by_block
425
+ @sort_by_block = table_column_proxy.sort_by_block
426
+ elsif table_column_proxy&.sort_block
427
+ @sort_block = table_column_proxy.sort_block
428
+ else
429
+ detect_sort_type
430
+ end
431
+
432
+ sort!
433
+ end
434
+
435
+ def initial_sort!
436
+ sort_by_column!
437
+ end
438
+
439
+ def sort!
440
+ return unless sort_property && (sort_type || sort_block || sort_by_block)
441
+ array = model_binding.evaluate_property
442
+ array = array.sort_by(&:hash) # this ensures consistent subsequent sorting in case there are equivalent sorts to avoid an infinite loop
443
+ # Converting value to_s first to handle nil cases. Should work with numeric, boolean, and date fields
444
+ if sort_block
445
+ sorted_array = array.sort(&sort_block)
446
+ elsif sort_by_block
447
+ sorted_array = array.sort_by(&sort_by_block)
448
+ else
449
+ sorted_array = array.sort_by do |object|
450
+ sort_property.each_with_index.map do |a_sort_property, i|
451
+ value = object.send(a_sort_property)
452
+ # handle nil and difficult to compare types gracefully
453
+ if sort_type[i] == Integer
454
+ value = value.to_i
455
+ elsif sort_type[i] == Float
456
+ value = value.to_f
457
+ elsif sort_type[i] == String
458
+ value = value.to_s
459
+ end
460
+ value
461
+ end
462
+ end
463
+ end
464
+ sorted_array = sorted_array.reverse if @sort_direction == :descending
465
+ model_binding.call(sorted_array)
466
+ end
467
+
468
+ def additional_sort_properties=(*args)
469
+ @additional_sort_properties = args unless args.empty?
470
+ end
471
+
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)
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
+
584
+
585
+ def header_visible=(value)
586
+ @header_visible = value
587
+ if @header_visible
588
+ thead_dom_element.remove_class('hide')
589
+ else
590
+ thead_dom_element.add_class('hide')
591
+ end
592
+ end
593
+
594
+ def header_visible
595
+ @header_visible
66
596
  end
67
597
 
68
598
  def selector
@@ -84,7 +614,8 @@ module Glimmer
84
614
  event.singleton_class.send(:define_method, :column_index) do
85
615
  (table_data || event.target).attr('data-column-index')
86
616
  end
87
- event_listener.call(event)
617
+
618
+ event_listener.call(event) unless event.table_item.nil? && event.column_index.nil?
88
619
  }
89
620
  }
90
621
 
@@ -96,13 +627,27 @@ module Glimmer
96
627
  'on_mouse_up' => {
97
628
  event: 'mouseup',
98
629
  event_handler: mouse_handler,
99
- }
630
+ },
631
+ 'on_widget_selected' => {
632
+ event: 'mouseup',
633
+ event_handler: mouse_handler,
634
+ },
100
635
  }
101
636
  end
102
637
 
103
638
  def redraw
104
639
  super()
105
640
  @columns.to_a.each(&:redraw)
641
+ redraw_empty_items
642
+ end
643
+
644
+ def redraw_empty_items
645
+ if @children&.size.to_i < item_count.to_i
646
+ item_count.to_i.times do
647
+ empty_columns = column_properties&.size.to_i.times.map { |i| "<td data-column-index='#{i}'></td>" }
648
+ items_dom_element.append("<tr class='table-item empty-table-item'>#{empty_columns}</tr>")
649
+ end
650
+ end
106
651
  end
107
652
 
108
653
  def element
@@ -136,6 +681,10 @@ module Glimmer
136
681
  }
137
682
  end
138
683
 
684
+ def thead_dom_element
685
+ dom_element.find('thead')
686
+ end
687
+
139
688
  def items_dom
140
689
  tbody {
141
690
  }
@@ -145,7 +694,8 @@ module Glimmer
145
694
  table_id = id
146
695
  table_id_style = css
147
696
  table_id_css_classes = css_classes
148
- 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')
149
699
  table_id_css_classes_string = table_id_css_classes.to_a.join(' ')
150
700
  @dom ||= html {
151
701
  table(id: table_id, style: table_id_style, class: table_id_css_classes_string) {
@@ -154,6 +704,23 @@ module Glimmer
154
704
  }
155
705
  }.to_s
156
706
  end
707
+
708
+ private
709
+
710
+ def property_type_converters
711
+ super.merge({
712
+ selection: lambda do |value|
713
+ if value.is_a?(Array)
714
+ search {|ti| value.include?(ti.get_data) }
715
+ else
716
+ search {|ti| ti.get_data == value}
717
+ end
718
+ end,
719
+ })
720
+ end
721
+
157
722
  end
723
+
158
724
  end
725
+
159
726
  end