lib/reactive-ruby/isomorphic_helpers.rb
require "react/config"
module React
module IsomorphicHelpers
def self.included(base)
base.extend(ClassMethods)
end
if RUBY_ENGINE != 'opal'
def self.load_context(ctx, controller, name = nil)
puts "************************** React Server Context Initialized #{name} *********************************************"
@context = Context.new("#{controller.object_id}-#{Time.now.to_i}", ctx, controller, name)
end
else
def self.load_context(unique_id = nil, name = nil)
# can be called on the client to force re-initialization for testing purposes
if !unique_id || !@context || @context.unique_id != unique_id
if on_opal_server?
`console.history = []` rescue nil
message = "************************ React Prerendering Context Initialized #{name} ***********************"
else
message = "************************ React Browser Context Initialized ****************************"
end
log(message)
@context = Context.new(unique_id)
end
@context
end
end
def self.log(message, message_type = :info)
message = [message] unless message.is_a? Array
is_production = React::Config.config[:environment] == 'production'
if (message_type == :info || message_type == :warning) && is_production
return
end
if message_type == :info
if on_opal_server?
style = 'background: #00FFFF; color: red'
else
style = 'background: #222; color: #bada55'
end
message = ["%c" + message[0], style]+message[1..-1]
`console.log.apply(console, message)`
elsif message_type == :warning
`console.warn.apply(console, message)`
else
`console.error.apply(console, message)`
end
end
if RUBY_ENGINE != 'opal'
def self.on_opal_server?
false
end
def self.on_opal_client?
false
end
else
def self.on_opal_server?
`typeof Opal.global.document === 'undefined'`
end
def self.on_opal_client?
!on_opal_server?
end
end
def log(*args)
IsomorphicHelpers.log(*args)
end
def on_opal_server?
self.class.on_opal_server?
end
def on_opal_client?
self.class.on_opal_client?
end
def self.prerender_footers(controller = nil)
footer = Context.prerender_footer_blocks.collect { |block| block.call controller }.join("\n")
if RUBY_ENGINE != 'opal'
footer = (footer + @context.send_to_opal(:prerender_footers).to_s) if @context
footer = footer.html_safe
end
footer
end
class Context
attr_reader :controller
attr_reader :unique_id
def self.before_first_mount_blocks
@before_first_mount_blocks ||= []
end
def self.prerender_footer_blocks
@prerender_footer_blocks ||= []
end
def initialize(unique_id, ctx = nil, controller = nil, name = nil)
@unique_id = unique_id
if RUBY_ENGINE != 'opal'
@controller = controller
@ctx = ctx
ctx["ServerSideIsomorphicMethods"] = self
send_to_opal(:load_context, @unique_id, name)
end
Hyperloop::Application::Boot.run(context: self)
self.class.before_first_mount_blocks.each { |block| block.call(self) }
end
def eval(js)
@ctx.eval(js) if @ctx
end
def send_to_opal(method, *args)
return unless @ctx
args = [1] if args.length == 0
::ReactiveRuby::ComponentLoader.new(@ctx).load!
@ctx.eval("Opal.React.$const_get('IsomorphicHelpers').$#{method}(#{args.collect { |arg| "'#{arg}'"}.join(', ')})")
end
def self.register_before_first_mount_block(&block)
before_first_mount_blocks << block
end
def self.register_prerender_footer_block(&block)
prerender_footer_blocks << block
end
end
class IsomorphicProcCall
attr_reader :context
def result
@result.first if @result
end
def initialize(name, block, context, *args)
@name = name
@context = context
block.call(self, *args)
@result ||= send_to_server(*args) if IsomorphicHelpers.on_opal_server?
end
def when_on_client(&block)
@result = [block.call] if IsomorphicHelpers.on_opal_client?
end
def send_to_server(*args)
if IsomorphicHelpers.on_opal_server?
args_as_json = args.to_json
@result = [JSON.parse(`Opal.global.ServerSideIsomorphicMethods[#{@name}](#{args_as_json})`)]
end
end
def when_on_server(&block)
@result = [block.call.to_json] unless IsomorphicHelpers.on_opal_client? || IsomorphicHelpers.on_opal_server?
end
end
module ClassMethods
def on_opal_server?
IsomorphicHelpers.on_opal_server?
end
def on_opal_client?
IsomorphicHelpers.on_opal_client?
end
def log(*args)
IsomorphicHelpers.log(*args)
end
def controller
IsomorphicHelpers.context.controller
end
def before_first_mount(&block)
React::IsomorphicHelpers::Context.register_before_first_mount_block &block
end
def prerender_footer(&block)
React::IsomorphicHelpers::Context.register_prerender_footer_block &block
end
if RUBY_ENGINE != 'opal'
def isomorphic_method(name, &block)
React::IsomorphicHelpers::Context.send(:define_method, name) do |args_as_json|
React::IsomorphicHelpers::IsomorphicProcCall.new(name, block, self, *JSON.parse(args_as_json)).result
end
end
else
require 'json'
def isomorphic_method(name, &block)
self.class.send(:define_method, name) do | *args |
React::IsomorphicHelpers::IsomorphicProcCall.new(name, block, self, *args).result
end
end
end
end
end
end