pakyow-presenter 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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