/
layouts.txt
252 lines (190 loc) · 9.34 KB
/
layouts.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
= Layouts
Layouts in Ramaze are a special category of templates.
Every Controller has paths and rules used to find templates for layouts.
Lookup of the template is done when assembling an Action, but is in no way a
necessary element of an Action.
== History
Layouts were first introduced in mid-2007.
At first they were simply a small addition to wrap a rendered Action into
another Action.
This allowed for easier use of alternative templating engines, as at the time
only the Ezamar templating engine provided equivalent functionality through
Elements.
As it became evident that Elements were not as easy to work with, layouts were
used increasingly.
However, the functionality was still rather hard to control and not well
integrated into the framework.
This was changed when Ramaze was starting to utilize Innate, and they are now
well integrated and powerful citizens of Ramaze.
Since this time, layouts also have their own directory to live in.
== Usage
This is a collection of small examples that try to give you a taste of what's
possible with layouts.
Usually, there will be only one layout per controller, but you can dispatch to
different layouts depending on the request.
The `layout` method takes a block, which, depending on the argument to the
method, behaves differently.
In the normal case, you will only supply one argument and no block.
[source,ruby]
--------------------------------------------------------------------------------
layout('default')
--------------------------------------------------------------------------------
This will apply the same 'default' layout to all actions.
[source,ruby]
--------------------------------------------------------------------------------
layout('default'){|path, wish| !request.xhr? }
--------------------------------------------------------------------------------
This will apply the 'default' layout only to actions that were not requested
via AJAX (which sets the 'HTTP_X_REQUESTED_WITH' header to 'XMLHttpRequest').
This can be very useful if you want to include parts of a page dynamically from
JavaScript without using a separate controller.
[source,ruby]
--------------------------------------------------------------------------------
layout('default'){|path, wish| wish == 'html' }
--------------------------------------------------------------------------------
Now we apply a layout only if the client wished for html (through using no, or
the 'html' extension in the URI)
So a request to '/foo' or '/foo.html' will have the 'default' layout, but a
request to '/foo.json' won't.
Please see the section about content representations about more information
about how Ramaze handles these requests.
[source,ruby]
--------------------------------------------------------------------------------
layout{|path, wish| ['red', 'blue', 'green'].choice }
--------------------------------------------------------------------------------
Now we're getting even more dynamic, choosing a random layout for every request.
Usually you'll want to put some reasonable logic inside the block, but the
point of this example is that, if you don't pass an argument, but a block, to
the `layout` method, it will use the return value of the block as the name for
the layout.
This can be useful if you want to let users choose a theme, or have a different
layout for users that are logged in.
=== Layout from template
Layouts are usually read from a file template that resides in the '/layout'
directory.
Following example will search for a file called '/layout/default.xhtml' and, if
found, wrap the content of the action inside it.
[source,ruby]
--------------------------------------------------------------------------------
class Box < Ramaze::Controller
layout :default
def index
'Hello, World!'
end
end
--------------------------------------------------------------------------------
Additionally to this, we will need the file for the layout template to reside
in, we'll use an identical layout as further above.
[source,txt]
--------------------------------------------------------------------------------
$ cat layout/default.xhtml
{ #{@content} }
--------------------------------------------------------------------------------
That's just as easy, but enables you to work easier on larger layouts.
=== Layout from method
Following example should illustrate a simple use-case for a layout that uses
the return value of a method:
[source,ruby]
--------------------------------------------------------------------------------
class Box < Ramaze::Controller
layout :default
def index
'Hello, World!'
end
def default
'{ #{@content} }'
end
end
--------------------------------------------------------------------------------
This will wrap every request to '/index' in curly brackets. Not very useful,
but it should illustrate the basic working principle.
NOTE: If you have both a file template and a method for the layout, Ramaze will
use the file template and will not call the method.
In future versions this behavior might change to call the method as well.
=== Layout directories per controller
Additionally to the '/layout' directory, sub-directories thereof can be
searched for templates. This lookup is specific to each Controller.
To control the behavior you can use the `map_layouts` method, which takes
multiple arguments.
== Structure
By default, layout file templates are located in a directory called 'layout' in
the application root.
Nested inside this directory can be further directories, if you want a controller to look for layout file templates in a deeper directory, you can use the `map_layouts` method. By default all layout file templates are searched at the top.
It is not very common for applications to utilize more than a few layouts, you
will find many applications with only a single layout, and even a few without
any layouts at all.
The layouts of a medium-sized application may organized like this.
[source,txt]
--------------------------------------------------------------------------------
layout
|-- default.xhtml
|-- feed
| |-- default.rss.xhtml
| `-- default.atom.xhtml
`-- mobile.xhtml
--------------------------------------------------------------------------------
It is a common pattern to name the layout used for most controllers 'default',
but there is nothing enforcing it. In fact, to use a layout, you will have to
explicitly tell Ramaze which to use.
== Path lookup
To provide flexibility for large projects (and for use-cases I haven't imagined
yet), I decided to reuse the view file template lookup already in use for
layouts.
This ensures that the behavior of both is indentical and available through a
consistent API.
=== Inline templates
If no layout file template can be found for a specified layout, a method may be
used instead.
== Configuration
To get a feeling for the places where Ramaze will search for layouts, you can
inspect like in the following example.
[source,ruby]
--------------------------------------------------------------------------------
include::source/layouts/configuration/possible_paths_for.xmp[tabsize=2]
--------------------------------------------------------------------------------
There are two options on `Ramaze.options` that influence the lookup, one is the
`roots`, which is set depending on your application, the other the `layouts`
which defaults to `['/layout']`.
[source,ruby]
--------------------------------------------------------------------------------
include::source/layouts/configuration/global.xmp[tabsize=2]
--------------------------------------------------------------------------------
== Content representations
Layouts can also play a role in content representations, and you can add
layouts for specific provides.
As we saw above, we were using files like 'feed/default.rss.xhtml' and
'feed/default.atom.xhtml', they apply to the `rss` and `atom` provides
respectively.
Let's show this with another little example so you can learn how to use this
functionality effectively.
[source,ruby]
--------------------------------------------------------------------------------
include::source/layouts/provides/smiley.rb[tabsize=2]
--------------------------------------------------------------------------------
So we add two provides named `frown` and `smile`, and there is one default
provide for `html`.
To take advantage of this, we can add three layout files in our 'layout'
directory.
[source,txt]
--------------------------------------------------------------------------------
$ cat layout/default.xhtml
:| #{@content} |:
$ cat layout/default.frown.xhtml
:( #{@content} ):
$ cat layout/default.smile.xhtml
:) #{@content} (:
--------------------------------------------------------------------------------
Following responses will be served on requests:
|===============================================================================
| '/index' | ':\| emoticons ftw! \|:'
| '/index.frown' | ':( emoticons ftw! ):'
| '/index.smile' | ':) emoticons ftw! (:'
|===============================================================================
== Implementation
Sometimes the Ramaze default behavior won't be what you need, so knowing how
layouts are implemented can help you building custom actions with layouts
tailored to your needs.
I will start with an arbitrary existing Action, then explain how it was created
and how it will be rendered, finally we'll cover some more possibilities.
On every request, the layout will be determined and set in the Action, which
will clone itself.