Skip to content
This repository was archived by the owner on Oct 19, 2018. It is now read-only.

RenderContext Refactor #199

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -4,6 +4,9 @@ rvm:
- 2.0.0
- 2.1
- jruby-19mode
branches:
only:
- master
before_script:
- phantomjs --version
env:
9 changes: 6 additions & 3 deletions lib/react/component.rb
Original file line number Diff line number Diff line change
@@ -117,9 +117,12 @@ def render

def _render_wrapper
State.set_state_context_to(self, true) do
element = React::RenderingContext.render(nil) { render || '' }
@waiting_on_resources =
element.waiting_on_resources if element.respond_to? :waiting_on_resources
element = nil
React::RenderingContext.create_context do
element = React::RenderingContext.render(nil) { render || '' }
@waiting_on_resources =
element.waiting_on_resources if element.respond_to? :waiting_on_resources
end
element
end
# rubocop:disable Lint/RescueException # we want to catch all exceptions regardless
239 changes: 152 additions & 87 deletions lib/react/rendering_context.rb
Original file line number Diff line number Diff line change
@@ -1,117 +1,182 @@
module React
class RenderingContext
class << self
attr_accessor :waiting_on_resources
attr_accessor :waiting_on_resources

def build_only(name, *args, &block)
React::Component.deprecation_warning(
'..._as_node is deprecated. Render component and then use the .node method instead'
)
React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
end
def build_only(name, *args, &block)
React::Component.deprecation_warning(
'..._as_node is deprecated. Render component and then use the .node method instead'
)
build { render(name, *args, &block) }.to_n
end

def render(name, *args, &block)
remove_nodes_from_args(args)
@buffer ||= [] unless @buffer
if block
element = build do
saved_waiting_on_resources = waiting_on_resources
self.waiting_on_resources = nil
run_child_block(name.nil?, &block)
if name
buffer = @buffer.dup
React.create_element(name, *args) { buffer }.tap do |element|
element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) }
end
elsif @buffer.last.is_a? React::Element
@buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
else
@buffer.last.to_s.span.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
def render(name, *args, &block)
remove_nodes_from_args(args)
@buffer ||= [] unless @buffer
if block
element = build do
saved_waiting_on_resources = waiting_on_resources
self.waiting_on_resources = nil
run_child_block(name.nil?, &block)
if name
buffer = @buffer.dup
React.create_element(name, *args) { buffer }.tap do |element|
element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) }
end
elsif @buffer.last.is_a? React::Element
@buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
else
@buffer.last.to_s.span.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
end
elsif name.is_a? React::Element
element = name
end
elsif name.is_a? React::Element
element = name
else
element = React.create_element(name, *args)
element.waiting_on_resources = waiting_on_resources
end
@buffer << element
self.waiting_on_resources = nil
element
end

def build
current = @buffer
@buffer = []
return_val = yield @buffer
@buffer = current
return_val
end

def as_node(element)
@buffer.delete(element)
element
end

alias delete as_node

def rendered?(element)
@buffer.include? element
end

def replace(e1, e2)
@buffer[@buffer.index(e1)] = e2
end

def remove_nodes_from_args(args)
args[0].each do |key, value|
value.as_node if value.is_a?(Element) rescue nil
end if args[0] && args[0].is_a?(Hash)
end

# run_child_block gathers the element(s) generated by a child block.
# for example when rendering this div: div { "hello".span; "goodby".span }
# two child Elements will be generated.
#
# the final value of the block should either be
# 1 an object that responds to :acts_as_string?
# 2 a string,
# 3 an element that is NOT yet pushed on the rendering buffer
# 4 or the last element pushed on the buffer
#
# in case 1 we change the object to a string, and then it becomes case 2
# in case 2 we automatically push the string onto the buffer
# in case 3 we also push the Element onto the buffer IF the buffer is empty
# case 4 requires no special processing
#
# Once we have taken care of these special cases we do a check IF we are in an
# outer rendering scope. In this case react only allows us to generate 1 Element
# so we insure that is the case, and also check to make sure that element in the buffer
# is the element returned

def run_child_block(is_outer_scope)
result = yield
result = result.to_s if result.try :acts_as_string?
@buffer << result if result.is_a?(String) || (result.is_a?(Element) && @buffer.empty?)
raise_render_error(result) if is_outer_scope && @buffer != [result]
end

def raise_render_error(result)
improper_render 'A different element was returned than was generated within the DSL.',
'Possibly improper use of Element#delete.' if @buffer.count == 1
improper_render "Instead #{@buffer.count} elements were generated.",
'Do you want to wrap your elements in a div?' if @buffer.count > 1
improper_render "Instead the component #{result} was returned.",
"Did you mean #{result}()?" if result.try :reactrb_component?
improper_render "Instead the #{result.class} #{result} was returned.",
'You may need to convert this to a string.'
end

def improper_render(message, solution)
raise "a component's render method must generate and return exactly 1 element or a string.\n"\
" #{message} #{solution}"
end

class << self
attr_accessor :current_context

def contexts
@contexts ||= []
end

def clear
@contexts = nil
@current_context = nil
end

def create_context(&block)
is_root = !self.current_context
if is_root
self.current_context = self.new
result = yield
self.current_context = nil
result
else
element = React.create_element(name, *args)
element.waiting_on_resources = waiting_on_resources
contexts << self.current_context
self.current_context = self.new
result = yield
self.current_context = contexts.pop
result
end
@buffer << element
self.waiting_on_resources = nil
element
rescue => e
contexts.clear
self.current_context = nil
raise e
end

def build
current = @buffer
@buffer = []
return_val = yield @buffer
@buffer = current
return_val
def build_only(name, *args, &block)
current_context.build_only(name, *args, &block)
end

def render(name, *args, &block)
current_context.render(name, *args, &block)
end

def build(&block)
create_context {
current_context.build(&block)
}
end

def as_node(element)
@buffer.delete(element)
element
current_context.as_node(element)
end

alias delete as_node

def rendered?(element)
@buffer.include? element
current_context.rendered?(element)
end

def replace(e1, e2)
@buffer[@buffer.index(e1)] = e2
current_context.replace(e1, e2)
end

def remove_nodes_from_args(args)
args[0].each do |key, value|
value.as_node if value.is_a?(Element) rescue nil
end if args[0] && args[0].is_a?(Hash)
current_context.remove_nodes_from_args(args)
end

# run_child_block gathers the element(s) generated by a child block.
# for example when rendering this div: div { "hello".span; "goodby".span }
# two child Elements will be generated.
#
# the final value of the block should either be
# 1 an object that responds to :acts_as_string?
# 2 a string,
# 3 an element that is NOT yet pushed on the rendering buffer
# 4 or the last element pushed on the buffer
#
# in case 1 we change the object to a string, and then it becomes case 2
# in case 2 we automatically push the string onto the buffer
# in case 3 we also push the Element onto the buffer IF the buffer is empty
# case 4 requires no special processing
#
# Once we have taken care of these special cases we do a check IF we are in an
# outer rendering scope. In this case react only allows us to generate 1 Element
# so we insure that is the case, and also check to make sure that element in the buffer
# is the element returned

def run_child_block(is_outer_scope)
result = yield
result = result.to_s if result.try :acts_as_string?
@buffer << result if result.is_a?(String) || (result.is_a?(Element) && @buffer.empty?)
raise_render_error(result) if is_outer_scope && @buffer != [result]
end

# heurestically raise a meaningful error based on the situation

def raise_render_error(result)
improper_render 'A different element was returned than was generated within the DSL.',
'Possibly improper use of Element#delete.' if @buffer.count == 1
improper_render "Instead #{@buffer.count} elements were generated.",
'Do you want to wrap your elements in a div?' if @buffer.count > 1
improper_render "Instead the component #{result} was returned.",
"Did you mean #{result}()?" if result.try :reactrb_component?
improper_render "Instead the #{result.class} #{result} was returned.",
'You may need to convert this to a string.'
end

def improper_render(message, solution)
raise "a component's render method must generate and return exactly 1 element or a string.\n"\
" #{message} #{solution}"
current_context.run_child_block(is_outer_scope)
end
end
end
2 changes: 1 addition & 1 deletion spec/react/dsl_spec.rb
Original file line number Diff line number Diff line change
@@ -168,7 +168,7 @@ def render
_undefined_method
end
end
expect { React.render_to_static_markup(React.create_element(Foo)) }
expect { React::Server.render_to_static_markup(React.create_element(Foo)) }
.to raise_error(NoMethodError)
end

3 changes: 3 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -76,6 +76,9 @@ def self.should_immediately_generate(opts={}, &block)
RSpec.configure do |config|
config.include React::SpecHelpers
config.filter_run_excluding :ruby
config.after :all do
React::RenderingContext.clear
end
if `(React.version.search(/^0\.13/) === -1)`
config.filter_run_excluding :v13_only
else