reactive-record 0.7.0
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +29 -0
- data/app/controllers/reactive_record/application_controller.rb +4 -0
- data/app/controllers/reactive_record/reactive_record_controller.rb +22 -0
- data/config/routes.rb +5 -0
- data/lib/Gemfile +17 -0
- data/lib/reactive-record.rb +28 -0
- data/lib/reactive_record/active_record/aggregations.rb +38 -0
- data/lib/reactive_record/active_record/associations.rb +54 -0
- data/lib/reactive_record/active_record/base.rb +9 -0
- data/lib/reactive_record/active_record/class_methods.rb +113 -0
- data/lib/reactive_record/active_record/instance_methods.rb +76 -0
- data/lib/reactive_record/active_record/reactive_record/base.rb +287 -0
- data/lib/reactive_record/active_record/reactive_record/collection.rb +100 -0
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +274 -0
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +262 -0
- data/lib/reactive_record/engine.rb +13 -0
- data/lib/reactive_record/interval.rb +190 -0
- data/lib/reactive_record/serializers.rb +7 -0
- data/lib/reactive_record/server_data_cache.rb +250 -0
- data/lib/reactive_record/version.rb +3 -0
- metadata +191 -0
@@ -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
|