hanami-view 0.0.0 → 0.6.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +96 -0
- data/LICENSE.md +22 -0
- data/README.md +826 -7
- data/hanami-view.gemspec +17 -12
- data/lib/hanami-view.rb +1 -0
- data/lib/hanami/layout.rb +142 -0
- data/lib/hanami/presenter.rb +126 -0
- data/lib/hanami/view.rb +259 -2
- data/lib/hanami/view/configuration.rb +464 -0
- data/lib/hanami/view/dsl.rb +346 -0
- data/lib/hanami/view/errors.rb +47 -0
- data/lib/hanami/view/escape.rb +180 -0
- data/lib/hanami/view/inheritable.rb +54 -0
- data/lib/hanami/view/rendering.rb +265 -0
- data/lib/hanami/view/rendering/layout_finder.rb +128 -0
- data/lib/hanami/view/rendering/layout_registry.rb +63 -0
- data/lib/hanami/view/rendering/layout_scope.rb +240 -0
- data/lib/hanami/view/rendering/null_layout.rb +52 -0
- data/lib/hanami/view/rendering/null_template.rb +83 -0
- data/lib/hanami/view/rendering/partial.rb +29 -0
- data/lib/hanami/view/rendering/partial_finder.rb +73 -0
- data/lib/hanami/view/rendering/registry.rb +128 -0
- data/lib/hanami/view/rendering/scope.rb +88 -0
- data/lib/hanami/view/rendering/template.rb +67 -0
- data/lib/hanami/view/rendering/template_finder.rb +53 -0
- data/lib/hanami/view/rendering/template_name.rb +37 -0
- data/lib/hanami/view/rendering/templates_finder.rb +129 -0
- data/lib/hanami/view/rendering/view_finder.rb +37 -0
- data/lib/hanami/view/template.rb +43 -0
- data/lib/hanami/view/version.rb +4 -1
- metadata +91 -16
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/Rakefile +0 -2
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,240 @@
|
|
1
|
+
require 'hanami/utils/escape'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module View
|
5
|
+
module Rendering
|
6
|
+
# Scope for layout rendering
|
7
|
+
#
|
8
|
+
# @since 0.1.0
|
9
|
+
class LayoutScope < BasicObject
|
10
|
+
# Initialize the scope
|
11
|
+
#
|
12
|
+
# @param layout [Hanami::Layout] the layout to render
|
13
|
+
# @param scope [Hanami::View::Rendering::Scope] the scope of the current
|
14
|
+
# view
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
# @since 0.1.0
|
18
|
+
def initialize(layout, scope)
|
19
|
+
@layout, @scope = layout, scope
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the classname as string
|
23
|
+
#
|
24
|
+
# @return classname
|
25
|
+
#
|
26
|
+
# @since 0.3.0
|
27
|
+
def class
|
28
|
+
(class << self; self end).superclass
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns an inspect String
|
32
|
+
#
|
33
|
+
# @return [String] inspect String (contains classname, objectid in hex, available ivars)
|
34
|
+
#
|
35
|
+
# @since 0.3.0
|
36
|
+
def inspect
|
37
|
+
base = "#<#{ self.class }:#{'%x' % (self.object_id << 1)}"
|
38
|
+
base << " @layout=\"#{@layout.inspect}\"" if @layout
|
39
|
+
base << " @scope=\"#{@scope.inspect}\"" if @scope
|
40
|
+
base << ">"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Render a partial or a template within a layout template.
|
44
|
+
#
|
45
|
+
# @param options [Hash]
|
46
|
+
# @option options [String] :partial the partial template to render
|
47
|
+
# @option options [String] :template the template to render
|
48
|
+
#
|
49
|
+
# @return [String] the output of the rendering process
|
50
|
+
#
|
51
|
+
# @since 0.1.0
|
52
|
+
#
|
53
|
+
# @example Rendering partial
|
54
|
+
# # Given a partial under:
|
55
|
+
# # templates/shared/_sidebar.html.erb
|
56
|
+
# #
|
57
|
+
# # In the layout template:
|
58
|
+
# # templates/application.html.erb
|
59
|
+
# #
|
60
|
+
# # Use like this:
|
61
|
+
# <%= render partial: 'shared/sidebar' %>
|
62
|
+
#
|
63
|
+
# @example Rendering template
|
64
|
+
# # Given a template under:
|
65
|
+
# # templates/articles/index.html.erb
|
66
|
+
# #
|
67
|
+
# # In the layout template:
|
68
|
+
# # templates/application.html.erb
|
69
|
+
# #
|
70
|
+
# # Use like this:
|
71
|
+
# <%= render template: 'articles/index' %>
|
72
|
+
#
|
73
|
+
# @example Rendering partial, using optional :locals
|
74
|
+
# # Given a partial under:
|
75
|
+
# # templates/shared/_sidebar.html.erb
|
76
|
+
# #
|
77
|
+
# # In the layout template:
|
78
|
+
# # templates/application.html.erb
|
79
|
+
# #
|
80
|
+
# # Use like this:
|
81
|
+
# <%= render partial: 'shared/sidebar', { user: current_user } %>
|
82
|
+
#
|
83
|
+
# #
|
84
|
+
# # `user` will be available in the scope of the sidebar rendering
|
85
|
+
def render(options)
|
86
|
+
renderer(options).render
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the requested format.
|
90
|
+
#
|
91
|
+
# @return [Symbol] the requested format (eg. :html, :json, :xml, etc..)
|
92
|
+
#
|
93
|
+
# @since 0.1.0
|
94
|
+
def format
|
95
|
+
@scope.format
|
96
|
+
end
|
97
|
+
|
98
|
+
# The current view.
|
99
|
+
#
|
100
|
+
# @return [Hanami::View] the current view
|
101
|
+
#
|
102
|
+
# @since 0.1.0
|
103
|
+
def view
|
104
|
+
@view || @scope.view
|
105
|
+
end
|
106
|
+
|
107
|
+
# The current locals.
|
108
|
+
#
|
109
|
+
# @return [Hash] the current locals
|
110
|
+
#
|
111
|
+
# @since 0.1.0
|
112
|
+
def locals
|
113
|
+
@locals || @scope.locals
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns a content for the given key, by trying to invoke on the current
|
117
|
+
# scope, a method with the same name.
|
118
|
+
#
|
119
|
+
# The scope is made of locals and concrete methods from view and layout.
|
120
|
+
#
|
121
|
+
# @param key [Symbol] a method to invoke within current scope
|
122
|
+
# @return [String,NilClass] returning content if scope respond to the
|
123
|
+
# requested method
|
124
|
+
#
|
125
|
+
# @since 0.4.1
|
126
|
+
#
|
127
|
+
# @example
|
128
|
+
# # Given the following layout template
|
129
|
+
#
|
130
|
+
# <!doctype HTML>
|
131
|
+
# <html>
|
132
|
+
# <!-- ... -->
|
133
|
+
# <body>
|
134
|
+
# <!-- ... -->
|
135
|
+
# <%= content :footer %>
|
136
|
+
# </body>
|
137
|
+
# </html>
|
138
|
+
#
|
139
|
+
# # Case 1:
|
140
|
+
# # Products::Index doesn't respond to #footer, content will return nil
|
141
|
+
# #
|
142
|
+
# # Case 2:
|
143
|
+
# # Products::Show responds to #footer, content will send back
|
144
|
+
# # #footer returning value
|
145
|
+
#
|
146
|
+
# module Products
|
147
|
+
# class Index
|
148
|
+
# include Hanami::View
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# class Show
|
152
|
+
# include Hanami::View
|
153
|
+
#
|
154
|
+
# def footer
|
155
|
+
# "contents for footer"
|
156
|
+
# end
|
157
|
+
# end
|
158
|
+
# end
|
159
|
+
def content(key)
|
160
|
+
__send__(key) if respond_to?(key)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Implements "respond to" logic
|
164
|
+
#
|
165
|
+
# @return [TrueClass,FalseClass]
|
166
|
+
#
|
167
|
+
# @since 0.3.0
|
168
|
+
#
|
169
|
+
# @see http://ruby-doc.org/core/Object.html#method-i-respond_to-3F
|
170
|
+
def respond_to?(m, include_all = false)
|
171
|
+
respond_to_missing?(m, include_all)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Implements "respond to" logic
|
175
|
+
#
|
176
|
+
# @return [TrueClass,FalseClass]
|
177
|
+
#
|
178
|
+
# @since 0.3.0
|
179
|
+
# @api private
|
180
|
+
#
|
181
|
+
# @see http://ruby-doc.org/core/Object.html#method-i-respond_to_missing-3F
|
182
|
+
def respond_to_missing?(m, include_all)
|
183
|
+
@layout.respond_to?(m, include_all) ||
|
184
|
+
@scope.respond_to?(m, include_all)
|
185
|
+
end
|
186
|
+
|
187
|
+
protected
|
188
|
+
# Forward all the missing methods to the view scope or to the layout.
|
189
|
+
#
|
190
|
+
# @api private
|
191
|
+
# @since 0.1.0
|
192
|
+
#
|
193
|
+
# @see Hanami::View::Rendering::Scope
|
194
|
+
# @see Hanami::Layout
|
195
|
+
#
|
196
|
+
# @example
|
197
|
+
# # In the layout template:
|
198
|
+
# # templates/application.html.erb
|
199
|
+
# #
|
200
|
+
# # Use like this:
|
201
|
+
# <title><%= article.title %></title>
|
202
|
+
#
|
203
|
+
# # `article` will be looked up in the view scope first.
|
204
|
+
# # If not found, it will be searched within the layout.
|
205
|
+
def method_missing(m, *args, &blk)
|
206
|
+
if @scope.respond_to?(m)
|
207
|
+
@scope.__send__(m, *args, &blk)
|
208
|
+
elsif layout.respond_to?(m)
|
209
|
+
layout.__send__(m, *args, &blk)
|
210
|
+
else
|
211
|
+
::Hanami::View::Escape.html(super)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def renderer(options)
|
216
|
+
if options[:partial]
|
217
|
+
Rendering::Partial
|
218
|
+
elsif options[:template]
|
219
|
+
Rendering::Template
|
220
|
+
end.new(view, _options(options))
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
def _options(options)
|
225
|
+
options.dup.tap do |opts|
|
226
|
+
opts.merge!(format: format)
|
227
|
+
opts[:locals] = locals
|
228
|
+
opts[:locals].merge!(options.fetch(:locals){ ::Hash.new })
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# @since 0.4.2
|
233
|
+
# @api private
|
234
|
+
def layout
|
235
|
+
@layout || @layout.class.layout.new(@scope, "")
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Hanami
|
2
|
+
module View
|
3
|
+
module Rendering
|
4
|
+
# Null Object pattern for Layout.
|
5
|
+
# It's used when a view doesn't require a layout.
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
# @since 0.1.0
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# require 'hanami/view'
|
12
|
+
#
|
13
|
+
# module Articles
|
14
|
+
# class Show
|
15
|
+
# include Hanami::View
|
16
|
+
# layout false
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # In this scenario we will use a `NullLayout`.
|
21
|
+
class NullLayout
|
22
|
+
|
23
|
+
# Initialize a layout
|
24
|
+
#
|
25
|
+
# @param scope [Hanami::View::Rendering::Scope] view rendering scope
|
26
|
+
# @param rendered [String] the output of the view rendering process
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
# @since 0.1.0
|
30
|
+
#
|
31
|
+
# @see Hanami::Layout#initialize
|
32
|
+
# @see Hanami::View::Rendering#render
|
33
|
+
def initialize(scope, rendered)
|
34
|
+
@rendered = rendered
|
35
|
+
end
|
36
|
+
|
37
|
+
# Render the layout
|
38
|
+
#
|
39
|
+
# @return [String] the output of the rendering process
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
# @since 0.1.0
|
43
|
+
#
|
44
|
+
# @see Hanami::Layout#render
|
45
|
+
# @see Hanami::View::Rendering#render
|
46
|
+
def render
|
47
|
+
@rendered
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Hanami
|
2
|
+
module View
|
3
|
+
module Rendering
|
4
|
+
# Null Object pattern for layout template
|
5
|
+
#
|
6
|
+
# It's used when a layout doesn't have an associated template.
|
7
|
+
#
|
8
|
+
# A common scenario is for non-html requests.
|
9
|
+
# Usually we have a template for the application layout
|
10
|
+
# (eg `templates/application.html.erb`), but we don't use to have the a
|
11
|
+
# template for JSON requests (eg `templates/application.json.erb`).
|
12
|
+
# Because most of the times, we only return the output of the view.
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
# @since 0.1.0
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# require 'hanami/view'
|
19
|
+
#
|
20
|
+
# # We have an ApplicationLayout (views/application_layout.rb):
|
21
|
+
# class ApplicationLayout
|
22
|
+
# include Hanami::Layout
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # Our layout has a template for HTML requests, located at:
|
26
|
+
# # templates/application.html.erb
|
27
|
+
#
|
28
|
+
# # We set it as global layout
|
29
|
+
# Hanami::View.layout = :application
|
30
|
+
#
|
31
|
+
# # We have two views for HTML and JSON articles.
|
32
|
+
# # They have a template each:
|
33
|
+
# #
|
34
|
+
# # * templates/articles/show.html.erb
|
35
|
+
# # * templates/articles/show.json.erb
|
36
|
+
# module Articles
|
37
|
+
# class Show
|
38
|
+
# include Hanami::View
|
39
|
+
# format :html
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# class JsonShow < Show
|
43
|
+
# format :json
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# # We initialize the framework
|
48
|
+
# Hanami::View.load!
|
49
|
+
#
|
50
|
+
#
|
51
|
+
#
|
52
|
+
# # When we ask for a HTML rendering, it will use `Articles::Show` and
|
53
|
+
# # ApplicationLayout. The output will be a composition of:
|
54
|
+
# #
|
55
|
+
# # * templates/articles/show.html.erb
|
56
|
+
# # * templates/application.html.erb
|
57
|
+
#
|
58
|
+
# # When we ask for a JSON rendering, it will use `Articles::JsonShow`
|
59
|
+
# # and ApplicationLayout. Since, the layout doesn't have any associated
|
60
|
+
# # template for JSON, the output will be a composition of:
|
61
|
+
# #
|
62
|
+
# # * templates/articles/show.json.erb
|
63
|
+
class NullTemplate
|
64
|
+
# Render the layout template
|
65
|
+
#
|
66
|
+
# @param scope [Hanami::View::Scope] the rendering scope
|
67
|
+
# @param locals [Hash] a set of objects available during the rendering
|
68
|
+
# @yield [Proc] yields the given block
|
69
|
+
#
|
70
|
+
# @return [String] the output of the rendering process
|
71
|
+
#
|
72
|
+
# @api private
|
73
|
+
# @since 0.1.0
|
74
|
+
#
|
75
|
+
# @see Hanami::Layout#render
|
76
|
+
# @see Hanami::View::Rendering#render
|
77
|
+
def render(scope, locals = {})
|
78
|
+
yield
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'hanami/view/rendering/partial_finder'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module View
|
5
|
+
module Rendering
|
6
|
+
# Rendering partial
|
7
|
+
#
|
8
|
+
# It's used when a template wants to render a partial.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @since 0.1.0
|
12
|
+
#
|
13
|
+
# @see Hanami::View::Rendering::Template
|
14
|
+
# @see Hanami::View::Rendering::LayoutScope#render
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# # We have an application template (templates/application.html.erb)
|
18
|
+
# # that uses the following line:
|
19
|
+
#
|
20
|
+
# <%= render partial: 'shared/sidebar' %>
|
21
|
+
class Partial < Template
|
22
|
+
protected
|
23
|
+
def template
|
24
|
+
PartialFinder.new(@view.class, @options).find
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'hanami/view/rendering/template_finder'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module View
|
5
|
+
module Rendering
|
6
|
+
# Find a partial for the current view context.
|
7
|
+
# It's used when a template wants to render a partial.
|
8
|
+
#
|
9
|
+
# @see Hanami::View::Rendering::Partial
|
10
|
+
# @see Hanami::View::Rendering::TemplateFinder
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
# @since 0.1.0
|
14
|
+
class PartialFinder < TemplateFinder
|
15
|
+
# Template file name prefix.
|
16
|
+
# By convention a partial file name starts with this prefix.
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
# @since 0.1.0
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# "_sidebar.html.erb"
|
23
|
+
PREFIX = '_'.freeze
|
24
|
+
|
25
|
+
# Find a template for a partial. Initially it will look for the
|
26
|
+
# partial template under the directory of the parent directory
|
27
|
+
# view template, if not found it will search recursivly from
|
28
|
+
# the view root.
|
29
|
+
#
|
30
|
+
# @return [Hanami::View::Template] the requested template
|
31
|
+
#
|
32
|
+
# @see Hanami::View::Rendering::TemplateFinder#find
|
33
|
+
#
|
34
|
+
# @since 0.4.3
|
35
|
+
# @api private
|
36
|
+
def find
|
37
|
+
if path = partial_template_under_view_path
|
38
|
+
View::Template.new(path, @view.configuration.default_encoding)
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
# @since 0.4.3
|
46
|
+
# @api private
|
47
|
+
def partial_template_under_view_path
|
48
|
+
_find(view_template_dir).first
|
49
|
+
end
|
50
|
+
|
51
|
+
# @since 0.4.3
|
52
|
+
# @api private
|
53
|
+
def view_template_dir
|
54
|
+
*all, _ = @view.template.split(separator)
|
55
|
+
all.join(separator)
|
56
|
+
end
|
57
|
+
|
58
|
+
def template_name
|
59
|
+
*all, last = partial_name.split(separator)
|
60
|
+
all.push( last.prepend(prefix) ).join(separator)
|
61
|
+
end
|
62
|
+
|
63
|
+
def partial_name
|
64
|
+
@options[:partial]
|
65
|
+
end
|
66
|
+
|
67
|
+
def prefix
|
68
|
+
PREFIX
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|