Module: Dommy::Internal::ParentNode

Includes:
ChildNode
Included in:
Element, Fragment, ShadowRoot
Defined in:
lib/dommy/internal/parent_node.rb

Overview

Shared ParentNode tree-mutation surface for Element, Fragment, and ShadowRoot. Includers must expose @__node__ (the backing Nokogiri node) and @document (the owning Dommy::Document).

All child-list mutations funnel through notify_child_list, which forwards to MutationCoordinator#notify_child_list_mutation. That coordinator already no-ops on empty added/removed sets and on an unwrappable target, so callers may invoke it unconditionally.

Instance Method Summary collapse

Methods included from ChildNode

#child_node_after, #child_node_before, #child_node_replace_with, #nullable_dom_string, #validate_insert_before_ref!

Instance Method Details

#append(*args) ⇒ Object

ParentNode#append — mixed Node/String args appended in order.



31
32
33
34
35
36
37
# File 'lib/dommy/internal/parent_node.rb', line 31

def append(*args)
  validate_insertion_args!(args)
  nodes = args.flat_map { |arg| detach_dom_nodes(arg) }
  nodes.each { |n| @__node__.add_child(n) }
  notify_child_list(added: nodes)
  nil
end

#append_child(child) ⇒ Object

appendChild(child) — detach the node(s) from any current parent and append to the end of this node's child list.



21
22
23
24
25
26
27
28
# File 'lib/dommy/internal/parent_node.rb', line 21

def append_child(child)
  coerce_node_argument!(child)
  ensure_pre_insertion_validity!(child, nil)
  nodes = detach_dom_nodes(child)
  nodes.each { |n| @__node__.add_child(n) }
  notify_child_list(added: nodes)
  child
end

#normalizeObject

Node#normalize — merge each run of adjacent exclusive Text descendants into its first node (preserving that node's identity, so a JS reference to it survives) and drop empty Text nodes. Recurses the whole subtree, so it works for Element, DocumentFragment, and ShadowRoot alike.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/dommy/internal/parent_node.rb', line 72

def normalize
  text_nodes = []
  @__node__.traverse { |node| text_nodes << node if node.respond_to?(:text?) && node.text? }

  text_nodes.each do |node|
    next unless node.parent # already removed as part of an earlier run

    if node.content.to_s.empty?
      @document.remove_node_with_notify(node)
      next
    end

    merged = []
    sib = node.next
    while sib.respond_to?(:text?) && sib.text?
      merged << sib
      sib = sib.next
    end
    next if merged.empty?

    old = node.content.to_s
    node.content = old + merged.map { |m| m.content.to_s }.join
    @document.notify_character_data_mutation(target_node: node, old_value: old)
    merged.each { |m| @document.remove_node_with_notify(m) }
  end

  nil
end

#prepend(*args) ⇒ Object

ParentNode#prepend — insert before the current first child.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/dommy/internal/parent_node.rb', line 40

def prepend(*args)
  validate_insertion_args!(args)
  nodes = args.flat_map { |arg| detach_dom_nodes(arg) }
  anchor = @__node__.children.first
  if anchor
    # Insert each node before the (fixed) original first child in order:
    # forward iteration keeps document order (n1, n2, … then the old first
    # child). Reversing here would emit them backwards.
    nodes.each { |n| anchor.add_previous_sibling(n) }
  else
    nodes.each { |n| @__node__.add_child(n) }
  end
  notify_child_list(added: nodes)
  nil
end

#replace_children(*args) ⇒ Object

ParentNode#replaceChildren — remove all existing children, then append the new set. One mutation record carries both sides.



58
59
60
61
62
63
64
65
66
# File 'lib/dommy/internal/parent_node.rb', line 58

def replace_children(*args)
  validate_insertion_args!(args)
  removed = @__node__.children.to_a
  removed.each(&:unlink)
  nodes = args.flat_map { |arg| detach_dom_nodes(arg) }
  nodes.each { |n| @__node__.add_child(n) }
  notify_child_list(added: nodes, removed: removed)
  nil
end