red 4.1.0 → 4.1.1

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.
@@ -0,0 +1,216 @@
1
+ require 'selectors'
2
+
3
+ # The +Document+ object enables access to top-level HTML elements like
4
+ # <i><head></i>, <i><html></i>, and <i><body></i>.
5
+ #
6
+ # +Document+ also provides "DOM-ready" functionality with
7
+ # <tt>Document.ready?</tt> and element-finding functionality through the
8
+ # powerful <tt>Document.[]</tt> method.
9
+ #
10
+ module Document
11
+
12
+ `document.head=document.getElementsByTagName('head')[0]`
13
+ `document.html=document.getElementsByTagName('html')[0]`
14
+ `document.window=(document.defaultView||document.parentWindow)`
15
+ `c$Document.__native__= document`
16
+
17
+ # call-seq:
18
+ # Document["#id"] -> element or nil
19
+ # Document["selector"] -> [element, ...]
20
+ # Document[str, ...] -> [element, ...]
21
+ #
22
+ # The first form returns the +Element+ identified by the id <i>str</i> (e.g.
23
+ # <i>'#my_id'</i>), or +nil+ if no such element is found. The second form
24
+ # returns the array of elements identified by the selector <i>str</i>.
25
+ # The third form returns the array of elements found by calling
26
+ # <tt>Document.[]</tt> on each arg. The fourth form returns _element_
27
+ # unchanged.
28
+ #
29
+ # The first form returns the +Element+ identified by the string
30
+ # <i>"#id"</i>, or +nil+ if no such element is found.
31
+ #
32
+ # Document['#content'] #=> #<Element: DIV id="content">
33
+ #
34
+ # The second form returns the array of +Element+ objects that match the
35
+ # string <i>"selector"</i>, which takes the format of a CSS3 selector. See
36
+ # http://www.w3.org/TR/css3-selectors/ for details.
37
+ #
38
+ #
39
+ #
40
+ # Available Selectors
41
+ # Document[str] in its second form takes a selector string and scans the document
42
+ # returing an array of any elements that match the selector string.
43
+ #
44
+ # The selectors string takes the format of a CSS3 selector: http://www.w3.org/TR/css3-selectors/
45
+ #
46
+ # Class Selector
47
+ # Returns an array of elements that have the specified class name
48
+ # Document['.my_class'] #=> [#<Element: DIV class="my_class">, #<Element: DIV class="my_class">, #<Element: SPAN class="my_class">]
49
+ #
50
+ # Tag Selector
51
+ # Returns an array of elements that have the specified tag
52
+ # Document['a'] #=> [#<Element: A href="/foo/bar">, #<Element: A href="/foo/baz">, #<Element: A href="/foo/bat">]
53
+ #
54
+ def self.[](*args)
55
+ if args.length == 1
56
+ return self.find_by_string(args[0])
57
+ else
58
+ args.map do |str|
59
+ Document[str]
60
+ end.compact
61
+ #return self.find_many_with_array(args.unshift(obj))
62
+ end
63
+ end
64
+
65
+ # call-seq:
66
+ # Document.body -> element
67
+ #
68
+ # Returns the <i><body></i> element of the current page.
69
+ #
70
+ # Document.body #=> #<Element: BODY>
71
+ #
72
+ def self.body
73
+ `$E(document.body)`
74
+ end
75
+
76
+ # call-seq:
77
+ # Document.execute_js(str) -> str
78
+ #
79
+ # Executes _str_ as JavaScript, then returns _str_.
80
+ #
81
+ def self.execute_js(str)
82
+ return str if str == ''
83
+ if `window.execScript`
84
+ `window.execScript(str.__value__)`
85
+ else
86
+ `scriptElement = document.createElement('script')`
87
+ `scriptElement.setAttribute('type','text/javascript')`
88
+ `scriptElement.text = str`
89
+ `document.head.appendChild(scriptElement)`
90
+ `document.head.removeChild(scriptElement)`
91
+ end
92
+ return str
93
+ end
94
+
95
+ # call-seq:
96
+ # Document.head -> element
97
+ #
98
+ # Returns the <i><head></i> element of the current page.
99
+ #
100
+ # Document.head #=> #<Element: HEAD>
101
+ #
102
+ def self.head
103
+ `$E(document.head)`
104
+ end
105
+
106
+ # call-seq:
107
+ # Document.html -> element
108
+ #
109
+ # Returns the <i><html></i> element of the current page.
110
+ #
111
+ # Document.html #=> #<Element: HTML>
112
+ #
113
+ def self.html
114
+ `$E(document.html)`
115
+ end
116
+
117
+ # call-seq:
118
+ # Document.ready? { block } -> nil
119
+ #
120
+ # When the DOM is finished loading, executes the given _block_, then returns
121
+ # +nil+.
122
+ #
123
+ # Document.ready? do
124
+ # puts self.title
125
+ # end
126
+ #
127
+ # produces:
128
+ #
129
+ # "RDoc Documentation"
130
+ #
131
+ def self.ready?(&block)
132
+ @proc = block
133
+ `document.addEventListener('DOMContentLoaded', function(){document.__loaded__=true;#{@proc.call};}.m$(this), false)`
134
+ return nil
135
+ end
136
+
137
+ # call-seq:
138
+ # Document.title -> string
139
+ #
140
+ # Returns the title of the current page.
141
+ #
142
+ # Document.title #=> "RDoc Documentation"
143
+ #
144
+ def self.title
145
+ `$q(document.title)`
146
+ end
147
+
148
+ # Uses the Selector library to find native elements and retrun an array of extended elements
149
+ def self.find_all_by_selector(selector) # :nodoc:
150
+ `Array.fromCollection(Selectors.Utils.search(document, selector.__value__, {}));`
151
+ end
152
+
153
+ # Uses the browser's native getElementById to find an element. Returns an extended element.
154
+ def self.find_by_id(str) # :nodoc:
155
+ `$E(document.getElementById(str.__value__))`
156
+ end
157
+
158
+ # Finds an element from a provided string. If the string is a represents a single id ('#my_id') calls
159
+ # self.find_all_by_id, otherwise calls self.find_all_by_selector
160
+ def self.find_by_string(str) # :nodoc:
161
+ `str.__value__.match(/^#[a-zA-z_]*$/)` ? self.find_by_id(`$q(str.__value__.replace('#',''))`) : self.find_all_by_selector(str)
162
+ end
163
+
164
+ def self.find_many_with_array(ary) # :nodoc:
165
+ `for(var i=0,l=ary.length,result=[];i<l;++i){var el=#{Document[`ary[i]`]};if($T(el)){result.push(el);};}`
166
+ return `result`.flatten
167
+ end
168
+
169
+ # Walks the DOM from a particular element. The first step it talks in the walk with either
170
+ # be the specified start_relation or the specified path. If 'all' is set to false, the walk
171
+ # will termiante and return the single element. Otherwise, it will continue the walk until it
172
+ # has reached the end of the specified path.
173
+ # The walk will then termiante and return an array of all elements on the path.
174
+ #
175
+ # if an optional <tt>match_selector<tt> is provided, only element(s) that match
176
+ # the selector can be returned
177
+ #
178
+ # Examples:
179
+ # Document.walk(my_element, 'parentNode', nil, nil, false)
180
+ # will go down the parentNode path of my_element, starting at the my_element, and
181
+ # return just a single element
182
+ #
183
+ # Document.walk(my_element, 'parentNode', nil, nil, true)
184
+ # will go down the parentNode path of my_element, starting at my_element, and
185
+ # return an array of all elements on the parentNode path
186
+ #
187
+ # Document.walk(my_element, 'nextSibling', 'firstChild', nil, true)
188
+ # will go down the nextSibling path of my_element, starting at my_element's firstChild
189
+ # and return an array of all elements on the nextSibling path from that starting point.
190
+ #
191
+ # Document.walk(my_element, 'nextSibling', 'firstChild', '.my_class', true)
192
+ # will go down the nextSibling path of my_element, starting at my_element's firstChild
193
+ # and return an array of all elements on the nextSibling path from that starting point that
194
+ # have the class 'my_class'
195
+ #
196
+ def self.walk(element, path, startRelation, matchSelector, all) # :nodoc
197
+ `if(startRelation){startRelation = startRelation.__value__;};
198
+ var el = element.__native__[startRelation || path.__value__],elements = [];
199
+ while (el){
200
+ if (el.nodeType == 1 && (#{!matchSelector} || Element.match(el, #{matchSelector}))){
201
+ if (!all) {return $E(el);}
202
+ elements.push($E(el));
203
+ }
204
+ el = el[path.__value__];
205
+ }`
206
+ return `(all) ? elements : nil`
207
+ end
208
+
209
+ def self.window # :nodoc:
210
+ Window
211
+ end
212
+
213
+ def self.document # :nodoc
214
+ self
215
+ end
216
+ end
@@ -0,0 +1,417 @@
1
+ require 'document'
2
+ require 'user_events'
3
+ require 'code_events'
4
+ require 'accessors'
5
+
6
+ # +Element+ objects represent DOM elements from the browser.
7
+ #
8
+ class Element
9
+ `window.$E=function(element){if(element==null){return nil;};var E=c$Element.m$new(null);E.__native__=element;return E;}`
10
+
11
+ include UserEvents
12
+ include CodeEvents
13
+
14
+ def self.destroy(elem) # :nodoc:
15
+ `var el = elem.__native__ || elem`
16
+ `c$Element.m$empty(el)`
17
+ `c$Element.m$remove(el)`
18
+ return true
19
+ end
20
+
21
+ def self.empty(elem) # :nodoc:
22
+ `for (var c=(elem.__native__||elem).childNodes,i=c.length;i>0;){c$Element.m$destroy(c[--i]);};`
23
+ return true
24
+ end
25
+
26
+ def self.remove(elem) # :nodoc:
27
+ `var el = elem.__native__ || elem`
28
+ `(el.parentNode) ? el.parentNode.removeChild(el) : this`
29
+ end
30
+
31
+ # call-seq:
32
+ # Element.new(sym, attributes = {}) -> element
33
+ #
34
+ # Returns a new +Element+ with the tag _sym_ and the given attributes.
35
+ #
36
+ # Element.new(:div, :id => 'my_div', :class => 'inserted') #=> #<Element: DIV id="my_div" class="inserted">
37
+ #
38
+ def initialize(tag, attributes = {})
39
+ `if(!tag){return nil;}`
40
+ `this.__native__ = document.createElement(tag.__value__)`
41
+ self.set_properties(attributes)
42
+ end
43
+
44
+ def ==(elem) # :nodoc:
45
+ `this.__native__ === elem.__native__`
46
+ end
47
+
48
+ def ===(elem) # :nodoc:
49
+ `this.__native__ === elem.__native__`
50
+ end
51
+
52
+ # call-seq:
53
+ # elem[str] -> array
54
+ #
55
+ # *args?
56
+ #
57
+ def [](expression, *args)
58
+ `if (expression.__value__.match(/^#[a-zA-z_]*$/) && #{args.empty?}){return #{self}.__native__.getElementById(expression.__value__.replace('#',''));}`
59
+ expression = expression.split(',')
60
+ items = []
61
+ `for (var i = 0, l = expression.length, local = {}; i < l; i++){
62
+ var selector = expression[i].__value__, elements = Selectors.Utils.search(#{self}.__native__, selector, local);
63
+ elements = Array.fromCollection(elements);
64
+ items = (i == 0) ? elements : items.concat(elements);
65
+ }`
66
+ return items
67
+ end
68
+
69
+ # call-seq:
70
+ # elem.children -> array
71
+ #
72
+ # Returns the array of _elem_'s child elements on the DOM tree.
73
+ #
74
+ # <div id="container">
75
+ # <div id="a_element"></div>
76
+ # <div id="a_inner_element"></div>
77
+ # </div>
78
+ # <div id="b_element"></div>
79
+ # </div>
80
+ #
81
+ # Document['#container'].children #=> [#<Element: DIV id="a_element">, #<Element: DIV id="b_element">]
82
+ #
83
+ def children(match_selector = nil)
84
+ Document.walk(self, 'nextSibling', 'firstChild', match_selector, true)
85
+ end
86
+
87
+ # TODO: re: the native object and event listeners; does it really do this stuff?
88
+ # Removes the element from the page and the DOM, destroys the element object, the native object,
89
+ # any attached event listeners, and frees their memory location. Returns +true+ if successful, +false+ otherwise.
90
+
91
+ # call-seq:
92
+ # elem.destroy! -> true
93
+ #
94
+ # Removes _elem_ and all its children from the page and from the DOM, then
95
+ # returns +true+.
96
+ #
97
+ def destroy!
98
+ Element.destroy(self)
99
+ return true
100
+ end
101
+
102
+ def document # :nodoc:
103
+ `this.__native__.ownerDocument`
104
+ end
105
+
106
+ # call-seq:
107
+ # elem.empty! -> true or false
108
+ #
109
+ # Removes _elem_'s inner text and child elements from the page.
110
+ #
111
+ def empty!
112
+ Element.empty(self)
113
+ return self
114
+ end
115
+
116
+ def eql?(elem) # :nodoc:
117
+ `this.__native__ === elem.__native__`
118
+ end
119
+
120
+ # call-seq:
121
+ # elem.first_child -> element
122
+ #
123
+ # Returns the first child element of _elem_ on the DOM tree, or +nil+ if no
124
+ # such element is found.
125
+ #
126
+ # <div id="container">
127
+ # <div id="a_element"></div>
128
+ # <div id="b_element"></div>
129
+ # <div id="c_element"></div>
130
+ # <div id="d_element"></div>
131
+ # </div>
132
+ #
133
+ # Document['#container'].first_child #=> #<Element: DIV id="a_element">
134
+ #
135
+ def first_child(match_selector = nil)
136
+ Document.walk(self, 'nextSibling', 'firstChild', match_selector, false)
137
+ end
138
+
139
+ # call-seq:
140
+ # elem.insert(other, sym = :bottom) -> elem
141
+ #
142
+ # Returns _elem_ with _other_ inserted at location _sym_.
143
+ #
144
+ # Inserting into <tt>:bottom</tt> (default location):
145
+ #
146
+ # <div id='a_element'>
147
+ # <div id='b_element'></div>
148
+ # <div id='c_element'></div>
149
+ # </div>
150
+ # <div id='d_element'></div>
151
+ #
152
+ # elem = Document['#a_element']
153
+ # other = Document['#d_element']
154
+ # elem.insert(other)
155
+ #
156
+ # produces:
157
+ #
158
+ # <div id='a_element'>
159
+ # <div id='b_element'></div>
160
+ # <div id='c_element'></div>
161
+ # <div id='d_element'></div>
162
+ # </div>
163
+ #
164
+ # Inserting into <tt>:top</tt>:
165
+ #
166
+ # <div id='a_element'>
167
+ # <div id='b_element'></div>
168
+ # <div id='c_element'></div>
169
+ # </div>
170
+ # <div id='d_element'></div>
171
+ #
172
+ # elem = Document['#a_element']
173
+ # other = Document['#d_element']
174
+ # elem.insert(other, :top)
175
+ #
176
+ # produces:
177
+ #
178
+ # <div id='a_element'>
179
+ # <div id='d_element'></div>
180
+ # <div id='b_element'></div>
181
+ # <div id='c_element'></div>
182
+ # </div>
183
+ #
184
+ # Inserting <tt>:before</tt>:
185
+ #
186
+ # <div id='a_element'>
187
+ # <div id='b_element'></div>
188
+ # <div id='c_element'></div>
189
+ # </div>
190
+ # <div id='d_element'></div>
191
+ #
192
+ # elem = Document['#a_element']
193
+ # other = Document['#d_element']
194
+ # elem.insert(other, :before)
195
+ #
196
+ # produces:
197
+ #
198
+ # <div id='d_element'></div>
199
+ # <div id='a_element'>
200
+ # <div id='b_element'></div>
201
+ # <div id='c_element'></div>
202
+ # </div>
203
+ #
204
+ def insert(element, where = :bottom)
205
+ self.send("insert_#{where.to_s}", element)
206
+ return self
207
+ end
208
+
209
+ def insert_after(element) # :nodoc
210
+ `if (!element.parentNode) return`
211
+ `next = #{self}.__native__.nextSibling`
212
+ `(next) ? #{self}.__native__.parentNode.insertBefore(#{element}.__native__, next) : #{self}__native__.parentNode.appendChild(#{element}.__native__)`
213
+ return true
214
+ end
215
+
216
+ def insert_before(element) # :nodoc:
217
+ `if (#{self}.__native__.parentNode) #{self}.__native__.parentNode.insertBefore(#{element}.__native__, #{self}.__native__)`
218
+ return true
219
+ end
220
+
221
+ def insert_bottom(element) # :nodoc
222
+ `#{self}.__native__.appendChild(#{element}.__native__)`
223
+ return true
224
+ end
225
+ alias :insert_inside :insert_bottom
226
+
227
+ def insert_top(element) # :nodoc:
228
+ `first = #{self}.__native__.firstChild`
229
+ `(first) ? #{self}.__native__.insertBefore(#{element}.__native__, first) : #{self}.__native__.appendChild(#{element}.__native__)`
230
+ return true
231
+ end
232
+
233
+ # call-seq:
234
+ # elem.inspect -> string
235
+ #
236
+ # Returns a string representation of _elem_ including its tag name, classes,
237
+ # and id.
238
+ #
239
+ # <div style="width:300px" id="a_element" class="draggable container">
240
+ #
241
+ # Document['#a_element'].inspect #=> "#<Element: DIV id=\"a_element\" class=\"draggable container\">"
242
+ #
243
+ def inspect
244
+ attributes = [`$q(this.__native__.tagName.toUpperCase())`]
245
+ attributes << `$q('id="'+this.__native__.id+'"')` if `this.__native__.id!==''`
246
+ attributes << `$q('class="'+this.__native__.className+'"')` if `this.__native__.className!==''`
247
+ "#<Element: %s>" % attributes.join(' ')
248
+ end
249
+
250
+ # call-seq:
251
+ # elem.is_body? -> true or false
252
+ #
253
+ # Returns +true+ if the element is the body element, +false+ otherwise.
254
+ #
255
+ # Document['#my_div'].is_body? #=> false
256
+ # Document.body.is_body? #=> true
257
+ #
258
+ def is_body?
259
+ `(/^(?:body|html)$/i).test(#{self}.__native__.tagName)`
260
+ end
261
+
262
+ # call-seq:
263
+ # elem.last_child -> element or nil
264
+ #
265
+ # Returns the last child element of _elem_ on the DOM tree, or +nil+ if no
266
+ # such element is found.
267
+ #
268
+ # <div id="container">
269
+ # <div id="a_element"></div>
270
+ # <div id="b_element"></div>
271
+ # <div id="c_element"></div>
272
+ # <div id="d_element"></div>
273
+ # </div>
274
+ #
275
+ # Document['#container'].last_child #=> #<Element: DIV id="d_element">
276
+ #
277
+ def last_child(match_selector = nil)
278
+ Document.walk(self, 'previousSibling', 'lastChild', match_selector, false)
279
+ end
280
+
281
+ # call-seq:
282
+ # elem.next_element -> element or nil
283
+ #
284
+ # Returns the sibling element immediately following _elem_ on the DOM tree,
285
+ # or +nil+ if no such element is found.
286
+ #
287
+ # <div id='container'>
288
+ # <div id='a_element'></div>
289
+ # <div id='b_element'></div>
290
+ # <div id='c_element'></div>
291
+ # <div id='d_element'></div>
292
+ # </div>
293
+ #
294
+ # Document['#b_element'].next_element #=> #<Element: DIV id="c_element">
295
+ #
296
+ def next_element(match_selector = nil)
297
+ Document.walk(self, 'nextSibling', nil, match_selector, false)
298
+ end
299
+
300
+ # call-seq:
301
+ # elem.next_elements -> array
302
+ #
303
+ # Returns the array of sibling elements that follow _elem_ on the DOM tree.
304
+ #
305
+ # <div id='container'>
306
+ # <div id='a_element'></div>
307
+ # <div id='b_element'></div>
308
+ # <div id='c_element'></div>
309
+ # <div id='d_element'></div>
310
+ # </div>
311
+ #
312
+ # elem = Document['#b_element'] #=> #<Element: DIV id="b_element">
313
+ # elem.previous_elements #=> [#<Element: DIV id="c_element">, #<Element: DIV id="d_element">]
314
+ #
315
+ def next_elements(match_selector = nil)
316
+ Document.walk(self, 'nextSibling', nil, match_selector, true)
317
+ end
318
+
319
+ # call-seq:
320
+ # elem.parent -> element or nil
321
+ #
322
+ # Returns the parent element of _elem_ on the DOM tree, or +nil+ if no such
323
+ # element is found.
324
+ #
325
+ # <div id="container">
326
+ # <div id="a_element"></div>
327
+ # <div id="b_element"></div>
328
+ # <div id="c_element"></div>
329
+ # <div id="d_element"></div>
330
+ # </div>
331
+ #
332
+ # Document['#c_element'].parent #=> #<Element: DIV id="container">
333
+ # Document.body.parent #=> #<Element: HTML>
334
+ # Document.body.parent.parent #=> nil
335
+ #
336
+ def parent(match_selector = nil)
337
+ Document.walk(self, 'parentNode', nil, match_selector, false)
338
+ end
339
+
340
+ # call-seq:
341
+ # elem.parents -> array
342
+ #
343
+ # Returns the array of _elem_'s ancestors on the DOM tree.
344
+ #
345
+ # <div id='container'>
346
+ # <div id='a_element'></div>
347
+ # <div id='b_element'>
348
+ # <div id='b_inner_element'></div>
349
+ # </div>
350
+ # <div id='c_element'></div>
351
+ # <div id='d_element'></div>
352
+ # </div>
353
+ #
354
+ # Document['#b_inner_element'].parents #=> [#<Element: DIV id="b_element">, #<Element: DIV id="container">, <Element: BODY>, <Element: HTML>]
355
+ # Document.html.parents #=> []
356
+ #
357
+ def parents(match_selector = nil)
358
+ Document.walk(self, 'parentNode', nil, match_selector, true)
359
+ end
360
+
361
+ # call-seq:
362
+ # elem.previous_element -> element or nil
363
+ #
364
+ # Returns the sibling element immediately preceding _elem_ on the DOM tree,
365
+ # or +nil+ if no such element is found.
366
+ #
367
+ # <div id='container'>
368
+ # <div id='a_element'></div>
369
+ # <div id='b_element'></div>
370
+ # <div id='c_element'></div>
371
+ # <div id='d_element'></div>
372
+ # </div>
373
+ #
374
+ # Document['#b_element'].previous_element #=> #<Element: DIV id="a_element">
375
+ #
376
+ def previous_element(match_selector = nil)
377
+ Document.walk(self, 'previousSibling', nil, match_selector, false)
378
+ end
379
+
380
+ # call-seq:
381
+ # elem.previous_elements -> array
382
+ #
383
+ # Returns the array of sibling elements that precede _elem_ on the DOM tree.
384
+ #
385
+ # <div id='container'>
386
+ # <div id='a_element'></div>
387
+ # <div id='b_element'></div>
388
+ # <div id='c_element'></div>
389
+ # <div id='d_element'></div>
390
+ # </div>
391
+ #
392
+ # elem = Document['#c_element'] #=> #<Element: DIV id="c_element">
393
+ # elem.previous_elements #=> [#<Element: DIV id="a_element">, #<Element: DIV id="b_element">]
394
+ #
395
+ def previous_elements(match_selector = nil)
396
+ Document.walk(self, 'previousSibling', nil, match_selector, true)
397
+ end
398
+
399
+ # call-seq:
400
+ # elem.remove! -> elem
401
+ #
402
+ # Removes _elem_ and all of its child elements from the page, then returns
403
+ # _elem_.
404
+ #
405
+ def remove!
406
+ Element.remove(self)
407
+ return self
408
+ end
409
+
410
+ # call-seq:
411
+ # elem.to_s -> string
412
+ #
413
+ # FIX: Incomplete
414
+ #
415
+ def to_s
416
+ end
417
+ end