Class: Glimmer::Web::ElementProxy

Inherits:
Object
  • Object
show all
Includes:
Glimmer
Defined in:
lib/glimmer/web/element_proxy.rb

Defined Under Namespace

Classes: Event

Constant Summary collapse

ELEMENT_KEYWORDS =
[
  "a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio",
  "base", "basefont", "bdi", "bdo", "bgsound", "big", "blink", "blockquote", "body",
  "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "data",
  "datalist", "dd", "decorator", "details", "dfn", "dir", "div", "dl", "dt",
  "element", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame",
  "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup",
  "hr", "html", "iframe", "img", "input", "isindex", "kbd", "keygen",
  "label", "legend", "li", "link", "listing", "main", "map", "marquee", "menu",
  "menuitem", "meta", "meter", "nav", "nobr", "noframes", "noscript", "object", "ol", "optgroup",
  "option", "output", "p", "param", "plaintext", "pre", "progress", "q", "rp", "rt",
  "ruby", "s", "samp", "script", "section", "select", "shadow", "source", "spacer",
  "span", "strike", "style", "summary", "table", "tbody", "td",
  "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt",
  "u", "ul", "var", "video", "wbr", "xmp",
]
GLIMMER_ATTRIBUTES =
[:parent]
PROPERTY_ALIASES =
{
  'inner_html' => 'innerHTML',
  'outer_html' => 'outerHTML',
}
FORMAT_DATETIME =
'%Y-%m-%dT%H:%M'
FORMAT_DATE =
'%Y-%m-%d'
FORMAT_TIME =
'%H:%M'
REGEX_FORMAT_DATETIME =
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/
REGEX_FORMAT_DATE =
/^\d{4}-\d{2}-\d{2}$/
REGEX_FORMAT_TIME =
/^\d{2}:\d{2}$/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(keyword, parent, args, block) ⇒ ElementProxy

Returns a new instance of ElementProxy.



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/glimmer/web/element_proxy.rb', line 129

def initialize(keyword, parent, args, block)
  @keyword = keyword
  @parent = parent.is_a?(Glimmer::Web::Component) ? parent.markup_root : parent
  @parent_component = parent if parent.is_a?(Glimmer::Web::Component)
  @options = args.last.is_a?(Hash) ? args.last.symbolize_keys : {}
  if parent.nil?
    options[:parent] ||= Component.interpretation_stack.last&.options&.[](:parent)
    options[:render] ||= Component.interpretation_stack.last&.options&.[](:render)
    options[:bulk_render] ||= Component.interpretation_stack.last&.options&.[](:bulk_render)
  end
  @args = args
  @block = block
  @children = []
  @parent&.post_initialize_child(self)
  render if !bulk_render? && !@rendered && render_after_create?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object



601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
# File 'lib/glimmer/web/element_proxy.rb', line 601

def method_missing(method_name, *args, &block)
  # TODO consider doing more correct checking of availability of properties/methods using native ticks
  property_name = property_name_for(method_name)
  unnormalized_property_name = unnormalized_property_name_for(method_name)
  if method_name.to_s.start_with?('on_')
    handle_observation_request(method_name, block)
  elsif dom_element.respond_to?(method_name)
    if rendered?
      dom_element.send(method_name, *args, &block)
    else
      enqueue_post_render_method_call(method_name, *args, &block)
    end
  elsif !dom_element.prop(property_name).nil? && !dom_element.prop(property_name).is_a?(Proc)
    if rendered?
      if method_name.end_with?('=')
        dom_element.prop(property_name, *args)
      else
        dom_element.prop(property_name)
      end
    else
      enqueue_post_render_method_call(method_name, *args, &block)
    end
  elsif !dom_element.prop(unnormalized_property_name).nil? && !dom_element.prop(unnormalized_property_name).is_a?(Proc)
    if rendered?
      if method_name.end_with?('=')
        dom_element.prop(unnormalized_property_name, *args)
      else
        dom_element.prop(unnormalized_property_name)
      end
    else
      enqueue_post_render_method_call(method_name, *args, &block)
    end
  elsif dom_element && dom_element.length > 0
    if rendered?
      js_args = block.nil? ? args : (args + [block])
      begin
        Native.call(dom_element, '0').method_missing(method_name.to_s.camelcase, *js_args)
      rescue Exception => e
        begin
          Native.call(dom_element, '0').method_missing(method_name.to_s, *js_args)
        rescue Exception => e
          super(method_name, *args, &block)
        end
      end
    else
      enqueue_post_render_method_call(method_name, *args, &block)
    end
  else
    super(method_name, *args, &block)
  end
end

Instance Attribute Details

#argsObject (readonly)

Returns the value of attribute args.



126
127
128
# File 'lib/glimmer/web/element_proxy.rb', line 126

def args
  @args
end

#backgroundObject

Returns the value of attribute background.



126
127
128
# File 'lib/glimmer/web/element_proxy.rb', line 126

def background
  @background
end

#childrenObject (readonly)

Returns the value of attribute children.



126
127
128
# File 'lib/glimmer/web/element_proxy.rb', line 126

def children
  @children
end

#enabledObject

Returns the value of attribute enabled.



126
127
128
# File 'lib/glimmer/web/element_proxy.rb', line 126

def enabled
  @enabled
end

#foregroundObject

Returns the value of attribute foreground.



126
127
128
# File 'lib/glimmer/web/element_proxy.rb', line 126

def foreground
  @foreground
end

#keywordObject (readonly)

Returns the value of attribute keyword.



126
127
128
# File 'lib/glimmer/web/element_proxy.rb', line 126

def keyword
  @keyword
end

#optionsObject (readonly)

Returns the value of attribute options.



126
127
128
# File 'lib/glimmer/web/element_proxy.rb', line 126

def options
  @options
end

#parentObject (readonly)

Returns the value of attribute parent.



126
127
128
# File 'lib/glimmer/web/element_proxy.rb', line 126

def parent
  @parent
end

#parent_componentObject (readonly)

Returns the value of attribute parent_component.



126
127
128
# File 'lib/glimmer/web/element_proxy.rb', line 126

def parent_component
  @parent_component
end

#removed?Object (readonly)

Returns the value of attribute removed?.



126
127
128
# File 'lib/glimmer/web/element_proxy.rb', line 126

def removed?
  @removed?
end

#renderedObject (readonly) Also known as: rendered?

Returns the value of attribute rendered.



126
127
128
# File 'lib/glimmer/web/element_proxy.rb', line 126

def rendered
  @rendered
end

Class Method Details

.element_type(keyword) ⇒ Object

NOTE: Avoid using this method for now as it has slow performance returns Ruby proxy class (type) that would handle this keyword



43
44
45
46
47
48
# File 'lib/glimmer/web/element_proxy.rb', line 43

def element_type(keyword)
  class_name_main = "#{keyword.camelcase(:upper)}Proxy"
  Glimmer::Web::ElementProxy.const_get(class_name_main.to_sym)
rescue NameError => e
  Glimmer::Web::ElementProxy
end

.for(keyword, parent, args, block) ⇒ Object

NOTE: Avoid using this method until we start supporting ElementProxy subclasses in which case, we must cache them to avoid the slow performance of element_type Factory Method that translates a Glimmer DSL keyword into a ElementProxy object



37
38
39
# File 'lib/glimmer/web/element_proxy.rb', line 37

def for(keyword, parent, args, block)
  element_type(keyword).new(keyword, parent, args, block)
end

.keyword_supported?(keyword) ⇒ Boolean

Returns:

  • (Boolean)


30
31
32
# File 'lib/glimmer/web/element_proxy.rb', line 30

def keyword_supported?(keyword)
  ELEMENT_KEYWORDS.include?(keyword.to_s)
end

.max_id_number_for(name) ⇒ Object



54
55
56
# File 'lib/glimmer/web/element_proxy.rb', line 54

def max_id_number_for(name)
  @max_id_numbers[name] = max_id_numbers[name] || 0
end

.max_id_numbersObject



58
59
60
# File 'lib/glimmer/web/element_proxy.rb', line 58

def max_id_numbers
  @max_id_numbers ||= reset_max_id_numbers!
end

.next_id_number_for(name) ⇒ Object



50
51
52
# File 'lib/glimmer/web/element_proxy.rb', line 50

def next_id_number_for(name)
  @max_id_numbers[name] = max_id_number_for(name) + 1
end

.render_html(element, attributes, content = nil) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/glimmer/web/element_proxy.rb', line 74

def render_html(element, attributes, content = nil)
  attributes = attributes.reduce('') do |output, option_pair|
    attribute, value = option_pair
    value = value.to_s.sub('"', '"').sub("'", ''')
    output += " #{attribute}=\"#{value}\""
  end
  if content.nil?
    "<#{element}#{attributes} />"
  else
    "<#{element}#{attributes}>#{content}</#{element}>"
  end
end

.reset_max_id_numbers!Object



62
63
64
# File 'lib/glimmer/web/element_proxy.rb', line 62

def reset_max_id_numbers!
  @max_id_numbers = {}
end

.underscored_widget_name(widget_proxy) ⇒ Object



66
67
68
# File 'lib/glimmer/web/element_proxy.rb', line 66

def underscored_widget_name(widget_proxy)
  widget_proxy.class.name.split(/::|\./).last.sub(/Proxy$/, '').underscore
end

.unrendered_dom_element(keyword) ⇒ Object



87
88
89
90
# File 'lib/glimmer/web/element_proxy.rb', line 87

def unrendered_dom_element(keyword)
  @unrendered_dom_elements ||= {}
  @unrendered_dom_elements[keyword] ||= Element["<#{keyword} />"]
end

.widget_handling_listenerObject



70
71
72
# File 'lib/glimmer/web/element_proxy.rb', line 70

def widget_handling_listener
  @@widget_handling_listener
end

Instance Method Details

#add_content_on_render(&content_block) ⇒ Object



337
338
339
340
341
342
343
# File 'lib/glimmer/web/element_proxy.rb', line 337

def add_content_on_render(&content_block)
  if rendered?
    content_block.call
  else
    content_on_render_blocks << content_block
  end
end

#add_contents_for_render_blocksObject



679
680
681
682
683
684
685
686
# File 'lib/glimmer/web/element_proxy.rb', line 679

def add_contents_for_render_blocks
  unless skip_content_on_render_blocks?
    content_on_render_blocks.each do |content_block|
      content(&content_block)
    end
  end
  children.each(&:add_contents_for_render_blocks) if bulk_render?
end

#add_css_class(css_class) ⇒ Object



412
413
414
415
416
417
418
# File 'lib/glimmer/web/element_proxy.rb', line 412

def add_css_class(css_class)
  if rendered?
    dom_element.add_class(css_class)
  else
    enqueue_post_render_method_call('class_name=', value)
  end
end

#add_css_classes(css_classes_to_add) ⇒ Object



420
421
422
# File 'lib/glimmer/web/element_proxy.rb', line 420

def add_css_classes(css_classes_to_add)
  css_classes_to_add.each {|css_class| add_css_class(css_class)}
end

#add_text_content(text, on_empty: false) ⇒ Object



321
322
323
324
325
326
327
# File 'lib/glimmer/web/element_proxy.rb', line 321

def add_text_content(text, on_empty: false)
  if rendered?
    dom_element.append(text.to_s) if !on_empty || dom_element.text.to_s.empty?
  else
    enqueue_post_render_method_call('add_text_content', text, on_empty:)
  end
end

#attach(the_parent_dom_element) ⇒ Object



308
309
310
# File 'lib/glimmer/web/element_proxy.rb', line 308

def attach(the_parent_dom_element)
  the_parent_dom_element.append(@dom)
end

#bind_content(*binding_args, &content_block) ⇒ Object

Data-binds the generation of nested content to a model/property (in binding args) consider providing an option to avoid initial rendering without any changes happening



566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
# File 'lib/glimmer/web/element_proxy.rb', line 566

def bind_content(*binding_args, &content_block)
  content_binding_work = proc do |*values|
    # TODO in the future, consider optimizing code by diffing content if that makes sense (e.g. using opal-virtual-dom)
    # To do so, we must avoid generating new content with new unique IDs/Classes and only append the new IDs classes after mounting
    # TODO consider optimizing remove performance by doing clear instead and removing listeners separately
    children.dup.each { |child| child.remove }
    content(bulk_render: true, &content_block)
    if bulk_render? && rendered?
      self.inner_html = children_dom_content
      children.each(&:mark_rendered)
      children.each(&:invoke_post_render_method_calls)
      children.each(&:handle_observation_requests)
      children.each(&:add_contents_for_render_blocks)
      children.each(&:notify_on_render_listeners)
    end
  end
  model_binding_observer = Glimmer::DataBinding::ModelBinding.new(*binding_args)
  content_binding_observer = Glimmer::DataBinding::Observer.proc(&content_binding_work)
  content_binding_observer.observe(model_binding_observer)
  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]
end

#build_dom(layout: true) ⇒ Object



345
346
347
348
# File 'lib/glimmer/web/element_proxy.rb', line 345

def build_dom(layout: true)
  # TODO consider passing parent element instead and having table item include a table cell widget only for opal
  @dom = dom # TODO unify how to build dom for most widgets based on element, id, and name (class)
end

#bulk_render?Boolean

Returns:

  • (Boolean)


146
147
148
# File 'lib/glimmer/web/element_proxy.rb', line 146

def bulk_render?
  options[:bulk_render] != false && (@parent.nil? || @parent.bulk_render?)
end

#can_handle_observation_request?(keyword) ⇒ Boolean

Returns:

  • (Boolean)


480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/glimmer/web/element_proxy.rb', line 480

def can_handle_observation_request?(keyword)
  # TODO sort this out for Opal
  keyword = keyword.to_s
  keyword.start_with?('on')
#         if keyword.start_with?('on_swt_')
#           constant_name = keyword.sub(/^on_swt_/, '')
#           SWTProxy.has_constant?(constant_name)
#         elsif keyword.start_with?('on_')
#           # event = keyword.sub(/^on_/, '')
#           # can_add_listener?(event) || can_handle_drag_observation_request?(keyword) || can_handle_drop_observation_request?(keyword)
#           true # TODO filter by valid listeners only in the future
#         end
end

#children_dom_contentObject



360
361
362
# File 'lib/glimmer/web/element_proxy.rb', line 360

def children_dom_content
  children.map(&:dom).join
end

#class_name=(value) ⇒ Object



402
403
404
405
406
407
408
409
410
# File 'lib/glimmer/web/element_proxy.rb', line 402

def class_name=(value)
  if rendered?
    value = value.is_a?(Array) ? value.join(' ') : value.to_s
    new_class_name = "#{name} #{element_id} #{value}"
    dom_element.prop('className', new_class_name)
  else
    enqueue_post_render_method_call('class_name=', value)
  end
end

#clear_css_classesObject



436
437
438
# File 'lib/glimmer/web/element_proxy.rb', line 436

def clear_css_classes
  css_classes.each {|css_class| remove_css_class(css_class)}
end

#content(bulk_render: false, &block) ⇒ Object



378
379
380
381
382
383
384
# File 'lib/glimmer/web/element_proxy.rb', line 378

def content(bulk_render: false, &block)
  original_bulk_render = options[:bulk_render]
  options[:bulk_render] = bulk_render if rendered?
  return_value = Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Web::ElementExpression.new, keyword, &block)
  options[:bulk_render] = original_bulk_render if rendered?
  return_value
end

#content_on_render_blocksObject



329
330
331
# File 'lib/glimmer/web/element_proxy.rb', line 329

def content_on_render_blocks
  @content_on_render_blocks ||= []
end

#css_classesObject



170
171
172
# File 'lib/glimmer/web/element_proxy.rb', line 170

def css_classes
  dom_element.attr('class').to_s.split if rendered?
end

#data_bind(property, model_binding) ⇒ Object



541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
# File 'lib/glimmer/web/element_proxy.rb', line 541

def data_bind(property, model_binding)
  element_binding_read_translator = value_converters_for_input_type(type)&.[](:model_to_view)
  element_binding_parameters = [self, property, element_binding_read_translator]
  element_binding = DataBinding::ElementBinding.new(*element_binding_parameters)
  #TODO make this options observer dependent and all similar observers in element specific data binding handlers
  element_binding.observe(model_binding)
  element_binding.call(model_binding.evaluate_property)
  data_bindings[element_binding] = model_binding
  unless model_binding.binding_options[:read_only]
    # TODO add guards against nil cases for hash below
    listener_keyword = data_binding_listener_for_element_and_property(keyword, property)
    if listener_keyword
      data_binding_read_listener = lambda do |event|
        view_property_value = send(property)
        element_binding_write_translator = value_converters_for_input_type(type)&.[](:view_to_model)
        converted_view_property_value = element_binding_write_translator&.call(view_property_value, model_binding.evaluate_property) || view_property_value
        model_binding.call(converted_view_property_value)
      end
      handle_observation_request(listener_keyword, data_binding_read_listener)
    end
  end
end

#data_binding_element_keyword_to_property_listener_mapObject



710
711
712
713
714
715
716
717
718
719
720
721
722
723
# File 'lib/glimmer/web/element_proxy.rb', line 710

def data_binding_element_keyword_to_property_listener_map
  @data_binding_element_keyword_to_property_listener_map ||= {
    'input' => {
      'value' => 'oninput',
      'checked' => 'oninput',
    },
    'select' => {
      'value' => 'onchange',
    },
    'textarea' => {
      'value' => 'oninput',
    },
  }
end

#data_binding_listener_for_element_and_property(element_keyword, property) ⇒ Object



702
703
704
# File 'lib/glimmer/web/element_proxy.rb', line 702

def data_binding_listener_for_element_and_property(element_keyword, property)
  data_binding_property_listener_map_for_element(element_keyword)[property]
end

#data_binding_property_listener_map_for_element(element_keyword) ⇒ Object



706
707
708
# File 'lib/glimmer/web/element_proxy.rb', line 706

def data_binding_property_listener_map_for_element(element_keyword)
  data_binding_element_keyword_to_property_listener_map[element_keyword] || {}
end

#data_bindingsObject



529
530
531
# File 'lib/glimmer/web/element_proxy.rb', line 529

def data_bindings
  @data_bindings ||= {}
end

#dialog_ancestorObject



227
228
229
# File 'lib/glimmer/web/element_proxy.rb', line 227

def dialog_ancestor
  parents.detect {|p| p.is_a?(DialogProxy)}
end

#domObject



350
351
352
353
354
355
356
357
358
# File 'lib/glimmer/web/element_proxy.rb', line 350

def dom
  # TODO auto-convert known glimmer attributes like parent to data attributes like data-parent
  # TODO check if we need to avoid rendering content block if no content is available
  @dom ||= begin
    content = args.first.is_a?(String) ? args.first : ''
    content += children_dom_content if bulk_render?
    ElementProxy.render_html(keyword, html_options, content)
  end
end

#dom_elementObject



440
441
442
443
444
445
446
447
448
# File 'lib/glimmer/web/element_proxy.rb', line 440

def dom_element
  if rendered?
    # TODO consider making this pick an element in relation to its parent, allowing unhooked dom elements to be built if needed (unhooked to the visible page dom)
    Document.find(selector)
  else
    # Using a fill-in dom element until self is rendered
    ElementProxy.unrendered_dom_element(keyword)
  end
end

#elementObject

Root element representing widget. Must be overridden by subclasses if different from div



207
208
209
# File 'lib/glimmer/web/element_proxy.rb', line 207

def element
  keyword
end

#element_idObject

element ID is used as a css class to identify the element. It is intentionally not set as the actual HTML element ID to let software engineers specify their own IDs if they wanted



398
399
400
# File 'lib/glimmer/web/element_proxy.rb', line 398

def element_id
  @element_id ||= "element-#{ElementProxy.next_id_number_for(name)}"
end

#enqueue_post_render_method_call(method_name, *args, &block) ⇒ Object



657
658
659
660
# File 'lib/glimmer/web/element_proxy.rb', line 657

def enqueue_post_render_method_call(method_name, *args, &block)
  post_render_method_calls << [method_name, args, block]
  nil
end

#event_handling_suspended?Boolean

Returns:

  • (Boolean)


468
469
470
# File 'lib/glimmer/web/element_proxy.rb', line 468

def event_handling_suspended?
  @event_handling_suspended
end

#event_listener_proxiesObject



456
457
458
# File 'lib/glimmer/web/element_proxy.rb', line 456

def event_listener_proxies
  @event_listener_proxies ||= []
end

#handle_observation_request(keyword, original_event_listener) ⇒ Object



494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
# File 'lib/glimmer/web/element_proxy.rb', line 494

def handle_observation_request(keyword, original_event_listener)
  if rendered?
    listener = ListenerProxy.new(
      element: self,
      selector: selector,
      dom_element: dom_element,
      event_attribute: keyword,
      original_event_listener: original_event_listener,
    )
    listener.register
    listeners_for(keyword) << listener
    listener
  else
    enqueue_post_render_method_call('handle_observation_request', keyword, original_event_listener)
  end
end

#handle_observation_requestsObject



670
671
672
673
674
675
676
677
# File 'lib/glimmer/web/element_proxy.rb', line 670

def handle_observation_requests
  observation_requests&.each do |keyword, event_listener_set|
    event_listener_set.each do |event_listener|
      handle_observation_request(keyword, event_listener)
    end
  end
  children.each(&:handle_observation_requests) if bulk_render?
end

#html_optionsObject



364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/glimmer/web/element_proxy.rb', line 364

def html_options
  body_class = ([name, element_id] + css_classes.to_a).join(' ')
  html_options = options.dup
  GLIMMER_ATTRIBUTES.each do |attribute|
    next unless html_options.include?(attribute)
    data_normalized_attribute = attribute.split('_').join('-')
    html_options["data-#{data_normalized_attribute}"] = html_options.delete(attribute)
  end
  html_options[:class] ||= ''
  html_options[:class] = "#{html_options[:class]} #{body_class}".strip
  html_options['data-turbo'] = 'false' if parent.nil?
  html_options
end

#input_value_convertersObject



729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
# File 'lib/glimmer/web/element_proxy.rb', line 729

def input_value_converters
  @input_value_converters ||= {
    'number' => {
      model_to_view: -> (value, old_value) { value.to_s },
      view_to_model: -> (value, old_value) {
        value.include?('.') ? value.to_f : value.to_i
      },
    },
    'range' => {
      model_to_view: -> (value, old_value) { value.to_s },
      view_to_model: -> (value, old_value) {
        value.include?('.') ? value.to_f : value.to_i
      },
    },
    'datetime-local' => {
      model_to_view: -> (value, old_value) {
        if value.respond_to?(:strftime)
          value.strftime(FORMAT_DATETIME)
        elsif value.is_a?(String) && valid_js_date_string?(value)
          value
        else
          old_value
        end
      },
      view_to_model: -> (value, old_value) {
        if value.to_s.empty?
          nil
        else
          date = Native(`new Date(Date.parse(#{value}))`)
          year = Native.call(date, 'getFullYear')
          month = Native.call(date, 'getMonth') + 1
          day = Native.call(date, 'getDate')
          hour = Native.call(date, 'getHours')
          minute = Native.call(date, 'getMinutes')
          Time.new(year, month, day, hour, minute)
        end
      },
    },
    'date' => {
      model_to_view: -> (value, old_value) {
        if value.respond_to?(:strftime)
          value.strftime(FORMAT_DATE)
        elsif value.is_a?(String) && valid_js_date_string?(value)
          value
        else
          old_value
        end
      },
      view_to_model: -> (value, old_value) {
        if value.to_s.empty?
          nil
        else
          year, month, day = value.split('-')
          if old_value
            Time.new(year, month, day, old_value.hour, old_value.min)
          else
            Time.new(year, month, day)
          end
        end
      },
    },
    'time' => {
      model_to_view: -> (value, old_value) {
        if value.respond_to?(:strftime)
          value.strftime(FORMAT_TIME)
        elsif value.is_a?(String) && valid_js_date_string?(value)
          value
        else
          old_value
        end
      },
      view_to_model: -> (value, old_value) {
        if value.to_s.empty?
          nil
        else
          hour, minute = value.split(':')
          if old_value
            Time.new(old_value.year, old_value.month, old_value.day, hour, minute)
          else
            now = Time.now
            Time.new(now.year, now.month, now.day, hour, minute)
          end
        end
      },
    },
  }
end

#invoke_post_render_method_callsObject



662
663
664
665
666
667
668
# File 'lib/glimmer/web/element_proxy.rb', line 662

def invoke_post_render_method_calls
  return unless rendered?
  post_render_method_calls.each do |method_name, args, block|
    send(method_name, *args, &block)
  end
  children.each(&:invoke_post_render_method_calls) if bulk_render?
end

#listenersObject



472
473
474
# File 'lib/glimmer/web/element_proxy.rb', line 472

def listeners
  @listeners ||= {}
end

#listeners_for(listener_event) ⇒ Object



476
477
478
# File 'lib/glimmer/web/element_proxy.rb', line 476

def listeners_for(listener_event)
  listeners[listener_event.to_s] ||= []
end

#mark_renderedObject



316
317
318
319
# File 'lib/glimmer/web/element_proxy.rb', line 316

def mark_rendered
  @rendered = true
  children.each(&:mark_rendered) if bulk_render?
end

#nameObject



391
392
393
# File 'lib/glimmer/web/element_proxy.rb', line 391

def name
  self.class.name.split('::').last.underscore.sub(/_proxy$/, '').gsub('_', '-')
end

#notify_listeners(event) ⇒ Object



518
519
520
521
522
# File 'lib/glimmer/web/element_proxy.rb', line 518

def notify_listeners(event)
  listeners_for(event).each do |listener|
    listener.original_event_listener.call(EventProxy.new(listener: listener))
  end
end

#notify_on_render_listenersObject



524
525
526
527
# File 'lib/glimmer/web/element_proxy.rb', line 524

def notify_on_render_listeners
  notify_listeners('on_render')
  children.each(&:notify_on_render_listeners) if bulk_render?
end

#observation_request_to_event_mappingObject

Subclasses must override with their own mappings



387
388
389
# File 'lib/glimmer/web/element_proxy.rb', line 387

def observation_request_to_event_mapping
  {}
end

#observation_requestsObject

TODO consider adding a default #dom method implementation for the common case, automatically relying on #element and other methods to build the dom html



452
453
454
# File 'lib/glimmer/web/element_proxy.rb', line 452

def observation_requests
  @observation_requests ||= {}
end

#parent_dom_elementObject



269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/glimmer/web/element_proxy.rb', line 269

def parent_dom_element
  if parent
    parent.dom_element
  else
    options[:parent] ||= 'body'
    the_element = Document.find(options[:parent])
    if the_element.length == 0
      options[:parent] = 'body'
      the_element = Document.find('body')
    end
    the_element
  end
end

#parent_selectorObject



265
266
267
# File 'lib/glimmer/web/element_proxy.rb', line 265

def parent_selector
  @parent&.selector
end

#parentsObject



217
218
219
220
221
222
223
224
225
# File 'lib/glimmer/web/element_proxy.rb', line 217

def parents
  parents_array = []
  current_widget = self
  until current_widget.parent.nil?
    current_widget = current_widget.parent
    parents_array << current_widget
  end
  parents_array
end

#post_add_contentObject

Executes at the closing of a parent widget curly braces after all children/properties have been added/set



166
167
168
# File 'lib/glimmer/web/element_proxy.rb', line 166

def post_add_content
  render if bulk_render? && @parent.nil?
end

#post_initialize_child(child) ⇒ Object

Executes for the parent of a child that just got added



155
156
157
158
# File 'lib/glimmer/web/element_proxy.rb', line 155

def post_initialize_child(child)
  @children << child
  child.render if !bulk_render? && !render_after_create?
end

#post_remove_child(child) ⇒ Object

Executes for the parent of a child that just got removed



161
162
163
# File 'lib/glimmer/web/element_proxy.rb', line 161

def post_remove_child(child)
  @children.delete(child)
end

#post_render_method_callsObject



653
654
655
# File 'lib/glimmer/web/element_proxy.rb', line 653

def post_render_method_calls
  @post_render_method_calls ||= []
end


231
232
233
234
# File 'lib/glimmer/web/element_proxy.rb', line 231

def print
  `window.print()`
  true
end

#property_name_for(method_name) ⇒ Object



688
689
690
691
# File 'lib/glimmer/web/element_proxy.rb', line 688

def property_name_for(method_name)
  attribute_name = method_name.end_with?('=') ? method_name.to_s[0...-1] : method_name.to_s
  PROPERTY_ALIASES[attribute_name] || attribute_name.camelcase
end

#reattach(old_element) ⇒ Object



312
313
314
# File 'lib/glimmer/web/element_proxy.rb', line 312

def reattach(old_element)
  old_element.replace_with(@dom)
end

#removeObject



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/glimmer/web/element_proxy.rb', line 174

def remove
  on_remove_listeners = listeners_for('on_remove').dup
  if rendered?
    @children.dup.each do |child|
      child.remove
    end
    remove_all_listeners
    dom_element.remove
  end
  parent&.post_remove_child(self)
  @removed = true
  on_remove_listeners.each do |listener|
    listener.original_event_listener.call(EventProxy.new(listener: listener))
  end
end

#remove_all_listenersObject



190
191
192
193
194
195
196
197
198
199
# File 'lib/glimmer/web/element_proxy.rb', line 190

def remove_all_listeners
  listeners.each do |event, event_listeners|
    event_listeners.dup.each(&:unregister)
  end
  listeners.clear
  data_bindings.each do |element_binding, model_binding|
    element_binding.unregister_all_observables
  end
  data_bindings.clear
end

#remove_css_class(css_class) ⇒ Object



424
425
426
427
428
429
430
# File 'lib/glimmer/web/element_proxy.rb', line 424

def remove_css_class(css_class)
  if rendered?
    dom_element.remove_class(css_class)
  else
    enqueue_post_render_method_call('class_name=', value)
  end
end

#remove_css_classes(css_classes_to_remove) ⇒ Object



432
433
434
# File 'lib/glimmer/web/element_proxy.rb', line 432

def remove_css_classes(css_classes_to_remove)
  css_classes_to_remove.each {|css_class| remove_css_class(css_class)}
end

#remove_event_listener_proxiesObject



511
512
513
514
515
516
# File 'lib/glimmer/web/element_proxy.rb', line 511

def remove_event_listener_proxies
  event_listener_proxies.each do |event_listener_proxy|
    event_listener_proxy.unregister
  end
  event_listener_proxies.clear
end

#render(parent: nil, custom_parent_dom_element: nil, brand_new: false) ⇒ Object Also known as: rerender



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/glimmer/web/element_proxy.rb', line 283

def render(parent: nil, custom_parent_dom_element: nil, brand_new: false)
  parent_selector = parent
  options[:parent] = parent_selector if !parent_selector.to_s.empty?
  if !options[:parent].to_s.empty?
    # ensure element is orphaned as it is becoming a top-level root element
    @parent&.post_remove_child(self)
    @parent = nil
  end
  the_parent_dom_element = custom_parent_dom_element || parent_dom_element
  brand_new ||= @dom.nil? || !options[:parent].to_s.empty? || (old_element = dom_element).empty?
  build_dom(layout: !custom_parent_dom_element) # TODO handle custom parent layout by passing parent instead of parent dom element
  if brand_new
    attach(the_parent_dom_element)
  else
    reattach(old_element)
  end
  mark_rendered
  invoke_post_render_method_calls if bulk_render?
  handle_observation_requests
  children.each(&:render) if !bulk_render? && !render_after_create?
  add_contents_for_render_blocks
  notify_on_render_listeners
end

#render_after_create?Boolean

Returns:

  • (Boolean)


150
151
152
# File 'lib/glimmer/web/element_proxy.rb', line 150

def render_after_create?
  options[:render] != false && (@parent.nil? || @parent.render_after_create?)
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


588
589
590
591
592
593
594
595
596
597
598
599
# File 'lib/glimmer/web/element_proxy.rb', line 588

def respond_to_missing?(method_name, include_private = false)
  # TODO consider doing more correct checking of availability of properties/methods using native ticks
  property_name = property_name_for(method_name)
  unnormalized_property_name = unnormalized_property_name_for(method_name)
  super(method_name, include_private) ||
    (dom_element && dom_element.length > 0 && Native.call(dom_element, '0').respond_to?(method_name.to_s.camelcase, include_private)) ||
    (dom_element && dom_element.length > 0 && Native.call(dom_element, '0').respond_to?(method_name.to_s, include_private)) ||
    dom_element.respond_to?(method_name, include_private) ||
    (!dom_element.prop(property_name).nil? && !dom_element.prop(property_name).is_a?(Proc)) ||
    (!dom_element.prop(unnormalized_property_name).nil? && !dom_element.prop(unnormalized_property_name).is_a?(Proc)) ||
    method_name.to_s.start_with?('on_')
end

#resume_event_handlingObject



464
465
466
# File 'lib/glimmer/web/element_proxy.rb', line 464

def resume_event_handling
  @event_handling_suspended = false
end

#selectorObject

Subclasses can override with their own selector



202
203
204
# File 'lib/glimmer/web/element_proxy.rb', line 202

def selector
  ".#{element_id}"
end

#shellObject



211
212
213
214
215
# File 'lib/glimmer/web/element_proxy.rb', line 211

def shell
  current_widget = self
  current_widget = current_widget.parent until current_widget.parent.nil?
  current_widget
end

#skip_content_on_render_blocks?Boolean

Returns:

  • (Boolean)


333
334
335
# File 'lib/glimmer/web/element_proxy.rb', line 333

def skip_content_on_render_blocks?
  false
end

#suspend_event_handlingObject



460
461
462
# File 'lib/glimmer/web/element_proxy.rb', line 460

def suspend_event_handling
  @event_handling_suspended = true
end

#swt_widgetObject



697
698
699
700
# File 'lib/glimmer/web/element_proxy.rb', line 697

def swt_widget
  # only added for compatibility/adaptibility with Glimmer DSL for SWT
  self
end

#typeObject



533
534
535
536
537
538
539
# File 'lib/glimmer/web/element_proxy.rb', line 533

def type
  if rendered?
    super
  else
    options[:type] || 'text'
  end
end

#unnormalized_property_name_for(method_name) ⇒ Object



693
694
695
# File 'lib/glimmer/web/element_proxy.rb', line 693

def unnormalized_property_name_for(method_name)
  method_name.end_with?('=') ? method_name.to_s[0...-1] : method_name.to_s
end

#value_converters_for_input_type(input_type) ⇒ Object



725
726
727
# File 'lib/glimmer/web/element_proxy.rb', line 725

def value_converters_for_input_type(input_type)
  input_value_converters[input_type]
end