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

HTML_ELEMENT_KEYWORDS =
[
  "a", "abbr", "acronym", "address", "applet", "article", "aside", "audio",
  "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",
]
SVG_ELEMENT_KEYWORDS =
[
  "animate", "animateMotion", "animateTransform", "circle", "clipPath", "defs", "desc", "ellipse",
  "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix",
  "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA",
  "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode",
  "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile",
  "feTurbulence", "filter", "foreignObject", "g", "image", "line", "linearGradient", "marker",
  "mask", "metadata", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect",
  "set", "stop", "svg", "switch", "symbol", "text", "textPath", "tspan", "use", "view",
].map(&:downcase)
ELEMENT_KEYWORDS =
HTML_ELEMENT_KEYWORDS + SVG_ELEMENT_KEYWORDS
HTML_VOID_ELEMENT_KEYWORDS =
%w[
  area base br col embed hr img input link meta param source track wbr
  keygen menuitem frame
]
HTML_ELEMENT_SPECIAL_ATTRIBUTES =

title is a special attribute because it matches an element tag name (needs special treatment)

['title']
HTML_ELEMENT_GLOBAL_BOOLEAN_ATTRIBUTES =
['hidden', 'inert', 'contenteditable', 'spellcheck']
HTML_ELEMENT_BOOLEAN_ATTRIBUTES =
{
  'audio' => ['autoplay', 'controls', 'loop', 'muted'],
  'button' => ['autofocus', 'disabled', 'formnovalidate'],
  'details' => ['open'],
  'dialog' => ['open'],
  'fieldset' => ['disabled'],
  'form' => ['novalidate'],
  'iframe' => ['allowfullscreen'],
  'img' => ['ismap'],
  'input' => ['alpha', 'autofocus', 'checked', 'disabled', 'formnovalidate', 'multiple', 'readonly', 'required'],
  'ol' => ['reversed'],
  'optgroup' => ['disabled'],
  'option' => ['disabled', 'selected'],
  'script' => ['async', 'defer', 'nomodule'],
  'select' => ['autofocus', 'disabled', 'multiple', 'required'],
  'template' => ['shadowrootclonable', 'shadowrootdelegatesfocus', 'shadowrootserializable', 'shadowrootcustomelementregistry'],
  'textarea' => ['autofocus', 'disabled', 'readonly', 'required'],
  'track' => ['default'],
  'video' => ['autoplay', 'controls', 'loop', 'muted', 'playsinline']
}
GLIMMER_ATTRIBUTES =
[:parent]
PROPERTY_ALIASES =
{
  'inner_html' => 'innerHTML',
  'outer_html' => 'outerHTML',
}
CSS_CLASS_GLIMMER =
'glimmer'
FORMAT_DATETIME =

TODO ensure using when setting value on date/time fields without data-binding

'%Y-%m-%dT%H:%M'
FORMAT_DATE =

TODO ensure using when setting value on date/time fields without data-binding

'%Y-%m-%d'
FORMAT_TIME =

TODO ensure using when setting value on date/time fields without data-binding

'%H:%M'
FORMAT_MONTH =

TODO ensure using when setting value on date/time fields without data-binding

'%Y-%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}$/
REGEX_FORMAT_MONTH =
/^\d{4}-\d{2}$/
REGEX_STYLE_SUB_PROPERTY =
/^(style)_(.*)$/
REGEX_CLASS_NAME_SUB_PROPERTY =
/^(class_name)_(.*)$/

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.



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/glimmer/web/element_proxy.rb', line 183

def initialize(keyword, parent, args, block)
  @keyword = keyword
  @parent = parent.is_a?(Glimmer::Web::Component) ? parent.markup_root : parent
  # @parent_component is the component that is the parent of an external element (not in markup {...} block) that is nested underneath (e.g. address_form { this_element })
  @parent_component = parent if parent.is_a?(Glimmer::Web::Component)
  if Component.interpretation_stack.last&.markup_root.nil?
    # @component is the one tied to this component-internal element as the markup root
    @component = Component.interpretation_stack.last
    @component&.instance_variable_set("@markup_root", self)
  else
    # @ancestor_component is the one tied to this component-internal element as an ancestor
    @ancestor_component = Component.interpretation_stack.last
  end
  extract_options_from_args(args) # sets @options
  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
  @slot = options['slot'] || options[:slot]
  @slot = @slot.to_sym if @slot
  validate_slot!
  ancestor_component.slot_elements[@slot] = self if @slot && ancestor_component
  @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



891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
# File 'lib/glimmer/web/element_proxy.rb', line 891

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.end_with?('=') && HTML_ELEMENT_SPECIAL_ATTRIBUTES.include?(property_name)
    if rendered?
      dom_element.attr(property_name, *args)
    else
      enqueue_post_render_method_call(method_name, *args, &block)
    end
  elsif method_name.to_s.start_with?('class_name_')
    property, sub_property = method_name.to_s.match(REGEX_CLASS_NAME_SUB_PROPERTY).to_a.drop(1)
    if args.empty?
      class_name_included(sub_property)
    else
      class_name_included(sub_property, args.first)
    end
  elsif method_name.to_s.start_with?('style_')
    property, sub_property = method_name.to_s.match(REGEX_STYLE_SUB_PROPERTY).to_a.drop(1)
    sub_property = sub_property.gsub('_', '-')
    if args.empty?
      style_property(sub_property)
    else
      style_property(sub_property, args.first) # TODO in the future, support more sophisticated forms of CSS sub-property values, like [1.px, :solid, :black] for border
    end
  elsif 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

#ancestor_component ⇒ Object (readonly)

Returns the value of attribute ancestor_component.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def ancestor_component
  @ancestor_component
end

#args ⇒ Object (readonly)

Returns the value of attribute args.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def args
  @args
end

#background ⇒ Object

Returns the value of attribute background.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def background
  @background
end

#children ⇒ Object (readonly)

Returns the value of attribute children.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def children
  @children
end

#component ⇒ Object (readonly)

Returns the value of attribute component.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def component
  @component
end

#enabled ⇒ Object

Returns the value of attribute enabled.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def enabled
  @enabled
end

#foreground ⇒ Object

Returns the value of attribute foreground.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def foreground
  @foreground
end

#insert_index ⇒ Object

to be used when add_child_mutation is insert_at



179
180
181
# File 'lib/glimmer/web/element_proxy.rb', line 179

def insert_index
  @insert_index
end

#keyword ⇒ Object (readonly)

Returns the value of attribute keyword.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def keyword
  @keyword
end

#options ⇒ Object (readonly)

Returns the value of attribute options.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def options
  @options
end

#parent ⇒ Object (readonly)

Returns the value of attribute parent.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def parent
  @parent
end

#parent_component ⇒ Object (readonly)

Returns the value of attribute parent_component.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def parent_component
  @parent_component
end

#removed ⇒ Object (readonly) Also known as: removed?

Returns the value of attribute removed.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def removed
  @removed
end

#rendered ⇒ Object (readonly) Also known as: rendered?

Returns the value of attribute rendered.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def rendered
  @rendered
end

#slot ⇒ Object (readonly)

Returns the value of attribute slot.



178
179
180
# File 'lib/glimmer/web/element_proxy.rb', line 178

def slot
  @slot
end

Class Method Details

.element_boolean_attribute?(keyword, attribute) ⇒ Boolean

Returns:

  • (Boolean)


46
47
48
49
50
51
# File 'lib/glimmer/web/element_proxy.rb', line 46

def element_boolean_attribute?(keyword, attribute)
  attribute = attribute.to_s
  keyword = keyword.to_s
  HTML_ELEMENT_GLOBAL_BOOLEAN_ATTRIBUTES.include?(attribute) ||
    HTML_ELEMENT_BOOLEAN_ATTRIBUTES[keyword]&.include?(attribute)
end

.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



39
40
41
42
43
44
# File 'lib/glimmer/web/element_proxy.rb', line 39

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



33
34
35
# File 'lib/glimmer/web/element_proxy.rb', line 33

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

.html_keyword_supported?(keyword) ⇒ Boolean

Returns:

  • (Boolean)


15
16
17
# File 'lib/glimmer/web/element_proxy.rb', line 15

def html_keyword_supported?(keyword)
  HTML_ELEMENT_KEYWORDS.include?(keyword.to_s)
end

.html_void_keyword?(keyword) ⇒ Boolean Also known as: void_keyword?

an HTML element that doesn't need a closing tag (officially, it's wrong to call it self-closing; it's called void instead)

Returns:

  • (Boolean)


25
26
27
# File 'lib/glimmer/web/element_proxy.rb', line 25

def html_void_keyword?(keyword)
  HTML_VOID_ELEMENT_KEYWORDS.include?(keyword.to_s)
end

.keyword_supported?(keyword) ⇒ Boolean

Returns:

  • (Boolean)


9
10
11
12
13
# File 'lib/glimmer/web/element_proxy.rb', line 9

def keyword_supported?(keyword)
  keyword = keyword.to_s
  ELEMENT_KEYWORDS.include?(keyword) ||
    FormattingElementProxy::FORMATTING_ELEMENT_KEYWORDS.include?(keyword)
end

.max_id_number_for(name) ⇒ Object



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

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

.max_id_numbers ⇒ Object



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

def max_id_numbers
  @max_id_numbers ||= reset_max_id_numbers!
end

.next_id_number_for(name) ⇒ Object



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

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

.render_html(element, attributes:, boolean_attributes: [], content: nil) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/glimmer/web/element_proxy.rb', line 77

def render_html(element, attributes:, boolean_attributes: [], content: nil)
  attributes_string = attributes.reduce('') do |output, option_pair|
    attribute, value = option_pair
    value = value.to_s.sub('"', '"').sub("'", ''')
    output += " #{attribute}=\"#{value}\""
  end
  boolean_attributes_string = boolean_attributes.to_a.map(&:to_s).join(' ')
  if html_void_keyword?(element)
    "<#{element}#{attributes_string} #{boolean_attributes_string}>"
  else
    "<#{element}#{attributes_string} #{boolean_attributes_string}>#{content}</#{element}>"
  end
end

.reset_max_id_numbers! ⇒ Object



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

def reset_max_id_numbers!
  @max_id_numbers = {}
end

.svg_keyword_supported?(keyword) ⇒ Boolean

Returns:

  • (Boolean)


19
20
21
# File 'lib/glimmer/web/element_proxy.rb', line 19

def svg_keyword_supported?(keyword)
  SVG_ELEMENT_KEYWORDS.include?(keyword.to_s)
end

.underscored_widget_name(widget_proxy) ⇒ Object



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

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

.unrendered_dom_element(keyword) ⇒ Object



91
92
93
94
# File 'lib/glimmer/web/element_proxy.rb', line 91

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

.widget_handling_listener ⇒ Object



73
74
75
# File 'lib/glimmer/web/element_proxy.rb', line 73

def widget_handling_listener
  @@widget_handling_listener
end

Instance Method Details

#add_child_mutation ⇒ Object



578
579
580
# File 'lib/glimmer/web/element_proxy.rb', line 578

def add_child_mutation
  @add_child_mutation || :append
end

#add_child_mutation=(value) ⇒ Object



582
583
584
# File 'lib/glimmer/web/element_proxy.rb', line 582

def add_child_mutation=(value)
  @add_child_mutation = value
end

#add_content_on_render(&content_block) ⇒ Object



520
521
522
523
524
525
526
# File 'lib/glimmer/web/element_proxy.rb', line 520

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_blocks ⇒ Object



990
991
992
993
994
995
996
997
# File 'lib/glimmer/web/element_proxy.rb', line 990

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



644
645
646
647
648
649
650
# File 'lib/glimmer/web/element_proxy.rb', line 644

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

#add_css_classes(css_classes_to_add) ⇒ Object



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

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



504
505
506
507
508
509
510
# File 'lib/glimmer/web/element_proxy.rb', line 504

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

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



611
612
613
614
615
# File 'lib/glimmer/web/element_proxy.rb', line 611

def after(bulk_render: false, &block)
  return if parent.nil?
  parent_insert_index = parent.children.index(self) + 1
  parent.content(bulk_render:, add_child_mutation: :insert_at, insert_index: parent_insert_index, &block)
end

#attach(the_parent_dom_element, add_child_mutation: :append, insert_index: nil) ⇒ Object



469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'lib/glimmer/web/element_proxy.rb', line 469

def attach(the_parent_dom_element, add_child_mutation: :append, insert_index: nil)
  case add_child_mutation
  when :prepend
    the_parent_dom_element.prepend(@dom)
  when :insert_at
    if insert_index == the_parent_dom_element.children.size
      the_parent_dom_element.append(@dom)
    else
      the_parent_dom_element.children.eq(insert_index).before(@dom)
    end
  else # append
    the_parent_dom_element.append(@dom)
  end
end

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



605
606
607
608
609
# File 'lib/glimmer/web/element_proxy.rb', line 605

def before(bulk_render: false, &block)
  return if parent.nil?
  parent_insert_index = parent.children.index(self)
  parent.content(bulk_render:, add_child_mutation: :insert_at, insert_index: parent_insert_index, &block)
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



850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
# File 'lib/glimmer/web/element_proxy.rb', line 850

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_registrations << 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



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

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)


217
218
219
# File 'lib/glimmer/web/element_proxy.rb', line 217

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

#can_handle_observation_request?(keyword) ⇒ Boolean

Returns:

  • (Boolean)


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

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_content ⇒ Object



559
560
561
# File 'lib/glimmer/web/element_proxy.rb', line 559

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

#class_name=(*values) ⇒ Object Also known as: classes=



633
634
635
636
637
638
639
640
641
# File 'lib/glimmer/web/element_proxy.rb', line 633

def class_name=(*values)
  if rendered?
    values = normalize_class_name(values).split(' ')
    new_class_name = (base_css_classes + values).uniq.compact.join(' ')
    dom_element.prop('className', new_class_name)
  else
    enqueue_post_render_method_call('class_name=', *values)
  end
end

#class_name_included(one_class_name, value = nil) ⇒ Object



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/glimmer/web/element_proxy.rb', line 377

def class_name_included(one_class_name, value = nil)
  if rendered?
    if value.nil?
      class_name.include?(one_class_name)
    else
      if value
        add_css_class(one_class_name)
      else
        remove_css_class(one_class_name)
      end
    end
  else
    enqueue_args = ['class_name_included', one_class_name]
    enqueue_args << value unless value.nil?
    enqueue_post_render_method_call(*enqueue_args)
  end
end

#clear_css_classes ⇒ Object



668
669
670
# File 'lib/glimmer/web/element_proxy.rb', line 668

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

#content(bulk_render: false, add_child_mutation: :append, insert_index: nil, &block) ⇒ Object Also known as: append



586
587
588
589
590
591
592
# File 'lib/glimmer/web/element_proxy.rb', line 586

def content(bulk_render: false, add_child_mutation: :append, insert_index: nil, &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, add_child_mutation:, insert_index:, &block)
  options[:bulk_render] = original_bulk_render if rendered?
  return_value
end

#content_binding_observer_registrations ⇒ Object



872
873
874
# File 'lib/glimmer/web/element_proxy.rb', line 872

def content_binding_observer_registrations
  @content_binding_observer_registrations ||= []
end

#content_on_render_blocks ⇒ Object



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

def content_on_render_blocks
  @content_on_render_blocks ||= []
end

#css_classes ⇒ Object



248
249
250
# File 'lib/glimmer/web/element_proxy.rb', line 248

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

#data_bind(property, model_binding) ⇒ Object



822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
# File 'lib/glimmer/web/element_proxy.rb', line 822

def data_bind(property, model_binding)
  element_binding_read_translator = value_converters_for_input_type(type)&.[](:model_to_view)
  element_binding = DataBinding::ElementBinding.new(self, property, translator: element_binding_read_translator)
  #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
  if !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)
        is_date_time = ['date', 'time', 'datetime-local', 'month'].include?(options['type'])
        converted_view_property_value = element_binding_write_translator&.call(view_property_value, model_binding.evaluate_property)
        if !is_date_time
          converted_view_property_value ||= view_property_value
        end
        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_map ⇒ Object



1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
# File 'lib/glimmer/web/element_proxy.rb', line 1021

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



1013
1014
1015
# File 'lib/glimmer/web/element_proxy.rb', line 1013

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



1017
1018
1019
# File 'lib/glimmer/web/element_proxy.rb', line 1017

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

#data_bindings ⇒ Object



765
766
767
# File 'lib/glimmer/web/element_proxy.rb', line 765

def data_bindings
  @data_bindings ||= {}
end

#dom ⇒ Object Also known as: to_s



533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
# File 'lib/glimmer/web/element_proxy.rb', line 533

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
    if args.first.is_a?(String)
      if !Glimmer::Web::ElementProxy.element_boolean_attribute?(keyword, args.first)
        content = args.first
        remaining_args = args[1, args.size - 1]
      else
        content = ''
        remaining_args = args
      end
    else
      content = ''
      remaining_args = args
    end
    remaining_args = remaining_args.to_a
    content += children_dom_content if bulk_render?
    remaining_args = remaining_args[0...-1] if remaining_args.last.is_a?(Hash)
    boolean_attributes = remaining_args
    ElementProxy.render_html(keyword, attributes: html_options, boolean_attributes:, content:)
  end
end

#dom_element ⇒ Object



672
673
674
675
676
677
678
679
680
# File 'lib/glimmer/web/element_proxy.rb', line 672

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

#element ⇒ Object

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



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

def element
  keyword
end

#element_id ⇒ Object

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



629
630
631
# File 'lib/glimmer/web/element_proxy.rb', line 629

def element_id
  @element_id ||= "#{CSS_CLASS_GLIMMER}-#{name}-#{ElementProxy.next_id_number_for(self.class.to_s)}"
end

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



968
969
970
971
# File 'lib/glimmer/web/element_proxy.rb', line 968

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)


700
701
702
# File 'lib/glimmer/web/element_proxy.rb', line 700

def event_handling_suspended?
  @event_handling_suspended
end

#event_listener_proxies ⇒ Object



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

def event_listener_proxies
  @event_listener_proxies ||= []
end

#find_ancestor(include_self: false, &condition) ⇒ Object



333
334
335
336
337
338
339
340
341
# File 'lib/glimmer/web/element_proxy.rb', line 333

def find_ancestor(include_self: false, &condition)
  current_element_proxy = self
  return current_element_proxy if include_self && condition.call(current_element_proxy)
  until current_element_proxy.parent.nil?
    current_element_proxy = current_element_proxy.parent
    return current_element_proxy if condition.call(current_element_proxy)
  end
  nil
end

#focused ⇒ Object



488
489
490
491
# File 'lib/glimmer/web/element_proxy.rb', line 488

def focused
  @focused = `#{dom_element}[0] == document.activeElement`
  @focused
end

#focused=(value) ⇒ Object



493
494
495
496
497
# File 'lib/glimmer/web/element_proxy.rb', line 493

def focused=(value)
  @focused = value
  focus if @focused
  @focused
end

#handle_observation_request(keyword, original_event_listener) ⇒ Object



726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
# File 'lib/glimmer/web/element_proxy.rb', line 726

def handle_observation_request(keyword, original_event_listener)
  if rendered?
    if keyword.start_with?('on_') && !['on_render', 'on_remove'].include?(keyword.to_s)
      (ancestor_component || component)&.handle_observation_request(keyword, original_event_listener)
    else
      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
    end
  else
    enqueue_post_render_method_call('handle_observation_request', keyword, original_event_listener)
  end
end

#handle_observation_requests ⇒ Object



981
982
983
984
985
986
987
988
# File 'lib/glimmer/web/element_proxy.rb', line 981

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? ⇒ Boolean

Returns:

  • (Boolean)


252
253
254
# File 'lib/glimmer/web/element_proxy.rb', line 252

def html?
  ElementProxy.html_keyword_supported?(keyword)
end

#html_options ⇒ Object



563
564
565
566
567
568
569
570
571
572
573
574
575
576
# File 'lib/glimmer/web/element_proxy.rb', line 563

def html_options
  body_class = (base_css_classes + css_classes.to_a).uniq.compact.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] = "#{normalize_class_name(html_options.delete('class') || html_options.delete(:class))} #{body_class}".strip
  html_options[:style] = normalize_style(html_options.delete('style') || html_options.delete(:style))
  html_options['data-turbo'] = 'false' if parent.nil?
  html_options
end

#html_void? ⇒ Boolean Also known as: void?

Returns:

  • (Boolean)


260
261
262
# File 'lib/glimmer/web/element_proxy.rb', line 260

def html_void?
  ElementProxy.html_void_keyword?(keyword)
end

#input_value_converters ⇒ Object



1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
# File 'lib/glimmer/web/element_proxy.rb', line 1040

def input_value_converters
  @input_value_converters ||= {
    'number' => {
      model_to_view: -> (value, old_value) { value.to_s },
      view_to_model: -> (value, old_value) {
        value.to_s.include?('.') ? value.to_f : value.to_i
      },
    },
    'range' => {
      model_to_view: -> (value, old_value) { value.to_s },
      view_to_model: -> (value, old_value) {
        value.to_s.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
        elsif value.to_s.empty?
          ''
        else # keep old value if a bad model attribute value was set
          old_value&.strftime(FORMAT_DATETIME)
        end
      },
      view_to_model: -> (value, old_value) {
        if value.to_s.empty?
          nil
        elsif value.is_a?(String)
          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)
        else
          value
        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
        elsif value.to_s.empty?
          ''
        else # keep old value if a bad model attribute value was set
          old_value&.strftime(FORMAT_DATE)
        end
      },
      view_to_model: -> (value, old_value) {
        if value.to_s.empty?
          nil
        elsif value.is_a?(String)
          year, month, day = value.split('-')
          if !old_value.to_s.empty?
            Time.new(year, month, day, old_value.hour, old_value.min)
          else
            Time.new(year, month, day)
          end
        else
          value
        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
        elsif value.to_s.empty?
          ''
        else # keep old value if a bad model attribute value was set
          old_value&.strftime(FORMAT_TIME)
        end
      },
      view_to_model: -> (value, old_value) {
        if value.to_s.empty?
          nil
        elsif value.is_a?(String)
          hour, minute = value.split(':')
          if !old_value.to_s.empty?
            Time.new(old_value.year, old_value.month, old_value.day, hour, minute)
          elsif value.is_a?(String)
            now = Time.now
            Time.new(now.year, now.month, now.day, hour, minute)
          end
        else
          value
        end
      },
    },
    'month' => {
      model_to_view: -> (value, old_value) {
        if value.respond_to?(:strftime)
          value.strftime(FORMAT_MONTH)
        elsif value.is_a?(String) && valid_js_date_string?(value)
          value
        elsif value.to_s.empty?
          ''
        else # keep old value if a bad model attribute value was set
          old_value&.strftime(FORMAT_MONTH)
        end
      },
      view_to_model: -> (value, old_value) {
        if value.to_s.empty?
          nil
        elsif value.is_a?(String)
          year, month = value.split('-')
          if !old_value.to_s.empty?
            Time.new(year, month, old_value.day, old_value.hour, old_value.min)
          else
            Time.new(year, month)
          end
        else
          value
        end
      },
    },
  }
end

#insert_at(index, bulk_render: false, &block) ⇒ Object Also known as: insert



599
600
601
602
# File 'lib/glimmer/web/element_proxy.rb', line 599

def insert_at(index, bulk_render: false, &block)
  index = index.to_i if index.is_a?(String) && index.match(/^\d+$/)
  content(bulk_render:, add_child_mutation: :insert_at, insert_index: index, &block)
end

#inspect(basic: false) ⇒ Object



1167
1168
1169
1170
1171
1172
1173
1174
1175
# File 'lib/glimmer/web/element_proxy.rb', line 1167

def inspect(basic: false)
  if basic
    attributes = {keyword:, args:}
  else
    parent_inspect = parent.is_a?(Glimmer::Web::ElementProxy) || parent.is_a?(Glimmer::Web::Component) ? parent.inspect(basic: true) : parent.inspect
    attributes = {keyword:, args:, parent: parent_inspect}
  end
  "#<#{self.class}:0x#{object_id.to_s(16)} #{keyword}##{element_id} #{attributes}>"
end

#invoke_post_render_method_calls ⇒ Object



973
974
975
976
977
978
979
# File 'lib/glimmer/web/element_proxy.rb', line 973

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

#listeners ⇒ Object



704
705
706
# File 'lib/glimmer/web/element_proxy.rb', line 704

def listeners
  @listeners ||= {}
end

#listeners_for(listener_event) ⇒ Object



708
709
710
# File 'lib/glimmer/web/element_proxy.rb', line 708

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

#mark_rendered ⇒ Object



499
500
501
502
# File 'lib/glimmer/web/element_proxy.rb', line 499

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

#name ⇒ Object



622
623
624
# File 'lib/glimmer/web/element_proxy.rb', line 622

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

#notify_listeners(event) ⇒ Object



754
755
756
757
758
# File 'lib/glimmer/web/element_proxy.rb', line 754

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

#notify_on_render_listeners ⇒ Object



760
761
762
763
# File 'lib/glimmer/web/element_proxy.rb', line 760

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

#observation_request_to_event_mapping ⇒ Object

Subclasses must override with their own mappings



618
619
620
# File 'lib/glimmer/web/element_proxy.rb', line 618

def observation_request_to_event_mapping
  {}
end

#observation_requests ⇒ Object

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



684
685
686
# File 'lib/glimmer/web/element_proxy.rb', line 684

def observation_requests
  @observation_requests ||= {}
end

#parent_dom_element ⇒ Object



430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/glimmer/web/element_proxy.rb', line 430

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_selector ⇒ Object



426
427
428
# File 'lib/glimmer/web/element_proxy.rb', line 426

def parent_selector
  @parent&.selector
end

#parents ⇒ Object Also known as: ancestors



317
318
319
320
321
322
323
324
325
# File 'lib/glimmer/web/element_proxy.rb', line 317

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_content ⇒ Object

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



244
245
246
# File 'lib/glimmer/web/element_proxy.rb', line 244

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

#post_initialize_child(child) ⇒ Object

Executes for the parent of a child that just got added



226
227
228
229
230
231
232
233
234
235
236
# File 'lib/glimmer/web/element_proxy.rb', line 226

def post_initialize_child(child)
  case self.add_child_mutation # TODO this is where the problem is.. It's an append instead of an insert_at
  when :prepend
    @children.prepend(child)
  when :insert_at
    @children.insert(self.insert_index, child)
  else # append
    @children << child
  end
  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



239
240
241
# File 'lib/glimmer/web/element_proxy.rb', line 239

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

#post_render_method_calls ⇒ Object



964
965
966
# File 'lib/glimmer/web/element_proxy.rb', line 964

def post_render_method_calls
  @post_render_method_calls ||= []
end

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



595
596
597
# File 'lib/glimmer/web/element_proxy.rb', line 595

def prepend(bulk_render: false, &block)
  content(bulk_render:, add_child_mutation: :prepend, &block)
end


343
344
345
346
# File 'lib/glimmer/web/element_proxy.rb', line 343

def print
  `window.print()`
  true
end

#property_name_for(method_name) ⇒ Object



999
1000
1001
1002
# File 'lib/glimmer/web/element_proxy.rb', line 999

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



484
485
486
# File 'lib/glimmer/web/element_proxy.rb', line 484

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

#remove ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/glimmer/web/element_proxy.rb', line 265

def remove
  return if @removed
  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)
  if component
    Glimmer::Web::Component.remove_component(component)
    component.remove_style_block
  end
  @removed = true
  on_remove_listeners.each do |listener|
    listener.original_event_listener.call(EventProxy.new(listener: listener))
  end
end

#remove_all_listeners ⇒ Object



286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/glimmer/web/element_proxy.rb', line 286

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
  content_binding_observer_registrations.each do |observer_registration|
    observer_registration.unregister
  end
  content_binding_observer_registrations.clear
end

#remove_css_class(css_class) ⇒ Object



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

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

#remove_css_classes(css_classes_to_remove) ⇒ Object



664
665
666
# File 'lib/glimmer/web/element_proxy.rb', line 664

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

#remove_event_listener_proxies ⇒ Object



747
748
749
750
751
752
# File 'lib/glimmer/web/element_proxy.rb', line 747

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



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/glimmer/web/element_proxy.rb', line 444

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, add_child_mutation: self.parent&.add_child_mutation, insert_index: self.parent&.insert_index)
  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)


221
222
223
# File 'lib/glimmer/web/element_proxy.rb', line 221

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)


876
877
878
879
880
881
882
883
884
885
886
887
888
889
# File 'lib/glimmer/web/element_proxy.rb', line 876

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_') ||
    method_name.to_s.start_with?('style_') ||
    method_name.to_s.start_with?('class_name_')
end

#resume_event_handling ⇒ Object



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

def resume_event_handling
  @event_handling_suspended = false
end

#root_parent ⇒ Object Also known as: root_ancesor



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

def root_parent
  parents.last
end

#selector ⇒ Object

Subclasses can override with their own selector



302
303
304
# File 'lib/glimmer/web/element_proxy.rb', line 302

def selector
  ".#{element_id}"
end

#shell ⇒ Object



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

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)


516
517
518
# File 'lib/glimmer/web/element_proxy.rb', line 516

def skip_content_on_render_blocks?
  false
end

#style(value = null) ⇒ Object Also known as: style=



395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/glimmer/web/element_proxy.rb', line 395

def style(value = null)
  if rendered?
    if value.nil?
      dom_element.attr('style')
    else
      value = normalize_style(value)
      dom_element.attr('style', value)
    end
  else
    enqueue_args = ['style']
    enqueue_args << value unless value.nil?
    enqueue_post_render_method_call(*enqueue_args)
  end
end

#style_property(property, value = nil) ⇒ Object



411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/glimmer/web/element_proxy.rb', line 411

def style_property(property, value = nil)
  if rendered?
    property = property.to_s.gsub('_', '-')
    if value.nil?
      dom_element.css(property)
    else
      dom_element.css(property, value)
    end
  else
    enqueue_args = ['style_property', property]
    enqueue_args << value unless value.nil?
    enqueue_post_render_method_call(*enqueue_args)
  end
end

#suspend_event_handling ⇒ Object



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

def suspend_event_handling
  @event_handling_suspended = true
end

#svg? ⇒ Boolean

Returns:

  • (Boolean)


256
257
258
# File 'lib/glimmer/web/element_proxy.rb', line 256

def svg?
  ElementProxy.svg_keyword_supported?(keyword)
end

#swt_widget ⇒ Object



1008
1009
1010
1011
# File 'lib/glimmer/web/element_proxy.rb', line 1008

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

#type ⇒ Object



769
770
771
772
773
774
775
# File 'lib/glimmer/web/element_proxy.rb', line 769

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

#unnormalized_property_name_for(method_name) ⇒ Object



1004
1005
1006
# File 'lib/glimmer/web/element_proxy.rb', line 1004

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

#value(raw: false) ⇒ Object



777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
# File 'lib/glimmer/web/element_proxy.rb', line 777

def value(raw: false)
  if rendered?
    view_to_model_converter = value_converters_for_input_type(type)&.[](:view_to_model)
    view_value = super()
    if !raw && view_to_model_converter
      view_to_model_converter.call(view_value, nil)
    else
      view_value
    end
  else # this is the value passed by the programmer as per its format in the model (date/time or string)
    model_to_view_converter = value_converters_for_input_type(type)&.[](:model_to_view)
    if !raw && model_to_view_converter
      model_to_view_converter.call(options[:value], nil)
    else
      options[:value]
    end
  end
end

#value=(new_value) ⇒ Object



796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
# File 'lib/glimmer/web/element_proxy.rb', line 796

def value=(new_value)
  model_to_view_converter = value_converters_for_input_type(type)&.[](:model_to_view)
  if rendered?
    if model_to_view_converter
      super(model_to_view_converter.call(new_value, nil))
    else
      super(new_value)
    end
  else
    the_value = if model_to_view_converter
      model_to_view_converter.call(super(new_value), nil)
    else
      new_value
    end
    if keyword == 'textarea'
      if args.first.is_a?(String)
        args[0] = the_value
      else
        args.prepend(the_value)
      end
    else
      options[:value] = the_value
    end
  end
end

#value_converters_for_input_type(input_type) ⇒ Object



1036
1037
1038
# File 'lib/glimmer/web/element_proxy.rb', line 1036

def value_converters_for_input_type(input_type)
  input_value_converters[input_type]
end