Class: Dommy::HTMLCollection

Inherits:
Object
  • Object
show all
Includes:
Bridge::Methods, Enumerable
Defined in:
lib/dommy/html_collection.rb

Overview

‘HTMLCollection` — live, ordered set of Element nodes. Distinct from `NodeList` in two ways:

- Always element-only (Node types other than Element are skipped)
- Supports `namedItem(name)` lookup by `id` or `name` attribute

Live behavior: pass an evaluator block (called ‘&compute`) that returns the current element list on every access. Each query re-evaluates, so mutations to the parent tree are reflected immediately.

Intentionally NOT a subclass of Array; spec semantics demand ‘Array.isArray(html_collection) === false` in real browsers, and mirroring that here helps tests written against MDN behavior.

Direct Known Subclasses

HTMLOptionsCollection

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Bridge::Methods

included

Constructor Details

#initialize(&compute) ⇒ HTMLCollection

Returns a new instance of HTMLCollection.



21
22
23
# File 'lib/dommy/html_collection.rb', line 21

def initialize(&compute)
  @compute = compute
end

Class Method Details

.elements_by_tag_name_ns(root, document, namespace, local_name) ⇒ Object

Shared ‘getElementsByTagNameNS(namespace, localName)` — a live collection of descendants of `root` matching the (namespace, localName) filter, where “*” matches any. An empty-string namespace means the null namespace.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/dommy/html_collection.rb', line 28

def self.elements_by_tag_name_ns(root, document, namespace, local_name)
  ns = namespace.to_s
  ns_filter = ns == "*" ? :any : (ns.empty? ? nil : ns)
  local = local_name.to_s
  new do
    nodes = local == "*" ? root.css("*") : root.css(local)
    nodes.filter_map do |node|
      el = document.wrap_node(node)
      next nil unless el

      el_ns = el.respond_to?(:namespace_uri) ? el.namespace_uri : nil
      (ns_filter == :any || el_ns == ns_filter) ? el : nil
    end
  end
end

Instance Method Details

#[](key) ⇒ Object

‘[]` supports both integer index (`coll`, `coll`) and string name (`coll`). Negative indices are interpreted Ruby-style (offset from the end), even though the spec’s ‘item(i)` is positive-only.



81
82
83
84
85
86
87
88
89
90
# File 'lib/dommy/html_collection.rb', line 81

def [](key)
  case key
  when Integer
    to_a[key]
  when /\A-?\d+\z/
    to_a[key.to_i]
  else
    named_item(key)
  end
end

#__js_call__(method, args) ⇒ Object



151
152
153
154
155
156
157
158
# File 'lib/dommy/html_collection.rb', line 151

def __js_call__(method, args)
  case method
  when "item"
    item(args[0])
  when "namedItem"
    named_item(args[0])
  end
end

#__js_get__(key) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/dommy/html_collection.rb', line 108

def __js_get__(key)
  case key
  when "length"
    length
  when Integer
    item(key)
  else
    s = key.to_s
    if s.match?(/\A\d+\z/) && s.to_i < 4_294_967_295
      # A valid array index (0 ≤ n < 2^32-1) is a pure indexed lookup — out
      # of range yields nil (→ undefined), never a named fallback.
      item(s.to_i)
    else
      # Non-array-index strings (negative, ≥ 2^32-1, or names) use the named
      # getter.
      named_item(s) || (s == "length" ? length : nil)
    end
  end
end

#__js_named_props__Object

WebIDL “supported property names” for HTMLCollection: in tree order, each element contributes its non-empty ‘id`, then (if it is in the HTML namespace) its non-empty `name` — ignoring duplicates.



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/dommy/html_collection.rb', line 131

def __js_named_props__
  names = []
  to_a.each do |el|
    next unless el.respond_to?(:__dommy_backend_node__)

    node = el.__dommy_backend_node__
    id = node["id"].to_s
    names << id if !id.empty? && !names.include?(id)

    name = node["name"].to_s
    next if name.empty? || names.include?(name)

    html_ns = !el.respond_to?(:namespace_uri) || el.namespace_uri == "http://www.w3.org/1999/xhtml"
    names << name if html_ns
  end
  names
end

#each(&blk) ⇒ Object



100
101
102
# File 'lib/dommy/html_collection.rb', line 100

def each(&blk)
  to_a.each(&blk)
end

#empty?Boolean

Returns:

  • (Boolean)


50
51
52
# File 'lib/dommy/html_collection.rb', line 50

def empty?
  to_a.empty?
end

#first(n = nil) ⇒ Object



92
93
94
# File 'lib/dommy/html_collection.rb', line 92

def first(n = nil)
  n.nil? ? to_a.first : to_a.first(n)
end

#item(index) ⇒ Object



54
55
56
57
58
59
# File 'lib/dommy/html_collection.rb', line 54

def item(index)
  i = index.to_i
  return nil if i < 0

  to_a[i]
end

#last(n = nil) ⇒ Object



96
97
98
# File 'lib/dommy/html_collection.rb', line 96

def last(n = nil)
  n.nil? ? to_a.last : to_a.last(n)
end

#lengthObject Also known as: size



44
45
46
# File 'lib/dommy/html_collection.rb', line 44

def length
  to_a.length
end

#named_item(name) ⇒ Object

‘namedItem(name)` returns the first element whose `id` or `name` attribute equals `name`. Returns nil if no match.



63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/dommy/html_collection.rb', line 63

def named_item(name)
  # A numeric argument (`namedItem(2147483648)`) crosses from JS as a Float
  # for values past int32; format it as an integer string so it matches an
  # `id`/`name` attribute like "2147483648" (not "2147483648.0").
  key = (name.is_a?(Float) && name.finite? && name == name.to_i) ? name.to_i.to_s : name.to_s
  return nil if key.empty?

  to_a.find do |el|
    next false unless el.respond_to?(:__dommy_backend_node__)

    el.__dommy_backend_node__["id"].to_s == key || el.__dommy_backend_node__["name"].to_s == key
  end
end

#to_aObject



104
105
106
# File 'lib/dommy/html_collection.rb', line 104

def to_a
  @compute.call
end