pakyow-presenter 0.8.rc4 → 0.8.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.
- checksums.yaml +4 -4
- data/pakyow-presenter/CHANGES +4 -0
- data/pakyow-presenter/lib/presenter/attributes.rb +31 -7
- data/pakyow-presenter/lib/presenter/base.rb +3 -0
- data/pakyow-presenter/lib/presenter/binder.rb +10 -6
- data/pakyow-presenter/lib/presenter/binder_set.rb +12 -5
- data/pakyow-presenter/lib/presenter/container.rb +6 -0
- data/pakyow-presenter/lib/presenter/doc_helpers.rb +7 -0
- data/pakyow-presenter/lib/presenter/exceptions.rb +4 -0
- data/pakyow-presenter/lib/presenter/helpers.rb +6 -4
- data/pakyow-presenter/lib/presenter/page.rb +44 -51
- data/pakyow-presenter/lib/presenter/partial.rb +1 -19
- data/pakyow-presenter/lib/presenter/presenter.rb +67 -51
- data/pakyow-presenter/lib/presenter/template.rb +11 -15
- data/pakyow-presenter/lib/presenter/title_helpers.rb +21 -0
- data/pakyow-presenter/lib/presenter/view.rb +280 -124
- data/pakyow-presenter/lib/presenter/view_collection.rb +108 -12
- data/pakyow-presenter/lib/presenter/view_composer.rb +194 -0
- data/pakyow-presenter/lib/presenter/view_store.rb +65 -18
- metadata +18 -15
|
@@ -2,6 +2,9 @@ module Pakyow
|
|
|
2
2
|
module Presenter
|
|
3
3
|
class View
|
|
4
4
|
include DocHelpers
|
|
5
|
+
include TitleHelpers
|
|
6
|
+
|
|
7
|
+
PARTIAL_REGEX = /<!--\s*@include\s*([a-zA-Z0-9\-_]*)\s*-->/
|
|
5
8
|
|
|
6
9
|
class << self
|
|
7
10
|
attr_accessor :binders
|
|
@@ -19,15 +22,9 @@ module Pakyow
|
|
|
19
22
|
end
|
|
20
23
|
end
|
|
21
24
|
|
|
22
|
-
attr_accessor :doc, :scoped_as, :scopes, :related_views
|
|
25
|
+
attr_accessor :doc, :scoped_as, :scopes, :related_views, :context, :composer
|
|
23
26
|
attr_writer :bindings
|
|
24
27
|
|
|
25
|
-
def dup
|
|
26
|
-
view = self.class.from_doc(@doc.dup)
|
|
27
|
-
view.scoped_as = scoped_as
|
|
28
|
-
return view
|
|
29
|
-
end
|
|
30
|
-
|
|
31
28
|
def initialize(contents = '', format = :html)
|
|
32
29
|
@related_views = []
|
|
33
30
|
|
|
@@ -40,6 +37,15 @@ module Pakyow
|
|
|
40
37
|
end
|
|
41
38
|
end
|
|
42
39
|
|
|
40
|
+
def initialize_copy(original_view)
|
|
41
|
+
super
|
|
42
|
+
|
|
43
|
+
@doc = original_view.doc.dup
|
|
44
|
+
@scoped_as = original_view.scoped_as
|
|
45
|
+
@context = @context
|
|
46
|
+
@composer = @composer
|
|
47
|
+
end
|
|
48
|
+
|
|
43
49
|
def self.from_doc(doc)
|
|
44
50
|
view = self.new
|
|
45
51
|
view.doc = doc
|
|
@@ -47,35 +53,20 @@ module Pakyow
|
|
|
47
53
|
end
|
|
48
54
|
|
|
49
55
|
def self.load(path)
|
|
50
|
-
format =
|
|
56
|
+
format = Utils::String.split_at_last_dot(path)[-1]
|
|
51
57
|
contents = File.read(path)
|
|
52
58
|
|
|
53
59
|
return self.new(contents, format)
|
|
54
60
|
end
|
|
55
61
|
|
|
56
|
-
def title=(title)
|
|
57
|
-
if @doc
|
|
58
|
-
if o = @doc.css('title').first
|
|
59
|
-
o.inner_html = Nokogiri::HTML::fragment(title)
|
|
60
|
-
else
|
|
61
|
-
if o = @doc.css('head').first
|
|
62
|
-
o.add_child(Nokogiri::HTML::fragment("<title>#{title}</title>"))
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def title
|
|
69
|
-
o = @doc.css('title').first
|
|
70
|
-
o.inner_html if o
|
|
71
|
-
end
|
|
72
|
-
|
|
73
62
|
# Allows multiple attributes to be set at once.
|
|
74
63
|
# root_view.find(selector).attributes(:class => my_class, :style => my_style)
|
|
75
64
|
#
|
|
76
65
|
def attributes(attrs = {})
|
|
66
|
+
#TODO this is not invalidating composer
|
|
67
|
+
|
|
77
68
|
if attrs.empty?
|
|
78
|
-
return Attributes.new(self.doc)
|
|
69
|
+
return Attributes.new(self.doc, @composer)
|
|
79
70
|
else
|
|
80
71
|
self.bind_attributes_to_doc(attrs, doc)
|
|
81
72
|
end
|
|
@@ -84,8 +75,14 @@ module Pakyow
|
|
|
84
75
|
alias :attrs :attributes
|
|
85
76
|
|
|
86
77
|
def remove
|
|
87
|
-
|
|
88
|
-
|
|
78
|
+
if doc.parent.nil?
|
|
79
|
+
# best we can do is to remove the children
|
|
80
|
+
doc.children.remove
|
|
81
|
+
else
|
|
82
|
+
doc.remove
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
invalidate!
|
|
89
86
|
end
|
|
90
87
|
|
|
91
88
|
alias :delete :remove
|
|
@@ -93,7 +90,7 @@ module Pakyow
|
|
|
93
90
|
def clear
|
|
94
91
|
return if self.doc.blank?
|
|
95
92
|
self.doc.inner_html = ''
|
|
96
|
-
self.
|
|
93
|
+
self.invalidate!
|
|
97
94
|
end
|
|
98
95
|
|
|
99
96
|
def text
|
|
@@ -103,7 +100,7 @@ module Pakyow
|
|
|
103
100
|
def text=(text)
|
|
104
101
|
text = text.call(self.text) if text.is_a?(Proc)
|
|
105
102
|
self.doc.content = text.to_s
|
|
106
|
-
self.
|
|
103
|
+
self.invalidate!
|
|
107
104
|
end
|
|
108
105
|
|
|
109
106
|
def html
|
|
@@ -113,7 +110,7 @@ module Pakyow
|
|
|
113
110
|
def html=(html)
|
|
114
111
|
html = html.call(self.html) if html.is_a?(Proc)
|
|
115
112
|
self.doc.inner_html = Nokogiri::HTML.fragment(html.to_s)
|
|
116
|
-
self.
|
|
113
|
+
self.invalidate!
|
|
117
114
|
end
|
|
118
115
|
|
|
119
116
|
def append(view)
|
|
@@ -124,7 +121,7 @@ module Pakyow
|
|
|
124
121
|
self.doc.add_child(view.doc)
|
|
125
122
|
|
|
126
123
|
self.update_binding_offset_at_path(num, path)
|
|
127
|
-
self.
|
|
124
|
+
self.invalidate!
|
|
128
125
|
end
|
|
129
126
|
|
|
130
127
|
def prepend(view)
|
|
@@ -139,7 +136,7 @@ module Pakyow
|
|
|
139
136
|
end
|
|
140
137
|
|
|
141
138
|
self.update_binding_offset_at_path(num, path)
|
|
142
|
-
self.
|
|
139
|
+
self.invalidate!
|
|
143
140
|
end
|
|
144
141
|
|
|
145
142
|
def after(view)
|
|
@@ -150,7 +147,7 @@ module Pakyow
|
|
|
150
147
|
self.doc.after(view.doc)
|
|
151
148
|
|
|
152
149
|
self.update_binding_offset_at_path(num, path)
|
|
153
|
-
self.
|
|
150
|
+
self.invalidate!
|
|
154
151
|
end
|
|
155
152
|
|
|
156
153
|
def before(view)
|
|
@@ -161,22 +158,35 @@ module Pakyow
|
|
|
161
158
|
self.doc.before(view.doc)
|
|
162
159
|
|
|
163
160
|
self.update_binding_offset_at_path(num, path)
|
|
164
|
-
self.
|
|
161
|
+
self.invalidate!
|
|
165
162
|
end
|
|
166
163
|
|
|
167
164
|
def replace(view)
|
|
168
|
-
doc.
|
|
165
|
+
view = view.doc if view.is_a?(View)
|
|
166
|
+
|
|
167
|
+
if doc.parent.nil?
|
|
168
|
+
doc.children.remove
|
|
169
|
+
doc.inner_html = view
|
|
170
|
+
else
|
|
171
|
+
doc.replace(view)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
invalidate!
|
|
169
175
|
end
|
|
170
176
|
|
|
171
177
|
def scope(name)
|
|
172
178
|
name = name.to_sym
|
|
173
179
|
|
|
174
180
|
views = ViewCollection.new
|
|
181
|
+
views.context = @context
|
|
182
|
+
views.composer = @composer
|
|
175
183
|
self.bindings.select{|b| b[:scope] == name}.each{|s|
|
|
176
184
|
v = self.view_from_path(s[:path])
|
|
177
185
|
|
|
178
186
|
v.bindings = self.update_binding_paths_from_path([s].concat(s[:nested_bindings]), s[:path])
|
|
179
187
|
v.scoped_as = s[:scope]
|
|
188
|
+
v.context = @context
|
|
189
|
+
v.composer = @composer
|
|
180
190
|
|
|
181
191
|
views << v
|
|
182
192
|
}
|
|
@@ -188,6 +198,8 @@ module Pakyow
|
|
|
188
198
|
name = name.to_sym
|
|
189
199
|
|
|
190
200
|
views = ViewCollection.new
|
|
201
|
+
views.context = @context
|
|
202
|
+
views.composer = @composer
|
|
191
203
|
|
|
192
204
|
if binding = self.bindings.select{|binding| binding[:scope] == self.scoped_as}[0]
|
|
193
205
|
binding[:props].each {|prop|
|
|
@@ -195,6 +207,8 @@ module Pakyow
|
|
|
195
207
|
v = self.view_from_path(prop[:path])
|
|
196
208
|
|
|
197
209
|
v.scoped_as = self.scoped_as
|
|
210
|
+
v.context = @context
|
|
211
|
+
v.composer = @composer
|
|
198
212
|
views << v
|
|
199
213
|
end
|
|
200
214
|
}
|
|
@@ -208,11 +222,13 @@ module Pakyow
|
|
|
208
222
|
#
|
|
209
223
|
# Creates a context in which view manipulations can be performed.
|
|
210
224
|
#
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
225
|
+
def with(&block)
|
|
226
|
+
if block.arity == 0
|
|
227
|
+
self.instance_exec(&block)
|
|
228
|
+
else
|
|
229
|
+
yield(self)
|
|
230
|
+
end
|
|
231
|
+
|
|
216
232
|
self
|
|
217
233
|
end
|
|
218
234
|
|
|
@@ -228,7 +244,27 @@ module Pakyow
|
|
|
228
244
|
def for(data, &block)
|
|
229
245
|
data = data.to_a if data.is_a?(Enumerator)
|
|
230
246
|
data = [data] if (!data.is_a?(Enumerable) || data.is_a?(Hash))
|
|
231
|
-
|
|
247
|
+
|
|
248
|
+
if block.arity == 1
|
|
249
|
+
self.instance_exec(data[0], &block)
|
|
250
|
+
else
|
|
251
|
+
block.call(self, data[0])
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# call-seq:
|
|
256
|
+
# for_with_index {|view, datum, i| block}
|
|
257
|
+
#
|
|
258
|
+
# Yields a view, its matching dataum, and the index. See #for.
|
|
259
|
+
#
|
|
260
|
+
def for_with_index(data, &block)
|
|
261
|
+
self.for(data) do |ctx, datum|
|
|
262
|
+
if block.arity == 2
|
|
263
|
+
ctx.instance_exec(datum, 0, &block)
|
|
264
|
+
else
|
|
265
|
+
block.call(ctx, datum, 0)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
232
268
|
end
|
|
233
269
|
|
|
234
270
|
# call-seq:
|
|
@@ -243,6 +279,8 @@ module Pakyow
|
|
|
243
279
|
data = [data] if (!data.is_a?(Enumerable) || data.is_a?(Hash))
|
|
244
280
|
|
|
245
281
|
views = ViewCollection.new
|
|
282
|
+
views.context = @context
|
|
283
|
+
views.composer = @composer
|
|
246
284
|
data.each {|datum|
|
|
247
285
|
d_v = self.doc.dup
|
|
248
286
|
self.doc.before(d_v)
|
|
@@ -250,6 +288,8 @@ module Pakyow
|
|
|
250
288
|
v = View.from_doc(d_v)
|
|
251
289
|
v.bindings = self.bindings.dup
|
|
252
290
|
v.scoped_as = self.scoped_as
|
|
291
|
+
v.context = @context
|
|
292
|
+
v.composer = @composer
|
|
253
293
|
|
|
254
294
|
views << v
|
|
255
295
|
}
|
|
@@ -267,16 +307,51 @@ module Pakyow
|
|
|
267
307
|
self.match(data).for(data, &block)
|
|
268
308
|
end
|
|
269
309
|
|
|
310
|
+
# call-seq:
|
|
311
|
+
# repeat_with_index(data) {|view, datum, i| block}
|
|
312
|
+
#
|
|
313
|
+
# Matches self with data and yields a view/datum pair with index.
|
|
314
|
+
#
|
|
315
|
+
def repeat_with_index(data, &block)
|
|
316
|
+
self.match(data).for_with_index(data, &block)
|
|
317
|
+
end
|
|
318
|
+
|
|
270
319
|
# call-seq:
|
|
271
320
|
# bind(data)
|
|
272
321
|
#
|
|
273
322
|
# Binds data across existing scopes.
|
|
274
323
|
#
|
|
275
324
|
def bind(data, bindings = {}, &block)
|
|
325
|
+
data = data.to_a if data.is_a?(Enumerator)
|
|
326
|
+
data = [data] if (!data.is_a?(Enumerable) || data.is_a?(Hash))
|
|
327
|
+
|
|
276
328
|
scope_info = self.bindings.first
|
|
277
329
|
|
|
278
|
-
self.bind_data_to_scope(data, scope_info, bindings)
|
|
279
|
-
|
|
330
|
+
self.bind_data_to_scope(data[0], scope_info, bindings)
|
|
331
|
+
invalidate!(true)
|
|
332
|
+
|
|
333
|
+
return if block.nil?
|
|
334
|
+
|
|
335
|
+
if block.arity == 1
|
|
336
|
+
self.instance_exec(data[0], &block)
|
|
337
|
+
else
|
|
338
|
+
block.call(self, data[0])
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# call-seq:
|
|
343
|
+
# bind_with_index(data)
|
|
344
|
+
#
|
|
345
|
+
# Binds data across existing scopes, yielding a view/datum pair with index.
|
|
346
|
+
#
|
|
347
|
+
def bind_with_index(data, bindings = {}, &block)
|
|
348
|
+
self.bind(data) do |ctx, datum|
|
|
349
|
+
if block.arity == 2
|
|
350
|
+
ctx.instance_exec(datum, 0, &block)
|
|
351
|
+
else
|
|
352
|
+
block.call(ctx, datum, 0)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
280
355
|
end
|
|
281
356
|
|
|
282
357
|
# call-seq:
|
|
@@ -285,15 +360,74 @@ module Pakyow
|
|
|
285
360
|
# Matches self to data then binds data to the view.
|
|
286
361
|
#
|
|
287
362
|
def apply(data, bindings = {}, &block)
|
|
288
|
-
|
|
363
|
+
self.match(data).bind(data, bindings, &block)
|
|
289
364
|
end
|
|
290
365
|
|
|
291
366
|
def bindings(refind = false)
|
|
292
367
|
@bindings = (!@bindings || refind) ? self.find_bindings : @bindings
|
|
293
368
|
end
|
|
294
369
|
|
|
370
|
+
def includes(partial_map)
|
|
371
|
+
partial_map = partial_map.dup
|
|
372
|
+
|
|
373
|
+
# mixin all the partials
|
|
374
|
+
partials.each do |partial|
|
|
375
|
+
partial[1].replace(partial_map[partial[0]].to_s)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# now delete them from the map
|
|
379
|
+
partials.each do |partial|
|
|
380
|
+
partial_map.delete(partial[0])
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# we have more partials
|
|
384
|
+
if partial_map.count > 0
|
|
385
|
+
# initiate another build if content contains partials
|
|
386
|
+
includes(partial_map) if partials(true).count > 0
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
return self
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def invalidate!(composer_only = false)
|
|
393
|
+
self.bindings(true) unless composer_only
|
|
394
|
+
@composer.dirty! unless @composer.nil?
|
|
395
|
+
|
|
396
|
+
@related_views.each {|v|
|
|
397
|
+
v.invalidate!(composer_only)
|
|
398
|
+
}
|
|
399
|
+
end
|
|
400
|
+
|
|
295
401
|
protected
|
|
296
402
|
|
|
403
|
+
def partials(refind = false)
|
|
404
|
+
@partials = (!@partials || refind) ? find_partials : @partials
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def partials_in(content)
|
|
408
|
+
partials = []
|
|
409
|
+
|
|
410
|
+
content.scan(PARTIAL_REGEX) do |m|
|
|
411
|
+
partials << m[0].to_sym
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
return partials
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def find_partials
|
|
418
|
+
partials = []
|
|
419
|
+
|
|
420
|
+
@doc.traverse { |e|
|
|
421
|
+
next unless e.is_a?(Nokogiri::XML::Comment)
|
|
422
|
+
next unless match = e.to_html.strip.match(PARTIAL_REGEX)
|
|
423
|
+
|
|
424
|
+
name = match[1]
|
|
425
|
+
partials << [name.to_sym, e]
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return partials
|
|
429
|
+
end
|
|
430
|
+
|
|
297
431
|
# populates the root_view using view_store data by recursively building
|
|
298
432
|
# and substituting in child views named in the structure
|
|
299
433
|
def populate_view(root_view, view_store, view_info)
|
|
@@ -301,6 +435,8 @@ module Pakyow
|
|
|
301
435
|
next unless path = view_info[e[:name]]
|
|
302
436
|
|
|
303
437
|
v = self.populate_view(View.new(path, view_store), view_store, view_info)
|
|
438
|
+
v.context = @context
|
|
439
|
+
v.composer = @composer
|
|
304
440
|
self.reset_container(e[:doc])
|
|
305
441
|
self.add_content_to_container(v, e[:doc])
|
|
306
442
|
}
|
|
@@ -325,7 +461,7 @@ module Pakyow
|
|
|
325
461
|
# this is the root node, which we need as the first hash in the
|
|
326
462
|
# list of bindings, but we don't want to nest other scopes inside
|
|
327
463
|
# of it in this case
|
|
328
|
-
bindings.last[:nested_bindings] =
|
|
464
|
+
bindings.last[:nested_bindings] = []
|
|
329
465
|
else
|
|
330
466
|
bindings.last[:nested_bindings] = find_bindings(o, true)
|
|
331
467
|
# reject so children aren't traversed
|
|
@@ -334,12 +470,14 @@ module Pakyow
|
|
|
334
470
|
}
|
|
335
471
|
|
|
336
472
|
# find unscoped props
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
473
|
+
unless doc[Config::Presenter.scope_attribute]
|
|
474
|
+
bindings.unshift({
|
|
475
|
+
:scope => nil,
|
|
476
|
+
:path => [],
|
|
477
|
+
:props => find_props(doc),
|
|
478
|
+
:nested_bindings => []
|
|
479
|
+
})
|
|
480
|
+
end
|
|
343
481
|
|
|
344
482
|
return bindings
|
|
345
483
|
end
|
|
@@ -388,42 +526,42 @@ module Pakyow
|
|
|
388
526
|
}
|
|
389
527
|
end
|
|
390
528
|
|
|
391
|
-
def refind_significant_nodes
|
|
392
|
-
self.bindings(true)
|
|
393
|
-
|
|
394
|
-
@related_views.each {|v|
|
|
395
|
-
v.refind_significant_nodes
|
|
396
|
-
}
|
|
397
|
-
end
|
|
398
|
-
|
|
399
529
|
def bind_data_to_scope(data, scope_info, bindings = {})
|
|
400
530
|
return unless data
|
|
401
531
|
|
|
402
532
|
scope = scope_info[:scope]
|
|
403
533
|
|
|
404
|
-
|
|
405
|
-
if value = Pakyow.app.presenter.binder.value_for_prop(:_root, scope, data, bindings)
|
|
406
|
-
value.is_a?(Hash) ? self.bind_attributes_to_doc(value, self.doc) : self.bind_value_to_doc(value, self.doc)
|
|
407
|
-
end
|
|
534
|
+
bind_data_to_root(data, scope, bindings)
|
|
408
535
|
|
|
409
|
-
scope_info[:props].each {|prop_info|
|
|
536
|
+
scope_info[:props].each { |prop_info|
|
|
410
537
|
catch(:unbound) {
|
|
411
538
|
prop = prop_info[:prop]
|
|
412
539
|
|
|
413
|
-
|
|
414
|
-
|
|
540
|
+
if data_has_prop?(data, prop) || Pakyow.app.presenter.binder.has_prop?(prop, scope, bindings)
|
|
541
|
+
value = Pakyow.app.presenter.binder.value_for_prop(prop, scope, data, bindings, context)
|
|
542
|
+
doc = doc_from_path(prop_info[:path])
|
|
415
543
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
self.bind_to_form_field(doc, scope, prop, value, data) if View.form_field?(doc.name)
|
|
544
|
+
if View.form_field?(doc.name)
|
|
545
|
+
bind_to_form_field(doc, scope, prop, value, data)
|
|
546
|
+
end
|
|
420
547
|
|
|
421
|
-
|
|
422
|
-
|
|
548
|
+
bind_data_to_doc(doc, value)
|
|
549
|
+
else
|
|
550
|
+
handle_unbound_data(scope, prop)
|
|
551
|
+
end
|
|
423
552
|
}
|
|
424
553
|
}
|
|
425
554
|
end
|
|
426
555
|
|
|
556
|
+
def bind_data_to_root(data, scope, bindings)
|
|
557
|
+
return unless value = Pakyow.app.presenter.binder.value_for_prop(:_root, scope, data, bindings, context)
|
|
558
|
+
value.is_a?(Hash) ? self.bind_attributes_to_doc(value, self.doc) : self.bind_value_to_doc(value, self.doc)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def bind_data_to_doc(doc, data)
|
|
562
|
+
data.is_a?(Hash) ? self.bind_attributes_to_doc(data, doc) : self.bind_value_to_doc(data, doc)
|
|
563
|
+
end
|
|
564
|
+
|
|
427
565
|
def data_has_prop?(data, prop)
|
|
428
566
|
(data.is_a?(Hash) && (data.key?(prop) || data.key?(prop.to_s))) || (!data.is_a?(Hash) && data.class.method_defined?(prop))
|
|
429
567
|
end
|
|
@@ -433,6 +571,7 @@ module Pakyow
|
|
|
433
571
|
|
|
434
572
|
tag = doc.name
|
|
435
573
|
return if View.tag_without_value?(tag)
|
|
574
|
+
|
|
436
575
|
if View.self_closing_tag?(tag)
|
|
437
576
|
# don't override value if set
|
|
438
577
|
if !doc['value'] || doc['value'].empty?
|
|
@@ -458,72 +597,89 @@ module Pakyow
|
|
|
458
597
|
attr = attr.to_s
|
|
459
598
|
attrs = Attributes.new(doc)
|
|
460
599
|
v = v.call(attrs.send(attr)) if v.is_a?(Proc)
|
|
461
|
-
|
|
600
|
+
|
|
601
|
+
if v.nil?
|
|
602
|
+
doc.remove_attribute(attr)
|
|
603
|
+
else
|
|
604
|
+
attrs.send(:"#{attr}=", v)
|
|
605
|
+
end
|
|
462
606
|
end
|
|
463
607
|
end
|
|
464
608
|
|
|
465
609
|
def bind_to_form_field(doc, scope, prop, value, bindable)
|
|
466
|
-
|
|
467
|
-
# don't overwrite the name if already defined
|
|
468
|
-
if !doc['name'] || doc['name'].empty?
|
|
469
|
-
# set name on form element
|
|
470
|
-
doc['name'] = "#{scope}[#{prop}]"
|
|
471
|
-
end
|
|
610
|
+
set_form_field_name(doc, scope, prop)
|
|
472
611
|
|
|
473
612
|
# special binding for checkboxes and radio buttons
|
|
474
613
|
if doc.name == 'input' && (doc[:type] == 'checkbox' || doc[:type] == 'radio')
|
|
475
|
-
|
|
476
|
-
doc[:checked] = 'checked'
|
|
477
|
-
else
|
|
478
|
-
doc.delete('checked')
|
|
479
|
-
end
|
|
480
|
-
|
|
481
|
-
# coerce to string since booleans are often used
|
|
482
|
-
# and fail when binding to a view
|
|
483
|
-
value = value.to_s
|
|
614
|
+
bind_to_checked_field(doc, value)
|
|
484
615
|
# special binding for selects
|
|
485
616
|
elsif doc.name == 'select'
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
617
|
+
bind_to_select_field(doc, scope, prop, value, bindable)
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def bind_to_checked_field(doc, value)
|
|
622
|
+
if value == true || (doc[:value] && doc[:value] == value.to_s)
|
|
623
|
+
doc[:checked] = 'checked'
|
|
624
|
+
else
|
|
625
|
+
doc.delete('checked')
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# coerce to string since booleans are often used and fail when binding to a view
|
|
629
|
+
value = value.to_s
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def bind_to_select_field(doc, scope, prop, value, bindable)
|
|
633
|
+
create_select_options(doc, scope, prop, value, bindable)
|
|
634
|
+
select_option_with_value(doc, value)
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def set_form_field_name(doc, scope, prop)
|
|
638
|
+
return if doc['name'] && !doc['name'].empty? # don't overwrite the name if already defined
|
|
639
|
+
doc['name'] = "#{scope}[#{prop}]"
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
def create_select_options(doc, scope, prop, value, bindable)
|
|
643
|
+
return unless options = Pakyow.app.presenter.binder.options_for_prop(prop, scope, bindable, context)
|
|
644
|
+
|
|
645
|
+
option_nodes = Nokogiri::HTML::DocumentFragment.parse ""
|
|
646
|
+
Nokogiri::HTML::Builder.with(option_nodes) do |h|
|
|
647
|
+
until options.length == 0
|
|
648
|
+
catch :optgroup do
|
|
649
|
+
o = options.first
|
|
650
|
+
|
|
651
|
+
# an array containing value/content
|
|
652
|
+
if o.is_a?(Array)
|
|
653
|
+
h.option o[1], :value => o[0]
|
|
654
|
+
options.shift
|
|
655
|
+
# likely an object (e.g. string); start a group
|
|
656
|
+
else
|
|
657
|
+
h.optgroup(:label => o) {
|
|
658
|
+
options.shift
|
|
659
|
+
|
|
660
|
+
options[0..-1].each_with_index { |o2,i2|
|
|
661
|
+
# starting a new group
|
|
662
|
+
throw :optgroup if !o2.is_a?(Array)
|
|
663
|
+
|
|
664
|
+
h.option o2[1], :value => o2[0]
|
|
496
665
|
options.shift
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
h.optgroup(:label => o) {
|
|
500
|
-
options.shift
|
|
501
|
-
|
|
502
|
-
options[0..-1].each_with_index { |o2,i2|
|
|
503
|
-
# starting a new group
|
|
504
|
-
throw :optgroup if !o2.is_a?(Array)
|
|
505
|
-
|
|
506
|
-
h.option o2[1], :value => o2[0]
|
|
507
|
-
options.shift
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
end
|
|
511
|
-
end
|
|
666
|
+
}
|
|
667
|
+
}
|
|
512
668
|
end
|
|
513
669
|
end
|
|
670
|
+
end
|
|
671
|
+
end
|
|
514
672
|
|
|
515
|
-
|
|
516
|
-
|
|
673
|
+
# remove existing options
|
|
674
|
+
doc.children.remove
|
|
517
675
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
676
|
+
# add generated options
|
|
677
|
+
doc.add_child(option_nodes)
|
|
678
|
+
end
|
|
521
679
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
end
|
|
526
|
-
end
|
|
680
|
+
def select_option_with_value(doc, value)
|
|
681
|
+
return unless o = doc.css('option[value="' + value.to_s + '"]').first
|
|
682
|
+
o[:selected] = 'selected'
|
|
527
683
|
end
|
|
528
684
|
|
|
529
685
|
def handle_unbound_data(scope, prop)
|