reactrb/reactrb

View on GitHub
lib/reactive-ruby/isomorphic_helpers.rb

Summary

Maintainability
A
2 hrs
Test Coverage
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