reactive-record 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,262 @@
1
+ module ReactiveRecord
2
+
3
+ # will repeatedly execute the block until it is loaded
4
+ # immediately returns a promise that will resolve once the block is loaded
5
+
6
+ def self.load(&block)
7
+ promise = Promise.new
8
+ @load_stack ||= []
9
+ @load_stack << @loads_pending
10
+ @loads_pending = nil
11
+ result = block.call
12
+ if @loads_pending
13
+ @blocks_to_load ||= []
14
+ @blocks_to_load << [promise, block]
15
+ else
16
+ promise.resolve result
17
+ end
18
+ @loads_pending = @load_stack.pop
19
+ promise
20
+ end
21
+
22
+ def self.loads_pending!
23
+ @loads_pending = true
24
+ end
25
+
26
+ def self.run_blocks_to_load
27
+ if @blocks_to_load
28
+ blocks_to_load = @blocks_to_load
29
+ @blocks_to_load = []
30
+ blocks_to_load.each do |promise_and_block|
31
+ @loads_pending = nil
32
+ result = promise_and_block.last.call
33
+ if @loads_pending
34
+ @blocks_to_load << promise_and_block
35
+ else
36
+ promise_and_block.first.resolve result
37
+ end
38
+ end
39
+ end
40
+ rescue Exception => e
41
+ message = "ReactiveRecord.run_blocks_to_load exception raised: #{e}"
42
+ `console.error(#{message})`
43
+ end
44
+
45
+
46
+ # Adds while_loading feature to React
47
+ # to use attach a .while_loading handler to any element for example
48
+ # div { "displayed if everything is loaded" }.while_loading { "displayed while I'm loading" }
49
+ # the contents of the div will be switched (using jQuery.show/hide) depending on the state of contents of the first block
50
+
51
+ # To notify React that something is loading use React::WhileLoading.loading!
52
+ # once everything is loaded then do React::WhileLoading.loaded_at message (typically a time stamp just for debug purposes)
53
+
54
+ class WhileLoading
55
+
56
+ include React::IsomorphicHelpers
57
+
58
+ before_first_mount do
59
+ @css_to_preload = ""
60
+ @while_loading_counter = 0
61
+ end
62
+
63
+ def get_next_while_loading_counter
64
+ @while_loading_counter += 1
65
+ end
66
+
67
+ def preload_css(css)
68
+ @css_to_preload << css << "\n"
69
+ end
70
+
71
+ prerender_footer do
72
+ "<style>\n#{@css_to_preload}\n</style>".tap { @css_to_preload = ""}
73
+ end
74
+
75
+ if RUBY_ENGINE == 'opal'
76
+
77
+ # I DONT THINK WE USE opal-jquery in this module anymore - require 'opal-jquery' if opal_client?
78
+
79
+ include React::Component
80
+
81
+ required_param :loading
82
+ required_param :loaded_children
83
+ required_param :loading_children
84
+ required_param :element_type
85
+ required_param :element_props
86
+ optional_param :display, default: ""
87
+
88
+ class << self
89
+
90
+ def loading?
91
+ @is_loading
92
+ end
93
+
94
+ def loading!
95
+ React::RenderingContext.waiting_on_resources = true
96
+ React::State.get_state(self, :loaded_at)
97
+ @is_loading = true
98
+ end
99
+
100
+ def loaded_at(loaded_at)
101
+ React::State.set_state(self, :loaded_at, loaded_at)
102
+ @is_loading = false
103
+ end
104
+
105
+ def add_style_sheet
106
+ @style_sheet ||= %x{
107
+ $('<style type="text/css">'+
108
+ ' .reactive_record_is_loading > .reactive_record_show_when_loaded { display: none; }'+
109
+ ' .reactive_record_is_loaded > .reactive_record_show_while_loading { display: none; }'+
110
+ '</style>').appendTo("head")
111
+ }
112
+ end
113
+
114
+ end
115
+
116
+ before_mount do
117
+ @uniq_id = WhileLoading.get_next_while_loading_counter
118
+ WhileLoading.preload_css(
119
+ ".reactive_record_while_loading_container_#{@uniq_id} > :nth-child(1n+#{loaded_children.count+1}) {\n"+
120
+ " display: none;\n"+
121
+ "}\n"
122
+ )
123
+ end
124
+
125
+ after_mount do
126
+ @waiting_on_resources = loading
127
+ WhileLoading.add_style_sheet
128
+ %x{
129
+ var node = #{@native}.getDOMNode();
130
+ $(node).children(':nth-child(-1n+'+#{loaded_children.count}+')').addClass('reactive_record_show_when_loaded');
131
+ $(node).children(':nth-child(1n+'+#{loaded_children.count+1}+')').addClass('reactive_record_show_while_loading');
132
+ }
133
+ end
134
+
135
+ after_update do
136
+ @waiting_on_resources = loading
137
+ end
138
+
139
+ def render
140
+ props = element_props.dup
141
+ classes = [props[:class], props[:className], "reactive_record_while_loading_container_#{@uniq_id}"].compact.join(" ")
142
+ props.merge!({
143
+ "data-reactive_record_while_loading_container_id" => @uniq_id,
144
+ "data-reactive_record_enclosing_while_loading_container_id" => @uniq_id,
145
+ class: classes
146
+ })
147
+ React.create_element(element_type, props) { loaded_children + loading_children }
148
+ end
149
+
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+
156
+ module React
157
+
158
+ class Element
159
+
160
+ def while_loading(display = "", &loading_display_block)
161
+
162
+ loaded_children = []
163
+ loaded_children = block.call.dup if block
164
+
165
+ loading_children = [display]
166
+ loading_children = RenderingContext.build do |buffer|
167
+ result = loading_display_block.call
168
+ buffer << result.to_s if result.is_a? String
169
+ buffer.dup
170
+ end if loading_display_block
171
+ RenderingContext.replace(
172
+ self,
173
+ React.create_element(
174
+ ReactiveRecord::WhileLoading,
175
+ loading: waiting_on_resources,
176
+ loading_children: loading_children,
177
+ loaded_children: loaded_children,
178
+ element_type: type,
179
+ element_props: properties)
180
+ )
181
+ end
182
+
183
+ def hide_while_loading
184
+ while_loading
185
+ end
186
+
187
+ end
188
+
189
+ module Component
190
+
191
+ alias_method :original_component_did_mount, :component_did_mount
192
+
193
+ def component_did_mount(*args)
194
+ original_component_did_mount(*args)
195
+ reactive_record_link_to_enclosing_while_loading_container
196
+ reactive_record_link_set_while_loading_container_class
197
+ end
198
+
199
+ alias_method :original_component_did_update, :component_did_update
200
+
201
+ def component_did_update(*args)
202
+ original_component_did_update(*args)
203
+ reactive_record_link_set_while_loading_container_class
204
+ end
205
+
206
+ def reactive_record_link_to_enclosing_while_loading_container
207
+ # Call after any component mounts - attaches the containers loading id to this component
208
+ # Fyi, the while_loading container is responsible for setting its own link to itself
209
+
210
+ %x{
211
+ var node = #{@native}.getDOMNode();
212
+ if (!$(node).is('[data-reactive_record_enclosing_while_loading_container_id]')) {
213
+ var while_loading_container = $(node).closest('[data-reactive_record_while_loading_container_id]')
214
+ if (while_loading_container.length > 0) {
215
+ var container_id = $(while_loading_container).attr('data-reactive_record_while_loading_container_id')
216
+ $(node).attr('data-reactive_record_enclosing_while_loading_container_id', container_id)
217
+ }
218
+ }
219
+ }
220
+
221
+ end
222
+
223
+ def reactive_record_link_set_while_loading_container_class
224
+
225
+ %x{
226
+
227
+ var node = #{@native}.getDOMNode();
228
+ var while_loading_container_id = $(node).attr('data-reactive_record_enclosing_while_loading_container_id');
229
+ if (while_loading_container_id) {
230
+ var while_loading_container = $('[data-reactive_record_while_loading_container_id='+while_loading_container_id+']');
231
+ var loading = (#{waiting_on_resources} == true);
232
+ if (loading) {
233
+ $(node).addClass('reactive_record_is_loading');
234
+ $(node).removeClass('reactive_record_is_loaded');
235
+ $(while_loading_container).addClass('reactive_record_is_loading');
236
+ $(while_loading_container).removeClass('reactive_record_is_loaded');
237
+
238
+ } else if (!$(node).hasClass('reactive_record_is_loaded')) {
239
+
240
+ if (!$(node).attr('data-reactive_record_while_loading_container_id')) {
241
+ $(node).removeClass('reactive_record_is_loading');
242
+ $(node).addClass('reactive_record_is_loaded');
243
+ }
244
+ if (!$(while_loading_container).hasClass('reactive_record_is_loaded')) {
245
+ var loading_children = $(while_loading_container).
246
+ find('[data-reactive_record_enclosing_while_loading_container_id='+while_loading_container_id+'].reactive_record_is_loading')
247
+ if (loading_children.length == 0) {
248
+ $(while_loading_container).removeClass('reactive_record_is_loading')
249
+ $(while_loading_container).addClass('reactive_record_is_loaded')
250
+ }
251
+ }
252
+
253
+ }
254
+
255
+ }
256
+ }
257
+
258
+ end
259
+
260
+ end if RUBY_ENGINE == 'opal'
261
+
262
+ end
@@ -0,0 +1,13 @@
1
+ #require 'rails'
2
+
3
+ module ReactiveRecord
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace ReactiveRecord
6
+ config.generators do |g|
7
+ g.test_framework :rspec, :fixture => false
8
+ g.fixture_replacement :factory_girl, :dir => 'spec/factories'
9
+ g.assets false
10
+ g.helper false
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,190 @@
1
+ module Browser
2
+
3
+ # Allows you to create an interval that executes the function every given
4
+ # seconds.
5
+ #
6
+ # @see https://developer.mozilla.org/en-US/docs/Web/API/Window.setInterval
7
+ class Interval
8
+ # @!attribute [r] every
9
+ # @return [Float] the seconds every which the block is called
10
+ attr_reader :every
11
+
12
+ # Create and start an interval.
13
+ #
14
+ # @param window [Window] the window to start the interval on
15
+ # @param time [Float] seconds every which to call the block
16
+ def initialize(window, time, &block)
17
+ @window = Native.convert(window)
18
+ @every = time
19
+ @block = block
20
+
21
+ @aborted = false
22
+ end
23
+
24
+ # Check if the interval has been stopped.
25
+ def stopped?
26
+ @id.nil?
27
+ end
28
+
29
+ # Check if the interval has been aborted.
30
+ def aborted?
31
+ @aborted
32
+ end
33
+
34
+ # Abort the interval, it won't be possible to start it again.
35
+ def abort
36
+ `#@window.clearInterval(#@id)`
37
+
38
+ @aborted = true
39
+ @id = nil
40
+ end
41
+
42
+ # Stop the interval, it will be possible to start it again.
43
+ def stop
44
+ return if stopped?
45
+
46
+ `#@window.clearInterval(#@id)`
47
+
48
+ @stopped = true
49
+ @id = nil
50
+ end
51
+
52
+ # Start the interval if it has been stopped.
53
+ def start
54
+ raise "the interval has been aborted" if aborted?
55
+ return unless stopped?
56
+
57
+ @id = `#@window.setInterval(#@block, #@every * 1000)`
58
+ end
59
+
60
+ # Call the [Interval] block.
61
+ def call
62
+ @block.call
63
+ end
64
+ end
65
+
66
+ class Window
67
+ # Execute the block every given seconds.
68
+ #
69
+ # @param time [Float] the seconds between every call
70
+ #
71
+ # @return [Interval] the object representing the interval
72
+ def every(time, &block)
73
+ Interval.new(@native, time, &block).tap(&:start)
74
+ end
75
+
76
+ # Execute the block every given seconds, you have to call [#start] on it
77
+ # yourself.
78
+ #
79
+ # @param time [Float] the seconds between every call
80
+ #
81
+ # @return [Interval] the object representing the interval
82
+ def every!(time, &block)
83
+ Interval.new(@native, time, &block)
84
+ end
85
+ end
86
+
87
+ end
88
+
89
+ module Kernel
90
+ # (see Browser::Window#every)
91
+ def every(time, &block)
92
+ $window.every(time, &block)
93
+ end
94
+
95
+ # (see Browser::Window#every!)
96
+ def every!(time, &block)
97
+ $window.every!(time, &block)
98
+ end
99
+ end
100
+
101
+ class Proc
102
+ # (see Browser::Window#every)
103
+ def every(time)
104
+ $window.every(time, &self)
105
+ end
106
+
107
+ # (see Browser::Window#every!)
108
+ def every!(time)
109
+ $window.every!(time, &self)
110
+ end
111
+ end
112
+
113
+ module Browser
114
+
115
+ # Allows you to delay the call to a function which gets called after the
116
+ # given time.
117
+ #
118
+ # @see https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout
119
+ class Delay
120
+ # @!attribute [r] after
121
+ # @return [Float] the seconds after which the block is called
122
+ attr_reader :after
123
+
124
+ # Create and start a timeout.
125
+ #
126
+ # @param window [Window] the window to start the timeout on
127
+ # @param time [Float] seconds after which the block is called
128
+ def initialize(window, time, &block)
129
+ @window = Native.convert(window)
130
+ @after = time
131
+ @block = block
132
+ end
133
+
134
+ # Abort the timeout.
135
+ def abort
136
+ `#@window.clearTimeout(#@id)`
137
+ end
138
+
139
+ # Start the delay.
140
+ def start
141
+ @id = `#@window.setTimeout(#{@block.to_n}, #@after * 1000)`
142
+ end
143
+ end
144
+
145
+ class Window
146
+ # Execute a block after the given seconds.
147
+ #
148
+ # @param time [Float] the seconds after it gets called
149
+ #
150
+ # @return [Delay] the object representing the timeout
151
+ def after(time, &block)
152
+ Delay.new(@native, time, &block).tap(&:start)
153
+ end
154
+
155
+ # Execute a block after the given seconds, you have to call [#start] on it
156
+ # yourself.
157
+ #
158
+ # @param time [Float] the seconds after it gets called
159
+ #
160
+ # @return [Delay] the object representing the timeout
161
+ def after!(time, &block)
162
+ Delay.new(@native, time, &block)
163
+ end
164
+ end
165
+
166
+ end
167
+
168
+ module Kernel
169
+ # (see Browser::Window#after)
170
+ def after(time, &block)
171
+ `setTimeout(#{block.to_n}, time * 1000)`
172
+ end
173
+
174
+ # (see Browser::Window#after!)
175
+ def after!(time, &block)
176
+ `setTimeout(#{block.to_n}, time * 1000)`
177
+ end
178
+ end
179
+
180
+ class Proc
181
+ # (see Browser::Window#after)
182
+ def after(time)
183
+ $window.after(time, &self)
184
+ end
185
+
186
+ # (see Browser::Window#after!)
187
+ def after!(time)
188
+ $window.after!(time, &self)
189
+ end
190
+ end