Class: Dommy::NamedNodeMap

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

Overview

‘Element.attributes` returns this. Iterable, `.length`, `.item(i)`, `.getNamedItem(name)`, `.removeNamedItem(name)`, `.setNamedItem(attr)`, plus property-style access (`attributes.id`, `attributes.class`).

NamedNodeMap is live — it re-reads the element’s Nokogiri attributes on every access so DOM mutations are reflected.

Instance Method Summary collapse

Methods included from Bridge::Methods

included

Constructor Details

#initialize(element) ⇒ NamedNodeMap

Returns a new instance of NamedNodeMap.



170
171
172
173
174
175
176
# File 'lib/dommy/attr.rb', line 170

def initialize(element)
  @element = element
  # Attr-node identity cache, keyed by [namespace_or_nil, localName].
  # Every accessor (item / index / getNamedItem(NS)) returns the SAME
  # Attr object for a given underlying attribute, per the DOM.
  @attrs = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object



356
357
358
359
# File 'lib/dommy/attr.rb', line 356

def method_missing(name, *args)
  attr = get_named_item(name)
  attr || super
end

Instance Method Details

#[](key) ⇒ Object

Property-style access — ‘el.attributes.id`, `el.attributes`.



303
304
305
306
307
308
309
310
# File 'lib/dommy/attr.rb', line 303

def [](key)
  case key
  when Integer
    item(key)
  else
    get_named_item(key)
  end
end

#__internal_evict__(namespace, local_name) ⇒ Object

Detach and evict the cached Attr for (namespace, localName), if any —called by Element after the underlying attribute is removed so a held reference reports ‘ownerElement === null`.



271
272
273
274
275
276
# File 'lib/dommy/attr.rb', line 271

def __internal_evict__(namespace, local_name)
  key = [namespace.to_s.empty? ? nil : namespace.to_s, local_name.to_s]
  attr = @attrs.delete(key)
  attr&.__internal_detach__
  nil
end

#__js_call__(method, args) ⇒ Object



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/dommy/attr.rb', line 337

def __js_call__(method, args)
  case method
  when "item"
    item(args[0])
  when "getNamedItem"
    get_named_item(args[0])
  when "setNamedItem"
    set_named_item(args[0])
  when "removeNamedItem"
    remove_named_item(args[0])
  when "getNamedItemNS"
    get_named_item_ns(args[0], args[1])
  when "setNamedItemNS"
    set_named_item_ns(args[0])
  when "removeNamedItemNS"
    remove_named_item_ns(args[0], args[1])
  end
end

#__js_get__(key) ⇒ Object



312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/dommy/attr.rb', line 312

def __js_get__(key)
  case key
  when "length"
    length
  else
    # Numeric key = item(i); string key = named item
    if key.is_a?(Integer) || key.to_s.match?(/\A\d+\z/)
      item(key.to_i)
    else
      get_named_item(key)
    end
  end
end

#__js_named_props__Object

WebIDL “supported property names” for NamedNodeMap: the qualified name of each attribute, in order (the indexed names are reflected separately).



328
329
330
331
332
# File 'lib/dommy/attr.rb', line 328

def __js_named_props__
  Backend.attribute_nodes(@element.__dommy_backend_node__).map do |a|
    Backend.attribute_ns_info(a)[:qualified_name]
  end
end

#attr_for(attr_node) ⇒ Object

Return the cached Attr for a backend attribute node, creating (and caching) one on first access so DOM node identity holds.



191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/dommy/attr.rb', line 191

def attr_for(attr_node)
  info = Backend.attribute_ns_info(attr_node)
  key = [info[:namespace_uri], info[:local_name]]
  cached = @attrs[key]
  return cached if cached && cached.owner_element.equal?(@element)

  attr = Attr.new(info[:qualified_name], owner: @element,
                                         namespace_uri: info[:namespace_uri],
                                         prefix: info[:prefix],
                                         local_name: info[:local_name])
  @attrs[key] = attr
  attr
end

#eachObject



229
230
231
232
233
# File 'lib/dommy/attr.rb', line 229

def each
  Backend.attribute_nodes(@element.__dommy_backend_node__).each do |a|
    yield attr_for(a)
  end
end

#get_named_item(name) ⇒ Object



205
206
207
208
209
210
211
# File 'lib/dommy/attr.rb', line 205

def get_named_item(name)
  key = name.to_s.downcase
  node = Backend.attribute_nodes(@element.__dommy_backend_node__).find do |a|
    Backend.attribute_ns_info(a)[:qualified_name] == key
  end
  node && attr_for(node)
end

#get_named_item_ns(namespace, local_name) ⇒ Object

—– Namespaced named-item access (getNamedItemNS etc.) —–



280
281
282
283
284
285
286
287
# File 'lib/dommy/attr.rb', line 280

def get_named_item_ns(namespace, local_name)
  node = Backend.attribute_nodes(@element.__dommy_backend_node__).find do |a|
    info = Backend.attribute_ns_info(a)
    info[:local_name] == local_name.to_s &&
      (info[:namespace_uri] || nil) == (namespace.to_s.empty? ? nil : namespace.to_s)
  end
  node && attr_for(node)
end

#item(index) ⇒ Object



184
185
186
187
# File 'lib/dommy/attr.rb', line 184

def item(index)
  node = Backend.attribute_nodes(@element.__dommy_backend_node__)[index.to_i]
  node && attr_for(node)
end

#lengthObject Also known as: size



178
179
180
# File 'lib/dommy/attr.rb', line 178

def length
  Backend.attribute_nodes(@element.__dommy_backend_node__).size
end

#remove_named_item(name) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
# File 'lib/dommy/attr.rb', line 217

def remove_named_item(name)
  key = name.to_s.downcase
  node = Backend.attribute_nodes(@element.__dommy_backend_node__).find do |a|
    Backend.attribute_ns_info(a)[:qualified_name] == key
  end
  return nil unless node

  removed = attr_for(node)
  @element.remove_attribute(key)
  removed
end

#remove_named_item_ns(namespace, local_name) ⇒ Object



294
295
296
297
298
299
300
# File 'lib/dommy/attr.rb', line 294

def remove_named_item_ns(namespace, local_name)
  existing = get_named_item_ns(namespace, local_name)
  return nil unless existing

  @element.remove_attribute_ns(namespace, local_name)
  existing
end

#respond_to_missing?(name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


361
362
363
# File 'lib/dommy/attr.rb', line 361

def respond_to_missing?(name, include_private = false)
  @element.__dommy_backend_node__.key?(name.to_s.downcase) || super
end

#set_attribute_node(attr) ⇒ Object

WHATWG “set an attribute” / “set attribute node”. Adopts ‘attr` (the exact object — identity is preserved), replacing any attribute with the same (namespace, localName) and returning the previous Attr (detached), or nil. Throws InUseAttributeError if `attr` is bound to another element.



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/dommy/attr.rb', line 239

def set_attribute_node(attr)
  return nil unless attr.is_a?(Attr)

  owner = attr.owner_element
  if owner && !owner.equal?(@element)
    raise DOMException::InUseAttributeError, "attribute is in use by another element"
  end

  ns = attr.namespace_uri
  local = attr.local_name
  old = get_named_item_ns(ns, local)
  return attr if old && old.equal?(attr)

  value = attr.value
  key = [ns, local]
  if old
    old.__internal_detach__
    @attrs.delete(key)
  end
  attr.__internal_attach__(@element)
  if ns
    @element.set_attribute_ns(ns, attr.name, value)
  else
    @element.set_attribute(attr.name, value)
  end
  @attrs[key] = attr
  old
end

#set_named_item(attr) ⇒ Object



213
214
215
# File 'lib/dommy/attr.rb', line 213

def set_named_item(attr)
  set_attribute_node(attr)
end

#set_named_item_ns(attr) ⇒ Object

setNamedItemNS shares the “set an attribute” algorithm with setNamedItem.



290
291
292
# File 'lib/dommy/attr.rb', line 290

def set_named_item_ns(attr)
  set_attribute_node(attr)
end