pakyow-presenter 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,6 @@ module Pakyow
3
3
  class ViewCollection
4
4
  include Enumerable
5
5
 
6
- attr_accessor :context, :composer
7
-
8
6
  def initialize
9
7
  @views = []
10
8
  end
@@ -13,70 +11,60 @@ module Pakyow
13
11
  @views.each { |v| yield(v) }
14
12
  end
15
13
 
16
- def attributes(attrs = {})
17
- collection = AttributesCollection.new
18
- self.each{|v| collection << v.attributes(attrs)}
19
- return collection
14
+ def attrs(attrs = {})
15
+ inject(AttributesCollection.new) { |coll, view|
16
+ coll << view.attrs(attrs)
17
+ }
20
18
  end
21
19
 
22
- alias :attrs :attributes
23
-
24
20
  def remove
25
- self.each {|e| e.remove}
21
+ each {|e| e.remove}
26
22
  end
27
23
 
28
- alias :delete :remove
29
-
30
- # SEE COMMENT IN VIEW
31
- # def add_class(val)
32
- # self.each {|e| e.add_class(val)}
33
- # end
34
-
35
- # SEE COMMENT IN VIEW
36
- # def remove_class(val)
37
- # self.each {|e| e.remove_class(val)}
38
- # end
39
-
40
24
  def clear
41
- self.each {|e| e.clear}
25
+ each {|e| e.clear}
42
26
  end
43
27
 
44
28
  def text
45
- self.map { |v| v.text }
29
+ map { |v| v.text }
46
30
  end
47
31
 
48
32
  def text=(text)
49
- self.each {|e| e.text = text}
33
+ each {|e| e.text = text}
50
34
  end
51
35
 
52
36
  def html
53
- self.map { |v| v.html }
37
+ map { |v| v.html }
54
38
  end
55
39
 
56
40
  def html=(html)
57
- self.each {|e| e.html = html}
41
+ each {|e| e.html = html}
58
42
  end
59
43
 
60
44
  def to_html
61
- self.map { |v| v.to_html }.join('')
45
+ map { |v| v.to_html }.join('')
62
46
  end
63
47
 
64
48
  alias :to_s :to_html
65
49
 
66
50
  def append(content)
67
- self.each {|e| e.append(content)}
51
+ each do |view|
52
+ view.append(content)
53
+ end
68
54
  end
69
55
 
70
- alias :render :append
71
-
72
56
  def prepend(content)
73
- self.each {|e| e.prepend(content)}
57
+ each do |view|
58
+ view.prepend(content)
59
+ end
74
60
  end
75
61
 
76
62
  def <<(val)
77
63
  if val.is_a? View
78
64
  @views << val
79
65
  end
66
+
67
+ self
80
68
  end
81
69
 
82
70
  def concat(views)
@@ -84,12 +72,7 @@ module Pakyow
84
72
  end
85
73
 
86
74
  def [](i)
87
- view = @views[i]
88
- return if view.nil?
89
-
90
- view.context = @context
91
- view.composer = @composer
92
- return view
75
+ @views[i]
93
76
  end
94
77
 
95
78
  def length
@@ -97,35 +80,25 @@ module Pakyow
97
80
  end
98
81
 
99
82
  def scope(name)
100
- views = ViewCollection.new
101
- views.context = @context
102
- views.composer = @composer
103
- self.each{|v|
104
- next unless svs = v.scope(name)
105
- svs.each{ |sv|
106
- sv.context = @context
107
- sv.composer = @composer
108
- views << sv
83
+ inject(ViewCollection.new) { |coll, view|
84
+ scopes = view.scope(name)
85
+ next if scopes.nil?
86
+
87
+ scopes.inject(coll) { |coll, scoped_view|
88
+ coll << scoped_view
109
89
  }
110
90
  }
111
-
112
- views
113
91
  end
114
92
 
115
93
  def prop(name)
116
- views = ViewCollection.new
117
- views.context = @context
118
- views.composer = @composer
119
- self.each{|v|
120
- next unless svs = v.prop(name)
121
- svs.each{ |sv|
122
- sv.context = @context
123
- sv.composer = @composer
124
- views << sv
94
+ inject(ViewCollection.new) { |coll, view|
95
+ scopes = view.prop(name)
96
+ next if scopes.nil?
97
+
98
+ scopes.inject(coll) { |coll, scoped_view|
99
+ coll << scoped_view
125
100
  }
126
101
  }
127
-
128
- views
129
102
  end
130
103
 
131
104
  # call-seq:
@@ -135,7 +108,7 @@ module Pakyow
135
108
  #
136
109
  def with(&block)
137
110
  if block.arity == 0
138
- self.instance_exec(&block)
111
+ instance_exec(&block)
139
112
  else
140
113
  yield(self)
141
114
  end
@@ -153,18 +126,17 @@ module Pakyow
153
126
  # (this is basically Bret's `map` function)
154
127
  #
155
128
  def for(data, &block)
156
- data = data.to_a if data.is_a?(Enumerator)
157
- data = [data] if (!data.is_a?(Enumerable) || data.is_a?(Hash))
158
-
159
- self.each_with_index { |v,i|
160
- break unless datum = data[i]
129
+ data = Array.ensure(data)
130
+ each_with_index do |view, i|
131
+ datum = data[i]
132
+ break if datum.nil?
161
133
 
162
134
  if block.arity == 1
163
- v.instance_exec(data[i], &block)
135
+ view.instance_exec(data[i], &block)
164
136
  else
165
- block.call(v, data[i])
137
+ block.call(view, data[i])
166
138
  end
167
- }
139
+ end
168
140
  end
169
141
 
170
142
  # call-seq:
@@ -190,46 +162,32 @@ module Pakyow
190
162
  # call-seq:
191
163
  # match(data) => ViewCollection
192
164
  #
193
- # Returns a ViewCollection object that has been manipulated to match the data.
194
- # For the ViewCollection case, the returned ViewCollection collection will consist n copies
195
- # of self[data index] || self[-1], where n = data.length.
165
+ # Manipulates the current collection to match the data. The final ViewCollection object
166
+ # will consist n copies of self[data index] || self[-1], where n = data.length.
196
167
  #
197
168
  def match(data)
198
- data = data.to_a if data.is_a?(Enumerator)
199
- data = [data] if (!data.is_a?(Enumerable) || data.is_a?(Hash))
169
+ data = Array.ensure(data)
200
170
 
201
- views = ViewCollection.new
202
- views.context = @context
203
- views.composer = @composer
204
- data.each_with_index {|datum,i|
205
- unless v = self[i]
171
+ # an empty set always means an empty view
172
+ if data.empty?
173
+ remove
174
+ else
175
+ original_view = self[-1].soft_copy if data.length > length
206
176
 
207
- # we're out of views, so use the last one
208
- v = self[-1]
177
+ if length > data.length
178
+ self[data.length..-1].each do |view|
179
+ view.remove
180
+ end
181
+ else
182
+ data[length..-1].each do
183
+ duped_view = original_view.soft_copy
184
+ self[-1].after(duped_view)
185
+ self << duped_view
186
+ end
209
187
  end
188
+ end
210
189
 
211
- d_v = v.doc.dup
212
- v.doc.before(d_v)
213
- v.invalidate!(true)
214
-
215
- new_v = View.from_doc(d_v)
216
-
217
- # find binding subset (keeps us from refinding)
218
- new_v.bindings = v.bindings.dup
219
- new_v.scoped_as = v.scoped_as
220
- new_v.context = @context
221
- new_v.composer = @composer
222
-
223
- views << new_v
224
- }
225
-
226
- # do not use self.remove since that refinds bindings
227
- self.each {|v|
228
- next if v.doc.parent.nil?
229
- v.doc.remove
230
- }
231
-
232
- views
190
+ self
233
191
  end
234
192
 
235
193
  # call-seq:
@@ -238,7 +196,7 @@ module Pakyow
238
196
  # Matches self to data and yields a view/datum pair.
239
197
  #
240
198
  def repeat(data, &block)
241
- self.match(data).for(data, &block)
199
+ match(data).for(data, &block)
242
200
  end
243
201
 
244
202
  # call-seq:
@@ -247,7 +205,7 @@ module Pakyow
247
205
  # Matches self with data and yields a view/datum pair with index.
248
206
  #
249
207
  def repeat_with_index(data, &block)
250
- self.match(data).for_with_index(data, &block)
208
+ match(data).for_with_index(data, &block)
251
209
  end
252
210
 
253
211
  # call-seq:
@@ -255,9 +213,9 @@ module Pakyow
255
213
  #
256
214
  # Binds data across existing scopes.
257
215
  #
258
- def bind(data, bindings = {}, &block)
259
- self.for(data) {|view, datum|
260
- view.bind(datum, bindings)
216
+ def bind(data, bindings: {}, context: nil, &block)
217
+ self.for(data) do |view, datum|
218
+ view.bind(datum, bindings: bindings, context: context)
261
219
  next if block.nil?
262
220
 
263
221
  if block.arity == 1
@@ -265,7 +223,7 @@ module Pakyow
265
223
  else
266
224
  block.call(view, datum)
267
225
  end
268
- }
226
+ end
269
227
  end
270
228
 
271
229
  # call-seq:
@@ -273,9 +231,9 @@ module Pakyow
273
231
  #
274
232
  # Binds data across existing scopes, yielding a view/datum pair with index.
275
233
  #
276
- def bind_with_index(data, bindings = {}, &block)
234
+ def bind_with_index(*a, **k, &block)
277
235
  i = 0
278
- self.bind(data) do |ctx, datum|
236
+ bind(*a, **k) do |ctx, datum|
279
237
  if block.arity == 2
280
238
  ctx.instance_exec(datum, i, &block)
281
239
  else
@@ -291,8 +249,8 @@ module Pakyow
291
249
  #
292
250
  # Matches self to data then binds data to the view.
293
251
  #
294
- def apply(data, bindings = {}, &block)
295
- self.match(data).bind(data, bindings, &block)
252
+ def apply(data, bindings: {}, context: nil, &block)
253
+ match(data).bind(data, bindings: bindings, context: context, &block)
296
254
  end
297
255
  end
298
256
  end
@@ -12,7 +12,6 @@ module Pakyow
12
12
  def_delegators :template, :title, :title=
13
13
  def_delegators :parts, :scope, :prop
14
14
 
15
- attr_accessor :context
16
15
  attr_reader :store, :path, :page, :partials
17
16
 
18
17
  def initialize(store, path = nil, opts = {}, &block)
@@ -35,10 +34,6 @@ module Pakyow
35
34
  @partials = store.partials(path) unless path.nil?
36
35
  end
37
36
 
38
- @partials.each do |name, partial|
39
- partial.composer = self
40
- end
41
-
42
37
  instance_exec(&block) if block_given?
43
38
  end
44
39
 
@@ -47,6 +42,7 @@ module Pakyow
47
42
 
48
43
  %w[store path page template partials view].each do |ivar|
49
44
  value = original.instance_variable_get("@#{ivar}")
45
+ next if value.nil?
50
46
 
51
47
  if value.is_a?(Hash)
52
48
  dup_value = {}
@@ -57,34 +53,15 @@ module Pakyow
57
53
 
58
54
  self.instance_variable_set("@#{ivar}", dup_value)
59
55
  end
60
-
61
- # update composer reference for partials
62
- @partials.each do |name, partial|
63
- partial.composer = self
64
- end
65
-
66
- # update composer reference for page
67
- @page.composer = self
68
- end
69
-
70
- def precompose!
71
- @view = build_view
72
- clean!
73
56
  end
74
57
 
75
58
  def view
76
- if dirty?
77
- @view = build_view
78
- clean!
79
- end
80
-
81
- return @view
59
+ build_view
82
60
  end
83
61
  alias_method :composed, :view
84
62
 
85
63
  def template(template = nil)
86
64
  if template.nil?
87
- @template.context = context
88
65
  return @template
89
66
  end
90
67
 
@@ -99,7 +76,6 @@ module Pakyow
99
76
  end
100
77
 
101
78
  @template = template
102
- dirty!
103
79
 
104
80
  return self
105
81
  end
@@ -110,48 +86,31 @@ module Pakyow
110
86
  page = @store.page(page)
111
87
  end
112
88
 
113
- page.composer = self
114
89
  @page = page
115
- dirty!
116
90
 
117
91
  return self
118
92
  end
119
93
 
120
94
  def includes(partial_map)
121
- dirty!
122
-
123
95
  @partials.merge!(remap_partials(partial_map))
124
96
  end
125
97
 
126
98
  def partials=(partial_map)
127
- dirty!
128
99
  @partials.merge!(remap_partials(partial_map))
129
100
  end
130
101
 
131
102
  def partial(name)
132
103
  partial = @partials[name]
133
- partial.context = context
134
104
  return partial
135
105
  end
136
106
 
137
107
  def container(name)
138
108
  container = @page.container(name)
139
- container.context = context
140
109
  return container
141
110
  end
142
111
 
143
- def dirty?
144
- @dirty
145
- end
146
-
147
- def dirty!
148
- @dirty = true
149
- end
150
-
151
112
  def parts
152
113
  parts = ViewCollection.new
153
- parts.context = @context
154
- parts.composer = self
155
114
  parts << @template
156
115
  @page.each_container do |name, container| parts << container end
157
116
  partials.each_pair do |name, partial| parts << partial end
@@ -160,10 +119,6 @@ module Pakyow
160
119
 
161
120
  private
162
121
 
163
- def clean!
164
- @dirty = false
165
- end
166
-
167
122
  def build_view
168
123
  raise MissingTemplate, "No template provided to view composer" if @template.nil?
169
124
  raise MissingPage, "No page provided to view composer" if @page.nil?
@@ -0,0 +1,89 @@
1
+ module Pakyow
2
+ module Presenter
3
+ # This is a wrapper for View / ViewCollection that passes the current
4
+ # AppContext to all binding methods. This object is expected to only
5
+ # be used from the app, not internally.
6
+ #
7
+ class ViewContext
8
+ include Helpers
9
+ VIEW_CLASSES = [View, ViewCollection, Partial, Template, Container]
10
+
11
+ # The arities of misc view methods that switch the behavior from
12
+ # instance_exec to yield.
13
+ #
14
+ EXEC_ARITIES = { with: 0, for: 1, for_with_index: 2, repeat: 1,
15
+ repeat_with_index: 2, bind: 1, bind_with_index: 2, apply: 1 }
16
+
17
+ def initialize(view, context)
18
+ @view = view
19
+ @context = context
20
+ end
21
+
22
+ # View methods that expect context, so it can be mixed in.
23
+ #
24
+ %i[bind bind_with_index apply].each do |method|
25
+ define_method(method) do |data, **kargs, &block|
26
+ kargs[:context] ||= @context
27
+ ret = @view.send(method, data, **kargs, &wrap(method, &block))
28
+ handle_return_value(ret)
29
+ end
30
+ end
31
+
32
+ # View methods that return views, but don't expect context.
33
+ #
34
+ %i[with for for_with_index repeat repeat_with_index].each do |method|
35
+ define_method(method) do |*args, &block|
36
+ ret = @view.send(method, *args, &wrap(method, &block))
37
+ handle_return_value(ret)
38
+ end
39
+ end
40
+
41
+ # Pass these through, handling the return value.
42
+ #
43
+ def method_missing(method, *args, &block)
44
+ ret = @view.send(method, *args, &block)
45
+ handle_return_value(ret)
46
+ end
47
+
48
+ private
49
+
50
+ def view?(obj)
51
+ VIEW_CLASSES.include?(obj.class)
52
+ end
53
+
54
+ # Returns a new context for returned views, or the return value.
55
+ #
56
+ def handle_return_value(value)
57
+ if view?(value)
58
+ return ViewContext.new(value, @context)
59
+ end
60
+
61
+ value
62
+ end
63
+
64
+ # Wrap the block, substituting the view with the current view context.
65
+ #
66
+ def wrap(method, &block)
67
+ return if block.nil?
68
+
69
+ Proc.new do |*args|
70
+ ctx = args.map! { |arg|
71
+ view?(arg) ? ViewContext.new(arg, @context) : arg
72
+ }.find { |arg| arg.is_a?(ViewContext) }
73
+
74
+ case block.arity
75
+ when EXEC_ARITIES[method]
76
+ # Rejecting ViewContext handles the edge cases around the order of
77
+ # arguments from view methods (since view is not present in some
78
+ # situations and when it is present, is always the first arg).
79
+ ctx.instance_exec(*args.reject { |arg|
80
+ arg.is_a?(ViewContext)
81
+ }, &block)
82
+ else
83
+ block.call(*args)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end