opal-rails 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,28 +1,78 @@
1
1
  # Opal/Rails adapter
2
2
 
3
- ## Usage
3
+ For Rails 3.2 only.
4
+
4
5
 
5
- For Rails 3.1+
6
+ ## Installation
6
7
 
7
- ### Gemfile
8
+ In your `Gemfile`
8
9
 
9
10
  ``` ruby
10
11
  gem 'opal-rails'
11
12
  ```
12
13
 
13
- ### app/assets/javascripts/application.js
14
+
15
+ ## Usage
16
+
17
+ ### Asset Pipeline
14
18
 
15
19
  ``` js
16
- // require 'opal'
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
- ### app/assets/javascripts/hi-world.js.opal
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,3 @@
1
+ //= require rquery/sizzle
2
+ //= require rquery/document
3
+ //= require rquery/element
@@ -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