Class: Dommy::Element
- Inherits:
-
Object
- Object
- Dommy::Element
- Includes:
- EventTarget, Node
- Defined in:
- lib/dommy/element.rb
Direct Known Subclasses
Constant Summary collapse
- SHADOW_HOST_TAGS =
Elements that may host a Shadow DOM tree per the HTML spec. Custom-element-style names (containing “-”) are also allowed.
%w[ article aside blockquote body div footer h1 h2 h3 h4 h5 h6 header main nav p section span ] .freeze
- ELEMENT_NODE =
Node type / NodeFilter bitmask constants — DOM Level 3 says these are exposed on both the constructor and every instance. Defined at the bottom of the class so subclasses inherit them too.
1- ATTRIBUTE_NODE =
2- TEXT_NODE =
3- CDATA_SECTION_NODE =
4- PROCESSING_INSTRUCTION_NODE =
7- COMMENT_NODE =
8- DOCUMENT_NODE =
9- DOCUMENT_TYPE_NODE =
10- DOCUMENT_FRAGMENT_NODE =
11- DOCUMENT_POSITION_DISCONNECTED =
0x01- DOCUMENT_POSITION_PRECEDING =
0x02- DOCUMENT_POSITION_FOLLOWING =
0x04- DOCUMENT_POSITION_CONTAINS =
0x08- DOCUMENT_POSITION_CONTAINED_BY =
0x10- DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC =
0x20
Instance Attribute Summary collapse
-
#__node__ ⇒ Object
readonly
Returns the value of attribute __node__.
-
#document ⇒ Object
readonly
Returns the value of attribute document.
Instance Method Summary collapse
- #[](key) ⇒ Object
- #[]=(key, value) ⇒ Object
- #__js_call__(method, args) ⇒ Object
- #__js_get__(key) ⇒ Object
- #__js_set__(key, value) ⇒ Object
-
#__shadow_root__ ⇒ Object
Internal — gives access to the shadow root regardless of mode.
- #after(*args) ⇒ Object
-
#anchor_href ⇒ Object
Anchor / area ‘href` IDL attribute reflects the attribute resolved against the document base URL (browser semantics).
-
#append(*args) ⇒ Object
ParentNode mixin methods — append / prepend / replaceChildren take a mix of Node and String args (strings become text nodes).
- #append_child(child) ⇒ Object
-
#attach_shadow(options = nil) ⇒ Object
‘el.attachShadow({ mode: “open” | “closed” })` — creates and attaches a ShadowRoot.
-
#attributes ⇒ Object
NamedNodeMap of attributes.
-
#base_uri ⇒ Object
‘Node.baseURI` — resolves against the document’s base URL, which in turn honors the first ‘<base href>` element (see `Document#base_uri`).
-
#before(*args) ⇒ Object
ChildNode mixin — before / after / replaceWith with mixed args.
- #blur ⇒ Object
- #child_element_count ⇒ Object
- #child_nodes ⇒ Object
- #children ⇒ Object
- #class_list ⇒ Object
- #class_name ⇒ Object
- #class_name=(value) ⇒ Object
- #click ⇒ Object
- #clone_node(deep_arg) ⇒ Object
- #closest(selector) ⇒ Object
-
#compare_document_position(other) ⇒ Object
Standard DOM compareDocumentPosition.
-
#contains?(other) ⇒ Boolean
‘el.contains(other)` — true if `other` is `el` itself or any descendant.
- #dataset ⇒ Object
-
#equal_node?(other) ⇒ Boolean
Structural equality — same nodeType, same tagName, same attribute set, and recursively-equal children.
- #first_child ⇒ Object
- #first_element_child ⇒ Object
-
#focus ⇒ Object
‘focus()` / `blur()` — Dommy has no layout / real focus, but tests rely on `document.activeElement` updating.
-
#get_attribute(name) ⇒ Object
HTML attribute names are case-insensitive — browser DOM stores them in lowercase regardless of the case passed to setAttribute.
- #get_attribute_node(name) ⇒ Object
- #get_elements_by_class_name(name) ⇒ Object
- #get_elements_by_tag_name(name) ⇒ Object
- #get_html(_options = nil) ⇒ Object
-
#get_inner_html(_options = nil) ⇒ Object
‘getInnerHTML()` — happy-dom alias for the `innerHTML` getter.
- #has_attribute?(name) ⇒ Boolean
- #has_attributes? ⇒ Boolean
- #has_child_nodes? ⇒ Boolean
- #id ⇒ Object
- #id=(value) ⇒ Object
-
#initialize(document, nokogiri_node) ⇒ Element
constructor
A new instance of Element.
- #inner_html ⇒ Object
- #inner_html=(value) ⇒ Object
-
#insert_adjacent_element(position, element) ⇒ Object
‘el.insertAdjacentElement(position, element)` — DOM spec positions: “beforebegin”, “afterbegin”, “beforeend”, “afterend”.
- #insert_adjacent_html(position, html) ⇒ Object
- #insert_adjacent_text(position, text) ⇒ Object
- #insert_before(child, reference) ⇒ Object
-
#is_connected? ⇒ Boolean
(also: #connected?)
Walks parents up to the Document (or false when the chain dead-ends).
- #last_child ⇒ Object
- #last_element_child ⇒ Object
-
#live_child_nodes ⇒ Object
Live NodeList over this element’s children.
- #local_name ⇒ Object
- #matches?(selector) ⇒ Boolean
-
#namespace_uri ⇒ Object
HTML namespace constants — most HTML elements live in xhtml ns.
- #next_element_sibling ⇒ Object
- #next_sibling ⇒ Object
-
#normalize ⇒ Object
Merge adjacent text node siblings and drop empty text nodes.
-
#on(type, &block) ⇒ Object
Ruby block-style listener (in addition to the (type, callable, options) form inherited from EventTarget).
-
#outer_html ⇒ Object
Outer HTML — serializes this element and its subtree.
-
#outer_html=(html) ⇒ Object
Per WHATWG DOM Parsing: - parent is null (detached element) → return silently - parent is the Document (‘<html>` element) → throw NoModificationAllowedError (can’t replace the document element via this API) - otherwise, parse ‘html` as a fragment in the parent’s context and replace this element with the parsed nodes.
- #owner_document ⇒ Object
- #parent_element ⇒ Object (also: #parent)
- #parent_node ⇒ Object
- #prepend(*args) ⇒ Object
- #previous_element_sibling ⇒ Object
- #previous_sibling ⇒ Object
- #query_selector(selector) ⇒ Object
- #query_selector_all(selector) ⇒ Object
-
#reflected_attr_name(key) ⇒ Object
Map a JS boolean property name to its underlying HTML attribute.
- #remove ⇒ Object
- #remove_attribute(name) ⇒ Object
- #remove_attribute_node(attr) ⇒ Object
- #remove_child(child) ⇒ Object
-
#replace_child(new_child, old_child) ⇒ Object
‘node.replaceChild(newChild, oldChild)` — required for in-place item updates in list reconcilers.
- #replace_children(*args) ⇒ Object
- #replace_with_nodes(*args) ⇒ Object
- #role ⇒ Object
- #role=(value) ⇒ Object
-
#root_node ⇒ Object
(also: #get_root_node)
‘el.getRootNode()` — returns the topmost ancestor (document, ShadowRoot, fragment, or self if detached).
-
#same_node?(other) ⇒ Boolean
‘Node.isSameNode(other)` — strict reference identity.
- #set_attribute(name, value) ⇒ Object
- #set_attribute_node(attr) ⇒ Object
-
#shadow_root ⇒ Object
‘el.shadowRoot` — returns the attached ShadowRoot only when mode is “open”; closed shadows are hidden from external code.
-
#slot ⇒ Object
‘slot` and `role` are simple reflected string attributes — added as named accessors for happy-dom test parity.
- #slot=(value) ⇒ Object
- #style ⇒ Object
- #tag_name ⇒ Object
-
#text_content ⇒ Object
—– Public Ruby API (snake_case) —–.
- #text_content=(value) ⇒ Object
-
#to_s ⇒ Object
Convenience alias matching the DOM idiom ‘String(el)` → outerHTML.
- #toggle_attribute(name, force = nil) ⇒ Object
Methods included from EventTarget
#__deliver_event__, #add_event_listener, #dispatch_event, #invoke_listener, #remove_event_listener
Constructor Details
#initialize(document, nokogiri_node) ⇒ Element
Returns a new instance of Element.
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 |
# File 'lib/dommy/element.rb', line 683 def initialize(document, nokogiri_node) @document = document @__node__ = nokogiri_node @class_list = ClassList.new(self) @style = StyleDeclaration.new(self) @dataset = DatasetMap.new(self) # `HTMLCollection` re-evaluates the child list on every # property access so callers that capture `el[:children]` once # see DOM mutations made between iterations — required by list # reconciliation patterns that rely on the spec's live # HTMLCollection semantics to detect already-positioned nodes. @live_children = HTMLCollection.new do @__node__.element_children.map { |n| @document.wrap_node(n) }.compact end end |
Instance Attribute Details
#__node__ ⇒ Object (readonly)
Returns the value of attribute __node__.
681 682 683 |
# File 'lib/dommy/element.rb', line 681 def __node__ @__node__ end |
#document ⇒ Object (readonly)
Returns the value of attribute document.
681 682 683 |
# File 'lib/dommy/element.rb', line 681 def document @document end |
Instance Method Details
#[](key) ⇒ Object
1393 1394 1395 |
# File 'lib/dommy/element.rb', line 1393 def [](key) __js_get__(key.to_s) end |
#[]=(key, value) ⇒ Object
1397 1398 1399 |
# File 'lib/dommy/element.rb', line 1397 def []=(key, value) __js_set__(key.to_s, value) end |
#__js_call__(method, args) ⇒ Object
1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 |
# File 'lib/dommy/element.rb', line 1576 def __js_call__(method, args) case method when "getAttribute" get_attribute(args[0]) when "setAttribute" set_attribute(args[0], args[1]) when "hasAttribute" has_attribute?(args[0]) when "removeAttribute" remove_attribute(args[0]) when "getAttributeNames" @__node__.attribute_nodes.map(&:name) when "closest" closest(args[0]) when "querySelector" query_selector(args[0]) when "querySelectorAll" query_selector_all(args[0]) when "getElementsByClassName" get_elements_by_class_name(args[0]) when "getElementsByTagName" get_elements_by_tag_name(args[0]) when "insertAdjacentElement" insert_adjacent_element(args[0], args[1]) when "insertAdjacentHTML" insert_adjacent_html(args[0], args[1]) when "insertAdjacentText" insert_adjacent_text(args[0], args[1]) when "toggleAttribute" toggle_attribute(args[0], args[1]) when "matches" matches?(args[0]) when "toString" to_s when "getAttributeNode" get_attribute_node(args[0]) when "setAttributeNode" set_attribute_node(args[0]) when "removeAttributeNode" remove_attribute_node(args[0]) when "focus" focus when "blur" blur when "attachShadow" attach_shadow(args[0]) when "addEventListener" add_event_listener(args[0], args[1], args[2]) when "removeEventListener" remove_event_listener(args[0], args[1]) when "dispatchEvent" dispatch_event(args[0]) when "appendChild" append_child(args[0]) when "insertBefore" insert_before(args[0], args[1]) when "removeChild" remove_child(args[0]) when "replaceChild" replace_child(args[0], args[1]) when "cloneNode" clone_node(args[0]) when "append" append_nodes(args) when "prepend" prepend_nodes(args) when "replaceChildren" replace_children(*args) when "before" insert_adjacent(:before, args) when "after" insert_adjacent(:after, args) when "getInnerHTML", "getHTML" inner_html when "remove" parent = @__node__.parent @__node__.unlink @document.notify_child_list_mutation(target_node: parent, added_nodes: [], removed_nodes: [@__node__]) if parent nil when "replaceWith" replace_with(args) when "click" dispatch_event(MouseEvent.new("click", "bubbles" => true, "cancelable" => true, "button" => 0)) when "getBoundingClientRect" DOMRect.new else nil end end |
#__js_get__(key) ⇒ Object
1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 |
# File 'lib/dommy/element.rb', line 1401 def __js_get__(key) case key when "nodeType" 1 when "isConnected" is_connected? when "children" @live_children when "firstElementChild" @document.wrap_node(@__node__.element_children.first) when "parentElement", "parent" wrap_parent(@__node__.parent) when "parentNode" # `parentNode` is broader than `parentElement` — includes # DocumentFragment / Document parents too. Reconcilers use # this to find the host before calling replaceChild. @__node__.parent && @document.wrap_node(@__node__.parent) when "textContent" @__node__.text when "innerHTML" if @__node__.name == "template" @document.template_content_inner_html(self) else @__node__.inner_html end when "tagName" @__node__.name.upcase when "classList" @class_list when "style" @style when "dataset" @dataset when "content" template_content when "className" # DOM reflects the `class` attribute as the `className` string # property (space-separated tokens, "" when absent). @__node__["class"].to_s when "id" @__node__["id"].to_s when "hidden", "disabled", "checked", "readOnly", "multiple", "required" # Boolean reflected properties — true iff the matching HTML # attribute is present. Real DOM normalizes attribute names to # lowercase, mapped here too (e.g. `readOnly` ↔ `readonly`). @__node__.key?(reflected_attr_name(key)) when "value" # For form elements `value` is a property that defaults to the # `value` attribute. We don't model the property/attribute # split here — both reads and writes go through the attribute. @__node__["value"].to_s when "href" anchor_href when "attributes" attributes when "namespaceURI" namespace_uri when "localName" local_name when "nodeName" @__node__.name.upcase when "slot" slot when "role" role when "baseURI" base_uri when "shadowRoot" shadow_root when "ownerDocument" @document else # `el.onXxx` event handler property — returns the registered # callback (if any), or nil. if key.start_with?("on") && key.length > 2 @on_handlers&.[](event_name_from_on(key)) end end end |
#__js_set__(key, value) ⇒ Object
1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 |
# File 'lib/dommy/element.rb', line 1503 def __js_set__(key, value) case key when "textContent" @__node__.content = value.to_s when "innerHTML" removed = @__node__.children.to_a if @__node__.name == "template" # `<template>` content is invisible to outer selectors in # real DOM (it lives in a separate DocumentFragment exposed # via `[:content]`). Mirror that here so child placeholders # inside the template don't pollute outer queries. @document.attach_template_content(self, value.to_s) else @__node__.inner_html = value.to_s @document.migrate_template_descendants(@__node__) end @document.notify_child_list_mutation( target_node: @__node__, added_nodes: @__node__.children.to_a, removed_nodes: removed ) when "hidden", "disabled", "checked", "readOnly", "multiple", "required" # Boolean reflected property — funnel through set_attribute / # remove_attribute so MutationObserver attribute records fire. name = reflected_attr_name(key) if value set_attribute(name, "") elsif @__node__.key?(name) remove_attribute(name) end when "className" set_attribute("class", value.to_s) when "id" set_attribute("id", value.to_s) when "value" set_attribute("value", value.to_s) when "slot" set_attribute("slot", value.to_s) when "role" set_attribute("role", value.to_s) else # `el.onXxx = fn` registers fn as a single named handler. # Setting to nil removes it. Mirrors HTMLElement IDL. if key.start_with?("on") && key.length > 2 set_on_handler(event_name_from_on(key), value) else nil end end end |
#__shadow_root__ ⇒ Object
Internal — gives access to the shadow root regardless of mode. Used by event composition / ‘composedPath()`.
1134 1135 1136 |
# File 'lib/dommy/element.rb', line 1134 def __shadow_root__ @__shadow_root end |
#after(*args) ⇒ Object
1359 1360 1361 |
# File 'lib/dommy/element.rb', line 1359 def after(*args) insert_adjacent(:after, args) end |
#anchor_href ⇒ Object
Anchor / area ‘href` IDL attribute reflects the attribute resolved against the document base URL (browser semantics). Routers rely on this to compare origins and detect external links.
1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 |
# File 'lib/dommy/element.rb', line 1485 def anchor_href raw = @__node__["href"] return "" if raw.nil? win = @document.default_view base = win&.location ? win.location.href : "" URI.join(base, raw.to_s).to_s rescue URI::InvalidURIError, ArgumentError raw.to_s end |
#append(*args) ⇒ Object
ParentNode mixin methods — append / prepend / replaceChildren take a mix of Node and String args (strings become text nodes).
1336 1337 1338 |
# File 'lib/dommy/element.rb', line 1336 def append(*args) append_nodes(args) end |
#append_child(child) ⇒ Object
1763 1764 1765 1766 1767 1768 1769 |
# File 'lib/dommy/element.rb', line 1763 def append_child(child) check_hierarchy!(child) nodes = detach_dom_nodes(child) append_dom_nodes(nodes) @document.notify_child_list_mutation(target_node: @__node__, added_nodes: nodes, removed_nodes: []) child end |
#attach_shadow(options = nil) ⇒ Object
‘el.attachShadow({ mode: “open” | “closed” })` — creates and attaches a ShadowRoot. The shadow tree lives in its own Nokogiri fragment and is invisible to the outer querySelector / children chain. Per spec:
- the `mode` field is REQUIRED in the init dict
- only certain host element types are valid (see SHADOW_HOST_TAGS)
- re-attaching to an element that already has a shadow throws
1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 |
# File 'lib/dommy/element.rb', line 1099 def attach_shadow( = nil) tag = @__node__.name.downcase unless SHADOW_HOST_TAGS.include?(tag) || tag.include?("-") raise DOMException::NotSupportedError, "<#{tag}> cannot host a shadow root" end raise DOMException::InvalidStateError, "Shadow root already attached" if @__shadow_root opts = .is_a?(Hash) ? : {} mode_raw = opts.key?("mode") ? opts["mode"] : opts[:mode] raise TypeError, "attachShadow init dictionary requires 'mode'" if mode_raw.nil? mode = mode_raw.to_s raise DOMException::SyntaxError, "mode must be 'open' or 'closed'" unless %w[open closed].include?(mode) @__shadow_root = ShadowRoot.new( self, mode: mode, delegates_focus: opts["delegatesFocus"] || opts[:delegatesFocus] || false, slot_assignment: opts["slotAssignment"] || opts[:slotAssignment] || "named" ) @__shadow_root end |
#attributes ⇒ Object
NamedNodeMap of attributes. Lazily allocated and re-used so ‘el.attributes === el.attributes` and `attr.ownerElement === el`.
968 969 970 |
# File 'lib/dommy/element.rb', line 968 def attributes @attributes ||= NamedNodeMap.new(self) end |
#base_uri ⇒ Object
‘Node.baseURI` — resolves against the document’s base URL, which in turn honors the first ‘<base href>` element (see `Document#base_uri`).
1017 1018 1019 |
# File 'lib/dommy/element.rb', line 1017 def base_uri @document.base_uri end |
#before(*args) ⇒ Object
ChildNode mixin — before / after / replaceWith with mixed args.
1355 1356 1357 |
# File 'lib/dommy/element.rb', line 1355 def before(*args) insert_adjacent(:before, args) end |
#blur ⇒ Object
1063 1064 1065 1066 |
# File 'lib/dommy/element.rb', line 1063 def blur @document.__set_active_element__(nil) nil end |
#child_element_count ⇒ Object
784 785 786 |
# File 'lib/dommy/element.rb', line 784 def child_element_count @__node__.element_children.size end |
#child_nodes ⇒ Object
788 789 790 |
# File 'lib/dommy/element.rb', line 788 def child_nodes NodeList.new(@__node__.children.map { |n| @document.wrap_node(n) }.compact) end |
#children ⇒ Object
754 755 756 |
# File 'lib/dommy/element.rb', line 754 def children @live_children end |
#class_list ⇒ Object
742 743 744 |
# File 'lib/dommy/element.rb', line 742 def class_list @class_list end |
#class_name ⇒ Object
734 735 736 |
# File 'lib/dommy/element.rb', line 734 def class_name @__node__["class"].to_s end |
#class_name=(value) ⇒ Object
738 739 740 |
# File 'lib/dommy/element.rb', line 738 def class_name=(value) set_attribute("class", value.to_s) end |
#click ⇒ Object
1378 1379 1380 |
# File 'lib/dommy/element.rb', line 1378 def click __js_call__("click", []) end |
#clone_node(deep_arg) ⇒ Object
1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 |
# File 'lib/dommy/element.rb', line 1823 def clone_node(deep_arg) deep = !!deep_arg if deep @document.wrap_node( Parser.fragment(@__node__.to_html, owner_doc: @document.nokogiri_doc).children.find(&:element?) ) else clone = @document.create_element(@__node__.name) @__node__.attribute_nodes.each do |attr| clone.__js_call__("setAttribute", [attr.name, attr.value]) end clone end end |
#closest(selector) ⇒ Object
1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 |
# File 'lib/dommy/element.rb', line 1738 def closest(selector) return nil if selector.nil? || selector.to_s.empty? node = @__node__ while node&.element? return @document.wrap_node(node) if matches_selector?(node, selector.to_s) node = node.parent end nil end |
#compare_document_position(other) ⇒ Object
Standard DOM compareDocumentPosition. Returns 0 for self, a CONTAINS/CONTAINED_BY bitmask for ancestor/descendant pairs, or PRECEDING/FOLLOWING for siblings (and DISCONNECTED for unrelated nodes).
1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 |
# File 'lib/dommy/element.rb', line 1239 def compare_document_position(other) return 0 if equal?(other) return DOCUMENT_POSITION_DISCONNECTED unless other.respond_to?(:__node__) self_node = @__node__ other_node = other.__node__ self_ancestors = ancestor_chain(self_node) other_ancestors = ancestor_chain(other_node) common = nil self_ancestors.each do |a| if other_ancestors.include?(a) common = a break end end unless common return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOCUMENT_POSITION_PRECEDING end if common == self_node return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING elsif common == other_node return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING end # Sibling-of-some-level case: compare the two branch points # under the common ancestor. self_branch = branch_under(common, self_ancestors) other_branch = branch_under(common, other_ancestors) common.children.each do |child| if child == self_branch return DOCUMENT_POSITION_FOLLOWING elsif child == other_branch return DOCUMENT_POSITION_PRECEDING end end DOCUMENT_POSITION_DISCONNECTED end |
#contains?(other) ⇒ Boolean
‘el.contains(other)` — true if `other` is `el` itself or any descendant. Per spec, returns false for null/non-Node.
868 869 870 871 872 873 874 875 |
# File 'lib/dommy/element.rb', line 868 def contains?(other) return false unless other.respond_to?(:__node__) other_node = other.__node__ return true if other_node == @__node__ Internal::NodeTraversal.ancestor_of?(@__node__, other_node) end |
#dataset ⇒ Object
750 751 752 |
# File 'lib/dommy/element.rb', line 750 def dataset @dataset end |
#equal_node?(other) ⇒ Boolean
Structural equality — same nodeType, same tagName, same attribute set, and recursively-equal children. Used by linkedom test suite and standard DOM Node.isEqualNode.
1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 |
# File 'lib/dommy/element.rb', line 1292 def equal_node?(other) return false unless other.is_a?(Element) return false unless @__node__.name == other.__node__.name return false unless attribute_signature == other.send(:attribute_signature) return false unless @__node__.children.size == other.__node__.children.size @__node__.children.zip(other.__node__.children).all? do |a, b| wa = @document.wrap_node(a) wb = @document.wrap_node(b) wa.respond_to?(:equal_node?) ? wa.equal_node?(wb) : a.content == b.content end end |
#first_child ⇒ Object
776 777 778 |
# File 'lib/dommy/element.rb', line 776 def first_child @document.wrap_node(@__node__.children.first) end |
#first_element_child ⇒ Object
768 769 770 |
# File 'lib/dommy/element.rb', line 768 def first_element_child @document.wrap_node(@__node__.element_children.first) end |
#focus ⇒ Object
‘focus()` / `blur()` — Dommy has no layout / real focus, but tests rely on `document.activeElement` updating. Track the most recently focused element on the document.
1058 1059 1060 1061 |
# File 'lib/dommy/element.rb', line 1058 def focus @document.__set_active_element__(self) nil end |
#get_attribute(name) ⇒ Object
HTML attribute names are case-insensitive — browser DOM stores them in lowercase regardless of the case passed to setAttribute. Matches that behavior so callers using ‘“SRC”` / `“Action”` / etc. interoperate with `getAttribute(“src”)` round-trips.
1704 1705 1706 1707 1708 |
# File 'lib/dommy/element.rb', line 1704 def get_attribute(name) return nil if name.nil? @__node__[name.to_s.downcase] end |
#get_attribute_node(name) ⇒ Object
972 973 974 |
# File 'lib/dommy/element.rb', line 972 def get_attribute_node(name) attributes.get_named_item(name) end |
#get_elements_by_class_name(name) ⇒ Object
943 944 945 946 947 948 949 950 951 952 953 |
# File 'lib/dommy/element.rb', line 943 def get_elements_by_class_name(name) tokens = name.to_s.split(/\s+/).reject(&:empty?) root = @__node__ doc = @document HTMLCollection.new do next [] if tokens.empty? selector = tokens.map { |t| ".#{t}" }.join("") root.css(selector).map { |n| doc.wrap_node(n) }.compact end end |
#get_elements_by_tag_name(name) ⇒ Object
955 956 957 958 959 960 961 962 963 964 |
# File 'lib/dommy/element.rb', line 955 def get_elements_by_tag_name(name) n = name.to_s.downcase root = @__node__ doc = @document if n == "*" HTMLCollection.new { root.css("*").map { |x| doc.wrap_node(x) }.compact } else HTMLCollection.new { root.css(n).map { |x| doc.wrap_node(x) }.compact } end end |
#get_html(_options = nil) ⇒ Object
1374 1375 1376 |
# File 'lib/dommy/element.rb', line 1374 def get_html( = nil) inner_html end |
#get_inner_html(_options = nil) ⇒ Object
‘getInnerHTML()` — happy-dom alias for the `innerHTML` getter. Real browsers add a `{ includeShadowRoots }` option which we ignore (no Shadow DOM in Dommy).
1370 1371 1372 |
# File 'lib/dommy/element.rb', line 1370 def get_inner_html( = nil) inner_html end |
#has_attribute?(name) ⇒ Boolean
1720 1721 1722 1723 1724 |
# File 'lib/dommy/element.rb', line 1720 def has_attribute?(name) return false if name.nil? @__node__.key?(name.to_s.downcase) end |
#has_attributes? ⇒ Boolean
804 805 806 |
# File 'lib/dommy/element.rb', line 804 def has_attributes? @__node__.attribute_nodes.any? end |
#has_child_nodes? ⇒ Boolean
800 801 802 |
# File 'lib/dommy/element.rb', line 800 def has_child_nodes? @__node__.children.any? end |
#id ⇒ Object
726 727 728 |
# File 'lib/dommy/element.rb', line 726 def id @__node__["id"].to_s end |
#id=(value) ⇒ Object
730 731 732 |
# File 'lib/dommy/element.rb', line 730 def id=(value) set_attribute("id", value.to_s) end |
#inner_html ⇒ Object
714 715 716 |
# File 'lib/dommy/element.rb', line 714 def inner_html __js_get__("innerHTML") end |
#inner_html=(value) ⇒ Object
718 719 720 |
# File 'lib/dommy/element.rb', line 718 def inner_html=(value) __js_set__("innerHTML", value) end |
#insert_adjacent_element(position, element) ⇒ Object
‘el.insertAdjacentElement(position, element)` — DOM spec positions: “beforebegin”, “afterbegin”, “beforeend”, “afterend”. Returns the inserted element or nil if position has no anchor (root cases).
1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 |
# File 'lib/dommy/element.rb', line 1141 def insert_adjacent_element(position, element) return nil unless element.respond_to?(:__node__) case position.to_s when "beforebegin" return nil unless @__node__.parent node = detach_for_insert(element) @__node__.add_previous_sibling(node) @document.notify_child_list_mutation(target_node: @__node__.parent, added_nodes: [node], removed_nodes: []) when "afterbegin" node = detach_for_insert(element) first = @__node__.children.first first ? first.add_previous_sibling(node) : @__node__.add_child(node) @document.notify_child_list_mutation(target_node: @__node__, added_nodes: [node], removed_nodes: []) when "beforeend" node = detach_for_insert(element) @__node__.add_child(node) @document.notify_child_list_mutation(target_node: @__node__, added_nodes: [node], removed_nodes: []) when "afterend" return nil unless @__node__.parent node = detach_for_insert(element) @__node__.add_next_sibling(node) @document.notify_child_list_mutation(target_node: @__node__.parent, added_nodes: [node], removed_nodes: []) else return nil end element end |
#insert_adjacent_html(position, html) ⇒ Object
1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 |
# File 'lib/dommy/element.rb', line 1173 def insert_adjacent_html(position, html) fragment = Parser.fragment(html.to_s, owner_doc: @__node__.document) nodes = fragment.children.to_a case position.to_s when "beforebegin" return nil unless @__node__.parent nodes.reverse_each { |n| @__node__.add_previous_sibling(n) } @document.notify_child_list_mutation(target_node: @__node__.parent, added_nodes: nodes, removed_nodes: []) when "afterbegin" first = @__node__.children.first if first nodes.reverse_each { |n| first.add_previous_sibling(n) } else nodes.each { |n| @__node__.add_child(n) } end @document.notify_child_list_mutation(target_node: @__node__, added_nodes: nodes, removed_nodes: []) when "beforeend" nodes.each { |n| @__node__.add_child(n) } @document.notify_child_list_mutation(target_node: @__node__, added_nodes: nodes, removed_nodes: []) when "afterend" return nil unless @__node__.parent nodes.reverse_each { |n| @__node__.add_next_sibling(n) } @document.notify_child_list_mutation(target_node: @__node__.parent, added_nodes: nodes, removed_nodes: []) end nil end |
#insert_adjacent_text(position, text) ⇒ Object
1204 1205 1206 1207 1208 |
# File 'lib/dommy/element.rb', line 1204 def insert_adjacent_text(position, text) return nil if text.to_s.empty? insert_adjacent_element(position, @document.create_text_node(text.to_s)) end |
#insert_before(child, reference) ⇒ Object
1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 |
# File 'lib/dommy/element.rb', line 1771 def insert_before(child, reference) check_hierarchy!(child) nodes = detach_dom_nodes(child) if reference.nil? append_dom_nodes(nodes) else ref_node = unwrap_dom_node(reference) if ref_node&.parent != @__node__ # Per spec this should be a NotFoundError, but the legacy # behaviour of `appendChild` when reference is foreign is a # silent append. Preserve that for compatibility. append_dom_nodes(nodes) else nodes.reverse_each { |node| ref_node.add_previous_sibling(node) } end end @document.notify_child_list_mutation(target_node: @__node__, added_nodes: nodes, removed_nodes: []) child end |
#is_connected? ⇒ Boolean Also known as: connected?
Walks parents up to the Document (or false when the chain dead-ends). Crosses ShadowRoot boundaries: a node inside an open or closed shadow tree is connected iff its host is.
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 |
# File 'lib/dommy/element.rb', line 1028 def is_connected? current = @__node__ seen = {} loop do # Guard against unexpected cycles in malformed trees. return false if seen[current.object_id] seen[current.object_id] = true parent = current.respond_to?(:parent) ? current.parent : nil return false unless parent return true if parent.is_a?(Nokogiri::XML::Document) sr = @document.__shadow_root_for_fragment__(parent) if sr host = sr.host return false unless host current = host.__node__ else current = parent end end end |
#last_child ⇒ Object
780 781 782 |
# File 'lib/dommy/element.rb', line 780 def last_child @document.wrap_node(@__node__.children.last) end |
#last_element_child ⇒ Object
772 773 774 |
# File 'lib/dommy/element.rb', line 772 def last_element_child @document.wrap_node(@__node__.element_children.last) end |
#live_child_nodes ⇒ Object
Live NodeList over this element’s children. Reflects later mutations on every access.
794 795 796 797 798 |
# File 'lib/dommy/element.rb', line 794 def live_child_nodes @live_child_nodes ||= LiveNodeList.new do @__node__.children.map { |n| @document.wrap_node(n) }.compact end end |
#local_name ⇒ Object
992 993 994 |
# File 'lib/dommy/element.rb', line 992 def local_name @__node__.name.downcase end |
#matches?(selector) ⇒ Boolean
935 936 937 938 939 940 941 |
# File 'lib/dommy/element.rb', line 935 def matches?(selector) return false if selector.nil? || selector.to_s.empty? # `:scope` pseudo — match against this element itself. sel = selector.to_s.gsub(":scope", "*:nth-last-child(n)") matches_selector?(@__node__, sel) end |
#namespace_uri ⇒ Object
HTML namespace constants — most HTML elements live in xhtml ns.
987 988 989 990 |
# File 'lib/dommy/element.rb', line 987 def namespace_uri ns = @__node__.namespace ns ? ns.href : "http://www.w3.org/1999/xhtml" end |
#next_element_sibling ⇒ Object
816 817 818 819 820 |
# File 'lib/dommy/element.rb', line 816 def next_element_sibling node = @__node__.next node = node.next while node && !node.element? node && @document.wrap_node(node) end |
#next_sibling ⇒ Object
808 809 810 |
# File 'lib/dommy/element.rb', line 808 def next_sibling @__node__.next && @document.wrap_node(@__node__.next) end |
#normalize ⇒ Object
Merge adjacent text node siblings and drop empty text nodes.
906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 |
# File 'lib/dommy/element.rb', line 906 def normalize @__node__.traverse do |node| next unless node.text? next if node.parent.nil? if node.content == "" && node.parent node.unlink elsif node.next && node.next.text? node.content = node.content + node.next.content node.next.unlink end end nil end |
#on(type, &block) ⇒ Object
Ruby block-style listener (in addition to the (type, callable, options) form inherited from EventTarget). Returns the resolved listener so callers can pass it back to remove_event_listener.
1385 1386 1387 1388 |
# File 'lib/dommy/element.rb', line 1385 def on(type, &block) add_event_listener(type, block) block end |
#outer_html ⇒ Object
Outer HTML — serializes this element and its subtree. Setter replaces this element in its parent with the parsed fragment.
830 831 832 |
# File 'lib/dommy/element.rb', line 830 def outer_html @__node__.to_html end |
#outer_html=(html) ⇒ Object
Per WHATWG DOM Parsing:
- parent is null (detached element) → return silently
- parent is the Document (`<html>` element) → throw
NoModificationAllowedError (can't replace the document
element via this API)
- otherwise, parse `html` as a fragment in the parent's
context and replace this element with the parsed nodes
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 |
# File 'lib/dommy/element.rb', line 841 def outer_html=(html) parent = @__node__.parent return unless parent if parent.is_a?(Nokogiri::XML::Document) raise( DOMException::NoModificationAllowedError, "outerHTML setter not allowed on the document element" ) end fragment = Parser.fragment(html.to_s, owner_doc: @__node__.document) anchor = @__node__.next_sibling removed = @__node__ new_nodes = fragment.children.to_a @__node__.unlink if anchor new_nodes.reverse_each { |n| anchor.add_previous_sibling(n) } else new_nodes.each { |n| parent.add_child(n) } end @document.notify_child_list_mutation(target_node: parent, added_nodes: new_nodes, removed_nodes: [removed]) end |
#owner_document ⇒ Object
1021 1022 1023 |
# File 'lib/dommy/element.rb', line 1021 def owner_document @document end |
#parent_element ⇒ Object Also known as: parent
758 759 760 |
# File 'lib/dommy/element.rb', line 758 def parent_element @document.wrap_node(@__node__.parent) if @__node__.parent&.element? end |
#parent_node ⇒ Object
764 765 766 |
# File 'lib/dommy/element.rb', line 764 def parent_node @__node__.parent && @document.wrap_node(@__node__.parent) end |
#prepend(*args) ⇒ Object
1340 1341 1342 |
# File 'lib/dommy/element.rb', line 1340 def prepend(*args) prepend_nodes(args) end |
#previous_element_sibling ⇒ Object
822 823 824 825 826 |
# File 'lib/dommy/element.rb', line 822 def previous_element_sibling node = @__node__.previous node = node.previous while node && !node.element? node && @document.wrap_node(node) end |
#previous_sibling ⇒ Object
812 813 814 |
# File 'lib/dommy/element.rb', line 812 def previous_sibling @__node__.previous && @document.wrap_node(@__node__.previous) end |
#query_selector(selector) ⇒ Object
1751 1752 1753 1754 1755 |
# File 'lib/dommy/element.rb', line 1751 def query_selector(selector) return nil if selector.nil? || selector.to_s.empty? @document.wrap_node(@__node__.at_css(selector.to_s)) end |
#query_selector_all(selector) ⇒ Object
1757 1758 1759 1760 1761 |
# File 'lib/dommy/element.rb', line 1757 def query_selector_all(selector) return NodeList.new if selector.nil? || selector.to_s.empty? NodeList.new(@__node__.css(selector.to_s).map { |node| @document.wrap_node(node) }.compact) end |
#reflected_attr_name(key) ⇒ Object
Map a JS boolean property name to its underlying HTML attribute. HTML attribute names are lowercase; the DOM property may be camelCase (‘readOnly` → `readonly`).
1499 1500 1501 |
# File 'lib/dommy/element.rb', line 1499 def reflected_attr_name(key) {"readOnly" => "readonly"}.fetch(key, key) end |
#remove ⇒ Object
1329 1330 1331 |
# File 'lib/dommy/element.rb', line 1329 def remove __js_call__("remove", []) end |
#remove_attribute(name) ⇒ Object
1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 |
# File 'lib/dommy/element.rb', line 1726 def remove_attribute(name) return nil if name.nil? key = name.to_s.downcase return nil unless @__node__.key?(key) old = @__node__[key] @__node__.remove_attribute(key) @document.notify_attribute_mutation(target_node: @__node__, attribute_name: key, old_value: old) nil end |
#remove_attribute_node(attr) ⇒ Object
980 981 982 983 984 |
# File 'lib/dommy/element.rb', line 980 def remove_attribute_node(attr) return nil unless attr.respond_to?(:name) attributes.remove_named_item(attr.name) end |
#remove_child(child) ⇒ Object
1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 |
# File 'lib/dommy/element.rb', line 1792 def remove_child(child) node = unwrap_dom_node(child) unless node&.parent == @__node__ raise DOMException::NotFoundError, "node is not a child of this element" end node.unlink @document.notify_child_list_mutation(target_node: @__node__, added_nodes: [], removed_nodes: [node]) child end |
#replace_child(new_child, old_child) ⇒ Object
‘node.replaceChild(newChild, oldChild)` — required for in-place item updates in list reconcilers. Inserts newChild where oldChild was, then unlinks oldChild. Notifies MutationObserver of both changes in one record so observers see the swap atomically.
1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 |
# File 'lib/dommy/element.rb', line 1808 def replace_child(new_child, old_child) old_node = unwrap_dom_node(old_child) return nil unless old_node&.parent == @__node__ new_nodes = detach_dom_nodes(new_child) new_nodes.reverse_each { |node| old_node.add_previous_sibling(node) } old_node.unlink @document.notify_child_list_mutation( target_node: @__node__, added_nodes: new_nodes, removed_nodes: [old_node] ) old_child end |
#replace_children(*args) ⇒ Object
1344 1345 1346 1347 1348 1349 1350 1351 |
# File 'lib/dommy/element.rb', line 1344 def replace_children(*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) } @document.notify_child_list_mutation(target_node: @__node__, added_nodes: nodes, removed_nodes: removed) nil end |
#replace_with_nodes(*args) ⇒ Object
1363 1364 1365 |
# File 'lib/dommy/element.rb', line 1363 def replace_with_nodes(*args) replace_with(args) end |
#role ⇒ Object
1006 1007 1008 |
# File 'lib/dommy/element.rb', line 1006 def role @__node__["role"].to_s end |
#role=(value) ⇒ Object
1010 1011 1012 |
# File 'lib/dommy/element.rb', line 1010 def role=(value) set_attribute("role", value.to_s) end |
#root_node ⇒ Object Also known as: get_root_node
‘el.getRootNode()` — returns the topmost ancestor (document, ShadowRoot, fragment, or self if detached). If the element lives inside a shadow tree, returns that ShadowRoot. Otherwise walks until we hit the Nokogiri Document (then returns the Document).
881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 |
# File 'lib/dommy/element.rb', line 881 def root_node sr = @document.__shadow_root_containing__(@__node__) return sr if sr current = @__node__ attached = false loop do parent = current.respond_to?(:parent) ? current.parent : nil break unless parent if parent.is_a?(Nokogiri::XML::Document) attached = true break end current = parent end return @document if attached @document.wrap_node(current) || @document end |
#same_node?(other) ⇒ Boolean
‘Node.isSameNode(other)` — strict reference identity. The DOM spec deprecates this in favor of `===`, but linkedom-style tests still call it.
1285 1286 1287 |
# File 'lib/dommy/element.rb', line 1285 def same_node?(other) equal?(other) end |
#set_attribute(name, value) ⇒ Object
1710 1711 1712 1713 1714 1715 1716 1717 1718 |
# File 'lib/dommy/element.rb', line 1710 def set_attribute(name, value) return nil if name.nil? key = name.to_s.downcase old = @__node__[key] @__node__[key] = value.to_s @document.notify_attribute_mutation(target_node: @__node__, attribute_name: key, old_value: old) nil end |
#set_attribute_node(attr) ⇒ Object
976 977 978 |
# File 'lib/dommy/element.rb', line 976 def set_attribute_node(attr) attributes.set_named_item(attr) end |
#shadow_root ⇒ Object
‘el.shadowRoot` — returns the attached ShadowRoot only when mode is “open”; closed shadows are hidden from external code.
1125 1126 1127 1128 1129 1130 |
# File 'lib/dommy/element.rb', line 1125 def shadow_root return nil unless @__shadow_root return nil if @__shadow_root.mode == "closed" @__shadow_root end |
#slot ⇒ Object
‘slot` and `role` are simple reflected string attributes —added as named accessors for happy-dom test parity.
998 999 1000 |
# File 'lib/dommy/element.rb', line 998 def slot @__node__["slot"].to_s end |
#slot=(value) ⇒ Object
1002 1003 1004 |
# File 'lib/dommy/element.rb', line 1002 def slot=(value) set_attribute("slot", value.to_s) end |
#style ⇒ Object
746 747 748 |
# File 'lib/dommy/element.rb', line 746 def style @style end |
#tag_name ⇒ Object
722 723 724 |
# File 'lib/dommy/element.rb', line 722 def tag_name @__node__.name.upcase end |
#text_content ⇒ Object
—– Public Ruby API (snake_case) —–
Mirrors HTMLElement DOM properties / methods in idiomatic Ruby form. The bridge protocol (‘js_get` / `js_call`) routes camelCase JS names through these same accessors, so any fix here is visible in both views.
706 707 708 |
# File 'lib/dommy/element.rb', line 706 def text_content @__node__.text end |
#text_content=(value) ⇒ Object
710 711 712 |
# File 'lib/dommy/element.rb', line 710 def text_content=(value) __js_set__("textContent", value) end |
#to_s ⇒ Object
Convenience alias matching the DOM idiom ‘String(el)` → outerHTML.
1211 1212 1213 |
# File 'lib/dommy/element.rb', line 1211 def to_s outer_html end |
#toggle_attribute(name, force = nil) ⇒ Object
922 923 924 925 926 927 928 929 930 931 932 933 |
# File 'lib/dommy/element.rb', line 922 def toggle_attribute(name, force = nil) key = name.to_s.downcase present = @__node__.key?(key) desired = force.nil? ? !present : !!force if desired set_attribute(key, "") unless present true else remove_attribute(key) if present false end end |