tiny 0.2.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.
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/README.md +388 -0
- data/Rakefile +1 -0
- data/lib/tiny.rb +519 -0
- data/lib/tiny/erubis.rb +63 -0
- data/lib/tiny/html.rb +177 -0
- data/lib/tiny/safe_string.rb +16 -0
- data/lib/tiny/tag.rb +47 -0
- data/lib/tiny/version.rb +3 -0
- data/spec/erubis_spec.rb +48 -0
- data/spec/fixtures/erb_list.erb +20 -0
- data/spec/fixtures/erb_list_with_helpers.erb +5 -0
- data/spec/fixtures/haml_list.haml +16 -0
- data/spec/fixtures/haml_list_with_helpers.haml +4 -0
- data/spec/helpers_spec.rb +303 -0
- data/spec/integration/erb_spec.rb +83 -0
- data/spec/integration/haml_spec.rb +59 -0
- data/spec/integration/rails_spec.rb +42 -0
- data/spec/integration/sinatra_spec.rb +42 -0
- data/spec/spec_helper.rb +54 -0
- data/spec/support/form_helper.rb +34 -0
- data/spec/support/list_helper.rb +14 -0
- data/spec/support/rails_app.rb +44 -0
- data/spec/support/sinatra_app.rb +26 -0
- data/spec/widget_spec.rb +146 -0
- data/tiny.gemspec +31 -0
- metadata +237 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,388 @@
|
|
1
|
+
# tiny
|
2
|
+
|
3
|
+
Tiny is a framework agnostic markup builder. It is useful for defining
|
4
|
+
view helpers or generating HTML markup using ruby objects, leveraging
|
5
|
+
inheritance and composition while defining templates.
|
6
|
+
|
7
|
+
It is inspired by Erector and Markaby but with a minimalistic aproach
|
8
|
+
and it opts for evaluating content blocks in their original context
|
9
|
+
rather than using instance\_eval thus instance variables need not to be
|
10
|
+
"smuggled in". It also attempts to be a tiny framework for defining view
|
11
|
+
helpers to be used in ERB and HAML templates from Rails, Sinatra or any
|
12
|
+
other framework.
|
13
|
+
|
14
|
+
It provides a mixin for inline building HTML markup from any class or to
|
15
|
+
define pure ruby object templates with all object-oriented programming
|
16
|
+
advantages such as inheritance and encapsulation.
|
17
|
+
|
18
|
+
Tiny is pretty much fully documented. Please check [Tiny
|
19
|
+
Rdoc](http://rdoc.info/github/maca/tiny/master/file/README.md) for more
|
20
|
+
info.
|
21
|
+
|
22
|
+
|
23
|
+
## Install
|
24
|
+
|
25
|
+
$ gem install tiny
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
require 'tiny'
|
30
|
+
|
31
|
+
class MyPage < Tiny::Widget
|
32
|
+
def markup
|
33
|
+
html do
|
34
|
+
head do
|
35
|
+
title "Hello"
|
36
|
+
end
|
37
|
+
body do
|
38
|
+
h1 "Hello"
|
39
|
+
p :class => 'content' do
|
40
|
+
text "Lorem ipsum..."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
MyPage.new.to_html
|
47
|
+
# => <html>
|
48
|
+
<head>
|
49
|
+
<title>Hello</title>
|
50
|
+
</head>
|
51
|
+
<body>
|
52
|
+
<h1>Hello</h1>
|
53
|
+
<p>
|
54
|
+
Lorem ipsum...
|
55
|
+
</p>
|
56
|
+
</body>
|
57
|
+
</html>
|
58
|
+
|
59
|
+
|
60
|
+
## Markup Helpers
|
61
|
+
|
62
|
+
There are a few was of generating markup with Tiny, while one is by
|
63
|
+
encapsulated classes inheriting from `Tiny::Widget`, another one is
|
64
|
+
doing it inline by including `Tiny::Helpers` in any context.
|
65
|
+
|
66
|
+
Including `Tiny::Helpers` gives access to a handfull of methods, the
|
67
|
+
basic one is `html_tag` aliased as `tag`.
|
68
|
+
|
69
|
+
include Tiny::Helpers
|
70
|
+
|
71
|
+
tag(:ul) do
|
72
|
+
tag(:li) do
|
73
|
+
tag :a, 'Home', :class => 'home', :href => '/'
|
74
|
+
end
|
75
|
+
...
|
76
|
+
end
|
77
|
+
# => <ul>
|
78
|
+
<li>
|
79
|
+
<a class="home" href="/">Home</a>
|
80
|
+
</li>
|
81
|
+
...
|
82
|
+
</ul>
|
83
|
+
|
84
|
+
## HTML tags
|
85
|
+
|
86
|
+
Tags are self closed or explicitly closed depeding on the tag name.
|
87
|
+
Attributes are HTML-escaped and mapped as follows:
|
88
|
+
|
89
|
+
tag(:link, :href => 'my-styles.css')
|
90
|
+
# => <link href="my-styles.css" />
|
91
|
+
tag(:li, 'Bicycle', :class => ['with-discount', 'in-stock'])
|
92
|
+
# => <li class="with-discount in-stock">Bicycle</li>
|
93
|
+
tag(:textarea, :disabled => true)
|
94
|
+
# => <textarea disabled></textarea>
|
95
|
+
tag(:textarea, :disabled => false)
|
96
|
+
# => <textarea></textarea>
|
97
|
+
|
98
|
+
Tag content can be defined either by passing a string and optionally an
|
99
|
+
attributes hash or by passing a content block.
|
100
|
+
|
101
|
+
## Markup
|
102
|
+
|
103
|
+
Other methods for generating markup are `text` for appending HTML
|
104
|
+
escaped `text`, `text!` or `append!` for appending HTML, `comment`, `cdata`
|
105
|
+
and `doctype`.
|
106
|
+
|
107
|
+
The method `with_buffer` is for capturing template content, just like
|
108
|
+
Rails `capture` but it also serves for concatenating content.
|
109
|
+
|
110
|
+
with_buffer do
|
111
|
+
tag(:h1, "Hello")
|
112
|
+
tag(:p, "Lorem ipsum...")
|
113
|
+
end
|
114
|
+
# => <h1>Hello</h1>
|
115
|
+
<p>Lorem ipsum...</p>
|
116
|
+
|
117
|
+
## Rails
|
118
|
+
|
119
|
+
Tiny ActionView helpers are allready included in ActionView, no further
|
120
|
+
step is required for using Tiny in Rails view helpers, just use `html_tag`
|
121
|
+
instead of `tag` because ActionView allready defines `tag`.
|
122
|
+
|
123
|
+
The advantage over Rails' markup method such as `tag` and `content\_tag` is
|
124
|
+
that generated strings need not to be explicitly concatenated.
|
125
|
+
|
126
|
+
In addition to defining view helpers to be used from templates, a
|
127
|
+
`Widget` can substitute a template view with the benefit of inheritance.
|
128
|
+
No template handler es provided but is not cumbersome explicitly
|
129
|
+
rendering the Widget.
|
130
|
+
|
131
|
+
controller Products
|
132
|
+
def index
|
133
|
+
products = Product.all
|
134
|
+
render :text => MyProductList.new(products).to_html
|
135
|
+
end
|
136
|
+
...
|
137
|
+
end
|
138
|
+
|
139
|
+
## Sinatra
|
140
|
+
|
141
|
+
For using the markup helpers:
|
142
|
+
|
143
|
+
class MyApp < Sinatra::Base
|
144
|
+
helpers Tiny::Helpers
|
145
|
+
|
146
|
+
get '/home' do
|
147
|
+
with_buffer do
|
148
|
+
doctype
|
149
|
+
html do
|
150
|
+
...
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
Rendering `Tiny::Widgets`
|
157
|
+
|
158
|
+
class MyApp < Sinatra::Base
|
159
|
+
get '/products' do
|
160
|
+
products = Product.all
|
161
|
+
MyProductList.new(products).to_html
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
## Shortcuts
|
167
|
+
|
168
|
+
Including `Tiny::HTML` gives access to shortcuts for HTML tags. Caution
|
169
|
+
must be exercised because its quite a few methods.
|
170
|
+
|
171
|
+
## View Inheritance
|
172
|
+
|
173
|
+
class Template < Tiny::Widget
|
174
|
+
def markup
|
175
|
+
doctype
|
176
|
+
html do
|
177
|
+
head do
|
178
|
+
title @title
|
179
|
+
end
|
180
|
+
body do
|
181
|
+
navigation
|
182
|
+
section(:id => 'content') do
|
183
|
+
yield
|
184
|
+
end
|
185
|
+
footer_content
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def navigation
|
191
|
+
nav(:id) do
|
192
|
+
tag(:ul) do
|
193
|
+
tag(:li) do
|
194
|
+
tag :a, 'Home', :class => 'home', :href => '/'
|
195
|
+
end
|
196
|
+
tag(:li) do
|
197
|
+
tag :a, 'About', :class => 'about', :href => '/about'
|
198
|
+
end
|
199
|
+
tag(:li) do
|
200
|
+
tag :a, 'Home', :class => 'products', :href => '/products'
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def footer_content
|
207
|
+
footer "© 2012"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
class HomePage < Template
|
212
|
+
def initialize
|
213
|
+
@title = "Home"
|
214
|
+
end
|
215
|
+
|
216
|
+
def markup
|
217
|
+
super do
|
218
|
+
h1 "Welcome!!"
|
219
|
+
p "Lorem ipsum..."
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
HomePage.new.to_html
|
225
|
+
# => <!DOCTYPE html>
|
226
|
+
<html>
|
227
|
+
<head>
|
228
|
+
<title>Home</title>
|
229
|
+
</head>
|
230
|
+
<body>
|
231
|
+
<nav>
|
232
|
+
<ul>
|
233
|
+
<li>
|
234
|
+
<a class="home" href="/">Home</a>
|
235
|
+
</li>
|
236
|
+
<li>
|
237
|
+
<a class="about" href="/about">About</a>
|
238
|
+
</li>
|
239
|
+
<li>
|
240
|
+
<a class="products" href="/products">Home</a>
|
241
|
+
</li>
|
242
|
+
</ul>
|
243
|
+
</nav>
|
244
|
+
<section id="content">
|
245
|
+
<h1>Welcome!!</h1>
|
246
|
+
<p>Lorem ipsum...</p>
|
247
|
+
</section>
|
248
|
+
<footer>
|
249
|
+
© 2012
|
250
|
+
</footer>
|
251
|
+
</body>
|
252
|
+
</html>
|
253
|
+
|
254
|
+
|
255
|
+
## View helpers for HAML and ERB templates
|
256
|
+
|
257
|
+
One of the Tiny's main goals is providing facilities for defining view
|
258
|
+
helpers that can be used from Ruby or templating laguages regardless of
|
259
|
+
the web framework.
|
260
|
+
|
261
|
+
A Widget can take a block while calling `to_html`. Tiny can determine
|
262
|
+
wether the block was originated in an ERB or HAML template or not and
|
263
|
+
treat it accordingly. #to\_html forwards the passed block to #markup but
|
264
|
+
concatenates the result of calling it.
|
265
|
+
|
266
|
+
class MyForm < Tiny::Widget
|
267
|
+
def initialize(action)
|
268
|
+
@action = action
|
269
|
+
end
|
270
|
+
|
271
|
+
def markup
|
272
|
+
form(:action => @action) do
|
273
|
+
fieldset do
|
274
|
+
yield(self)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def text_input(name, value)
|
280
|
+
TextInput.new(name, value).to_html
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
class TextInput < Tiny::Widget
|
285
|
+
def initialize(name, value)
|
286
|
+
@name, @value = name, value
|
287
|
+
end
|
288
|
+
|
289
|
+
def markup
|
290
|
+
label(@name.capitalize, :for => @name)
|
291
|
+
input(:type => 'text', :id => @name, :name => @name, :value => @value)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def my_form(action, &block)
|
296
|
+
# the block is forwarded to MyForm#to_html
|
297
|
+
MyForm.new(action).to_html(&block)
|
298
|
+
end
|
299
|
+
|
300
|
+
Using the helper from an ERB template, note that Tiny allows explicitly
|
301
|
+
concatenating calls with blocks just like with Rails ERB.
|
302
|
+
|
303
|
+
<%= my_form('/login') do |form| %>
|
304
|
+
<%= form.text_input 'email', 'email@example.com' %>
|
305
|
+
<% end %>
|
306
|
+
# => <form action="/login">
|
307
|
+
...
|
308
|
+
<fieldset>
|
309
|
+
<label for="email">Email</label>
|
310
|
+
<input type="text" id="email" name="email" value="email@example.com" />
|
311
|
+
</fieldset>
|
312
|
+
...
|
313
|
+
</form>
|
314
|
+
|
315
|
+
Using the same helper from Ruby:
|
316
|
+
|
317
|
+
my_form('/login') do |form|
|
318
|
+
append! form.text_input 'email', 'email@example.com'
|
319
|
+
end
|
320
|
+
# => <form action="/login">
|
321
|
+
...
|
322
|
+
<fieldset>
|
323
|
+
<label for="email">Email</label>
|
324
|
+
<input type="text" id="email" name="email" value="email@example.com" />
|
325
|
+
</fieldset>
|
326
|
+
...
|
327
|
+
</form>
|
328
|
+
|
329
|
+
## HTML Representation For Any Object.
|
330
|
+
|
331
|
+
By including the `Rendering` any object can emit it's HTML representation.
|
332
|
+
Whether this is or isn't a good idea is up to you.
|
333
|
+
|
334
|
+
class User < Model
|
335
|
+
include Tiny::Rendering
|
336
|
+
|
337
|
+
def markup
|
338
|
+
div(:id => "user-#{self.id}") do
|
339
|
+
img :src => self.avatar_url
|
340
|
+
dl do
|
341
|
+
dt "First Name"
|
342
|
+
dd self.first_name
|
343
|
+
dt "Last Name"
|
344
|
+
dd self.last_name
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
user = User.create(:first_name => 'Macario',
|
351
|
+
:last_name => 'Ortega',
|
352
|
+
:avatar_url => 'http://example.com/profile/dbg.jpeg')
|
353
|
+
user.to_html
|
354
|
+
# => <div id="user-1">
|
355
|
+
<img src="http://example.com/profile/dbg.jpeg" />
|
356
|
+
<dl>
|
357
|
+
<dt>First Name</dt>
|
358
|
+
<dd>Macario</dd>
|
359
|
+
<dt>Last Name</dt>
|
360
|
+
<dd>Ortega</dd>
|
361
|
+
</dl>
|
362
|
+
</div>
|
363
|
+
|
364
|
+
|
365
|
+
== LICENSE:
|
366
|
+
|
367
|
+
(The MIT License)
|
368
|
+
|
369
|
+
Copyright (c) 2012 Macario Ortega
|
370
|
+
|
371
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
372
|
+
a copy of this software and associated documentation files (the
|
373
|
+
'Software'), to deal in the Software without restriction, including
|
374
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
375
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
376
|
+
permit persons to whom the Software is furnished to do so, subject to
|
377
|
+
the following conditions:
|
378
|
+
|
379
|
+
The above copyright notice and this permission notice shall be
|
380
|
+
included in all copies or substantial portions of the Software.
|
381
|
+
|
382
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
383
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
384
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
385
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
386
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
387
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
388
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/tiny.rb
ADDED
@@ -0,0 +1,519 @@
|
|
1
|
+
require 'erubis'
|
2
|
+
require 'haml'
|
3
|
+
require 'tilt'
|
4
|
+
require 'escape_utils'
|
5
|
+
|
6
|
+
require 'tiny/version'
|
7
|
+
require 'tiny/erubis'
|
8
|
+
require 'tiny/safe_string'
|
9
|
+
require 'tiny/tag'
|
10
|
+
require 'tiny/html'
|
11
|
+
|
12
|
+
module Tiny
|
13
|
+
# Provides basic markup generation support.
|
14
|
+
module Markup
|
15
|
+
# Generates an HTML tag or structured HTML markup
|
16
|
+
#
|
17
|
+
# This method is the basis for defining html helper functions or
|
18
|
+
# constructing html markup using pure ruby.
|
19
|
+
#
|
20
|
+
# HTML attributes are HTML-escaped and tags are explicitly or self
|
21
|
+
# closed depeding on the tag name.
|
22
|
+
#
|
23
|
+
# Calls to markup generating methods within the content block are
|
24
|
+
# appended to the tag's content.
|
25
|
+
# See {Markup} and {HTML}.
|
26
|
+
#
|
27
|
+
# Content blocks originating from HAML or ERB templates are
|
28
|
+
# correctly captured and handled.
|
29
|
+
#
|
30
|
+
# @param name [Symbol, String] The name of the tag.
|
31
|
+
# @param attrs_or_content [Hash, String] Tag's attributes or content.
|
32
|
+
# @param attrs [Hash] Tag's attributes if content string passed.
|
33
|
+
# @yield Content block.
|
34
|
+
#
|
35
|
+
# @return [String] HTML markup
|
36
|
+
#
|
37
|
+
# @example Attribute mapping
|
38
|
+
#
|
39
|
+
# html_tag(:link, :href => 'my-styles.css')
|
40
|
+
# # => <link href="my-styles.css" />
|
41
|
+
# html_tag(:li, 'Bicycle', :class => ['with-discount', 'in-stock'])
|
42
|
+
# # => <li class="with-discount in-stock">Bicycle</li>
|
43
|
+
# html_tag(:textarea, :disabled => true)
|
44
|
+
# # => <textarea disabled></textarea>
|
45
|
+
# html_tag(:textarea, :disabled => false)
|
46
|
+
# # => <textarea></textarea>
|
47
|
+
#
|
48
|
+
# @example HTML-escaping
|
49
|
+
#
|
50
|
+
# html_tag(:a, 'Art&Copy', :href => '/art©')
|
51
|
+
# # => <a href="/art&copy">Art&copy</a>
|
52
|
+
#
|
53
|
+
# @example Tag closing
|
54
|
+
#
|
55
|
+
# html_tag(:p) # => <p></p>
|
56
|
+
# html_tag(:link) # => <link />
|
57
|
+
#
|
58
|
+
# @example Content block
|
59
|
+
#
|
60
|
+
# html_tag(:ul) do
|
61
|
+
# html_tag(:li, 'Cheese')
|
62
|
+
# html_tag(:li, 'Ham')
|
63
|
+
# html_tag(:li, 'Milk')
|
64
|
+
# end
|
65
|
+
# # => <ul>
|
66
|
+
# <li>Cheese</li>
|
67
|
+
# <li>Haml</li>
|
68
|
+
# <li>Milk</li>
|
69
|
+
# </ul>
|
70
|
+
#
|
71
|
+
# html_tag(:p) do
|
72
|
+
# text 'Neque porro quisquam est qui dolorem...'
|
73
|
+
# 'this string will be ignored'
|
74
|
+
# end
|
75
|
+
# # => <p>
|
76
|
+
# Neque porro quisquam est qui dolorem...
|
77
|
+
# </p>
|
78
|
+
#
|
79
|
+
# @example ERB blocks
|
80
|
+
#
|
81
|
+
# !!!ruby
|
82
|
+
# # application_helper.rb
|
83
|
+
# module ApplicationHelper
|
84
|
+
# def my_form(url, &block)
|
85
|
+
# html_tag(:form, :action => url) do
|
86
|
+
# ...
|
87
|
+
# html_tag(:fieldset, &block)
|
88
|
+
# ...
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# def text_input(name, value)
|
93
|
+
# html_tag(:input, :type => 'text', :name => name, :value => value)
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# # form.erb
|
98
|
+
# <%= my_form '/login' do %>
|
99
|
+
# <%= text_input 'email', @email %>
|
100
|
+
# <% end %>
|
101
|
+
# # => <form action="/login">
|
102
|
+
# ...
|
103
|
+
# <fieldset>
|
104
|
+
# <input type="text" name="email" value="email@example.com" />
|
105
|
+
# </fieldset>
|
106
|
+
# ...
|
107
|
+
# </form>
|
108
|
+
#
|
109
|
+
#
|
110
|
+
def html_tag name, attrs_or_content = {}, attrs = nil, &block
|
111
|
+
append! Tag.new(name, attrs_or_content, attrs).render(&block)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Appends an HTML coment to the content.
|
115
|
+
#
|
116
|
+
# div do
|
117
|
+
# comment 'foo'
|
118
|
+
# end
|
119
|
+
# # => <div>
|
120
|
+
# <!-- foo -->
|
121
|
+
# </div>
|
122
|
+
#
|
123
|
+
# @return [SafeString] HTML content.
|
124
|
+
#
|
125
|
+
def comment content
|
126
|
+
append! "<!-- #{content.to_s.gsub(/-(?=-)/, '- ')} -->"
|
127
|
+
end
|
128
|
+
|
129
|
+
# Appends a CDATA section to the content.
|
130
|
+
#
|
131
|
+
# div do
|
132
|
+
# cdata 'foo'
|
133
|
+
# end
|
134
|
+
# # => <div>
|
135
|
+
# <![CDATA[foo]]>
|
136
|
+
# </div>
|
137
|
+
#
|
138
|
+
# @return [String] CDATA section.
|
139
|
+
#
|
140
|
+
def cdata content
|
141
|
+
content = content.to_s.gsub(']]>', ']]]]><![CDATA[>')
|
142
|
+
append! "<![CDATA[#{content}]]>"
|
143
|
+
end
|
144
|
+
|
145
|
+
# Appends the doctype to the content
|
146
|
+
#
|
147
|
+
# with_buffer do
|
148
|
+
# doctype
|
149
|
+
# html_tag(:html) do
|
150
|
+
# ...
|
151
|
+
# end
|
152
|
+
# end
|
153
|
+
# # => <!DOCTYPE html>
|
154
|
+
# <html>
|
155
|
+
# ...
|
156
|
+
# </html>
|
157
|
+
#
|
158
|
+
# @return [String] Doctype.
|
159
|
+
#
|
160
|
+
def doctype
|
161
|
+
append! "<!DOCTYPE html>"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Buffering and capturing support.
|
166
|
+
module Buffering
|
167
|
+
# Appends sanitized text to the content.
|
168
|
+
#
|
169
|
+
# html_tag(:p) do
|
170
|
+
# text 'Foo &'
|
171
|
+
# text 'Bar'
|
172
|
+
# end
|
173
|
+
# # => <p>
|
174
|
+
# Foo &
|
175
|
+
# Bar
|
176
|
+
# </p>
|
177
|
+
#
|
178
|
+
# @return [String] HTML-escaped.
|
179
|
+
#
|
180
|
+
def append string
|
181
|
+
string = Helpers.sanitize(string)
|
182
|
+
if working_buffer
|
183
|
+
working_buffer << string.gsub(/(?<!^|\n)\z/, "\n")
|
184
|
+
else
|
185
|
+
string
|
186
|
+
end
|
187
|
+
end
|
188
|
+
alias text append
|
189
|
+
|
190
|
+
# Appends non HTML-escaped text to the content.
|
191
|
+
#
|
192
|
+
# html_tag(:p) do
|
193
|
+
# append! 'Foo & Bar'
|
194
|
+
# append! '<evil>'
|
195
|
+
# end
|
196
|
+
# # => <p>
|
197
|
+
# Foo & Bar
|
198
|
+
# <evil>
|
199
|
+
# </p>
|
200
|
+
#
|
201
|
+
# Shortcut for
|
202
|
+
#
|
203
|
+
# append raw(content)
|
204
|
+
#
|
205
|
+
# @return [SafeString] Non HTML-escaped.
|
206
|
+
#
|
207
|
+
def append! content
|
208
|
+
append raw(content)
|
209
|
+
end
|
210
|
+
alias text! append!
|
211
|
+
|
212
|
+
# Returns content that won't be HTML escaped when appended to content.
|
213
|
+
#
|
214
|
+
# @return [SafeString] Considered html safe.
|
215
|
+
#
|
216
|
+
def raw val
|
217
|
+
SafeString.new val.to_s
|
218
|
+
end
|
219
|
+
|
220
|
+
# Buffers calls to markup generating methods.
|
221
|
+
# @see Markup
|
222
|
+
# @see HTML
|
223
|
+
#
|
224
|
+
# @example Not using #with_buffer Only the last tag is returned.
|
225
|
+
# def my_helper
|
226
|
+
# html_tag(:span, 'Foo')
|
227
|
+
# html_tag(:span, 'Bar')
|
228
|
+
# end
|
229
|
+
# my_helper()
|
230
|
+
# # => <span>Bar</span>
|
231
|
+
#
|
232
|
+
# @example By using #with_buffer structured markup is generated.
|
233
|
+
# def my_helper
|
234
|
+
# with_buffer do
|
235
|
+
# html_tag(:span, 'Foo')
|
236
|
+
# html_tag(:span, 'Bar')
|
237
|
+
# end
|
238
|
+
# end
|
239
|
+
# my_helper()
|
240
|
+
# # => <span>Foo</span>
|
241
|
+
# <span>Bar</span>
|
242
|
+
#
|
243
|
+
# @param args [any] n number of arguments to be passed to block evaluation.
|
244
|
+
# @yield [*args] Content block.
|
245
|
+
#
|
246
|
+
# @return [String] HTML markup.
|
247
|
+
#
|
248
|
+
def with_buffer *args, &block
|
249
|
+
buffer_stack << ''
|
250
|
+
yield *args
|
251
|
+
buffer_stack.pop
|
252
|
+
end
|
253
|
+
|
254
|
+
private
|
255
|
+
# Pushing and popping.
|
256
|
+
def buffer_stack
|
257
|
+
@buffer_stack ||= []
|
258
|
+
end
|
259
|
+
|
260
|
+
# Current buffer from the buffer stack.
|
261
|
+
def working_buffer
|
262
|
+
buffer_stack.last
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Provides support for using Tiny helpers within a HAML template.
|
267
|
+
module HamlTemplating
|
268
|
+
include Buffering
|
269
|
+
include Markup
|
270
|
+
|
271
|
+
# Extracts a section of a HAML template or buffers a block not
|
272
|
+
# originated from an HAML template. Akin to Rails capture method.
|
273
|
+
#
|
274
|
+
# @see Buffering#with_buffer
|
275
|
+
#
|
276
|
+
# @param args [any] n number of arguments to be passed to block evaluation.
|
277
|
+
# @yield [*args] HAML block or content block.
|
278
|
+
# @return [String] HTML markup.
|
279
|
+
#
|
280
|
+
def with_buffer *args, &block
|
281
|
+
::Haml::Helpers.block_is_haml?(block) ? capture_haml(*args, &block) : super
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Provides support for using Tiny helpers within an Erubis template.
|
286
|
+
module ErubisTemplating
|
287
|
+
include Buffering
|
288
|
+
include Markup
|
289
|
+
|
290
|
+
# Extracts a section of a ERB template or buffers a block not
|
291
|
+
# originated from an ERB template. Akin to Rails capture method.
|
292
|
+
#
|
293
|
+
# @see Buffering#with_buffer
|
294
|
+
#
|
295
|
+
# @param args [any] n number of arguments to be passed to block evaluation.
|
296
|
+
# @yield [*args] ERB block or content block.
|
297
|
+
# @return [String] HTML markup.
|
298
|
+
#
|
299
|
+
def with_buffer *args, &block
|
300
|
+
erb_block?(block) ? capture_erb(*args, &block) : super
|
301
|
+
end
|
302
|
+
|
303
|
+
# Capture a section of an ERB template.
|
304
|
+
#
|
305
|
+
# @param args [any] n number of arguments to be passed to block evaluation
|
306
|
+
# @return [String] HTML markup
|
307
|
+
# @yield [*args]
|
308
|
+
#
|
309
|
+
def capture_erb *args, &block
|
310
|
+
output_buffer = eval('_buf', block.binding)
|
311
|
+
buffer = output_buffer.dup
|
312
|
+
output_buffer.clear and yield(*args)
|
313
|
+
return output_buffer.dup
|
314
|
+
ensure
|
315
|
+
output_buffer.replace buffer
|
316
|
+
end
|
317
|
+
|
318
|
+
# Was the block defined within an ERB template?
|
319
|
+
#
|
320
|
+
# @param block [Proc] a Proc object
|
321
|
+
#
|
322
|
+
def erb_block? block
|
323
|
+
block && eval('defined?(__in_erb_template)', block.binding)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Include this module anywhere you want to use markup generation, or
|
328
|
+
# define view helpers using Tiny.
|
329
|
+
module Helpers
|
330
|
+
include Buffering
|
331
|
+
include Markup
|
332
|
+
include ErubisTemplating
|
333
|
+
include HamlTemplating
|
334
|
+
|
335
|
+
# Alias for {Markup#html_tag}
|
336
|
+
#
|
337
|
+
# @return [String] HTML markup
|
338
|
+
#
|
339
|
+
def tag name, attrs_or_content = {}, attrs = nil, &block
|
340
|
+
html_tag name, attrs_or_content, attrs, &block
|
341
|
+
end
|
342
|
+
|
343
|
+
# HTML-escapes the passed value unless the content is considered
|
344
|
+
# safe ({SafeString#html_safe? html_safe?} is implemented and
|
345
|
+
# returns true)
|
346
|
+
#
|
347
|
+
# @param value [String, Object]
|
348
|
+
# @return [String]
|
349
|
+
#
|
350
|
+
def self.sanitize value
|
351
|
+
if value.respond_to?(:html_safe?) && value.html_safe?
|
352
|
+
value.to_s
|
353
|
+
else
|
354
|
+
EscapeUtils.escape_html value.to_s
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# Support for HTML markup generation for classes, can be included in
|
360
|
+
# any class that is to be represented with HTML markup.
|
361
|
+
# @see Widget
|
362
|
+
#
|
363
|
+
# @example
|
364
|
+
# class User < Model
|
365
|
+
# include Tiny::Rendering
|
366
|
+
#
|
367
|
+
# def markup
|
368
|
+
# div(:id => "user-#{self.id}") do
|
369
|
+
# img :src => self.avatar_url
|
370
|
+
# dl do
|
371
|
+
# dt "First Name"
|
372
|
+
# dd self.first_name
|
373
|
+
# dt "Last Name"
|
374
|
+
# dd self.last_name
|
375
|
+
# end
|
376
|
+
# end
|
377
|
+
# end
|
378
|
+
# end
|
379
|
+
#
|
380
|
+
# user = User.create(:first_name => 'Macario',
|
381
|
+
# :last_name => 'Ortega',
|
382
|
+
# :avatar_url => 'http://example.com/profile/dbg.jpeg')
|
383
|
+
# user.to_html
|
384
|
+
# # => <div id="user-1">
|
385
|
+
# <img src="http://example.com/profile/dbg.jpeg" />
|
386
|
+
# <dl>
|
387
|
+
# <dt>First Name</dt>
|
388
|
+
# <dd>Macario</dd>
|
389
|
+
# <dt>Last Name</dt>
|
390
|
+
# <dd>Ortega</dd>
|
391
|
+
# </dl>
|
392
|
+
# </div>
|
393
|
+
#
|
394
|
+
module Rendering
|
395
|
+
include HTML
|
396
|
+
include Helpers
|
397
|
+
|
398
|
+
# Override this method with specific markup.
|
399
|
+
#
|
400
|
+
# @yield [self] Content block (from calling to {#render}).
|
401
|
+
# @see Widget
|
402
|
+
#
|
403
|
+
def markup
|
404
|
+
raise NotImplementedError
|
405
|
+
end
|
406
|
+
|
407
|
+
# Renders the html markup specified by #markup.
|
408
|
+
#
|
409
|
+
# @return [String] HTML markup.
|
410
|
+
# @see Widget.
|
411
|
+
#
|
412
|
+
def render &block
|
413
|
+
with_buffer do
|
414
|
+
next markup unless block_given?
|
415
|
+
markup do |args|
|
416
|
+
context = eval('self', block.binding)
|
417
|
+
append! context.instance_eval{ with_buffer(*args, &block) }
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
alias to_html render
|
422
|
+
end
|
423
|
+
|
424
|
+
module ActionViewAdditions
|
425
|
+
include Buffering
|
426
|
+
include Markup
|
427
|
+
|
428
|
+
# Extracts a section of a template or buffers a block not
|
429
|
+
# originated from an template.
|
430
|
+
#
|
431
|
+
# @see Buffering#with_buffer
|
432
|
+
#
|
433
|
+
# @param args [any] n number of arguments to be passed to block evaluation.
|
434
|
+
# @yield [*args] HAML block or content block.
|
435
|
+
# @return [String] HTML markup.
|
436
|
+
#
|
437
|
+
def with_buffer *args, &block
|
438
|
+
block_from_template?(block) ? capture(*args, &block) : super
|
439
|
+
end
|
440
|
+
|
441
|
+
# Appends sanitized text to the content.
|
442
|
+
# @see Buffering#append
|
443
|
+
def append markup
|
444
|
+
super(markup).html_safe
|
445
|
+
end
|
446
|
+
|
447
|
+
# Returns true if the block was originated in an ERB or HAML template.
|
448
|
+
def block_from_template? block
|
449
|
+
block && eval('defined?(output_buffer)', block.binding) == 'local-variable'
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# @example
|
454
|
+
# class MyForm < Tiny::Widget
|
455
|
+
# def initialize(action)
|
456
|
+
# @action = action
|
457
|
+
# end
|
458
|
+
#
|
459
|
+
# def markup
|
460
|
+
# form(:action => @action) do
|
461
|
+
# fieldset do
|
462
|
+
# yield(self)
|
463
|
+
# end
|
464
|
+
# end
|
465
|
+
# end
|
466
|
+
#
|
467
|
+
# def text_input(name, value)
|
468
|
+
# TextInput.new(name, value).to_html
|
469
|
+
# end
|
470
|
+
# end
|
471
|
+
#
|
472
|
+
# class TextInput < Tiny::Widget
|
473
|
+
# def initialize(name, value)
|
474
|
+
# @name, @value = name, value
|
475
|
+
# end
|
476
|
+
#
|
477
|
+
# def markup
|
478
|
+
# label(@name.capitalize, :for => @name)
|
479
|
+
# input(:type => 'text', :id => @name, :name => @name, :value => @value)
|
480
|
+
# end
|
481
|
+
# end
|
482
|
+
#
|
483
|
+
# def my_form(action, &block)
|
484
|
+
# MyForm.new(action).to_html(&block)
|
485
|
+
# end
|
486
|
+
#
|
487
|
+
# my_form('/login') do |form|
|
488
|
+
# append! form.text_input 'email', 'email@example.com'
|
489
|
+
# end
|
490
|
+
# # => <form action="/login">
|
491
|
+
# ...
|
492
|
+
# <fieldset>
|
493
|
+
# <label for="email">Email</label>
|
494
|
+
# <input type="text" id="email" name="email" value="email@example.com" />
|
495
|
+
# </fieldset>
|
496
|
+
# ...
|
497
|
+
# </form>
|
498
|
+
#
|
499
|
+
# # from template
|
500
|
+
# <%= my_form('/login') do |form| %>
|
501
|
+
# <%= form.text_input 'email', 'email@example.com' %>
|
502
|
+
# <% end %>
|
503
|
+
# # => <form action="/login">
|
504
|
+
# ...
|
505
|
+
# <fieldset>
|
506
|
+
# <label for="email">Email</label>
|
507
|
+
# <input type="text" id="email" name="email" value="email@example.com" />
|
508
|
+
# </fieldset>
|
509
|
+
# ...
|
510
|
+
# </form>
|
511
|
+
#
|
512
|
+
# @see Rendering
|
513
|
+
#
|
514
|
+
class Widget
|
515
|
+
include Rendering
|
516
|
+
end
|
517
|
+
|
518
|
+
ActionView::Base.send :include, ActionViewAdditions if defined?(ActionView)
|
519
|
+
end
|