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 Makiri 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.



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

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



400
401
402
403
# File 'lib/dommy/attr.rb', line 400

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["class"].



342
343
344
345
346
347
348
349
# File 'lib/dommy/attr.rb', line 342

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.



310
311
312
313
314
315
# File 'lib/dommy/attr.rb', line 310

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



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/dommy/attr.rb', line 381

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



351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/dommy/attr.rb', line 351

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: each attribute's qualified name, in order with duplicates omitted (the indexed names are reflected separately). For an HTML element in an HTML document, names containing an ASCII upper alpha are excluded (they can't be reached by the case-insensitive named getter).



370
371
372
373
374
375
376
# File 'lib/dommy/attr.rb', line 370

def __js_named_props__
  names = Backend.attribute_nodes(@element.__dommy_backend_node__).map do |a|
    Backend.attribute_ns_info(a)[:qualified_name]
  end.uniq
  names.reject! { |n| n.match?(/[A-Z]/) } unless @element.__internal_case_sensitive_attribute_names__?
  names
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.



228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/dommy/attr.rb', line 228

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



268
269
270
271
272
# File 'lib/dommy/attr.rb', line 268

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

#get_named_item(name) ⇒ Object



242
243
244
245
246
247
248
249
250
# File 'lib/dommy/attr.rb', line 242

def get_named_item(name)
  # getNamedItem / getAttribute lowercase the qualified name only for an HTML
  # element in an HTML document; other elements match case-sensitively.
  key = @element.__internal_normalize_attr_key__(name)
  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.) -----



319
320
321
322
323
324
325
326
# File 'lib/dommy/attr.rb', line 319

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



221
222
223
224
# File 'lib/dommy/attr.rb', line 221

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

#lengthObject Also known as: size



215
216
217
# File 'lib/dommy/attr.rb', line 215

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

#remove_named_item(name) ⇒ Object



256
257
258
259
260
261
262
263
264
265
266
# File 'lib/dommy/attr.rb', line 256

def remove_named_item(name)
  key = @element.__internal_normalize_attr_key__(name)
  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



333
334
335
336
337
338
339
# File 'lib/dommy/attr.rb', line 333

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)


405
406
407
# File 'lib/dommy/attr.rb', line 405

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.



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/dommy/attr.rb', line 278

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



252
253
254
# File 'lib/dommy/attr.rb', line 252

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.



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

def set_named_item_ns(attr)
  set_attribute_node(attr)
end