Module: RubyWasmUi::Template::BuildVdom

Defined in:
lib/ruby_wasm_ui/runtime/template/build_vdom.rb

Class Method Summary collapse

Class Method Details

.build(elements) ⇒ String



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/ruby_wasm_ui/runtime/template/build_vdom.rb', line 10

def build(elements)
  vdom = []
  i = 0
  elements_length = elements[:length].to_i

  while i < elements_length
    element = elements[i]

    # text node
    if element[:nodeType] == JS.global[:Node][:TEXT_NODE]
      text_result = parse_text_node(element)
      vdom << text_result if text_result
      i += 1
      next
    end

    tag_name = element[:tagName].to_s.downcase

    # fragment node (including div elements with data-template attribute)
    if element[:nodeType] == JS.global[:Node][:ELEMENT_NODE] && (tag_name == 'template' || (tag_name == 'div' && has_data_template_attribute?(element)))
      # Check for conditional attributes on template
      if RubyWasmUi::Template::BuildConditionalGroup.has_conditional_attribute?(element)
        # Process conditional group (r-if, r-elsif, r-else)
        conditional_group, next_index = RubyWasmUi::Template::BuildConditionalGroup.build_conditional_group(elements, i)
        vdom << conditional_group
        i = next_index
      else
        vdom << build_fragment(element, tag_name)
        i += 1
      end
      next
    end

    # element node (including components)
    if element[:nodeType] == JS.global[:Node][:ELEMENT_NODE]
      # Check for conditional attributes on all elements (including components)
      if RubyWasmUi::Template::BuildConditionalGroup.has_conditional_attribute?(element)
        # Process conditional group (r-if, r-elsif, r-else)
        conditional_group, next_index = RubyWasmUi::Template::BuildConditionalGroup.build_conditional_group(elements, i)
        vdom << conditional_group
        i = next_index
      # Check for r-for attribute on all elements (including components)
      elsif RubyWasmUi::Template::BuildForGroup.has_for_attribute?(element)
        # Process r-for loop - the result is a map expression that returns an array
        for_loop = RubyWasmUi::Template::BuildForGroup.build_for_loop(element)
        if for_loop && !for_loop.empty?
          # Wrap the map result with splat operator to expand the array
          vdom << "*#{for_loop}"
        end
        i += 1
      else
        # Handle components and regular elements
        if is_component?(tag_name)
          vdom << build_component(element, tag_name)
        else
          vdom << build_element(element, tag_name)
        end
        i += 1
      end
      next
    end

    i += 1
  end

  vdom.compact.join(',')
end

.build_component(element, tag_name, filtered_attributes = nil) ⇒ String

Build component element



198
199
200
201
202
203
204
205
# File 'lib/ruby_wasm_ui/runtime/template/build_vdom.rb', line 198

def build_component(element, tag_name, filtered_attributes = nil)
  attributes_str = parse_attributes(filtered_attributes || element[:attributes].to_a)
  children = build(element[:childNodes])

  # Convert kebab-case to PascalCase for component name
  component_name = tag_name.split('-').map(&:capitalize).join
  "RubyWasmUi::Vdom.h(#{component_name}, {#{attributes_str}}, [#{children}])"
end

.build_element(element, tag_name, filtered_attributes = nil) ⇒ String

Build regular HTML element



212
213
214
215
216
217
# File 'lib/ruby_wasm_ui/runtime/template/build_vdom.rb', line 212

def build_element(element, tag_name, filtered_attributes = nil)
  attributes_str = parse_attributes(filtered_attributes || element[:attributes].to_a)
  children = build(element[:childNodes])

  "RubyWasmUi::Vdom.h('#{tag_name}', {#{attributes_str}}, [#{children}])"
end

.build_fragment(element, tag_name) ⇒ String

Build fragment or div element with data-template attribute



182
183
184
185
186
187
188
189
190
191
# File 'lib/ruby_wasm_ui/runtime/template/build_vdom.rb', line 182

def build_fragment(element, tag_name)
  # div elements with data-template don't have content property, use childNodes directly
  if tag_name == 'template' && element[:content]
    content_nodes = element[:content][:childNodes]
  else
    content_nodes = element[:childNodes]
  end
  children = build(content_nodes)
  "RubyWasmUi::Vdom.h_fragment([#{children}])"
end

.embed_script?(doc) ⇒ Boolean



151
152
153
# File 'lib/ruby_wasm_ui/runtime/template/build_vdom.rb', line 151

def embed_script?(doc)
  doc.match?(/\{.+\}/)
end

.get_embed_script(script) ⇒ String

get value from embed script ex) Count: component.state[:count] -> Count: component.state



159
160
161
# File 'lib/ruby_wasm_ui/runtime/template/build_vdom.rb', line 159

def get_embed_script(script)
  script.gsub(/\{(.+)\}/) { ::Regexp.last_match(1) }
end

.has_data_template_attribute?(element) ⇒ Boolean

Check if element has data-template attribute



166
167
168
169
170
171
172
173
174
175
176
# File 'lib/ruby_wasm_ui/runtime/template/build_vdom.rb', line 166

def has_data_template_attribute?(element)
  return false unless element[:attributes]

  length = element[:attributes][:length].to_i
  length.times do |i|
    attribute = element[:attributes][i]
    key = attribute[:name].to_s
    return true if key == 'data-template'
  end
  false
end

.is_component?(tag_name) ⇒ Boolean



141
142
143
144
145
146
147
# File 'lib/ruby_wasm_ui/runtime/template/build_vdom.rb', line 141

def is_component?(tag_name)
  # Component tags start with letter but exclude standard HTML elements
  return false unless tag_name.match?(/^[a-z]/)

  # Use the standard HTML elements list from Parser module
  !RubyWasmUi::Template::Parser::STANDARD_HTML_ELEMENTS.include?(tag_name)
end

.parse_attributes(attributes) ⇒ String

Parse attributes array



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/ruby_wasm_ui/runtime/template/build_vdom.rb', line 117

def parse_attributes(attributes)
  attributes_str = []

  attributes.each do |attribute|
    key = attribute[:name].to_s
    value = attribute[:value].to_s

    if embed_script?(value)
      # Special handling for 'on' attribute to preserve hash structure
      if key == 'on'
        attributes_str << ":#{key} => #{value}"
      else
        attributes_str << ":#{key} => #{get_embed_script(value)}"
      end
      next
    end

    attributes_str << ":#{key} => '#{value}'"
  end
  attributes_str.join(', ')
end

.parse_text_node(element) ⇒ String

parse text node ex) "test" -> "test" ex) "test state[:count]" -> "test #state[:count]" ex) "test + 1" -> "test #+ 1" ex) "test state[:count] test" -> "test #state[:count] test" ex) "test state[:count] test state[:count] test" -> "test #state[:count] test #state[:count] test"



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/ruby_wasm_ui/runtime/template/build_vdom.rb', line 86

def parse_text_node(element)
  value = element[:nodeValue].to_s.chomp.strip

  return nil if value.empty?

  # Split the text by embedded script pattern and process each part
  # Regular expression explanation:
  # (        : Start capture group (this ensures the pattern itself is included in the result)
  #  \{      : Match an opening curly brace (escaped because { is special in regex)
  #  [^}]+   : Match one or more characters that are not a closing curly brace
  #  \}      : Match a closing curly brace (escaped because } is special in regex)
  # )        : End capture group
  # Example:
  #   Input:  "hello {state[:count]} world"
  #   Output: ["hello ", "{state[:count]}", " world"]
  parts = value.split(/(\{[^}]+\})/)
  processed_parts = parts.map do |part|
    if embed_script?(part)
      "\#{#{get_embed_script(part)}}"
    else
      part
    end
  end

  # Join all parts and wrap in double quotes
  %("#{processed_parts.join}")
end