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