pakyow-presenter 0.8rc1 → 0.8.rc4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/pakyow-presenter/lib/pakyow-presenter.rb +2 -1
- data/pakyow-presenter/lib/presenter/attributes.rb +128 -35
- data/pakyow-presenter/lib/presenter/base.rb +8 -4
- data/pakyow-presenter/lib/presenter/binder.rb +62 -30
- data/pakyow-presenter/lib/presenter/binder_set.rb +95 -0
- data/pakyow-presenter/lib/presenter/config/presenter.rb +61 -0
- data/pakyow-presenter/lib/presenter/doc_helpers.rb +70 -0
- data/pakyow-presenter/lib/presenter/exceptions.rb +7 -0
- data/pakyow-presenter/lib/presenter/ext/app.rb +32 -0
- data/pakyow-presenter/lib/presenter/helpers.rb +4 -5
- data/pakyow-presenter/lib/presenter/page.rb +126 -0
- data/pakyow-presenter/lib/presenter/partial.rb +24 -0
- data/pakyow-presenter/lib/presenter/presenter.rb +120 -187
- data/pakyow-presenter/lib/presenter/template.rb +79 -0
- data/pakyow-presenter/lib/presenter/view.rb +255 -300
- data/pakyow-presenter/lib/presenter/view_collection.rb +55 -39
- data/pakyow-presenter/lib/presenter/view_store.rb +174 -0
- metadata +43 -32
- data/pakyow-presenter/lib/presenter/bindings.rb +0 -103
- data/pakyow-presenter/lib/presenter/configuration/base.rb +0 -12
- data/pakyow-presenter/lib/presenter/configuration/presenter.rb +0 -45
- data/pakyow-presenter/lib/presenter/lazy_view.rb +0 -42
- data/pakyow-presenter/lib/presenter/view_context.rb +0 -20
- data/pakyow-presenter/lib/presenter/view_lookup_store.rb +0 -220
@@ -1,23 +1,10 @@
|
|
1
1
|
module Pakyow
|
2
2
|
module Presenter
|
3
3
|
class View
|
4
|
-
|
5
|
-
attr_accessor :binders, :default_view_path, :default_is_root_view
|
6
|
-
|
7
|
-
def view_store
|
8
|
-
Pakyow.app.presenter.current_view_lookup_store
|
9
|
-
end
|
10
|
-
|
11
|
-
def binder_for_scope(scope, bindable)
|
12
|
-
bindings = Pakyow.app.presenter.bindings(scope)
|
13
|
-
bindings.bindable = bindable
|
14
|
-
return bindings
|
15
|
-
end
|
4
|
+
include DocHelpers
|
16
5
|
|
17
|
-
|
18
|
-
|
19
|
-
self.default_is_root_view = dirv
|
20
|
-
end
|
6
|
+
class << self
|
7
|
+
attr_accessor :binders
|
21
8
|
|
22
9
|
def self_closing_tag?(tag)
|
23
10
|
%w[area base basefont br hr input img link meta].include? tag
|
@@ -30,68 +17,42 @@ module Pakyow
|
|
30
17
|
def tag_without_value?(tag)
|
31
18
|
%w[select].include? tag
|
32
19
|
end
|
33
|
-
|
34
|
-
def at_path(view_path)
|
35
|
-
v = self.new(self.view_store.root_path(view_path), true)
|
36
|
-
v.compile(view_path)
|
37
|
-
end
|
38
|
-
|
39
|
-
def root_at_path(view_path)
|
40
|
-
self.new(self.view_store.root_path(view_path), true)
|
41
|
-
end
|
42
|
-
|
43
20
|
end
|
44
21
|
|
45
|
-
attr_accessor :doc, :scoped_as, :scopes
|
22
|
+
attr_accessor :doc, :scoped_as, :scopes, :related_views
|
46
23
|
attr_writer :bindings
|
47
24
|
|
48
25
|
def dup
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
def initialize(
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@doc = arg.first.doc.dup
|
62
|
-
elsif arg.is_a?(Pakyow::Presenter::View)
|
63
|
-
@doc = arg.doc.dup
|
64
|
-
elsif arg.is_a?(String)
|
65
|
-
view_path = self.class.view_store.real_path(arg)
|
66
|
-
|
67
|
-
# run parsers
|
68
|
-
format = StringUtils.split_at_last_dot(view_path)[1].to_sym
|
69
|
-
content = parse_content(File.read(view_path), format)
|
70
|
-
|
71
|
-
if is_root_view then
|
72
|
-
@doc = Nokogiri::HTML::Document.parse(content)
|
73
|
-
else
|
74
|
-
@doc = Nokogiri::HTML.fragment(content)
|
75
|
-
end
|
26
|
+
view = self.class.from_doc(@doc.dup)
|
27
|
+
view.scoped_as = scoped_as
|
28
|
+
return view
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(contents = '', format = :html)
|
32
|
+
@related_views = []
|
33
|
+
|
34
|
+
processed = Presenter.process(contents, format)
|
35
|
+
|
36
|
+
if processed.match(/<html.*>/)
|
37
|
+
@doc = Nokogiri::HTML::Document.parse(processed)
|
76
38
|
else
|
77
|
-
|
39
|
+
@doc = Nokogiri::HTML.fragment(processed)
|
78
40
|
end
|
79
41
|
end
|
80
42
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
43
|
+
def self.from_doc(doc)
|
44
|
+
view = self.new
|
45
|
+
view.doc = doc
|
46
|
+
return view
|
84
47
|
end
|
85
48
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
content
|
92
|
-
end
|
49
|
+
def self.load(path)
|
50
|
+
format = StringUtils.split_at_last_dot(path)[-1]
|
51
|
+
contents = File.read(path)
|
52
|
+
|
53
|
+
return self.new(contents, format)
|
93
54
|
end
|
94
|
-
|
55
|
+
|
95
56
|
def title=(title)
|
96
57
|
if @doc
|
97
58
|
if o = @doc.css('title').first
|
@@ -108,104 +69,113 @@ module Pakyow
|
|
108
69
|
o = @doc.css('title').first
|
109
70
|
o.inner_html if o
|
110
71
|
end
|
111
|
-
|
112
|
-
def to_html(container = nil)
|
113
|
-
if container
|
114
|
-
if o = @doc.css("*[#{Configuration::Presenter.container_attribute}='#{container}']").first
|
115
|
-
o.inner_html
|
116
|
-
else
|
117
|
-
''
|
118
|
-
end
|
119
|
-
else
|
120
|
-
@doc.to_html
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
alias :to_s :to_html
|
125
72
|
|
126
73
|
# Allows multiple attributes to be set at once.
|
127
74
|
# root_view.find(selector).attributes(:class => my_class, :style => my_style)
|
128
75
|
#
|
129
|
-
def attributes(
|
130
|
-
if
|
131
|
-
return Attributes.new(self)
|
76
|
+
def attributes(attrs = {})
|
77
|
+
if attrs.empty?
|
78
|
+
return Attributes.new(self.doc)
|
132
79
|
else
|
133
|
-
|
134
|
-
#TODO use this instead of (or combine with) bind_attributes_to_doc?
|
80
|
+
self.bind_attributes_to_doc(attrs, doc)
|
135
81
|
end
|
136
|
-
|
137
|
-
# if args.empty?
|
138
|
-
# @previous_method = :attributes
|
139
|
-
# return self
|
140
|
-
# else
|
141
|
-
# args[0].each_pair { |name, value|
|
142
|
-
# @previous_method = :attributes
|
143
|
-
# self.send(name.to_sym, value)
|
144
|
-
# }
|
145
|
-
# end
|
146
82
|
end
|
147
83
|
|
148
84
|
alias :attrs :attributes
|
149
|
-
|
85
|
+
|
150
86
|
def remove
|
151
87
|
self.doc.remove
|
88
|
+
self.refind_significant_nodes
|
152
89
|
end
|
153
|
-
|
90
|
+
|
154
91
|
alias :delete :remove
|
155
|
-
|
156
|
-
#TODO replace this with a different syntax (?): view.attributes.class.add/remove/has?(:foo)
|
157
|
-
# def add_class(val)
|
158
|
-
# self.doc['class'] = "#{self.doc['class']} #{val}".strip
|
159
|
-
# end
|
160
|
-
|
161
|
-
# def remove_class(val)
|
162
|
-
# self.doc['class'] = self.doc['class'].gsub(val.to_s, '').strip if self.doc['class']
|
163
|
-
# end
|
164
|
-
|
165
|
-
# def has_class(val)
|
166
|
-
# self.doc['class'].include? val
|
167
|
-
# end
|
168
|
-
|
92
|
+
|
169
93
|
def clear
|
170
94
|
return if self.doc.blank?
|
171
95
|
self.doc.inner_html = ''
|
96
|
+
self.refind_significant_nodes
|
172
97
|
end
|
173
|
-
|
98
|
+
|
174
99
|
def text
|
175
100
|
self.doc.inner_text
|
176
101
|
end
|
177
|
-
|
178
|
-
def
|
102
|
+
|
103
|
+
def text=(text)
|
104
|
+
text = text.call(self.text) if text.is_a?(Proc)
|
105
|
+
self.doc.content = text.to_s
|
106
|
+
self.refind_significant_nodes
|
107
|
+
end
|
108
|
+
|
109
|
+
def html
|
179
110
|
self.doc.inner_html
|
180
111
|
end
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
self.
|
112
|
+
|
113
|
+
def html=(html)
|
114
|
+
html = html.call(self.html) if html.is_a?(Proc)
|
115
|
+
self.doc.inner_html = Nokogiri::HTML.fragment(html.to_s)
|
116
|
+
self.refind_significant_nodes
|
186
117
|
end
|
187
|
-
|
188
|
-
alias :html= :content=
|
189
|
-
|
118
|
+
|
190
119
|
def append(view)
|
120
|
+
doc = view.doc
|
121
|
+
num = doc.children.count
|
122
|
+
path = self.path_to(doc)
|
123
|
+
|
191
124
|
self.doc.add_child(view.doc)
|
125
|
+
|
126
|
+
self.update_binding_offset_at_path(num, path)
|
127
|
+
self.refind_significant_nodes
|
128
|
+
end
|
129
|
+
|
130
|
+
def prepend(view)
|
131
|
+
doc = view.doc
|
132
|
+
num = doc.children.count
|
133
|
+
path = self.path_to(doc)
|
134
|
+
|
135
|
+
if first_child = self.doc.children.first
|
136
|
+
first_child.add_previous_sibling(doc)
|
137
|
+
else
|
138
|
+
self.doc = doc
|
139
|
+
end
|
140
|
+
|
141
|
+
self.update_binding_offset_at_path(num, path)
|
142
|
+
self.refind_significant_nodes
|
192
143
|
end
|
193
|
-
|
144
|
+
|
194
145
|
def after(view)
|
146
|
+
doc = view.doc
|
147
|
+
num = doc.children.count
|
148
|
+
path = self.path_to(doc)
|
149
|
+
|
195
150
|
self.doc.after(view.doc)
|
151
|
+
|
152
|
+
self.update_binding_offset_at_path(num, path)
|
153
|
+
self.refind_significant_nodes
|
196
154
|
end
|
197
|
-
|
155
|
+
|
198
156
|
def before(view)
|
157
|
+
doc = view.doc
|
158
|
+
num = doc.children.count
|
159
|
+
path = self.path_to(doc)
|
160
|
+
|
199
161
|
self.doc.before(view.doc)
|
162
|
+
|
163
|
+
self.update_binding_offset_at_path(num, path)
|
164
|
+
self.refind_significant_nodes
|
165
|
+
end
|
166
|
+
|
167
|
+
def replace(view)
|
168
|
+
doc.replace(view)
|
200
169
|
end
|
201
|
-
|
170
|
+
|
202
171
|
def scope(name)
|
203
172
|
name = name.to_sym
|
204
173
|
|
205
174
|
views = ViewCollection.new
|
206
175
|
self.bindings.select{|b| b[:scope] == name}.each{|s|
|
207
176
|
v = self.view_from_path(s[:path])
|
208
|
-
|
177
|
+
|
178
|
+
v.bindings = self.update_binding_paths_from_path([s].concat(s[:nested_bindings]), s[:path])
|
209
179
|
v.scoped_as = s[:scope]
|
210
180
|
|
211
181
|
views << v
|
@@ -213,21 +183,22 @@ module Pakyow
|
|
213
183
|
|
214
184
|
views
|
215
185
|
end
|
216
|
-
|
186
|
+
|
217
187
|
def prop(name)
|
218
188
|
name = name.to_sym
|
219
189
|
|
220
190
|
views = ViewCollection.new
|
221
|
-
|
191
|
+
|
192
|
+
if binding = self.bindings.select{|binding| binding[:scope] == self.scoped_as}[0]
|
222
193
|
binding[:props].each {|prop|
|
223
194
|
if prop[:prop] == name
|
224
195
|
v = self.view_from_path(prop[:path])
|
225
|
-
v.bindings = self.bindings_for_child_view(v)
|
226
196
|
|
197
|
+
v.scoped_as = self.scoped_as
|
227
198
|
views << v
|
228
199
|
end
|
229
200
|
}
|
230
|
-
|
201
|
+
end
|
231
202
|
|
232
203
|
views
|
233
204
|
end
|
@@ -242,6 +213,7 @@ module Pakyow
|
|
242
213
|
#
|
243
214
|
def with
|
244
215
|
yield(self)
|
216
|
+
self
|
245
217
|
end
|
246
218
|
|
247
219
|
# call-seq:
|
@@ -250,12 +222,13 @@ module Pakyow
|
|
250
222
|
# Yields a view and its matching dataum. This is driven by the view,
|
251
223
|
# meaning datums are yielded until no more views are available. For
|
252
224
|
# the single View case, only one view/datum pair is yielded.
|
253
|
-
#
|
225
|
+
#
|
254
226
|
# (this is basically Bret's `map` function)
|
255
227
|
#
|
256
228
|
def for(data, &block)
|
257
|
-
data =
|
258
|
-
|
229
|
+
data = data.to_a if data.is_a?(Enumerator)
|
230
|
+
data = [data] if (!data.is_a?(Enumerable) || data.is_a?(Hash))
|
231
|
+
block.call(self, data[0], 0) if block_given?
|
259
232
|
end
|
260
233
|
|
261
234
|
# call-seq:
|
@@ -266,16 +239,17 @@ module Pakyow
|
|
266
239
|
# of self, where n = data.length.
|
267
240
|
#
|
268
241
|
def match(data)
|
269
|
-
data =
|
242
|
+
data = data.to_a if data.is_a?(Enumerator)
|
243
|
+
data = [data] if (!data.is_a?(Enumerable) || data.is_a?(Hash))
|
270
244
|
|
271
245
|
views = ViewCollection.new
|
272
246
|
data.each {|datum|
|
273
247
|
d_v = self.doc.dup
|
274
248
|
self.doc.before(d_v)
|
275
249
|
|
276
|
-
v = View.
|
277
|
-
v.bindings = self.bindings
|
278
|
-
|
250
|
+
v = View.from_doc(d_v)
|
251
|
+
v.bindings = self.bindings.dup
|
252
|
+
v.scoped_as = self.scoped_as
|
279
253
|
|
280
254
|
views << v
|
281
255
|
}
|
@@ -298,14 +272,11 @@ module Pakyow
|
|
298
272
|
#
|
299
273
|
# Binds data across existing scopes.
|
300
274
|
#
|
301
|
-
def bind(data, bindings =
|
302
|
-
|
275
|
+
def bind(data, bindings = {}, &block)
|
276
|
+
scope_info = self.bindings.first
|
303
277
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
self.bind_data_to_scope(data, scope, binder)
|
308
|
-
yield(self, data) if block_given?
|
278
|
+
self.bind_data_to_scope(data, scope_info, bindings)
|
279
|
+
yield(self, data, 0) if block_given?
|
309
280
|
end
|
310
281
|
|
311
282
|
# call-seq:
|
@@ -313,215 +284,191 @@ module Pakyow
|
|
313
284
|
#
|
314
285
|
# Matches self to data then binds data to the view.
|
315
286
|
#
|
316
|
-
def apply(data, bindings =
|
287
|
+
def apply(data, bindings = {}, &block)
|
317
288
|
views = self.match(data).bind(data, bindings, &block)
|
318
289
|
end
|
319
290
|
|
320
|
-
def
|
321
|
-
|
322
|
-
|
323
|
-
vs = ViewCollection.new
|
324
|
-
matches.each{|m| vs << view_from_path(m[:path])}
|
325
|
-
vs
|
326
|
-
end
|
327
|
-
|
328
|
-
def containers
|
329
|
-
@containers ||= self.find_containers
|
330
|
-
end
|
331
|
-
|
332
|
-
def bindings
|
333
|
-
@bindings ||= self.find_bindings
|
291
|
+
def bindings(refind = false)
|
292
|
+
@bindings = (!@bindings || refind) ? self.find_bindings : @bindings
|
334
293
|
end
|
335
294
|
|
336
295
|
protected
|
337
296
|
|
338
|
-
def add_content_to_container(content, container)
|
339
|
-
content = content.doc unless content.class == String || content.class == Nokogiri::HTML::DocumentFragment || content.class == Nokogiri::XML::Element
|
340
|
-
container.add_child(content)
|
341
|
-
end
|
342
|
-
|
343
|
-
def reset_container(container)
|
344
|
-
container.inner_html = ''
|
345
|
-
end
|
346
|
-
|
347
|
-
|
348
297
|
# populates the root_view using view_store data by recursively building
|
349
298
|
# and substituting in child views named in the structure
|
350
|
-
def populate_view(root_view, view_info)
|
299
|
+
def populate_view(root_view, view_store, view_info)
|
351
300
|
root_view.containers.each {|e|
|
352
301
|
next unless path = view_info[e[:name]]
|
353
|
-
|
354
|
-
v = self.populate_view(View.new(path), view_info)
|
302
|
+
|
303
|
+
v = self.populate_view(View.new(path, view_store), view_store, view_info)
|
355
304
|
self.reset_container(e[:doc])
|
356
305
|
self.add_content_to_container(v, e[:doc])
|
357
306
|
}
|
358
307
|
root_view
|
359
308
|
end
|
360
309
|
|
361
|
-
# returns an array of hashes, each with the container name and doc
|
362
|
-
def find_containers
|
363
|
-
elements = []
|
364
|
-
@doc.traverse {|e|
|
365
|
-
if name = e.attr(Configuration::Presenter.container_attribute)
|
366
|
-
elements << { :name => name, :doc => e, :path => path_to(e)}
|
367
|
-
end
|
368
|
-
}
|
369
|
-
elements
|
370
|
-
end
|
371
310
|
|
372
311
|
# returns an array of hashes that describe each scope
|
373
|
-
def find_bindings
|
312
|
+
def find_bindings(doc = @doc, ignore_root = false)
|
374
313
|
bindings = []
|
375
|
-
breadth_first(
|
376
|
-
next
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
next unless prop = so[Configuration::Presenter.prop_attribute]
|
385
|
-
props << {:prop => prop.to_sym, :path => path_to(so)}
|
314
|
+
breadth_first(doc) {|o|
|
315
|
+
next if o == doc && ignore_root
|
316
|
+
next if !scope = o[Config::Presenter.scope_attribute]
|
317
|
+
|
318
|
+
bindings << {
|
319
|
+
:scope => scope.to_sym,
|
320
|
+
:path => path_to(o),
|
321
|
+
:props => find_props(o)
|
386
322
|
}
|
387
323
|
|
388
|
-
|
324
|
+
if o == doc
|
325
|
+
# this is the root node, which we need as the first hash in the
|
326
|
+
# list of bindings, but we don't want to nest other scopes inside
|
327
|
+
# of it in this case
|
328
|
+
bindings.last[:nested_bindings] = {}
|
329
|
+
else
|
330
|
+
bindings.last[:nested_bindings] = find_bindings(o, true)
|
331
|
+
# reject so children aren't traversed
|
332
|
+
throw :reject
|
333
|
+
end
|
389
334
|
}
|
390
335
|
|
391
|
-
#
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
# b[:nested_scopes] = nested
|
401
|
-
# }
|
336
|
+
# find unscoped props
|
337
|
+
bindings.unshift({
|
338
|
+
:scope => nil,
|
339
|
+
:path => [],
|
340
|
+
:props => find_props(doc),
|
341
|
+
:nested_bindings => {}
|
342
|
+
})
|
343
|
+
|
402
344
|
return bindings
|
403
345
|
end
|
404
346
|
|
405
|
-
def
|
406
|
-
|
407
|
-
|
408
|
-
|
347
|
+
def find_props(o)
|
348
|
+
props = []
|
349
|
+
breadth_first(o) {|so|
|
350
|
+
# don't go into deeper scopes
|
351
|
+
throw :reject if so != o && so[Config::Presenter.scope_attribute]
|
409
352
|
|
410
|
-
|
411
|
-
|
412
|
-
if (child_path - binding[:path]).empty?
|
413
|
-
# update paths relative to child
|
414
|
-
dup = Marshal.load(Marshal.dump(binding))
|
415
|
-
|
416
|
-
[dup].concat(dup[:props]).each{|p|
|
417
|
-
p[:path] = p[:path][child_path_len..-1]
|
418
|
-
}
|
419
|
-
|
420
|
-
child_bindings << dup
|
421
|
-
end
|
353
|
+
next unless prop = so[Config::Presenter.prop_attribute]
|
354
|
+
props << {:prop => prop.to_sym, :path => path_to(so)}
|
422
355
|
}
|
423
356
|
|
424
|
-
|
425
|
-
end
|
426
|
-
|
427
|
-
def breadth_first(doc)
|
428
|
-
queue = [doc]
|
429
|
-
until queue.empty?
|
430
|
-
node = queue.shift
|
431
|
-
catch(:reject) {
|
432
|
-
yield node
|
433
|
-
queue.concat(node.children)
|
434
|
-
}
|
435
|
-
end
|
357
|
+
return props
|
436
358
|
end
|
437
359
|
|
438
|
-
|
439
|
-
|
360
|
+
# returns a new binding set that takes into account the starting point of `path`
|
361
|
+
def update_binding_paths_from_path(bindings, path)
|
362
|
+
return bindings.collect { |binding|
|
363
|
+
dup_binding = binding.dup
|
364
|
+
dup_binding[:path] = dup_binding[:path][path.length..-1] || []
|
440
365
|
|
441
|
-
|
366
|
+
dup_binding[:props] = dup_binding[:props].collect {|prop|
|
367
|
+
dup_prop = prop.dup
|
368
|
+
dup_prop[:path] = dup_prop[:path][path.length..-1]
|
369
|
+
dup_prop
|
370
|
+
}
|
442
371
|
|
443
|
-
|
444
|
-
# since ancestors goes all the way to doc root, stop when we get to the level of @doc
|
445
|
-
break if a.children.include?(@doc)
|
372
|
+
dup_binding[:nested_bindings] = update_binding_paths_from_path(dup_binding[:nested_bindings], path)
|
446
373
|
|
447
|
-
|
448
|
-
child = a
|
374
|
+
dup_binding
|
449
375
|
}
|
450
|
-
|
451
|
-
return path
|
452
376
|
end
|
453
377
|
|
454
|
-
def
|
455
|
-
|
378
|
+
def update_binding_offset_at_path(offset, path)
|
379
|
+
# update binding paths for bindings we're iterating on
|
380
|
+
self.bindings.each {|binding|
|
381
|
+
next unless self.path_within_path?(binding[:path], path)
|
456
382
|
|
457
|
-
|
458
|
-
return o if path.empty?
|
383
|
+
binding[:path][0] += offset if binding[:path][0]
|
459
384
|
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
else
|
464
|
-
break
|
465
|
-
end
|
385
|
+
binding[:props].each { |prop|
|
386
|
+
prop[:path][0] += offset if prop[:path][0]
|
387
|
+
}
|
466
388
|
}
|
467
|
-
|
468
|
-
return o
|
469
389
|
end
|
470
390
|
|
471
|
-
def
|
472
|
-
|
391
|
+
def refind_significant_nodes
|
392
|
+
self.bindings(true)
|
393
|
+
|
394
|
+
@related_views.each {|v|
|
395
|
+
v.refind_significant_nodes
|
396
|
+
}
|
473
397
|
end
|
474
398
|
|
475
|
-
def bind_data_to_scope(data,
|
399
|
+
def bind_data_to_scope(data, scope_info, bindings = {})
|
476
400
|
return unless data
|
477
401
|
|
402
|
+
scope = scope_info[:scope]
|
403
|
+
|
478
404
|
# handle root binding
|
479
|
-
if
|
480
|
-
|
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)
|
481
407
|
end
|
482
408
|
|
483
|
-
|
484
|
-
|
485
|
-
|
409
|
+
scope_info[:props].each {|prop_info|
|
410
|
+
catch(:unbound) {
|
411
|
+
prop = prop_info[:prop]
|
412
|
+
|
413
|
+
self.handle_unbound_data(scope, prop) unless data_has_prop?(data, prop) || Pakyow.app.presenter.binder.has_prop?(prop, scope, bindings)
|
414
|
+
value = Pakyow.app.presenter.binder.value_for_prop(prop, scope, data, bindings)
|
486
415
|
|
487
|
-
|
416
|
+
doc = doc_from_path(prop_info[:path])
|
488
417
|
|
489
|
-
|
490
|
-
|
418
|
+
# handle form field
|
419
|
+
self.bind_to_form_field(doc, scope, prop, value, data) if View.form_field?(doc.name)
|
491
420
|
|
492
|
-
|
493
|
-
|
421
|
+
# bind attributes or value
|
422
|
+
value.is_a?(Hash) ? self.bind_attributes_to_doc(value, doc) : self.bind_value_to_doc(value, doc)
|
423
|
+
}
|
494
424
|
}
|
495
425
|
end
|
496
426
|
|
427
|
+
def data_has_prop?(data, prop)
|
428
|
+
(data.is_a?(Hash) && (data.key?(prop) || data.key?(prop.to_s))) || (!data.is_a?(Hash) && data.class.method_defined?(prop))
|
429
|
+
end
|
430
|
+
|
497
431
|
def bind_value_to_doc(value, doc)
|
498
|
-
|
432
|
+
value = String(value)
|
499
433
|
|
500
434
|
tag = doc.name
|
501
435
|
return if View.tag_without_value?(tag)
|
502
|
-
View.self_closing_tag?(tag)
|
436
|
+
if View.self_closing_tag?(tag)
|
437
|
+
# don't override value if set
|
438
|
+
if !doc['value'] || doc['value'].empty?
|
439
|
+
doc['value'] = value
|
440
|
+
end
|
441
|
+
else
|
442
|
+
doc.inner_html = value
|
443
|
+
end
|
503
444
|
end
|
504
445
|
|
505
446
|
def bind_attributes_to_doc(attrs, doc)
|
506
447
|
attrs.each do |attr, v|
|
507
|
-
|
448
|
+
case attr
|
449
|
+
when :content
|
508
450
|
v = v.call(doc.inner_html) if v.is_a?(Proc)
|
509
451
|
bind_value_to_doc(v, doc)
|
510
452
|
next
|
453
|
+
when :view
|
454
|
+
v.call(self)
|
455
|
+
next
|
511
456
|
end
|
512
457
|
|
513
458
|
attr = attr.to_s
|
514
|
-
|
515
|
-
v
|
459
|
+
attrs = Attributes.new(doc)
|
460
|
+
v = v.call(attrs.send(attr)) if v.is_a?(Proc)
|
461
|
+
v.nil? ? doc.remove_attribute(attr) : attrs.send(:"#{attr}=", v)
|
516
462
|
end
|
517
463
|
end
|
518
464
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
465
|
+
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
|
525
472
|
|
526
473
|
# special binding for checkboxes and radio buttons
|
527
474
|
if doc.name == 'input' && (doc[:type] == 'checkbox' || doc[:type] == 'radio')
|
@@ -531,50 +478,58 @@ module Pakyow
|
|
531
478
|
doc.delete('checked')
|
532
479
|
end
|
533
480
|
|
534
|
-
# coerce to string since booleans are often used
|
481
|
+
# coerce to string since booleans are often used
|
535
482
|
# and fail when binding to a view
|
536
483
|
value = value.to_s
|
537
484
|
# special binding for selects
|
538
|
-
elsif doc.name == 'select'
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
485
|
+
elsif doc.name == 'select'
|
486
|
+
if options = Pakyow.app.presenter.binder.options_for_prop(prop, scope, bindable)
|
487
|
+
option_nodes = Nokogiri::HTML::DocumentFragment.parse ""
|
488
|
+
Nokogiri::HTML::Builder.with(option_nodes) do |h|
|
489
|
+
until options.length == 0
|
490
|
+
catch :optgroup do
|
491
|
+
o = options.first
|
544
492
|
|
545
493
|
# an array containing value/content
|
546
494
|
if o.is_a?(Array)
|
547
495
|
h.option o[1], :value => o[0]
|
548
|
-
options.
|
496
|
+
options.shift
|
549
497
|
# likely an object (e.g. string); start a group
|
550
498
|
else
|
551
499
|
h.optgroup(:label => o) {
|
552
|
-
options.
|
500
|
+
options.shift
|
553
501
|
|
554
|
-
options[
|
502
|
+
options[0..-1].each_with_index { |o2,i2|
|
555
503
|
# starting a new group
|
556
504
|
throw :optgroup if !o2.is_a?(Array)
|
557
505
|
|
558
506
|
h.option o2[1], :value => o2[0]
|
559
|
-
options.
|
507
|
+
options.shift
|
560
508
|
}
|
561
509
|
}
|
562
510
|
end
|
563
|
-
|
564
|
-
}
|
511
|
+
end
|
565
512
|
end
|
566
|
-
end
|
567
|
-
end
|
513
|
+
end
|
568
514
|
|
569
|
-
|
570
|
-
|
515
|
+
# remove existing options
|
516
|
+
doc.children.remove
|
571
517
|
|
572
|
-
|
573
|
-
|
574
|
-
|
518
|
+
# add generated options
|
519
|
+
doc.add_child(option_nodes)
|
520
|
+
end
|
521
|
+
|
522
|
+
# select appropriate option
|
523
|
+
if o = doc.css('option[value="' + value.to_s + '"]').first
|
524
|
+
o[:selected] = 'selected'
|
525
|
+
end
|
575
526
|
end
|
576
527
|
end
|
577
528
|
|
529
|
+
def handle_unbound_data(scope, prop)
|
530
|
+
Pakyow.logger.warn("Unbound data for #{scope}[#{prop}]")
|
531
|
+
throw :unbound
|
532
|
+
end
|
578
533
|
end
|
579
534
|
end
|
580
535
|
end
|