pakyow-presenter 0.8rc1 → 0.8.rc4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,23 +1,10 @@
1
1
  module Pakyow
2
2
  module Presenter
3
3
  class View
4
- class << self
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
- def view_path(dvp, dirv=false)
18
- self.default_view_path = dvp
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
- v = self.class.new(@doc.dup)
50
- v.scoped_as = self.scoped_as
51
- v
52
- end
53
-
54
- def initialize(arg=nil, is_root_view=false)
55
- arg = self.class.default_view_path if arg.nil? && self.class.default_view_path
56
- is_root_view = self.class.default_is_root_view if arg.nil? && self.class.default_is_root_view
57
-
58
- if arg.is_a?(Nokogiri::XML::Element) || arg.is_a?(Nokogiri::XML::Document) || arg.is_a?(Nokogiri::HTML::DocumentFragment)
59
- @doc = arg
60
- elsif arg.is_a?(Pakyow::Presenter::ViewCollection)
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
- raise ArgumentError, "No View for you! Come back, one year."
39
+ @doc = Nokogiri::HTML.fragment(processed)
78
40
  end
79
41
  end
80
42
 
81
- def compile(view_path)
82
- return unless view_info = self.class.view_store.view_info(view_path)
83
- self.populate_view(self, view_info[:views])
43
+ def self.from_doc(doc)
44
+ view = self.new
45
+ view.doc = doc
46
+ return view
84
47
  end
85
48
 
86
- def parse_content(content, format)
87
- begin
88
- Pakyow.app.presenter.parser_store[format].call(content)
89
- rescue
90
- Log.warn("No parser defined for extension #{format}") unless format.to_sym == :html
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(*args)
130
- if args.empty?
131
- return Attributes.new(self)
76
+ def attributes(attrs = {})
77
+ if attrs.empty?
78
+ return Attributes.new(self.doc)
132
79
  else
133
- #TODO mass assign attributes (if we still want to do this)
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 content
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
- alias :html :content
183
-
184
- def content=(content)
185
- self.doc.inner_html = Nokogiri::HTML.fragment(content.to_s)
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
- v.bindings = self.bindings_for_child_view(v)
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
- self.bindings.each {|binding|
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 = [data] unless data.instance_of?(Array)
258
- block.call(self, data[0])
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 = [data] unless data.instance_of?(Array)
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.new(d_v)
277
- v.bindings = self.bindings
278
- #TODO set view scope
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 = nil, &block)
302
- scope = self.bindings.first
275
+ def bind(data, bindings = {}, &block)
276
+ scope_info = self.bindings.first
303
277
 
304
- binder = View.binder_for_scope(scope[:scope], data)
305
- binder.merge(bindings)
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 = nil, &block)
287
+ def apply(data, bindings = {}, &block)
317
288
  views = self.match(data).bind(data, bindings, &block)
318
289
  end
319
290
 
320
- def container(name)
321
- matches = self.containers.select{|c| c[:name].to_sym == name.to_sym}
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(@doc) {|o|
376
- next unless scope = o[Configuration::Presenter.scope_attribute]
377
-
378
- # find props
379
- props = []
380
- breadth_first(o) {|so|
381
- # don't go into deeper scopes
382
- throw :reject if so != o && so[Configuration::Presenter.scope_attribute]
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
- bindings << {:scope => scope.to_sym, :path => path_to(o), :props => props}
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
- # determine nestedness (currently unused; leaving in case needed)
392
- # bindings.each {|b|
393
- # nested = []
394
- # bindings.each {|b2|
395
- # b_doc = doc_from_path(b[:path])
396
- # b2_doc = doc_from_path(b2[:path])
397
- # nested << b2 if b2_doc.ancestors.include? b_doc
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 bindings_for_child_view(child)
406
- child_path = self.path_to(child.doc)
407
- child_path_len = child_path.length
408
- child_bindings = []
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
- self.bindings.each {|binding|
411
- # we want paths within the child path
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
- child_bindings
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
- def path_to(child)
439
- path = []
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
- return path if child == @doc
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
- child.ancestors.each {|a|
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
- path.unshift(a.children.index(child))
448
- child = a
374
+ dup_binding
449
375
  }
450
-
451
- return path
452
376
  end
453
377
 
454
- def doc_from_path(path)
455
- o = @doc
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
- # if path is empty we're at self
458
- return o if path.empty?
383
+ binding[:path][0] += offset if binding[:path][0]
459
384
 
460
- path.each {|i|
461
- if child = o.children[i]
462
- o = child
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 view_from_path(path)
472
- View.new(doc_from_path(path))
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, scope, binder = nil)
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 binder && v = binder.value_for_prop(:_root)
480
- v.is_a?(Hash) ? self.bind_attributes_to_doc(v, self.doc) : self.bind_value_to_doc(v, self.doc)
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
- scope[:props].each {|p|
484
- k = p[:prop]
485
- v = binder ? binder.value_for_prop(k) : data[k]
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
- doc = doc_from_path(p[:path])
416
+ doc = doc_from_path(prop_info[:path])
488
417
 
489
- # handle form field
490
- self.bind_to_form_field(doc, scope, k, v, binder) if View.form_field?(doc.name)
418
+ # handle form field
419
+ self.bind_to_form_field(doc, scope, prop, value, data) if View.form_field?(doc.name)
491
420
 
492
- # bind attributes or value
493
- v.is_a?(Hash) ? self.bind_attributes_to_doc(v, doc) : self.bind_value_to_doc(v, doc)
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
- return unless value
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) ? doc['value'] = value : doc.inner_html = value
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
- if attr == :content
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
- v = v.call(doc[attr]) if v.is_a?(Proc)
515
- v.nil? ? doc.remove_attribute(attr) : doc[attr] = v.to_s
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
- #TODO refactor to use new options_for
520
- def bind_to_form_field(doc, scope, prop, value, binder)
521
- return unless !doc['name'] || doc['name'].empty?
522
-
523
- # set name on form element
524
- doc['name'] = "#{scope[:scope]}[#{prop}]"
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' && binder && options = binder.options_for_prop(prop)
539
- option_nodes = Nokogiri::HTML::DocumentFragment.parse ""
540
- Nokogiri::HTML::Builder.with(option_nodes) do |h|
541
- until options.length == 0
542
- catch :optgroup do
543
- options.each_with_index { |o,i|
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.delete_at(i)
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.delete_at(i)
500
+ options.shift
553
501
 
554
- options[i..-1].each_with_index { |o2,i2|
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.delete_at(i)
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
- doc.add_child(option_nodes)
570
- end
515
+ # remove existing options
516
+ doc.children.remove
571
517
 
572
- # select appropriate option
573
- if o = doc.css('option[value="' + value.to_s + '"]').first
574
- o[:selected] = 'selected'
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