pakyow-presenter 0.7.2 → 0.8rc1

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