opal/browser/dom/element.rb in opal-browser-0.2.0.beta1 vs opal/browser/dom/element.rb in opal-browser-0.2.0
- old
+ new
@@ -1,12 +1,17 @@
+require 'browser/dom/element/attributes'
+require 'browser/dom/element/data'
require 'browser/dom/element/position'
require 'browser/dom/element/offset'
require 'browser/dom/element/scroll'
require 'browser/dom/element/size'
require 'browser/dom/element/input'
+require 'browser/dom/element/select'
require 'browser/dom/element/image'
+require 'browser/dom/element/template'
+require 'browser/dom/element/textarea'
module Browser; module DOM
class Element < Node
def self.create(*args)
@@ -15,11 +20,11 @@
def self.new(node)
if self == Element
name = `node.nodeName`.capitalize
- if Element.const_defined?(name)
+ if Element.constants.include?(name)
Element.const_get(name).new(node)
else
super
end
else
@@ -31,239 +36,350 @@
target {|value|
DOM(value) rescue nil
}
- alias_native :id
+ if Browser.supports? 'Element.matches'
+ def =~(selector)
+ `#@native.matches(#{selector})`
+ end
+ elsif Browser.supports? 'Element.matches (Opera)'
+ def =~(selector)
+ `#@native.oMatchesSelector(#{selector})`
+ end
+ elsif Browser.supports? 'Element.matches (Internet Explorer)'
+ def =~(selector)
+ `#@native.msMatchesSelector(#{selector})`
+ end
+ elsif Browser.supports? 'Element.matches (Firefox)'
+ def =~(selector)
+ `#@native.mozMatchesSelector(#{selector})`
+ end
+ elsif Browser.supports? 'Element.matches (Chrome)'
+ def =~(selector)
+ `#@native.webkitMatchesSelector(#{selector})`
+ end
+ elsif Browser.loaded? 'Sizzle'
+ def =~(selector)
+ `Sizzle.matchesSelector(#@native, #{selector})`
+ end
+ else
+ # Check whether the element matches the given selector.
+ #
+ # @param selector [String] the CSS selector
+ def =~(selector)
+ raise NotImplementedError, 'selector matching unsupported'
+ end
+ end
+ # Query for children with the given XPpaths.
+ #
+ # @param paths [Array<String>] the XPaths to look for
+ #
+ # @return [NodeSet]
+ def /(*paths)
+ NodeSet[paths.map { |path| xpath(path) }]
+ end
+
+ # Get the attribute with the given name.
+ #
+ # @param name [String] the attribute name
+ # @param options [Hash] options for the attribute
+ #
+ # @option options [String] :namespace the namespace for the attribute
+ #
+ # @return [String?]
+ def [](name, options = {})
+ attributes.get(name, options)
+ end
+
+ # Set the attribute with the given name and value.
+ #
+ # @param name [String] the attribute name
+ # @param value [Object] the attribute value
+ # @param options [Hash] the options for the attribute
+ #
+ # @option options [String] :namespace the namespace for the attribute
+ def []=(name, value, options = {})
+ attributes.set(name, value, options)
+ end
+
+ # Add class names to the element.
+ #
+ # @param names [Array<String>] class names to add
+ #
+ # @return [self]
def add_class(*names)
classes = class_names + names
unless classes.empty?
`#@native.className = #{classes.uniq.join ' '}`
end
self
end
- def remove_class(*names)
- classes = class_names - names
+ # Get the first node that matches the given CSS selector or XPath.
+ #
+ # @param path_or_selector [String] an XPath or CSS selector
+ #
+ # @return [Node?]
+ def at(path_or_selector)
+ xpath(path_or_selector).first || css(path_or_selector).first
+ end
- if classes.empty?
- `#@native.removeAttribute('class')`
- else
- `#@native.className = #{classes.join ' '}`
- end
+ # Get the first node matching the given CSS selectors.
+ #
+ # @param rules [Array<String>] the CSS selectors to match with
+ #
+ # @return [Node?]
+ def at_css(*rules)
+ result = nil
- self
+ rules.each {|rule|
+ if result = css(rule).first
+ break
+ end
+ }
+
+ result
end
- alias_native :class_name, :className
+ # Get the first node matching the given XPath.
+ #
+ # @param paths [Array<String>] the XPath to match with
+ #
+ # @return [Node?]
+ def at_xpath(*paths)
+ result = nil
- def class_names
- `#@native.className`.split(/\s+/).reject(&:empty?)
+ paths.each {|path|
+ if result = xpath(path).first
+ break
+ end
+ }
+
+ result
end
- alias attribute attr
+ alias attr []
- def attribute_nodes
- Native::Array.new(`#@native.attributes`, get: :item) { |e| DOM(e) }
- end
+ alias attribute []
+ # @!attribute [r] attributes
+ # @return [Attributes] the attributes for the element
def attributes(options = {})
Attributes.new(self, options)
end
- def get(name, options = {})
- if namespace = options[:namespace]
- `#@native.getAttributeNS(#{namespace.to_s}, #{name.to_s}) || nil`
- else
- `#@native.getAttribute(#{name.to_s}) || nil`
- end
+ # @!attribute [r] attribute_nodes
+ # @return [NodeSet] the attribute nodes for the element
+ def attribute_nodes
+ NodeSet[Native::Array.new(`#@native.attributes`, get: :item)]
end
- def set(name, value, options = {})
- if namespace = options[:namespace]
- `#@native.setAttributeNS(#{namespace.to_s}, #{name.to_s}, #{value})`
- else
- `#@native.setAttribute(#{name.to_s}, #{value.to_s})`
+ # @!attribute [r] class_name
+ # @return [String] all the element class names
+ alias_native :class_name, :className
+
+ # @!attribute [r] class_names
+ # @return [Array<String>] all the element class names
+ def class_names
+ `#@native.className`.split(/\s+/).reject(&:empty?)
+ end
+
+ if Browser.supports? 'Query.css'
+ def css(path)
+ NodeSet[Native::Array.new(`#@native.querySelectorAll(path)`)]
+ rescue
+ NodeSet[]
end
+ elsif Browser.loaded? 'Sizzle'
+ def css(path)
+ NodeSet[`Sizzle(path, #@native)`]
+ rescue
+ NodeSet[]
+ end
+ else
+ # Query for children matching the given CSS selector.
+ #
+ # @param selector [String] the CSS selector
+ #
+ # @return [NodeSet]
+ def css(selector)
+ raise NotImplementedError, 'query by CSS selector unsupported'
+ end
end
- alias [] get
- alias []= set
+ # @overload data()
+ #
+ # Return the data for the element.
+ #
+ # @return [Data]
+ #
+ # @overload data(hash)
+ #
+ # Set data on the element.
+ #
+ # @param hash [Hash] the data to set
+ #
+ # @return [self]
+ def data(value = nil)
+ data = Data.new(self)
- alias attr get
- alias attribute get
+ return data unless value
- alias get_attribute get
- alias set_attribute set
+ if Hash === value
+ data.assign(value)
+ else
+ raise ArgumentError, 'unknown data type'
+ end
- def key?(name)
- !!self[name]
+ self
end
- def keys
- attributes_nodesmap(&:name)
- end
+ alias get_attribute []
- def values
- attribute_nodes.map(&:value)
- end
+ alias get []
- def remove_attribute(name)
- `#@native.removeAttribute(name)`
- end
-
- def size(*inc)
- Size.new(self, *inc)
- end
-
+ # @!attribute height
+ # @return [Integer] the height of the element
def height
size.height
end
def height=(value)
size.height = value
end
- def width
- size.width
- end
+ # @!attribute id
+ # @return [String?] the ID of the element
+ def id
+ %x{
+ var id = #@native.id;
- def width=(value)
- size.width = value
+ if (id === "") {
+ return nil;
+ }
+ else {
+ return id;
+ }
+ }
end
- def position
- Position.new(self)
+ def id=(value)
+ `#@native.id = #{value.to_s}`
end
- def offset(*values)
- off = Offset.new(self)
+ # Set the inner DOM of the element using the {Builder}.
+ def inner_dom(&block)
+ clear
- unless values.empty?
- off.set(*values)
- end
+ # FIXME: when block passing is fixed
+ doc = document
- off
+ self << Builder.new(doc, self, &block).to_a
end
- def offset=(value)
- offset.set(*value)
- end
+ # Set the inner DOM with the given node.
+ #
+ # (see #append_child)
+ def inner_dom=(node)
+ clear
- def scroll
- Scroll.new(self)
+ self << node
end
- def inner_dom(&block)
- # FIXME: when block passing is fixed
- doc = document
- clear; Builder.new(doc, self, &block)
+ def inspect
+ inspect = name.downcase
- self
- end
+ if id
+ inspect += '.' + id + '!'
+ end
- def inner_dom=(node)
- clear; self << node
- end
+ unless class_names.empty?
+ inspect += '.' + class_names.join('.')
+ end
- def /(*paths)
- paths.map { |path| xpath(path) }.flatten.uniq
+ "#<DOM::Element: #{inspect}>"
end
- def at(path)
- xpath(path).first || css(path).first
- end
+ # @!attribute offset
+ # @return [Offset] the offset of the element
+ def offset(*values)
+ off = Offset.new(self)
- def at_css(*rules)
- rules.each {|rule|
- found = css(rule).first
+ unless values.empty?
+ off.set(*values)
+ end
- return found if found
- }
+ off
+ end
- nil
+ def offset=(value)
+ offset.set(*value)
end
- def at_xpath(*paths)
- paths.each {|path|
- found = xpath(path).first
+ # @!attribute [r] position
+ # @return [Position] the position of the element
+ def position
+ Position.new(self)
+ end
- return found if found
- }
-
- nil
+ # @!attribute [r] scroll
+ # @return [Scroll] the scrolling for the element
+ def scroll
+ Scroll.new(self)
end
+ # Search for all the children matching the given XPaths or CSS selectors.
+ #
+ # @param selectors [Array<String>] mixed list of XPaths and CSS selectors
+ #
+ # @return [NodeSet]
def search(*selectors)
- NodeSet.new document, selectors.map {|selector|
+ NodeSet.new selectors.map {|selector|
xpath(selector).to_a.concat(css(selector).to_a)
}.flatten.uniq
end
- if Browser.supports? 'Query.css'
- def css(path)
- %x{
- try {
- var result = #@native.querySelectorAll(path);
+ alias set []=
- return #{NodeSet.new(document,
- Native::Array.new(`result`))};
- }
- catch(e) {
- return #{NodeSet.new(document)};
- }
- }
- end
- elsif Browser.loaded? 'Sizzle'
- def css(path)
- NodeSet.new(document, `Sizzle(#{path}, #@native)`)
- end
- else
- def css(selector)
- raise NotImplementedError, 'query by CSS selector unsupported'
- end
- end
+ alias set_attribute []=
- if Browser.supports?('Query.xpath') || Browser.loaded?('wicked-good-xpath')
- if Browser.loaded? 'wicked-good-xpath'
- `wgxpath.install()`
- end
-
- def xpath(path)
- %x{
- try {
- var result = (#@native.ownerDocument || #@native).evaluate(path,
- #@native, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
-
- return #{NodeSet.new(document,
- Native::Array.new(`result`, get: :snapshotItem, length: :snapshotLength))};
- }
- catch (e) {
- return #{NodeSet.new(document)};
- }
- }
- end
- else
- def xpath(path)
- raise NotImplementedError, 'query by XPath unsupported'
- end
- end
-
+ # @overload style()
+ #
+ # Return the style for the element.
+ #
+ # @return [CSS::Declaration]
+ #
+ # @overload style(data)
+ #
+ # Set the CSS style as string or set of values.
+ #
+ # @param data [String, Hash] the new style
+ #
+ # @return [self]
+ #
+ # @overload style(&block)
+ #
+ # Set the CSS style from a CSS builder DSL.
+ #
+ # @return [self]
def style(data = nil, &block)
style = CSS::Declaration.new(`#@native.style`)
return style unless data || block
- if data.is_a?(String)
+ if String === data
style.replace(data)
- elsif data.is_a?(Enumerable)
+ elsif Hash === data
style.assign(data)
elsif block
style.apply(&block)
+ else
+ raise ArgumentError, 'unknown data type'
end
self
end
@@ -274,114 +390,84 @@
elsif Browser.supports? 'CSS.current'
def style!
CSS::Declaration.new(`#@native.currentStyle`)
end
else
+ # @!attribute [r] style!
+ # @return [CSS::Declaration] get the computed style for the element
def style!
raise NotImplementedError, 'computed style unsupported'
end
end
- def data(what)
- if Hash === what
- unless defined?(`#@native.$data`)
- `#@native.$data = {}`
- end
+ # Remove an attribute from the element.
+ #
+ # @param name [String] the attribute name
+ def remove_attribute(name)
+ `#@native.removeAttribute(name)`
+ end
- what.each {|name, value|
- `#@native.$data[name] = value`
- }
+ # Remove class names from the element.
+ #
+ # @param names [Array<String>] class names to remove
+ #
+ # @return [self]
+ def remove_class(*names)
+ classes = class_names - names
+
+ if classes.empty?
+ `#@native.removeAttribute('class')`
else
- return self["data-#{what}"] if self["data-#{what}"]
+ `#@native.className = #{classes.join ' '}`
+ end
- return unless defined?(`#@native.$data`)
+ self
+ end
- %x{
- var value = #@native.$data[what];
+ # @!attribute [r] size
+ # @return [Size] the size of the element
+ def size(*inc)
+ Size.new(self, *inc)
+ end
- if (value === undefined) {
- return nil;
- }
- else {
- return value;
- }
- }
- end
+ # @!attribute width
+ # @return [Integer] the width of the element
+ def width
+ size.width
end
- if Browser.supports? 'Element.matches'
- def matches?(selector)
- `#@native.matches(#{selector})`
- end
- elsif Browser.supports? 'Element.matches (Opera)'
- def matches?(selector)
- `#@native.oMatchesSelector(#{selector})`
- end
- elsif Browser.supports? 'Element.matches (Internet Explorer)'
- def matches?(selector)
- `#@native.msMatchesSelector(#{selector})`
- end
- elsif Browser.supports? 'Element.matches (Firefox)'
- def matches?(selector)
- `#@native.mozMatchesSelector(#{selector})`
- end
- elsif Browser.supports? 'Element.matches (Chrome)'
- def matches?(selector)
- `#@native.webkitMatchesSelector(#{selector})`
- end
- elsif Browser.loaded? 'Sizzle'
- def matches?(selector)
- `Sizzle.matchesSelector(#@native, #{selector})`
- end
- else
- def matches?(selector)
- raise NotImplementedError, 'selector matching unsupported'
- end
+ def width=(value)
+ size.width = value
end
- # @abstract
+ # @!attribute [r] window
+ # @return [Window] the window for the element
def window
document.window
end
- def inspect
- "#<DOM::Element: #{name}>"
- end
-
- class Attributes
- include Enumerable
-
- attr_reader :namespace
-
- def initialize(element, options)
- @element = element
- @namespace = options[:namespace]
+ if Browser.supports?('Query.xpath') || Browser.loaded?('wicked-good-xpath')
+ if Browser.loaded? 'wicked-good-xpath'
+ `wgxpath.install()`
end
- def each(&block)
- return enum_for :each unless block_given?
-
- @element.attribute_nodes.each {|attr|
- yield attr.name, attr.value
- }
-
- self
+ def xpath(path)
+ NodeSet[Native::Array.new(
+ `(#@native.ownerDocument || #@native).evaluate(path,
+ #@native, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)`,
+ get: :snapshotItem,
+ length: :snapshotLength)]
+ rescue
+ NodeSet[]
end
-
- def [](name)
- @element.get_attribute(name, namespace: @namespace)
- end
-
- def []=(name, value)
- @element.set_attribute(name, value, namespace: @namespace)
- end
-
- def merge!(hash)
- hash.each {|name, value|
- self[name] = value
- }
-
- self
+ else
+ # Query for children matching the given XPath.
+ #
+ # @param path [String] the XPath
+ #
+ # @return [NodeSet]
+ def xpath(path)
+ raise NotImplementedError, 'query by XPath unsupported'
end
end
end
end; end