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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/LICENSE.txt +1 -1
- data/README.md +425 -36
- data/VERSION +1 -1
- data/glimmer-dsl-web.gemspec +11 -8
- data/lib/glimmer/data_binding/element_binding.rb +1 -1
- data/lib/glimmer/dsl/web/bind_expression.rb +1 -1
- data/lib/glimmer/dsl/web/content_data_binding_expression.rb +41 -0
- data/lib/glimmer/dsl/web/dsl.rb +2 -9
- data/lib/glimmer/dsl/web/element_expression.rb +3 -3
- data/lib/glimmer/util/proc_tracker.rb +1 -1
- data/lib/glimmer/web/element_proxy.rb +188 -459
- data/lib/glimmer/web/event_proxy.rb +1 -1
- data/lib/glimmer/web/listener_proxy.rb +1 -2
- data/lib/glimmer/web.rb +1 -1
- data/lib/glimmer-dsl-web/ext/date.rb +4 -1
- data/lib/glimmer-dsl-web/samples/hello/hello_button.rb +1 -1
- data/lib/glimmer-dsl-web/samples/hello/hello_content_data_binding.rb +137 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_data_binding.rb +26 -11
- data/lib/glimmer-dsl-web/samples/hello/hello_form.rb +1 -1
- data/lib/glimmer-dsl-web/samples/hello/hello_input_date_time.rb +117 -0
- data/lib/glimmer-dsl-web/samples/hello/hello_world.rb +1 -1
- data/lib/glimmer-dsl-web.rb +7 -3
- metadata +49 -33
- data/lib/glimmer/web/property_owner.rb +0 -24
@@ -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
|
-
|
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 =
|
817
|
-
|
818
|
-
|
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
|
-
|
825
|
-
|
826
|
-
|
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
|
-
|
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]
|
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
|
874
|
-
|
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
|
886
|
-
|
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
|