glimmer-dsl-web 0.0.6 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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