opal-rails 0.0.2 → 0.0.3
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.
- data/README.md +57 -7
- data/lib/assets/javascripts/rquery.js +3 -0
- data/lib/assets/javascripts/rquery/document.js.opal +100 -0
- data/lib/assets/javascripts/rquery/element.js.opal +361 -0
- data/lib/assets/javascripts/rquery/sizzle.js +1442 -0
- data/lib/opal-rails.rb +1 -0
- data/lib/opal/rails/haml_filter.rb +24 -0
- data/lib/opal/rails/version.rb +1 -1
- metadata +15 -10
data/README.md
CHANGED
@@ -1,28 +1,78 @@
|
|
1
1
|
# Opal/Rails adapter
|
2
2
|
|
3
|
-
|
3
|
+
For Rails 3.2 only.
|
4
|
+
|
4
5
|
|
5
|
-
|
6
|
+
## Installation
|
6
7
|
|
7
|
-
|
8
|
+
In your `Gemfile`
|
8
9
|
|
9
10
|
``` ruby
|
10
11
|
gem 'opal-rails'
|
11
12
|
```
|
12
13
|
|
13
|
-
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
### Asset Pipeline
|
14
18
|
|
15
19
|
``` js
|
16
|
-
//
|
20
|
+
// app/assets/application.js
|
21
|
+
|
22
|
+
// The main Opal VM
|
23
|
+
// require opal
|
24
|
+
|
25
|
+
// optional jQuery-like DOM manipulation for Opal
|
26
|
+
// require rquery
|
17
27
|
```
|
18
28
|
|
19
|
-
|
29
|
+
and then just use the `.opal` extensions:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# app/assets/javascripts/hi-world.js.opal
|
20
33
|
|
21
|
-
``` ruby
|
22
34
|
puts "G'day world!"
|
23
35
|
```
|
24
36
|
|
25
37
|
|
38
|
+
|
39
|
+
### As a template
|
40
|
+
|
41
|
+
You can use it for your dynamic templates too (Probably in conjunction with ERB to pass dynamic state)
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# app/views/posts/show.js.opal.erb
|
45
|
+
|
46
|
+
Element.id('<%= dom_id @post %>').show
|
47
|
+
```
|
48
|
+
|
49
|
+
|
50
|
+
### As an Haml filter (optional)
|
51
|
+
|
52
|
+
Of course you need to require `haml-rails` separately since its presence is not assumed
|
53
|
+
|
54
|
+
```haml
|
55
|
+
-# app/views/posts/show.html.haml
|
56
|
+
|
57
|
+
%article= post.body
|
58
|
+
|
59
|
+
%a#show-comments Display Comments!
|
60
|
+
|
61
|
+
.comments(style="display:none;")
|
62
|
+
- post.comments.each do |comment|
|
63
|
+
.comment= comment.body
|
64
|
+
|
65
|
+
:opal
|
66
|
+
Document.ready? do
|
67
|
+
Element.id('show-comments').on :click do
|
68
|
+
Element.find('.comments').first.show
|
69
|
+
false # aka preventDefault
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
|
75
|
+
|
26
76
|
## Licence
|
27
77
|
|
28
78
|
Copyright © 2012 by Elia Schito
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Document
|
2
|
+
|
3
|
+
# Returns the body element of the current page as an {Element}
|
4
|
+
# instance. Returns nil if the document hasn't finished loading
|
5
|
+
# (which might mean the body isn't actually ready yet).
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
#
|
9
|
+
# Document.body # => <body>
|
10
|
+
#
|
11
|
+
# @return [Element, nil] return this documents body element
|
12
|
+
def self.body
|
13
|
+
%x{
|
14
|
+
if (this.body) {
|
15
|
+
return this.body;
|
16
|
+
}
|
17
|
+
|
18
|
+
if (document.body) {
|
19
|
+
return this.body = #{ Element.new `document.body` };
|
20
|
+
}
|
21
|
+
|
22
|
+
return nil;
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the head element of this document as an {Element}.
|
27
|
+
#
|
28
|
+
# @return [Element] head element
|
29
|
+
def self.head
|
30
|
+
%x{
|
31
|
+
if (!this.head) {
|
32
|
+
var head = document.getElementsByTagName('head')[0]
|
33
|
+
this.head = #{ Element.new `head` };
|
34
|
+
}
|
35
|
+
|
36
|
+
return this.head;
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
%x{
|
41
|
+
var loaded = false, callbacks = [];
|
42
|
+
|
43
|
+
var trigger = function() {
|
44
|
+
if (loaded) return;
|
45
|
+
loaded = true;
|
46
|
+
|
47
|
+
for (var i = 0, length = callbacks.length; i < length; i++) {
|
48
|
+
var callback = callbacks[i];
|
49
|
+
callback.call(callback._s);
|
50
|
+
}
|
51
|
+
};
|
52
|
+
|
53
|
+
if (document.addEventListener) {
|
54
|
+
document.addEventListener('DOMContentLoaded', trigger, false);
|
55
|
+
}
|
56
|
+
else {
|
57
|
+
console.log("No Document.ready? in this browser");
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
# Used to both register blocks that should be once the Document is
|
62
|
+
# ready, as well as returning a boolean to indicate the ready state.
|
63
|
+
#
|
64
|
+
# Multiple blocks can be passed to this method which will then be run
|
65
|
+
# in order once the Document becomes ready. If no block is given then
|
66
|
+
# the ready state is just returned.
|
67
|
+
#
|
68
|
+
# The ready state is an indication of whether the document is ready
|
69
|
+
# for manipulation. Commonly, until the document is ready, there is
|
70
|
+
# no guarantee that the {body} element is created.
|
71
|
+
#
|
72
|
+
# This method will also try and perform in a cross browser way to
|
73
|
+
# eliminate any differences between browsers and their ready state.
|
74
|
+
#
|
75
|
+
# @example
|
76
|
+
#
|
77
|
+
# Document.ready? do
|
78
|
+
# puts "Document is ready and loaded!"
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# Document.ready? # => false
|
82
|
+
# # page load...
|
83
|
+
# Document.ready? # => true
|
84
|
+
#
|
85
|
+
# @return [true, false] document ready state
|
86
|
+
def self.ready?(&block)
|
87
|
+
%x{
|
88
|
+
if (block && block !== nil) {
|
89
|
+
if (loaded) {
|
90
|
+
block.call(block._s);
|
91
|
+
}
|
92
|
+
else {
|
93
|
+
callbacks.push(block);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
return loaded;
|
98
|
+
}
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,361 @@
|
|
1
|
+
class Element
|
2
|
+
|
3
|
+
# Returns an array of elements in the document matching the given
|
4
|
+
# css {selector}. Opal internally uses Sizzle to find elements, and
|
5
|
+
# each element in the returned array is already wrapped by an Opal
|
6
|
+
# {Element}.
|
7
|
+
#
|
8
|
+
# If no matching elements can be found in the document, then the
|
9
|
+
# returned array is simply empty.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
#
|
13
|
+
# # <body>
|
14
|
+
# # <div class="foo" id="a"></div>
|
15
|
+
# # <div class="foo" id="b"></div>
|
16
|
+
# # <p class="bar"></p>
|
17
|
+
# # </body>
|
18
|
+
#
|
19
|
+
# Element.find('.foo')
|
20
|
+
# # => [<div class="foo", id="a">, <div class="foo" id="b">]
|
21
|
+
#
|
22
|
+
# Element.find('.bar')
|
23
|
+
# # => [<p class="bar">]
|
24
|
+
#
|
25
|
+
# Element.find('.baz')
|
26
|
+
# # => []
|
27
|
+
#
|
28
|
+
# @param [String] selector css selector to search for
|
29
|
+
# @return [Array<Element>] the matching elements
|
30
|
+
def self.find(selector)
|
31
|
+
%x{
|
32
|
+
var elements = Sizzle(selector);
|
33
|
+
|
34
|
+
for (var i = 0, length = elements.length; i < length; i++) {
|
35
|
+
elements[i] = #{ self.new `elements[i]` };
|
36
|
+
}
|
37
|
+
|
38
|
+
return elements;
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns an Element instance for the native element with the given
|
43
|
+
# {id} if it exists. If the element cannot be found then {nil} is
|
44
|
+
# returned.
|
45
|
+
#
|
46
|
+
# It is important to note that this method does not cache element
|
47
|
+
# instances. This means that calling this method twice with the same
|
48
|
+
# element id will allocate and return two seperate instances of this
|
49
|
+
# class which wrap the same element. The two instances will be
|
50
|
+
# {==} however.
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
#
|
54
|
+
# # <body>
|
55
|
+
# # <div id="foo"></div>
|
56
|
+
# # </body>
|
57
|
+
#
|
58
|
+
# Element.id('foo') # => <div id="foo">
|
59
|
+
# Element.id('bar') # => nil
|
60
|
+
#
|
61
|
+
# @param [String] id element id to get
|
62
|
+
# @return [Element, nil] matching element
|
63
|
+
def self.id(id)
|
64
|
+
%x{
|
65
|
+
var el = document.getElementById(id);
|
66
|
+
|
67
|
+
if (!el) {
|
68
|
+
return nil;
|
69
|
+
}
|
70
|
+
|
71
|
+
return #{self.new `el`};
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
# Alias to Element.id(). (FIXME: remove this?)
|
76
|
+
def self.find_by_id(id); self.id id; end
|
77
|
+
|
78
|
+
# Creates a new {Element} instance. This class can either create new
|
79
|
+
# elements, or wrap existing ones. Passing a string or no args to
|
80
|
+
# {Element.new} will create a new native element of the type {type}
|
81
|
+
# and then wrap that.
|
82
|
+
#
|
83
|
+
# Alternatively a native element can be passed to this method which
|
84
|
+
# will then become the wrapped element.
|
85
|
+
#
|
86
|
+
# The wrapped element is stored as the privat `el` property on the
|
87
|
+
# receiver, but you shouldn't really access it, and instead use the
|
88
|
+
# methods provided by this class to manipulate the element.
|
89
|
+
#
|
90
|
+
# @example Creating a new element
|
91
|
+
#
|
92
|
+
# e = Element.new # => <div>
|
93
|
+
# f = Element.new 'script' # => <script>
|
94
|
+
#
|
95
|
+
# @example Wrapping an existng element
|
96
|
+
#
|
97
|
+
# # <html>
|
98
|
+
# # <body>
|
99
|
+
# # <div id="foo"></div>
|
100
|
+
# # </body>
|
101
|
+
# # </html>
|
102
|
+
#
|
103
|
+
# Element.new(`document.getElementById('foo')`)
|
104
|
+
# # => <div id="foo">
|
105
|
+
#
|
106
|
+
# Element.new(`document.body`)
|
107
|
+
# # => <body>
|
108
|
+
#
|
109
|
+
# @param [String] type the tag name or native element
|
110
|
+
# @return [self] returns receiver
|
111
|
+
def initialize(type = :div)
|
112
|
+
%x{
|
113
|
+
if (typeof(type) === 'string') {
|
114
|
+
type = document.createElement(type);
|
115
|
+
}
|
116
|
+
if (!type || !type.nodeType) {
|
117
|
+
throw new Error('not a valid element');
|
118
|
+
}
|
119
|
+
|
120
|
+
this.el = type;
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
# @!group Classes
|
125
|
+
|
126
|
+
# Add a CSS class name to this element. This method will add the
|
127
|
+
# given class name only if it is not already set on the element.
|
128
|
+
# Trying to add a duplicate class has no effect. This method will
|
129
|
+
# always return the receiver for chainability.
|
130
|
+
#
|
131
|
+
# @example
|
132
|
+
#
|
133
|
+
# # <div id="foo"></div>
|
134
|
+
# # <div id="bar" class="big"></div>
|
135
|
+
#
|
136
|
+
# foo = Element.id 'foo'
|
137
|
+
# foo.add_class 'limes' # => <div id="foo" class="limes">
|
138
|
+
# foo.add_class 'limes' # => <div id="foo" class="limes">
|
139
|
+
#
|
140
|
+
# bar = Element.id 'bar'
|
141
|
+
# bar.add_class 'title' # => <div id="bar" class="big title">
|
142
|
+
# bar.add_class 'big' # => <div id="bar" class="big title">
|
143
|
+
#
|
144
|
+
# @param [String] name class name to add
|
145
|
+
# @return [Element] returns self
|
146
|
+
def add_class(name)
|
147
|
+
%x{
|
148
|
+
var className = this.el.className;
|
149
|
+
|
150
|
+
if (!className) {
|
151
|
+
this.el.className = name;
|
152
|
+
}
|
153
|
+
else if ((' ' + className + ' ').indexOf(' ' + name + ' ') === -1) {
|
154
|
+
this.el.className += (' ' + name);
|
155
|
+
}
|
156
|
+
|
157
|
+
return this;
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
def class_name
|
162
|
+
`this.el.className || ""`
|
163
|
+
end
|
164
|
+
|
165
|
+
def class_name=(name)
|
166
|
+
`this.el.className = name`
|
167
|
+
end
|
168
|
+
|
169
|
+
def remove_class(name)
|
170
|
+
%x{
|
171
|
+
var className = ' ' + this.el.className + ' ';
|
172
|
+
className = className.replace(' ' + name + ' ', ' ');
|
173
|
+
className = className.replace(/^\\s+/, '').replace(/\\s+$/, '');
|
174
|
+
|
175
|
+
this.el.className = className;
|
176
|
+
return this;
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
# @!endgroup
|
181
|
+
|
182
|
+
# @!group Events
|
183
|
+
|
184
|
+
def on(name, &block)
|
185
|
+
return unless block_given?
|
186
|
+
|
187
|
+
%x{
|
188
|
+
var el = this.el;
|
189
|
+
|
190
|
+
var func = function(evt) {
|
191
|
+
block.call(block._s);
|
192
|
+
return true;
|
193
|
+
};
|
194
|
+
|
195
|
+
if (el.addEventListener) {
|
196
|
+
el.addEventListener(name, func, false);
|
197
|
+
}
|
198
|
+
else {
|
199
|
+
el.attachEvent(name, func);
|
200
|
+
}
|
201
|
+
}
|
202
|
+
block
|
203
|
+
end
|
204
|
+
|
205
|
+
# @!endgroup
|
206
|
+
|
207
|
+
# Returns an array of elements matching the given css selector that
|
208
|
+
# are within the context of this element. That means, that only
|
209
|
+
# elements that are decendants of this element will be matched.
|
210
|
+
#
|
211
|
+
# If no elements are found, then an empty array is simply returned.
|
212
|
+
#
|
213
|
+
# @example
|
214
|
+
#
|
215
|
+
# # <div id="foo">
|
216
|
+
# # <p class="a" id="bill"></p>
|
217
|
+
# # <p class="b" id="tom"></p>
|
218
|
+
# # </div>
|
219
|
+
# # <div id="bar">
|
220
|
+
# # <p class="a" id="ben"></p>
|
221
|
+
# # </div>
|
222
|
+
#
|
223
|
+
# Element.id('foo').find '.a'
|
224
|
+
# # => [<p id="bill" class="a">]
|
225
|
+
#
|
226
|
+
# Element.id('bar').find '.b'
|
227
|
+
# # => []
|
228
|
+
#
|
229
|
+
# @param [String] selector css selector to match elements against
|
230
|
+
# @return [Array<Element>] array of matched elements
|
231
|
+
def find(selector)
|
232
|
+
%x{
|
233
|
+
var elements = Sizzle(selector, this.el);
|
234
|
+
|
235
|
+
for (var i = 0, length = elements.length; i < length; i++) {
|
236
|
+
elements[i] = #{ Element.new `elements[i]` };
|
237
|
+
}
|
238
|
+
|
239
|
+
return elements;
|
240
|
+
}
|
241
|
+
end
|
242
|
+
|
243
|
+
# Hides the receiver element by setting `display: none` as a css
|
244
|
+
# property on the element.
|
245
|
+
#
|
246
|
+
# @return [self] returns the element
|
247
|
+
def hide
|
248
|
+
%x{
|
249
|
+
this.el.style.display = 'none';
|
250
|
+
return this;
|
251
|
+
}
|
252
|
+
end
|
253
|
+
|
254
|
+
# Remove the element from the DOM. This method will try to remove this
|
255
|
+
# element from it's parent (if it has one). If the element is not
|
256
|
+
# currently in the DOM then there is no affect.
|
257
|
+
#
|
258
|
+
# @return [self] the element is returned
|
259
|
+
def remove
|
260
|
+
%x{
|
261
|
+
var el = this.el, parent = el.parentNode;
|
262
|
+
|
263
|
+
if (parent)
|
264
|
+
parent.removeChild(el);
|
265
|
+
|
266
|
+
return this;
|
267
|
+
}
|
268
|
+
end
|
269
|
+
|
270
|
+
# Attempts to make the element visible by removing any `display` css
|
271
|
+
# property on the element itself. This will only affect elements that
|
272
|
+
# have been hidden with a direct style property and will **not**
|
273
|
+
# overwrite any styles from a stylesheet.
|
274
|
+
#
|
275
|
+
# @return [self] returns the element
|
276
|
+
def show
|
277
|
+
%x{
|
278
|
+
this.el.style.display = '';
|
279
|
+
return this;
|
280
|
+
}
|
281
|
+
end
|
282
|
+
|
283
|
+
# Returns whether this element is visible or not.
|
284
|
+
# @return [true, false] return if element is visible or not
|
285
|
+
def visible?
|
286
|
+
`this.el.style.display !== 'none'`
|
287
|
+
end
|
288
|
+
|
289
|
+
def append_to_body
|
290
|
+
%x{
|
291
|
+
document.body.appendChild(this.el);
|
292
|
+
return this;
|
293
|
+
}
|
294
|
+
end
|
295
|
+
|
296
|
+
def id
|
297
|
+
`this.el.id`
|
298
|
+
end
|
299
|
+
|
300
|
+
def id=(id)
|
301
|
+
`this.el.id = id`
|
302
|
+
end
|
303
|
+
|
304
|
+
def inspect
|
305
|
+
%x{
|
306
|
+
var val, el = this.el, str = '<' + el.tagName.toLowerCase();
|
307
|
+
|
308
|
+
if (val = el.id) str += (' id="' + val + '"');
|
309
|
+
if (val = el.className) str += (' class="' + val + '"');
|
310
|
+
|
311
|
+
return str + '>';
|
312
|
+
}
|
313
|
+
end
|
314
|
+
|
315
|
+
alias to_s inspect
|
316
|
+
|
317
|
+
def empty?
|
318
|
+
`!!(/^\s*$/.test(this.el.innerHTML))`
|
319
|
+
end
|
320
|
+
|
321
|
+
def clear
|
322
|
+
%x{
|
323
|
+
var el = this.el;
|
324
|
+
|
325
|
+
while (el.firstChild)
|
326
|
+
el.removeChild(el.firstChild);
|
327
|
+
|
328
|
+
return this;
|
329
|
+
}
|
330
|
+
end
|
331
|
+
|
332
|
+
def ==(other)
|
333
|
+
`this.el === other.el`
|
334
|
+
end
|
335
|
+
|
336
|
+
def html=(html)
|
337
|
+
`this.el.innerHTML = html`
|
338
|
+
end
|
339
|
+
|
340
|
+
def append(child)
|
341
|
+
`this.el.appendChild(child.el)`
|
342
|
+
end
|
343
|
+
|
344
|
+
def tag
|
345
|
+
%x{
|
346
|
+
var tag = this.el.tagName;
|
347
|
+
return tag ? tag.toLowerCase() : '';
|
348
|
+
}
|
349
|
+
end
|
350
|
+
|
351
|
+
def has_class?(name)
|
352
|
+
%x{
|
353
|
+
var full = this.el.className;
|
354
|
+
|
355
|
+
if (full === name) return true;
|
356
|
+
if (full === '') return false;
|
357
|
+
|
358
|
+
return (new RegExp("(^|\\s+)" + name + "(\\s+|$)")).test(full);
|
359
|
+
}
|
360
|
+
end
|
361
|
+
end
|