class Volt::ViewBinding

Public Class Methods

new(volt_app, target, context, binding_name, binding_in_path, getter, content_template_path = nil) click to toggle source

@param [String] binding_in_path is the path this binding was rendered from. Used to

lookup paths in ViewLookupForPath

@param [String|nil] content_template_path is the path to the template for the content

provided in the tag.
Calls superclass method Volt::BaseBinding::new
# File lib/volt/page/bindings/view_binding.rb, line 13
def initialize(volt_app, target, context, binding_name, binding_in_path, getter, content_template_path = nil)
  super(volt_app, target, context, binding_name)

  @content_template_path = content_template_path

  # Setup the view lookup helper
  @view_lookup = Volt::ViewLookupForPath.new(volt_app.templates, binding_in_path)

  @current_template = nil

  # Run the initial render
  @computation      = lambda do
    # Don't try to render if this has been removed
    if @context
      # Render
      update(*@context.instance_eval(&getter))
    end
  end.watch!
end

Public Instance Methods

call_ready() click to toggle source
# File lib/volt/page/bindings/view_binding.rb, line 227
def call_ready
  if @controller
    # Set the current section on the controller if it wants so it can manipulate
    # the dom if needed.
    # Only assign sections for action's, so we don't get AttributeSections bound
    # also.
    if @controller.respond_to?(:section=)
      dom_section = @current_template.dom_section

      # Only assign dom sections that can be manipulated via the dom (so not the title for example)
      @controller.section = dom_section unless dom_section.is_a?(Volt::AttributeSection)
    end

    # Call the ready callback on the controller
    @current_controller_handler.call_action(nil, 'ready')
  end
end
clear_grouped_controller() click to toggle source
# File lib/volt/page/bindings/view_binding.rb, line 134
def clear_grouped_controller
  if @grouped_controller
    @grouped_controller.clear
    @grouped_controller = nil
  end
end
create_controller_handler(full_path, controller_path) click to toggle source

Create controller handler loads up a controller inside of the controller handler for the paths

# File lib/volt/page/bindings/view_binding.rb, line 181
def create_controller_handler(full_path, controller_path)
  # If arguments is nil, then an blank SubContext will be created
  args = [SubContext.new(@arguments, nil, true)]

  # get the controller class and action
  controller_class, action = ControllerHandler.get_controller_and_action(controller_path)

  generated_new = false
  new_controller = proc do
    # Mark that we needed to generate a new controller instance (not reused
    # from the group)
    generated_new = true
    # Setup the controller
    controller_class.new(@volt_app, *args)
  end

  # Fetch grouped controllers if we're grouping
  if @grouped_controller
    # Find the controller in the group, or create it
    controller = @grouped_controller.lookup_or_create(controller_class, &new_controller)
  else
    # Just create the controller
    controller = new_controller.call
  end

  handler = ControllerHandler.fetch(controller, action)

  if generated_new
    # Call the action
    stopped = handler.call_action

    controller.instance_variable_set('@chain_stopped', true) if stopped
  else
    stopped = controller.instance_variable_get('@chain_stopped')
  end

  [handler, generated_new, stopped]
end
queue_clear_grouped_controller() click to toggle source

On the next tick, we clear the grouped controller so that any changes to template paths will create a new controller and trigger the action.

# File lib/volt/page/bindings/view_binding.rb, line 121
def queue_clear_grouped_controller
  if Volt.in_browser?
    # In the browser, we want to keep a grouped controller around during a single run
    # of the event loop.  To make that happen, we clear it on the next tick.
    `setImmediate(function() {`
    clear_grouped_controller
    `});`
  else
    # For the backend, clear it immediately
    clear_grouped_controller
  end
end
remove() click to toggle source

Called when the binding is removed from the page

Calls superclass method Volt::BaseBinding#remove
# File lib/volt/page/bindings/view_binding.rb, line 246
def remove
  # Cleanup any starting controller
  remove_starting_controller

  @computation.stop
  @computation = nil

  remove_current_controller_and_template

  super
end
remove_current_controller_and_template() click to toggle source
# File lib/volt/page/bindings/view_binding.rb, line 141
def remove_current_controller_and_template
  # Remove existing controller and template and call _removed
  if @current_controller_handler
    @current_controller_handler.call_action('before', 'remove')
  end

  if @current_template
    @current_template.remove
    @current_template = nil
  end

  if @current_controller_handler
    @current_controller_handler.call_action('after', 'remove')
  end

  if @grouped_controller && @current_controller_handler
    # We remove the controller after all of the current rendering is done.
    Timers.next_tick do
      # Remove a reference for the controller in the group.
      @grouped_controller.remove(@current_controller_handler.controller.class)
    end
  end

  @controller = nil
  @current_controller_handler = nil
end
remove_starting_controller() click to toggle source
# File lib/volt/page/bindings/view_binding.rb, line 168
def remove_starting_controller
  # Clear any previously running wait for loads.  This is for when the path changes
  # before the view actually renders.
  stop_waiting_for_load

  if @starting_controller_handler
    # Only call the after_..._removed because the dom never loaded.
    @starting_controller_handler.call_action('after', 'removed')
    @starting_controller_handler = nil
  end
end
render_next_template(full_path, path) click to toggle source

Called when the next template is ready to render

# File lib/volt/page/bindings/view_binding.rb, line 96
def render_next_template(full_path, path)
  remove_current_controller_and_template

  # Switch the current template
  @current_controller_handler = @starting_controller_handler
  @starting_controller_handler = nil

  # Also track the current controller directly
  @controller = @current_controller_handler.controller if full_path

  render_template(full_path || path)
# rescue => e
#   Volt.logger.error("Error during render of template at #{path}: #{e.inspect}")
#   Volt.logger.error(e.backtrace)
end
render_template(full_path) click to toggle source

The context for templates can be either a controller, or the original context.

# File lib/volt/page/bindings/view_binding.rb, line 221
def render_template(full_path)
  @current_template = TemplateRenderer.new(@volt_app, @target, @controller, @binding_name, full_path)

  call_ready
end
stop_waiting_for_load() click to toggle source
# File lib/volt/page/bindings/view_binding.rb, line 112
def stop_waiting_for_load
  if @waiting_for_load
    @waiting_for_load.stop
    @waiting_for_load = nil
  end
end
update(path, section_or_arguments = nil, options = {}) click to toggle source

update is called when the path string changes.

# File lib/volt/page/bindings/view_binding.rb, line 34
def update(path, section_or_arguments = nil, options = {})
  Computation.run_without_tracking do
    @options = options

    # A blank path needs to load a missing template, otherwise it tries to load
    # the same template.
    path     = path.blank? ? '---missing---' : path

    section    = nil
    @arguments = nil

    if section_or_arguments.is_a?(String)
      # Render this as a section
      section = section_or_arguments
    else
      # Use the value passed in as the default arguments
      @arguments = section_or_arguments

      # Include content_template_path in attrs
      if @content_template_path
        @arguments ||= {}
        @arguments[:content_template_path] = @content_template_path
        @arguments[:content_controller] = @context
      end
    end

    # Sometimes we want multiple template bindings to share the same controller (usually
    # when displaying a :Title and a :Body), this instance tracks those.
    if @options && (controller_group = @options[:controller_group])
      # Setup the grouped controller for the first time.
      @grouped_controller = GroupedControllers.new(controller_group)
    end

    # If a controller is already starting, but not yet started, then remove it.
    remove_starting_controller

    full_path, controller_path = @view_lookup.path_for_template(path, section)

    if full_path
      @starting_controller_handler, generated_new, chain_stopped = create_controller_handler(full_path, controller_path)

      # Check if chain was stopped when the action ran
      if chain_stopped
        # An action stopped the chain.  When this happens, we stop running here.
        remove_starting_controller
      else
        # None of the actions stopped the chain
        # Wait until the controller is loaded before we actually render.
        @waiting_for_load = -> { @starting_controller_handler.controller.loaded? }.watch_until!(true) do
          render_next_template(full_path, path)
        end

        queue_clear_grouped_controller
      end
    else
      # if we don't have a full path, then we have a missing template
      render_next_template(full_path, path)
    end
  end
end