module ReactiveRecord
# will repeatedly execute the block until it is loaded
# immediately returns a promise that will resolve once the block is loaded
def self.load(&block)
promise = Promise.new
@load_stack ||= []
@load_stack << @loads_pending
@loads_pending = nil
result = block.call
if @loads_pending
@blocks_to_load ||= []
@blocks_to_load << [Base.last_fetch_at, promise, block]
else
promise.resolve result
end
@loads_pending = @load_stack.pop
promise
rescue Exception => e
debugger
React::IsomorphicHelpers.log "ReactiveRecord.load exception raised during initial load: #{e}", :error
end
def self.loads_pending!
@loads_pending = true
end
def self.check_loads_pending
if @loads_pending
if Base.pending_fetches.count > 0
true
else # this happens when for example loading foo.x results in somebody looking at foo.y while foo.y is still being loaded
ReactiveRecord::WhileLoading.loaded_at Base.last_fetch_at
ReactiveRecord::WhileLoading.quiet!
false
end
end
end
def self.run_blocks_to_load(fetch_id, failure = nil)
if @blocks_to_load
blocks_to_load_now = @blocks_to_load.select { |data| data.first == fetch_id }
@blocks_to_load = @blocks_to_load.reject { |data| data.first == fetch_id }
@load_stack ||= []
blocks_to_load_now.each do |data|
id, promise, block = data
@load_stack << @loads_pending
@loads_pending = nil
result = block.call(failure)
if check_loads_pending and !failure
@blocks_to_load << [Base.last_fetch_at, promise, block]
else
promise.resolve result
end
@loads_pending = @load_stack.pop
end
end
rescue Exception => e
React::IsomorphicHelpers.log "ReactiveRecord.load exception raised during retry: #{e}", :error
end
# Adds while_loading feature to React
# to use attach a .while_loading handler to any element for example
# div { "displayed if everything is loaded" }.while_loading { "displayed while I'm loading" }
# the contents of the div will be switched (using jQuery.show/hide) depending on the state of contents of the first block
# To notify React that something is loading use React::WhileLoading.loading!
# once everything is loaded then do React::WhileLoading.loaded_at message (typically a time stamp just for debug purposes)
class WhileLoading
include React::IsomorphicHelpers
before_first_mount do
@css_to_preload = ""
@while_loading_counter = 0
end
def get_next_while_loading_counter
@while_loading_counter += 1
end
def preload_css(css)
@css_to_preload << css << "\n"
end
def self.has_observers?
React::State.has_observers?(self, :loaded_at)
end
prerender_footer do
"".tap { @css_to_preload = ""}
end
if RUBY_ENGINE == 'opal'
# I DONT THINK WE USE opal-jquery in this module anymore - require 'opal-jquery' if opal_client?
include React::Component
param :loading
param :loaded_children
param :loading_children
param :element_type
param :element_props
param :display, default: ""
class << self
def loading?
@is_loading
end
def loading!
React::RenderingContext.waiting_on_resources = true
React::State.get_state(self, :loaded_at)
React::State.set_state(self, :quiet, false)
@is_loading = true
end
def loaded_at(loaded_at)
React::State.set_state(self, :loaded_at, loaded_at)
@is_loading = false
end
def quiet?
React::State.get_state(self, :quiet)
end
def page_loaded?
React::State.get_state(self, :page_loaded)
end
def quiet!
React::State.set_state(self, :quiet, true)
after(1) { React::State.set_state(self, :page_loaded, true) } unless on_opal_server? or @page_loaded
@page_loaded = true
end
def add_style_sheet
@style_sheet ||= %x{
$('').appendTo("head")
}
end
end
before_mount do
@uniq_id = WhileLoading.get_next_while_loading_counter
WhileLoading.preload_css(
".reactive_record_while_loading_container_#{@uniq_id} > :nth-child(1n+#{loaded_children.count+1}) {\n"+
" display: none;\n"+
"}\n"
)
end
after_mount do
@waiting_on_resources = loading
WhileLoading.add_style_sheet
%x{
var node = #{dom_node};
$(node).children(':nth-child(-1n+'+#{loaded_children.count}+')').addClass('reactive_record_show_when_loaded');
$(node).children(':nth-child(1n+'+#{loaded_children.count+1}+')').addClass('reactive_record_show_while_loading');
}
end
after_update do
@waiting_on_resources = loading
end
def render
props = element_props.dup
classes = [props[:class], props[:className], "reactive_record_while_loading_container_#{@uniq_id}"].compact.join(" ")
props.merge!({
"data-reactive_record_while_loading_container_id" => @uniq_id,
"data-reactive_record_enclosing_while_loading_container_id" => @uniq_id,
class: classes
})
React.create_element(element_type, props) { loaded_children + loading_children }
end
end
end
end
module React
class Element
def while_loading(display = "", &loading_display_block)
loaded_children = []
loaded_children = block.call.dup if block
loading_children = [display]
loading_children = RenderingContext.build do |buffer|
result = loading_display_block.call
buffer << result.to_s if result.is_a? String
buffer.dup
end if loading_display_block
RenderingContext.replace(
self,
React.create_element(
ReactiveRecord::WhileLoading,
loading: waiting_on_resources,
loading_children: loading_children,
loaded_children: loaded_children,
element_type: type,
element_props: properties)
)
end
def hide_while_loading
while_loading
end
end
module Component
alias_method :original_component_did_mount, :component_did_mount
def component_did_mount(*args)
original_component_did_mount(*args)
reactive_record_link_to_enclosing_while_loading_container
reactive_record_link_set_while_loading_container_class
end
alias_method :original_component_did_update, :component_did_update
def component_did_update(*args)
original_component_did_update(*args)
reactive_record_link_set_while_loading_container_class
end
def reactive_record_link_to_enclosing_while_loading_container
# Call after any component mounts - attaches the containers loading id to this component
# Fyi, the while_loading container is responsible for setting its own link to itself
%x{
var node = #{dom_node};
if (!$(node).is('[data-reactive_record_enclosing_while_loading_container_id]')) {
var while_loading_container = $(node).closest('[data-reactive_record_while_loading_container_id]')
if (while_loading_container.length > 0) {
var container_id = $(while_loading_container).attr('data-reactive_record_while_loading_container_id')
$(node).attr('data-reactive_record_enclosing_while_loading_container_id', container_id)
}
}
}
end
def reactive_record_link_set_while_loading_container_class
%x{
var node = #{dom_node};
var while_loading_container_id = $(node).attr('data-reactive_record_enclosing_while_loading_container_id');
if (while_loading_container_id) {
var while_loading_container = $('[data-reactive_record_while_loading_container_id='+while_loading_container_id+']');
var loading = (#{waiting_on_resources} == true);
if (loading) {
$(node).addClass('reactive_record_is_loading');
$(node).removeClass('reactive_record_is_loaded');
$(while_loading_container).addClass('reactive_record_is_loading');
$(while_loading_container).removeClass('reactive_record_is_loaded');
} else if (!$(node).hasClass('reactive_record_is_loaded')) {
if (!$(node).attr('data-reactive_record_while_loading_container_id')) {
$(node).removeClass('reactive_record_is_loading');
$(node).addClass('reactive_record_is_loaded');
}
if (!$(while_loading_container).hasClass('reactive_record_is_loaded')) {
var loading_children = $(while_loading_container).
find('[data-reactive_record_enclosing_while_loading_container_id='+while_loading_container_id+'].reactive_record_is_loading')
if (loading_children.length == 0) {
$(while_loading_container).removeClass('reactive_record_is_loading')
$(while_loading_container).addClass('reactive_record_is_loaded')
}
}
}
}
}
end
end if RUBY_ENGINE == 'opal'
end