opal-browser 0.2.0.beta1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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