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 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