padrino-helpers 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +7 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/lib/padrino-helpers.rb +16 -0
- data/lib/padrino-helpers/asset_tag_helpers.rb +97 -0
- data/lib/padrino-helpers/form_builder/abstract_form_builder.rb +139 -0
- data/lib/padrino-helpers/form_builder/standard_form_builder.rb +37 -0
- data/lib/padrino-helpers/form_helpers.rb +194 -0
- data/lib/padrino-helpers/format_helpers.rb +74 -0
- data/lib/padrino-helpers/output_helpers.rb +98 -0
- data/lib/padrino-helpers/render_helpers.rb +63 -0
- data/lib/padrino-helpers/tag_helpers.rb +42 -0
- data/test/active_support_helpers.rb +7 -0
- data/test/fixtures/markup_app/app.rb +61 -0
- data/test/fixtures/markup_app/views/capture_concat.erb +14 -0
- data/test/fixtures/markup_app/views/capture_concat.haml +13 -0
- data/test/fixtures/markup_app/views/content_for.erb +11 -0
- data/test/fixtures/markup_app/views/content_for.haml +9 -0
- data/test/fixtures/markup_app/views/content_tag.erb +11 -0
- data/test/fixtures/markup_app/views/content_tag.haml +9 -0
- data/test/fixtures/markup_app/views/fields_for.erb +8 -0
- data/test/fixtures/markup_app/views/fields_for.haml +6 -0
- data/test/fixtures/markup_app/views/form_for.erb +56 -0
- data/test/fixtures/markup_app/views/form_for.haml +47 -0
- data/test/fixtures/markup_app/views/form_tag.erb +57 -0
- data/test/fixtures/markup_app/views/form_tag.haml +45 -0
- data/test/fixtures/markup_app/views/link_to.erb +5 -0
- data/test/fixtures/markup_app/views/link_to.haml +4 -0
- data/test/fixtures/markup_app/views/mail_to.erb +3 -0
- data/test/fixtures/markup_app/views/mail_to.haml +3 -0
- data/test/fixtures/render_app/app.rb +53 -0
- data/test/fixtures/render_app/views/erb/test.erb +1 -0
- data/test/fixtures/render_app/views/haml/test.haml +1 -0
- data/test/fixtures/render_app/views/template/_user.haml +7 -0
- data/test/fixtures/render_app/views/template/haml_template.haml +1 -0
- data/test/fixtures/render_app/views/template/some_template.haml +2 -0
- data/test/helper.rb +73 -0
- data/test/test_asset_tag_helpers.rb +127 -0
- data/test/test_form_builder.rb +611 -0
- data/test/test_form_helpers.rb +406 -0
- data/test/test_format_helpers.rb +96 -0
- data/test/test_output_helpers.rb +63 -0
- data/test/test_render_helpers.rb +78 -0
- data/test/test_tag_helpers.rb +73 -0
- metadata +174 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module Padrino
|
2
|
+
module Helpers
|
3
|
+
module FormatHelpers
|
4
|
+
|
5
|
+
# Returns escaped text to protect against malicious content
|
6
|
+
def escape_html(text)
|
7
|
+
Rack::Utils.escape_html(text)
|
8
|
+
end
|
9
|
+
alias h escape_html
|
10
|
+
alias sanitize_html escape_html
|
11
|
+
|
12
|
+
# Returns escaped text to protect against malicious content
|
13
|
+
# Returns blank if the text is empty
|
14
|
+
def h!(text, blank_text = ' ')
|
15
|
+
return blank_text if text.nil? || text.empty?
|
16
|
+
h text
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Smart time helper which returns relative text representing times for recent dates
|
21
|
+
# and absolutes for dates that are far removed from the current date
|
22
|
+
# time_in_words(10.days.ago) => '10 days ago'
|
23
|
+
def time_in_words(date)
|
24
|
+
date = date.to_date
|
25
|
+
date = Date.parse(date, true) unless /Date.*/ =~ date.class.to_s
|
26
|
+
days = (date - Date.today).to_i
|
27
|
+
|
28
|
+
return 'today' if days >= 0 and days < 1
|
29
|
+
return 'tomorrow' if days >= 1 and days < 2
|
30
|
+
return 'yesterday' if days >= -1 and days < 0
|
31
|
+
|
32
|
+
return "in #{days} days" if days.abs < 60 and days > 0
|
33
|
+
return "#{days.abs} days ago" if days.abs < 60 and days < 0
|
34
|
+
|
35
|
+
return date.strftime('%A, %B %e') if days.abs < 182
|
36
|
+
return date.strftime('%A, %B %e, %Y')
|
37
|
+
end
|
38
|
+
alias time_ago time_in_words
|
39
|
+
|
40
|
+
# Returns relative time in words referencing the given date
|
41
|
+
# relative_time_ago(Time.now) => 'about a minute ago'
|
42
|
+
def relative_time_ago(from_time)
|
43
|
+
distance_in_minutes = (((Time.now - from_time.to_time).abs)/60).round
|
44
|
+
case distance_in_minutes
|
45
|
+
when 0..1 then 'about a minute'
|
46
|
+
when 2..44 then "#{distance_in_minutes} minutes"
|
47
|
+
when 45..89 then 'about 1 hour'
|
48
|
+
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
49
|
+
when 1440..2879 then '1 day'
|
50
|
+
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
|
51
|
+
when 43200..86399 then 'about 1 month'
|
52
|
+
when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
|
53
|
+
when 525600..1051199 then 'about 1 year'
|
54
|
+
else "over #{(distance_in_minutes / 525600).round} years"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Used in xxxx.js.erb files to escape html so that it can be passed to javascript from sinatra
|
59
|
+
# escape_javascript("<h1>Hey</h1>")
|
60
|
+
def escape_javascript(html_content)
|
61
|
+
return '' unless html_content
|
62
|
+
javascript_mapping = { '\\' => '\\\\', '</' => '<\/', "\r\n" => '\n', "\n" => '\n' }
|
63
|
+
javascript_mapping.merge("\r" => '\n', '"' => '\\"', "'" => "\\'")
|
64
|
+
escaped_string = html_content.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { javascript_mapping[$1] }
|
65
|
+
"\"#{escaped_string}\""
|
66
|
+
end
|
67
|
+
|
68
|
+
alias js_escape escape_javascript
|
69
|
+
alias js_escape_html escape_javascript
|
70
|
+
alias escape_for_javascript escape_javascript
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Padrino
|
2
|
+
module Helpers
|
3
|
+
module OutputHelpers
|
4
|
+
# Captures the html from a block of template code for erb or haml
|
5
|
+
# capture_html(&block) => "...html..."
|
6
|
+
def capture_html(*args, &block)
|
7
|
+
if self.respond_to?(:is_haml?) && is_haml?
|
8
|
+
block_is_haml?(block) ? capture_haml(*args, &block) : block.call
|
9
|
+
elsif has_erb_buffer?
|
10
|
+
result_text = capture_erb(*args, &block)
|
11
|
+
result_text.present? ? result_text : (block_given? && block.call(*args))
|
12
|
+
else # theres no template to capture, invoke the block directly
|
13
|
+
block.call(*args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Outputs the given text to the templates buffer directly
|
18
|
+
# concat_content("This will be output to the template buffer in erb or haml")
|
19
|
+
def concat_content(text="")
|
20
|
+
if self.respond_to?(:is_haml?) && is_haml?
|
21
|
+
haml_concat(text)
|
22
|
+
elsif has_erb_buffer?
|
23
|
+
erb_concat(text)
|
24
|
+
else # theres no template to concat, return the text directly
|
25
|
+
text
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns true if the block is from an ERB or HAML template; false otherwise.
|
30
|
+
# Used to determine if html should be returned or concatted to view
|
31
|
+
# block_is_template?(block)
|
32
|
+
def block_is_template?(block)
|
33
|
+
block && (block_is_erb?(block) || (self.respond_to?(:block_is_haml?) && block_is_haml?(block)))
|
34
|
+
end
|
35
|
+
|
36
|
+
# Capture a block of content to be rendered at a later time.
|
37
|
+
# Your blocks can also receive values, which are passed to them by <tt>yield_content</tt>
|
38
|
+
# content_for(:name) { ...content... }
|
39
|
+
# content_for(:name) { |name| ...content... }
|
40
|
+
def content_for(key, &block)
|
41
|
+
content_blocks[key.to_sym] << block
|
42
|
+
end
|
43
|
+
|
44
|
+
# Render the captured content blocks for a given key.
|
45
|
+
# You can also pass values to the content blocks by passing them
|
46
|
+
# as arguments after the key.
|
47
|
+
# yield_content :include
|
48
|
+
# yield_content :head, "param1", "param2"
|
49
|
+
def yield_content(key, *args)
|
50
|
+
content_blocks[key.to_sym].map { |content|
|
51
|
+
capture_html(*args, &content)
|
52
|
+
}.join
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
# Retrieves content_blocks stored by content_for or within yield_content
|
58
|
+
# content_blocks[:name] => ['...', '...']
|
59
|
+
def content_blocks
|
60
|
+
@content_blocks ||= Hash.new {|h,k| h[k] = [] }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Used to capture the html from a block of erb code
|
64
|
+
# capture_erb(&block) => '...html...'
|
65
|
+
def capture_erb(*args, &block)
|
66
|
+
erb_with_output_buffer { block_given? && block.call(*args) }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Concats directly to an erb template
|
70
|
+
# erb_concat("Direct to buffer")
|
71
|
+
def erb_concat(text)
|
72
|
+
@_out_buf << text if has_erb_buffer?
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns true if an erb buffer is detected
|
76
|
+
# has_erb_buffer? => true
|
77
|
+
def has_erb_buffer?
|
78
|
+
!@_out_buf.nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
# Used to determine if a block is called from ERB.
|
82
|
+
# NOTE: This doesn't actually work yet because the variable __in_erb_template
|
83
|
+
# hasn't been defined in ERB. We need to find a way to fix this.
|
84
|
+
def block_is_erb?(block)
|
85
|
+
has_erb_buffer? || block && eval('defined? __in_erb_template', block)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Used to direct the buffer for the erb capture
|
89
|
+
def erb_with_output_buffer(buf = '') #:nodoc:
|
90
|
+
@_out_buf, old_buffer = buf, @_out_buf
|
91
|
+
yield
|
92
|
+
@_out_buf
|
93
|
+
ensure
|
94
|
+
@_out_buf = old_buffer
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Padrino
|
2
|
+
module Helpers
|
3
|
+
module RenderHelpers
|
4
|
+
# Renders a erb template based on the relative path
|
5
|
+
# erb_template 'users/new'
|
6
|
+
def erb_template(template_path, options={})
|
7
|
+
render_template template_path, options.merge(:template_engine => :erb)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Renders a haml template based on the relative path
|
11
|
+
# haml_template 'users/new'
|
12
|
+
def haml_template(template_path, options={})
|
13
|
+
render_template template_path, options.merge(:template_engine => :haml)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Renders a template from a file path automatically determining rendering engine
|
17
|
+
# render_template 'users/new'
|
18
|
+
# options = { :template_engine => 'haml' }
|
19
|
+
def render_template(template_path, options={})
|
20
|
+
template_engine = options.delete(:template_engine) || resolve_template_engine(template_path)
|
21
|
+
render template_engine.to_sym, template_path.to_sym, options
|
22
|
+
end
|
23
|
+
|
24
|
+
# Partials implementation which includes collections support
|
25
|
+
# partial 'photo/_item', :object => @photo
|
26
|
+
# partial 'photo/_item', :collection => @photos
|
27
|
+
def partial(template, options={})
|
28
|
+
options.reverse_merge!(:locals => {}, :layout => false)
|
29
|
+
path = template.to_s.split(File::SEPARATOR)
|
30
|
+
object_name = path[-1].to_sym
|
31
|
+
path[-1] = "_#{path[-1]}"
|
32
|
+
template_path = File.join(path)
|
33
|
+
raise 'Partial collection specified but is nil' if options.has_key?(:collection) && options[:collection].nil?
|
34
|
+
if collection = options.delete(:collection)
|
35
|
+
options.delete(:object)
|
36
|
+
counter = 0
|
37
|
+
collection.collect do |member|
|
38
|
+
counter += 1
|
39
|
+
options[:locals].merge!(object_name => member, "#{object_name}_counter".to_sym => counter)
|
40
|
+
render_template(template_path, options.merge(:layout => false))
|
41
|
+
end.join("\n")
|
42
|
+
else
|
43
|
+
if member = options.delete(:object)
|
44
|
+
options[:locals].merge!(object_name => member)
|
45
|
+
end
|
46
|
+
render_template(template_path, options.merge(:layout => false))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
alias render_partial partial
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Returns the template engine (i.e haml) to use for a given template_path
|
54
|
+
# resolve_template_engine('users/new') => :haml
|
55
|
+
def resolve_template_engine(template_path)
|
56
|
+
resolved_template_path = File.join(self.options.views, template_path.to_s + ".*")
|
57
|
+
template_file = Dir[resolved_template_path].first
|
58
|
+
raise "Template path '#{template_path}' could not be located in views!" unless template_file
|
59
|
+
template_engine = File.extname(template_file)[1..-1].to_sym
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Padrino
|
2
|
+
module Helpers
|
3
|
+
module TagHelpers
|
4
|
+
# Creates an html input field with given type and options
|
5
|
+
# input_tag :text, :class => "test"
|
6
|
+
def input_tag(type, options = {})
|
7
|
+
options.reverse_merge!(:type => type)
|
8
|
+
tag(:input, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Creates an html tag with given name, content and options
|
12
|
+
# content_tag(:p, "hello", :class => 'light')
|
13
|
+
# content_tag(:p, :class => 'dark') do ... end
|
14
|
+
# parameters: content_tag(name, content=nil, options={}, &block)
|
15
|
+
def content_tag(*args, &block)
|
16
|
+
name = args.first
|
17
|
+
options = args.extract_options!
|
18
|
+
tag_html = block_given? ? capture_html(&block) : args[1]
|
19
|
+
tag_result = tag(name, options.merge(:content => tag_html))
|
20
|
+
block_is_template?(block) ? concat_content(tag_result) : tag_result
|
21
|
+
end
|
22
|
+
|
23
|
+
# Creates an html tag with the given name and options
|
24
|
+
# tag(:br, :style => 'clear:both')
|
25
|
+
# tag(:p, :content => "hello", :class => 'large')
|
26
|
+
def tag(name, options={})
|
27
|
+
content = options.delete(:content)
|
28
|
+
identity_tag_attributes.each { |attr| options[attr] = attr.to_s if options[attr] }
|
29
|
+
html_attrs = options.collect { |a, v| v.blank? ? nil : "#{a}=\"#{v}\"" }.compact.join(" ")
|
30
|
+
base_tag = (html_attrs.present? ? "<#{name} #{html_attrs}" : "<#{name}")
|
31
|
+
base_tag << (content ? ">#{content}</#{name}>" : " />")
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
# Returns a list of attributes which can only contain an identity value (i.e selected)
|
37
|
+
def identity_tag_attributes
|
38
|
+
[:checked, :disabled, :selected, :multiple]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'haml'
|
3
|
+
|
4
|
+
class MarkupDemo < Sinatra::Base
|
5
|
+
register Padrino::Helpers
|
6
|
+
|
7
|
+
configure do
|
8
|
+
set :root, File.dirname(__FILE__)
|
9
|
+
end
|
10
|
+
|
11
|
+
get '/:engine/:file' do
|
12
|
+
show(params[:engine], params[:file].to_sym)
|
13
|
+
end
|
14
|
+
|
15
|
+
helpers do
|
16
|
+
# show :erb, :index
|
17
|
+
# show :haml, :index
|
18
|
+
def show(kind, template)
|
19
|
+
eval("#{kind.to_s} #{template.to_sym.inspect}")
|
20
|
+
end
|
21
|
+
|
22
|
+
def captured_content(&block)
|
23
|
+
content_html = capture_html(&block)
|
24
|
+
"<p>#{content_html}</p>"
|
25
|
+
end
|
26
|
+
|
27
|
+
def concat_in_p(content_html)
|
28
|
+
concat_content "<p>#{content_html}</p>"
|
29
|
+
end
|
30
|
+
|
31
|
+
def ruby_not_template_block
|
32
|
+
determine_block_is_template('ruby') do
|
33
|
+
content_tag(:span, "This not a template block")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def determine_block_is_template(name, &block)
|
38
|
+
concat_content "<p class='is_template'>The #{name} block passed in is a template</p>" if block_is_template?(block)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class MarkupUser
|
44
|
+
def errors; Errors.new; end
|
45
|
+
def session_id; 45; end
|
46
|
+
def gender; 'male'; end
|
47
|
+
def remember_me; '1'; end
|
48
|
+
def permission; Permission.new; end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Permission
|
52
|
+
def can_edit; true; end
|
53
|
+
def can_delete; false; end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Errors < Array
|
57
|
+
def initialize; self << [:fake, :second, :third]; end
|
58
|
+
def full_messages
|
59
|
+
["This is a fake error", "This is a second fake error", "This is a third fake error"]
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<% @content = captured_content do %>
|
2
|
+
<span>Captured Line 1</span>
|
3
|
+
<span>Captured Line 2</span>
|
4
|
+
<% end %>
|
5
|
+
<%= @content %>
|
6
|
+
|
7
|
+
<% concat_in_p('Concat Line 3') %>
|
8
|
+
|
9
|
+
<% determine_block_is_template('erb') do %>
|
10
|
+
<span>This is erb</span>
|
11
|
+
<span>This is erb</span>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<% ruby_not_template_block %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% content_for :demo do %>
|
2
|
+
<h1>This is content yielded from a content_for</h1>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<div class='demo'><%= yield_content :demo %></p>
|
6
|
+
|
7
|
+
<% content_for :demo2 do |fname, lname| %>
|
8
|
+
<h1>This is content yielded with name <%= fname + " " + lname %></h1>
|
9
|
+
<% end %>
|
10
|
+
|
11
|
+
<div class='demo2'><%= yield_content :demo2, "Johnny", "Smith" %></div>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%= content_tag :p, "Test 1", :class => 'test', :id => "test1" %>
|
2
|
+
|
3
|
+
<%= content_tag :p, "Test 2" %>
|
4
|
+
|
5
|
+
<% content_tag(:p, :class => 'test', :id => 'test3') do %>
|
6
|
+
<span>Test 3</span>
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
<% content_tag(:p) do %>
|
10
|
+
<span>Test 4</span>
|
11
|
+
<% end %>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<% @user = MarkupUser.new %>
|
2
|
+
<% form_for @user , '/demo1', :id => 'demo-fields-for' do |f| %>
|
3
|
+
<%= f.text_field :gender %>
|
4
|
+
<% fields_for @user.permission do |permission| %>
|
5
|
+
<%= permission.check_box :can_edit %>
|
6
|
+
<%= permission.check_box :can_delete %>
|
7
|
+
<% end %>
|
8
|
+
<% end %>
|