pakyow-presenter 0.7.2 → 0.8rc1

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.
@@ -0,0 +1,92 @@
1
+ module Pakyow
2
+ module Presenter
3
+ class Attributes
4
+ @@types = {
5
+ :boolean => [:selected, :checked, :disabled, :readonly, :multiple],
6
+ :multiple => [:class]
7
+ }
8
+
9
+ def initialize(view)
10
+ @view = view
11
+ end
12
+
13
+ def method_missing(method, *args)
14
+ attribute = method.to_s
15
+
16
+ if method.to_s.include?('=')
17
+ attribute = attribute.gsub('=', '')
18
+ value = args[0]
19
+
20
+ self.set_attribute(attribute, value)
21
+ else
22
+ self.get_attribute(attribute)
23
+ end
24
+ end
25
+
26
+ def class(*args)
27
+ method_missing(:class, *args)
28
+ end
29
+
30
+ def id(*args)
31
+ method_missing(:id, *args)
32
+ end
33
+
34
+ protected
35
+
36
+ def set_attribute(attribute, value)
37
+ type = self.type_of_attribute(attribute)
38
+
39
+ value = value.call(self.deconstruct_attribute_value_of_type(@view.doc[attribute], type)) if value.is_a? Proc
40
+ value = construct_attribute_value_of_type(value, type, attribute)
41
+
42
+ if value.nil?
43
+ @view.doc.remove_attribute(attribute)
44
+ else
45
+ @view.doc[attribute] = value
46
+ end
47
+ end
48
+
49
+ def get_attribute(attribute)
50
+ self.deconstruct_attribute_value_of_type(@view.doc[attribute], self.type_of_attribute(attribute))
51
+ end
52
+
53
+ def type_of_attribute(attribute)
54
+ attribute = attribute.to_sym
55
+
56
+ return :boolean if @@types[:boolean].include?(attribute)
57
+ return :multiple if @@types[:multiple].include?(attribute)
58
+ return :single
59
+ end
60
+
61
+ def deconstruct_attribute_value_of_type(value, type)
62
+ return value if type == :single
63
+ return value ? value.split(' ') : [] if type == :multiple
64
+ return !value.nil? if type == :boolean
65
+ end
66
+
67
+ def construct_attribute_value_of_type(value, type, attribute)
68
+ return value if type == :single
69
+ return value.join(' ') if type == :multiple
70
+
71
+ # pp value
72
+ return value ? attribute : nil if type == :boolean
73
+ end
74
+ end
75
+
76
+ class AttributesCollection
77
+ include Enumerable
78
+
79
+ def initialize
80
+ @attributes = []
81
+ end
82
+
83
+ def <<(attributes)
84
+ @attributes << attributes
85
+ end
86
+
87
+ def method_missing(method, *args)
88
+ @attributes.each{|a| a.send(method, *args)}
89
+ end
90
+ end
91
+ end
92
+ end
@@ -1,9 +1,11 @@
1
1
  require 'presenter/view_lookup_store'
2
2
  require 'presenter/view'
3
3
  require 'presenter/lazy_view'
4
+ require 'presenter/view_collection'
4
5
  require 'presenter/binder'
5
- require 'presenter/views'
6
6
  require 'presenter/view_context'
7
+ require 'presenter/attributes'
8
+ require 'presenter/bindings'
7
9
 
8
10
  module Pakyow
9
11
  module Presenter
@@ -17,11 +17,14 @@ module Pakyow
17
17
  end
18
18
  end
19
19
 
20
- attr_accessor :bindable, :object
20
+ attr_accessor :bindable
21
21
 
22
- def initialize(bindable, object)
22
+ def initialize(bindable)
23
23
  self.bindable = bindable
24
- self.object = object
24
+ end
25
+
26
+ def value_for_prop(prop)
27
+ self.class.method_defined?(prop) ? self.send(prop) : bindable[prop]
25
28
  end
26
29
 
27
30
  def fetch_options_for(attribute)
@@ -35,26 +38,6 @@ module Pakyow
35
38
  end
36
39
  end
37
40
  end
38
-
39
- def action
40
- unless routes = Pakyow.app.restful_routes[bindable.class.name.to_sym]
41
- Log.warn "Attempting to bind object to #{bindable.class.name.downcase}[action] but could not find restful routes for #{bindable.class.name}."
42
- return {}
43
- end
44
-
45
- if id = bindable.id
46
- self.object.add_child('<input type="hidden" name="_method" value="put">')
47
-
48
-
49
- action = routes[:update].gsub(':id', id.to_s)
50
- method = "post"
51
- else
52
- action = routes[:create]
53
- method = "post"
54
- end
55
-
56
- return { :action => File.join('/', action), :method => method }
57
- end
58
41
  end
59
42
  end
60
43
  end
@@ -0,0 +1,103 @@
1
+ module Pakyow
2
+ module Presenter
3
+ class Bindings
4
+ attr_accessor :bindable
5
+ attr_reader :bindings, :binding_options, :mapping
6
+
7
+ include GeneralHelpers
8
+
9
+ def fn(name, &block)
10
+ @funcs[name] = block and return if block
11
+ @funcs[name]
12
+ end
13
+
14
+ def self.for(block)
15
+ Bindings.new(block)
16
+ end
17
+
18
+ def initialize(block = nil)
19
+ @funcs = {}
20
+ @bindings = {}
21
+ @binding_options = {}
22
+ self.instance_exec(&block) if block
23
+ end
24
+
25
+ def binding(name, func = nil, &block)
26
+ @bindings[name] = func || block
27
+ end
28
+
29
+ def options(name, func = nil, &block)
30
+ @binding_options[name] = func || block
31
+ end
32
+
33
+ def options_for_prop(prop)
34
+ if fn = @binding_options[prop]
35
+ fn.call
36
+ end
37
+ end
38
+
39
+ def value_for_prop(prop)
40
+ # mapping always overrides fns for a scope
41
+ if @mapping
42
+ @binder ||= Kernel.const_get(@mapping).new(@bindable)
43
+ @binder.value_for_prop(prop)
44
+ elsif binding = @bindings[prop]
45
+ case binding.arity
46
+ when 0
47
+ self.instance_exec(&binding)
48
+ when 1
49
+ self.instance_exec(@bindable[prop], &binding)
50
+ end
51
+ else
52
+ # default
53
+ @bindable[prop]
54
+ end
55
+ end
56
+
57
+ # Merges a Bindings instance or Hash of bindings to self
58
+ def merge(bindings)
59
+ if bindings.is_a?(Hash)
60
+ @bindings = @bindings.merge(bindings)
61
+ elsif bindings
62
+ @bindings = bindings.bindings.merge(@bindings)
63
+ @mapping = bindings.mapping if bindings.mapping
64
+ @binding_options = bindings.binding_options if bindings.binding_options && !bindings.binding_options.empty?
65
+ end
66
+
67
+ self
68
+ end
69
+
70
+ def map(klass)
71
+ #TODO make sure klass is subclass of Binder
72
+ @mapping = klass
73
+ self
74
+
75
+ # klass must be a subclass of Binder
76
+ # sets flag on self to indicate it's a mapping
77
+ # value_for_prop checks flag, if set call Binder (just like 0.7)
78
+ end
79
+
80
+ def restful(route_group)
81
+ self.binding(:action) {
82
+ routes = router.group(route_group)
83
+ return_data = {}
84
+
85
+ if id = bindable[:id]
86
+ return_data[:content] = lambda { |content|
87
+ '<input type="hidden" name="_method" value="put">' + content
88
+ }
89
+
90
+ action = routes.populate(:update, :id => id)
91
+ else
92
+ action = routes.populate(:create)
93
+ end
94
+
95
+ return_data[:action] = action
96
+ return_data[:method] = 'post'
97
+ return_data
98
+ }
99
+ end
100
+ end
101
+ end
102
+ end
103
+
@@ -2,7 +2,8 @@ module Pakyow
2
2
  module Configuration
3
3
  class Presenter
4
4
  class << self
5
- attr_accessor :view_caching, :javascripts, :stylesheets, :view_dir, :default_view
5
+ attr_accessor :view_caching, :javascripts, :stylesheets, :view_stores, :default_view,
6
+ :scope_attribute, :prop_attribute, :container_attribute
6
7
 
7
8
  # Location of javascripts
8
9
  def javascripts
@@ -14,8 +15,8 @@ module Pakyow
14
15
  @stylesheets || '/stylesheets'
15
16
  end
16
17
 
17
- def view_dir
18
- @view_dir || "#{Configuration::Base.app.root}/app/views"
18
+ def view_stores
19
+ @view_stores ||= {:default => "#{Configuration::Base.app.root}/views"}
19
20
  end
20
21
 
21
22
  def view_caching
@@ -26,6 +27,18 @@ module Pakyow
26
27
  @default_view || "pakyow.html"
27
28
  end
28
29
 
30
+ def scope_attribute
31
+ @scope_attribute || "data-scope"
32
+ end
33
+
34
+ def prop_attribute
35
+ @prop_attribute || "data-prop"
36
+ end
37
+
38
+ def container_attribute
39
+ @container_attribute || "data-container"
40
+ end
41
+
29
42
  end
30
43
  end
31
44
  end
@@ -4,5 +4,13 @@ module Pakyow
4
4
  Pakyow.app.presenter.current_context = self
5
5
  Pakyow.app.presenter
6
6
  end
7
+
8
+ def bindings(name)
9
+ presenter.bindings(name).bindings
10
+ end
11
+
12
+ def view
13
+ self.presenter.view
14
+ end
7
15
  end
8
16
  end
@@ -1,10 +1,37 @@
1
1
  module Pakyow
2
2
  module Presenter
3
3
  class Presenter < PresenterBase
4
- attr_accessor :current_context
4
+ class << self
5
+ attr_accessor :proc
6
+ end
7
+
8
+ attr_accessor :current_context, :parser_store, :view_store
5
9
 
6
10
  def initialize
7
- reset_state()
11
+ reset_state
12
+ reset_bindings
13
+ end
14
+
15
+ def scope(name, set = :default, &block)
16
+ @bindings[set] ||= {}
17
+
18
+ bs = Bindings.for(block)
19
+ @bindings[set][name] = bs
20
+ bs
21
+ end
22
+
23
+ def bindings(scope)
24
+ #TODO think about merging on launch instead
25
+ @bindings.inject(Bindings.new) { |bs, b| bs.merge(b[1][scope]) }
26
+ end
27
+
28
+ def reset_bindings(set = :default)
29
+ @bindings ||= {}
30
+ @bindings[set] = {}
31
+ end
32
+
33
+ def current_view_lookup_store
34
+ @view_stores[self.view_store]
8
35
  end
9
36
 
10
37
  #
@@ -13,20 +40,38 @@ module Pakyow
13
40
 
14
41
  def load
15
42
  load_views
43
+
44
+ self.reset_bindings
45
+ self.instance_eval(&Presenter.proc) if Presenter.proc
16
46
  end
17
47
 
18
48
  def prepare_for_request(request)
19
49
  reset_state()
20
50
  @request = request
51
+
52
+ if @request && @request.route_path && !@request.route_path.is_a?(Regexp) && @request.route_path.index(':')
53
+ @view_path = StringUtils.remove_route_vars(@request.route_path)
54
+ else
55
+ @view_path = @request && @request.working_path
56
+ end
57
+ @root_path = self.current_view_lookup_store.root_path(@view_path)
58
+ end
59
+
60
+ def reset
61
+ @request = nil
62
+ reset_state
21
63
  end
22
64
 
23
65
  def presented?
66
+ #TODO the right thing to do?
67
+ self.ensure_root_view_built
68
+
24
69
  @presented
25
70
  end
26
71
 
27
72
  def content
28
73
  return unless view
29
- request_container = @request.params[:_container]
74
+ request_container = @request.params[:_container] if @request
30
75
  return view.to_html(request_container) if request_container
31
76
  view.to_html(@container_name)
32
77
  end
@@ -40,7 +85,7 @@ module Pakyow
40
85
  #
41
86
 
42
87
  def view_for_path(abstract_path, is_root_view=false, klass=View)
43
- real_path = @view_lookup_store.real_path(abstract_path)
88
+ real_path = self.current_view_lookup_store.real_path(abstract_path)
44
89
  klass.new(real_path, is_root_view)
45
90
  end
46
91
 
@@ -54,74 +99,48 @@ module Pakyow
54
99
  @root_view
55
100
  end
56
101
 
57
- def set_view(view)
58
- @root_view = View.new(view)
102
+ def view=(v)
103
+ # TODO: Why is it important to dup here?
104
+ @root_view = View.new(v)
59
105
  @root_view_is_built = true
60
106
  @presented = true
107
+
108
+ # reset paths
61
109
  @view_path = nil
62
110
  @root_path = nil
63
111
  end
64
112
 
65
- def limit_to_container(id)
66
- @container_name = id
113
+ def root
114
+ @is_compiled = false
115
+ @root ||= View.root_at_path(@root_path)
67
116
  end
68
117
 
69
- def use_view_path(path)
70
- @view_path = path
71
- @root_view_is_built = false
118
+ def root=(v)
119
+ @is_compiled = false
120
+ @root = v
121
+ end
122
+
123
+ def limit_to_container(id)
124
+ @container_name = id
72
125
  end
73
126
 
74
127
  def view_path
75
128
  @view_path
76
129
  end
77
130
 
78
- def use_root_view_file(abstract_view_file)
79
- real_path = @view_lookup_store.real_path(abstract_view_file)
80
- @root_path = real_path
81
- @root_view_is_built = false
131
+ def view_path=(path)
132
+ @is_compiled = false
133
+ @view_path = path
82
134
  end
83
135
 
84
- def use_root_view_at_view_path(abstract_view_dir)
85
- @root_path = @view_lookup_store.view_info(abstract_view_dir)[:root_view]
86
- @root_view_is_built = false
87
- end
88
-
89
- # This is for creating views from within a controller using the route based lookup mechanism
90
- def view_for_view_path(v_p, name, deep = false)
91
- v = nil
92
- view_info = @view_lookup_store.view_info(v_p)
93
- vpath = view_info[:views][name] if view_info
94
- v = View.new(vpath) if vpath
95
- if v && deep
96
- populate_view(v, view_info[:views])
97
- end
98
- v
99
- end
100
-
101
- # This is also for creating views from within a controller using the route based lookup mechanism.
102
- # This method takes either a dir or file path and builds either a root_view or view, respectively.
103
- def view_for_full_view_path(f_v_p, deep = false)
104
- v = nil
105
- real_path_info = @view_lookup_store.real_path_info(f_v_p)
106
- if real_path_info
107
- if real_path_info[:file_or_dir] == :file
108
- v = View.new(real_path_info[:real_path])
109
- elsif real_path_info[:file_or_dir] == :dir
110
- root_view = @view_lookup_store.view_info(f_v_p)[:root_view]
111
- v = View.new(root_view)
112
- if v && deep
113
- populate_view(v, @view_lookup_store.view_info(f_v_p)[:views])
114
- end
115
- end
116
- end
117
- v
136
+ def root_path
137
+ @root_path
118
138
  end
119
139
 
120
- def populate_view_for_view_path(view, v_p)
121
- return view unless view_info = @view_lookup_store.view_info(v_p)
122
- views = view_info[:views]
123
- populate_view(view, views)
124
- view
140
+ def root_path=(abstract_path)
141
+ @is_compiled = false
142
+ @root = nil
143
+ @root_path = abstract_path
125
144
  end
126
145
 
127
146
  # Call as part of View DSL for DOM manipulation
@@ -145,6 +164,7 @@ module Pakyow
145
164
  #
146
165
 
147
166
  def reset_state
167
+ @view_store = :default
148
168
  @presented = false
149
169
  @root_path = nil
150
170
  @root_view_is_built = false
@@ -156,18 +176,9 @@ module Pakyow
156
176
  def build_root_view
157
177
  @root_view_is_built = true
158
178
 
159
- if @view_path
160
- v_p = @view_path
161
- elsif @request && @request.restful
162
- v_p = restful_view_path(@request.restful)
163
- elsif @request && @request.route_spec && !@request.route_spec.is_a?(Regexp) && @request.route_spec.index(':')
164
- v_p = StringUtils.remove_route_vars(@request.route_spec)
165
- else
166
- v_p = @request && @request.working_path
167
- end
168
- return unless v_p
179
+ return unless v_p = @view_path
169
180
 
170
- return unless view_info = @view_lookup_store.view_info(v_p)
181
+ return unless view_info = self.current_view_lookup_store.view_info(v_p)
171
182
  @root_path ||= view_info[:root_view]
172
183
 
173
184
  if Configuration::Base.presenter.view_caching
@@ -191,9 +202,13 @@ module Pakyow
191
202
  end
192
203
 
193
204
  def load_views
194
- @view_lookup_store = ViewLookupStore.new("#{Configuration::Presenter.view_dir}")
205
+ @view_stores = {}
206
+ Configuration::Presenter.view_stores.each_pair {|name, path|
207
+ @view_stores[name] = ViewLookupStore.new(path)
208
+ }
209
+
195
210
  if Configuration::Base.presenter.view_caching then
196
- @populated_root_view_cache = build_root_view_cache(@view_lookup_store.view_info)
211
+ @populated_root_view_cache = build_root_view_cache(self.current_view_lookup_store.view_info)
197
212
  end
198
213
  end
199
214
 
@@ -211,19 +226,30 @@ module Pakyow
211
226
  # populates the top_view using view_store data by recursively building
212
227
  # and substituting in child views named in the structure
213
228
  def populate_view(top_view, views)
214
- containers = top_view.elements_with_ids
215
- containers.each {|e|
216
- name = e.attr("id")
217
- path = views[name]
218
- if path
219
- v = populate_view(View.new(path), views)
220
- top_view.reset_container(name) # TODO revisit how this is implemented; assumes all LazyViews are root views
221
- top_view.add_content_to_container(v, name)
222
- end
229
+ top_view.containers.each {|e|
230
+ next unless path = views[e[:name]]
231
+
232
+ v = populate_view(View.new(path), views)
233
+ self.reset_container(e[:doc])
234
+ self.add_content_to_container(v, e[:doc])
223
235
  }
224
236
  top_view
225
237
  end
226
238
 
239
+ def parser(format, &block)
240
+ @parser_store ||= {}
241
+ @parser_store[format.to_sym] = block
242
+ end
243
+
244
+ def add_content_to_container(content, container)
245
+ content = content.doc unless content.class == String || content.class == Nokogiri::HTML::DocumentFragment || content.class == Nokogiri::XML::Element
246
+ container.add_child(content)
247
+ end
248
+
249
+ def reset_container(container)
250
+ container.inner_html = ''
251
+ end
252
+
227
253
  end
228
254
  end
229
255
  end