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, #volt_app

Instance Method Summary collapse

Methods inherited from BaseBinding

#dom_section, #getter_fail, #page, #remove_anchors

Constructor Details

#initialize(volt_app, 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.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 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(page, 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

Instance Method Details

#call_readyObject



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/volt/page/bindings/view_binding.rb', line 225

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



135
136
137
138
139
140
# File 'lib/volt/page/bindings/view_binding.rb', line 135

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



179
180
181
182
183
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
# File 'lib/volt/page/bindings/view_binding.rb', line 179

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.new(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_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.



122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/volt/page/bindings/view_binding.rb', line 122

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



244
245
246
247
248
249
250
251
252
253
254
# File 'lib/volt/page/bindings/view_binding.rb', line 244

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



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/volt/page/bindings/view_binding.rb', line 142

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



166
167
168
169
170
171
172
173
174
175
176
# File 'lib/volt/page/bindings/view_binding.rb', line 166

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



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/volt/page/bindings/view_binding.rb', line 97

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

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



219
220
221
222
223
# File 'lib/volt/page/bindings/view_binding.rb', line 219

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

  call_ready
end

#stop_waiting_for_loadObject



113
114
115
116
117
118
# File 'lib/volt/page/bindings/view_binding.rb', line 113

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.



34
35
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
# 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)

    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