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

#browser, #dom_section, #getter_fail, #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(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

Instance Method Details

#call_readyObject



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# 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_controllerObject



134
135
136
137
138
139
# 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) ⇒ Object

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



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
217
218
# 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_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.



121
122
123
124
125
126
127
128
129
130
131
132
# 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

#removeObject

Called when the binding is removed from the page



246
247
248
249
250
251
252
253
254
255
256
# 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_templateObject



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# 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_controllerObject



168
169
170
171
172
173
174
175
176
177
178
# 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) ⇒ Object

Called when the next template is ready to render



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# 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) ⇒ Object

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



221
222
223
224
225
# 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_loadObject



112
113
114
115
116
117
# 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 = {}) ⇒ 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
# 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