require 'sinatra/base'
unless Object.method_defined?(:blank?)
require 'active_support/core_ext/object/blank'
end
module Sinatra #:nodoc:
module NiceEasyHelpers
# :stopdoc:
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked selected).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' }
# :startdoc:
@@asset_sub_directories ||= {
:images => 'images',
:javascript => 'javascripts',
:stylesheets => 'stylesheets'
}
def self.asset_sub_directories
@@asset_sub_directories
end
def self.asset_sub_directories=(options)
@@asset_sub_directories = options
end
def asset_sub_directories
@@asset_sub_directories
end
@@asset_directory = nil
def self.asset_directory
@@asset_directory
end
def self.asset_directory=(path)
@@asset_directory = path
end
def asset_directory
@@asset_directory
end
# Creates a link to a given URL with the given text as the link.
# link "Check this out", '/path/to/something' # =>
# Check this out
#
def link(content, href, options = {})
tag :a, content, options.merge(:href => href)
end
# Creates an image tag for the given image file.
# If a relative source is given it assumes it lives in /images/ on your server.
# image_tag "button.jpg" :alt => 'Do some Action' # =>
#
#
# image_tag "/icons/delete.jpg" :alt => 'Remove', :class => 'small-button' # =>
#
#
# image_tag "http://www.example.com/close.jpg" # =>
#
#
def image_tag(src, options = {})
single_tag :img, options.merge(:src => compute_public_path(src, asset_sub_directories[:images]))
end
# Creates a script tag for each source provided. If you just supply a relative filename
# (with or without the .js extension) it will assume it can be found in your public/javascripts
# directory. If you provide an absolute path it will use that.
#
# javascript_include_tag 'jquery' # =>
#
#
# javascript_include_tag 'jquery', 'jquery-ui.min.js' # =>
#
#
#
# javascript_include_tag '/js/facebox.js' # =>
#
#
# javascript_include_tag 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js' # =>
#
#
def javascript_include_tag(*sources)
sources.inject([]) { |tags, source|
tags << tag(:script, '', {:src => compute_public_path(source, asset_sub_directories[:javascript], 'js'), :type => 'text/javascript'})
tags
}.join("\n")
end
# :call-seq:
# stylesheet_link_tag(*sources, options = {})
#
# This helper is used to create a set of link tags for CSS stylesheets. If you just
# supply a relative filename (with or without the .css extension) it will assume it
# can be found in your public/javascripts directory. If you provide
# an absolute path it will use that. You can also pass attributes for the link tag
# in a hash as the last argument.
#
# stylesheet_link_tag 'global' # =>
#
#
# stylesheet_link_tag 'global' # =>
#
#
def stylesheet_link_tag(*sources)
options = sources.extract_options!.symbolize_keys
sources.inject([]) { |tags, source|
tags << single_tag(:link, {:href => compute_public_path(source, asset_sub_directories[:stylesheets], 'css'),
:type => 'text/css', :rel => 'stylesheet', :media => 'screen'}.merge(options))
tags
}.join("\n")
end
# :call-seq:
# label(field, options = {})
# label(obj, field, options = {})
#
# Creates a label tag for the specified field, which may be a field on an object.
# It will use the field name as the text for the label unless a :text
# option is provided.
#
# label :email # =>
#
#
# label :email, :text => "Email Address:" # =>
#
#
# label :user, :email # =>
#
#
def label(*args)
obj, field, options = extract_options_and_field(*args)
text = options.delete(:text)
if text.blank?
if String.method_defined?(:titleize)
text = field.blank? ? obj.to_s.titleize : field.to_s.titleize
else
text = field.blank? ? obj.to_s : field.to_s
end
end
tag :label, text, options.merge(:for => (field.blank? ? obj : "#{obj}_#{field}"))
end
# :call-seq:
# text_field(field, options = {})
# text_field(obj, field, options = {})
#
# Returns a text input for the specified field, which may be on an object.
# If there is a value for the field in the request params or an object is
# provided with that value set, it will auto-populate the input.
#
# text_field :email # =>
#
#
# # Where the @params[:email] value is set already to "joe@example.com"
# text_field :email # =>
#
#
# text_field :user, :email # =>
#
#
# @user = User.new(:email => 'joe@example.com')
# text_field @user, :email # =>
#
#
def text_field(*args)
input_tag 'text', *args
end
# :call-seq:
# password_field(field, options = {})
# password_field(obj, field, options = {})
#
# Returns a password input for the specified field, which may be on an object.
# If there is a value for the field in the request params or an object is
# provided with that value set, it will auto-populate the input.
#
# password_field :password # =>
#
#
# # Where the @params[:password] value is set already to "secret"
# password_field :password # =>
#
#
# password_field :user, :password # =>
#
#
# @user = User.new(:password => 'secret')
# password_field @user, :password # =>
#
#
def password_field(*args)
input_tag 'password', *args
end
# :call-seq:
# file_field(field, options = {})
# file_field(obj, field, options = {})
#
# Returns a file input for the specified field, which may be on an object.
#
# file_field :picture # =>
#
#
# file_field :user, :picture # =>
#
#
# @user = User.new
# file_field @user, :picture # =>
#
#
def file_field(*args)
input_tag 'file', *args
end
# :call-seq:
# button(name, content, options = {})
# button(name, content, type = "submit", options = {})
#
# Creates a button element with the name/id and with the content provided.
# Defaults to the submit type.
#
# button "continue", "Save and continue" # =>
#
#
# button "add-email", "Add another Email", 'button' # =>
#
#
def button(*args)
options = args.extract_options!.symbolize_keys
name = args.shift
content = args.shift
type = args.shift || 'submit'
tag :button, content, options.merge(:type => type, :name => name, :id => name)
end
# :call-seq:
# text_area(field, options = {})
# text_area(obj, field, options = {})
#
# Returns a text area tag for the specified field, which may be on an object.
# If there is a value for the field in the request params or an object is
# provided with that value set, it will auto-populate the input.
#
# text_area :description # =>
#
#
# # Where the @params[:description] value is set already to "A brand new book."
# text_area :description # =>
#
#
# text_area :product, :description # =>
#
#
# @product = Product.new(:description => 'A brand new book.')
# text_area @product, :description # =>
#
#
def text_area(*args)
obj, field, options = extract_options_and_field(*args)
value = get_value(obj, field)
tag :textarea, value, options.merge(get_id_and_name(obj, field))
end
# Creates an image input field for the source and options provided.
# The image source is calculated the same way it is for Sinatra::NiceEasyHelpers#image_tag
#
# image_input "buttons/save_close.png", :alt => 'Save and close'
#
def image_input(src, options = {})
single_tag :input, options.merge(:type => 'image', :src => compute_public_path(src, asset_sub_directories[:images]))
end
# Creates as submit input field with the text and options provided.
#
# submit # =>
#
#
# submit 'Save and continue' # =>
#
#
def submit(value = "Save", options = {})
single_tag :input, options.merge(:type => "submit", :value => value)
end
# :call-seq:
# checkbox_field(field, options)
# checkbox_field(obj, field, options)
#
# Returns a checkbox input for the specified field, which may be on an object.
# If there is a value for the field in the request params or an object is
# provided with that value set, it will mark this field as checked if it
# matches the value for the field.
#
# checkbox_field :subscriptions, :value => 1 # =>
#
#
# # Where the @params[:subscriptions] value is set already to "1"
# checkbox_field :subscriptions, :value => 1 # =>
#
#
# checkbox_field :user, :subscriptions, :value => 1 # =>
#
#
# @user = User.new(:subscriptions => ['newsletters'])
# checkbox_field @user, :subscriptions, :value => 'newsletters' # =>
#
#
def checkbox_field(*args)
input_tag 'checkbox', *args
end
# :call-seq:
# radio_button(field, options)
# radio_button(obj, field, options)
#
# Returns a radio button input for the specified field, which may be on an object.
# If there is a value for the field in the request params or an object is
# provided with that value set, it will mark this field as checked if it
# matches the value for the field.
#
# radio_button :education, :value => 'college' # =>
#
#
# # Where the @params[:education] value is set already to "college"
# radio_button :education, :value => 'college # =>
#
#
# radio_button :user, :education, :value => 'college' # =>
#
#
# @user = User.new(:education => 'college')
# radio_button @user, :education, :value => 'college' # =>
#
#
def radio_button(*args)
input_tag 'radio', *args
end
# :call-seq:
# select_field(field, choices, options = {})
# select_field(obj, field, choices, options = {})
#
# Creates a select tag for the specified field, which may be on an object.
# The helper also creates the options elements inside the select tag for each
# of the choices provided.
#
# Given a choices container of an array of strings the strings will be used for
# the test and value of the options.
# Given a container where the elements respond to first and last (such as a two-element array),
# the “lasts” serve as option values and the “firsts” as option text.
# Hashes are turned into this form automatically, so the keys become “firsts” and values become lasts.
#
# If there is a value for the field in the request params or an object is
# provided with that value set, it will auto-select that options from the choices.
#
# select_field :membership_type, ['Lifetime', '1 Month', '1 Year'] # =>
#
#
# select_field :membership_type, [['Lifetime', 1], ['1 Month', 2], ['1 Year', 3]] # =>
#
#
# select_field :membership_type, {'Lifetime' => 1, '1 Month' => 2, '1 Year' => 3} # =>
#
#
# # Where the @params[:membership_type] value is set already to "year"
# select_field :membership_type, {'Lifetime' => 'life', '1 Month' => 'month', '1 Year' => 'year'} # =>
#
#
# select_field :user, :membership_type, ['Lifetime', '1 Month', '1 Year'] # =>
#
#
# @user = User.new(:membership_type => 'month')
# select_field :user, :membership_type, {'Lifetime' => 'life', '1 Month' => 'month', '1 Year' => 'year'} # =>
#
#
def select_field(*args)
case args.size
when 2
options = {}
choices = args.pop
obj = args.shift
field = nil
else
options = args.extract_options!.symbolize_keys
choices = args.pop
obj = args.shift
field = args.shift
end
unless choices.is_a? Enumerable
raise ArgumentError, 'the choices parameter must be an Enumerable object'
end
value = get_value(obj, field)
content = choices.inject([]) { |opts, choice|
text, opt_val = option_text_and_value(choice)
opts << tag(:option, escape_once(text), {:value => escape_once(opt_val), :selected => (opt_val == value)})
}.join("\n")
tag :select, "\n#{content}\n", options.merge(get_id_and_name(obj, field))
end
# :call-seq:
# hidden_field(field, options = {})
# hidden_field(obj, field, options = {})
#
# Returns a hidden input for the specified field, which may be on an object.
# If there is a value for the field in the request params or an object is
# provided with that value set, it will auto-populate the input. If not
# you should set the value with the :value option.
#
# hidden_field :external_id, :value => 25 # =>
#
#
# # Where the @params[:external_id] value is set already to "247"
# hidden_field :external_id # =>
#
#
# hidden_field :user, :external_id, :value => 25 # =>
#
#
# @user = User.new(:external_id => 247)
# hidden_field @user, :external_id # =>
#
#
def hidden_field(*args)
input_tag 'hidden', *args
end
# Creats a standard open and close tags for the name provided with the content
# and attributes supplied.
# tag :h2, "Sinatra Steps to the Stage", :title => "Applause" # =>
#