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
-
#__scroll_log__ ⇒ Object
Test inspector for scroll calls (no real layout to scroll).
-
#__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).
-
#animate(keyframes, options = nil) ⇒ Object
Web Animations: start an animation on this element.
-
#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_animations(_options = nil) ⇒ Object (also: #getAnimations)
- #get_attribute(name) ⇒ Object
- #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.
701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 |
# File 'lib/dommy/element.rb', line 701 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__.
699 700 701 |
# File 'lib/dommy/element.rb', line 699 def __node__ @__node__ end |
#document ⇒ Object (readonly)
Returns the value of attribute document.
699 700 701 |
# File 'lib/dommy/element.rb', line 699 def document @document end |
Instance Method Details
#[](key) ⇒ Object
1411 1412 1413 |
# File 'lib/dommy/element.rb', line 1411 def [](key) __js_get__(key.to_s) end |
#[]=(key, value) ⇒ Object
1415 1416 1417 |
# File 'lib/dommy/element.rb', line 1415 def []=(key, value) __js_set__(key.to_s, value) end |
#__js_call__(method, args) ⇒ Object
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 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 |
# File 'lib/dommy/element.rb', line 1614 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 when "getClientRects" [] when "scrollIntoView", "scroll", "scrollTo", "scrollBy" # No layout — record the request for tests to assert against. @__scroll_log__ ||= [] @__scroll_log__ << [method, args] nil when "requestFullscreen" @document.__set_fullscreen_element__(self) PromiseValue.resolve(@document.default_view, nil) when "showPopover" toggle_popover_state(true) nil when "hidePopover" toggle_popover_state(false) nil when "togglePopover" new_state = !@__popover_open__ toggle_popover_state(new_state) new_state else nil end end |
#__js_get__(key) ⇒ Object
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 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 |
# File 'lib/dommy/element.rb', line 1419 def __js_get__(key) case key when "nodeType" 1 when "isConnected" is_connected? when "scrollTop", "scrollLeft", "scrollWidth", "scrollHeight", "clientWidth", "clientHeight", "clientTop", "clientLeft", "offsetWidth", "offsetHeight", "offsetTop", "offsetLeft" # No layout engine — zeroed values match what real browsers # report for hidden / pre-paint elements. 0 when "offsetParent" nil when "popover" get_attribute("popover") 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
1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 |
# File 'lib/dommy/element.rb', line 1541 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 |
#__scroll_log__ ⇒ Object
Test inspector for scroll calls (no real layout to scroll).
2069 2070 2071 |
# File 'lib/dommy/element.rb', line 2069 def __scroll_log__ @__scroll_log__ ||= [] end |
#__shadow_root__ ⇒ Object
Internal — gives access to the shadow root regardless of mode. Used by event composition / ‘composedPath()`.
1152 1153 1154 |
# File 'lib/dommy/element.rb', line 1152 def __shadow_root__ @__shadow_root end |
#after(*args) ⇒ Object
1377 1378 1379 |
# File 'lib/dommy/element.rb', line 1377 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.
1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 |
# File 'lib/dommy/element.rb', line 1523 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 |
#animate(keyframes, options = nil) ⇒ Object
Web Animations: start an animation on this element. Returns the new Animation. Dommy doesn’t interpolate; the animation simply transitions through the ‘playState` lifecycle, finishing via `scheduler.advance_time(duration)` or an explicit `animation.finish`.
1826 1827 1828 1829 1830 1831 1832 1833 |
# File 'lib/dommy/element.rb', line 1826 def animate(keyframes, = nil) effect = KeyframeEffect.new(self, keyframes, ) animation = Animation.new(effect, nil, window: @document.default_view) @__animations ||= [] @__animations << animation animation.play animation end |
#append(*args) ⇒ Object
ParentNode mixin methods — append / prepend / replaceChildren take a mix of Node and String args (strings become text nodes).
1354 1355 1356 |
# File 'lib/dommy/element.rb', line 1354 def append(*args) append_nodes(args) end |
#append_child(child) ⇒ Object
1853 1854 1855 1856 1857 1858 1859 |
# File 'lib/dommy/element.rb', line 1853 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
1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 |
# File 'lib/dommy/element.rb', line 1117 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`.
986 987 988 |
# File 'lib/dommy/element.rb', line 986 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`).
1035 1036 1037 |
# File 'lib/dommy/element.rb', line 1035 def base_uri @document.base_uri end |
#before(*args) ⇒ Object
ChildNode mixin — before / after / replaceWith with mixed args.
1373 1374 1375 |
# File 'lib/dommy/element.rb', line 1373 def before(*args) insert_adjacent(:before, args) end |
#blur ⇒ Object
1081 1082 1083 1084 |
# File 'lib/dommy/element.rb', line 1081 def blur @document.__set_active_element__(nil) nil end |
#child_element_count ⇒ Object
802 803 804 |
# File 'lib/dommy/element.rb', line 802 def child_element_count @__node__.element_children.size end |
#child_nodes ⇒ Object
806 807 808 |
# File 'lib/dommy/element.rb', line 806 def child_nodes NodeList.new(@__node__.children.map { |n| @document.wrap_node(n) }.compact) end |
#children ⇒ Object
772 773 774 |
# File 'lib/dommy/element.rb', line 772 def children @live_children end |
#class_list ⇒ Object
760 761 762 |
# File 'lib/dommy/element.rb', line 760 def class_list @class_list end |
#class_name ⇒ Object
752 753 754 |
# File 'lib/dommy/element.rb', line 752 def class_name @__node__["class"].to_s end |
#class_name=(value) ⇒ Object
756 757 758 |
# File 'lib/dommy/element.rb', line 756 def class_name=(value) set_attribute("class", value.to_s) end |
#click ⇒ Object
1396 1397 1398 |
# File 'lib/dommy/element.rb', line 1396 def click __js_call__("click", []) end |
#clone_node(deep_arg) ⇒ Object
1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 |
# File 'lib/dommy/element.rb', line 1913 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
1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 |
# File 'lib/dommy/element.rb', line 1808 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).
1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 |
# File 'lib/dommy/element.rb', line 1257 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.
886 887 888 889 890 891 892 893 |
# File 'lib/dommy/element.rb', line 886 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
768 769 770 |
# File 'lib/dommy/element.rb', line 768 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.
1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 |
# File 'lib/dommy/element.rb', line 1310 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
794 795 796 |
# File 'lib/dommy/element.rb', line 794 def first_child @document.wrap_node(@__node__.children.first) end |
#first_element_child ⇒ Object
786 787 788 |
# File 'lib/dommy/element.rb', line 786 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.
1076 1077 1078 1079 |
# File 'lib/dommy/element.rb', line 1076 def focus @document.__set_active_element__(self) nil end |
#get_animations(_options = nil) ⇒ Object Also known as: getAnimations
1835 1836 1837 |
# File 'lib/dommy/element.rb', line 1835 def get_animations( = nil) (@__animations ||= []).dup end |
#get_attribute(name) ⇒ Object
1774 1775 1776 1777 1778 |
# File 'lib/dommy/element.rb', line 1774 def get_attribute(name) return nil if name.nil? @__node__[normalize_attr_key(name)] end |
#get_attribute_node(name) ⇒ Object
990 991 992 |
# File 'lib/dommy/element.rb', line 990 def get_attribute_node(name) attributes.get_named_item(name) end |
#get_elements_by_class_name(name) ⇒ Object
961 962 963 964 965 966 967 968 969 970 971 |
# File 'lib/dommy/element.rb', line 961 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
973 974 975 976 977 978 979 980 981 982 |
# File 'lib/dommy/element.rb', line 973 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
1392 1393 1394 |
# File 'lib/dommy/element.rb', line 1392 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).
1388 1389 1390 |
# File 'lib/dommy/element.rb', line 1388 def get_inner_html( = nil) inner_html end |
#has_attribute?(name) ⇒ Boolean
1790 1791 1792 1793 1794 |
# File 'lib/dommy/element.rb', line 1790 def has_attribute?(name) return false if name.nil? @__node__.key?(normalize_attr_key(name)) end |
#has_attributes? ⇒ Boolean
822 823 824 |
# File 'lib/dommy/element.rb', line 822 def has_attributes? @__node__.attribute_nodes.any? end |
#has_child_nodes? ⇒ Boolean
818 819 820 |
# File 'lib/dommy/element.rb', line 818 def has_child_nodes? @__node__.children.any? end |
#id ⇒ Object
744 745 746 |
# File 'lib/dommy/element.rb', line 744 def id @__node__["id"].to_s end |
#id=(value) ⇒ Object
748 749 750 |
# File 'lib/dommy/element.rb', line 748 def id=(value) set_attribute("id", value.to_s) end |
#inner_html ⇒ Object
732 733 734 |
# File 'lib/dommy/element.rb', line 732 def inner_html __js_get__("innerHTML") end |
#inner_html=(value) ⇒ Object
736 737 738 |
# File 'lib/dommy/element.rb', line 736 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).
1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 |
# File 'lib/dommy/element.rb', line 1159 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
1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 |
# File 'lib/dommy/element.rb', line 1191 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
1222 1223 1224 1225 1226 |
# File 'lib/dommy/element.rb', line 1222 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
1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 |
# File 'lib/dommy/element.rb', line 1861 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.
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 |
# File 'lib/dommy/element.rb', line 1046 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
798 799 800 |
# File 'lib/dommy/element.rb', line 798 def last_child @document.wrap_node(@__node__.children.last) end |
#last_element_child ⇒ Object
790 791 792 |
# File 'lib/dommy/element.rb', line 790 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.
812 813 814 815 816 |
# File 'lib/dommy/element.rb', line 812 def live_child_nodes @live_child_nodes ||= LiveNodeList.new do @__node__.children.map { |n| @document.wrap_node(n) }.compact end end |
#local_name ⇒ Object
1010 1011 1012 |
# File 'lib/dommy/element.rb', line 1010 def local_name @__node__.name.downcase end |
#matches?(selector) ⇒ Boolean
953 954 955 956 957 958 959 |
# File 'lib/dommy/element.rb', line 953 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.
1005 1006 1007 1008 |
# File 'lib/dommy/element.rb', line 1005 def namespace_uri ns = @__node__.namespace ns ? ns.href : "http://www.w3.org/1999/xhtml" end |
#next_element_sibling ⇒ Object
834 835 836 837 838 |
# File 'lib/dommy/element.rb', line 834 def next_element_sibling node = @__node__.next node = node.next while node && !node.element? node && @document.wrap_node(node) end |
#next_sibling ⇒ Object
826 827 828 |
# File 'lib/dommy/element.rb', line 826 def next_sibling @__node__.next && @document.wrap_node(@__node__.next) end |
#normalize ⇒ Object
Merge adjacent text node siblings and drop empty text nodes.
924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 |
# File 'lib/dommy/element.rb', line 924 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.
1403 1404 1405 1406 |
# File 'lib/dommy/element.rb', line 1403 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.
848 849 850 |
# File 'lib/dommy/element.rb', line 848 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
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 |
# File 'lib/dommy/element.rb', line 859 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
1039 1040 1041 |
# File 'lib/dommy/element.rb', line 1039 def owner_document @document end |
#parent_element ⇒ Object Also known as: parent
776 777 778 |
# File 'lib/dommy/element.rb', line 776 def parent_element @document.wrap_node(@__node__.parent) if @__node__.parent&.element? end |
#parent_node ⇒ Object
782 783 784 |
# File 'lib/dommy/element.rb', line 782 def parent_node @__node__.parent && @document.wrap_node(@__node__.parent) end |
#prepend(*args) ⇒ Object
1358 1359 1360 |
# File 'lib/dommy/element.rb', line 1358 def prepend(*args) prepend_nodes(args) end |
#previous_element_sibling ⇒ Object
840 841 842 843 844 |
# File 'lib/dommy/element.rb', line 840 def previous_element_sibling node = @__node__.previous node = node.previous while node && !node.element? node && @document.wrap_node(node) end |
#previous_sibling ⇒ Object
830 831 832 |
# File 'lib/dommy/element.rb', line 830 def previous_sibling @__node__.previous && @document.wrap_node(@__node__.previous) end |
#query_selector(selector) ⇒ Object
1841 1842 1843 1844 1845 |
# File 'lib/dommy/element.rb', line 1841 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
1847 1848 1849 1850 1851 |
# File 'lib/dommy/element.rb', line 1847 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`).
1537 1538 1539 |
# File 'lib/dommy/element.rb', line 1537 def reflected_attr_name(key) {"readOnly" => "readonly"}.fetch(key, key) end |
#remove ⇒ Object
1347 1348 1349 |
# File 'lib/dommy/element.rb', line 1347 def remove __js_call__("remove", []) end |
#remove_attribute(name) ⇒ Object
1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 |
# File 'lib/dommy/element.rb', line 1796 def remove_attribute(name) return nil if name.nil? key = normalize_attr_key(name) 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
998 999 1000 1001 1002 |
# File 'lib/dommy/element.rb', line 998 def remove_attribute_node(attr) return nil unless attr.respond_to?(:name) attributes.remove_named_item(attr.name) end |
#remove_child(child) ⇒ Object
1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 |
# File 'lib/dommy/element.rb', line 1882 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.
1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 |
# File 'lib/dommy/element.rb', line 1898 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
1362 1363 1364 1365 1366 1367 1368 1369 |
# File 'lib/dommy/element.rb', line 1362 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
1381 1382 1383 |
# File 'lib/dommy/element.rb', line 1381 def replace_with_nodes(*args) replace_with(args) end |
#role ⇒ Object
1024 1025 1026 |
# File 'lib/dommy/element.rb', line 1024 def role @__node__["role"].to_s end |
#role=(value) ⇒ Object
1028 1029 1030 |
# File 'lib/dommy/element.rb', line 1028 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).
899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 |
# File 'lib/dommy/element.rb', line 899 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.
1303 1304 1305 |
# File 'lib/dommy/element.rb', line 1303 def same_node?(other) equal?(other) end |
#set_attribute(name, value) ⇒ Object
1780 1781 1782 1783 1784 1785 1786 1787 1788 |
# File 'lib/dommy/element.rb', line 1780 def set_attribute(name, value) return nil if name.nil? key = normalize_attr_key(name) 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
994 995 996 |
# File 'lib/dommy/element.rb', line 994 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.
1143 1144 1145 1146 1147 1148 |
# File 'lib/dommy/element.rb', line 1143 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.
1016 1017 1018 |
# File 'lib/dommy/element.rb', line 1016 def slot @__node__["slot"].to_s end |
#slot=(value) ⇒ Object
1020 1021 1022 |
# File 'lib/dommy/element.rb', line 1020 def slot=(value) set_attribute("slot", value.to_s) end |
#style ⇒ Object
764 765 766 |
# File 'lib/dommy/element.rb', line 764 def style @style end |
#tag_name ⇒ Object
740 741 742 |
# File 'lib/dommy/element.rb', line 740 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.
724 725 726 |
# File 'lib/dommy/element.rb', line 724 def text_content @__node__.text end |
#text_content=(value) ⇒ Object
728 729 730 |
# File 'lib/dommy/element.rb', line 728 def text_content=(value) __js_set__("textContent", value) end |
#to_s ⇒ Object
Convenience alias matching the DOM idiom ‘String(el)` → outerHTML.
1229 1230 1231 |
# File 'lib/dommy/element.rb', line 1229 def to_s outer_html end |
#toggle_attribute(name, force = nil) ⇒ Object
940 941 942 943 944 945 946 947 948 949 950 951 |
# File 'lib/dommy/element.rb', line 940 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 |