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.
- data/pakyow-presenter/lib/presenter/attributes.rb +92 -0
- data/pakyow-presenter/lib/presenter/base.rb +3 -1
- data/pakyow-presenter/lib/presenter/binder.rb +6 -23
- data/pakyow-presenter/lib/presenter/bindings.rb +103 -0
- data/pakyow-presenter/lib/presenter/configuration/presenter.rb +16 -3
- data/pakyow-presenter/lib/presenter/helpers.rb +8 -0
- data/pakyow-presenter/lib/presenter/presenter.rb +102 -76
- data/pakyow-presenter/lib/presenter/view.rb +441 -244
- data/pakyow-presenter/lib/presenter/view_collection.rb +187 -0
- data/pakyow-presenter/lib/presenter/view_lookup_store.rb +10 -6
- metadata +59 -76
- data/pakyow-presenter/lib/presenter/views.rb +0 -112
@@ -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
|
20
|
+
attr_accessor :bindable
|
21
21
|
|
22
|
-
def initialize(bindable
|
22
|
+
def initialize(bindable)
|
23
23
|
self.bindable = bindable
|
24
|
-
|
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, :
|
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
|
18
|
-
@
|
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
|
@@ -1,10 +1,37 @@
|
|
1
1
|
module Pakyow
|
2
2
|
module Presenter
|
3
3
|
class Presenter < PresenterBase
|
4
|
-
|
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 =
|
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
|
58
|
-
|
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
|
66
|
-
@
|
113
|
+
def root
|
114
|
+
@is_compiled = false
|
115
|
+
@root ||= View.root_at_path(@root_path)
|
67
116
|
end
|
68
117
|
|
69
|
-
def
|
70
|
-
@
|
71
|
-
@
|
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
|
79
|
-
|
80
|
-
@
|
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
|
85
|
-
@root_path
|
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
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
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 =
|
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
|
-
@
|
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(
|
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
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|