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