Class: Pakyow::Presenter::View

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Support::SafeStringHelpers
Defined in:
lib/pakyow/presenter/view.rb

Overview

Provides an interface for manipulating view templates.

Constant Summary collapse

INFO_MERGER =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Thanks Dan! stackoverflow.com/a/30225093

proc { |_, v1, v2| Support::IndifferentHash === v1 && Support::IndifferentHash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(html, info: {}, logical_path: nil) ⇒ View

Creates a view with html.


77
78
79
80
81
82
83
84
85
86
# File 'lib/pakyow/presenter/view.rb', line 77

def initialize(html, info: {}, logical_path: nil)
  @object = StringDoc.new(html)
  @info, @logical_path = Support::IndifferentHash.deep(info), logical_path

  if @object.respond_to?(:attributes)
    self.attributes = @object.attributes
  else
    @attributes = nil
  end
end

Instance Attribute Details

#logical_pathObject (readonly)

The logical path to the view template.


73
74
75
# File 'lib/pakyow/presenter/view.rb', line 73

def logical_path
  @logical_path
end

#objectObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The object responsible for transforming and rendering the underlying document or node.


69
70
71
# File 'lib/pakyow/presenter/view.rb', line 69

def object
  @object
end

Class Method Details

.from_object(object) ⇒ Object

Creates a view wrapping an object.


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/pakyow/presenter/view.rb', line 26

def from_object(object)
  instance = if object.is_a?(StringDoc::Node) && object.labeled?(:view_type)
    object.label(:view_type).allocate
  else
    allocate
  end

  instance.instance_variable_set(:@object, object)
  instance.instance_variable_set(:@info, {})
  instance.instance_variable_set(:@logical_path, nil)

  if object.respond_to?(:attributes)
    instance.attributes = object.attributes
  else
    instance.instance_variable_set(:@attributes, nil)
  end

  instance
end

.from_view_or_string(view_or_string) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


47
48
49
50
51
52
53
54
# File 'lib/pakyow/presenter/view.rb', line 47

def from_view_or_string(view_or_string)
  case view_or_string
  when View, VersionedView
    view_or_string
  else
    View.new(Support::SafeStringHelpers.ensure_html_safety(view_or_string.to_s))
  end
end

.load(path, content: nil) ⇒ Object

Creates a view from a file.


20
21
22
# File 'lib/pakyow/presenter/view.rb', line 20

def load(path, content: nil)
  new(content || File.read(path))
end

Instance Method Details

#==(other) ⇒ Object

Returns true if self equals other.


392
393
394
# File 'lib/pakyow/presenter/view.rb', line 392

def ==(other)
  other.is_a?(self.class) && @object == other.object
end

#add_info(*infos) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


559
560
561
562
563
564
565
# File 'lib/pakyow/presenter/view.rb', line 559

def add_info(*infos)
  tap do
    infos.each do |info|
      @info.merge!(Support::IndifferentHash.deep(info), &INFO_MERGER)
    end
  end
end

#after(view_or_string) ⇒ Object

Inserts a view or string after self.


322
323
324
325
326
# File 'lib/pakyow/presenter/view.rb', line 322

def after(view_or_string)
  tap do
    @object.after(self.class.from_view_or_string(view_or_string).object)
  end
end

#append(view_or_string) ⇒ Object

Appends a view or string to self.


306
307
308
309
310
# File 'lib/pakyow/presenter/view.rb', line 306

def append(view_or_string)
  tap do
    @object.append(self.class.from_view_or_string(view_or_string).object)
  end
end

#attributesObject Also known as: attrs

Returns attributes object for self.


398
399
400
# File 'lib/pakyow/presenter/view.rb', line 398

def attributes
  @attributes
end

#attributes=(attributes) ⇒ Object Also known as: attrs=

Wraps attributes in a Attributes instance.


405
406
407
# File 'lib/pakyow/presenter/view.rb', line 405

def attributes=(attributes)
  @attributes = Attributes.new(attributes)
end

#before(view_or_string) ⇒ Object

Inserts a view or string before self.


330
331
332
333
334
# File 'lib/pakyow/presenter/view.rb', line 330

def before(view_or_string)
  tap do
    @object.before(self.class.from_view_or_string(view_or_string).object)
  end
end

#bind(object) ⇒ Object

Binds a single object.


276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/pakyow/presenter/view.rb', line 276

def bind(object)
  tap do
    unless object.nil?
      each_binding_prop do |binding|
        binding_name = if binding.significant?(:multipart_binding)
          binding.label(:binding_prop)
        else
          binding.label(:binding)
        end

        if object.include?(binding_name)
          value = if object.is_a?(Binder)
            object.__content(binding_name, binding)
          else
            object[binding_name]
          end

          bind_value_to_node(value, binding)
          binding.set_label(:bound, true)
        end
      end

      attributes[:"data-id"] = object[:id]
      self.object.set_label(:bound, true)
    end
  end
end

#binding?Boolean

Returns true if self is a binding.

Returns:

  • (Boolean)

368
369
370
# File 'lib/pakyow/presenter/view.rb', line 368

def binding?
  @object.significant?(:binding)
end

#binding_nameObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


427
428
429
# File 'lib/pakyow/presenter/view.rb', line 427

def binding_name
  label(:binding)
end

#binding_prop?(node) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)

527
528
529
# File 'lib/pakyow/presenter/view.rb', line 527

def binding_prop?(node)
  node.significant?(:binding) && node.label(:version) != :empty && (!node.significant?(:binding_within) || node.significant?(:multipart_binding))
end

#binding_props(descend: false) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


517
518
519
# File 'lib/pakyow/presenter/view.rb', line 517

def binding_props(descend: false)
  each_binding_prop(descend: descend).map(&:itself)
end

#binding_scope?(node) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)

522
523
524
# File 'lib/pakyow/presenter/view.rb', line 522

def binding_scope?(node)
  node.significant?(:binding) && (node.significant?(:binding_within) || node.significant?(:multipart_binding) || node.label(:version) == :empty)
end

#binding_scopes(descend: false) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


512
513
514
# File 'lib/pakyow/presenter/view.rb', line 512

def binding_scopes(descend: false)
  each_binding_scope(descend: descend).map(&:itself)
end

#bodyObject

Returns a view for the <body> node.


221
222
223
224
225
226
227
# File 'lib/pakyow/presenter/view.rb', line 221

def body
  if body_node = @object.find_first_significant_node(:body)
    View.from_object(body_node)
  else
    nil
  end
end

#channeled_binding_nameObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


442
443
444
# File 'lib/pakyow/presenter/view.rb', line 442

def channeled_binding_name
  label(:channeled_binding)
end

#channeled_binding_scope?(scope) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)

568
569
570
571
572
573
574
# File 'lib/pakyow/presenter/view.rb', line 568

def channeled_binding_scope?(scope)
  binding_scopes.select { |node|
    node.label(:binding) == scope
  }.any? { |node|
    node.label(:channel).any?
  }
end

#clearObject

Removes self‘s children.


354
355
356
357
358
# File 'lib/pakyow/presenter/view.rb', line 354

def clear
  tap do
    @object.clear
  end
end

#component(name, renderable: false) ⇒ Object

Finds a component matching name.


179
180
181
182
183
184
185
186
# File 'lib/pakyow/presenter/view.rb', line 179

def component(name, renderable: false)
  name = name.to_sym
  components(renderable: renderable).find { |component|
    component.object.label(:components).any? { |possible_component|
      possible_component[:name] == name
    }
  }
end

#components(renderable: false) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns all components.


191
192
193
194
195
196
197
# File 'lib/pakyow/presenter/view.rb', line 191

def components(renderable: false)
  @object.each_significant_node_without_descending_into_type(:component, descend: true).select { |node|
    !renderable || node.label(:components).any? { |component| component[:renderable] }
  }.map { |node|
    View.from_object(node)
  }
end

#container?Boolean

Returns true if self is a container.

Returns:

  • (Boolean)

374
375
376
# File 'lib/pakyow/presenter/view.rb', line 374

def container?
  @object.significant?(:container)
end

#each_binding(name) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
# File 'lib/pakyow/presenter/view.rb', line 495

def each_binding(name)
  return enum_for(:each_binding, name) unless block_given?

  each_binding_scope do |node|
    if node.label(:channeled_binding) == name
      yield node
    end
  end

  each_binding_prop do |node|
    if (node.significant?(:multipart_binding) && node.label(:binding_prop) == name) || (!node.significant?(:multipart_binding) && node.label(:binding) == name)
      yield node
    end
  end
end

#each_binding_prop(descend: false) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/pakyow/presenter/view.rb', line 474

def each_binding_prop(descend: false)
  return enum_for(:each_binding_prop, descend: descend) unless block_given?

  if (@object.is_a?(StringDoc::Node) || @object.is_a?(StringDoc::MetaNode)) && @object.significant?(:multipart_binding)
    yield @object
  else
    method = if descend
      :each_significant_node
    else
      :each_significant_node_without_descending_into_type
    end

    @object.send(method, :binding, descend: descend) do |node|
      if binding_prop?(node)
        yield node
      end
    end
  end
end

#each_binding_scope(descend: false) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'lib/pakyow/presenter/view.rb', line 457

def each_binding_scope(descend: false)
  return enum_for(:each_binding_scope, descend: descend) unless block_given?

  method = if descend
    :each_significant_node
  else
    :each_significant_node_without_descending_into_type
  end

  @object.send(method, :binding, descend: descend) do |node|
    if binding_scope?(node)
      yield node
    end
  end
end

#find(*names) ⇒ Object

Finds a view binding by name. When passed more than one value, the view will be traversed through each name. Returns a Pakyow::Presenter::VersionedView.


122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/pakyow/presenter/view.rb', line 122

def find(*names)
  if names.any?
    named = names.shift.to_sym
    found = each_binding(named).map(&:itself)

    result = if names.empty? && !found.empty? # found everything; wrap it up
      if found[0].is_a?(StringDoc::MetaNode)
        VersionedView.new(View.from_object(found[0]))
      else
        VersionedView.new(View.from_object(StringDoc::MetaNode.new(found)))
      end
    elsif !found.empty? && names.count > 0 # descend further
      View.from_object(found[0]).find(*names)
    else
      nil
    end

    if result && block_given?
      yield result
    end

    result
  else
    nil
  end
end

#find_all(named) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Finds all view bindings by name, returning an array of Pakyow::Presenter::View objects.


152
153
154
155
156
# File 'lib/pakyow/presenter/view.rb', line 152

def find_all(named)
  each_binding(named).map { |node|
    View.from_object(node)
  }
end

#find_partials(partials, found = []) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


532
533
534
535
536
537
538
539
540
541
# File 'lib/pakyow/presenter/view.rb', line 532

def find_partials(partials, found = [])
  found.tap do
    @object.each_significant_node(:partial, descend: true) do |node|
      if replacement = partials[node.label(:partial)]
        found << node.label(:partial)
        replacement.find_partials(partials, found)
      end
    end
  end
end

#form(name) ⇒ Object

Finds a form with a binding matching name.


160
161
162
163
164
165
166
# File 'lib/pakyow/presenter/view.rb', line 160

def form(name)
  @object.each_significant_node(:form) do |form_node|
    return Views::Form.from_object(form_node) if form_node.label(:binding) == name
  end

  nil
end

#form?Boolean

Returns true if self is a form.

Returns:

  • (Boolean)

386
387
388
# File 'lib/pakyow/presenter/view.rb', line 386

def form?
  @object.significant?(:form)
end

#formsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns all forms.


171
172
173
174
175
# File 'lib/pakyow/presenter/view.rb', line 171

def forms
  @object.each_significant_node(:form, descend: true).map { |node|
    Views::Form.from_object(node)
  }
end

#headObject

Returns a view for the <head> node.


211
212
213
214
215
216
217
# File 'lib/pakyow/presenter/view.rb', line 211

def head
  if head_node = @object.find_first_significant_node(:head)
    View.from_object(head_node)
  else
    nil
  end
end

#html=(html) ⇒ Object

Safely sets the html value of self.


362
363
364
# File 'lib/pakyow/presenter/view.rb', line 362

def html=(html)
  @object.html = ensure_html_safety(html.to_s)
end

#info(key = nil) ⇒ Object

Returns all view info when key is nil, otherwise returns the value for key.


201
202
203
204
205
206
207
# File 'lib/pakyow/presenter/view.rb', line 201

def info(key = nil)
  if key.nil?
    @info
  else
    @info.fetch(key, nil)
  end
end

#initialize_copy(_) ⇒ Object


88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/pakyow/presenter/view.rb', line 88

def initialize_copy(_)
  super

  @info = @info.dup
  @object = @object.dup

  if @object.respond_to?(:attributes)
    self.attributes = @object.attributes
  else
    @attributes = nil
  end
end

#mixin(partials) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


544
545
546
547
548
549
550
551
552
# File 'lib/pakyow/presenter/view.rb', line 544

def mixin(partials)
  tap do
    @object.each_significant_node(:partial, descend: true) do |partial_node|
      if replacement = partials[partial_node.label(:partial)]
        partial_node.replace(replacement.mixin(partials).object)
      end
    end
  end
end

#partial?Boolean

Returns true if self is a partial.

Returns:

  • (Boolean)

380
381
382
# File 'lib/pakyow/presenter/view.rb', line 380

def partial?
  @object.significant?(:partial)
end

#plural_binding_nameObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


437
438
439
# File 'lib/pakyow/presenter/view.rb', line 437

def plural_binding_name
  label(:plural_binding)
end

#plural_channeled_binding_nameObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


447
448
449
# File 'lib/pakyow/presenter/view.rb', line 447

def plural_channeled_binding_name
  label(:plural_channeled_binding)
end

#prepend(view_or_string) ⇒ Object

Prepends a view or string to self.


314
315
316
317
318
# File 'lib/pakyow/presenter/view.rb', line 314

def prepend(view_or_string)
  tap do
    @object.prepend(self.class.from_view_or_string(view_or_string).object)
  end
end

#removeObject

Removes self.


346
347
348
349
350
# File 'lib/pakyow/presenter/view.rb', line 346

def remove
  tap do
    @object.remove
  end
end

#replace(view_or_string) ⇒ Object

Replaces self with a view or string.


338
339
340
341
342
# File 'lib/pakyow/presenter/view.rb', line 338

def replace(view_or_string)
  tap do
    @object.replace(self.class.from_view_or_string(view_or_string).object)
  end
end

#singular_binding_nameObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


432
433
434
# File 'lib/pakyow/presenter/view.rb', line 432

def singular_binding_name
  label(:singular_binding)
end

#singular_channeled_binding_nameObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


452
453
454
# File 'lib/pakyow/presenter/view.rb', line 452

def singular_channeled_binding_name
  label(:singular_channeled_binding)
end

#soft_copyObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/pakyow/presenter/view.rb', line 102

def soft_copy
  instance = self.class.allocate

  instance.instance_variable_set(:@info, @info.dup)

  new_object = @object.soft_copy
  instance.instance_variable_set(:@object, new_object)

  if new_object.respond_to?(:attributes)
    instance.attributes = new_object.attributes
  else
    instance.instance_variable_set(:@attributes, nil)
  end

  instance
end

#titleObject

Returns a view for the <title> node.


231
232
233
234
235
236
237
# File 'lib/pakyow/presenter/view.rb', line 231

def title
  if title_node = @object.find_first_significant_node(:title)
    View.from_object(title_node)
  else
    nil
  end
end

#to_htmlObject

Converts self to html, rendering the view.


418
419
420
# File 'lib/pakyow/presenter/view.rb', line 418

def to_html
  @object.to_html
end

#to_sObject


422
423
424
# File 'lib/pakyow/presenter/view.rb', line 422

def to_s
  @object.to_s
end

#transform(object) ⇒ Object

Transforms self to match structure of object.


249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/pakyow/presenter/view.rb', line 249

def transform(object)
  tap do
    if object.nil? || (object.respond_to?(:empty?) && object.empty?)
      remove
    else
      removals = []
      each_binding_prop(descend: false) do |binding|
        binding_name = if binding.significant?(:multipart_binding)
          binding.label(:binding_prop)
        else
          binding.label(:binding)
        end

        unless object.present?(binding_name)
          removals << binding
        end
      end

      removals.each(&:remove)
    end

    yield self, object if block_given?
  end
end

#versionObject

Returns the version name for self.


412
413
414
# File 'lib/pakyow/presenter/view.rb', line 412

def version
  (label(:version) || VersionedView::DEFAULT_VERSION).to_sym
end

#withObject

Yields self.


241
242
243
244
245
# File 'lib/pakyow/presenter/view.rb', line 241

def with
  tap do
    yield self
  end
end