opal/browser/dom/node.rb
# backtick_javascript: true
module Browser; module DOM
# Abstract class for all DOM node types.
#
# @see https://developer.mozilla.org/en-US/docs/Web/API/Node
class Node
include Browser::NativeCachedWrapper
ELEMENT_NODE = 1
ATTRIBUTE_NODE = 2
TEXT_NODE = 3
CDATA_SECTION_NODE = 4
ENTITY_REFERENCE_NOCE = 5
ENTITY_NODE = 6
PROCESSING_INSTRUCTION_NODE = 7
COMMENT_NODE = 8
DOCUMENT_NODE = 9
DOCUMENT_TYPE_NODE = 10
DOCUMENT_FRAGMENT_NODE = 11
NOTATION_NODE = 12
# Wrap a native DOM node.
#
# @param value [native] the native DOM node
#
# @return [Node]
def self.new(value)
if self == Node
@classes ||= [nil, Element, Attribute, Text, CDATA, nil, nil, nil, Comment, Document, nil, DocumentFragment]
if klass = @classes[`value.nodeType`]
klass.new(value)
else
raise ArgumentError, 'cannot instantiate a non derived Node object'
end
else
super
end
end
def initialize(node)
raise ArgumentError, "Please ensure that #initialize of #{self.class} accepts one argument" unless node
super
end
# Return true of the other element is the same underlying DOM node.
#
# @return [Boolean]
def ==(other)
`#@native === #{Native.convert(other)}`
end
# Initialize a new node after `#dup` or `#clone`.
#
# This method is not to be called directly. Use `Node#dup` or
# `Node#clone`.
#
# This method creates a deep detached clone of a DOM subtree to be used
# in the same document. The new node will have all events detached.
def initialize_copy(old)
set_native_reference `#{old.to_n}.cloneNode(true)`
end
# Append a child to the node.
#
# When passing a {String} a text node will be created.
#
# When passing an Object that responds to #each, every yielded element
# will be added following the same logic.
#
# @param node [String, Node, #each, #to_n] the node to append
#
# @return [self]
def <<(node)
if Opal.respond_to? node, :each
node.each { |n| self << n }
return self
elsif Opal.respond_to? node, :to_dom
node = node.to_dom(document)
end
unless native?(node)
if String === node
node = `#@native.ownerDocument.createTextNode(node)`
else
node = Native.convert(node)
end
end
`#@native.appendChild(node)`
self
end
def >>(node)
if Opal.respond_to? node, :each
node.each { |n| self >> n }
return self
elsif Opal.respond_to? node, :to_dom
node = node.to_dom(document)
end
unless native?(node)
if String === node
node = `#@native.ownerDocument.createTextNode(node)`
else
node = Native.convert(node)
end
end
if `#@native.firstChild == null`
`#@native.appendChild(node)`
else
`#@native.insertBefore(node, #@native.firstChild)`
end
self
end
def add_child(node = nil, &block)
unless node
node = DOM(&block)
end
self << node
end
# Add the passed node after this one.
#
# When passing a {String} a text node will be created.
#
# @param node [String, Node, #to_n] the node to add
def add_next_sibling(node = nil, &block)
unless node
node = DOM(&block)
end
node = node.to_dom(document) if Opal.respond_to? node, :to_dom
unless native?(node)
if String === node
node = `#@native.ownerDocument.createTextNode(node)`
else
node = Native.convert(node)
end
end
`#@native.parentNode.insertBefore(node, #@native.nextSibling)`
end
# Add the passed node before this one.
#
# When passing a {String} a text node will be created.
#
# @param node [String, Node, #to_n] the node to add
def add_previous_sibling(node = nil, &block)
unless node
node = DOM(&block)
end
node = node.to_dom(document) if Opal.respond_to? node, :to_dom
unless native?(node)
if String === node
node = `#@native.ownerDocument.createTextNode(node)`
else
node = Native.convert(node)
end
end
`#@native.parentNode.insertBefore(node, #@native)`
end
alias after add_next_sibling
# Append the node to the passed one.
#
# @param node [Node] the node to append to
def append_to(node)
node << self
self
end
# Get an array of ancestors.
#
# Passing a selector will select the ancestors matching it.
#
# @param expression [String] the selector to use as filter
#
# @return [NodeSet]
def ancestors(expression = nil)
return NodeSet[] unless parent
parents = [parent]
while parent = parents.last.parent
parents << parent
end
if Document === parents.last
parents.pop
end
if expression
parents.select! { |p| p =~ expression }
end
NodeSet.new(parents)
end
def attached?
`#@native.isConnected`
end
alias before add_previous_sibling
# Remove the node from its parent.
def remove
parent.remove_child(self) if parent
self
end
# Remove all the children of the node.
def clear
children.remove
end
# @!attribute content
# @return [String] the inner text content of the node
if Browser.supports? 'Element.textContent'
def content
`#@native.textContent`
end
def content=(value)
`#@native.textContent = #{value}`
end
elsif Browser.supports? 'Element.innerText'
def content
`#@native.innerText`
end
def content=(value)
`#@native.innerText = #{value}`
end
else
def content
raise NotImplementedError, 'node text content unsupported'
end
def content=(value)
raise NotImplementedError, 'node text content unsupported'
end
end
def blank?
raise NotImplementedError
end
# Return true if the node is a CDATA section.
def cdata?
node_type == CDATA_SECTION_NODE
end
# @!attribute [r] child
# @return [Node?] the first child of the node
def child
children.first
end
# @!attribute children
# @return [NodeSet] the children of the node
def children
NodeSet[Native::Array.new(`#@native.childNodes`)]
end
def children=(node)
raise NotImplementedError
end
# Return true if the node is a comment.
def comment?
node_type == COMMENT_NODE
end
# Return true if the node is a custom element.
def custom?
false
end
# @!attribute [rw] document
# @return [Document?] the document the node is attached to
def document
DOM(`#@native.ownerDocument`) if defined?(`#@native.ownerDocument`)
end
# Detach a node and transfer it to another document.
def document=(new_document)
`#{Native.try_convert(new_document, new_document)}.adoptNode(#@native)`
end
# Return true if the node is a document.
def document?
node_type == DOCUMENT_NODE
end
# Return true if the node is an element.
def elem?
node_type == ELEMENT_NODE
end
alias element? elem?
# @!attribute [r] element_children
# @return [NodeSet] all the children which are elements
def element_children
children.select(&:element?)
end
alias elements element_children
# @!attribute [r] first_element_child
# @return [Element?] the first element child
def first_element_child
element_children.first
end
# Return true if the node is a document fragment.
def fragment?
node_type == DOCUMENT_FRAGMENT_NODE
end
alias inner_text content
alias inner_text= content=
# @!attribute [r] last_element_child
# @return [Element?] the last element child
def last_element_child
element_children.last
end
# @!attribute name
# @return [String] the name of the node
def name
`#@native.nodeName || nil`
end
def name=(value)
`#@native.nodeName = #{value.to_s}`
end
# @!attribute [r] namespace
# @return [String] the namespace of the node
def namespace
`#@native.namespaceURI || nil`
end
# @!attribute next
# @return [Node?] the next sibling of the node
def next
DOM(`#@native.nextSibling`) if `#@native.nextSibling != null`
end
alias next= add_next_sibling
# @!attribute [r] next_element
# @return [Element?] the next element sibling of the node
def next_element
current = self.next
while current && !current.element?
current = current.next
end
current
end
alias next_sibling next
alias node_name name
alias node_name= name=
# @!attribute [r] node_type
# @return [Symbol] the type of the node
def node_type
`#@native.nodeType`
end
# @!attribute outer_html
# @return [String] the simulated outer html of the node
def outer_html
div = $document.create_element("DIV")
div << self.dup
div.inner_html
end
# @!attribute parent
# @return [Element?] the parent of the node
def parent
DOM(`#@native.parentNode`) if `#@native.parentNode != null`
end
def parent=(node)
`#@native.parentNode = #{Native.convert(node)}`
end
def parse(text, options = {})
raise NotImplementedError
end
def path
raise NotImplementedError
end
# Prepend the node to the passed one.
#
# @param node [Node] the node to prepend to
def prepend_to(node)
node >> self
self
end
# @!attribute previous
# @return [Node?] the previous sibling of the node
def previous
DOM(`#@native.previousSibling`) if `#@native.previousSibling != null`
end
alias previous= add_previous_sibling
# @!attribute [r] previous_element
# @return [Element?] the previous element sibling of the node
def previous_element
current = self.previous
while current && !current.element?
current = current.previous
end
current
end
alias previous_sibling previous
# Remove the given node from the children of this node.
def remove_child(node)
`#@native.removeChild(#{Native.try_convert(node)})`
self
end
# Replace the node with the given one.
#
# @todo implement for NodeSet
#
# @param node [Node] the node to replace with
# @return [Node] the passed node
def replace(node)
node = node.to_dom(document) if Opal.respond_to? node, :to_dom
unless native?(node)
if String === node
node = `#@native.ownerDocument.createTextNode(node)`
else
node = Native.convert(node)
end
end
`#@native.parentNode.replaceChild(node, #@native)`
DOM(node)
end
alias replace_with replace
alias text content
alias text= content=
# Return true if the node is a text node.
def text?
node_type == TEXT_NODE
end
def traverse(&block)
raise NotImplementedError
end
alias type node_type
# @!attribute value
# @return [String] the value of the node
def value
`#@native.nodeValue || nil`
end
def value=(value)
`#@native.nodeValue = value`
end
# @private
def inspect
"#<DOM::Node: #{name}>"
end
end
end; end