lib/hyperstack/internal/component/rendering_context.rb in hyper-component-1.0.alpha1.5 vs lib/hyperstack/internal/component/rendering_context.rb in hyper-component-1.0.alpha1.6
- old
+ new
@@ -17,31 +17,45 @@
def quiet_test(component)
return unless component.waiting_on_resources && raise_if_not_quiet? #&& component.class != RescueMetaWrapper <- WHY can't create a spec that this fails without this, but several fail with it.
raise NotQuiet.new("#{component} is waiting on resources")
end
+ def render_string(string)
+ @buffer ||= []
+ @buffer << string
+ end
+
def render(name, *args, &block)
was_outer_most = !@not_outer_most
@not_outer_most = true
remove_nodes_from_args(args)
- @buffer ||= [] unless @buffer
+ @buffer ||= [] #unless @buffer
if block
element = build do
saved_waiting_on_resources = nil #waiting_on_resources what was the purpose of this its used below to or in with the current elements waiting_for_resources
self.waiting_on_resources = nil
- run_child_block(name.nil?, &block)
+ run_child_block(&block)
if name
buffer = @buffer.dup
ReactWrapper.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) }
element.waiting_on_resources ||= waiting_on_resources if buffer.last.is_a?(String)
end
- elsif @buffer.last.is_a? Hyperstack::Component::Element
- @buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
else
- buffer_s = @buffer.last.to_s
- RenderingContext.render(:span) { buffer_s }.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
+ buffer = @buffer.collect do |item|
+ if item.is_a? Hyperstack::Component::Element
+ item.waiting_on_resources ||= saved_waiting_on_resources
+ item
+ else
+ RenderingContext.render(:span) { item.to_s }.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
+ end
+ end
+ if buffer.length > 1
+ buffer
+ else
+ buffer.first
+ end
end
end
elsif name.is_a? Hyperstack::Component::Element
element = name
else
@@ -63,10 +77,11 @@
return_val
end
def delete(element)
@buffer.delete(element)
+ @last_deleted = element
element
end
alias as_node delete
def rendered?(element)
@@ -84,68 +99,87 @@
rescue Exception
end
end if args[0] && args[0].is_a?(Hash)
end
- # run_child_block gathers the element(s) generated by a child block.
+ # run_child_block yields to the child rendering block which will put any
+ # elements to be rendered into the current rendering buffer.
+ #
# 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
+ # However the block itself will return a value, which in some cases should
+ # also added to the buffer:
#
- # in case 1 we render a span
- # 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
+ # If the final value of the block is a
#
- # 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
+ # a hyper model dummy value that is being loaded, then wrap it in a span and add it to the buffer
+ # a string (or if the buffer is empty any value), then add it to the buffer
+ # an Element, then add it on the buffer unless it has been just deleted
+ # #
+ # Note that the reason we don't always allow Strings to be automatically pushed is
+ # to avoid confusing results in situations like this:
+ # DIV { collection.each { |item| SPAN { item } } }
+ # If we accepted any object to be rendered this would generate:
+ # DIV { SPAN { collection[0] } SPAN { collection[n] } collection.to_s }
+ # which is probably not the desired output. If it was you would just append to_s
+ # to the end of the expression, to force it to be added to the output buffer.
+ #
+ # However if the buffer is empty then it makes sense to automatically apply the `.to_s`
+ # to the value, and push it on the buffer, unless it is a falsy value or an array
- def run_child_block(is_outer_scope)
+ def run_child_block
result = yield
- if result.respond_to?(:acts_as_string?) && result.acts_as_string?
- # hyper-mesh DummyValues respond to acts_as_string, and must
+ check_for_component_return(result)
+ if dummy_value?(result)
+ # hyper-mesh DummyValues must
# be converted to spans INSIDE the parent, otherwise the waiting_on_resources
# flag will get set in the wrong context
RenderingContext.render(:span) { result.to_s }
- elsif result.is_a?(String) || (result.is_a?(Hyperstack::Component::Element) && @buffer.empty?)
- @buffer << result
+ elsif result.is_a?(Hyperstack::Component::Element)
+ @buffer << result if @buffer.empty? unless @last_deleted == result
+ elsif pushable_string?(result)
+ @buffer << result.to_s
end
- raise_render_error(result) if is_outer_scope && @buffer != [result]
+ @last_deleted = nil
end
- # heurestically raise a meaningful error based on the situation
+ def check_for_component_return(result)
+ # check for a common error of saying (for example) DIV (without parens)
+ # which returns the DIV component class instead of a rendered DIV
+ return unless result.try :hyper_component?
- 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 :hyper_component?
- improper_render "Instead the #{result.class} #{result} was returned.",
- 'You may need to convert this to a string.'
+ Hyperstack::Component::IsomorphicHelpers.log(
+ "a component's render method returned the component class #{result}, did you mean to say #{result}()",
+ :warning
+ )
end
+ def dummy_value?(result)
+ result.respond_to?(:loading?) && result.loading?
+ end
+
+ def pushable_string?(result)
+ # if the buffer is not empty we will only push on strings, and ignore anything else
+ return result.is_a?(String) unless @buffer.empty?
+
+ # if the buffer IS empty then we can push on anything except we avoid nil, false and arrays
+ # as these are almost never what you want to render, and if you do there are mechanisms
+ # to render them explicitly
+ result && result.respond_to?(:to_n) && !result.is_a?(Array)
+ 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
end
end
end
end
end
class Object
- [:span, :td, :th].each do |tag|
+ %i[span td th].each do |tag|
define_method(tag) do |*args, &block|
args.unshift(tag)
# legacy hyperloop allowed tags to be lower case as well so if self is a component
# then this is just a DSL method for example:
# render(:div) do
@@ -153,27 +187,29 @@
# end
# in this case self is just the component being rendered, so span is just a method
# in the component.
# If we fully deprecate lowercase tags, then this next line can go...
return send(*args, &block) if respond_to?(:hyper_component?) && hyper_component?
+
Hyperstack::Internal::Component::RenderingContext.render(*args) { to_s }
end
end
-
def para(*args, &block)
args.unshift(:p)
# see above comment
return send(*args, &block) if respond_to?(:hyper_component?) && hyper_component?
+
Hyperstack::Internal::Component::RenderingContext.render(*args) { to_s }
end
def br
# see above comment
return send(:br) if respond_to?(:hyper_component?) && hyper_component?
- Hyperstack::Internal::Component::RenderingContext.render(:span) do
- Hyperstack::Internal::Component::RenderingContext.render(to_s)
- Hyperstack::Internal::Component::RenderingContext.render(:br)
+
+ Hyperstack::Internal::Component::RenderingContext.render(Hyperstack::Internal::Component::Tags::FRAGMENT) do
+ Hyperstack::Internal::Component::RenderingContext.render(Hyperstack::Internal::Component::Tags::FRAGMENT) { to_s }
+ Hyperstack::Internal::Component::RenderingContext.render(Hyperstack::Internal::Component::Tags::BR)
end
end
end