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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b5fa7e5cacbc48414e630515f6c43d5c579514a2
4
- data.tar.gz: 69b8ac7e07ea57ff52c573dfc95dbb6fdd103cb2
3
+ metadata.gz: 0fa2f0c09d6500c146316de763a71a98afefdf03
4
+ data.tar.gz: 7d96ea6c912cdd30fc2945c786c6a5d057cffc98
5
5
  SHA512:
6
- metadata.gz: 854fa6b2b24c398a50901e4ea7f4994901670106c2aef4fcac4b315575652cf3d8527fc056ab39fa0e18fff587786c2460502f54b1fb51d46a6760c9974b0cd7
7
- data.tar.gz: 175ac6b7b96e1efefac54e555d8f39f82ab8061ccb2a9d27f0dac8be7366bc000a37ea8886e00691edc21130ce7800cd9041e33c75c6a51fb0fca0bd6ca5b8a5
6
+ metadata.gz: f818e7eb0e57bf05852b7f8a1b6fe1971ca87d9d20404815d179aa7202eb07c3627bc996e29136babff7d1b2072f531748b6401361e7d7e5a179ed2b7f25979a
7
+ data.tar.gz: 675e51826977c6e4bf1872aa4350e95d68deb225833a8a821b66ac686062f5b3418f9a19918572c850f5d9746bbc10a9e401382da4c27df9fab5d587bb1a9592
@@ -0,0 +1,96 @@
1
+ # Hanami::View
2
+ View layer for Hanami
3
+
4
+ ## v0.6.0 - 2016-01-22
5
+ ### Changed
6
+ - [Luca Guidi] Renamed the project
7
+
8
+ ## v0.5.0 - 2016-01-12
9
+ ### Added
10
+ - [Luca Guidi] Added `Lotus::View::Configuration#default_encoding` to set the encoding for templates
11
+
12
+ ### Fixed
13
+ - [Luca Guidi] Let exceptions to be raised as they occur in rendering context. This fixes misleading backtraces for exceptions.
14
+ - [Martin Rubi] Raise a `Lotus::View::MissingTemplateError` when rendering a missing partial from a template
15
+ - [Luca Guidi] Fix for `template.erb is not valid US-ASCII (Encoding::InvalidByteSequenceError)` when system encoding is not set
16
+
17
+ ### Changed
18
+ - [Liam Dawson] Introduced `Lotus::View::Error` and let all the framework exceptions to inherit from it.
19
+
20
+ ## v0.4.4 - 2015-09-30
21
+ ### Added
22
+ - [Luca Guidi] Autoescape for layout helpers.
23
+
24
+ ## v0.4.3 - 2015-07-10
25
+ ### Fixed
26
+ - [Farrel Lifson] Force partial finder to be explicit when to templates have the same name.
27
+
28
+ ## v0.4.2 - 2015-06-23
29
+ ### Fixed
30
+ - [Tom Kadwill] Ensure views to use methods defined by the associated layout.
31
+
32
+ ## v0.4.1 - 2015-05-22
33
+ ### Added
34
+ - [Luca Guidi] Introduced `#content` to render optional contents in a different context (eg. a view sets a page specific javascript in the application template footer).
35
+
36
+ ## v0.4.0 - 2015-03-23
37
+ ### Changed
38
+ - [Luca Guidi] Autoescape concrete and virtual methods from presenters
39
+ - [Luca Guidi] Autoescape concrete and virtual methods from views
40
+
41
+ ### Fixed
42
+ - [Tom Kadwill] Improve error message for undefined method in view
43
+ - [Luca Guidi] Ensure that layouts will include modules from `Configuration#prepare`
44
+
45
+ ## v0.3.0 - 2014-12-23
46
+ ### Added
47
+ - [Trung Lê] When duplicate the framework, also duplicate `Presenter`
48
+ - [Benny Klotz] Introduced `Scope#class`, `#inspect`, `LayoutScope#class` and `#inspect`
49
+ - [Alfonso Uceda Pompa & Trung Lê] Introduced `Configuration#prepare`
50
+ - [Luca Guidi] Implemented "respond to" logic for `Lotus::View::Scope` (`respond_to?` and `respond_to_missing?`)
51
+ - [Luca Guidi] Implemented "respond to" logic for `Lotus::Layout` (`respond_to?` and `respond_to_missing?`)
52
+ - [Jeremy Stephens] Allow view concrete methods that accept a block to be invoked from templates
53
+ - [Peter Suschlik] Implemented "respond to" logic for `Lotus::Presenter` (`respond_to?` and `respond_to_missing?`)
54
+ - [Luca Guidi] Official support for Ruby 2.2
55
+
56
+ ### Changed
57
+ - [Alfonso Uceda Pompa] Raise an exception when a layout doesn't have an associated template
58
+
59
+ ### Fixed
60
+ - [Luca Guidi] Ensure that concrete methods in layouts are available in templates
61
+ - [Luca Guidi] Ensure to associate the right layout to a view in case fo duplicated framework
62
+ - [Luca Guidi] Safe override of Ruby's top level methods in Scope. (Eg. use `select` from a view, not from `::Kernel`)
63
+
64
+ ## v0.2.0 - 2014-06-23
65
+ ### Added
66
+ - [Luca Guidi] Introduced `Configuration#duplicate`
67
+ - [Luca Guidi] Introduced `Configuration#layout` to define the layout that all the views will use
68
+ - [Luca Guidi] Introduced `Configuration#load_paths` to define several sources where to lookup for templates
69
+ - [Luca Guidi] Introduced `Configuration#root` to define the root path where to find templates
70
+ - [Luca Guidi] Introduced `Lotus::View::Configuration`
71
+ - [Grant Ammons] Allow view concrete methods with arity > 0 to be invoked from templates
72
+ - [Luca Guidi] Official support for Ruby 2.1
73
+
74
+ ### Changed
75
+ - [Luca Guidi] `Rendering::TemplatesFinder` now look recursively for templates, starting from the root.
76
+ - [Luca Guidi] Removed `View.layout=`
77
+ - [Luca Guidi] Removed `View.root=`
78
+
79
+ ### Fixed
80
+ - [Luca Guidi] Ensure outermost locals to not shadow innermost inside templates/partials
81
+
82
+ ## v0.1.0 - 2014-03-23
83
+ ### Added
84
+ - [Luca Guidi] Allow custom rendering policy via `Action#render` override. This bypasses the template lookup and rendering.
85
+ - [Luca Guidi] Introduced `Lotus::Presenter`
86
+ - [Luca Guidi] Introduced templates rendering from templates and layouts
87
+ - [Luca Guidi] Introduced partials rendering from templates and layouts
88
+ - [Luca Guidi] Introduced layouts support
89
+ - [Luca Guidi] Introduced `Lotus::View.load!` as entry point to load views and templates
90
+ - [Luca Guidi] Allow to setup template name via `View.template`
91
+ - [Luca Guidi] Rendering context also considers locals passed to the constructor
92
+ - [Luca Guidi] Introduced `View.format` as DSL to declare which format to handle
93
+ - [Luca Guidi] Introduced view subclasses as way to handle different formats (mime types)
94
+ - [Luca Guidi] Introduced multiple templates per each View
95
+ - [Luca Guidi] Implemented basic rendering with templates
96
+ - [Luca Guidi] Official support for Ruby 2.0
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014-2016 Luca Guidi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,8 +1,40 @@
1
1
  # Hanami::View
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/hanami/view`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ A View layer for [Hanami](http://hanamirb.org).
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ It's based on a **separation between views and templates**.
6
+
7
+ A _view_ is an object that encapsulates the presentation logic of a page.
8
+ A _template_ is a file that defines the semantic and visual elements of a page.
9
+ In order to show a result to a user, a template must be _rendered_ by a view.
10
+
11
+ Keeping things separated helps to declutter templates and models from presentation logic.
12
+ Also, since views are objects, they are easily testable.
13
+ If you ever used [Mustache](http://mustache.github.io/), you are already aware of the advantages.
14
+
15
+ Like all the other Hanami components, it can be used as a standalone framework or within a full Hanami application.
16
+
17
+ ## Status
18
+
19
+ [![Gem Version](http://img.shields.io/gem/v/hanami-view.svg)](https://badge.fury.io/rb/hanami-view)
20
+ [![Build Status](http://img.shields.io/travis/hanami/view/master.svg)](https://travis-ci.org/hanami/view?branch=master)
21
+ [![Coverage](http://img.shields.io/coveralls/hanami/view/master.svg)](https://coveralls.io/r/hanami/view)
22
+ [![Code Climate](http://img.shields.io/codeclimate/github/hanami/view.svg)](https://codeclimate.com/github/hanami/view)
23
+ [![Dependencies](http://img.shields.io/gemnasium/hanami/view.svg)](https://gemnasium.com/hanami/view)
24
+ [![Inline docs](http://inch-ci.org/github/hanami/view.svg?branch=master)](http://inch-ci.org/github/hanami/view)
25
+
26
+ ## Contact
27
+
28
+ * Home page: http://hanamirb.org
29
+ * Mailing List: http://hanamirb.org/mailing-list
30
+ * API Doc: http://rdoc.info/gems/hanami-view
31
+ * Bugs/Issues: https://github.com/hanami/view/issues
32
+ * Support: http://stackoverflow.com/questions/tagged/hanami
33
+ * Chat: http://chat.hanamirb.org
34
+
35
+ ## Rubies
36
+
37
+ __Hanami::View__ supports Ruby (MRI) 2+
6
38
 
7
39
  ## Installation
8
40
 
@@ -22,15 +54,802 @@ Or install it yourself as:
22
54
 
23
55
  ## Usage
24
56
 
25
- TODO: Write usage instructions here
57
+ ### Conventions
58
+
59
+ * Templates are searched under `Hanami::View.configuration.root`, set this value according to your app structure (eg. `"app/templates"`).
60
+ * A view will look for a template with a file name that is composed by its full class name (eg. `"articles/index"`).
61
+ * A template must have two concatenated extensions: one for the format and one for the engine (eg. `".html.erb"`).
62
+ * The framework must be loaded before rendering the first time: `Hanami::View.load!`.
63
+
64
+ ### Views
65
+
66
+ A simple view looks like this:
67
+
68
+ ```ruby
69
+ require 'hanami/view'
70
+
71
+ module Articles
72
+ class Index
73
+ include Hanami::View
74
+ end
75
+ end
76
+ ```
77
+
78
+ Suppose that we want to render a list of `articles`:
79
+
80
+ ```ruby
81
+ require 'hanami/view'
82
+
83
+ module Articles
84
+ class Index
85
+ include Hanami::View
86
+ end
87
+ end
88
+
89
+ Hanami::View.configure do
90
+ root 'app/templates'
91
+ end
92
+
93
+ Hanami::View.load!
94
+
95
+ path = Hanami::View.configuration.root.join('articles/index.html.erb')
96
+ template = Hanami::View::Template.new(path)
97
+ articles = ArticleRepository.all
98
+
99
+ Articles::Index.new(template, articles: articles).render
100
+ ```
101
+
102
+ While this code is working fine, it's inefficient and verbose, because we are loading a template from the filesystem for each rendering attempt.
103
+ Also, this is strictly related to the HTML format, what if we want to manage other formats?
104
+
105
+ ```ruby
106
+ require 'hanami/view'
107
+
108
+ module Articles
109
+ class Index
110
+ include Hanami::View
111
+ end
112
+
113
+ class AtomIndex < Index
114
+ format :atom
115
+ end
116
+ end
117
+
118
+ Hanami::View.configure do
119
+ root 'app/templates'
120
+ end
121
+
122
+ Hanami::View.load!
123
+
124
+ articles = ArticleRepository.all
125
+
126
+ Articles::Index.render(format: :html, articles: articles)
127
+ # => This will use Articles::Index
128
+ # and "articles/index.html.erb"
129
+
130
+ Articles::Index.render(format: :atom, articles: articles)
131
+ # => This will use Articles::AtomIndex
132
+ # and "articles/index.atom.erb"
133
+
134
+ Articles::Index.render(format: :xml, articles: articles)
135
+ # => This will raise a Hanami::View::MissingTemplateError
136
+ ```
137
+
138
+ ### Locals
139
+
140
+ All the objects passed in the context are called _locals_, they are available both in the view and in the template:
141
+
142
+ ```ruby
143
+ require 'hanami/view'
144
+
145
+ module Articles
146
+ class Show
147
+ include Hanami::View
148
+
149
+ def authors
150
+ article.authors.map(&:full_name).join ', '
151
+ end
152
+ end
153
+ end
154
+ ```
155
+
156
+ ```erb
157
+ <h1><%= article.title %></h1>
158
+ <article>
159
+ <%= article.content %>
160
+ </article>
161
+ ```
162
+
163
+ All the methods defined in the view are accessible from the template:
164
+
165
+ ```erb
166
+ <h2><%= authors %></h2>
167
+ ```
168
+
169
+ For convenience, they are also available to the view as a Hash, accessed through the `locals` method.
170
+
171
+ ```ruby
172
+ require 'hanami/view'
173
+
174
+ module Articles
175
+ class Show
176
+ include Hanami::View
177
+
178
+ # This view already responds to `#article` because there is an element in
179
+ # the locals with the same key.
180
+ #
181
+ # In order to allow developers to override those methods, and decorate a
182
+ # single locals object, a view has a Hash with the same values.
183
+ #
184
+ # If we had implemented this method like this:
185
+ #
186
+ # def article
187
+ # ArticlePresenter.new(article)
188
+ # end
189
+ #
190
+ # We would have generated a `SystemStackError` (stack level too deep).
191
+ def article
192
+ ArticlePresenter.new(locals[:article])
193
+ end
194
+ end
195
+ end
196
+ ```
197
+
198
+ ### Custom rendering
199
+
200
+ Since a view is an object, you can override `#render` and provide your own rendering policy:
201
+
202
+ ```ruby
203
+ require 'hanami/view'
204
+
205
+ module Articles
206
+ class Show
207
+ include Hanami::View
208
+ format :json
209
+
210
+ def render
211
+ ArticleSerializer.new(article).to_json
212
+ end
213
+ end
214
+ end
215
+
216
+ Articles::Show.render({format: :json, article: article})
217
+ # => This will render from ArticleSerializer,
218
+ # without the need of a template
219
+ ```
220
+
221
+ ### Format
222
+
223
+ The `.format` DSL is used to declare one or more mime types that a view is able to render.
224
+ These values are **arbitrary**, just **be sure to create a corresponding template**.
225
+
226
+ ```ruby
227
+ require 'hanami/view'
228
+
229
+ module Articles
230
+ class Show
231
+ include Hanami::View
232
+ format :custom
233
+ end
234
+ end
235
+
236
+ Articles::Show.render({format: :custom, article: article})
237
+ # => This will render "articles/show.custom.erb"
238
+ ```
239
+
240
+ ### Engines
241
+
242
+ The builtin rendering engine is [ERb](http://en.wikipedia.org/wiki/ERuby).
243
+ However, Hanami::View supports countless rendering engines out of the box.
244
+ Require your library of choice **before** requiring `'hanami/view'`, and it will just work.
245
+
246
+ ```ruby
247
+ require 'haml'
248
+ require 'hanami/view'
249
+
250
+ module Articles
251
+ class Show
252
+ include Hanami::View
253
+ end
254
+ end
255
+
256
+ Articles::Show.render({format: :html, article: article})
257
+ # => This will render "articles/show.html.haml"
258
+ ```
259
+
260
+ This is the list of the supported engines.
261
+ They are listed in order of **higher precedence**, for a given extension.
262
+ For instance, if [ERubis](http://www.kuwata-lab.com/erubis/) is loaded, it will be preferred over ERb to render `.erb` templates.
263
+
264
+ <table>
265
+ <tr>
266
+ <th>Engine</th>
267
+ <th>Extensions</th>
268
+ </tr>
269
+ <tr>
270
+ <td>Erubis</td>
271
+ <td>erb, rhtml, erubis</td>
272
+ </tr>
273
+ <tr>
274
+ <td>ERb</td>
275
+ <td>erb, rhtml</td>
276
+ </tr>
277
+ <tr>
278
+ <td>Redcarpet</td>
279
+ <td>markdown, mkd, md</td>
280
+ </tr>
281
+ <tr>
282
+ <td>RDiscount</td>
283
+ <td>markdown, mkd, md</td>
284
+ </tr>
285
+ <tr>
286
+ <td>Kramdown</td>
287
+ <td>markdown, mkd, md</td>
288
+ </tr>
289
+ <tr>
290
+ <td>Maruku</td>
291
+ <td>markdown, mkd, md</td>
292
+ </tr>
293
+ <tr>
294
+ <td>BlueCloth</td>
295
+ <td>markdown, mkd, md</td>
296
+ </tr>
297
+ <tr>
298
+ <td>Asciidoctor</td>
299
+ <td>ad, adoc, asciidoc</td>
300
+ </tr>
301
+ <tr>
302
+ <td>Builder</td>
303
+ <td>builder</td>
304
+ </tr>
305
+ <tr>
306
+ <td>CSV</td>
307
+ <td>rcsv</td>
308
+ </tr>
309
+ <tr>
310
+ <td>CoffeeScript</td>
311
+ <td>coffee</td>
312
+ </tr>
313
+ <tr>
314
+ <td>WikiCloth</td>
315
+ <td>wiki, mediawiki, mw</td>
316
+ </tr>
317
+ <tr>
318
+ <td>Creole</td>
319
+ <td>wiki, creole</td>
320
+ </tr>
321
+ <tr>
322
+ <td>Etanni</td>
323
+ <td>etn, etanni</td>
324
+ </tr>
325
+ <tr>
326
+ <td>Haml</td>
327
+ <td>haml</td>
328
+ </tr>
329
+ <tr>
330
+ <td>Less</td>
331
+ <td>less</td>
332
+ </tr>
333
+ <tr>
334
+ <td>Liquid</td>
335
+ <td>liquid</td>
336
+ </tr>
337
+ <tr>
338
+ <td>Markaby</td>
339
+ <td>mab</td>
340
+ </tr>
341
+ <tr>
342
+ <td>Nokogiri</td>
343
+ <td>nokogiri</td>
344
+ </tr>
345
+ <tr>
346
+ <td>Plain</td>
347
+ <td>html</td>
348
+ </tr>
349
+ <tr>
350
+ <td>RDoc</td>
351
+ <td>rdoc</td>
352
+ </tr>
353
+ <tr>
354
+ <td>Radius</td>
355
+ <td>radius</td>
356
+ </tr>
357
+ <tr>
358
+ <td>RedCloth</td>
359
+ <td>textile</td>
360
+ </tr>
361
+ <tr>
362
+ <td>Sass</td>
363
+ <td>sass</td>
364
+ </tr>
365
+ <tr>
366
+ <td>Scss</td>
367
+ <td>scss</td>
368
+ </tr>
369
+ <tr>
370
+ <td>Slim</td>
371
+ <td>slim</td>
372
+ </tr>
373
+ <tr>
374
+ <td>String</td>
375
+ <td>str</td>
376
+ </tr>
377
+ <tr>
378
+ <td>Yajl</td>
379
+ <td>yajl</td>
380
+ </tr>
381
+ </table>
382
+
383
+ ### Root
384
+
385
+ Template lookup is performed under the `Hanami::View.configuration.root` directory. You can specify a different path on a per view basis:
386
+
387
+ ```ruby
388
+ class ViewWithDifferentRoot
389
+ include Hanami::View
390
+
391
+ root 'path/to/root'
392
+ end
393
+ ```
394
+
395
+ ### Template
396
+
397
+ The template file must be located under the relevant `root` and must match the class name:
398
+
399
+ ```ruby
400
+ puts Hanami::View.configuration.root # => #<Pathname:app/templates>
401
+ Articles::Index.template # => "articles/index"
402
+ ```
403
+
404
+ Each view can specify a different template:
405
+
406
+ ```ruby
407
+ module Articles
408
+ class Create
409
+ include Hanami::View
410
+
411
+ template 'articles/new'
412
+ end
413
+ end
414
+
415
+ Articles::Create.template # => "articles/new"
416
+ ```
417
+
418
+ ### Partials
419
+
420
+ Partials can be rendered within a template:
421
+
422
+ ```erb
423
+ <%= render partial: 'articles/form', locals: { secret: 23 } %>
424
+ ```
425
+
426
+ It will look for a template `articles/_form.html.erb` and make available both the view's and partial's locals (eg. `article` and `secret`).
427
+
428
+ ### Templates
429
+
430
+ Templates can be rendered within another template:
431
+
432
+ ```erb
433
+ <%= render template: 'articles/new', locals: { errors: {} } %>
434
+ ```
435
+
436
+ It will render `articles/new.html.erb` and make available both the view's and templates's locals (eg. `article` and `errors`).
437
+
438
+ ### Layouts
439
+
440
+ Layouts are wrappers for views. Layouts may serve to reuse common markup.
441
+
442
+ ```ruby
443
+ class ApplicationLayout
444
+ include Hanami::Layout
445
+
446
+ def page_title
447
+ 'Title:'
448
+ end
449
+ end
450
+
451
+ module Articles
452
+ class Index
453
+ include Hanami::View
454
+ layout :application
455
+
456
+ def page_title
457
+ "#{ layout.page_title } articles"
458
+ end
459
+ end
460
+
461
+ class RssIndex < Index
462
+ format :rss
463
+ layout false
464
+ end
465
+ end
466
+
467
+ Articles::Index.render(format: :html) # => Will use ApplicationLayout
468
+ Articles::Index.render(format: :rss) # => Will use nothing
469
+ ```
470
+
471
+ As per convention, layout templates are located under `Hanami::View.root` or `ApplicationLayout.root` and use the underscored name (eg. `ApplicationLayout => application.html.erb`).
472
+
473
+ ### Optional Content
474
+
475
+ If we want to render optional contents such as sidebar links or page specific javascripts, we can use `#content`
476
+ It accepts a key that represents a method that should be available within the rendering context.
477
+ That context is made of the locals, and the methods that view and layout respond to.
478
+ If the context can't dispatch that method, it returns `nil`.
479
+
480
+ Given the following layout template.
26
481
 
27
- ## Development
482
+ ```erb
483
+ <!doctype HTML>
484
+ <html>
485
+ <!-- ... -->
486
+ <body>
487
+ <!-- ... -->
488
+ <%= content :footer %>
489
+ </body>
490
+ </html>
491
+ ```
492
+
493
+ We have two views, one responds to `#footer` (`Products::Show`) and the other doesn't (`Products::Index`).
494
+ When the first is rendered, `content` gives back the returning value of `#footer`.
495
+ In the other case, `content` returns `nil`.
496
+
497
+ ```ruby
498
+ module Products
499
+ class Index
500
+ include Hanami::View
501
+ end
502
+
503
+ class Show
504
+ include Hanami::View
505
+
506
+ def footer
507
+ "contents for footer"
508
+ end
509
+ end
510
+ end
511
+ ```
512
+
513
+ ### Presenters
514
+
515
+ The goal of a presenter is to wrap and reuse presentational logic for an object.
516
+
517
+ ```ruby
518
+ class Map
519
+ attr_reader :locations
520
+
521
+ def initialize(locations)
522
+ @locations = locations
523
+ end
524
+
525
+ def location_names
526
+ @locations.join(', ')
527
+ end
528
+ end
529
+
530
+ class MapPresenter
531
+ include Hanami::Presenter
532
+
533
+ def count
534
+ locations.count
535
+ end
536
+
537
+ def location_names
538
+ super.upcase
539
+ end
540
+
541
+ def inspect_object
542
+ @object.inspect
543
+ end
544
+ end
545
+
546
+ map = Map.new(['Rome', 'Boston'])
547
+ presenter = MapPresenter.new(map)
548
+
549
+ # access a map method
550
+ puts presenter.locations # => ['Rome', 'Boston']
551
+
552
+ # access presenter concrete methods
553
+ puts presenter.count # => 2
554
+
555
+ # uses super to access original object implementation
556
+ puts presenter.location_names # => 'ROME, BOSTON'
557
+
558
+ # it has private access to the original object
559
+ puts presenter.inspect_object # => #<Map:0x007fdeada0b2f0 @locations=["Rome", "Boston"]>
560
+ ```
561
+
562
+ ### Configuration
563
+
564
+ __Hanami::View__ can be configured with a DSL that determines its behavior.
565
+ It supports a few options:
566
+
567
+ ```ruby
568
+ require 'hanami/view'
569
+
570
+ Hanami::View.configure do
571
+ # Set the root path where to search for templates
572
+ # Argument: String, Pathname, #to_pathname, defaults to the current directory
573
+ #
574
+ root '/path/to/root'
575
+
576
+ # Default encoding for templates
577
+ # Argument: String, defaults to utf-8
578
+ #
579
+ default_encoding 'koi-8'
580
+
581
+ # Set the Ruby namespace where to lookup for views
582
+ # Argument: Class, Module, String, defaults to Object
583
+ #
584
+ namespace 'MyApp::Views'
585
+
586
+ # Set the global layout
587
+ # Argument: Symbol, defaults to nil
588
+ #
589
+ layout :application
590
+
591
+ # Set modules that you want to include in all views
592
+ # Argument: Block
593
+ #
594
+ prepare do
595
+ include MyCustomModule
596
+ before { do_something }
597
+ end
598
+ end
599
+ ```
600
+
601
+ All those global configurations can be overwritten at a finer grained level:
602
+ views. Each view and layout has its own copy of the global configuration, so
603
+ that changes are inherited from the top to the bottom, but not bubbled up in the
604
+ opposite direction.
605
+
606
+ ```ruby
607
+ require 'hanami/view'
608
+
609
+ Hanami::View.configure do
610
+ root '/path/to/root'
611
+ end
612
+
613
+ class Show
614
+ include Hanami::View
615
+ root '/another/root'
616
+ end
617
+
618
+ Hanami::View.configuration.root # => #<Pathname:/path/to/root>
619
+ Show.root # => #<Pathname:/another/root>
620
+ ```
621
+
622
+ ### Reusability
623
+
624
+ __Hanami::View__ can be used as a singleton framework as seen in this README.
625
+ The application code includes `Hanami::View` or `Hanami::Layout` directly
626
+ and the configuration is unique per Ruby process.
627
+
628
+ While this is convenient for tiny applications, it doesn't fit well for more
629
+ complex scenarios, where we want micro applications to coexist together.
630
+
631
+ ```ruby
632
+ require 'hanami/view'
633
+
634
+ Hanami::View.configure do
635
+ root '/path/to/root'
636
+ end
637
+
638
+ module WebApp
639
+ View = Hanami::View.duplicate(self)
640
+ end
641
+
642
+ module ApiApp
643
+ View = Hanami::View.duplicate(self) do
644
+ root '/another/root'
645
+ end
646
+ end
647
+
648
+ Hanami::View.configuration.root # => #<Pathname:/path/to/root>
649
+ WebApp::View.configuration.root # => #<Pathname:/path/to/root>, inherited from Hanami::View
650
+ ApiApp::View.configuration.root # => #<Pathname:/another/root>
651
+ ```
652
+
653
+ The code above defines `WebApp::View` and `WebApp::Layout`, to be used for
654
+ the `WebApp` views, while `ApiApp::View` and `ApiApp::Layout` have a different
655
+ configuration.
656
+
657
+ ### Thread safety
658
+
659
+ __Hanami::View__ is thread safe during the runtime, but it isn't during the loading process.
660
+ Please load the framework as the last thing before your application starts.
661
+ Also, be sure that your app provides a thread safe context while it's loaded.
662
+
663
+
664
+ ```ruby
665
+ Mutex.new.synchronize do
666
+ Hanami::View.load!
667
+ end
668
+ ```
669
+
670
+ After this operation, all the class variables are frozen, in order to prevent accidental modifications at the run time.
671
+
672
+ **This is not necessary, when Hanami::View is used within a Hanami application.**
673
+
674
+ ### Security
28
675
 
29
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
676
+ The output of views and presenters is always **autoescaped**.
30
677
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
678
+ **ATTENTION:** In order to prevent XSS attacks, please read the instructions below.
679
+ Because Hanami::View supports a lot of template engines, the escape happens at the level of the view.
680
+ Most of the time everything happens automatically, but there are still some corner cases that need your manual intervention.
681
+
682
+ #### View autoescape
683
+
684
+ ```ruby
685
+ require 'hanami/view'
686
+
687
+ User = Struct.new(:name)
688
+
689
+ module Users
690
+ class Show
691
+ include Hanami::View
692
+
693
+ def user_name
694
+ user.name
695
+ end
696
+ end
697
+ end
698
+
699
+ # ERB template
700
+ # <div id="user_name"><%= user_name %></div>
701
+
702
+ user = User.new("<script>alert('xss')</script>")
703
+
704
+ # THIS IS USEFUL FOR UNIT TESTING:
705
+ template = Hanami::View::Template.new('users/show.html.erb')
706
+ view = Users::Show.new(template, user: user)
707
+ view.user_name # => "&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;"
708
+
709
+ # THIS IS THE RENDERING OUTPUT:
710
+ Users::Show.render(format: :html, user: user)
711
+ # => <div id="user_name">&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</div>
712
+ ```
713
+
714
+ #### Presenter autoescape
715
+
716
+ ```ruby
717
+ require 'hanami/view'
718
+
719
+ User = Struct.new(:name)
720
+
721
+ class UserPresenter
722
+ include Hanami::Presenter
723
+ end
724
+
725
+ user = User.new("<script>alert('xss')</script>")
726
+ presenter = UserPresenter.new(user)
727
+
728
+ presenter.name # => "&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;"
729
+ ```
730
+
731
+ #### Escape entire objects
732
+
733
+ We have seen that concrete methods in views are automatically escaped.
734
+ This is great, but tedious if you need to print a lot of information from a given object.
735
+
736
+ Imagine you have `user` as part of the view locals.
737
+ If you want to use `<%= user.name %>` directly, **you're still vulnerable to XSS attacks**.
738
+
739
+ You have two alternatives:
740
+
741
+ * To use a concrete presenter (eg. `UserPresenter`)
742
+ * Escape the entire object (see the example below)
743
+
744
+ Both those solutions allow you to keep the template syntax unchanged, but to have a safer output.
745
+
746
+ ```ruby
747
+ require 'hanami/view'
748
+
749
+ User = Struct.new(:first_name, :last_name)
750
+
751
+ module Users
752
+ class Show
753
+ include Hanami::View
754
+
755
+ def user
756
+ _escape locals[:user]
757
+ end
758
+ end
759
+ end
760
+
761
+ # ERB template:
762
+ #
763
+ # <div id="first_name">
764
+ # <%= user.first_name %>
765
+ # </div>
766
+ # <div id="last_name">
767
+ # <%= user.last_name %>
768
+ # </div>
769
+
770
+ first_name = "<script>alert('first_name')</script>"
771
+ last_name = "<script>alert('last_name')</script>"
772
+
773
+ user = User.new(first_name, last_name)
774
+ html = Users::Show.render(format: :html, user: user)
775
+
776
+ html
777
+ # =>
778
+ # <div id="first_name">
779
+ # &lt;script&gt;alert(&apos;first_name&apos;)&lt;&#x2F;script&gt;
780
+ # </div>
781
+ # <div id="last_name">
782
+ # &lt;script&gt;alert(&apos;last_name&apos;)&lt;&#x2F;script&gt;
783
+ # </div>
784
+ ```
785
+
786
+ #### Raw contents
787
+
788
+ You can use `_raw` to mark an output as safe.
789
+ Please note that **this may open your application to XSS attacks.**
790
+
791
+ #### Raw contents in views
792
+
793
+ ```ruby
794
+ require 'hanami/view'
795
+
796
+ User = Struct.new(:name)
797
+
798
+ module Users
799
+ class Show
800
+ include Hanami::View
801
+
802
+ def user_name
803
+ _raw user.name
804
+ end
805
+ end
806
+ end
807
+
808
+ # ERB template
809
+ # <div id="user_name"><%= user_name %></div>
810
+
811
+ user = User.new("<script>alert('xss')</script>")
812
+ html = Users::Show.render(format: :html, user: user)
813
+
814
+ html
815
+ # => <div id="user_name"><script>alert('xss')</script></div>
816
+ ```
817
+
818
+ #### Raw contents in presenters
819
+
820
+ ```ruby
821
+ require 'hanami/view'
822
+
823
+ User = Struct.new(:name)
824
+
825
+ class UserPresenter
826
+ include Hanami::Presenter
827
+
828
+ def first_name
829
+ _raw @object.first_name
830
+ end
831
+ end
832
+
833
+ user = User.new("<script>alert('xss')</script>")
834
+ presenter = UserPresenter.new(user)
835
+
836
+ presenter.name # => "<script>alert('xss')</script>"
837
+ ```
838
+
839
+ ## Versioning
840
+
841
+ __Hanami::View__ uses [Semantic Versioning 2.0.0](http://semver.org)
32
842
 
33
843
  ## Contributing
34
844
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/hanami-view.
845
+ 1. Fork it
846
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
847
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
848
+ 4. Push to the branch (`git push origin my-new-feature`)
849
+ 5. Create new Pull Request
850
+
851
+ ## Copyright
852
+
853
+ Copyright 2014-2016 Luca Guidi – Released under MIT License
36
854
 
855
+ This project was formerly known as Lotus (`lotus-view`).