DOMComponent.update_dom_children()   F
last analyzed

Complexity

Conditions 14

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
c 6
b 0
f 0
dl 0
loc 30
rs 2.7581
cc 14

How to fix   Complexity   

Complexity

Complex classes like DOMComponent.update_dom_children() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
require 'hyalite/multi_children'
2
require 'hyalite/dom_property_operations'
3
require 'hyalite/internal_component'
4
require 'hyalite/input_wrapper'
5
6
module Hyalite
7
  class DOMComponent
8
    include MultiChildren
9
    include InternalComponent
10
11
    attr_reader :root_node_id
12
13
    def initialize(element)
14
      @element = element
15
      @tag = @element.type.downcase
16
      @input_wrapper = InputWrapper.new(self)
17
    end
18
19
    def current_element
20
      @element
21
    end
22
23
    def mount_component(root_id, mount_ready, context)
24
      return if @tag == "noscript"
25
      @root_node_id = root_id
26
27
      props = current_element.props
28
29
      case @tag
30
      # when 'iframe', 'img', 'form', 'video', 'audio'
31
      #   this._wrapperState = {
32
      #     listeners: null,
33
      #   }
34
      #   transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
35
      # when 'button'
36
      #   props = ReactDOMButton.getNativeProps(this, props, nativeParent);
37
      when 'input'
38
        @input_wrapper.mount_wrapper
39
        props = @input_wrapper.native_props
40
      # when 'option'
41
      #   ReactDOMOption.mountWrapper(this, props, nativeParent);
42
      #   props = ReactDOMOption.getNativeProps(this, props);
43
      # when 'select'
44
      #   ReactDOMSelect.mountWrapper(this, props, nativeParent);
45
      #   props = ReactDOMSelect.getNativeProps(this, props);
46
      #   transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
47
      # when 'textarea'
48
      #   ReactDOMTextarea.mountWrapper(this, props, nativeParent);
49
      #   props = ReactDOMTextarea.getNativeProps(this, props);
50
      #   transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
51
      end
52
53
      markup = create_open_tag_markup_and_put_listeners(mount_ready, @element.props)
54
      create_content_markup(mount_ready, markup, context)
55
    end
56
57
    def unmount_component
58
      case @tag
59
      # when 'iframe', 'img', 'form'
60
      #   listeners = this._wrapperState.listeners;
61
      #   if (listeners) {
62
      #     for (var i = 0; i < listeners.length; i++) {
63
      #       listeners[i].remove();
64
      #     }
65
      #   }
66
      when 'input'
67
        @input_wrapper.unmount_wrapper
68
      end
69
70
      unmount_children
71
      BrowserEvent.delete_all_listeners(@root_node_id)
72
      Mount.purge_id(@root_node_id)
73
      @root_node_id = nil
74
      # @wrapper_state = null;
75
      if @node_with_legacy_properties
76
        node = @node_with_legacy_properties
77
        node.internal_component = nil
78
        @node_with_legacy_properties = nil
79
      end
80
    end
81
82
    def receive_component(next_element, mount_ready, context)
83
      prev_element = @element
84
      @element = next_element
85
      update_component(mount_ready, prev_element, next_element, context);
86
    end
87
88
    def update_component(mount_ready, prev_element, next_element, context)
89
      last_props = prev_element.props
90
      next_props = @element.props
91
92
      case @tag
93
      # when 'button':
94
      #   lastProps = ReactDOMButton.getNativeProps(this, lastProps);
95
      #   nextProps = ReactDOMButton.getNativeProps(this, nextProps);
96
      when 'input'
97
        @input_wrapper.update_wrapper
98
        last_props = @input_wrapper.native_props(last_props)
99
      #   nextProps = ReactDOMInput.getNativeProps(this, nextProps);
100
      # when 'option':
101
      #   lastProps = ReactDOMOption.getNativeProps(this, lastProps);
102
      #   nextProps = ReactDOMOption.getNativeProps(this, nextProps);
103
      # when 'select':
104
      #   lastProps = ReactDOMSelect.getNativeProps(this, lastProps);
105
      #   nextProps = ReactDOMSelect.getNativeProps(this, nextProps);
106
      # when 'textarea':
107
      #   ReactDOMTextarea.updateWrapper(this);
108
      #   lastProps = ReactDOMTextarea.getNativeProps(this, lastProps);
109
      #   nextProps = ReactDOMTextarea.getNativeProps(this, nextProps);
110
      end
111
112
      # assertValidProps(this, nextProps);
113
      update_dom_properties(last_props, next_props, mount_ready)
114
      update_dom_children(last_props, next_props, mount_ready, context)
115
116
      # if (!canDefineProperty && this._nodeWithLegacyProperties) {
117
      #   this._nodeWithLegacyProperties.props = nextProps;
118
      # }
119
120
      if @tag == 'select'
121
        mount_ready.enqueue { post_update_select_wrapper }
122
      end
123
    end
124
125
    def public_instance
126
      native_node
127
    end
128
129
    def to_s
130
      {
131
        tag: @tag,
132
        root_node_id: root_node_id,
133
        rendered_children: @rendered_children,
134
      }.to_s
135
    end
136
137
    private
138
139
    def native_node
140
      @native_node ||= Mount.node(@root_node_id)
141
    end
142
143
    def update_dom_properties(last_props, next_props, mount_ready)
144
      style_updates = {}
145
146
      last_props.each do |prop_key, prop_value|
147
        next if next_props.has_key?(prop_key)
148
149
        if prop_key == :style
150
          @previous_style_copy.each do |style_name, style|
151
            style_updates[style_name] = ''
152
          end
153
154
          @previous_style_copy = nil
155
        elsif BrowserEvent.include?(prop_key)
156
          if last_props.has_key? prop_key
157
            BrowserEvent.delete_listener(root_node_id, prop_key)
158
          end
159
        elsif DOMProperty.include?(prop_key) || DOMProperty.is_custom_attribute(prop_key)
160
          node = Mount.node(root_node_id)
161
          DOMPropertyOperations.delete_value_for_property(node, prop_key, prop_value)
162
        end
163
      end
164
165
      next_props.each do |prop_key, next_prop|
166
        last_prop = prop_key == :style ? @previous_style_copy : last_props[prop_key]
167
        next if next_prop == last_prop
168
169
        if prop_key == :style
170
          if next_prop
171
            next_prop = @previous_style_copy = next_prop.clone
172
          else
173
            @previous_style_copy = nil
174
          end
175
176
          if last_prop
177
            last_prop.each do |style_name, style|
178
              unless next_prop.has_key?(style_name)
179
                style_updates[style_name] = ''
180
              end
181
            end
182
183
            next_prop.each do |style_name, style|
184
              if next_prop.has_key?(style_name) && last_prop[style_name] != next_prop[style_name]
185
                style_updates[style_name] = next_prop[style_name]
186
              end
187
            end
188
          else
189
            style_updates = next_prop
190
          end
191
        elsif BrowserEvent.include?(prop_key)
192
          if next_prop
193
            enqueue_put_listener(root_node_id, prop_key, next_prop, mount_ready)
194
          elsif last_prop
195
            BrowserEvent.delete_listener(root_node_id, prop_key)
196
          end
197
        elsif is_custom_component(@tag, next_props)
198
          node = Mount.node(root_node_id)
199
          DOMPropertyOperations.set_value_for_attribute(node, prop_key, next_prop);
200
        elsif DOMProperty.include?(prop_key) || DOMProperty.is_custom_attribute(prop_key)
201
          node = Mount.node(root_node_id)
202
          if next_prop
203
            DOMPropertyOperations.set_value_for_property(node, prop_key, next_prop);
204
          else
205
            DOMPropertyOperations.delete_value_for_property(node, prop_key)
206
          end
207
        end
208
      end
209
210
      if style_updates.any?
211
        node = Mount.node(root_node_id)
212
        node.style(style_updates)
213
      end
214
    rescue => e
215
        p e
216
      raise
217
    end
218
219
    def update_dom_children(last_props, next_props, mount_ready, context)
220
      last_content = last_props[:children] if is_text_content(last_props[:children])
221
      next_content = next_props[:children] if is_text_content(next_props[:children])
222
223
      last_html = last_props[:dangerouslySetInnerHTML].try {|_| _['__html'] }
224
      next_html = next_props[:dangerouslySetInnerHTML].try {|_| _['__html'] }
225
226
      last_children = last_props[:children] unless last_content
227
      next_children = next_props[:children] unless next_content
228
229
      last_has_content_or_html = !last_content.nil? || !last_html.nil?
230
      next_has_content_or_html = !next_content.nil? || !next_html.nil?
231
      if last_children && next_children.nil?
232
        update_children(nil, mount_ready, context)
233
      elsif last_has_content_or_html && !next_has_content_or_html
234
        update_text_content('')
235
      end
236
237
      if next_content
238
        unless last_content == next_content
239
          update_text_content(next_content.to_s)
240
        end
241
      elsif next_html
242
        unless last_html == next_html
243
          update_markup(next_html.to_s)
244
        end
245
      elsif next_children
246
        update_children(next_children, mount_ready, context)
247
      end
248
    end
249
250
    def create_open_tag_markup_and_put_listeners(mount_ready, props)
251
      element = $document.create_element(@tag)
252
253
      props.each do |prop_key, prop_value|
254
        next unless prop_value
255
256
        if BrowserEvent.include?(prop_key)
257
          enqueue_put_listener(@root_node_id, prop_key, prop_value, mount_ready)
258
        else
259
          if prop_key == :style
260
            if prop_value
261
              prop_value = @previous_style_copy = props[:style].clone
262
            end
263
            DOMPropertyOperations.create_markup_for_styles(element, prop_value)
264
          else
265
            if is_custom_component(@tag, props)
266
              DOMPropertyOperations.create_markup_for_custom_attribute(element, prop_key, prop_value)
267
            else
268
              DOMPropertyOperations.create_markup_for_property(element, prop_key, prop_value)
269
            end
270
          end
271
        end
272
      end
273
274
      #return element if mount_ready.render_to_static_markup
275
276
      element.attributes[Mount::ID_ATTR_NAME] = @root_node_id
277
      element
278
    end
279
280
    def create_content_markup(mount_ready, markup, context)
281
      children = @element.props[:children]
282
283
      inner_html = @element.props[:dangerouslySetInnerHTML]
284
      if inner_html
285
        if inner_html.has_key?(:__html)
286
          markup.inner_html = inner_html[:__html]
287
        end
288
      elsif is_text_content(children)
289
        markup.text = children.to_s
290
      else
291
        mount_images = mount_children(children, mount_ready, context)
292
        mount_images.compact.each do |image|
293
          if is_text_content(image)
294
            markup << Hyalite::DOM::Text.new(image.to_s)
295
          else
296
            markup << image
297
          end
298
        end
299
      end
300
      markup
301
    end
302
303
    def enqueue_put_listener(id, event_name, listener, mount_ready)
304
      container = Mount.container_for_id(id)
305
      if container
306
        doc = container.element? ? container.document : container
307
        BrowserEvent.listen_to(event_name, doc)
308
      end
309
      mount_ready.enqueue do
310
        BrowserEvent.put_listener(id, event_name, listener)
311
      end
312
    end
313
314
    def is_custom_component(tag, props)
315
      tag.include?('-') || props.has_key?(:is)
316
    end
317
318
    def is_text_content(children)
319
      case children
320
      when String, Numeric
321
        true
322
      else
323
        false
324
      end
325
    end
326
  end
327
end
328