class Volt::EachBinding

Public Class Methods

new(volt_app, target, context, binding_name, getter, variable_name, index_name, template_name) click to toggle source
Calls superclass method Volt::BaseBinding::new
# File lib/volt/page/bindings/each_binding.rb, line 7
def initialize(volt_app, target, context, binding_name, getter, variable_name, index_name, template_name)
  super(volt_app, target, context, binding_name)

  @item_name     = variable_name
  @index_name    = index_name
  @template_name = template_name

  @templates = []

  @getter      = getter

  # Listen for changes
  @computation = lambda do
    begin
      value = @context.instance_eval(&@getter)
    rescue => e
      Volt.logger.error("EachBinding Error: #{e.inspect}")
      if RUBY_PLATFORM == 'opal'
        Volt.logger.error(`#{@getter}`)
      else
        Volt.logger.error(e.backtrace.join("\n"))
      end

      value = []
    end

    value
  end.watch_and_resolve!(
    method(:update),
    method(:getter_fail)
  )
end

Public Instance Methods

current_values(values) click to toggle source
# File lib/volt/page/bindings/each_binding.rb, line 161
def current_values(values)
  return [] if values.is_a?(Model) || values.is_a?(Exception)
  values = values.attributes if values.respond_to?(:attributes)

  values
end
item_added(position) click to toggle source
# File lib/volt/page/bindings/each_binding.rb, line 93
def item_added(position)
  item_context = nil

  binding_name     = @@binding_number
  @@binding_number += 1

  if position >= @templates.size
    # Setup new bindings in the spot we want to insert the item
    dom_section.insert_anchor_before_end(binding_name)
  else
    # Insert the item before an existing item
    dom_section.insert_anchor_before(binding_name, @templates[position].binding_name)
  end

  # TODORW: parent: @value may change
  item_context                           = SubContext.new({ _index_value: position, parent: @value }, @context)
  item_context.locals[@item_name.to_sym] = proc do
    # Fetch only whats there currently, no promises.
    Volt.run_in_mode(:no_model_promises) do
      # puts "GET AT: #{item_context.locals[:_index_value]}"
      @value[item_context.locals[:_index_value]]
    end
  end

  position_dependency                    = Dependency.new
  item_context.locals[:_index_dependency] = position_dependency

  # Get and set index
  item_context.locals[:_index=]           = proc do |val|
    position_dependency.changed!
    item_context.locals[:_index_value] = val
  end

  # Get and set value
  value_dependency                    = Dependency.new
  item_context.locals["_#{@item_name}_dependency".to_sym] = value_dependency

  item_context.locals["#{@item_name}=".to_sym] = proc do |val|
    value_dependency.changed!
    @value[item_context.locals[:_index_value]] = val
  end

  # If the user provides an each_with_index, we can assign the lookup for the index
  # variable here.
  if @index_name
    item_context.locals[@index_name.to_sym] = proc do
      position_dependency.depend
      item_context.locals[:_index_value]
    end
  end

  item_template = TemplateRenderer.new(@volt_app, @target, item_context, binding_name, @template_name)
  @templates.insert(position, item_template)

  update_indexes_after(position)
end
item_removed(position) click to toggle source
# File lib/volt/page/bindings/each_binding.rb, line 80
def item_removed(position)
  # Remove dependency
  @templates[position].context.locals[:_index_dependency].remove
  @templates[position].context.locals["_#{@item_name}_dependency".to_sym].remove

  @templates[position].remove_anchors
  @templates[position].remove
  @templates.delete_at(position)

  # Removed at the position, update context for every item after this position
  update_indexes_after(position)
end
remove() click to toggle source

When this each_binding is removed, cleanup.

Calls superclass method Volt::BaseBinding#remove
# File lib/volt/page/bindings/each_binding.rb, line 180
def remove
  @computation.stop
  @computation = nil

  # Clear value
  @value       = []

  @getter = nil

  remove_listeners

  if @templates
    template_count = @templates.size
    template_count.times do |index|
      item_removed(template_count - index - 1)
    end
    # @templates.compact.each(&:remove)
    @templates = nil
  end

  super
end
remove_listeners() click to toggle source
# File lib/volt/page/bindings/each_binding.rb, line 168
def remove_listeners
  if @added_listener
    @added_listener.remove
    @added_listener = nil
  end
  if @removed_listener
    @removed_listener.remove
    @removed_listener = nil
  end
end
update(value) click to toggle source

When a changed event happens, we update to the new size.

# File lib/volt/page/bindings/each_binding.rb, line 41
def update(value)
  # Since we're checking things like size, we don't want this to be re-triggered on a
  # size change, so we run without tracking.
  Computation.run_without_tracking do
    # Adjust to the new size
    values = current_values(value)

    @value = values

    remove_listeners

    if @value.respond_to?(:on)
      @added_listener   = @value.on('added') { |position| item_added(position) }
      @removed_listener = @value.on('removed') { |position| item_removed(position) }
    end

    templates_size = nil
    values_size = nil

    Volt.run_in_mode(:no_model_promises) do
      templates_size = @templates.size

      unless values.respond_to?(:size)
        fail InvalidObjectForEachBinding, "Each binding's require an object that responds to size and [] methods.  The binding received: #{values.inspect}"
      end

      values_size    = values.size
    end

    # Start over, re-create all nodes
    (templates_size - 1).downto(0) do |index|
      item_removed(index)
    end
    0.upto(values_size - 1) do |index|
      item_added(index)
    end
  end
end
update_indexes_after(start_index) click to toggle source

When items are added or removed in the middle of the list, we need to update each templates index value.

# File lib/volt/page/bindings/each_binding.rb, line 152
def update_indexes_after(start_index)
  size = @templates.size
  if size > 0
    start_index.upto(size - 1) do |index|
      @templates[index].context.locals[:_index=].call(index)
    end
  end
end