Class: Volt::ViewBinding

Inherits:
BaseBinding show all
Defined in:
lib/volt/page/bindings/view_binding.rb

Direct Known Subclasses

ComponentBinding

Instance Attribute Summary

Attributes inherited from BaseBinding

#binding_name, #context, #target

Instance Method Summary collapse

Methods inherited from BaseBinding

#dom_section, #remove_anchors

Constructor Details

#initialize(page, target, context, binding_name, binding_in_path, getter, content_template_path = nil) ⇒ ViewBinding

Returns a new instance of ViewBinding.

Parameters:

  • binding_in_path (String)

    is the path this binding was rendered from. Used to lookup paths in ViewLookupForPath

  • content_template_path (String|nil) (defaults to: nil)

    is the path to the template for the content provided in the tag.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/volt/page/bindings/view_binding.rb', line 15

def initialize(page, target, context, binding_name, binding_in_path, getter, content_template_path=nil)
  super(page, target, context, binding_name)

  @content_template_path = content_template_path

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

  @current_template = nil

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

Instance Method Details

#call_readyObject



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/volt/page/bindings/view_binding.rb', line 232

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_controllerObject



140
141
142
143
144
145
# File 'lib/volt/page/bindings/view_binding.rb', line 140

def clear_grouped_controller
  if @grouped_controller
    @grouped_controller.clear
    @grouped_controller = nil
  end
end

#create_controller_handler(full_path, controller_path) ⇒ Object

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



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/volt/page/bindings/view_binding.rb', line 184

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.new 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(*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.new(controller, action)

  if generated_new
    # Call the action
    stopped = handler.call_action

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

  return handler, generated_new, stopped
end

#queue_clear_grouped_controllerObject

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.



127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/volt/page/bindings/view_binding.rb', line 127

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

#removeObject

Called when the binding is removed from the page



251
252
253
254
255
256
257
258
259
260
261
# File 'lib/volt/page/bindings/view_binding.rb', line 251

def remove
  # Cleanup any starting controller
  remove_starting_controller

  @computation.stop
  @computation = nil

  remove_current_controller_and_template

  super
end

#remove_current_controller_and_templateObject



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/volt/page/bindings/view_binding.rb', line 147

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
    # Remove a reference for the controller in the group.
    @grouped_controller.remove(@current_controller_handler.controller.class)
  end

  @controller = nil
  @current_controller_handler = nil
end

#remove_starting_controllerObject



171
172
173
174
175
176
177
178
179
180
181
# File 'lib/volt/page/bindings/view_binding.rb', line 171

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) ⇒ Object

Called when the next template is ready to render



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/volt/page/bindings/view_binding.rb', line 99

def render_next_template(full_path, path)
  begin
    # stop_waiting_for_load
    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
end

#render_template(full_path, path) ⇒ Object

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



226
227
228
229
230
# File 'lib/volt/page/bindings/view_binding.rb', line 226

def render_template(full_path, path)
  @current_template = TemplateRenderer.new(@page, @target, @controller, @binding_name, full_path, path)

  call_ready
end

#stop_waiting_for_loadObject



118
119
120
121
122
123
# File 'lib/volt/page/bindings/view_binding.rb', line 118

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 = {}) ⇒ Object

update is called when the path string changes.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/volt/page/bindings/view_binding.rb', line 36

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)

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

    @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
  end
end