opal-browser 0.2.0.beta1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +22 -8
  3. data/Gemfile +1 -1
  4. data/README.md +59 -5
  5. data/index.html.erb +7 -4
  6. data/lib/opal-browser.rb +1 -0
  7. data/opal-browser.gemspec +1 -1
  8. data/opal/browser.rb +1 -0
  9. data/opal/browser/animation_frame.rb +26 -1
  10. data/opal/browser/canvas.rb +0 -10
  11. data/opal/browser/canvas/data.rb +0 -10
  12. data/opal/browser/canvas/gradient.rb +0 -10
  13. data/opal/browser/canvas/style.rb +0 -10
  14. data/opal/browser/canvas/text.rb +0 -10
  15. data/opal/browser/cookies.rb +6 -8
  16. data/opal/browser/database/sql.rb +194 -0
  17. data/opal/browser/delay.rb +25 -7
  18. data/opal/browser/dom.rb +2 -11
  19. data/opal/browser/dom/attribute.rb +12 -11
  20. data/opal/browser/dom/builder.rb +4 -9
  21. data/opal/browser/dom/document.rb +105 -41
  22. data/opal/browser/dom/element.rb +317 -231
  23. data/opal/browser/dom/element/attributes.rb +87 -0
  24. data/opal/browser/dom/element/data.rb +67 -0
  25. data/opal/browser/dom/element/input.rb +12 -1
  26. data/opal/browser/dom/element/offset.rb +5 -0
  27. data/opal/browser/dom/element/position.rb +11 -2
  28. data/opal/browser/dom/element/scroll.rb +77 -10
  29. data/opal/browser/dom/element/select.rb +36 -0
  30. data/opal/browser/dom/element/size.rb +5 -0
  31. data/opal/browser/dom/element/template.rb +9 -0
  32. data/opal/browser/dom/element/textarea.rb +24 -0
  33. data/opal/browser/dom/mutation_observer.rb +2 -2
  34. data/opal/browser/dom/node.rb +93 -51
  35. data/opal/browser/dom/node_set.rb +66 -48
  36. data/opal/browser/effects.rb +11 -0
  37. data/opal/browser/{dom/event.rb → event.rb} +32 -32
  38. data/opal/browser/{dom/event → event}/animation.rb +2 -2
  39. data/opal/browser/{dom/event → event}/audio_processing.rb +2 -2
  40. data/opal/browser/{dom/event → event}/base.rb +65 -7
  41. data/opal/browser/{dom/event → event}/before_unload.rb +2 -2
  42. data/opal/browser/{dom/event → event}/clipboard.rb +2 -2
  43. data/opal/browser/{dom/event → event}/close.rb +2 -2
  44. data/opal/browser/{dom/event → event}/composition.rb +2 -2
  45. data/opal/browser/{dom/event → event}/custom.rb +2 -2
  46. data/opal/browser/{dom/event → event}/device_light.rb +2 -2
  47. data/opal/browser/{dom/event → event}/device_motion.rb +2 -2
  48. data/opal/browser/{dom/event → event}/device_orientation.rb +2 -2
  49. data/opal/browser/{dom/event → event}/device_proximity.rb +2 -2
  50. data/opal/browser/{dom/event → event}/drag.rb +2 -2
  51. data/opal/browser/{dom/event → event}/focus.rb +2 -2
  52. data/opal/browser/{dom/event → event}/gamepad.rb +2 -2
  53. data/opal/browser/{dom/event → event}/hash_change.rb +2 -2
  54. data/opal/browser/{dom/event → event}/keyboard.rb +2 -2
  55. data/opal/browser/{dom/event → event}/message.rb +2 -2
  56. data/opal/browser/{dom/event → event}/mouse.rb +2 -2
  57. data/opal/browser/{dom/event → event}/page_transition.rb +2 -2
  58. data/opal/browser/{dom/event → event}/pop_state.rb +2 -2
  59. data/opal/browser/{dom/event → event}/progress.rb +2 -2
  60. data/opal/browser/{dom/event → event}/sensor.rb +2 -2
  61. data/opal/browser/{dom/event → event}/storage.rb +2 -2
  62. data/opal/browser/{dom/event → event}/touch.rb +2 -2
  63. data/opal/browser/{dom/event → event}/ui.rb +2 -2
  64. data/opal/browser/{dom/event → event}/wheel.rb +2 -2
  65. data/opal/browser/event_source.rb +1 -1
  66. data/opal/browser/http.rb +25 -0
  67. data/opal/browser/http/binary.rb +1 -0
  68. data/opal/browser/http/headers.rb +16 -2
  69. data/opal/browser/http/request.rb +14 -38
  70. data/opal/browser/immediate.rb +9 -3
  71. data/opal/browser/interval.rb +34 -11
  72. data/opal/browser/navigator.rb +23 -4
  73. data/opal/browser/screen.rb +1 -1
  74. data/opal/browser/socket.rb +5 -1
  75. data/opal/browser/storage.rb +51 -33
  76. data/opal/browser/support.rb +59 -4
  77. data/opal/browser/version.rb +1 -1
  78. data/opal/browser/window.rb +17 -9
  79. data/opal/browser/window/size.rb +17 -3
  80. data/opal/opal-browser.rb +1 -0
  81. data/spec/database/sql_spec.rb +131 -0
  82. data/spec/delay_spec.rb +38 -0
  83. data/spec/dom/attribute_spec.rb +49 -0
  84. data/spec/dom/builder_spec.rb +25 -8
  85. data/spec/dom/document_spec.rb +20 -0
  86. data/spec/dom/element/attributes_spec.rb +52 -0
  87. data/spec/dom/element_spec.rb +139 -4
  88. data/spec/dom/node_set_spec.rb +44 -0
  89. data/spec/interval_spec.rb +50 -0
  90. data/spec/runner.rb +46 -28
  91. data/spec/socket_spec.rb +1 -0
  92. data/spec/spec_helper.rb +0 -4
  93. data/spec/storage_spec.rb +1 -1
  94. metadata +57 -39
  95. data/opal/browser/http/parameters.rb +0 -8
@@ -17,8 +17,6 @@ class Delay
17
17
  @window = Native.convert(window)
18
18
  @after = time
19
19
  @block = block
20
-
21
- start
22
20
  end
23
21
 
24
22
  # Abort the timeout.
@@ -39,22 +37,42 @@ class Window
39
37
  #
40
38
  # @return [Delay] the object representing the timeout
41
39
  def after(time, &block)
40
+ Delay.new(@native, time, &block).tap(&:start)
41
+ end
42
+
43
+ # Execute a block after the given seconds, you have to call [#start] on it
44
+ # yourself.
45
+ #
46
+ # @param time [Float] the seconds after it gets called
47
+ #
48
+ # @return [Delay] the object representing the timeout
49
+ def after!(time, &block)
42
50
  Delay.new(@native, time, &block)
43
51
  end
44
52
  end
45
53
 
46
54
  end
47
55
 
56
+ module Kernel
57
+ # (see Browser::Window#after)
58
+ def after(time, &block)
59
+ $window.after(time, &block)
60
+ end
61
+
62
+ # (see Browser::Window#after!)
63
+ def after!(time, &block)
64
+ $window.after!(time, &block)
65
+ end
66
+ end
67
+
48
68
  class Proc
49
69
  # (see Browser::Window#after)
50
70
  def after(time)
51
71
  $window.after(time, &self)
52
72
  end
53
- end
54
73
 
55
- module Kernel
56
- # (see Browser::Window#after)
57
- def after(time, &block)
58
- $window.after(time, &block)
74
+ # (see Browser::Window#after!)
75
+ def after!(time)
76
+ $window.after!(time, &self)
59
77
  end
60
78
  end
@@ -1,4 +1,3 @@
1
- require 'browser/dom/event'
2
1
  require 'browser/dom/node_set'
3
2
  require 'browser/dom/node'
4
3
  require 'browser/dom/attribute'
@@ -40,14 +39,12 @@ module Kernel
40
39
  def DOM(*args, &block)
41
40
  if block
42
41
  document = args.shift || $document
43
- element = args.shift
44
-
45
- roots = Browser::DOM::Builder.new(document, element, &block).to_a
42
+ roots = Browser::DOM::Builder.new(document, &block).to_a
46
43
 
47
44
  if roots.length == 1
48
45
  roots.first
49
46
  else
50
- Browser::DOM::NodeSet.new(document, roots)
47
+ Browser::DOM::NodeSet.new(roots)
51
48
  end
52
49
  else
53
50
  what = args.shift
@@ -74,12 +71,6 @@ end
74
71
  module Browser
75
72
 
76
73
  class Window
77
- include DOM::Event::Target
78
-
79
- target {|value|
80
- $window if `#{value} == window`
81
- }
82
-
83
74
  # Get the {DOM::Document} for this window.
84
75
  #
85
76
  # @return [DOM::Document]
@@ -4,21 +4,22 @@ module Browser; module DOM
4
4
  class Attribute
5
5
  include Native
6
6
 
7
- # Returns true if the attribute is an id.
8
- def id?
9
- `#@native.isId`
10
- end
11
-
12
7
  # @!attribute [r] name
13
8
  # @return [String] the name of the attribute
14
- def name
15
- `#@native.name`
16
- end
9
+ alias_native :name
17
10
 
18
- # @!attribute [r] value
11
+ # @!attribute value
19
12
  # @return [String] the value of the attribute
20
- def value
21
- `#@native.value`
13
+ alias_native :value
14
+ alias_native :value=
15
+
16
+ # Returns true if the attribute is an id.
17
+ if Browser.supports? 'Attr.isId'
18
+ alias_native :id?, :isId
19
+ else
20
+ def id?
21
+ name == :id
22
+ end
22
23
  end
23
24
  end
24
25
 
@@ -29,24 +29,19 @@ class Builder
29
29
  def self.build(builder, item)
30
30
  to_h.each {|klass, block|
31
31
  if klass === item
32
- break block.call(builder, item)
32
+ return block.call(builder, item)
33
33
  end
34
34
  }
35
+
36
+ raise ArgumentError, "cannot build unknown item #{item}"
35
37
  end
36
38
 
37
39
  attr_reader :document, :element
38
40
 
39
- def initialize(document, element = nil, &block)
41
+ def initialize(document, &block)
40
42
  @document = document
41
- @element = element
42
43
  @builder = Paggio::HTML.new(&block)
43
44
  @roots = @builder.each.map { |e| Builder.build(self, e) }
44
-
45
- if @element
46
- @roots.each {|root|
47
- @element << root
48
- }
49
- end
50
45
  end
51
46
 
52
47
  def to_a
@@ -1,34 +1,11 @@
1
- require 'browser/location'
2
-
3
1
  module Browser; module DOM
4
2
 
5
3
  class Document < Element
6
- def create_element(name, options = {})
7
- if ns = options[:namespace]
8
- DOM(`#@native.createElementNS(#{ns}, #{name})`)
9
- else
10
- DOM(`#@native.createElement(name)`)
11
- end
12
- end
13
-
14
- if Browser.supports? 'Document.view'
15
- def window
16
- Window.new(`#@native.defaultView`)
17
- end
18
- elsif Browser.supports? 'Document.window'
19
- def window
20
- Window.new(`#@native.parentWindow`)
21
- end
22
- else
23
- def window
24
- raise NotImplementedError, 'window from document unsupported'
25
- end
26
- end
27
-
28
- def create_text(content)
29
- DOM(`#@native.createTextNode(#{content})`)
30
- end
31
-
4
+ # Get the first element matching the given ID, CSS selector or XPath.
5
+ #
6
+ # @param what [String] ID, CSS selector or XPath
7
+ #
8
+ # @return [Element?] the first matching element
32
9
  def [](what)
33
10
  %x{
34
11
  var result = #@native.getElementById(what);
@@ -43,42 +20,129 @@ class Document < Element
43
20
 
44
21
  alias at []
45
22
 
23
+ # @!attribute [r] body
24
+ # @return [Element?] the body element of the document
25
+ def body
26
+ DOM(`#@native.body`)
27
+ end
28
+
29
+ # Create a new element for the document.
30
+ #
31
+ # @param name [String] the node name
32
+ # @param options [Hash] optional `:namespace` name
33
+ #
34
+ # @return [Element]
35
+ def create_element(name, options = {})
36
+ if ns = options[:namespace]
37
+ DOM(`#@native.createElementNS(#{ns}, #{name})`)
38
+ else
39
+ DOM(`#@native.createElement(name)`)
40
+ end
41
+ end
42
+
43
+ # Create a new text node for the document.
44
+ #
45
+ # @param content [String] the text content
46
+ #
47
+ # @return [Text]
48
+ def create_text(content)
49
+ DOM(`#@native.createTextNode(#{content})`)
50
+ end
51
+
46
52
  def document
47
53
  self
48
54
  end
49
55
 
56
+ # @!attribute [r] head
57
+ # @return [Element?] the head element of the document
58
+ def head
59
+ DOM(`#@native.getElementsByTagName("head")[0]`)
60
+ end
61
+
50
62
  def inspect
51
- "#<DOM::Document: #{children.inspect}>"
63
+ "#<DOM::Document>"
52
64
  end
53
65
 
54
- def title
55
- `#@native.title`
66
+ if Browser.supports? 'Event.addListener'
67
+ def ready(&block)
68
+ raise ArgumentError, 'no block given' unless block
69
+
70
+ return block.call if ready?
71
+
72
+ on 'dom:load' do |e|
73
+ e.off
74
+
75
+ block.call
76
+ end
77
+ end
78
+ elsif Browser.supports? 'Event.attach'
79
+ def ready(&block)
80
+ raise ArgumentError, 'no block given' unless block
81
+
82
+ return block.call if ready?
83
+
84
+ on 'ready:state:change' do |e|
85
+ if ready?
86
+ e.off
87
+
88
+ block.call
89
+ end
90
+ end
91
+ end
92
+ else
93
+ # Wait for the document to be ready and call the block.
94
+ def ready(&block)
95
+ raise NotImplementedError, 'document ready unsupported'
96
+ end
56
97
  end
57
98
 
58
- def title=(value)
59
- `#@native.title = value`
99
+ # Check if the document is ready.
100
+ def ready?
101
+ `#@native.readyState === "complete"`
60
102
  end
61
103
 
104
+ # @!attribute root
105
+ # @return [Element?] the root element of the document
62
106
  def root
63
107
  DOM(`#@native.documentElement`)
64
108
  end
65
109
 
66
- def head
67
- DOM(`#@native.getElementsByTagName("head")[0]`)
68
- end
69
-
70
- def body
71
- DOM(`#@native.body`)
110
+ def root=(element)
111
+ `#@native.documentElement = #{Native.convert(element)}`
72
112
  end
73
113
 
114
+ # @!attribute [r] style_sheets
115
+ # @return [Array<CSS::StyleSheet>] the style sheets for the document
74
116
  def style_sheets
75
117
  Native::Array.new(`#@native.styleSheets`) {|e|
76
118
  CSS::StyleSheet.new(e)
77
119
  }
78
120
  end
79
121
 
80
- def root=(element)
81
- `#@native.documentElement = #{Native.convert(element)}`
122
+ # @!attribute title
123
+ # @return [String] the document title
124
+ def title
125
+ `#@native.title`
126
+ end
127
+
128
+ def title=(value)
129
+ `#@native.title = value`
130
+ end
131
+
132
+ if Browser.supports? 'Document.view'
133
+ def window
134
+ Window.new(`#@native.defaultView`)
135
+ end
136
+ elsif Browser.supports? 'Document.window'
137
+ def window
138
+ Window.new(`#@native.parentWindow`)
139
+ end
140
+ else
141
+ # @!attribute [r] window
142
+ # @return [Window] the window for the document
143
+ def window
144
+ raise NotImplementedError, 'window from document unsupported'
145
+ end
82
146
  end
83
147
  end
84
148
 
@@ -1,10 +1,15 @@
1
+ require 'browser/dom/element/attributes'
2
+ require 'browser/dom/element/data'
1
3
  require 'browser/dom/element/position'
2
4
  require 'browser/dom/element/offset'
3
5
  require 'browser/dom/element/scroll'
4
6
  require 'browser/dom/element/size'
5
7
 
6
8
  require 'browser/dom/element/input'
9
+ require 'browser/dom/element/select'
7
10
  require 'browser/dom/element/image'
11
+ require 'browser/dom/element/template'
12
+ require 'browser/dom/element/textarea'
8
13
 
9
14
  module Browser; module DOM
10
15
 
@@ -17,7 +22,7 @@ class Element < Node
17
22
  if self == Element
18
23
  name = `node.nodeName`.capitalize
19
24
 
20
- if Element.const_defined?(name)
25
+ if Element.constants.include?(name)
21
26
  Element.const_get(name).new(node)
22
27
  else
23
28
  super
@@ -33,8 +38,76 @@ class Element < Node
33
38
  DOM(value) rescue nil
34
39
  }
35
40
 
36
- alias_native :id
41
+ if Browser.supports? 'Element.matches'
42
+ def =~(selector)
43
+ `#@native.matches(#{selector})`
44
+ end
45
+ elsif Browser.supports? 'Element.matches (Opera)'
46
+ def =~(selector)
47
+ `#@native.oMatchesSelector(#{selector})`
48
+ end
49
+ elsif Browser.supports? 'Element.matches (Internet Explorer)'
50
+ def =~(selector)
51
+ `#@native.msMatchesSelector(#{selector})`
52
+ end
53
+ elsif Browser.supports? 'Element.matches (Firefox)'
54
+ def =~(selector)
55
+ `#@native.mozMatchesSelector(#{selector})`
56
+ end
57
+ elsif Browser.supports? 'Element.matches (Chrome)'
58
+ def =~(selector)
59
+ `#@native.webkitMatchesSelector(#{selector})`
60
+ end
61
+ elsif Browser.loaded? 'Sizzle'
62
+ def =~(selector)
63
+ `Sizzle.matchesSelector(#@native, #{selector})`
64
+ end
65
+ else
66
+ # Check whether the element matches the given selector.
67
+ #
68
+ # @param selector [String] the CSS selector
69
+ def =~(selector)
70
+ raise NotImplementedError, 'selector matching unsupported'
71
+ end
72
+ end
37
73
 
74
+ # Query for children with the given XPpaths.
75
+ #
76
+ # @param paths [Array<String>] the XPaths to look for
77
+ #
78
+ # @return [NodeSet]
79
+ def /(*paths)
80
+ NodeSet[paths.map { |path| xpath(path) }]
81
+ end
82
+
83
+ # Get the attribute with the given name.
84
+ #
85
+ # @param name [String] the attribute name
86
+ # @param options [Hash] options for the attribute
87
+ #
88
+ # @option options [String] :namespace the namespace for the attribute
89
+ #
90
+ # @return [String?]
91
+ def [](name, options = {})
92
+ attributes.get(name, options)
93
+ end
94
+
95
+ # Set the attribute with the given name and value.
96
+ #
97
+ # @param name [String] the attribute name
98
+ # @param value [Object] the attribute value
99
+ # @param options [Hash] the options for the attribute
100
+ #
101
+ # @option options [String] :namespace the namespace for the attribute
102
+ def []=(name, value, options = {})
103
+ attributes.set(name, value, options)
104
+ end
105
+
106
+ # Add class names to the element.
107
+ #
108
+ # @param names [Array<String>] class names to add
109
+ #
110
+ # @return [self]
38
111
  def add_class(*names)
39
112
  classes = class_names + names
40
113
 
@@ -45,79 +118,131 @@ class Element < Node
45
118
  self
46
119
  end
47
120
 
48
- def remove_class(*names)
49
- classes = class_names - names
121
+ # Get the first node that matches the given CSS selector or XPath.
122
+ #
123
+ # @param path_or_selector [String] an XPath or CSS selector
124
+ #
125
+ # @return [Node?]
126
+ def at(path_or_selector)
127
+ xpath(path_or_selector).first || css(path_or_selector).first
128
+ end
50
129
 
51
- if classes.empty?
52
- `#@native.removeAttribute('class')`
53
- else
54
- `#@native.className = #{classes.join ' '}`
55
- end
130
+ # Get the first node matching the given CSS selectors.
131
+ #
132
+ # @param rules [Array<String>] the CSS selectors to match with
133
+ #
134
+ # @return [Node?]
135
+ def at_css(*rules)
136
+ result = nil
56
137
 
57
- self
138
+ rules.each {|rule|
139
+ if result = css(rule).first
140
+ break
141
+ end
142
+ }
143
+
144
+ result
58
145
  end
59
146
 
60
- alias_native :class_name, :className
147
+ # Get the first node matching the given XPath.
148
+ #
149
+ # @param paths [Array<String>] the XPath to match with
150
+ #
151
+ # @return [Node?]
152
+ def at_xpath(*paths)
153
+ result = nil
61
154
 
62
- def class_names
63
- `#@native.className`.split(/\s+/).reject(&:empty?)
155
+ paths.each {|path|
156
+ if result = xpath(path).first
157
+ break
158
+ end
159
+ }
160
+
161
+ result
64
162
  end
65
163
 
66
- alias attribute attr
164
+ alias attr []
67
165
 
68
- def attribute_nodes
69
- Native::Array.new(`#@native.attributes`, get: :item) { |e| DOM(e) }
70
- end
166
+ alias attribute []
71
167
 
168
+ # @!attribute [r] attributes
169
+ # @return [Attributes] the attributes for the element
72
170
  def attributes(options = {})
73
171
  Attributes.new(self, options)
74
172
  end
75
173
 
76
- def get(name, options = {})
77
- if namespace = options[:namespace]
78
- `#@native.getAttributeNS(#{namespace.to_s}, #{name.to_s}) || nil`
79
- else
80
- `#@native.getAttribute(#{name.to_s}) || nil`
81
- end
82
- end
83
-
84
- def set(name, value, options = {})
85
- if namespace = options[:namespace]
86
- `#@native.setAttributeNS(#{namespace.to_s}, #{name.to_s}, #{value})`
87
- else
88
- `#@native.setAttribute(#{name.to_s}, #{value.to_s})`
89
- end
174
+ # @!attribute [r] attribute_nodes
175
+ # @return [NodeSet] the attribute nodes for the element
176
+ def attribute_nodes
177
+ NodeSet[Native::Array.new(`#@native.attributes`, get: :item)]
90
178
  end
91
179
 
92
- alias [] get
93
- alias []= set
94
-
95
- alias attr get
96
- alias attribute get
97
-
98
- alias get_attribute get
99
- alias set_attribute set
180
+ # @!attribute [r] class_name
181
+ # @return [String] all the element class names
182
+ alias_native :class_name, :className
100
183
 
101
- def key?(name)
102
- !!self[name]
184
+ # @!attribute [r] class_names
185
+ # @return [Array<String>] all the element class names
186
+ def class_names
187
+ `#@native.className`.split(/\s+/).reject(&:empty?)
103
188
  end
104
189
 
105
- def keys
106
- attributes_nodesmap(&:name)
190
+ if Browser.supports? 'Query.css'
191
+ def css(path)
192
+ NodeSet[Native::Array.new(`#@native.querySelectorAll(path)`)]
193
+ rescue
194
+ NodeSet[]
195
+ end
196
+ elsif Browser.loaded? 'Sizzle'
197
+ def css(path)
198
+ NodeSet[`Sizzle(path, #@native)`]
199
+ rescue
200
+ NodeSet[]
201
+ end
202
+ else
203
+ # Query for children matching the given CSS selector.
204
+ #
205
+ # @param selector [String] the CSS selector
206
+ #
207
+ # @return [NodeSet]
208
+ def css(selector)
209
+ raise NotImplementedError, 'query by CSS selector unsupported'
210
+ end
107
211
  end
108
212
 
109
- def values
110
- attribute_nodes.map(&:value)
111
- end
213
+ # @overload data()
214
+ #
215
+ # Return the data for the element.
216
+ #
217
+ # @return [Data]
218
+ #
219
+ # @overload data(hash)
220
+ #
221
+ # Set data on the element.
222
+ #
223
+ # @param hash [Hash] the data to set
224
+ #
225
+ # @return [self]
226
+ def data(value = nil)
227
+ data = Data.new(self)
228
+
229
+ return data unless value
230
+
231
+ if Hash === value
232
+ data.assign(value)
233
+ else
234
+ raise ArgumentError, 'unknown data type'
235
+ end
112
236
 
113
- def remove_attribute(name)
114
- `#@native.removeAttribute(name)`
237
+ self
115
238
  end
116
239
 
117
- def size(*inc)
118
- Size.new(self, *inc)
119
- end
240
+ alias get_attribute []
241
+
242
+ alias get []
120
243
 
244
+ # @!attribute height
245
+ # @return [Integer] the height of the element
121
246
  def height
122
247
  size.height
123
248
  end
@@ -126,142 +251,133 @@ class Element < Node
126
251
  size.height = value
127
252
  end
128
253
 
129
- def width
130
- size.width
131
- end
132
-
133
- def width=(value)
134
- size.width = value
135
- end
254
+ # @!attribute id
255
+ # @return [String?] the ID of the element
256
+ def id
257
+ %x{
258
+ var id = #@native.id;
136
259
 
137
- def position
138
- Position.new(self)
139
- end
140
-
141
- def offset(*values)
142
- off = Offset.new(self)
143
-
144
- unless values.empty?
145
- off.set(*values)
146
- end
147
-
148
- off
149
- end
150
-
151
- def offset=(value)
152
- offset.set(*value)
260
+ if (id === "") {
261
+ return nil;
262
+ }
263
+ else {
264
+ return id;
265
+ }
266
+ }
153
267
  end
154
268
 
155
- def scroll
156
- Scroll.new(self)
269
+ def id=(value)
270
+ `#@native.id = #{value.to_s}`
157
271
  end
158
272
 
273
+ # Set the inner DOM of the element using the {Builder}.
159
274
  def inner_dom(&block)
275
+ clear
276
+
160
277
  # FIXME: when block passing is fixed
161
278
  doc = document
162
- clear; Builder.new(doc, self, &block)
163
279
 
164
- self
280
+ self << Builder.new(doc, self, &block).to_a
165
281
  end
166
282
 
283
+ # Set the inner DOM with the given node.
284
+ #
285
+ # (see #append_child)
167
286
  def inner_dom=(node)
168
- clear; self << node
169
- end
287
+ clear
170
288
 
171
- def /(*paths)
172
- paths.map { |path| xpath(path) }.flatten.uniq
289
+ self << node
173
290
  end
174
291
 
175
- def at(path)
176
- xpath(path).first || css(path).first
177
- end
292
+ def inspect
293
+ inspect = name.downcase
178
294
 
179
- def at_css(*rules)
180
- rules.each {|rule|
181
- found = css(rule).first
295
+ if id
296
+ inspect += '.' + id + '!'
297
+ end
182
298
 
183
- return found if found
184
- }
299
+ unless class_names.empty?
300
+ inspect += '.' + class_names.join('.')
301
+ end
185
302
 
186
- nil
303
+ "#<DOM::Element: #{inspect}>"
187
304
  end
188
305
 
189
- def at_xpath(*paths)
190
- paths.each {|path|
191
- found = xpath(path).first
306
+ # @!attribute offset
307
+ # @return [Offset] the offset of the element
308
+ def offset(*values)
309
+ off = Offset.new(self)
192
310
 
193
- return found if found
194
- }
311
+ unless values.empty?
312
+ off.set(*values)
313
+ end
195
314
 
196
- nil
315
+ off
197
316
  end
198
317
 
199
- def search(*selectors)
200
- NodeSet.new document, selectors.map {|selector|
201
- xpath(selector).to_a.concat(css(selector).to_a)
202
- }.flatten.uniq
318
+ def offset=(value)
319
+ offset.set(*value)
203
320
  end
204
321
 
205
- if Browser.supports? 'Query.css'
206
- def css(path)
207
- %x{
208
- try {
209
- var result = #@native.querySelectorAll(path);
210
-
211
- return #{NodeSet.new(document,
212
- Native::Array.new(`result`))};
213
- }
214
- catch(e) {
215
- return #{NodeSet.new(document)};
216
- }
217
- }
218
- end
219
- elsif Browser.loaded? 'Sizzle'
220
- def css(path)
221
- NodeSet.new(document, `Sizzle(#{path}, #@native)`)
222
- end
223
- else
224
- def css(selector)
225
- raise NotImplementedError, 'query by CSS selector unsupported'
226
- end
322
+ # @!attribute [r] position
323
+ # @return [Position] the position of the element
324
+ def position
325
+ Position.new(self)
227
326
  end
228
327
 
229
- if Browser.supports?('Query.xpath') || Browser.loaded?('wicked-good-xpath')
230
- if Browser.loaded? 'wicked-good-xpath'
231
- `wgxpath.install()`
232
- end
328
+ # @!attribute [r] scroll
329
+ # @return [Scroll] the scrolling for the element
330
+ def scroll
331
+ Scroll.new(self)
332
+ end
233
333
 
234
- def xpath(path)
235
- %x{
236
- try {
237
- var result = (#@native.ownerDocument || #@native).evaluate(path,
238
- #@native, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
239
-
240
- return #{NodeSet.new(document,
241
- Native::Array.new(`result`, get: :snapshotItem, length: :snapshotLength))};
242
- }
243
- catch (e) {
244
- return #{NodeSet.new(document)};
245
- }
246
- }
247
- end
248
- else
249
- def xpath(path)
250
- raise NotImplementedError, 'query by XPath unsupported'
251
- end
334
+ # Search for all the children matching the given XPaths or CSS selectors.
335
+ #
336
+ # @param selectors [Array<String>] mixed list of XPaths and CSS selectors
337
+ #
338
+ # @return [NodeSet]
339
+ def search(*selectors)
340
+ NodeSet.new selectors.map {|selector|
341
+ xpath(selector).to_a.concat(css(selector).to_a)
342
+ }.flatten.uniq
252
343
  end
253
344
 
345
+ alias set []=
346
+
347
+ alias set_attribute []=
348
+
349
+ # @overload style()
350
+ #
351
+ # Return the style for the element.
352
+ #
353
+ # @return [CSS::Declaration]
354
+ #
355
+ # @overload style(data)
356
+ #
357
+ # Set the CSS style as string or set of values.
358
+ #
359
+ # @param data [String, Hash] the new style
360
+ #
361
+ # @return [self]
362
+ #
363
+ # @overload style(&block)
364
+ #
365
+ # Set the CSS style from a CSS builder DSL.
366
+ #
367
+ # @return [self]
254
368
  def style(data = nil, &block)
255
369
  style = CSS::Declaration.new(`#@native.style`)
256
370
 
257
371
  return style unless data || block
258
372
 
259
- if data.is_a?(String)
373
+ if String === data
260
374
  style.replace(data)
261
- elsif data.is_a?(Enumerable)
375
+ elsif Hash === data
262
376
  style.assign(data)
263
377
  elsif block
264
378
  style.apply(&block)
379
+ else
380
+ raise ArgumentError, 'unknown data type'
265
381
  end
266
382
 
267
383
  self
@@ -276,111 +392,81 @@ class Element < Node
276
392
  CSS::Declaration.new(`#@native.currentStyle`)
277
393
  end
278
394
  else
395
+ # @!attribute [r] style!
396
+ # @return [CSS::Declaration] get the computed style for the element
279
397
  def style!
280
398
  raise NotImplementedError, 'computed style unsupported'
281
399
  end
282
400
  end
283
401
 
284
- def data(what)
285
- if Hash === what
286
- unless defined?(`#@native.$data`)
287
- `#@native.$data = {}`
288
- end
402
+ # Remove an attribute from the element.
403
+ #
404
+ # @param name [String] the attribute name
405
+ def remove_attribute(name)
406
+ `#@native.removeAttribute(name)`
407
+ end
289
408
 
290
- what.each {|name, value|
291
- `#@native.$data[name] = value`
292
- }
409
+ # Remove class names from the element.
410
+ #
411
+ # @param names [Array<String>] class names to remove
412
+ #
413
+ # @return [self]
414
+ def remove_class(*names)
415
+ classes = class_names - names
416
+
417
+ if classes.empty?
418
+ `#@native.removeAttribute('class')`
293
419
  else
294
- return self["data-#{what}"] if self["data-#{what}"]
420
+ `#@native.className = #{classes.join ' '}`
421
+ end
295
422
 
296
- return unless defined?(`#@native.$data`)
423
+ self
424
+ end
297
425
 
298
- %x{
299
- var value = #@native.$data[what];
426
+ # @!attribute [r] size
427
+ # @return [Size] the size of the element
428
+ def size(*inc)
429
+ Size.new(self, *inc)
430
+ end
300
431
 
301
- if (value === undefined) {
302
- return nil;
303
- }
304
- else {
305
- return value;
306
- }
307
- }
308
- end
432
+ # @!attribute width
433
+ # @return [Integer] the width of the element
434
+ def width
435
+ size.width
309
436
  end
310
437
 
311
- if Browser.supports? 'Element.matches'
312
- def matches?(selector)
313
- `#@native.matches(#{selector})`
314
- end
315
- elsif Browser.supports? 'Element.matches (Opera)'
316
- def matches?(selector)
317
- `#@native.oMatchesSelector(#{selector})`
318
- end
319
- elsif Browser.supports? 'Element.matches (Internet Explorer)'
320
- def matches?(selector)
321
- `#@native.msMatchesSelector(#{selector})`
322
- end
323
- elsif Browser.supports? 'Element.matches (Firefox)'
324
- def matches?(selector)
325
- `#@native.mozMatchesSelector(#{selector})`
326
- end
327
- elsif Browser.supports? 'Element.matches (Chrome)'
328
- def matches?(selector)
329
- `#@native.webkitMatchesSelector(#{selector})`
330
- end
331
- elsif Browser.loaded? 'Sizzle'
332
- def matches?(selector)
333
- `Sizzle.matchesSelector(#@native, #{selector})`
334
- end
335
- else
336
- def matches?(selector)
337
- raise NotImplementedError, 'selector matching unsupported'
338
- end
438
+ def width=(value)
439
+ size.width = value
339
440
  end
340
441
 
341
- # @abstract
442
+ # @!attribute [r] window
443
+ # @return [Window] the window for the element
342
444
  def window
343
445
  document.window
344
446
  end
345
447
 
346
- def inspect
347
- "#<DOM::Element: #{name}>"
348
- end
349
-
350
- class Attributes
351
- include Enumerable
352
-
353
- attr_reader :namespace
354
-
355
- def initialize(element, options)
356
- @element = element
357
- @namespace = options[:namespace]
358
- end
359
-
360
- def each(&block)
361
- return enum_for :each unless block_given?
362
-
363
- @element.attribute_nodes.each {|attr|
364
- yield attr.name, attr.value
365
- }
366
-
367
- self
368
- end
369
-
370
- def [](name)
371
- @element.get_attribute(name, namespace: @namespace)
448
+ if Browser.supports?('Query.xpath') || Browser.loaded?('wicked-good-xpath')
449
+ if Browser.loaded? 'wicked-good-xpath'
450
+ `wgxpath.install()`
372
451
  end
373
452
 
374
- def []=(name, value)
375
- @element.set_attribute(name, value, namespace: @namespace)
453
+ def xpath(path)
454
+ NodeSet[Native::Array.new(
455
+ `(#@native.ownerDocument || #@native).evaluate(path,
456
+ #@native, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)`,
457
+ get: :snapshotItem,
458
+ length: :snapshotLength)]
459
+ rescue
460
+ NodeSet[]
376
461
  end
377
-
378
- def merge!(hash)
379
- hash.each {|name, value|
380
- self[name] = value
381
- }
382
-
383
- self
462
+ else
463
+ # Query for children matching the given XPath.
464
+ #
465
+ # @param path [String] the XPath
466
+ #
467
+ # @return [NodeSet]
468
+ def xpath(path)
469
+ raise NotImplementedError, 'query by XPath unsupported'
384
470
  end
385
471
  end
386
472
  end