glimmer-dsl-web 0.0.6 → 0.0.8

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.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -19,16 +19,16 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
- # require 'glimmer/web/event_listener_proxy'
23
- require 'glimmer/web/property_owner'
24
22
  require 'glimmer/web/listener_proxy'
25
23
 
26
- # TODO implement menu (which delays building it till render using add_content_on_render)
27
-
28
24
  module Glimmer
29
25
  module Web
30
26
  class ElementProxy
31
27
  class << self
28
+ def keyword_supported?(keyword)
29
+ ELEMENT_KEYWORDS.include?(keyword.to_s)
30
+ end
31
+
32
32
  # Factory Method that translates a Glimmer DSL keyword into a ElementProxy object
33
33
  def for(keyword, parent, args, block)
34
34
  element_type(keyword).new(keyword, parent, args, block)
@@ -68,11 +68,37 @@ module Glimmer
68
68
  end
69
69
 
70
70
  include Glimmer
71
- include PropertyOwner
72
71
 
73
72
  Event = Struct.new(:widget, keyword_init: true)
74
73
 
74
+ ELEMENT_KEYWORDS = [
75
+ "a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b",
76
+ "base", "basefont", "bdi", "bdo", "bgsound", "big", "blink", "blockquote", "body", "br",
77
+ "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data",
78
+ "datalist", "dd", "decorator", "del", "details", "dfn", "dir", "div", "dl", "dt",
79
+ "element", "em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame",
80
+ "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup",
81
+ "hr", "html", "i", "iframe", "img", "input", "ins", "isindex", "kbd", "keygen",
82
+ "label", "legend", "li", "link", "listing", "main", "map", "mark", "marquee", "menu",
83
+ "menuitem", "meta", "meter", "nav", "nobr", "noframes", "noscript", "object", "ol", "optgroup",
84
+ "option", "output", "p", "param", "plaintext", "pre", "progress", "q", "rp", "rt",
85
+ "ruby", "s", "samp", "script", "section", "select", "shadow", "small", "source", "spacer",
86
+ "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td",
87
+ "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt",
88
+ "u", "ul", "var", "video", "wbr", "xmp",
89
+ ]
90
+
75
91
  GLIMMER_ATTRIBUTES = [:parent]
92
+ PROPERTY_ALIASES = {
93
+ 'inner_html' => 'innerHTML',
94
+ 'outer_html' => 'outerHTML',
95
+ }
96
+ FORMAT_DATETIME = '%Y-%m-%dT%H:%M'
97
+ FORMAT_DATE = '%Y-%m-%d'
98
+ FORMAT_TIME = '%H:%M'
99
+ REGEX_FORMAT_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/
100
+ REGEX_FORMAT_DATE = /^\d{4}-\d{2}-\d{2}$/
101
+ REGEX_FORMAT_TIME = /^\d{2}:\d{2}$/
76
102
 
77
103
  attr_reader :keyword, :parent, :args, :options, :children, :enabled, :foreground, :background, :removed?, :rendered
78
104
  alias rendered? rendered
@@ -122,6 +148,11 @@ module Glimmer
122
148
  listeners.each do |event, event_listeners|
123
149
  event_listeners.dup.each(&:unregister)
124
150
  end
151
+ listeners.clear
152
+ data_bindings.each do |element_binding, model_binding|
153
+ element_binding.unregister_all_observables
154
+ end
155
+ data_bindings.clear
125
156
  end
126
157
 
127
158
  # Subclasses can override with their own selector
@@ -286,360 +317,6 @@ module Glimmer
286
317
  {}
287
318
  end
288
319
 
289
- def effective_observation_request_to_event_mapping
290
- default_observation_request_to_event_mapping.merge(observation_request_to_event_mapping)
291
- end
292
-
293
- def default_observation_request_to_event_mapping
294
- myself = self
295
- mouse_event_handler = -> (event_listener) {
296
- -> (event) {
297
- # TODO generalize this solution to all widgets that support key presses
298
- event.define_singleton_method(:widget) {myself}
299
- event.define_singleton_method(:button, &event.method(:which))
300
- event.define_singleton_method(:count) {1} # TODO support double-click count of 2 in the future by using ondblclick
301
- event.define_singleton_method(:x, &event.method(:page_x))
302
- event.define_singleton_method(:y, &event.method(:page_y))
303
- doit = true
304
- event.define_singleton_method(:doit=) do |value|
305
- doit = value
306
- end
307
- event.define_singleton_method(:doit) { doit }
308
-
309
- if event.which == 1
310
- # event.prevent # TODO consider if this is needed
311
- event_listener.call(event)
312
- end
313
-
314
- # TODO Imlement doit properly for all different kinds of events
315
- # unless doit
316
- # event.prevent
317
- # event.stop
318
- # event.stop_immediate
319
- # end
320
- }
321
- }
322
- mouse_move_event_handler = -> (event_listener) {
323
- -> (event) {
324
- # TODO generalize this solution to all widgets that support key presses
325
- event.define_singleton_method(:widget) {myself}
326
- event.define_singleton_method(:button, &event.method(:which))
327
- event.define_singleton_method(:count) {1} # TODO support double-click count of 2 in the future by using ondblclick
328
- event.define_singleton_method(:x, &event.method(:page_x))
329
- event.define_singleton_method(:y, &event.method(:page_y))
330
- doit = true
331
- event.define_singleton_method(:doit=) do |value|
332
- doit = value
333
- end
334
- event.define_singleton_method(:doit) { doit }
335
-
336
- event_listener.call(event)
337
-
338
- # TODO Imlement doit properly for all different kinds of events
339
- # unless doit
340
- # event.prevent
341
- # event.stop
342
- # event.stop_immediate
343
- # end
344
- }
345
- }
346
- context_menu_handler = -> (event_listener) {
347
- -> (event) {
348
- # TODO generalize this solution to all widgets that support key presses
349
- event.define_singleton_method(:widget) {myself}
350
- event.define_singleton_method(:button, &event.method(:which))
351
- event.define_singleton_method(:count) {1} # TODO support double-click count of 2 in the future by using ondblclick
352
- event.define_singleton_method(:x, &event.method(:page_x))
353
- event.define_singleton_method(:y, &event.method(:page_y))
354
- doit = true
355
- event.define_singleton_method(:doit=) do |value|
356
- doit = value
357
- end
358
- event.define_singleton_method(:doit) { doit }
359
-
360
- if event.which == 3
361
- event.prevent
362
- event_listener.call(event)
363
- end
364
- # TODO Imlement doit properly for all different kinds of events
365
- # unless doit
366
- # event.prevent
367
- # event.stop
368
- # event.stop_immediate
369
- # end
370
- }
371
- }
372
- {
373
- 'on_focus_gained' => {
374
- event: 'focus',
375
- },
376
- 'on_focus_lost' => {
377
- event: 'blur',
378
- },
379
- 'on_mouse_move' => [
380
- {
381
- event: 'mousemove',
382
- event_handler: mouse_move_event_handler,
383
- },
384
- ],
385
- 'on_mouse_up' => [
386
- {
387
- event: 'mouseup',
388
- event_handler: mouse_event_handler,
389
- },
390
- {
391
- event: 'contextmenu',
392
- event_handler: context_menu_handler,
393
- },
394
- ],
395
- 'on_mouse_down' => [
396
- {
397
- event: 'mousedown',
398
- event_handler: mouse_event_handler,
399
- },
400
- {
401
- event: 'contextmenu',
402
- event_handler: context_menu_handler,
403
- },
404
- ],
405
- 'on_swt_mouseup' => [
406
- {
407
- event: 'mouseup',
408
- event_handler: mouse_event_handler,
409
- },
410
- {
411
- event: 'contextmenu',
412
- event_handler: context_menu_handler,
413
- },
414
- ],
415
- 'on_swt_mousedown' => [
416
- {
417
- event: 'mousedown',
418
- event_handler: mouse_event_handler,
419
- },
420
- {
421
- event: 'contextmenu',
422
- event_handler: context_menu_handler,
423
- },
424
- ],
425
- 'on_key_pressed' => {
426
- event: 'keypress',
427
- event_handler: -> (event_listener) {
428
- -> (event) {
429
- event.define_singleton_method(:widget) {myself}
430
- event.define_singleton_method(:keyLocation) do
431
- location = `#{event.to_n}.originalEvent.location`
432
- JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
433
- end
434
- event.define_singleton_method(:key_location, &event.method(:keyLocation))
435
- event.define_singleton_method(:keyCode) {
436
- JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
437
- }
438
- event.define_singleton_method(:key_code, &event.method(:keyCode))
439
- event.define_singleton_method(:character) {event.which.chr}
440
- event.define_singleton_method(:stateMask) do
441
- state_mask = 0
442
- state_mask |= SWTProxy[:alt] if event.alt_key
443
- state_mask |= SWTProxy[:ctrl] if event.ctrl_key
444
- state_mask |= SWTProxy[:shift] if event.shift_key
445
- state_mask |= SWTProxy[:command] if event.meta_key
446
- state_mask
447
- end
448
- event.define_singleton_method(:state_mask, &event.method(:stateMask))
449
- doit = true
450
- event.define_singleton_method(:doit=) do |value|
451
- doit = value
452
- end
453
- event.define_singleton_method(:doit) { doit }
454
- event_listener.call(event)
455
-
456
- # TODO Fix doit false, it's not stopping input
457
- unless doit
458
- event.prevent
459
- event.prevent_default
460
- event.stop_propagation
461
- event.stop_immediate_propagation
462
- end
463
-
464
- doit
465
- }
466
- } },
467
- 'on_key_released' => {
468
- event: 'keyup',
469
- event_handler: -> (event_listener) {
470
- -> (event) {
471
- event.define_singleton_method(:keyLocation) do
472
- location = `#{event.to_n}.originalEvent.location`
473
- JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
474
- end
475
- event.define_singleton_method(:key_location, &event.method(:keyLocation))
476
- event.define_singleton_method(:widget) {myself}
477
- event.define_singleton_method(:keyCode) {
478
- JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
479
- }
480
- event.define_singleton_method(:key_code, &event.method(:keyCode))
481
- event.define_singleton_method(:character) {event.which.chr}
482
- event.define_singleton_method(:stateMask) do
483
- state_mask = 0
484
- state_mask |= SWTProxy[:alt] if event.alt_key
485
- state_mask |= SWTProxy[:ctrl] if event.ctrl_key
486
- state_mask |= SWTProxy[:shift] if event.shift_key
487
- state_mask |= SWTProxy[:command] if event.meta_key
488
- state_mask
489
- end
490
- event.define_singleton_method(:state_mask, &event.method(:stateMask))
491
- doit = true
492
- event.define_singleton_method(:doit=) do |value|
493
- doit = value
494
- end
495
- event.define_singleton_method(:doit) { doit }
496
- event_listener.call(event)
497
-
498
- # TODO Fix doit false, it's not stopping input
499
- unless doit
500
- event.prevent
501
- event.prevent_default
502
- event.stop_propagation
503
- event.stop_immediate_propagation
504
- end
505
-
506
- doit
507
- }
508
- }
509
- },
510
- 'on_swt_keydown' => [
511
- {
512
- event: 'keypress',
513
- event_handler: -> (event_listener) {
514
- -> (event) {
515
- event.define_singleton_method(:keyLocation) do
516
- location = `#{event.to_n}.originalEvent.location`
517
- JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
518
- end
519
- event.define_singleton_method(:key_location, &event.method(:keyLocation))
520
- event.define_singleton_method(:keyCode) {
521
- JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
522
- }
523
- event.define_singleton_method(:key_code, &event.method(:keyCode))
524
- event.define_singleton_method(:widget) {myself}
525
- event.define_singleton_method(:character) {event.which.chr}
526
- event.define_singleton_method(:stateMask) do
527
- state_mask = 0
528
- state_mask |= SWTProxy[:alt] if event.alt_key
529
- state_mask |= SWTProxy[:ctrl] if event.ctrl_key
530
- state_mask |= SWTProxy[:shift] if event.shift_key
531
- state_mask |= SWTProxy[:command] if event.meta_key
532
- state_mask
533
- end
534
- event.define_singleton_method(:state_mask, &event.method(:stateMask))
535
- doit = true
536
- event.define_singleton_method(:doit=) do |value|
537
- doit = value
538
- end
539
- event.define_singleton_method(:doit) { doit }
540
- event_listener.call(event)
541
-
542
- # TODO Fix doit false, it's not stopping input
543
- unless doit
544
- event.prevent
545
- event.prevent_default
546
- event.stop_propagation
547
- event.stop_immediate_propagation
548
- end
549
-
550
- doit
551
- }
552
- }
553
- },
554
- {
555
- event: 'keydown',
556
- event_handler: -> (event_listener) {
557
- -> (event) {
558
- event.define_singleton_method(:keyLocation) do
559
- location = `#{event.to_n}.originalEvent.location`
560
- JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
561
- end
562
- event.define_singleton_method(:key_location, &event.method(:keyLocation))
563
- event.define_singleton_method(:keyCode) {
564
- JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
565
- }
566
- event.define_singleton_method(:key_code, &event.method(:keyCode))
567
- event.define_singleton_method(:widget) {myself}
568
- event.define_singleton_method(:character) {event.which.chr}
569
- event.define_singleton_method(:stateMask) do
570
- state_mask = 0
571
- state_mask |= SWTProxy[:alt] if event.alt_key
572
- state_mask |= SWTProxy[:ctrl] if event.ctrl_key
573
- state_mask |= SWTProxy[:shift] if event.shift_key
574
- state_mask |= SWTProxy[:command] if event.meta_key
575
- state_mask
576
- end
577
- event.define_singleton_method(:state_mask, &event.method(:stateMask))
578
- doit = true
579
- event.define_singleton_method(:doit=) do |value|
580
- doit = value
581
- end
582
- event.define_singleton_method(:doit) { doit }
583
- event_listener.call(event) if event.which != 13 && (event.which == 127 || event.which <= 40)
584
-
585
- # TODO Fix doit false, it's not stopping input
586
- unless doit
587
- event.prevent
588
- event.prevent_default
589
- event.stop_propagation
590
- event.stop_immediate_propagation
591
- end
592
- doit
593
- }
594
- }
595
- }
596
- ],
597
- 'on_swt_keyup' => {
598
- event: 'keyup',
599
- event_handler: -> (event_listener) {
600
- -> (event) {
601
- event.define_singleton_method(:keyLocation) do
602
- location = `#{event.to_n}.originalEvent.location`
603
- JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
604
- end
605
- event.define_singleton_method(:key_location, &event.method(:keyLocation))
606
- event.define_singleton_method(:widget) {myself}
607
- event.define_singleton_method(:keyCode) {
608
- JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
609
- }
610
- event.define_singleton_method(:key_code, &event.method(:keyCode))
611
- event.define_singleton_method(:character) {event.which.chr}
612
- event.define_singleton_method(:stateMask) do
613
- state_mask = 0
614
- state_mask |= SWTProxy[:alt] if event.alt_key
615
- state_mask |= SWTProxy[:ctrl] if event.ctrl_key
616
- state_mask |= SWTProxy[:shift] if event.shift_key
617
- state_mask |= SWTProxy[:command] if event.meta_key
618
- state_mask
619
- end
620
- event.define_singleton_method(:state_mask, &event.method(:stateMask))
621
- doit = true
622
- event.define_singleton_method(:doit=) do |value|
623
- doit = value
624
- end
625
- event.define_singleton_method(:doit) { doit }
626
- event_listener.call(event)
627
-
628
- # TODO Fix doit false, it's not stopping input
629
- unless doit
630
- event.prevent
631
- event.prevent_default
632
- event.stop_propagation
633
- event.stop_immediate_propagation
634
- end
635
-
636
- doit
637
- }
638
- }
639
- },
640
- }
641
- end
642
-
643
320
  def name
644
321
  self.class.name.split('::').last.underscore.sub(/_proxy$/, '').gsub('_', '-')
645
322
  end
@@ -767,35 +444,6 @@ module Glimmer
767
444
  listener.register
768
445
  listeners_for(keyword) << listener
769
446
  listener
770
- # return unless effective_observation_request_to_event_mapping.keys.include?(keyword)
771
- # event = nil
772
- # delegate = nil
773
- # effective_observation_request_to_event_mapping[keyword].to_collection.each do |mapping|
774
- # observation_requests[keyword] ||= Set.new
775
- # observation_requests[keyword] << original_event_listener
776
- # event = mapping[:event]
777
- # event_handler = mapping[:event_handler]
778
- # event_element_css_selector = mapping[:event_element_css_selector]
779
- # potential_event_listener = event_handler&.call(original_event_listener)
780
- # event_listener = potential_event_listener || original_event_listener
781
- # async_event_listener = proc do |event|
782
- ## TODO look into the issue with using async::task.new here. maybe put it in event listener (like not being able to call preventDefault or return false successfully )
783
- ## maybe consider pushing inside the widget classes instead where needed only or implement universal doit support correctly to bypass this issue
784
- ## Async::Task.new do
785
- # @@widget_handling_listener = self
786
- ## TODO also make sure to disable all widgets for suspension
787
- # event_listener.call(event) unless dialog_ancestor&.event_handling_suspended?
788
- # @widget_handling_listener = nil
789
- ## end
790
- # end
791
- # the_listener_dom_element = event_element_css_selector ? Element[event_element_css_selector] : listener_dom_element
792
- # unless the_listener_dom_element.empty?
793
- # the_listener_dom_element.on(event, &async_event_listener)
794
- ## TODO ensure uniqueness of insertion (perhaps adding equals/hash method to event listener proxy)
795
- #
796
- # event_listener_proxies << EventListenerProxy.new(element_proxy: self, selector: selector, dom_element: the_listener_dom_element, event: event, listener: async_event_listener, original_event_listener: original_event_listener)
797
- # end
798
- # end
799
447
  end
800
448
 
801
449
  def remove_event_listener_proxies
@@ -805,40 +453,62 @@ module Glimmer
805
453
  event_listener_proxies.clear
806
454
  end
807
455
 
456
+ def data_bindings
457
+ @data_bindings ||= {}
458
+ end
459
+
808
460
  def data_bind(property, model_binding)
809
- element_binding_parameters = [self, property]
461
+ element_binding_translator = value_converters_for_input_type(type)[:model_to_view]
462
+ element_binding_parameters = [self, property, element_binding_translator]
810
463
  element_binding = DataBinding::ElementBinding.new(*element_binding_parameters)
811
464
  element_binding.call(model_binding.evaluate_property)
812
465
  #TODO make this options observer dependent and all similar observers in element specific data binding handlers
813
466
  element_binding.observe(model_binding)
467
+ data_bindings[element_binding] = model_binding
814
468
  unless model_binding.binding_options[:read_only]
815
469
  # TODO add guards against nil cases for hash below
816
- listener_keyword = data_binding_element_keyword_to_property_listener_map[keyword][property]
817
- data_binding_read_listener = lambda do |event|
818
- model_binding.call(send(property))
470
+ listener_keyword = data_binding_listener_for_element_and_property(keyword, property)
471
+ if listener_keyword
472
+ data_binding_read_listener = lambda do |event|
473
+ view_property_value = send(property)
474
+ converted_view_property_value = value_converters_for_input_type(type)[:view_to_model].call(view_property_value, model_binding.evaluate_property)
475
+ model_binding.call(converted_view_property_value)
476
+ end
477
+ handle_observation_request(listener_keyword, data_binding_read_listener)
819
478
  end
820
- handle_observation_request(listener_keyword, data_binding_read_listener)
821
479
  end
822
480
  end
823
481
 
824
- def set_attribute(attribute_name, *args)
825
- apply_property_type_converters(attribute_name, args)
826
- super(attribute_name, *args) # PropertyOwner
482
+ # Data-binds the generation of nested content to a model/property (in binding args)
483
+ # consider providing an option to avoid initial rendering without any changes happening
484
+ def bind_content(*binding_args, &content_block)
485
+ # TODO in the future, consider optimizing code by diffing content if that makes sense
486
+ content_binding_work = proc do |*values|
487
+ children.dup.each { |child| child.remove }
488
+ content(&content_block)
489
+ end
490
+ content_binding_observer = Glimmer::DataBinding::Observer.proc(&content_binding_work)
491
+ content_binding_observer.observe(*binding_args)
492
+ content_binding_work.call # TODO inspect if we need to pass args here (from observed attributes) [but it's simpler not to pass anything at first]
827
493
  end
828
494
 
829
495
  def respond_to_missing?(method_name, include_private = false)
830
496
  # TODO consider doing more correct checking of availability of properties/methods using native `` ticks
831
497
  property_name = property_name_for(method_name)
498
+ unnormalized_property_name = unnormalized_property_name_for(method_name)
832
499
  super(method_name, include_private) ||
833
500
  (dom_element && dom_element.length > 0 && Native.call(dom_element, '0').respond_to?(method_name.to_s.camelcase, include_private)) ||
501
+ (dom_element && dom_element.length > 0 && Native.call(dom_element, '0').respond_to?(method_name.to_s, include_private)) ||
834
502
  dom_element.respond_to?(method_name, include_private) ||
835
503
  (!dom_element.prop(property_name).nil? && !dom_element.prop(property_name).is_a?(Proc)) ||
504
+ (!dom_element.prop(unnormalized_property_name).nil? && !dom_element.prop(unnormalized_property_name).is_a?(Proc)) ||
836
505
  method_name.to_s.start_with?('on_')
837
506
  end
838
507
 
839
508
  def method_missing(method_name, *args, &block)
840
509
  # TODO consider doing more correct checking of availability of properties/methods using native `` ticks
841
510
  property_name = property_name_for(method_name)
511
+ unnormalized_property_name = unnormalized_property_name_for(method_name)
842
512
  if method_name.to_s.start_with?('on_')
843
513
  handle_observation_request(method_name, block)
844
514
  elsif dom_element.respond_to?(method_name)
@@ -849,12 +519,22 @@ module Glimmer
849
519
  else
850
520
  dom_element.prop(property_name)
851
521
  end
522
+ elsif !dom_element.prop(unnormalized_property_name).nil? && !dom_element.prop(unnormalized_property_name).is_a?(Proc)
523
+ if method_name.end_with?('=')
524
+ dom_element.prop(unnormalized_property_name, *args)
525
+ else
526
+ dom_element.prop(unnormalized_property_name)
527
+ end
852
528
  elsif dom_element && dom_element.length > 0
529
+ js_args = block.nil? ? args : (args + [block])
853
530
  begin
854
- js_args = block.nil? ? args : (args + [block])
855
531
  Native.call(dom_element, '0').method_missing(method_name.to_s.camelcase, *js_args)
856
532
  rescue Exception => e
857
- super(method_name, *args, &block)
533
+ begin
534
+ Native.call(dom_element, '0').method_missing(method_name.to_s, *js_args)
535
+ rescue Exception => e
536
+ super(method_name, *args, &block)
537
+ end
858
538
  end
859
539
  else
860
540
  super(method_name, *args, &block)
@@ -862,7 +542,12 @@ module Glimmer
862
542
  end
863
543
 
864
544
  def property_name_for(method_name)
865
- method_name.end_with?('=') ? method_name.to_s[0...-1].camelcase : method_name.to_s.camelcase
545
+ attribute_name = method_name.end_with?('=') ? method_name.to_s[0...-1] : method_name.to_s
546
+ PROPERTY_ALIASES[attribute_name] || attribute_name.camelcase
547
+ end
548
+
549
+ def unnormalized_property_name_for(method_name)
550
+ method_name.end_with?('=') ? method_name.to_s[0...-1] : method_name.to_s
866
551
  end
867
552
 
868
553
  def swt_widget
@@ -870,73 +555,19 @@ module Glimmer
870
555
  self
871
556
  end
872
557
 
873
- def apply_property_type_converters(attribute_name, args)
874
- if args.count == 1
875
- value = args.first
876
- converter = property_type_converters[attribute_name.to_sym]
877
- args[0] = converter.call(value) if converter
878
- end
879
- # if args.count == 1 && args.first.is_a?(ColorProxy)
880
- # g_color = args.first
881
- # args[0] = g_color.swt_color
882
- # end
558
+ def data_binding_listener_for_element_and_property(element_keyword, property)
559
+ data_binding_property_listener_map_for_element(element_keyword)[property]
883
560
  end
884
561
 
885
- def property_type_converters
886
- color_converter = proc do |value|
887
- if value.is_a?(Symbol) || value.is_a?(String)
888
- ColorProxy.new(value)
889
- else
890
- value
891
- end
892
- end
893
- @property_type_converters ||= {
894
- :background => color_converter,
895
- # :background_image => proc do |value|
896
- # if value.is_a?(String)
897
- # if value.start_with?('uri:classloader')
898
- # value = value.sub(/^uri\:classloader\:\//, '')
899
- # object = java.lang.Object.new
900
- # value = object.java_class.resource_as_stream(value)
901
- # value = java.io.BufferedInputStream.new(value)
902
- # end
903
- # image_data = ImageData.new(value)
904
- # on_event_Resize do |resize_event|
905
- # new_image_data = image_data.scaledTo(@swt_widget.getSize.x, @swt_widget.getSize.y)
906
- # @swt_widget.getBackgroundImage&.remove
907
- # @swt_widget.setBackgroundImage(Image.new(@swt_widget.getDisplay, new_image_data))
908
- # end
909
- # Image.new(@swt_widget.getDisplay, image_data)
910
- # else
911
- # value
912
- # end
913
- # end,
914
- :foreground => color_converter,
915
- # :font => proc do |value|
916
- # if value.is_a?(Hash)
917
- # font_properties = value
918
- # FontProxy.new(self, font_properties).swt_font
919
- # else
920
- # value
921
- # end
922
- # end,
923
- :text => proc do |value|
924
- # if swt_widget.is_a?(Browser)
925
- # value.to_s
926
- # else
927
- value.to_s
928
- # end
929
- end,
930
- # :visible => proc do |value|
931
- # !!value
932
- # end,
933
- }
562
+ def data_binding_property_listener_map_for_element(element_keyword)
563
+ data_binding_element_keyword_to_property_listener_map[element_keyword] || {}
934
564
  end
935
565
 
936
566
  def data_binding_element_keyword_to_property_listener_map
937
567
  @data_binding_element_keyword_to_property_listener_map ||= {
938
568
  'input' => {
939
569
  'value' => 'oninput',
570
+ 'checked' => 'oninput',
940
571
  },
941
572
  'select' => {
942
573
  'value' => 'onchange',
@@ -947,8 +578,106 @@ module Glimmer
947
578
  }
948
579
  end
949
580
 
581
+ def value_converters_for_input_type(input_type)
582
+ input_value_converters[input_type] || {model_to_view: ->(value, old_value) {value}, view_to_model: ->(value, old_value) {value}}
583
+ end
584
+
585
+ def input_value_converters
586
+ @input_value_converters ||= {
587
+ 'number' => {
588
+ model_to_view: -> (value, old_value) { value.to_s },
589
+ view_to_model: -> (value, old_value) {
590
+ value.include?('.') ? value.to_f : value.to_i
591
+ },
592
+ },
593
+ 'range' => {
594
+ model_to_view: -> (value, old_value) { value.to_s },
595
+ view_to_model: -> (value, old_value) {
596
+ value.include?('.') ? value.to_f : value.to_i
597
+ },
598
+ },
599
+ 'datetime-local' => {
600
+ model_to_view: -> (value, old_value) {
601
+ if value.respond_to?(:strftime)
602
+ value.strftime(FORMAT_DATETIME)
603
+ elsif value.is_a?(String) && valid_js_date_string?(value)
604
+ value
605
+ else
606
+ old_value
607
+ end
608
+ },
609
+ view_to_model: -> (value, old_value) {
610
+ if value.to_s.empty?
611
+ nil
612
+ else
613
+ date = Native(`new Date(Date.parse(#{value}))`)
614
+ year = Native.call(date, 'getFullYear')
615
+ month = Native.call(date, 'getMonth') + 1
616
+ day = Native.call(date, 'getDate')
617
+ hour = Native.call(date, 'getHours')
618
+ minute = Native.call(date, 'getMinutes')
619
+ Time.new(year, month, day, hour, minute)
620
+ end
621
+ },
622
+ },
623
+ 'date' => {
624
+ model_to_view: -> (value, old_value) {
625
+ if value.respond_to?(:strftime)
626
+ value.strftime(FORMAT_DATE)
627
+ elsif value.is_a?(String) && valid_js_date_string?(value)
628
+ value
629
+ else
630
+ old_value
631
+ end
632
+ },
633
+ view_to_model: -> (value, old_value) {
634
+ if value.to_s.empty?
635
+ nil
636
+ else
637
+ year, month, day = value.split('-')
638
+ if old_value
639
+ Time.new(year, month, day, old_value.hour, old_value.min)
640
+ else
641
+ Time.new(year, month, day)
642
+ end
643
+ end
644
+ },
645
+ },
646
+ 'time' => {
647
+ model_to_view: -> (value, old_value) {
648
+ if value.respond_to?(:strftime)
649
+ value.strftime(FORMAT_TIME)
650
+ elsif value.is_a?(String) && valid_js_date_string?(value)
651
+ value
652
+ else
653
+ old_value
654
+ end
655
+ },
656
+ view_to_model: -> (value, old_value) {
657
+ if value.to_s.empty?
658
+ nil
659
+ else
660
+ hour, minute = value.split(':')
661
+ if old_value
662
+ Time.new(old_value.year, old_value.month, old_value.day, hour, minute)
663
+ else
664
+ now = Time.now
665
+ Time.new(now.year, now.month, now.day, hour, minute)
666
+ end
667
+ end
668
+ },
669
+ },
670
+ }
671
+ end
672
+
950
673
  private
951
674
 
675
+ def valid_js_date_string?(string)
676
+ [REGEX_FORMAT_DATETIME, REGEX_FORMAT_DATE, REGEX_FORMAT_TIME].any? do |format|
677
+ string.match(format)
678
+ end
679
+ end
680
+
952
681
  def css_cursor
953
682
  SWT_CURSOR_TO_CSS_CURSOR_MAP[@cursor]
954
683
  end