mirror of
https://github.com/TracksApp/tracks.git
synced 2026-02-22 15:14:07 +01:00
Removed superfluous 'tracks' directory at the root of the repository.
Testing commits to github.
This commit is contained in:
parent
6a42901514
commit
4cbf5a34d3
2269 changed files with 0 additions and 0 deletions
255
vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb
vendored
Normal file
255
vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
require 'cgi'
|
||||
require 'action_view/helpers/form_helper'
|
||||
|
||||
module ActionView
|
||||
class Base
|
||||
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>" }
|
||||
cattr_accessor :field_error_proc
|
||||
end
|
||||
|
||||
module Helpers
|
||||
# The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the form
|
||||
# method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This
|
||||
# is a great way of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form.
|
||||
# In that case, it's better to use the input method and the specialized form methods in link:classes/ActionView/Helpers/FormHelper.html
|
||||
module ActiveRecordHelper
|
||||
# Returns a default input tag for the type of object returned by the method. For example, let's say you have a model
|
||||
# that has an attribute +title+ of type VARCHAR column, and this instance holds "Hello World":
|
||||
# input("post", "title") =>
|
||||
# <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
|
||||
def input(record_name, method, options = {})
|
||||
InstanceTag.new(record_name, method, self).to_tag(options)
|
||||
end
|
||||
|
||||
# Returns an entire form with all needed input tags for a specified Active Record object. For example, let's say you
|
||||
# have a table model <tt>Post</tt> with attributes named <tt>title</tt> of type <tt>VARCHAR</tt> and <tt>body</tt> of type <tt>TEXT</tt>:
|
||||
# form("post")
|
||||
# That line would yield a form like the following:
|
||||
# <form action='/post/create' method='post'>
|
||||
# <p>
|
||||
# <label for="post_title">Title</label><br />
|
||||
# <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
|
||||
# </p>
|
||||
# <p>
|
||||
# <label for="post_body">Body</label><br />
|
||||
# <textarea cols="40" id="post_body" name="post[body]" rows="20">
|
||||
# </textarea>
|
||||
# </p>
|
||||
# <input type='submit' value='Create' />
|
||||
# </form>
|
||||
#
|
||||
# It's possible to specialize the form builder by using a different action name and by supplying another
|
||||
# block renderer. For example, let's say you have a model <tt>Entry</tt> with an attribute <tt>message</tt> of type <tt>VARCHAR</tt>:
|
||||
#
|
||||
# form("entry", :action => "sign", :input_block =>
|
||||
# Proc.new { |record, column| "#{column.human_name}: #{input(record, column.name)}<br />" }) =>
|
||||
#
|
||||
# <form action='/post/sign' method='post'>
|
||||
# Message:
|
||||
# <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /><br />
|
||||
# <input type='submit' value='Sign' />
|
||||
# </form>
|
||||
#
|
||||
# It's also possible to add additional content to the form by giving it a block, such as:
|
||||
#
|
||||
# form("entry", :action => "sign") do |form|
|
||||
# form << content_tag("b", "Department")
|
||||
# form << collection_select("department", "id", @departments, "id", "name")
|
||||
# end
|
||||
def form(record_name, options = {})
|
||||
record = instance_variable_get("@#{record_name}")
|
||||
|
||||
options = options.symbolize_keys
|
||||
options[:action] ||= record.new_record? ? "create" : "update"
|
||||
action = url_for(:action => options[:action], :id => record)
|
||||
|
||||
submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize
|
||||
|
||||
contents = ''
|
||||
contents << hidden_field(record_name, :id) unless record.new_record?
|
||||
contents << all_input_tags(record, record_name, options)
|
||||
yield contents if block_given?
|
||||
contents << submit_tag(submit_value)
|
||||
|
||||
content_tag('form', contents, :action => action, :method => 'post', :enctype => options[:multipart] ? 'multipart/form-data': nil)
|
||||
end
|
||||
|
||||
# Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
|
||||
# This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a +prepend_text+ and/or +append_text+
|
||||
# (to properly explain the error), and a +css_class+ to style it accordingly. +object+ should either be the name of an instance variable or
|
||||
# the actual object. As an example, let's say you have a model
|
||||
# +post+ that has an error message on the +title+ attribute:
|
||||
#
|
||||
# <%= error_message_on "post", "title" %> =>
|
||||
# <div class="formError">can't be empty</div>
|
||||
#
|
||||
# <%= error_message_on @post, "title" %> =>
|
||||
# <div class="formError">can't be empty</div>
|
||||
#
|
||||
# <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %> =>
|
||||
# <div class="inputError">Title simply can't be empty (or it won't work).</div>
|
||||
def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
|
||||
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
|
||||
(errors = obj.errors.on(method))
|
||||
content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class)
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a string with a <tt>DIV</tt> containing all of the error messages for the objects located as instance variables by the names
|
||||
# given. If more than one object is specified, the errors for the objects are displayed in the order that the object names are
|
||||
# provided.
|
||||
#
|
||||
# This <tt>DIV</tt> can be tailored by the following options:
|
||||
#
|
||||
# * <tt>header_tag</tt> - Used for the header of the error div (default: h2)
|
||||
# * <tt>id</tt> - The id of the error div (default: errorExplanation)
|
||||
# * <tt>class</tt> - The class of the error div (default: errorExplanation)
|
||||
# * <tt>object</tt> - The object (or array of objects) for which to display errors, if you need to escape the instance variable convention
|
||||
# * <tt>object_name</tt> - The object name to use in the header, or any text that you prefer. If <tt>object_name</tt> is not set, the name of the first object will be used.
|
||||
# * <tt>header_message</tt> - The message in the header of the error div. Pass +nil+ or an empty string to avoid the header message altogether. (default: X errors prohibited this object from being saved)
|
||||
# * <tt>message</tt> - The explanation message after the header message and before the error list. Pass +nil+ or an empty string to avoid the explanation message altogether. (default: There were problems with the following fields:)
|
||||
#
|
||||
# To specify the display for one object, you simply provide its name as a parameter. For example, for the +User+ model:
|
||||
#
|
||||
# error_messages_for 'user'
|
||||
#
|
||||
# To specify more than one object, you simply list them; optionally, you can add an extra +object_name+ parameter, which
|
||||
# will be the name used in the header message.
|
||||
#
|
||||
# error_messages_for 'user_common', 'user', :object_name => 'user'
|
||||
#
|
||||
# If the objects cannot be located as instance variables, you can add an extra +object+ paremeter which gives the actual
|
||||
# object (or array of objects to use)
|
||||
#
|
||||
# error_messages_for 'user', :object => @question.user
|
||||
#
|
||||
# NOTE: This is a pre-packaged presentation of the errors with embedded strings and a certain HTML structure. If what
|
||||
# you need is significantly different from the default presentation, it makes plenty of sense to access the object.errors
|
||||
# instance yourself and set it up. View the source of this method to see how easy it is.
|
||||
def error_messages_for(*params)
|
||||
options = params.extract_options!.symbolize_keys
|
||||
if object = options.delete(:object)
|
||||
objects = [object].flatten
|
||||
else
|
||||
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
|
||||
end
|
||||
count = objects.inject(0) {|sum, object| sum + object.errors.count }
|
||||
unless count.zero?
|
||||
html = {}
|
||||
[:id, :class].each do |key|
|
||||
if options.include?(key)
|
||||
value = options[key]
|
||||
html[key] = value unless value.blank?
|
||||
else
|
||||
html[key] = 'errorExplanation'
|
||||
end
|
||||
end
|
||||
options[:object_name] ||= params.first
|
||||
options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message)
|
||||
options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message)
|
||||
error_messages = objects.map {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }
|
||||
|
||||
contents = ''
|
||||
contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank?
|
||||
contents << content_tag(:p, options[:message]) unless options[:message].blank?
|
||||
contents << content_tag(:ul, error_messages)
|
||||
|
||||
content_tag(:div, contents, html)
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def all_input_tags(record, record_name, options)
|
||||
input_block = options[:input_block] || default_input_block
|
||||
record.class.content_columns.collect{ |column| input_block.call(record_name, column) }.join("\n")
|
||||
end
|
||||
|
||||
def default_input_block
|
||||
Proc.new { |record, column| %(<p><label for="#{record}_#{column.name}">#{column.human_name}</label><br />#{input(record, column.name)}</p>) }
|
||||
end
|
||||
end
|
||||
|
||||
class InstanceTag #:nodoc:
|
||||
def to_tag(options = {})
|
||||
case column_type
|
||||
when :string
|
||||
field_type = @method_name.include?("password") ? "password" : "text"
|
||||
to_input_field_tag(field_type, options)
|
||||
when :text
|
||||
to_text_area_tag(options)
|
||||
when :integer, :float, :decimal
|
||||
to_input_field_tag("text", options)
|
||||
when :date
|
||||
to_date_select_tag(options)
|
||||
when :datetime, :timestamp
|
||||
to_datetime_select_tag(options)
|
||||
when :time
|
||||
to_time_select_tag(options)
|
||||
when :boolean
|
||||
to_boolean_select_tag(options)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :tag_without_error_wrapping, :tag
|
||||
def tag(name, options)
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name))
|
||||
else
|
||||
tag_without_error_wrapping(name, options)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :content_tag_without_error_wrapping, :content_tag
|
||||
def content_tag(name, value, options)
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name))
|
||||
else
|
||||
content_tag_without_error_wrapping(name, value, options)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag
|
||||
def to_date_select_tag(options = {})
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
error_wrapping(to_date_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
|
||||
else
|
||||
to_date_select_tag_without_error_wrapping(options)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag
|
||||
def to_datetime_select_tag(options = {})
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
error_wrapping(to_datetime_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
|
||||
else
|
||||
to_datetime_select_tag_without_error_wrapping(options)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag
|
||||
def to_time_select_tag(options = {})
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
error_wrapping(to_time_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
|
||||
else
|
||||
to_time_select_tag_without_error_wrapping(options)
|
||||
end
|
||||
end
|
||||
|
||||
def error_wrapping(html_tag, has_error)
|
||||
has_error ? Base.field_error_proc.call(html_tag, self) : html_tag
|
||||
end
|
||||
|
||||
def error_message
|
||||
object.errors.on(@method_name)
|
||||
end
|
||||
|
||||
def column_type
|
||||
object.send("column_for_attribute", @method_name).type
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
548
vendor/rails/actionpack/lib/action_view/helpers/asset_tag_helper.rb
vendored
Normal file
548
vendor/rails/actionpack/lib/action_view/helpers/asset_tag_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,548 @@
|
|||
require 'cgi'
|
||||
require 'action_view/helpers/url_helper'
|
||||
require 'action_view/helpers/tag_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
# This module provides methods for generating HTML that links views to assets such
|
||||
# as images, javascripts, stylesheets, and feeds. These methods do not verify
|
||||
# the assets exist before linking to them.
|
||||
#
|
||||
# === Using asset hosts
|
||||
# By default, Rails links to these assets on the current host in the public
|
||||
# folder, but you can direct Rails to link to assets from a dedicated assets server by
|
||||
# setting ActionController::Base.asset_host in your environment.rb. For example,
|
||||
# let's say your asset host is assets.example.com.
|
||||
#
|
||||
# ActionController::Base.asset_host = "assets.example.com"
|
||||
# image_tag("rails.png")
|
||||
# => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
|
||||
# stylesheet_include_tag("application")
|
||||
# => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# This is useful since browsers typically open at most two connections to a single host,
|
||||
# which means your assets often wait in single file for their turn to load. You can
|
||||
# alleviate this by using a %d wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
|
||||
# to automatically distribute asset requests among four hosts (e.g., assets0.example.com through assets3.example.com)
|
||||
# so browsers will open eight connections rather than two.
|
||||
#
|
||||
# image_tag("rails.png")
|
||||
# => <img src="http://assets0.example.com/images/rails.png" alt="Rails" />
|
||||
# stylesheet_include_tag("application")
|
||||
# => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
|
||||
# the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from
|
||||
# your ISP.
|
||||
#
|
||||
# Note: This is purely a browser performance optimization and is not meant
|
||||
# for server load balancing. See http://www.die.net/musings/page_load_time/
|
||||
# for background.
|
||||
#
|
||||
# Alternatively, you can exert more control over the asset host by setting <tt>asset_host</tt> to a proc
|
||||
# that takes a single source argument. This is useful if you are unable to setup 4 actual hosts or have
|
||||
# fewer/more than 4 hosts. The example proc below generates http://assets1.example.com and
|
||||
# http://assets2.example.com randomly.
|
||||
#
|
||||
# ActionController::Base.asset_host = Proc.new { |source| "http://assets#{rand(2) + 1}.example.com" }
|
||||
# image_tag("rails.png")
|
||||
# => <img src="http://assets2.example.com/images/rails.png" alt="Rails" />
|
||||
# stylesheet_include_tag("application")
|
||||
# => <link href="http://assets1.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# The proc takes a single <tt>source</tt> parameter which is the path of the source asset. This can be used to
|
||||
# generate a particular asset host depending on the asset path.
|
||||
#
|
||||
# ActionController::Base.asset_host = Proc.new { |source|
|
||||
# if source.starts_with?('/images')
|
||||
# "http://images.example.com"
|
||||
# else
|
||||
# "http://assets.example.com"
|
||||
# end
|
||||
# }
|
||||
# image_tag("rails.png")
|
||||
# => <img src="http://images.example.com/images/rails.png" alt="Rails" />
|
||||
# stylesheet_include_tag("application")
|
||||
# => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# === Using asset timestamps
|
||||
#
|
||||
# By default, Rails will append all asset paths with that asset's timestamp. This allows you to set a cache-expiration date for the
|
||||
# asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp,
|
||||
# which then updates the URL as the timestamp is part of that, which in turn busts the cache).
|
||||
#
|
||||
# It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
|
||||
# advantage of this feature. Here's an example for Apache:
|
||||
#
|
||||
# # Asset Expiration
|
||||
# ExpiresActive On
|
||||
# <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
|
||||
# ExpiresDefault "access plus 1 year"
|
||||
# </FilesMatch>
|
||||
#
|
||||
# Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
|
||||
# have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't
|
||||
# work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use
|
||||
# something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
|
||||
# requested over and over).
|
||||
module AssetTagHelper
|
||||
ASSETS_DIR = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : "public"
|
||||
JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
|
||||
STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets"
|
||||
|
||||
# Returns a link tag that browsers and news readers can use to auto-detect
|
||||
# an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
|
||||
# <tt>:atom</tt>. Control the link options in url_for format using the
|
||||
# +url_options+. You can modify the LINK tag itself in +tag_options+.
|
||||
#
|
||||
# ==== Options:
|
||||
# * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
|
||||
# * <tt>:type</tt> - Override the auto-generated mime type
|
||||
# * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
|
||||
#
|
||||
# ==== Examples
|
||||
# auto_discovery_link_tag # =>
|
||||
# <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
|
||||
# auto_discovery_link_tag(:atom) # =>
|
||||
# <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
|
||||
# auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
|
||||
# <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
|
||||
# auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
|
||||
# <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
|
||||
# auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"}) # =>
|
||||
# <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
|
||||
# auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"}) # =>
|
||||
# <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
|
||||
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
|
||||
tag(
|
||||
"link",
|
||||
"rel" => tag_options[:rel] || "alternate",
|
||||
"type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s,
|
||||
"title" => tag_options[:title] || type.to_s.upcase,
|
||||
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
|
||||
)
|
||||
end
|
||||
|
||||
# Computes the path to a javascript asset in the public javascripts directory.
|
||||
# If the +source+ filename has no extension, .js will be appended.
|
||||
# Full paths from the document root will be passed through.
|
||||
# Used internally by javascript_include_tag to build the script path.
|
||||
#
|
||||
# ==== Examples
|
||||
# javascript_path "xmlhr" # => /javascripts/xmlhr.js
|
||||
# javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
|
||||
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
|
||||
# javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr.js
|
||||
# javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
|
||||
def javascript_path(source)
|
||||
compute_public_path(source, 'javascripts', 'js')
|
||||
end
|
||||
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
|
||||
|
||||
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
|
||||
@@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
|
||||
|
||||
# Returns an html script tag for each of the +sources+ provided. You
|
||||
# can pass in the filename (.js extension is optional) of javascript files
|
||||
# that exist in your public/javascripts directory for inclusion into the
|
||||
# current page or you can pass the full path relative to your document
|
||||
# root. To include the Prototype and Scriptaculous javascript libraries in
|
||||
# your application, pass <tt>:defaults</tt> as the source. When using
|
||||
# :defaults, if an <tt>application.js</tt> file exists in your public
|
||||
# javascripts directory, it will be included as well. You can modify the
|
||||
# html attributes of the script tag by passing a hash as the last argument.
|
||||
#
|
||||
# ==== Examples
|
||||
# javascript_include_tag "xmlhr" # =>
|
||||
# <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
|
||||
#
|
||||
# javascript_include_tag "xmlhr.js" # =>
|
||||
# <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
|
||||
#
|
||||
# javascript_include_tag "common.javascript", "/elsewhere/cools" # =>
|
||||
# <script type="text/javascript" src="/javascripts/common.javascript"></script>
|
||||
# <script type="text/javascript" src="/elsewhere/cools.js"></script>
|
||||
#
|
||||
# javascript_include_tag "http://www.railsapplication.com/xmlhr" # =>
|
||||
# <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js"></script>
|
||||
#
|
||||
# javascript_include_tag "http://www.railsapplication.com/xmlhr.js" # =>
|
||||
# <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js"></script>
|
||||
#
|
||||
# javascript_include_tag :defaults # =>
|
||||
# <script type="text/javascript" src="/javascripts/prototype.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/effects.js"></script>
|
||||
# ...
|
||||
# <script type="text/javascript" src="/javascripts/application.js"></script>
|
||||
#
|
||||
# * = The application.js file is only referenced if it exists
|
||||
#
|
||||
# Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
|
||||
# (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method.
|
||||
#
|
||||
# You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source:
|
||||
#
|
||||
# javascript_include_tag :all # =>
|
||||
# <script type="text/javascript" src="/javascripts/prototype.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/effects.js"></script>
|
||||
# ...
|
||||
# <script type="text/javascript" src="/javascripts/application.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/shop.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/checkout.js"></script>
|
||||
#
|
||||
# Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
|
||||
# all subsequently included files.
|
||||
#
|
||||
# == Caching multiple javascripts into one
|
||||
#
|
||||
# You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
|
||||
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
|
||||
# is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
|
||||
# environment).
|
||||
#
|
||||
# ==== Examples
|
||||
# javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
|
||||
# <script type="text/javascript" src="/javascripts/prototype.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/effects.js"></script>
|
||||
# ...
|
||||
# <script type="text/javascript" src="/javascripts/application.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/shop.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/checkout.js"></script>
|
||||
#
|
||||
# javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
|
||||
# <script type="text/javascript" src="/javascripts/all.js"></script>
|
||||
#
|
||||
# javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false =>
|
||||
# <script type="text/javascript" src="/javascripts/prototype.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/cart.js"></script>
|
||||
# <script type="text/javascript" src="/javascripts/checkout.js"></script>
|
||||
#
|
||||
# javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false =>
|
||||
# <script type="text/javascript" src="/javascripts/shop.js"></script>
|
||||
def javascript_include_tag(*sources)
|
||||
options = sources.extract_options!.stringify_keys
|
||||
cache = options.delete("cache")
|
||||
|
||||
if ActionController::Base.perform_caching && cache
|
||||
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
|
||||
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
|
||||
|
||||
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources))
|
||||
javascript_src_tag(joined_javascript_name, options)
|
||||
else
|
||||
expand_javascript_sources(sources).collect { |source| javascript_src_tag(source, options) }.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
# Register one or more additional JavaScript files to be included when
|
||||
# <tt>javascript_include_tag :defaults</tt> is called. This method is
|
||||
# typically intended to be called from plugin initialization to register additional
|
||||
# .js files that the plugin installed in <tt>public/javascripts</tt>.
|
||||
def self.register_javascript_include_default(*sources)
|
||||
@@javascript_default_sources.concat(sources)
|
||||
end
|
||||
|
||||
def self.reset_javascript_include_default #:nodoc:
|
||||
@@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
|
||||
end
|
||||
|
||||
# Computes the path to a stylesheet asset in the public stylesheets directory.
|
||||
# If the +source+ filename has no extension, .css will be appended.
|
||||
# Full paths from the document root will be passed through.
|
||||
# Used internally by stylesheet_link_tag to build the stylesheet path.
|
||||
#
|
||||
# ==== Examples
|
||||
# stylesheet_path "style" # => /stylesheets/style.css
|
||||
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
|
||||
# stylesheet_path "/dir/style.css" # => /dir/style.css
|
||||
# stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style.css
|
||||
# stylesheet_path "http://www.railsapplication.com/css/style.js" # => http://www.railsapplication.com/css/style.css
|
||||
def stylesheet_path(source)
|
||||
compute_public_path(source, 'stylesheets', 'css')
|
||||
end
|
||||
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
|
||||
|
||||
# Returns a stylesheet link tag for the sources specified as arguments. If
|
||||
# you don't specify an extension, .css will be appended automatically.
|
||||
# You can modify the link attributes by passing a hash as the last argument.
|
||||
#
|
||||
# ==== Examples
|
||||
# stylesheet_link_tag "style" # =>
|
||||
# <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# stylesheet_link_tag "style.css" # =>
|
||||
# <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# stylesheet_link_tag "http://www.railsapplication.com/style.css" # =>
|
||||
# <link href="http://www.railsapplication.com/style.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# stylesheet_link_tag "style", :media => "all" # =>
|
||||
# <link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# stylesheet_link_tag "style", :media => "print" # =>
|
||||
# <link href="/stylesheets/style.css" media="print" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# stylesheet_link_tag "random.styles", "/css/stylish" # =>
|
||||
# <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# You can also include all styles in the stylesheet directory using :all as the source:
|
||||
#
|
||||
# stylesheet_link_tag :all # =>
|
||||
# <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# == Caching multiple stylesheets into one
|
||||
#
|
||||
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
|
||||
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
|
||||
# is set to true (which is the case by default for the Rails production environment, but not for the development
|
||||
# environment). Examples:
|
||||
#
|
||||
# ==== Examples
|
||||
# stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
|
||||
# <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
|
||||
# <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is false =>
|
||||
# <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
# <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true =>
|
||||
# <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
def stylesheet_link_tag(*sources)
|
||||
options = sources.extract_options!.stringify_keys
|
||||
cache = options.delete("cache")
|
||||
|
||||
if ActionController::Base.perform_caching && cache
|
||||
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
|
||||
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
|
||||
|
||||
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources))
|
||||
stylesheet_tag(joined_stylesheet_name, options)
|
||||
else
|
||||
expand_stylesheet_sources(sources).collect { |source| stylesheet_tag(source, options) }.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
# Computes the path to an image asset in the public images directory.
|
||||
# Full paths from the document root will be passed through.
|
||||
# Used internally by image_tag to build the image path.
|
||||
#
|
||||
# ==== Examples
|
||||
# image_path("edit") # => /images/edit
|
||||
# image_path("edit.png") # => /images/edit.png
|
||||
# image_path("icons/edit.png") # => /images/icons/edit.png
|
||||
# image_path("/icons/edit.png") # => /icons/edit.png
|
||||
# image_path("http://www.railsapplication.com/img/edit.png") # => http://www.railsapplication.com/img/edit.png
|
||||
def image_path(source)
|
||||
compute_public_path(source, 'images')
|
||||
end
|
||||
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
|
||||
|
||||
# Returns an html image tag for the +source+. The +source+ can be a full
|
||||
# path or a file that exists in your public images directory.
|
||||
#
|
||||
# ==== Options
|
||||
# You can add HTML attributes using the +options+. The +options+ supports
|
||||
# three additional keys for convenience and conformance:
|
||||
#
|
||||
# * <tt>:alt</tt> - If no alt text is given, the file name part of the
|
||||
# +source+ is used (capitalized and without the extension)
|
||||
# * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
|
||||
# width="30" and height="45". <tt>:size</tt> will be ignored if the
|
||||
# value is not in the correct format.
|
||||
# * <tt>:mouseover</tt> - Set an alternate image to be used when the onmouseover
|
||||
# event is fired, and sets the original image to be replaced onmouseout.
|
||||
# This can be used to implement an easy image toggle that fires on onmouseover.
|
||||
#
|
||||
# ==== Examples
|
||||
# image_tag("icon") # =>
|
||||
# <img src="/images/icon" alt="Icon" />
|
||||
# image_tag("icon.png") # =>
|
||||
# <img src="/images/icon.png" alt="Icon" />
|
||||
# image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
|
||||
# <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" />
|
||||
# image_tag("/icons/icon.gif", :size => "16x16") # =>
|
||||
# <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
|
||||
# image_tag("/icons/icon.gif", :height => '32', :width => '32') # =>
|
||||
# <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
|
||||
# image_tag("/icons/icon.gif", :class => "menu_icon") # =>
|
||||
# <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
|
||||
# image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
|
||||
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
|
||||
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
|
||||
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
|
||||
def image_tag(source, options = {})
|
||||
options.symbolize_keys!
|
||||
|
||||
options[:src] = path_to_image(source)
|
||||
options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize
|
||||
|
||||
if size = options.delete(:size)
|
||||
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
|
||||
end
|
||||
|
||||
if mouseover = options.delete(:mouseover)
|
||||
options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
|
||||
options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
|
||||
end
|
||||
|
||||
tag("img", options)
|
||||
end
|
||||
|
||||
private
|
||||
def file_exist?(path)
|
||||
@@file_exist_cache ||= {}
|
||||
if !(@@file_exist_cache[path] ||= File.exist?(path))
|
||||
@@file_exist_cache[path] = true
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Add the .ext if not present. Return full URLs otherwise untouched.
|
||||
# Prefix with /dir/ if lacking a leading /. Account for relative URL
|
||||
# roots. Rewrite the asset path for cache-busting asset ids. Include
|
||||
# asset host, if configured, with the correct request protocol.
|
||||
def compute_public_path(source, dir, ext = nil, include_host = true)
|
||||
has_request = @controller.respond_to?(:request)
|
||||
|
||||
cache_key =
|
||||
if has_request
|
||||
[ @controller.request.protocol,
|
||||
ActionController::Base.asset_host.to_s,
|
||||
@controller.request.relative_url_root,
|
||||
dir, source, ext, include_host ].join
|
||||
else
|
||||
[ ActionController::Base.asset_host.to_s,
|
||||
dir, source, ext, include_host ].join
|
||||
end
|
||||
|
||||
ActionView::Base.computed_public_paths[cache_key] ||=
|
||||
begin
|
||||
source += ".#{ext}" if File.extname(source).blank? && ext
|
||||
|
||||
if source =~ %r{^[-a-z]+://}
|
||||
source
|
||||
else
|
||||
source = "/#{dir}/#{source}" unless source[0] == ?/
|
||||
if has_request
|
||||
source = "#{@controller.request.relative_url_root}#{source}"
|
||||
end
|
||||
rewrite_asset_path!(source)
|
||||
|
||||
if include_host
|
||||
host = compute_asset_host(source)
|
||||
|
||||
if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
|
||||
host = "#{@controller.request.protocol}#{host}"
|
||||
end
|
||||
|
||||
"#{host}#{source}"
|
||||
else
|
||||
source
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Pick an asset host for this source. Returns nil if no host is set,
|
||||
# the host if no wildcard is set, the host interpolated with the
|
||||
# numbers 0-3 if it contains %d (the number is the source hash mod 4),
|
||||
# or the value returned from invoking the proc if it's a proc.
|
||||
def compute_asset_host(source)
|
||||
if host = ActionController::Base.asset_host
|
||||
if host.is_a?(Proc)
|
||||
host.call(source)
|
||||
else
|
||||
host % (source.hash % 4)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Use the RAILS_ASSET_ID environment variable or the source's
|
||||
# modification time as its cache-busting asset id.
|
||||
def rails_asset_id(source)
|
||||
if asset_id = ENV["RAILS_ASSET_ID"]
|
||||
asset_id
|
||||
else
|
||||
path = File.join(ASSETS_DIR, source)
|
||||
|
||||
if File.exist?(path)
|
||||
File.mtime(path).to_i.to_s
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Break out the asset path rewrite so you wish to put the asset id
|
||||
# someplace other than the query string.
|
||||
def rewrite_asset_path!(source)
|
||||
asset_id = rails_asset_id(source)
|
||||
source << "?#{asset_id}" if !asset_id.blank?
|
||||
end
|
||||
|
||||
def javascript_src_tag(source, options)
|
||||
content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
|
||||
end
|
||||
|
||||
def stylesheet_tag(source, options)
|
||||
tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
|
||||
end
|
||||
|
||||
def compute_javascript_paths(sources)
|
||||
expand_javascript_sources(sources).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
|
||||
end
|
||||
|
||||
def compute_stylesheet_paths(sources)
|
||||
expand_stylesheet_sources(sources).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
|
||||
end
|
||||
|
||||
def expand_javascript_sources(sources)
|
||||
case
|
||||
when sources.include?(:all)
|
||||
all_javascript_files = Dir[File.join(JAVASCRIPTS_DIR, '*.js')].collect { |file| File.basename(file).split(".", 0).first }.sort
|
||||
sources = ((@@javascript_default_sources.dup & all_javascript_files) + all_javascript_files).uniq
|
||||
|
||||
when sources.include?(:defaults)
|
||||
sources = sources[0..(sources.index(:defaults))] +
|
||||
@@javascript_default_sources.dup +
|
||||
sources[(sources.index(:defaults) + 1)..sources.length]
|
||||
|
||||
sources.delete(:defaults)
|
||||
sources << "application" if file_exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
|
||||
end
|
||||
|
||||
sources
|
||||
end
|
||||
|
||||
def expand_stylesheet_sources(sources)
|
||||
if sources.first == :all
|
||||
@@all_stylesheet_sources ||= Dir[File.join(STYLESHEETS_DIR, '*.css')].collect { |file| File.basename(file).split(".", 1).first }.sort
|
||||
else
|
||||
sources
|
||||
end
|
||||
end
|
||||
|
||||
def join_asset_file_contents(paths)
|
||||
paths.collect { |path| File.read(File.join(ASSETS_DIR, path.split("?").first)) }.join("\n\n")
|
||||
end
|
||||
|
||||
def write_asset_file_contents(joined_asset_path, asset_paths)
|
||||
unless file_exist?(joined_asset_path)
|
||||
FileUtils.mkdir_p(File.dirname(joined_asset_path))
|
||||
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
111
vendor/rails/actionpack/lib/action_view/helpers/atom_feed_helper.rb
vendored
Normal file
111
vendor/rails/actionpack/lib/action_view/helpers/atom_feed_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other
|
||||
# template languages).
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
module AtomFeedHelper
|
||||
# Full usage example:
|
||||
#
|
||||
# config/routes.rb:
|
||||
# ActionController::Routing::Routes.draw do |map|
|
||||
# map.resources :posts
|
||||
# map.root :controller => "posts"
|
||||
# end
|
||||
#
|
||||
# app/controllers/posts_controller.rb:
|
||||
# class PostsController < ApplicationController::Base
|
||||
# # GET /posts.html
|
||||
# # GET /posts.atom
|
||||
# def index
|
||||
# @posts = Post.find(:all)
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html
|
||||
# format.atom
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# app/views/posts/index.atom.builder:
|
||||
# atom_feed do |feed|
|
||||
# feed.title("My great blog!")
|
||||
# feed.updated((@posts.first.created_at))
|
||||
#
|
||||
# for post in @posts
|
||||
# feed.entry(post) do |entry|
|
||||
# entry.title(post.title)
|
||||
# entry.content(post.body, :type => 'html')
|
||||
#
|
||||
# entry.author do |author|
|
||||
# author.name("DHH")
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The options are for atom_feed are:
|
||||
#
|
||||
# * <tt>:language</tt>: Defaults to "en-US".
|
||||
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
|
||||
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
|
||||
#
|
||||
# atom_feed yields a AtomFeedBuilder instance.
|
||||
def atom_feed(options = {}, &block)
|
||||
xml = options[:xml] || eval("xml", block.binding)
|
||||
xml.instruct!
|
||||
|
||||
xml.feed "xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom' do
|
||||
xml.id("tag:#{request.host}:#{request.request_uri.split(".")[0].gsub("/", "")}")
|
||||
xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port))
|
||||
|
||||
if options[:url]
|
||||
xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url)
|
||||
end
|
||||
|
||||
yield AtomFeedBuilder.new(xml, self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class AtomFeedBuilder
|
||||
def initialize(xml, view)
|
||||
@xml, @view = xml, view
|
||||
end
|
||||
|
||||
# Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used.
|
||||
def updated(date_or_time = nil)
|
||||
@xml.updated((date_or_time || Time.now.utc).xmlschema)
|
||||
end
|
||||
|
||||
# Creates an entry tag for a specific record and prefills the id using class and id.
|
||||
#
|
||||
# Options:
|
||||
#
|
||||
# * <tt>:updated</tt>: Time of update. Defaults to the created_at attribute on the record if one such exists.
|
||||
# * <tt>:published</tt>: Time first published. Defaults to the updated_at attribute on the record if one such exists.
|
||||
# * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
|
||||
def entry(record, options = {})
|
||||
@xml.entry do
|
||||
@xml.id("tag:#{@view.request.host_with_port}:#{record.class}#{record.id}")
|
||||
|
||||
if options[:published] || (record.respond_to?(:created_at) && record.created_at)
|
||||
@xml.published((options[:published] || record.created_at).xmlschema)
|
||||
end
|
||||
|
||||
if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
|
||||
@xml.updated((options[:updated] || record.updated_at).xmlschema)
|
||||
end
|
||||
|
||||
@xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record))
|
||||
|
||||
yield @xml
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def method_missing(method, *arguments)
|
||||
@xml.__send__(method, *arguments)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
31
vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb
vendored
Normal file
31
vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
require 'benchmark'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# This helper offers a method to measure the execution time of a block
|
||||
# in a template.
|
||||
module BenchmarkHelper
|
||||
# Allows you to measure the execution time of a block
|
||||
# in a template and records the result to the log. Wrap this block around
|
||||
# expensive operations or possible bottlenecks to get a time reading
|
||||
# for the operation. For example, let's say you thought your file
|
||||
# processing method was taking too long; you could wrap it in a benchmark block.
|
||||
#
|
||||
# <% benchmark "Process data files" do %>
|
||||
# <%= expensive_files_operation %>
|
||||
# <% end %>
|
||||
#
|
||||
# That would add something like "Process data files (0.34523)" to the log,
|
||||
# which you can then use to compare timings when optimizing your code.
|
||||
#
|
||||
# You may give an optional logger level as the second argument
|
||||
# (:debug, :info, :warn, :error); the default value is :info.
|
||||
def benchmark(message = "Benchmarking", level = :info)
|
||||
if @logger
|
||||
real = Benchmark.realtime { yield }
|
||||
@logger.send level, "#{message} (#{'%.5f' % real})"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
39
vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb
vendored
Normal file
39
vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
module ActionView
|
||||
module Helpers
|
||||
# This helper to exposes a method for caching of view fragments.
|
||||
# See ActionController::Caching::Fragments for usage instructions.
|
||||
module CacheHelper
|
||||
# A method for caching fragments of a view rather than an entire
|
||||
# action or page. This technique is useful caching pieces like
|
||||
# menus, lists of news topics, static HTML fragments, and so on.
|
||||
# This method takes a block that contains the content you wish
|
||||
# to cache. See ActionController::Caching::Fragments for more
|
||||
# information.
|
||||
#
|
||||
# ==== Examples
|
||||
# If you wanted to cache a navigation menu, you could do the
|
||||
# following.
|
||||
#
|
||||
# <% cache do %>
|
||||
# <%= render :partial => "menu" %>
|
||||
# <% end %>
|
||||
#
|
||||
# You can also cache static content...
|
||||
#
|
||||
# <% cache do %>
|
||||
# <p>Hello users! Welcome to our website!</p>
|
||||
# <% end %>
|
||||
#
|
||||
# ...and static content mixed with RHTML content.
|
||||
#
|
||||
# <% cache do %>
|
||||
# Topics:
|
||||
# <%= render :partial => "topics", :collection => @topic_list %>
|
||||
# <i>Topics listed alphabetically</i>
|
||||
# <% end %>
|
||||
def cache(name = {}, &block)
|
||||
@controller.cache_erb_fragment(block, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
162
vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb
vendored
Normal file
162
vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
module ActionView
|
||||
module Helpers
|
||||
# CaptureHelper exposes methods to let you extract generated markup which
|
||||
# can be used in other parts of a template or layout file.
|
||||
# It provides a method to capture blocks into variables through capture and
|
||||
# a way to capture a block of markup for use in a layout through content_for.
|
||||
module CaptureHelper
|
||||
# The capture method allows you to extract part of a template into a
|
||||
# variable. You can then use this variable anywhere in your templates or layout.
|
||||
#
|
||||
# ==== Examples
|
||||
# The capture method can be used in ERb templates...
|
||||
#
|
||||
# <% @greeting = capture do %>
|
||||
# Welcome to my shiny new web page! The date and time is
|
||||
# <%= Time.now %>
|
||||
# <% end %>
|
||||
#
|
||||
# ...and Builder (RXML) templates.
|
||||
#
|
||||
# @timestamp = capture do
|
||||
# "The current timestamp is #{Time.now}."
|
||||
# end
|
||||
#
|
||||
# You can then use that variable anywhere else. For example:
|
||||
#
|
||||
# <html>
|
||||
# <head><title><%= @greeting %></title></head>
|
||||
# <body>
|
||||
# <b><%= @greeting %></b>
|
||||
# </body></html>
|
||||
#
|
||||
def capture(*args, &block)
|
||||
# execute the block
|
||||
begin
|
||||
buffer = eval(ActionView::Base.erb_variable, block.binding)
|
||||
rescue
|
||||
buffer = nil
|
||||
end
|
||||
|
||||
if buffer.nil?
|
||||
capture_block(*args, &block).to_s
|
||||
else
|
||||
capture_erb_with_buffer(buffer, *args, &block).to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Calling content_for stores a block of markup in an identifier for later use.
|
||||
# You can make subsequent calls to the stored content in other templates or the layout
|
||||
# by passing the identifier as an argument to <tt>yield</tt>.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# <% content_for :not_authorized do %>
|
||||
# alert('You are not authorized to do that!')
|
||||
# <% end %>
|
||||
#
|
||||
# You can then use <tt>yield :not_authorized</tt> anywhere in your templates.
|
||||
#
|
||||
# <%= yield :not_authorized if current_user.nil? %>
|
||||
#
|
||||
# You can also use this syntax alongside an existing call to <tt>yield</tt> in a layout. For example:
|
||||
#
|
||||
# <%# This is the layout %>
|
||||
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
# <head>
|
||||
# <title>My Website</title>
|
||||
# <%= yield :script %>
|
||||
# </head>
|
||||
# <body>
|
||||
# <%= yield %>
|
||||
# </body>
|
||||
# </html>
|
||||
#
|
||||
# And now, we'll create a view that has a content_for call that
|
||||
# creates the <tt>script</tt> identifier.
|
||||
#
|
||||
# <%# This is our view %>
|
||||
# Please login!
|
||||
#
|
||||
# <% content_for :script do %>
|
||||
# <script type="text/javascript">alert('You are not authorized to view this page!')</script>
|
||||
# <% end %>
|
||||
#
|
||||
# Then, in another view, you could to do something like this:
|
||||
#
|
||||
# <%= link_to_remote 'Logout', :action => 'logout' %>
|
||||
#
|
||||
# <% content_for :script do %>
|
||||
# <%= javascript_include_tag :defaults %>
|
||||
# <% end %>
|
||||
#
|
||||
# That will place <script> tags for Prototype, Scriptaculous, and application.js (if it exists)
|
||||
# on the page; this technique is useful if you'll only be using these scripts in a few views.
|
||||
#
|
||||
# Note that content_for concatenates the blocks it is given for a particular
|
||||
# identifier in order. For example:
|
||||
#
|
||||
# <% content_for :navigation do %>
|
||||
# <li><%= link_to 'Home', :action => 'index' %></li>
|
||||
# <% end %>
|
||||
#
|
||||
# <%# Add some other content, or use a different template: %>
|
||||
#
|
||||
# <% content_for :navigation do %>
|
||||
# <li><%= link_to 'Login', :action => 'login' %></li>
|
||||
# <% end %>
|
||||
#
|
||||
# Then, in another template or layout, this code would render both links in order:
|
||||
#
|
||||
# <ul><%= yield :navigation %></ul>
|
||||
#
|
||||
# Lastly, simple content can be passed as a parameter:
|
||||
#
|
||||
# <% content_for :script, javascript_include_tag(:defaults) %>
|
||||
#
|
||||
# WARNING: content_for is ignored in caches. So you shouldn't use it
|
||||
# for elements that will be fragment cached.
|
||||
#
|
||||
# The deprecated way of accessing a content_for block is to use an instance variable
|
||||
# named <tt>@content_for_#{name_of_the_content_block}</tt>. So <tt><%= content_for :footer %></tt>
|
||||
# would be available as <tt><%= @content_for_footer %></tt>. The preferred usage is now
|
||||
# <tt><%= yield :footer %></tt>.
|
||||
def content_for(name, content = nil, &block)
|
||||
existing_content_for = instance_variable_get("@content_for_#{name}").to_s
|
||||
new_content_for = existing_content_for + (block_given? ? capture(&block) : content)
|
||||
instance_variable_set("@content_for_#{name}", new_content_for)
|
||||
end
|
||||
|
||||
private
|
||||
def capture_block(*args, &block)
|
||||
block.call(*args)
|
||||
end
|
||||
|
||||
def capture_erb(*args, &block)
|
||||
buffer = eval(ActionView::Base.erb_variable, block.binding)
|
||||
capture_erb_with_buffer(buffer, *args, &block)
|
||||
end
|
||||
|
||||
def capture_erb_with_buffer(buffer, *args, &block)
|
||||
pos = buffer.length
|
||||
block.call(*args)
|
||||
|
||||
# extract the block
|
||||
data = buffer[pos..-1]
|
||||
|
||||
# replace it in the original with empty string
|
||||
buffer[pos..-1] = ''
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
def erb_content_for(name, &block)
|
||||
eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_erb(&block)"
|
||||
end
|
||||
|
||||
def block_content_for(name, &block)
|
||||
eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_block(&block)"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
689
vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb
vendored
Executable file
689
vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb
vendored
Executable file
|
|
@ -0,0 +1,689 @@
|
|||
require "date"
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods
|
||||
# share a number of common options that are as follows:
|
||||
#
|
||||
# * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give
|
||||
# birthday[month] instead of date[month] if passed to the select_month method.
|
||||
# * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
|
||||
# * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, the select_month
|
||||
# method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of "date[month]".
|
||||
module DateHelper
|
||||
DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')
|
||||
|
||||
# Reports the approximate distance in time between two Time or Date objects or integers as seconds.
|
||||
# Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs
|
||||
# Distances are reported base on the following table:
|
||||
#
|
||||
# 0 <-> 29 secs # => less than a minute
|
||||
# 30 secs <-> 1 min, 29 secs # => 1 minute
|
||||
# 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
|
||||
# 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
|
||||
# 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
|
||||
# 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
|
||||
# 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
|
||||
# 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
|
||||
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
|
||||
# 1 yr <-> 2 yrs minus 1 secs # => about 1 year
|
||||
# 2 yrs <-> max time or date # => over [2..X] years
|
||||
#
|
||||
# With include_seconds = true and the difference < 1 minute 29 seconds
|
||||
# 0-4 secs # => less than 5 seconds
|
||||
# 5-9 secs # => less than 10 seconds
|
||||
# 10-19 secs # => less than 20 seconds
|
||||
# 20-39 secs # => half a minute
|
||||
# 40-59 secs # => less than a minute
|
||||
# 60-89 secs # => 1 minute
|
||||
#
|
||||
# ==== Examples
|
||||
# from_time = Time.now
|
||||
# distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
|
||||
# distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
|
||||
# distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
|
||||
# distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
|
||||
# distance_of_time_in_words(from_time, 3.years.from_now) # => over 3 years
|
||||
# distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days
|
||||
# distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
|
||||
# distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
|
||||
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
|
||||
# distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
|
||||
# distance_of_time_in_words(from_time, from_time + 4.years + 15.days + 30.minutes + 5.seconds) # => over 4 years
|
||||
#
|
||||
# to_time = Time.now + 6.years + 19.days
|
||||
# distance_of_time_in_words(from_time, to_time, true) # => over 6 years
|
||||
# distance_of_time_in_words(to_time, from_time, true) # => over 6 years
|
||||
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
|
||||
#
|
||||
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
|
||||
from_time = from_time.to_time if from_time.respond_to?(:to_time)
|
||||
to_time = to_time.to_time if to_time.respond_to?(:to_time)
|
||||
distance_in_minutes = (((to_time - from_time).abs)/60).round
|
||||
distance_in_seconds = ((to_time - from_time).abs).round
|
||||
|
||||
case distance_in_minutes
|
||||
when 0..1
|
||||
return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
|
||||
case distance_in_seconds
|
||||
when 0..4 then 'less than 5 seconds'
|
||||
when 5..9 then 'less than 10 seconds'
|
||||
when 10..19 then 'less than 20 seconds'
|
||||
when 20..39 then 'half a minute'
|
||||
when 40..59 then 'less than a minute'
|
||||
else '1 minute'
|
||||
end
|
||||
|
||||
when 2..44 then "#{distance_in_minutes} minutes"
|
||||
when 45..89 then 'about 1 hour'
|
||||
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
||||
when 1440..2879 then '1 day'
|
||||
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
|
||||
when 43200..86399 then 'about 1 month'
|
||||
when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
|
||||
when 525600..1051199 then 'about 1 year'
|
||||
else "over #{(distance_in_minutes / 525600).round} years"
|
||||
end
|
||||
end
|
||||
|
||||
# Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
|
||||
#
|
||||
# ==== Examples
|
||||
# time_ago_in_words(3.minutes.from_now) # => 3 minutes
|
||||
# time_ago_in_words(Time.now - 15.hours) # => 15 hours
|
||||
# time_ago_in_words(Time.now) # => less than a minute
|
||||
#
|
||||
# from_time = Time.now - 3.days - 14.minutes - 25.seconds # => 3 days
|
||||
def time_ago_in_words(from_time, include_seconds = false)
|
||||
distance_of_time_in_words(from_time, Time.now, include_seconds)
|
||||
end
|
||||
|
||||
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
|
||||
|
||||
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
|
||||
# +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash,
|
||||
# which accepts all the keys that each of the individual select builders do (like :use_month_numbers for select_month) as well as a range of
|
||||
# discard options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set to true, they'll
|
||||
# drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly
|
||||
# set the order of the tags using the <tt>:order</tt> option with an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in
|
||||
# the desired order. Symbols may be omitted and the respective select is not included.
|
||||
#
|
||||
# Pass the <tt>:default</tt> option to set the default date. Use a Time object or a Hash of :year, :month, :day, :hour, :minute, and :second.
|
||||
#
|
||||
# Passing :disabled => true as part of the +options+ will make elements inaccessible for change.
|
||||
#
|
||||
# NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute
|
||||
# date_select("post", "written_on")
|
||||
#
|
||||
# # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute,
|
||||
# # with the year in the year drop down box starting at 1995.
|
||||
# date_select("post", "written_on", :start_year => 1995)
|
||||
#
|
||||
# # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute,
|
||||
# # with the year in the year drop down box starting at 1995, numbers used for months instead of words,
|
||||
# # and without a day select box.
|
||||
# date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true,
|
||||
# :discard_day => true, :include_blank => true)
|
||||
#
|
||||
# # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute
|
||||
# # with the fields ordered as day, month, year rather than month, day, year.
|
||||
# date_select("post", "written_on", :order => [:day, :month, :year])
|
||||
#
|
||||
# # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute
|
||||
# # lacking a year field.
|
||||
# date_select("user", "birthday", :order => [:month, :day])
|
||||
#
|
||||
# # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute
|
||||
# # which is initially set to the date 3 days from the current date
|
||||
# date_select("post", "written_on", :default => 3.days.from_now)
|
||||
#
|
||||
# # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
|
||||
# # that will have a default day of 20.
|
||||
# date_select("credit_card", "bill_due", :default => { :day => 20 })
|
||||
#
|
||||
# The selects are prepared for multi-parameter assignment to an Active Record object.
|
||||
#
|
||||
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
|
||||
# choices are valid.
|
||||
def date_select(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options)
|
||||
end
|
||||
|
||||
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
|
||||
# time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
|
||||
# You can include the seconds with <tt>:include_seconds</tt>.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Creates a time select tag that, when POSTed, will be stored in the post variable in the sunrise attribute
|
||||
# time_select("post", "sunrise")
|
||||
#
|
||||
# # Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted attribute
|
||||
# time_select("order", "submitted")
|
||||
#
|
||||
# # Creates a time select tag that, when POSTed, will be stored in the mail variable in the sent_at attribute
|
||||
# time_select("mail", "sent_at")
|
||||
#
|
||||
# # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in
|
||||
# # the sunrise attribute.
|
||||
# time_select("post", "start_time", :include_seconds => true)
|
||||
#
|
||||
# # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in
|
||||
# # the submission_time attribute.
|
||||
# time_select("entry", "submission_time", :include_seconds => true)
|
||||
#
|
||||
# # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45.
|
||||
# time_select 'game', 'game_time', {:minute_step => 15}
|
||||
#
|
||||
# The selects are prepared for multi-parameter assignment to an Active Record object.
|
||||
#
|
||||
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
|
||||
# choices are valid.
|
||||
def time_select(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options)
|
||||
end
|
||||
|
||||
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
|
||||
# attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
|
||||
#
|
||||
# ==== Examples
|
||||
# # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on attribute
|
||||
# datetime_select("post", "written_on")
|
||||
#
|
||||
# # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
|
||||
# # post variable in the written_on attribute.
|
||||
# datetime_select("post", "written_on", :start_year => 1995)
|
||||
#
|
||||
# # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will be stored in the
|
||||
# # trip variable in the departing attribute.
|
||||
# datetime_select("trip", "departing", :default => 3.days.from_now)
|
||||
#
|
||||
# # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable as the written_on
|
||||
# # attribute.
|
||||
# datetime_select("post", "written_on", :discard_type => true)
|
||||
#
|
||||
# The selects are prepared for multi-parameter assignment to an Active Record object.
|
||||
def datetime_select(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options)
|
||||
end
|
||||
|
||||
# Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
|
||||
# It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
|
||||
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
|
||||
# will be appended onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt> and <tt>:time_separator</tt>
|
||||
# keys to the +options+ to control visual display of the elements.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_date_time = Time.now + 4.days
|
||||
#
|
||||
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
|
||||
# select_datetime(my_date_time)
|
||||
#
|
||||
# # Generates a datetime select that defaults to today (no specified datetime)
|
||||
# select_datetime()
|
||||
#
|
||||
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
|
||||
# # with the fields ordered year, month, day rather than month, day, year.
|
||||
# select_datetime(my_date_time, :order => [:year, :month, :day])
|
||||
#
|
||||
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
|
||||
# # with a '/' between each date field.
|
||||
# select_datetime(my_date_time, :date_separator => '/')
|
||||
#
|
||||
# # Generates a datetime select that discards the type of the field and defaults to the datetime in
|
||||
# # my_date_time (four days after today)
|
||||
# select_datetime(my_date_time, :discard_type => true)
|
||||
#
|
||||
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
|
||||
# # prefixed with 'payday' rather than 'date'
|
||||
# select_datetime(my_date_time, :prefix => 'payday')
|
||||
#
|
||||
def select_datetime(datetime = Time.now, options = {})
|
||||
separator = options[:datetime_separator] || ''
|
||||
select_date(datetime, options) + separator + select_time(datetime, options)
|
||||
end
|
||||
|
||||
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
|
||||
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
|
||||
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
|
||||
# will be appended onto the <tt>:order</tt> passed in.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_date = Time.today + 6.days
|
||||
#
|
||||
# # Generates a date select that defaults to the date in my_date (six days after today)
|
||||
# select_date(my_date)
|
||||
#
|
||||
# # Generates a date select that defaults to today (no specified date)
|
||||
# select_date()
|
||||
#
|
||||
# # Generates a date select that defaults to the date in my_date (six days after today)
|
||||
# # with the fields ordered year, month, day rather than month, day, year.
|
||||
# select_date(my_date, :order => [:year, :month, :day])
|
||||
#
|
||||
# # Generates a date select that discards the type of the field and defaults to the date in
|
||||
# # my_date (six days after today)
|
||||
# select_datetime(my_date_time, :discard_type => true)
|
||||
#
|
||||
# # Generates a date select that defaults to the datetime in my_date (six days after today)
|
||||
# # prefixed with 'payday' rather than 'date'
|
||||
# select_datetime(my_date_time, :prefix => 'payday')
|
||||
#
|
||||
def select_date(date = Date.today, options = {})
|
||||
options[:order] ||= []
|
||||
[:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
|
||||
|
||||
select_date = ''
|
||||
options[:order].each do |o|
|
||||
select_date << self.send("select_#{o}", date, options)
|
||||
end
|
||||
select_date
|
||||
end
|
||||
|
||||
# Returns a set of html select-tags (one for hour and minute)
|
||||
# You can set <tt>:time_separator</tt> key to format the output, and
|
||||
# the <tt>:include_seconds</tt> option to include an input for seconds.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds
|
||||
#
|
||||
# # Generates a time select that defaults to the time in my_time
|
||||
# select_time(my_time)
|
||||
#
|
||||
# # Generates a time select that defaults to the current time (no specified time)
|
||||
# select_time()
|
||||
#
|
||||
# # Generates a time select that defaults to the time in my_time,
|
||||
# # which has fields separated by ':'
|
||||
# select_time(my_time, :time_separator => ':')
|
||||
#
|
||||
# # Generates a time select that defaults to the time in my_time,
|
||||
# # that also includes an input for seconds
|
||||
# select_time(my_time, :include_seconds => true)
|
||||
#
|
||||
# # Generates a time select that defaults to the time in my_time, that has fields
|
||||
# # separated by ':' and includes an input for seconds
|
||||
# select_time(my_time, :time_separator => ':', :include_seconds => true)
|
||||
#
|
||||
def select_time(datetime = Time.now, options = {})
|
||||
separator = options[:time_separator] || ''
|
||||
select_hour(datetime, options) + separator + select_minute(datetime, options) + (options[:include_seconds] ? separator + select_second(datetime, options) : '')
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
|
||||
# The <tt>second</tt> can also be substituted for a second number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_time = Time.now + 16.minutes
|
||||
#
|
||||
# # Generates a select field for seconds that defaults to the seconds for the time in my_time
|
||||
# select_second(my_time)
|
||||
#
|
||||
# # Generates a select field for seconds that defaults to the number given
|
||||
# select_second(33)
|
||||
#
|
||||
# # Generates a select field for seconds that defaults to the seconds for the time in my_time
|
||||
# # that is named 'interval' rather than 'second'
|
||||
# select_second(my_time, :field_name => 'interval')
|
||||
#
|
||||
def select_second(datetime, options = {})
|
||||
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : ''
|
||||
if options[:use_hidden]
|
||||
options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : ''
|
||||
else
|
||||
second_options = []
|
||||
0.upto(59) do |second|
|
||||
second_options << ((val == second) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'second', second_options, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
|
||||
# Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute selected
|
||||
# The <tt>minute</tt> can also be substituted for a minute number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_time = Time.now + 6.hours
|
||||
#
|
||||
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
|
||||
# select_minute(my_time)
|
||||
#
|
||||
# # Generates a select field for minutes that defaults to the number given
|
||||
# select_minute(14)
|
||||
#
|
||||
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
|
||||
# # that is named 'stride' rather than 'second'
|
||||
# select_minute(my_time, :field_name => 'stride')
|
||||
#
|
||||
def select_minute(datetime, options = {})
|
||||
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'minute', val, options)
|
||||
else
|
||||
minute_options = []
|
||||
0.step(59, options[:minute_step] || 1) do |minute|
|
||||
minute_options << ((val == minute) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'minute', minute_options, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
|
||||
# The <tt>hour</tt> can also be substituted for a hour number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_time = Time.now + 6.hours
|
||||
#
|
||||
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
|
||||
# select_minute(my_time)
|
||||
#
|
||||
# # Generates a select field for minutes that defaults to the number given
|
||||
# select_minute(14)
|
||||
#
|
||||
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
|
||||
# # that is named 'stride' rather than 'second'
|
||||
# select_minute(my_time, :field_name => 'stride')
|
||||
#
|
||||
def select_hour(datetime, options = {})
|
||||
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'hour', val, options)
|
||||
else
|
||||
hour_options = []
|
||||
0.upto(23) do |hour|
|
||||
hour_options << ((val == hour) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'hour', hour_options, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
|
||||
# The <tt>date</tt> can also be substituted for a hour number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_date = Time.today + 2.days
|
||||
#
|
||||
# # Generates a select field for days that defaults to the day for the date in my_date
|
||||
# select_day(my_time)
|
||||
#
|
||||
# # Generates a select field for days that defaults to the number given
|
||||
# select_day(5)
|
||||
#
|
||||
# # Generates a select field for days that defaults to the day for the date in my_date
|
||||
# # that is named 'due' rather than 'day'
|
||||
# select_day(my_time, :field_name => 'due')
|
||||
#
|
||||
def select_day(date, options = {})
|
||||
val = date ? (date.kind_of?(Fixnum) ? date : date.day) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'day', val, options)
|
||||
else
|
||||
day_options = []
|
||||
1.upto(31) do |day|
|
||||
day_options << ((val == day) ?
|
||||
%(<option value="#{day}" selected="selected">#{day}</option>\n) :
|
||||
%(<option value="#{day}">#{day}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'day', day_options, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the months January through December with the current month selected.
|
||||
# The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
|
||||
# (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
|
||||
# set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names,
|
||||
# set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer to show month names as abbreviations,
|
||||
# set the <tt>:use_short_month</tt> key in +options+ to true. If you want to use your own month names, set the
|
||||
# <tt>:use_month_names</tt> key in +options+ to an array of 12 month names. Override the field name using the
|
||||
# <tt>:field_name</tt> option, 'month' by default.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Generates a select field for months that defaults to the current month that
|
||||
# # will use keys like "January", "March".
|
||||
# select_month(Date.today)
|
||||
#
|
||||
# # Generates a select field for months that defaults to the current month that
|
||||
# # is named "start" rather than "month"
|
||||
# select_month(Date.today, :field_name => 'start')
|
||||
#
|
||||
# # Generates a select field for months that defaults to the current month that
|
||||
# # will use keys like "1", "3".
|
||||
# select_month(Date.today, :use_month_numbers => true)
|
||||
#
|
||||
# # Generates a select field for months that defaults to the current month that
|
||||
# # will use keys like "1 - January", "3 - March".
|
||||
# select_month(Date.today, :add_month_numbers => true)
|
||||
#
|
||||
# # Generates a select field for months that defaults to the current month that
|
||||
# # will use keys like "Jan", "Mar".
|
||||
# select_month(Date.today, :use_short_month => true)
|
||||
#
|
||||
# # Generates a select field for months that defaults to the current month that
|
||||
# # will use keys like "Januar", "Marts."
|
||||
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
|
||||
#
|
||||
def select_month(date, options = {})
|
||||
val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'month', val, options)
|
||||
else
|
||||
month_options = []
|
||||
month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES)
|
||||
month_names.unshift(nil) if month_names.size < 13
|
||||
1.upto(12) do |month_number|
|
||||
month_name = if options[:use_month_numbers]
|
||||
month_number
|
||||
elsif options[:add_month_numbers]
|
||||
month_number.to_s + ' - ' + month_names[month_number]
|
||||
else
|
||||
month_names[month_number]
|
||||
end
|
||||
|
||||
month_options << ((val == month_number) ?
|
||||
%(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
|
||||
%(<option value="#{month_number}">#{month_name}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'month', month_options, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
|
||||
# can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the +options+. Both ascending and descending year
|
||||
# lists are supported by making <tt>:start_year</tt> less than or greater than <tt>:end_year</tt>. The <tt>date</tt> can also be
|
||||
# substituted for a year given as a number. Override the field name using the <tt>:field_name</tt> option, 'year' by default.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Generates a select field for years that defaults to the current year that
|
||||
# # has ascending year values
|
||||
# select_year(Date.today, :start_year => 1992, :end_year => 2007)
|
||||
#
|
||||
# # Generates a select field for years that defaults to the current year that
|
||||
# # is named 'birth' rather than 'year'
|
||||
# select_year(Date.today, :field_name => 'birth')
|
||||
#
|
||||
# # Generates a select field for years that defaults to the current year that
|
||||
# # has descending year values
|
||||
# select_year(Date.today, :start_year => 2005, :end_year => 1900)
|
||||
#
|
||||
# # Generates a select field for years that defaults to the year 2006 that
|
||||
# # has ascending year values
|
||||
# select_year(2006, :start_year => 2000, :end_year => 2010)
|
||||
#
|
||||
def select_year(date, options = {})
|
||||
val = date ? (date.kind_of?(Fixnum) ? date : date.year) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'year', val, options)
|
||||
else
|
||||
year_options = []
|
||||
y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
|
||||
|
||||
start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
|
||||
step_val = start_year < end_year ? 1 : -1
|
||||
start_year.step(end_year, step_val) do |year|
|
||||
year_options << ((val == year) ?
|
||||
%(<option value="#{year}" selected="selected">#{year}</option>\n) :
|
||||
%(<option value="#{year}">#{year}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'year', year_options, options)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def select_html(type, html_options, options)
|
||||
name_and_id_from_options(options, type)
|
||||
select_html = %(<select id="#{options[:id]}" name="#{options[:name]}")
|
||||
select_html << %( disabled="disabled") if options[:disabled]
|
||||
select_html << %(>\n)
|
||||
select_html << %(<option value=""></option>\n) if options[:include_blank]
|
||||
select_html << html_options.to_s
|
||||
select_html << "</select>\n"
|
||||
end
|
||||
|
||||
def hidden_html(type, value, options)
|
||||
name_and_id_from_options(options, type)
|
||||
hidden_html = %(<input type="hidden" id="#{options[:id]}" name="#{options[:name]}" value="#{value}" />\n)
|
||||
end
|
||||
|
||||
def name_and_id_from_options(options, type)
|
||||
options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
|
||||
options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
|
||||
end
|
||||
|
||||
def leading_zero_on_single_digits(number)
|
||||
number > 9 ? number : "0#{number}"
|
||||
end
|
||||
end
|
||||
|
||||
class InstanceTag #:nodoc:
|
||||
include DateHelper
|
||||
|
||||
def to_date_select_tag(options = {})
|
||||
date_or_time_select(options.merge(:discard_hour => true))
|
||||
end
|
||||
|
||||
def to_time_select_tag(options = {})
|
||||
date_or_time_select options.merge(:discard_year => true, :discard_month => true)
|
||||
end
|
||||
|
||||
def to_datetime_select_tag(options = {})
|
||||
date_or_time_select options
|
||||
end
|
||||
|
||||
private
|
||||
def date_or_time_select(options)
|
||||
defaults = { :discard_type => true }
|
||||
options = defaults.merge(options)
|
||||
datetime = value(object)
|
||||
datetime ||= default_time_from_options(options[:default]) unless options[:include_blank]
|
||||
|
||||
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
|
||||
|
||||
order = (options[:order] ||= [:year, :month, :day])
|
||||
|
||||
# Discard explicit and implicit by not being included in the :order
|
||||
discard = {}
|
||||
discard[:year] = true if options[:discard_year] or !order.include?(:year)
|
||||
discard[:month] = true if options[:discard_month] or !order.include?(:month)
|
||||
discard[:day] = true if options[:discard_day] or discard[:month] or !order.include?(:day)
|
||||
discard[:hour] = true if options[:discard_hour]
|
||||
discard[:minute] = true if options[:discard_minute] or discard[:hour]
|
||||
discard[:second] = true unless options[:include_seconds] && !discard[:minute]
|
||||
|
||||
# If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are valid
|
||||
# (otherwise it could be 31 and february wouldn't be a valid date)
|
||||
if datetime && discard[:day] && !discard[:month]
|
||||
datetime = datetime.change(:day => 1)
|
||||
end
|
||||
|
||||
# Maintain valid dates by including hidden fields for discarded elements
|
||||
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
|
||||
|
||||
# Ensure proper ordering of :hour, :minute and :second
|
||||
[:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
|
||||
|
||||
date_or_time_select = ''
|
||||
order.reverse.each do |param|
|
||||
# Send hidden fields for discarded elements once output has started
|
||||
# This ensures AR can reconstruct valid dates using ParseDate
|
||||
next if discard[param] && date_or_time_select.empty?
|
||||
|
||||
date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param]))))
|
||||
date_or_time_select.insert(0,
|
||||
case param
|
||||
when :hour then (discard[:year] && discard[:day] ? "" : " — ")
|
||||
when :minute then " : "
|
||||
when :second then options[:include_seconds] ? " : " : ""
|
||||
else ""
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
date_or_time_select
|
||||
end
|
||||
|
||||
def options_with_prefix(position, options)
|
||||
prefix = "#{@object_name}"
|
||||
if options[:index]
|
||||
prefix << "[#{options[:index]}]"
|
||||
elsif @auto_index
|
||||
prefix << "[#{@auto_index}]"
|
||||
end
|
||||
options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]")
|
||||
end
|
||||
|
||||
def default_time_from_options(default)
|
||||
case default
|
||||
when nil
|
||||
Time.now
|
||||
when Date, Time
|
||||
default
|
||||
else
|
||||
# Rename :minute and :second to :min and :sec
|
||||
default[:min] ||= default[:minute]
|
||||
default[:sec] ||= default[:second]
|
||||
|
||||
[:year, :month, :day, :hour, :min, :sec].each do |key|
|
||||
default[key] ||= Time.now.send(key)
|
||||
end
|
||||
|
||||
Time.mktime(default[:year], default[:month], default[:day],
|
||||
default[:hour], default[:min], default[:sec])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FormBuilder
|
||||
def date_select(method, options = {})
|
||||
@template.date_select(@object_name, method, options.merge(:object => @object))
|
||||
end
|
||||
|
||||
def time_select(method, options = {})
|
||||
@template.time_select(@object_name, method, options.merge(:object => @object))
|
||||
end
|
||||
|
||||
def datetime_select(method, options = {})
|
||||
@template.datetime_select(@object_name, method, options.merge(:object => @object))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
31
vendor/rails/actionpack/lib/action_view/helpers/debug_helper.rb
vendored
Normal file
31
vendor/rails/actionpack/lib/action_view/helpers/debug_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
module ActionView
|
||||
module Helpers
|
||||
# Provides a set of methods for making it easier to debug Rails objects.
|
||||
module DebugHelper
|
||||
# Returns a <pre>-tag that has +object+ dumped by YAML. This creates a very
|
||||
# readable way to inspect an object.
|
||||
#
|
||||
# ==== Example
|
||||
# my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]}
|
||||
# debug(my_hash)
|
||||
#
|
||||
# => <pre class='debug_dump'>---
|
||||
# first: 1
|
||||
# second: two
|
||||
# third:
|
||||
# - 1
|
||||
# - 2
|
||||
# - 3
|
||||
# </pre>
|
||||
def debug(object)
|
||||
begin
|
||||
Marshal::dump(object)
|
||||
"<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", " ")}</pre>"
|
||||
rescue Exception => e # errors from Marshal or YAML
|
||||
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
|
||||
"<code class='debug_dump'>#{h(object.inspect)}</code>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
689
vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb
vendored
Normal file
689
vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,689 @@
|
|||
require 'cgi'
|
||||
require 'action_view/helpers/date_helper'
|
||||
require 'action_view/helpers/tag_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# Form helpers are designed to make working with models much easier compared to using just standard HTML
|
||||
# elements by providing a set of methods for creating forms based on your models. This helper generates the HTML
|
||||
# for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form
|
||||
# is submitted (i.e., when the user hits the submit button or <tt>form.submit</tt> is called via JavaScript), the form inputs will be bundled into the <tt>params</tt> object and passed back to the controller.
|
||||
#
|
||||
# There are two types of form helpers: those that specifically work with model attributes and those that don't.
|
||||
# This helper deals with those that work with model attributes; to see an example of form helpers that don't work
|
||||
# with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
|
||||
#
|
||||
# The core method of this helper, form_for, gives you the ability to create a form for a model instance;
|
||||
# for example, let's say that you have a model <tt>Person</tt> and want to create a new instance of it:
|
||||
#
|
||||
# # Note: a @person variable will have been created in the controller.
|
||||
# # For example: @person = Person.new
|
||||
# <% form_for :person, @person, :url => { :action => "create" } do |f| %>
|
||||
# <%= f.text_field :first_name %>
|
||||
# <%= f.text_field :last_name %>
|
||||
# <%= submit_tag 'Create' %>
|
||||
# <% end %>
|
||||
#
|
||||
# The HTML generated for this would be:
|
||||
#
|
||||
# <form action="/persons/create" method="post">
|
||||
# <input id="person_first_name" name="person[first_name]" size="30" type="text" />
|
||||
# <input id="person_last_name" name="person[last_name]" size="30" type="text" />
|
||||
# <input name="commit" type="submit" value="Create" />
|
||||
# </form>
|
||||
#
|
||||
# The <tt>params</tt> object created when this form is submitted would look like:
|
||||
#
|
||||
# {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
|
||||
#
|
||||
# The params hash has a nested <tt>person</tt> value, which can therefore be accessed with <tt>params[:person]</tt> in the controller.
|
||||
# If were editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than <tt>Person.new</tt> in the controller), the objects
|
||||
# attribute values are filled into the form (e.g., the <tt>person_first_name</tt> field would have that person's first name in it).
|
||||
#
|
||||
# If the object name contains square brackets the id for the object will be inserted. For example:
|
||||
#
|
||||
# <%= text_field "person[]", "name" %>
|
||||
#
|
||||
# ...will generate the following ERb.
|
||||
#
|
||||
# <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
|
||||
#
|
||||
# If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial
|
||||
# used by <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may come in handy. Example:
|
||||
#
|
||||
# <%= text_field "person", "name", "index" => 1 %>
|
||||
#
|
||||
# ...becomes...
|
||||
#
|
||||
# <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
|
||||
#
|
||||
# There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
|
||||
# link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
|
||||
module FormHelper
|
||||
# Creates a form and a scope around a specific model object that is used as a base for questioning about
|
||||
# values for the fields.
|
||||
#
|
||||
# <% form_for :person, @person, :url => { :action => "update" } do |f| %>
|
||||
# First name: <%= f.text_field :first_name %>
|
||||
# Last name : <%= f.text_field :last_name %>
|
||||
# Biography : <%= f.text_area :biography %>
|
||||
# Admin? : <%= f.check_box :admin %>
|
||||
# <% end %>
|
||||
#
|
||||
# Worth noting is that the form_for tag is called in a ERb evaluation block, not an ERb output block. So that's <tt><% %></tt>,
|
||||
# not <tt><%= %></tt>. Also worth noting is that form_for yields a <tt>form_builder</tt> object, in this example as <tt>f</tt>, which emulates
|
||||
# the API for the stand-alone FormHelper methods, but without the object name. So instead of <tt>text_field :person, :name</tt>,
|
||||
# you get away with <tt>f.text_field :name</tt>.
|
||||
#
|
||||
# Even further, the form_for method allows you to more easily escape the instance variable convention. So while the stand-alone
|
||||
# approach would require <tt>text_field :person, :name, :object => person</tt>
|
||||
# to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with
|
||||
# <tt>:person, person</tt> and all subsequent field calls save <tt>:person</tt> and <tt>:object => person</tt>.
|
||||
#
|
||||
# Also note that form_for doesn't create an exclusive scope. It's still possible to use both the stand-alone FormHelper methods
|
||||
# and methods from FormTagHelper. For example:
|
||||
#
|
||||
# <% form_for :person, @person, :url => { :action => "update" } do |f| %>
|
||||
# First name: <%= f.text_field :first_name %>
|
||||
# Last name : <%= f.text_field :last_name %>
|
||||
# Biography : <%= text_area :person, :biography %>
|
||||
# Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
|
||||
# <% end %>
|
||||
#
|
||||
# Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base,
|
||||
# like FormOptionHelper#collection_select and DateHelper#datetime_select.
|
||||
#
|
||||
# HTML attributes for the form tag can be given as :html => {...}. For example:
|
||||
#
|
||||
# <% form_for :person, @person, :html => {:id => 'person_form'} do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# The above form will then have the <tt>id</tt> attribute with the value </tt>person_form</tt>, which you can then
|
||||
# style with CSS or manipulate with JavaScript.
|
||||
#
|
||||
# === Relying on record identification
|
||||
#
|
||||
# In addition to manually configuring the form_for call, you can also rely on record identification, which will use
|
||||
# the conventions and named routes of that approach. Examples:
|
||||
#
|
||||
# <% form_for(@post) do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# This will expand to be the same as:
|
||||
#
|
||||
# <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# And for new records:
|
||||
#
|
||||
# <% form_for(Post.new) do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# This will expand to be the same as:
|
||||
#
|
||||
# <% form_for :post, @post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# You can also overwrite the individual conventions, like this:
|
||||
#
|
||||
# <% form_for(@post, :url => super_post_path(@post)) do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# And for namespaced routes, like admin_post_url:
|
||||
#
|
||||
# <% form_for([:admin, @post]) do |f| %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# === Customized form builders
|
||||
#
|
||||
# You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers,
|
||||
# then use your custom builder. For example, let's say you made a helper to automatically add labels to form inputs.
|
||||
#
|
||||
# <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
|
||||
# <%= f.text_field :first_name %>
|
||||
# <%= f.text_field :last_name %>
|
||||
# <%= text_area :person, :biography %>
|
||||
# <%= check_box_tag "person[admin]", @person.company.admin? %>
|
||||
# <% end %>
|
||||
#
|
||||
# In many cases you will want to wrap the above in another helper, so you could do something like the following:
|
||||
#
|
||||
# def labelled_form_for(name, object, options, &proc)
|
||||
# form_for(name, object, options.merge(:builder => LabellingFormBuiler), &proc)
|
||||
# end
|
||||
#
|
||||
# If you don't need to attach a form to a model instance, then check out FormTagHelper#form_tag.
|
||||
def form_for(record_or_name_or_array, *args, &proc)
|
||||
raise ArgumentError, "Missing block" unless block_given?
|
||||
|
||||
options = args.extract_options!
|
||||
|
||||
case record_or_name_or_array
|
||||
when String, Symbol
|
||||
object_name = record_or_name_or_array
|
||||
when Array
|
||||
object = record_or_name_or_array.last
|
||||
object_name = ActionController::RecordIdentifier.singular_class_name(object)
|
||||
apply_form_for_options!(record_or_name_or_array, options)
|
||||
args.unshift object
|
||||
else
|
||||
object = record_or_name_or_array
|
||||
object_name = ActionController::RecordIdentifier.singular_class_name(object)
|
||||
apply_form_for_options!([object], options)
|
||||
args.unshift object
|
||||
end
|
||||
|
||||
concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
|
||||
fields_for(object_name, *(args << options), &proc)
|
||||
concat('</form>', proc.binding)
|
||||
end
|
||||
|
||||
def apply_form_for_options!(object_or_array, options) #:nodoc:
|
||||
object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
|
||||
|
||||
html_options =
|
||||
if object.respond_to?(:new_record?) && object.new_record?
|
||||
{ :class => dom_class(object, :new), :id => dom_id(object), :method => :post }
|
||||
else
|
||||
{ :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }
|
||||
end
|
||||
|
||||
options[:html] ||= {}
|
||||
options[:html].reverse_merge!(html_options)
|
||||
options[:url] ||= polymorphic_path(object_or_array)
|
||||
end
|
||||
|
||||
# Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes
|
||||
# fields_for suitable for specifying additional model objects in the same form:
|
||||
#
|
||||
# ==== Examples
|
||||
# <% form_for @person, :url => { :action => "update" } do |person_form| %>
|
||||
# First name: <%= person_form.text_field :first_name %>
|
||||
# Last name : <%= person_form.text_field :last_name %>
|
||||
#
|
||||
# <% fields_for @person.permission do |permission_fields| %>
|
||||
# Admin? : <%= permission_fields.check_box :admin %>
|
||||
# <% end %>
|
||||
# <% end %>
|
||||
#
|
||||
# ...or if you have an object that needs to be represented as a different parameter, like a Client that acts as a Person:
|
||||
#
|
||||
# <% fields_for :person, @client do |permission_fields| %>
|
||||
# Admin?: <%= permission_fields.check_box :admin %>
|
||||
# <% end %>
|
||||
#
|
||||
# ...or if you don't have an object, just a name of the parameter
|
||||
#
|
||||
# <% fields_for :person do |permission_fields| %>
|
||||
# Admin?: <%= permission_fields.check_box :admin %>
|
||||
# <% end %>
|
||||
#
|
||||
# Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base,
|
||||
# like FormOptionHelper#collection_select and DateHelper#datetime_select.
|
||||
def fields_for(record_or_name_or_array, *args, &block)
|
||||
raise ArgumentError, "Missing block" unless block_given?
|
||||
options = args.extract_options!
|
||||
|
||||
case record_or_name_or_array
|
||||
when String, Symbol
|
||||
object_name = record_or_name_or_array
|
||||
object = args.first
|
||||
when Array
|
||||
object = record_or_name_or_array.last
|
||||
object_name = ActionController::RecordIdentifier.singular_class_name(object)
|
||||
apply_form_for_options!(record_or_name_or_array, options)
|
||||
else
|
||||
object = record_or_name_or_array
|
||||
object_name = ActionController::RecordIdentifier.singular_class_name(object)
|
||||
end
|
||||
|
||||
builder = options[:builder] || ActionView::Base.default_form_builder
|
||||
yield builder.new(object_name, object, self, options, block)
|
||||
end
|
||||
|
||||
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
|
||||
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
|
||||
# it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
|
||||
# onto the HTML as an HTML element attribute as in the example shown.
|
||||
#
|
||||
# ==== Examples
|
||||
# label(:post, :title)
|
||||
# #=> <label for="post_title">Title</label>
|
||||
#
|
||||
# label(:post, :title, "A short title")
|
||||
# #=> <label for="post_title">A short title</label>
|
||||
#
|
||||
# label(:post, :title, "A short title", :class => "title_label")
|
||||
# #=> <label for="post_title" class="title_label">A short title</label>
|
||||
#
|
||||
def label(object_name, method, text = nil, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options)
|
||||
end
|
||||
|
||||
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
|
||||
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
|
||||
# shown.
|
||||
#
|
||||
# ==== Examples
|
||||
# text_field(:post, :title, :size => 20)
|
||||
# # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
|
||||
#
|
||||
# text_field(:post, :title, :class => "create_input")
|
||||
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
|
||||
#
|
||||
# text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
|
||||
# # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/>
|
||||
#
|
||||
# text_field(:snippet, :code, :size => 20, :class => 'code_input')
|
||||
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
|
||||
#
|
||||
def text_field(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
|
||||
end
|
||||
|
||||
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
|
||||
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
|
||||
# shown.
|
||||
#
|
||||
# ==== Examples
|
||||
# password_field(:login, :pass, :size => 20)
|
||||
# # => <input type="text" id="login_pass" name="login[pass]" size="20" value="#{@login.pass}" />
|
||||
#
|
||||
# password_field(:account, :secret, :class => "form_input")
|
||||
# # => <input type="text" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
|
||||
#
|
||||
# password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
|
||||
# # => <input type="text" id="user_password" name="user[password]" value="#{@user.password}" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
|
||||
#
|
||||
# password_field(:account, :pin, :size => 20, :class => 'form_input')
|
||||
# # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
|
||||
#
|
||||
def password_field(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
|
||||
end
|
||||
|
||||
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
|
||||
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
|
||||
# shown.
|
||||
#
|
||||
# ==== Examples
|
||||
# hidden_field(:signup, :pass_confirm)
|
||||
# # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
|
||||
#
|
||||
# hidden_field(:post, :tag_list)
|
||||
# # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
|
||||
#
|
||||
# hidden_field(:user, :token)
|
||||
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
|
||||
def hidden_field(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
|
||||
end
|
||||
|
||||
# Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
|
||||
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
|
||||
# shown.
|
||||
#
|
||||
# ==== Examples
|
||||
# file_field(:user, :avatar)
|
||||
# # => <input type="file" id="user_avatar" name="user[avatar]" />
|
||||
#
|
||||
# file_field(:post, :attached, :accept => 'text/html')
|
||||
# # => <input type="file" id="post_attached" name="post[attached]" />
|
||||
#
|
||||
# file_field(:attachment, :file, :class => 'file_input')
|
||||
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
|
||||
#
|
||||
def file_field(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
|
||||
end
|
||||
|
||||
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
|
||||
# on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
|
||||
# hash with +options+.
|
||||
#
|
||||
# ==== Examples
|
||||
# text_area(:post, :body, :cols => 20, :rows => 40)
|
||||
# # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
|
||||
# # #{@post.body}
|
||||
# # </textarea>
|
||||
#
|
||||
# text_area(:comment, :text, :size => "20x30")
|
||||
# # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
|
||||
# # #{@comment.text}
|
||||
# # </textarea>
|
||||
#
|
||||
# text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
|
||||
# # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
|
||||
# # #{@application.notes}
|
||||
# # </textarea>
|
||||
#
|
||||
# text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
|
||||
# # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
|
||||
# # #{@entry.body}
|
||||
# # </textarea>
|
||||
def text_area(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
|
||||
end
|
||||
|
||||
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
# assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that
|
||||
# integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a
|
||||
# hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+
|
||||
# is set to 0 which is convenient for boolean values. Since HTTP standards say that unchecked checkboxes don't post anything,
|
||||
# we add a hidden value with the same name as the checkbox as a work around.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Let's say that @post.validated? is 1:
|
||||
# check_box("post", "validated")
|
||||
# # => <input type="checkbox" id="post_validate" name="post[validated]" value="1" checked="checked" />
|
||||
# # <input name="post[validated]" type="hidden" value="0" />
|
||||
#
|
||||
# # Let's say that @puppy.gooddog is "no":
|
||||
# check_box("puppy", "gooddog", {}, "yes", "no")
|
||||
# # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
|
||||
# # <input name="puppy[gooddog]" type="hidden" value="no" />
|
||||
#
|
||||
# check_box("eula", "accepted", {}, "yes", "no", :class => 'eula_check')
|
||||
# # => <input type="checkbox" id="eula_accepted" name="eula[accepted]" value="no" />
|
||||
# # <input name="eula[accepted]" type="hidden" value="no" />
|
||||
#
|
||||
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
|
||||
end
|
||||
|
||||
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
|
||||
# assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
|
||||
# radio button will be checked. Additional options on the input tag can be passed as a
|
||||
# hash with +options+.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Let's say that @post.category returns "rails":
|
||||
# radio_button("post", "category", "rails")
|
||||
# radio_button("post", "category", "java")
|
||||
# # => <input type="radio" id="post_category" name="post[category]" value="rails" checked="checked" />
|
||||
# # <input type="radio" id="post_category" name="post[category]" value="java" />
|
||||
#
|
||||
# radio_button("user", "receive_newsletter", "yes")
|
||||
# radio_button("user", "receive_newsletter", "no")
|
||||
# # => <input type="radio" id="user_receive_newsletter" name="user[receive_newsletter]" value="yes" />
|
||||
# # <input type="radio" id="user_receive_newsletter" name="user[receive_newsletter]" value="no" checked="checked" />
|
||||
def radio_button(object_name, method, tag_value, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
|
||||
end
|
||||
end
|
||||
|
||||
class InstanceTag #:nodoc:
|
||||
include Helpers::TagHelper
|
||||
|
||||
attr_reader :method_name, :object_name
|
||||
|
||||
DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
|
||||
DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
|
||||
DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
|
||||
DEFAULT_DATE_OPTIONS = { :discard_type => true }.freeze unless const_defined?(:DEFAULT_DATE_OPTIONS)
|
||||
|
||||
def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
|
||||
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
|
||||
@template_object, @local_binding = template_object, local_binding
|
||||
@object = object
|
||||
if @object_name.sub!(/\[\]$/,"")
|
||||
if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
|
||||
@auto_index = object.to_param
|
||||
else
|
||||
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_label_tag(text = nil, options = {})
|
||||
name_and_id = options.dup
|
||||
add_default_name_and_id(name_and_id)
|
||||
options["for"] = name_and_id["id"]
|
||||
content = (text.blank? ? nil : text.to_s) || method_name.humanize
|
||||
content_tag("label", content, options)
|
||||
end
|
||||
|
||||
def to_input_field_tag(field_type, options = {})
|
||||
options = options.stringify_keys
|
||||
options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
|
||||
options = DEFAULT_FIELD_OPTIONS.merge(options)
|
||||
if field_type == "hidden"
|
||||
options.delete("size")
|
||||
end
|
||||
options["type"] = field_type
|
||||
options["value"] ||= value_before_type_cast(object) unless field_type == "file"
|
||||
add_default_name_and_id(options)
|
||||
tag("input", options)
|
||||
end
|
||||
|
||||
def to_radio_button_tag(tag_value, options = {})
|
||||
options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
|
||||
options["type"] = "radio"
|
||||
options["value"] = tag_value
|
||||
if options.has_key?("checked")
|
||||
cv = options.delete "checked"
|
||||
checked = cv == true || cv == "checked"
|
||||
else
|
||||
checked = self.class.radio_button_checked?(value(object), tag_value)
|
||||
end
|
||||
options["checked"] = "checked" if checked
|
||||
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
|
||||
options["id"] ||= defined?(@auto_index) ?
|
||||
"#{@object_name}_#{@auto_index}_#{@method_name}_#{pretty_tag_value}" :
|
||||
"#{@object_name}_#{@method_name}_#{pretty_tag_value}"
|
||||
add_default_name_and_id(options)
|
||||
tag("input", options)
|
||||
end
|
||||
|
||||
def to_text_area_tag(options = {})
|
||||
options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
|
||||
add_default_name_and_id(options)
|
||||
|
||||
if size = options.delete("size")
|
||||
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
|
||||
end
|
||||
|
||||
content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
|
||||
end
|
||||
|
||||
def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
|
||||
options = options.stringify_keys
|
||||
options["type"] = "checkbox"
|
||||
options["value"] = checked_value
|
||||
if options.has_key?("checked")
|
||||
cv = options.delete "checked"
|
||||
checked = cv == true || cv == "checked"
|
||||
else
|
||||
checked = self.class.check_box_checked?(value(object), checked_value)
|
||||
end
|
||||
options["checked"] = "checked" if checked
|
||||
add_default_name_and_id(options)
|
||||
tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
|
||||
end
|
||||
|
||||
def to_date_tag()
|
||||
defaults = DEFAULT_DATE_OPTIONS.dup
|
||||
date = value(object) || Date.today
|
||||
options = Proc.new { |position| defaults.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
|
||||
html_day_select(date, options.call(3)) +
|
||||
html_month_select(date, options.call(2)) +
|
||||
html_year_select(date, options.call(1))
|
||||
end
|
||||
|
||||
def to_boolean_select_tag(options = {})
|
||||
options = options.stringify_keys
|
||||
add_default_name_and_id(options)
|
||||
value = value(object)
|
||||
tag_text = "<select"
|
||||
tag_text << tag_options(options)
|
||||
tag_text << "><option value=\"false\""
|
||||
tag_text << " selected" if value == false
|
||||
tag_text << ">False</option><option value=\"true\""
|
||||
tag_text << " selected" if value
|
||||
tag_text << ">True</option></select>"
|
||||
end
|
||||
|
||||
def to_content_tag(tag_name, options = {})
|
||||
content_tag(tag_name, value(object), options)
|
||||
end
|
||||
|
||||
def object
|
||||
@object || (@template_object.instance_variable_get("@#{@object_name}") rescue nil)
|
||||
end
|
||||
|
||||
def value(object)
|
||||
self.class.value(object, @method_name)
|
||||
end
|
||||
|
||||
def value_before_type_cast(object)
|
||||
self.class.value_before_type_cast(object, @method_name)
|
||||
end
|
||||
|
||||
class << self
|
||||
def value(object, method_name)
|
||||
object.send method_name unless object.nil?
|
||||
end
|
||||
|
||||
def value_before_type_cast(object, method_name)
|
||||
unless object.nil?
|
||||
object.respond_to?(method_name + "_before_type_cast") ?
|
||||
object.send(method_name + "_before_type_cast") :
|
||||
object.send(method_name)
|
||||
end
|
||||
end
|
||||
|
||||
def check_box_checked?(value, checked_value)
|
||||
case value
|
||||
when TrueClass, FalseClass
|
||||
value
|
||||
when NilClass
|
||||
false
|
||||
when Integer
|
||||
value != 0
|
||||
when String
|
||||
value == checked_value
|
||||
else
|
||||
value.to_i != 0
|
||||
end
|
||||
end
|
||||
|
||||
def radio_button_checked?(value, checked_value)
|
||||
value.to_s == checked_value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def add_default_name_and_id(options)
|
||||
if options.has_key?("index")
|
||||
options["name"] ||= tag_name_with_index(options["index"])
|
||||
options["id"] ||= tag_id_with_index(options["index"])
|
||||
options.delete("index")
|
||||
elsif defined?(@auto_index)
|
||||
options["name"] ||= tag_name_with_index(@auto_index)
|
||||
options["id"] ||= tag_id_with_index(@auto_index)
|
||||
else
|
||||
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
|
||||
options["id"] ||= tag_id
|
||||
end
|
||||
end
|
||||
|
||||
def tag_name
|
||||
"#{@object_name}[#{@method_name}]"
|
||||
end
|
||||
|
||||
def tag_name_with_index(index)
|
||||
"#{@object_name}[#{index}][#{@method_name}]"
|
||||
end
|
||||
|
||||
def tag_id
|
||||
"#{sanitized_object_name}_#{@method_name}"
|
||||
end
|
||||
|
||||
def tag_id_with_index(index)
|
||||
"#{sanitized_object_name}_#{index}_#{@method_name}"
|
||||
end
|
||||
|
||||
def sanitized_object_name
|
||||
@object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
||||
end
|
||||
end
|
||||
|
||||
class FormBuilder #:nodoc:
|
||||
# The methods which wrap a form helper call.
|
||||
class_inheritable_accessor :field_helpers
|
||||
self.field_helpers = (FormHelper.instance_methods - ['form_for'])
|
||||
|
||||
attr_accessor :object_name, :object, :options
|
||||
|
||||
def initialize(object_name, object, template, options, proc)
|
||||
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
|
||||
end
|
||||
|
||||
(field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
|
||||
src = <<-end_src
|
||||
def #{selector}(method, options = {})
|
||||
@template.send(#{selector.inspect}, @object_name, method, options.merge(:object => @object))
|
||||
end
|
||||
end_src
|
||||
class_eval src, __FILE__, __LINE__
|
||||
end
|
||||
|
||||
def fields_for(record_or_name_or_array, *args, &block)
|
||||
case record_or_name_or_array
|
||||
when String, Symbol
|
||||
name = "#{object_name}[#{record_or_name_or_array}]"
|
||||
when Array
|
||||
object = record_or_name_or_array.last
|
||||
name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
|
||||
args.unshift(object)
|
||||
else
|
||||
object = record_or_name_or_array
|
||||
name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
|
||||
args.unshift(object)
|
||||
end
|
||||
|
||||
@template.fields_for(name, *args, &block)
|
||||
end
|
||||
|
||||
def label(method, text = nil, options = {})
|
||||
@template.label(@object_name, method, text, options.merge(:object => @object))
|
||||
end
|
||||
|
||||
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
||||
@template.check_box(@object_name, method, options.merge(:object => @object), checked_value, unchecked_value)
|
||||
end
|
||||
|
||||
def radio_button(method, tag_value, options = {})
|
||||
@template.radio_button(@object_name, method, tag_value, options.merge(:object => @object))
|
||||
end
|
||||
|
||||
def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError")
|
||||
@template.error_message_on(@object, method, prepend_text, append_text, css_class)
|
||||
end
|
||||
|
||||
def error_messages(options = {})
|
||||
@template.error_messages_for(@object_name, options.merge(:object => @object))
|
||||
end
|
||||
|
||||
def submit(value = "Save changes", options = {})
|
||||
@template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Base
|
||||
cattr_accessor :default_form_builder
|
||||
self.default_form_builder = ::ActionView::Helpers::FormBuilder
|
||||
end
|
||||
end
|
||||
425
vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb
vendored
Normal file
425
vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
require 'cgi'
|
||||
require 'erb'
|
||||
require 'action_view/helpers/form_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# Provides a number of methods for turning different kinds of containers into a set of option tags.
|
||||
# == Options
|
||||
# The <tt>collection_select</tt>, <tt>country_select</tt>, <tt>select</tt>,
|
||||
# and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter,
|
||||
# a hash.
|
||||
#
|
||||
# * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
|
||||
#
|
||||
# For example,
|
||||
#
|
||||
# select("post", "category", Post::CATEGORIES, {:include_blank => true})
|
||||
#
|
||||
# could become:
|
||||
#
|
||||
# <select name="post[category]">
|
||||
# <option></option>
|
||||
# <option>joke</option>
|
||||
# <option>poem</option>
|
||||
# </select>
|
||||
#
|
||||
# Another common case is a select tag for an <tt>belongs_to</tt>-associated object.
|
||||
#
|
||||
# Example with @post.person_id => 2:
|
||||
#
|
||||
# select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'})
|
||||
#
|
||||
# could become:
|
||||
#
|
||||
# <select name="post[person_id]">
|
||||
# <option value="">None</option>
|
||||
# <option value="1">David</option>
|
||||
# <option value="2" selected="selected">Sam</option>
|
||||
# <option value="3">Tobias</option>
|
||||
# </select>
|
||||
#
|
||||
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'})
|
||||
#
|
||||
# could become:
|
||||
#
|
||||
# <select name="post[person_id]">
|
||||
# <option value="">Select Person</option>
|
||||
# <option value="1">David</option>
|
||||
# <option value="2">Sam</option>
|
||||
# <option value="3">Tobias</option>
|
||||
# </select>
|
||||
module FormOptionsHelper
|
||||
include ERB::Util
|
||||
|
||||
# Create a select tag and a series of contained option tags for the provided object and method.
|
||||
# The option currently held by the object will be selected, provided that the object is available.
|
||||
# See options_for_select for the required format of the choices parameter.
|
||||
#
|
||||
# Example with @post.person_id => 1:
|
||||
# select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, { :include_blank => true })
|
||||
#
|
||||
# could become:
|
||||
#
|
||||
# <select name="post[person_id]">
|
||||
# <option value=""></option>
|
||||
# <option value="1" selected="selected">David</option>
|
||||
# <option value="2">Sam</option>
|
||||
# <option value="3">Tobias</option>
|
||||
# </select>
|
||||
#
|
||||
# This can be used to provide a default set of options in the standard way: before rendering the create form, a
|
||||
# new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
|
||||
# to the database. Instead, a second model object is created when the create request is received.
|
||||
# This allows the user to submit a form page more than once with the expected results of creating multiple records.
|
||||
# In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
|
||||
#
|
||||
# By default, post.person_id is the selected option. Specify :selected => value to use a different selection
|
||||
# or :selected => nil to leave all options unselected.
|
||||
def select(object, method, choices, options = {}, html_options = {})
|
||||
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options)
|
||||
end
|
||||
|
||||
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
|
||||
# +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
|
||||
# be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
|
||||
# or <tt>:include_blank</tt> in the +options+ hash.
|
||||
#
|
||||
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
|
||||
# of +collection+. The return values are used as the +value+ attribute and contents of each
|
||||
# <tt><option></tt> tag, respectively.
|
||||
#
|
||||
# Example object structure for use with this method:
|
||||
# class Post < ActiveRecord::Base
|
||||
# belongs_to :author
|
||||
# end
|
||||
# class Author < ActiveRecord::Base
|
||||
# has_many :posts
|
||||
# def name_with_initial
|
||||
# "#{first_name.first}. #{last_name}"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Sample usage (selecting the associated +Author+ for an instance of +Post+, <tt>@post</tt>):
|
||||
# collection_select(:post, :author_id, Author.find(:all), :id, :name_with_initial, {:prompt => true})
|
||||
#
|
||||
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
|
||||
# <select name="post[author_id]">
|
||||
# <option value="">Please select</option>
|
||||
# <option value="1" selected="selected">D. Heinemeier Hansson</option>
|
||||
# <option value="2">D. Thomas</option>
|
||||
# <option value="3">M. Clark</option>
|
||||
# </select>
|
||||
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
|
||||
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
|
||||
end
|
||||
|
||||
# Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
|
||||
def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
|
||||
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
|
||||
end
|
||||
|
||||
# Return select and option tags for the given object and method, using
|
||||
# #time_zone_options_for_select to generate the list of option tags.
|
||||
#
|
||||
# In addition to the <tt>:include_blank</tt> option documented above,
|
||||
# this method also supports a <tt>:model</tt> option, which defaults
|
||||
# to TimeZone. This may be used by users to specify a different time
|
||||
# zone model object. (See #time_zone_options_for_select for more
|
||||
# information.)
|
||||
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
|
||||
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
|
||||
end
|
||||
|
||||
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. 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 +selected+ is specified, the matching "last" or element will get the selected option-tag. +Selected+
|
||||
# may also be an array of values to be selected when using a multiple select.
|
||||
#
|
||||
# Examples (call, result):
|
||||
# options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
|
||||
# <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
|
||||
#
|
||||
# options_for_select([ "VISA", "MasterCard" ], "MasterCard")
|
||||
# <option>VISA</option>\n<option selected="selected">MasterCard</option>
|
||||
#
|
||||
# options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
|
||||
# <option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
|
||||
#
|
||||
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
|
||||
# <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
|
||||
#
|
||||
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
|
||||
def options_for_select(container, selected = nil)
|
||||
container = container.to_a if Hash === container
|
||||
|
||||
options_for_select = container.inject([]) do |options, element|
|
||||
text, value = option_text_and_value(element)
|
||||
selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
|
||||
options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}>#{html_escape(text.to_s)}</option>)
|
||||
end
|
||||
|
||||
options_for_select.join("\n")
|
||||
end
|
||||
|
||||
# Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the
|
||||
# the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
|
||||
# If +selected+ is specified, the element returning a match on +value_method+ will get the selected option tag.
|
||||
#
|
||||
# Example (call, result). Imagine a loop iterating over each +person+ in <tt>@project.people</tt> to generate an input tag:
|
||||
# options_from_collection_for_select(@project.people, "id", "name")
|
||||
# <option value="#{person.id}">#{person.name}</option>
|
||||
#
|
||||
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
|
||||
def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
|
||||
options = collection.map do |element|
|
||||
[element.send(text_method), element.send(value_method)]
|
||||
end
|
||||
options_for_select(options, selected)
|
||||
end
|
||||
|
||||
# Returns a string of <tt><option></tt> tags, like <tt>#options_from_collection_for_select</tt>, but
|
||||
# groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments.
|
||||
#
|
||||
# Parameters:
|
||||
# +collection+:: An array of objects representing the <tt><optgroup></tt> tags
|
||||
# +group_method+:: The name of a method which, when called on a member of +collection+, returns an
|
||||
# array of child objects representing the <tt><option></tt> tags
|
||||
# +group_label_method+:: The name of a method which, when called on a member of +collection+, returns a
|
||||
# string to be used as the +label+ attribute for its <tt><optgroup></tt> tag
|
||||
# +option_key_method+:: The name of a method which, when called on a child object of a member of
|
||||
# +collection+, returns a value to be used as the +value+ attribute for its
|
||||
# <tt><option></tt> tag
|
||||
# +option_value_method+:: The name of a method which, when called on a child object of a member of
|
||||
# +collection+, returns a value to be used as the contents of its
|
||||
# <tt><option></tt> tag
|
||||
# +selected_key+:: A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
|
||||
# which will have the +selected+ attribute set. Corresponds to the return value
|
||||
# of one of the calls to +option_key_method+. If +nil+, no selection is made.
|
||||
#
|
||||
# Example object structure for use with this method:
|
||||
# class Continent < ActiveRecord::Base
|
||||
# has_many :countries
|
||||
# # attribs: id, name
|
||||
# end
|
||||
# class Country < ActiveRecord::Base
|
||||
# belongs_to :continent
|
||||
# # attribs: id, name, continent_id
|
||||
# end
|
||||
#
|
||||
# Sample usage:
|
||||
# option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3)
|
||||
#
|
||||
# Possible output:
|
||||
# <optgroup label="Africa">
|
||||
# <option value="1">Egypt</option>
|
||||
# <option value="4">Rwanda</option>
|
||||
# ...
|
||||
# </optgroup>
|
||||
# <optgroup label="Asia">
|
||||
# <option value="3" selected="selected">China</option>
|
||||
# <option value="12">India</option>
|
||||
# <option value="5">Japan</option>
|
||||
# ...
|
||||
# </optgroup>
|
||||
#
|
||||
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
|
||||
# wrap the output in an appropriate <tt><select></tt> tag.
|
||||
def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
|
||||
collection.inject("") do |options_for_select, group|
|
||||
group_label_string = eval("group.#{group_label_method}")
|
||||
options_for_select += "<optgroup label=\"#{html_escape(group_label_string)}\">"
|
||||
options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key)
|
||||
options_for_select += '</optgroup>'
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
|
||||
# have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
|
||||
# that they will be listed above the rest of the (long) list.
|
||||
#
|
||||
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
|
||||
def country_options_for_select(selected = nil, priority_countries = nil)
|
||||
country_options = ""
|
||||
|
||||
if priority_countries
|
||||
country_options += options_for_select(priority_countries, selected)
|
||||
country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
|
||||
end
|
||||
|
||||
return country_options + options_for_select(COUNTRIES, selected)
|
||||
end
|
||||
|
||||
# Returns a string of option tags for pretty much any time zone in the
|
||||
# world. Supply a TimeZone name as +selected+ to have it marked as the
|
||||
# selected option tag. You can also supply an array of TimeZone objects
|
||||
# as +priority_zones+, so that they will be listed above the rest of the
|
||||
# (long) list. (You can use TimeZone.us_zones as a convenience for
|
||||
# obtaining a list of the US time zones.)
|
||||
#
|
||||
# The +selected+ parameter must be either +nil+, or a string that names
|
||||
# a TimeZone.
|
||||
#
|
||||
# By default, +model+ is the TimeZone constant (which can be obtained
|
||||
# in ActiveRecord as a value object). The only requirement is that the
|
||||
# +model+ parameter be an object that responds to #all, and returns
|
||||
# an array of objects that represent time zones.
|
||||
#
|
||||
# NOTE: Only the option tags are returned, you have to wrap this call in
|
||||
# a regular HTML select tag.
|
||||
def time_zone_options_for_select(selected = nil, priority_zones = nil, model = TimeZone)
|
||||
zone_options = ""
|
||||
|
||||
zones = model.all
|
||||
convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
|
||||
|
||||
if priority_zones
|
||||
zone_options += options_for_select(convert_zones[priority_zones], selected)
|
||||
zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
|
||||
|
||||
zones = zones.reject { |z| priority_zones.include?( z ) }
|
||||
end
|
||||
|
||||
zone_options += options_for_select(convert_zones[zones], selected)
|
||||
zone_options
|
||||
end
|
||||
|
||||
private
|
||||
def option_text_and_value(option)
|
||||
# Options are [text, value] pairs or strings used for both.
|
||||
if !option.is_a?(String) and option.respond_to?(:first) and option.respond_to?(:last)
|
||||
[option.first, option.last]
|
||||
else
|
||||
[option, option]
|
||||
end
|
||||
end
|
||||
|
||||
def option_value_selected?(value, selected)
|
||||
if selected.respond_to?(:include?) && !selected.is_a?(String)
|
||||
selected.include? value
|
||||
else
|
||||
value == selected
|
||||
end
|
||||
end
|
||||
|
||||
# All the countries included in the country_options output.
|
||||
COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
|
||||
"Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
|
||||
"Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
|
||||
"Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
|
||||
"British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
|
||||
"Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
|
||||
"Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
|
||||
"Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
|
||||
"Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
|
||||
"El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
|
||||
"Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
|
||||
"French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
|
||||
"Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
|
||||
"Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
|
||||
"Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
|
||||
"Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
|
||||
"Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
|
||||
"Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
|
||||
"Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
|
||||
"Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
|
||||
"Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
|
||||
"Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
|
||||
"Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
|
||||
"Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
|
||||
"Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
|
||||
"Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
|
||||
"Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
|
||||
"Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
|
||||
"Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
|
||||
"South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
|
||||
"Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
|
||||
"Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
|
||||
"Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
|
||||
"Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
|
||||
"United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
|
||||
"Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
|
||||
"Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES")
|
||||
end
|
||||
|
||||
class InstanceTag #:nodoc:
|
||||
include FormOptionsHelper
|
||||
|
||||
def to_select_tag(choices, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
selected_value = options.has_key?(:selected) ? options[:selected] : value
|
||||
content_tag("select", add_options(options_for_select(choices, selected_value), options, selected_value), html_options)
|
||||
end
|
||||
|
||||
def to_collection_select_tag(collection, value_method, text_method, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
content_tag(
|
||||
"select", add_options(options_from_collection_for_select(collection, value_method, text_method, value), options, value), html_options
|
||||
)
|
||||
end
|
||||
|
||||
def to_country_select_tag(priority_countries, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
content_tag("select",
|
||||
add_options(
|
||||
country_options_for_select(value, priority_countries),
|
||||
options, value
|
||||
), html_options
|
||||
)
|
||||
end
|
||||
|
||||
def to_time_zone_select_tag(priority_zones, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
content_tag("select",
|
||||
add_options(
|
||||
time_zone_options_for_select(value, priority_zones, options[:model] || TimeZone),
|
||||
options, value
|
||||
), html_options
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
def add_options(option_tags, options, value = nil)
|
||||
if options[:include_blank]
|
||||
option_tags = "<option value=\"\">#{options[:include_blank] if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
|
||||
end
|
||||
if value.blank? && options[:prompt]
|
||||
("<option value=\"\">#{options[:prompt].kind_of?(String) ? options[:prompt] : 'Please select'}</option>\n") + option_tags
|
||||
else
|
||||
option_tags
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FormBuilder
|
||||
def select(method, choices, options = {}, html_options = {})
|
||||
@template.select(@object_name, method, choices, options.merge(:object => @object), html_options)
|
||||
end
|
||||
|
||||
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
||||
@template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options)
|
||||
end
|
||||
|
||||
def country_select(method, priority_countries = nil, options = {}, html_options = {})
|
||||
@template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options)
|
||||
end
|
||||
|
||||
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
||||
@template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
432
vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb
vendored
Normal file
432
vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
require 'cgi'
|
||||
require 'action_view/helpers/tag_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# Provides a number of methods for creating form tags that doesn't rely on an ActiveRecord object assigned to the template like
|
||||
# FormHelper does. Instead, you provide the names and values manually.
|
||||
#
|
||||
# NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
|
||||
# <tt>:disabled => true</tt> will give <tt>disabled="disabled"</tt>.
|
||||
module FormTagHelper
|
||||
# Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
|
||||
# ActionController::Base#url_for. The method for the form defaults to POST.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data".
|
||||
# * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
|
||||
# If "put", "delete", or another verb is used, a hidden input with name _method
|
||||
# is added to simulate the verb over post.
|
||||
# * A list of parameters to feed to the URL the form will be posted to.
|
||||
#
|
||||
# ==== Examples
|
||||
# form_tag('/posts')
|
||||
# # => <form action="/posts" method="post">
|
||||
#
|
||||
# form_tag('/posts/1', :method => :put)
|
||||
# # => <form action="/posts/1" method="put">
|
||||
#
|
||||
# form_tag('/upload', :multipart => true)
|
||||
# # => <form action="/upload" method="post" enctype="multipart/form-data">
|
||||
#
|
||||
# <% form_tag '/posts' do -%>
|
||||
# <div><%= submit_tag 'Save' %></div>
|
||||
# <% end -%>
|
||||
# # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
|
||||
def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
|
||||
html_options = html_options_for_form(url_for_options, options, *parameters_for_url)
|
||||
if block_given?
|
||||
form_tag_in_block(html_options, &block)
|
||||
else
|
||||
form_tag_html(html_options)
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a dropdown selection box, or if the <tt>:multiple</tt> option is set to true, a multiple
|
||||
# choice selection box.
|
||||
#
|
||||
# Helpers::FormOptions can be used to create common select boxes such as countries, time zones, or
|
||||
# associated records. <tt>option_tags</tt> is a string containing the option tags for the select box.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
|
||||
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
||||
# * Any other key creates standard HTML attributes for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
# select_tag "people", "<option>David</option>"
|
||||
# # => <select id="people" name="people"><option>David</option></select>
|
||||
#
|
||||
# select_tag "count", "<option>1</option><option>2</option><option>3</option><option>4</option>"
|
||||
# # => <select id="count" name="count"><option>1</option><option>2</option>
|
||||
# # <option>3</option><option>4</option></select>
|
||||
#
|
||||
# select_tag "colors", "<option>Red</option><option>Green</option><option>Blue</option>", :multiple => true
|
||||
# # => <select id="colors" multiple="multiple" name="colors"><option>Red</option>
|
||||
# # <option>Green</option><option>Blue</option></select>
|
||||
#
|
||||
# select_tag "locations", "<option>Home</option><option selected="selected">Work</option><option>Out</option>"
|
||||
# # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
|
||||
# # <option>Out</option></select>
|
||||
#
|
||||
# select_tag "access", "<option>Read</option><option>Write</option>", :multiple => true, :class => 'form_input'
|
||||
# # => <select class="form_input" id="access" multiple="multiple" name="access"><option>Read</option>
|
||||
# # <option>Write</option></select>
|
||||
#
|
||||
# select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>", :disabled => true
|
||||
# # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
|
||||
# # <option>Paris</option><option>Rome</option></select>
|
||||
def select_tag(name, option_tags = nil, options = {})
|
||||
content_tag :select, option_tags, { "name" => name, "id" => name }.update(options.stringify_keys)
|
||||
end
|
||||
|
||||
# Creates a standard text field; use these text fields to input smaller chunks of text like a username
|
||||
# or a search query.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
||||
# * <tt>:size</tt> - The number of visible characters that will fit in the input.
|
||||
# * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
|
||||
# * Any other key creates standard HTML attributes for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
# text_field_tag 'name'
|
||||
# # => <input id="name" name="name" type="text" />
|
||||
#
|
||||
# text_field_tag 'query', 'Enter your search query here'
|
||||
# # => <input id="query" name="query" type="text" value="Enter your search query here" />
|
||||
#
|
||||
# text_field_tag 'request', nil, :class => 'special_input'
|
||||
# # => <input class="special_input" id="request" name="request" type="text" />
|
||||
#
|
||||
# text_field_tag 'address', '', :size => 75
|
||||
# # => <input id="address" name="address" size="75" type="text" value="" />
|
||||
#
|
||||
# text_field_tag 'zip', nil, :maxlength => 5
|
||||
# # => <input id="zip" maxlength="5" name="zip" type="text" />
|
||||
#
|
||||
# text_field_tag 'payment_amount', '$0.00', :disabled => true
|
||||
# # => <input disabled="disabled" id="payment_amount" name="payment_amount" type="text" value="$0.00" />
|
||||
#
|
||||
# text_field_tag 'ip', '0.0.0.0', :maxlength => 15, :size => 20, :class => "ip-input"
|
||||
# # => <input class="ip-input" id="ip" maxlength="15" name="ip" size="20" type="text" value="0.0.0.0" />
|
||||
def text_field_tag(name, value = nil, options = {})
|
||||
tag :input, { "type" => "text", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
|
||||
end
|
||||
|
||||
# Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or
|
||||
# data that should be hidden from the user.
|
||||
#
|
||||
# ==== Options
|
||||
# * Creates standard HTML attributes for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
# hidden_field_tag 'tags_list'
|
||||
# # => <input id="tags_list" name="tags_list" type="hidden" />
|
||||
#
|
||||
# hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@'
|
||||
# # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" />
|
||||
#
|
||||
# hidden_field_tag 'collected_input', '', :onchange => "alert('Input collected!')"
|
||||
# # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
|
||||
# # type="hidden" value="" />
|
||||
def hidden_field_tag(name, value = nil, options = {})
|
||||
text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
|
||||
end
|
||||
|
||||
# Creates a file upload field. If you are using file uploads then you will also need
|
||||
# to set the multipart option for the form tag:
|
||||
#
|
||||
# <%= form_tag { :action => "post" }, { :multipart => true } %>
|
||||
# <label for="file">File to Upload</label> <%= file_field_tag "file" %>
|
||||
# <%= submit_tag %>
|
||||
# <%= end_form_tag %>
|
||||
#
|
||||
# The specified URL will then be passed a File object containing the selected file, or if the field
|
||||
# was left blank, a StringIO object.
|
||||
#
|
||||
# ==== Options
|
||||
# * Creates standard HTML attributes for the tag.
|
||||
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
||||
#
|
||||
# ==== Examples
|
||||
# file_field_tag 'attachment'
|
||||
# # => <input id="attachment" name="attachment" type="file" />
|
||||
#
|
||||
# file_field_tag 'avatar', :class => 'profile-input'
|
||||
# # => <input class="profile-input" id="avatar" name="avatar" type="file" />
|
||||
#
|
||||
# file_field_tag 'picture', :disabled => true
|
||||
# # => <input disabled="disabled" id="picture" name="picture" type="file" />
|
||||
#
|
||||
# file_field_tag 'resume', :value => '~/resume.doc'
|
||||
# # => <input id="resume" name="resume" type="file" value="~/resume.doc" />
|
||||
#
|
||||
# file_field_tag 'user_pic', :accept => 'image/png,image/gif,image/jpeg'
|
||||
# # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
|
||||
#
|
||||
# file_field_tag 'file', :accept => 'text/html', :class => 'upload', :value => 'index.html'
|
||||
# # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
|
||||
def file_field_tag(name, options = {})
|
||||
text_field_tag(name, nil, options.update("type" => "file"))
|
||||
end
|
||||
|
||||
# Creates a password field, a masked text field that will hide the users input behind a mask character.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
||||
# * <tt>:size</tt> - The number of visible characters that will fit in the input.
|
||||
# * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
|
||||
# * Any other key creates standard HTML attributes for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
# password_field_tag 'pass'
|
||||
# # => <input id="pass" name="pass" type="password" />
|
||||
#
|
||||
# password_field_tag 'secret', 'Your secret here'
|
||||
# # => <input id="secret" name="secret" type="password" value="Your secret here" />
|
||||
#
|
||||
# password_field_tag 'masked', nil, :class => 'masked_input_field'
|
||||
# # => <input class="masked_input_field" id="masked" name="masked" type="password" />
|
||||
#
|
||||
# password_field_tag 'token', '', :size => 15
|
||||
# # => <input id="token" name="token" size="15" type="password" value="" />
|
||||
#
|
||||
# password_field_tag 'key', nil, :maxlength => 16
|
||||
# # => <input id="key" maxlength="16" name="key" type="password" />
|
||||
#
|
||||
# password_field_tag 'confirm_pass', nil, :disabled => true
|
||||
# # => <input disabled="disabled" id="confirm_pass" name="confirm_pass" type="password" />
|
||||
#
|
||||
# password_field_tag 'pin', '1234', :maxlength => 4, :size => 6, :class => "pin-input"
|
||||
# # => <input class="pin-input" id="pin" maxlength="4" name="pin" size="6" type="password" value="1234" />
|
||||
def password_field_tag(name = "password", value = nil, options = {})
|
||||
text_field_tag(name, value, options.update("type" => "password"))
|
||||
end
|
||||
|
||||
# Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:size</tt> - A string specifying the dimensions (columns by rows) of the textarea (e.g., "25x10").
|
||||
# * <tt>:rows</tt> - Specify the number of rows in the textarea
|
||||
# * <tt>:cols</tt> - Specify the number of columns in the textarea
|
||||
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
||||
# * Any other key creates standard HTML attributes for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
# text_area_tag 'post'
|
||||
# # => <textarea id="post" name="post"></textarea>
|
||||
#
|
||||
# text_area_tag 'bio', @user.bio
|
||||
# # => <textarea id="bio" name="bio">This is my biography.</textarea>
|
||||
#
|
||||
# text_area_tag 'body', nil, :rows => 10, :cols => 25
|
||||
# # => <textarea cols="25" id="body" name="body" rows="10"></textarea>
|
||||
#
|
||||
# text_area_tag 'body', nil, :size => "25x10"
|
||||
# # => <textarea name="body" id="body" cols="25" rows="10"></textarea>
|
||||
#
|
||||
# text_area_tag 'description', "Description goes here.", :disabled => true
|
||||
# # => <textarea disabled="disabled" id="description" name="description">Description goes here.</textarea>
|
||||
#
|
||||
# text_area_tag 'comment', nil, :class => 'comment_input'
|
||||
# # => <textarea class="comment_input" id="comment" name="comment"></textarea>
|
||||
def text_area_tag(name, content = nil, options = {})
|
||||
options.stringify_keys!
|
||||
|
||||
if size = options.delete("size")
|
||||
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
|
||||
end
|
||||
|
||||
content_tag :textarea, content, { "name" => name, "id" => name }.update(options.stringify_keys)
|
||||
end
|
||||
|
||||
# Creates a check box form input tag.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
||||
# * Any other key creates standard HTML options for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
# check_box_tag 'accept'
|
||||
# # => <input id="accept" name="accept" type="checkbox" value="1" />
|
||||
#
|
||||
# check_box_tag 'rock', 'rock music'
|
||||
# # => <input id="rock" name="rock" type="checkbox" value="rock music" />
|
||||
#
|
||||
# check_box_tag 'receive_email', 'yes', true
|
||||
# # => <input checked="checked" id="receive_email" name="receive_email" type="checkbox" value="yes" />
|
||||
#
|
||||
# check_box_tag 'tos', 'yes', false, :class => 'accept_tos'
|
||||
# # => <input class="accept_tos" id="tos" name="tos" type="checkbox" value="yes" />
|
||||
#
|
||||
# check_box_tag 'eula', 'accepted', false, :disabled => true
|
||||
# # => <input disabled="disabled" id="eula" name="eula" type="checkbox" value="accepted" />
|
||||
def check_box_tag(name, value = "1", checked = false, options = {})
|
||||
html_options = { "type" => "checkbox", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
|
||||
html_options["checked"] = "checked" if checked
|
||||
tag :input, html_options
|
||||
end
|
||||
|
||||
# Creates a radio button; use groups of radio buttons named the same to allow users to
|
||||
# select from a group of options.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
||||
# * Any other key creates standard HTML options for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
# radio_button_tag 'gender', 'male'
|
||||
# # => <input id="gender_male" name="gender" type="radio" value="male" />
|
||||
#
|
||||
# radio_button_tag 'receive_updates', 'no', true
|
||||
# # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" />
|
||||
#
|
||||
# radio_button_tag 'time_slot', "3:00 p.m.", false, :disabled => true
|
||||
# # => <input disabled="disabled" id="time_slot_300_pm" name="time_slot" type="radio" value="3:00 p.m." />
|
||||
#
|
||||
# radio_button_tag 'color', "green", true, :class => "color_input"
|
||||
# # => <input checked="checked" class="color_input" id="color_green" name="color" type="radio" value="green" />
|
||||
def radio_button_tag(name, value, checked = false, options = {})
|
||||
pretty_tag_value = value.to_s.gsub(/\s/, "_").gsub(/(?!-)\W/, "").downcase
|
||||
pretty_name = name.to_s.gsub(/\[/, "_").gsub(/\]/, "")
|
||||
html_options = { "type" => "radio", "name" => name, "id" => "#{pretty_name}_#{pretty_tag_value}", "value" => value }.update(options.stringify_keys)
|
||||
html_options["checked"] = "checked" if checked
|
||||
tag :input, html_options
|
||||
end
|
||||
|
||||
# Creates a submit button with the text <tt>value</tt> as the caption.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
||||
# * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a disabled version
|
||||
# of the submit button when the form is submitted.
|
||||
# * Any other key creates standard HTML options for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
# submit_tag
|
||||
# # => <input name="commit" type="submit" value="Save changes" />
|
||||
#
|
||||
# submit_tag "Edit this article"
|
||||
# # => <input name="commit" type="submit" value="Edit this article" />
|
||||
#
|
||||
# submit_tag "Save edits", :disabled => true
|
||||
# # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
|
||||
#
|
||||
# submit_tag "Complete sale", :disable_with => "Please wait..."
|
||||
# # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();"
|
||||
# # type="submit" value="Complete sale" />
|
||||
#
|
||||
# submit_tag nil, :class => "form_submit"
|
||||
# # => <input class="form_submit" name="commit" type="submit" />
|
||||
#
|
||||
# submit_tag "Edit", :disable_with => "Editing...", :class => 'edit-button'
|
||||
# # => <input class="edit-button" disable_with="Editing..." name="commit" type="submit" value="Edit" />
|
||||
def submit_tag(value = "Save changes", options = {})
|
||||
options.stringify_keys!
|
||||
|
||||
if disable_with = options.delete("disable_with")
|
||||
options["onclick"] = [
|
||||
"this.setAttribute('originalValue', this.value)",
|
||||
"this.disabled=true",
|
||||
"this.value='#{disable_with}'",
|
||||
"#{options["onclick"]}",
|
||||
"result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit())",
|
||||
"if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false }",
|
||||
"return result",
|
||||
].join(";")
|
||||
end
|
||||
|
||||
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
|
||||
end
|
||||
|
||||
# Displays an image which when clicked will submit the form.
|
||||
#
|
||||
# <tt>source</tt> is passed to AssetTagHelper#image_path
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
||||
# * Any other key creates standard HTML options for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
# image_submit_tag("login.png")
|
||||
# # => <input src="/images/login.png" type="image" />
|
||||
#
|
||||
# image_submit_tag("purchase.png"), :disabled => true
|
||||
# # => <input disabled="disabled" src="/images/purchase.png" type="image" />
|
||||
#
|
||||
# image_submit_tag("search.png"), :class => 'search-button'
|
||||
# # => <input class="search-button" src="/images/search.png" type="image" />
|
||||
#
|
||||
# image_submit_tag("agree.png"), :disabled => true, :class => "agree-disagree-button"
|
||||
# # => <input class="agree-disagree-button" disabled="disabled" src="/images/agree.png" type="image" />
|
||||
def image_submit_tag(source, options = {})
|
||||
tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys)
|
||||
end
|
||||
|
||||
# Creates a field set for grouping HTML form elements.
|
||||
#
|
||||
# <tt>legend</tt> will become the fieldset's title (optional as per W3C).
|
||||
#
|
||||
# === Examples
|
||||
# <% field_set_tag do %>
|
||||
# <p><%= text_field_tag 'name' %></p>
|
||||
# <% end %>
|
||||
# # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset>
|
||||
#
|
||||
# <% field_set_tag 'Your details' do %>
|
||||
# <p><%= text_field_tag 'name' %></p>
|
||||
# <% end %>
|
||||
# # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset>
|
||||
def field_set_tag(legend = nil, &block)
|
||||
content = capture(&block)
|
||||
concat(tag(:fieldset, {}, true), block.binding)
|
||||
concat(content_tag(:legend, legend), block.binding) unless legend.blank?
|
||||
concat(content, block.binding)
|
||||
concat("</fieldset>", block.binding)
|
||||
end
|
||||
|
||||
private
|
||||
def html_options_for_form(url_for_options, options, *parameters_for_url)
|
||||
returning options.stringify_keys do |html_options|
|
||||
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
|
||||
html_options["action"] = url_for(url_for_options, *parameters_for_url)
|
||||
end
|
||||
end
|
||||
|
||||
def extra_tags_for_form(html_options)
|
||||
case method = html_options.delete("method").to_s
|
||||
when /^get$/i # must be case-insentive, but can't use downcase as might be nil
|
||||
html_options["method"] = "get"
|
||||
''
|
||||
when /^post$/i, "", nil
|
||||
html_options["method"] = "post"
|
||||
protect_against_forgery? ? content_tag(:div, token_tag, :style => 'margin:0;padding:0') : ''
|
||||
else
|
||||
html_options["method"] = "post"
|
||||
content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0')
|
||||
end
|
||||
end
|
||||
|
||||
def form_tag_html(html_options)
|
||||
extra_tags = extra_tags_for_form(html_options)
|
||||
tag(:form, html_options, true) + extra_tags
|
||||
end
|
||||
|
||||
def form_tag_in_block(html_options, &block)
|
||||
content = capture(&block)
|
||||
concat(form_tag_html(html_options), block.binding)
|
||||
concat(content, block.binding)
|
||||
concat("</form>", block.binding)
|
||||
end
|
||||
|
||||
def token_tag
|
||||
unless protect_against_forgery?
|
||||
''
|
||||
else
|
||||
tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
217
vendor/rails/actionpack/lib/action_view/helpers/javascript_helper.rb
vendored
Normal file
217
vendor/rails/actionpack/lib/action_view/helpers/javascript_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
require 'action_view/helpers/tag_helper'
|
||||
require 'action_view/helpers/prototype_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# Provides functionality for working with JavaScript in your views.
|
||||
#
|
||||
# == Ajax, controls and visual effects
|
||||
#
|
||||
# * For information on using Ajax, see
|
||||
# ActionView::Helpers::PrototypeHelper.
|
||||
# * For information on using controls and visual effects, see
|
||||
# ActionView::Helpers::ScriptaculousHelper.
|
||||
#
|
||||
# == Including the JavaScript libraries into your pages
|
||||
#
|
||||
# Rails includes the Prototype JavaScript framework and the Scriptaculous
|
||||
# JavaScript controls and visual effects library. If you wish to use
|
||||
# these libraries and their helpers (ActionView::Helpers::PrototypeHelper
|
||||
# and ActionView::Helpers::ScriptaculousHelper), you must do one of the
|
||||
# following:
|
||||
#
|
||||
# * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
|
||||
# section of your page (recommended): This function will return
|
||||
# references to the JavaScript files created by the +rails+ command in
|
||||
# your <tt>public/javascripts</tt> directory. Using it is recommended as
|
||||
# the browser can then cache the libraries instead of fetching all the
|
||||
# functions anew on every request.
|
||||
# * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
|
||||
# will only include the Prototype core library, which means you are able
|
||||
# to use all basic AJAX functionality. For the Scriptaculous-based
|
||||
# JavaScript helpers, like visual effects, autocompletion, drag and drop
|
||||
# and so on, you should use the method described above.
|
||||
# * Use <tt><%= define_javascript_functions %></tt>: this will copy all the
|
||||
# JavaScript support functions within a single script block. Not
|
||||
# recommended.
|
||||
#
|
||||
# For documentation on +javascript_include_tag+ see
|
||||
# ActionView::Helpers::AssetTagHelper.
|
||||
module JavaScriptHelper
|
||||
unless const_defined? :JAVASCRIPT_PATH
|
||||
JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts')
|
||||
end
|
||||
|
||||
include PrototypeHelper
|
||||
|
||||
# Returns a link that will trigger a JavaScript +function+ using the
|
||||
# onclick handler and return false after the fact.
|
||||
#
|
||||
# The +function+ argument can be omitted in favor of an +update_page+
|
||||
# block, which evaluates to a string when the template is rendered
|
||||
# (instead of making an Ajax request first).
|
||||
#
|
||||
# Examples:
|
||||
# link_to_function "Greeting", "alert('Hello world!')"
|
||||
# Produces:
|
||||
# <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
|
||||
#
|
||||
# link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()")
|
||||
# Produces:
|
||||
# <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#">
|
||||
# <img src="/images/delete.png?" alt="Delete"/>
|
||||
# </a>
|
||||
#
|
||||
# link_to_function("Show me more", nil, :id => "more_link") do |page|
|
||||
# page[:details].visual_effect :toggle_blind
|
||||
# page[:more_link].replace_html "Show me less"
|
||||
# end
|
||||
# Produces:
|
||||
# <a href="#" id="more_link" onclick="try {
|
||||
# $("details").visualEffect("toggle_blind");
|
||||
# $("more_link").update("Show me less");
|
||||
# }
|
||||
# catch (e) {
|
||||
# alert('RJS error:\n\n' + e.toString());
|
||||
# alert('$(\"details\").visualEffect(\"toggle_blind\");
|
||||
# \n$(\"more_link\").update(\"Show me less\");');
|
||||
# throw e
|
||||
# };
|
||||
# return false;">Show me more</a>
|
||||
#
|
||||
def link_to_function(name, *args, &block)
|
||||
html_options = args.extract_options!
|
||||
function = args[0] || ''
|
||||
|
||||
html_options.symbolize_keys!
|
||||
function = update_page(&block) if block_given?
|
||||
content_tag(
|
||||
"a", name,
|
||||
html_options.merge({
|
||||
:href => html_options[:href] || "#",
|
||||
:onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;"
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
# Returns a button that'll trigger a JavaScript +function+ using the
|
||||
# onclick handler.
|
||||
#
|
||||
# The +function+ argument can be omitted in favor of an +update_page+
|
||||
# block, which evaluates to a string when the template is rendered
|
||||
# (instead of making an Ajax request first).
|
||||
#
|
||||
# Examples:
|
||||
# button_to_function "Greeting", "alert('Hello world!')"
|
||||
# button_to_function "Delete", "if (confirm('Really?')) do_delete()"
|
||||
# button_to_function "Details" do |page|
|
||||
# page[:details].visual_effect :toggle_slide
|
||||
# end
|
||||
# button_to_function "Details", :class => "details_button" do |page|
|
||||
# page[:details].visual_effect :toggle_slide
|
||||
# end
|
||||
def button_to_function(name, *args, &block)
|
||||
html_options = args.extract_options!
|
||||
function = args[0] || ''
|
||||
|
||||
html_options.symbolize_keys!
|
||||
function = update_page(&block) if block_given?
|
||||
tag(:input, html_options.merge({
|
||||
:type => "button", :value => name,
|
||||
:onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
|
||||
}))
|
||||
end
|
||||
|
||||
# Includes the Action Pack JavaScript libraries inside a single <script>
|
||||
# tag. The function first includes prototype.js and then its core extensions,
|
||||
# (determined by filenames starting with "prototype").
|
||||
# Afterwards, any additional scripts will be included in undefined order.
|
||||
#
|
||||
# Note: The recommended approach is to copy the contents of
|
||||
# lib/action_view/helpers/javascripts/ into your application's
|
||||
# public/javascripts/ directory, and use +javascript_include_tag+ to
|
||||
# create remote <script> links.
|
||||
def define_javascript_functions
|
||||
javascript = "<script type=\"#{Mime::JS}\">"
|
||||
|
||||
# load prototype.js and its extensions first
|
||||
prototype_libs = Dir.glob(File.join(JAVASCRIPT_PATH, 'prototype*')).sort.reverse
|
||||
prototype_libs.each do |filename|
|
||||
javascript << "\n" << IO.read(filename)
|
||||
end
|
||||
|
||||
# load other libraries
|
||||
(Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename|
|
||||
javascript << "\n" << IO.read(filename)
|
||||
end
|
||||
javascript << '</script>'
|
||||
end
|
||||
|
||||
# Escape carrier returns and single and double quotes for JavaScript segments.
|
||||
def escape_javascript(javascript)
|
||||
(javascript || '').gsub('\\','\0\0').gsub('</','<\/').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
|
||||
end
|
||||
|
||||
# Returns a JavaScript tag with the +content+ inside. Example:
|
||||
# javascript_tag "alert('All is good')"
|
||||
#
|
||||
# Returns:
|
||||
# <script type="text/javascript">
|
||||
# //<![CDATA[
|
||||
# alert('All is good')
|
||||
# //]]>
|
||||
# </script>
|
||||
#
|
||||
# +html_options+ may be a hash of attributes for the <script> tag. Example:
|
||||
# javascript_tag "alert('All is good')", :defer => 'defer'
|
||||
# # => <script defer="defer" type="text/javascript">alert('All is good')</script>
|
||||
#
|
||||
# Instead of passing the content as an argument, you can also use a block
|
||||
# in which case, you pass your +html_options+ as the first parameter.
|
||||
# <% javascript_tag :defer => 'defer' do -%>
|
||||
# alert('All is good')
|
||||
# <% end -%>
|
||||
def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
|
||||
if block_given?
|
||||
html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
|
||||
content = capture(&block)
|
||||
else
|
||||
content = content_or_options_with_block
|
||||
end
|
||||
|
||||
javascript_tag = content_tag("script", javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
|
||||
|
||||
if block_given? && block_is_within_action_view?(block)
|
||||
concat(javascript_tag, block.binding)
|
||||
else
|
||||
javascript_tag
|
||||
end
|
||||
end
|
||||
|
||||
def javascript_cdata_section(content) #:nodoc:
|
||||
"\n//#{cdata_section("\n#{content}\n//")}\n"
|
||||
end
|
||||
|
||||
protected
|
||||
def options_for_javascript(options)
|
||||
'{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
|
||||
end
|
||||
|
||||
def array_or_string_for_javascript(option)
|
||||
js_option = if option.kind_of?(Array)
|
||||
"['#{option.join('\',\'')}']"
|
||||
elsif !option.nil?
|
||||
"'#{option}'"
|
||||
end
|
||||
js_option
|
||||
end
|
||||
|
||||
private
|
||||
def block_is_within_action_view?(block)
|
||||
eval("defined? _erbout", block.binding)
|
||||
end
|
||||
end
|
||||
|
||||
JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper
|
||||
end
|
||||
end
|
||||
963
vendor/rails/actionpack/lib/action_view/helpers/javascripts/controls.js
vendored
Normal file
963
vendor/rails/actionpack/lib/action_view/helpers/javascripts/controls.js
vendored
Normal file
|
|
@ -0,0 +1,963 @@
|
|||
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
|
||||
// Contributors:
|
||||
// Richard Livsey
|
||||
// Rahul Bhargava
|
||||
// Rob Wills
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
// Autocompleter.Base handles all the autocompletion functionality
|
||||
// that's independent of the data source for autocompletion. This
|
||||
// includes drawing the autocompletion menu, observing keyboard
|
||||
// and mouse events, and similar.
|
||||
//
|
||||
// Specific autocompleters need to provide, at the very least,
|
||||
// a getUpdatedChoices function that will be invoked every time
|
||||
// the text inside the monitored textbox changes. This method
|
||||
// should get the text for which to provide autocompletion by
|
||||
// invoking this.getToken(), NOT by directly accessing
|
||||
// this.element.value. This is to allow incremental tokenized
|
||||
// autocompletion. Specific auto-completion logic (AJAX, etc)
|
||||
// belongs in getUpdatedChoices.
|
||||
//
|
||||
// Tokenized incremental autocompletion is enabled automatically
|
||||
// when an autocompleter is instantiated with the 'tokens' option
|
||||
// in the options parameter, e.g.:
|
||||
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
|
||||
// will incrementally autocomplete with a comma as the token.
|
||||
// Additionally, ',' in the above example can be replaced with
|
||||
// a token array, e.g. { tokens: [',', '\n'] } which
|
||||
// enables autocompletion on multiple tokens. This is most
|
||||
// useful when one of the tokens is \n (a newline), as it
|
||||
// allows smart autocompletion after linebreaks.
|
||||
|
||||
if(typeof Effect == 'undefined')
|
||||
throw("controls.js requires including script.aculo.us' effects.js library");
|
||||
|
||||
var Autocompleter = { }
|
||||
Autocompleter.Base = Class.create({
|
||||
baseInitialize: function(element, update, options) {
|
||||
element = $(element)
|
||||
this.element = element;
|
||||
this.update = $(update);
|
||||
this.hasFocus = false;
|
||||
this.changed = false;
|
||||
this.active = false;
|
||||
this.index = 0;
|
||||
this.entryCount = 0;
|
||||
this.oldElementValue = this.element.value;
|
||||
|
||||
if(this.setOptions)
|
||||
this.setOptions(options);
|
||||
else
|
||||
this.options = options || { };
|
||||
|
||||
this.options.paramName = this.options.paramName || this.element.name;
|
||||
this.options.tokens = this.options.tokens || [];
|
||||
this.options.frequency = this.options.frequency || 0.4;
|
||||
this.options.minChars = this.options.minChars || 1;
|
||||
this.options.onShow = this.options.onShow ||
|
||||
function(element, update){
|
||||
if(!update.style.position || update.style.position=='absolute') {
|
||||
update.style.position = 'absolute';
|
||||
Position.clone(element, update, {
|
||||
setHeight: false,
|
||||
offsetTop: element.offsetHeight
|
||||
});
|
||||
}
|
||||
Effect.Appear(update,{duration:0.15});
|
||||
};
|
||||
this.options.onHide = this.options.onHide ||
|
||||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
||||
|
||||
if(typeof(this.options.tokens) == 'string')
|
||||
this.options.tokens = new Array(this.options.tokens);
|
||||
// Force carriage returns as token delimiters anyway
|
||||
if (!this.options.tokens.include('\n'))
|
||||
this.options.tokens.push('\n');
|
||||
|
||||
this.observer = null;
|
||||
|
||||
this.element.setAttribute('autocomplete','off');
|
||||
|
||||
Element.hide(this.update);
|
||||
|
||||
Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
|
||||
Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
|
||||
if(!this.iefix &&
|
||||
(Prototype.Browser.IE) &&
|
||||
(Element.getStyle(this.update, 'position')=='absolute')) {
|
||||
new Insertion.After(this.update,
|
||||
'<iframe id="' + this.update.id + '_iefix" '+
|
||||
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
||||
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
||||
this.iefix = $(this.update.id+'_iefix');
|
||||
}
|
||||
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
|
||||
},
|
||||
|
||||
fixIEOverlapping: function() {
|
||||
Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
|
||||
this.iefix.style.zIndex = 1;
|
||||
this.update.style.zIndex = 2;
|
||||
Element.show(this.iefix);
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this.stopIndicator();
|
||||
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
|
||||
if(this.iefix) Element.hide(this.iefix);
|
||||
},
|
||||
|
||||
startIndicator: function() {
|
||||
if(this.options.indicator) Element.show(this.options.indicator);
|
||||
},
|
||||
|
||||
stopIndicator: function() {
|
||||
if(this.options.indicator) Element.hide(this.options.indicator);
|
||||
},
|
||||
|
||||
onKeyPress: function(event) {
|
||||
if(this.active)
|
||||
switch(event.keyCode) {
|
||||
case Event.KEY_TAB:
|
||||
case Event.KEY_RETURN:
|
||||
this.selectEntry();
|
||||
Event.stop(event);
|
||||
case Event.KEY_ESC:
|
||||
this.hide();
|
||||
this.active = false;
|
||||
Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_LEFT:
|
||||
case Event.KEY_RIGHT:
|
||||
return;
|
||||
case Event.KEY_UP:
|
||||
this.markPrevious();
|
||||
this.render();
|
||||
Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_DOWN:
|
||||
this.markNext();
|
||||
this.render();
|
||||
Event.stop(event);
|
||||
return;
|
||||
}
|
||||
else
|
||||
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
|
||||
(Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
|
||||
|
||||
this.changed = true;
|
||||
this.hasFocus = true;
|
||||
|
||||
if(this.observer) clearTimeout(this.observer);
|
||||
this.observer =
|
||||
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.changed = false;
|
||||
this.hasFocus = true;
|
||||
this.getUpdatedChoices();
|
||||
},
|
||||
|
||||
onHover: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
if(this.index != element.autocompleteIndex)
|
||||
{
|
||||
this.index = element.autocompleteIndex;
|
||||
this.render();
|
||||
}
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
onClick: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
this.index = element.autocompleteIndex;
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
},
|
||||
|
||||
onBlur: function(event) {
|
||||
// needed to make click events working
|
||||
setTimeout(this.hide.bind(this), 250);
|
||||
this.hasFocus = false;
|
||||
this.active = false;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if(this.entryCount > 0) {
|
||||
for (var i = 0; i < this.entryCount; i++)
|
||||
this.index==i ?
|
||||
Element.addClassName(this.getEntry(i),"selected") :
|
||||
Element.removeClassName(this.getEntry(i),"selected");
|
||||
if(this.hasFocus) {
|
||||
this.show();
|
||||
this.active = true;
|
||||
}
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
markPrevious: function() {
|
||||
if(this.index > 0) this.index--
|
||||
else this.index = this.entryCount-1;
|
||||
this.getEntry(this.index).scrollIntoView(true);
|
||||
},
|
||||
|
||||
markNext: function() {
|
||||
if(this.index < this.entryCount-1) this.index++
|
||||
else this.index = 0;
|
||||
this.getEntry(this.index).scrollIntoView(false);
|
||||
},
|
||||
|
||||
getEntry: function(index) {
|
||||
return this.update.firstChild.childNodes[index];
|
||||
},
|
||||
|
||||
getCurrentEntry: function() {
|
||||
return this.getEntry(this.index);
|
||||
},
|
||||
|
||||
selectEntry: function() {
|
||||
this.active = false;
|
||||
this.updateElement(this.getCurrentEntry());
|
||||
},
|
||||
|
||||
updateElement: function(selectedElement) {
|
||||
if (this.options.updateElement) {
|
||||
this.options.updateElement(selectedElement);
|
||||
return;
|
||||
}
|
||||
var value = '';
|
||||
if (this.options.select) {
|
||||
var nodes = $(selectedElement).select('.' + this.options.select) || [];
|
||||
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
|
||||
} else
|
||||
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
|
||||
|
||||
var bounds = this.getTokenBounds();
|
||||
if (bounds[0] != -1) {
|
||||
var newValue = this.element.value.substr(0, bounds[0]);
|
||||
var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
|
||||
if (whitespace)
|
||||
newValue += whitespace[0];
|
||||
this.element.value = newValue + value + this.element.value.substr(bounds[1]);
|
||||
} else {
|
||||
this.element.value = value;
|
||||
}
|
||||
this.oldElementValue = this.element.value;
|
||||
this.element.focus();
|
||||
|
||||
if (this.options.afterUpdateElement)
|
||||
this.options.afterUpdateElement(this.element, selectedElement);
|
||||
},
|
||||
|
||||
updateChoices: function(choices) {
|
||||
if(!this.changed && this.hasFocus) {
|
||||
this.update.innerHTML = choices;
|
||||
Element.cleanWhitespace(this.update);
|
||||
Element.cleanWhitespace(this.update.down());
|
||||
|
||||
if(this.update.firstChild && this.update.down().childNodes) {
|
||||
this.entryCount =
|
||||
this.update.down().childNodes.length;
|
||||
for (var i = 0; i < this.entryCount; i++) {
|
||||
var entry = this.getEntry(i);
|
||||
entry.autocompleteIndex = i;
|
||||
this.addObservers(entry);
|
||||
}
|
||||
} else {
|
||||
this.entryCount = 0;
|
||||
}
|
||||
|
||||
this.stopIndicator();
|
||||
this.index = 0;
|
||||
|
||||
if(this.entryCount==1 && this.options.autoSelect) {
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addObservers: function(element) {
|
||||
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
|
||||
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
onObserverEvent: function() {
|
||||
this.changed = false;
|
||||
this.tokenBounds = null;
|
||||
if(this.getToken().length>=this.options.minChars) {
|
||||
this.getUpdatedChoices();
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
this.oldElementValue = this.element.value;
|
||||
},
|
||||
|
||||
getToken: function() {
|
||||
var bounds = this.getTokenBounds();
|
||||
return this.element.value.substring(bounds[0], bounds[1]).strip();
|
||||
},
|
||||
|
||||
getTokenBounds: function() {
|
||||
if (null != this.tokenBounds) return this.tokenBounds;
|
||||
var value = this.element.value;
|
||||
if (value.strip().empty()) return [-1, 0];
|
||||
var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
|
||||
var offset = (diff == this.oldElementValue.length ? 1 : 0);
|
||||
var prevTokenPos = -1, nextTokenPos = value.length;
|
||||
var tp;
|
||||
for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
|
||||
tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
|
||||
if (tp > prevTokenPos) prevTokenPos = tp;
|
||||
tp = value.indexOf(this.options.tokens[index], diff + offset);
|
||||
if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
|
||||
}
|
||||
return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
|
||||
}
|
||||
});
|
||||
|
||||
Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
|
||||
var boundary = Math.min(newS.length, oldS.length);
|
||||
for (var index = 0; index < boundary; ++index)
|
||||
if (newS[index] != oldS[index])
|
||||
return index;
|
||||
return boundary;
|
||||
};
|
||||
|
||||
Ajax.Autocompleter = Class.create(Autocompleter.Base, {
|
||||
initialize: function(element, update, url, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.asynchronous = true;
|
||||
this.options.onComplete = this.onComplete.bind(this);
|
||||
this.options.defaultParams = this.options.parameters || null;
|
||||
this.url = url;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
this.startIndicator();
|
||||
|
||||
var entry = encodeURIComponent(this.options.paramName) + '=' +
|
||||
encodeURIComponent(this.getToken());
|
||||
|
||||
this.options.parameters = this.options.callback ?
|
||||
this.options.callback(this.element, entry) : entry;
|
||||
|
||||
if(this.options.defaultParams)
|
||||
this.options.parameters += '&' + this.options.defaultParams;
|
||||
|
||||
new Ajax.Request(this.url, this.options);
|
||||
},
|
||||
|
||||
onComplete: function(request) {
|
||||
this.updateChoices(request.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
// The local array autocompleter. Used when you'd prefer to
|
||||
// inject an array of autocompletion options into the page, rather
|
||||
// than sending out Ajax queries, which can be quite slow sometimes.
|
||||
//
|
||||
// The constructor takes four parameters. The first two are, as usual,
|
||||
// the id of the monitored textbox, and id of the autocompletion menu.
|
||||
// The third is the array you want to autocomplete from, and the fourth
|
||||
// is the options block.
|
||||
//
|
||||
// Extra local autocompletion options:
|
||||
// - choices - How many autocompletion choices to offer
|
||||
//
|
||||
// - partialSearch - If false, the autocompleter will match entered
|
||||
// text only at the beginning of strings in the
|
||||
// autocomplete array. Defaults to true, which will
|
||||
// match text at the beginning of any *word* in the
|
||||
// strings in the autocomplete array. If you want to
|
||||
// search anywhere in the string, additionally set
|
||||
// the option fullSearch to true (default: off).
|
||||
//
|
||||
// - fullSsearch - Search anywhere in autocomplete array strings.
|
||||
//
|
||||
// - partialChars - How many characters to enter before triggering
|
||||
// a partial match (unlike minChars, which defines
|
||||
// how many characters are required to do any match
|
||||
// at all). Defaults to 2.
|
||||
//
|
||||
// - ignoreCase - Whether to ignore case when autocompleting.
|
||||
// Defaults to true.
|
||||
//
|
||||
// It's possible to pass in a custom function as the 'selector'
|
||||
// option, if you prefer to write your own autocompletion logic.
|
||||
// In that case, the other options above will not apply unless
|
||||
// you support them.
|
||||
|
||||
Autocompleter.Local = Class.create(Autocompleter.Base, {
|
||||
initialize: function(element, update, array, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.array = array;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
this.updateChoices(this.options.selector(this));
|
||||
},
|
||||
|
||||
setOptions: function(options) {
|
||||
this.options = Object.extend({
|
||||
choices: 10,
|
||||
partialSearch: true,
|
||||
partialChars: 2,
|
||||
ignoreCase: true,
|
||||
fullSearch: false,
|
||||
selector: function(instance) {
|
||||
var ret = []; // Beginning matches
|
||||
var partial = []; // Inside matches
|
||||
var entry = instance.getToken();
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < instance.options.array.length &&
|
||||
ret.length < instance.options.choices ; i++) {
|
||||
|
||||
var elem = instance.options.array[i];
|
||||
var foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase()) :
|
||||
elem.indexOf(entry);
|
||||
|
||||
while (foundPos != -1) {
|
||||
if (foundPos == 0 && elem.length != entry.length) {
|
||||
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
||||
elem.substr(entry.length) + "</li>");
|
||||
break;
|
||||
} else if (entry.length >= instance.options.partialChars &&
|
||||
instance.options.partialSearch && foundPos != -1) {
|
||||
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
|
||||
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
|
||||
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
|
||||
foundPos + entry.length) + "</li>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
|
||||
elem.indexOf(entry, foundPos + 1);
|
||||
|
||||
}
|
||||
}
|
||||
if (partial.length)
|
||||
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
|
||||
return "<ul>" + ret.join('') + "</ul>";
|
||||
}
|
||||
}, options || { });
|
||||
}
|
||||
});
|
||||
|
||||
// AJAX in-place editor and collection editor
|
||||
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
|
||||
|
||||
// Use this if you notice weird scrolling problems on some browsers,
|
||||
// the DOM might be a bit confused when this gets called so do this
|
||||
// waits 1 ms (with setTimeout) until it does the activation
|
||||
Field.scrollFreeActivate = function(field) {
|
||||
setTimeout(function() {
|
||||
Field.activate(field);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
Ajax.InPlaceEditor = Class.create({
|
||||
initialize: function(element, url, options) {
|
||||
this.url = url;
|
||||
this.element = element = $(element);
|
||||
this.prepareOptions();
|
||||
this._controls = { };
|
||||
arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
|
||||
Object.extend(this.options, options || { });
|
||||
if (!this.options.formId && this.element.id) {
|
||||
this.options.formId = this.element.id + '-inplaceeditor';
|
||||
if ($(this.options.formId))
|
||||
this.options.formId = '';
|
||||
}
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl = $(this.options.externalControl);
|
||||
if (!this.options.externalControl)
|
||||
this.options.externalControlOnly = false;
|
||||
this._originalBackground = this.element.getStyle('background-color') || 'transparent';
|
||||
this.element.title = this.options.clickToEditText;
|
||||
this._boundCancelHandler = this.handleFormCancellation.bind(this);
|
||||
this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
|
||||
this._boundFailureHandler = this.handleAJAXFailure.bind(this);
|
||||
this._boundSubmitHandler = this.handleFormSubmission.bind(this);
|
||||
this._boundWrapperHandler = this.wrapUp.bind(this);
|
||||
this.registerListeners();
|
||||
},
|
||||
checkForEscapeOrReturn: function(e) {
|
||||
if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
|
||||
if (Event.KEY_ESC == e.keyCode)
|
||||
this.handleFormCancellation(e);
|
||||
else if (Event.KEY_RETURN == e.keyCode)
|
||||
this.handleFormSubmission(e);
|
||||
},
|
||||
createControl: function(mode, handler, extraClasses) {
|
||||
var control = this.options[mode + 'Control'];
|
||||
var text = this.options[mode + 'Text'];
|
||||
if ('button' == control) {
|
||||
var btn = document.createElement('input');
|
||||
btn.type = 'submit';
|
||||
btn.value = text;
|
||||
btn.className = 'editor_' + mode + '_button';
|
||||
if ('cancel' == mode)
|
||||
btn.onclick = this._boundCancelHandler;
|
||||
this._form.appendChild(btn);
|
||||
this._controls[mode] = btn;
|
||||
} else if ('link' == control) {
|
||||
var link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.appendChild(document.createTextNode(text));
|
||||
link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
|
||||
link.className = 'editor_' + mode + '_link';
|
||||
if (extraClasses)
|
||||
link.className += ' ' + extraClasses;
|
||||
this._form.appendChild(link);
|
||||
this._controls[mode] = link;
|
||||
}
|
||||
},
|
||||
createEditField: function() {
|
||||
var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
|
||||
var fld;
|
||||
if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
|
||||
fld = document.createElement('input');
|
||||
fld.type = 'text';
|
||||
var size = this.options.size || this.options.cols || 0;
|
||||
if (0 < size) fld.size = size;
|
||||
} else {
|
||||
fld = document.createElement('textarea');
|
||||
fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
|
||||
fld.cols = this.options.cols || 40;
|
||||
}
|
||||
fld.name = this.options.paramName;
|
||||
fld.value = text; // No HTML breaks conversion anymore
|
||||
fld.className = 'editor_field';
|
||||
if (this.options.submitOnBlur)
|
||||
fld.onblur = this._boundSubmitHandler;
|
||||
this._controls.editor = fld;
|
||||
if (this.options.loadTextURL)
|
||||
this.loadExternalText();
|
||||
this._form.appendChild(this._controls.editor);
|
||||
},
|
||||
createForm: function() {
|
||||
var ipe = this;
|
||||
function addText(mode, condition) {
|
||||
var text = ipe.options['text' + mode + 'Controls'];
|
||||
if (!text || condition === false) return;
|
||||
ipe._form.appendChild(document.createTextNode(text));
|
||||
};
|
||||
this._form = $(document.createElement('form'));
|
||||
this._form.id = this.options.formId;
|
||||
this._form.addClassName(this.options.formClassName);
|
||||
this._form.onsubmit = this._boundSubmitHandler;
|
||||
this.createEditField();
|
||||
if ('textarea' == this._controls.editor.tagName.toLowerCase())
|
||||
this._form.appendChild(document.createElement('br'));
|
||||
if (this.options.onFormCustomization)
|
||||
this.options.onFormCustomization(this, this._form);
|
||||
addText('Before', this.options.okControl || this.options.cancelControl);
|
||||
this.createControl('ok', this._boundSubmitHandler);
|
||||
addText('Between', this.options.okControl && this.options.cancelControl);
|
||||
this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
|
||||
addText('After', this.options.okControl || this.options.cancelControl);
|
||||
},
|
||||
destroy: function() {
|
||||
if (this._oldInnerHTML)
|
||||
this.element.innerHTML = this._oldInnerHTML;
|
||||
this.leaveEditMode();
|
||||
this.unregisterListeners();
|
||||
},
|
||||
enterEditMode: function(e) {
|
||||
if (this._saving || this._editing) return;
|
||||
this._editing = true;
|
||||
this.triggerCallback('onEnterEditMode');
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.hide();
|
||||
this.element.hide();
|
||||
this.createForm();
|
||||
this.element.parentNode.insertBefore(this._form, this.element);
|
||||
if (!this.options.loadTextURL)
|
||||
this.postProcessEditField();
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
enterHover: function(e) {
|
||||
if (this.options.hoverClassName)
|
||||
this.element.addClassName(this.options.hoverClassName);
|
||||
if (this._saving) return;
|
||||
this.triggerCallback('onEnterHover');
|
||||
},
|
||||
getText: function() {
|
||||
return this.element.innerHTML;
|
||||
},
|
||||
handleAJAXFailure: function(transport) {
|
||||
this.triggerCallback('onFailure', transport);
|
||||
if (this._oldInnerHTML) {
|
||||
this.element.innerHTML = this._oldInnerHTML;
|
||||
this._oldInnerHTML = null;
|
||||
}
|
||||
},
|
||||
handleFormCancellation: function(e) {
|
||||
this.wrapUp();
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
handleFormSubmission: function(e) {
|
||||
var form = this._form;
|
||||
var value = $F(this._controls.editor);
|
||||
this.prepareSubmission();
|
||||
var params = this.options.callback(form, value) || '';
|
||||
if (Object.isString(params))
|
||||
params = params.toQueryParams();
|
||||
params.editorId = this.element.id;
|
||||
if (this.options.htmlResponse) {
|
||||
var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: params,
|
||||
onComplete: this._boundWrapperHandler,
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Updater({ success: this.element }, this.url, options);
|
||||
} else {
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: params,
|
||||
onComplete: this._boundWrapperHandler,
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Request(this.url, options);
|
||||
}
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
leaveEditMode: function() {
|
||||
this.element.removeClassName(this.options.savingClassName);
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.element.style.backgroundColor = this._originalBackground;
|
||||
this.element.show();
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.show();
|
||||
this._saving = false;
|
||||
this._editing = false;
|
||||
this._oldInnerHTML = null;
|
||||
this.triggerCallback('onLeaveEditMode');
|
||||
},
|
||||
leaveHover: function(e) {
|
||||
if (this.options.hoverClassName)
|
||||
this.element.removeClassName(this.options.hoverClassName);
|
||||
if (this._saving) return;
|
||||
this.triggerCallback('onLeaveHover');
|
||||
},
|
||||
loadExternalText: function() {
|
||||
this._form.addClassName(this.options.loadingClassName);
|
||||
this._controls.editor.disabled = true;
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
this._form.removeClassName(this.options.loadingClassName);
|
||||
var text = transport.responseText;
|
||||
if (this.options.stripLoadedTextTags)
|
||||
text = text.stripTags();
|
||||
this._controls.editor.value = text;
|
||||
this._controls.editor.disabled = false;
|
||||
this.postProcessEditField();
|
||||
}.bind(this),
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Request(this.options.loadTextURL, options);
|
||||
},
|
||||
postProcessEditField: function() {
|
||||
var fpc = this.options.fieldPostCreation;
|
||||
if (fpc)
|
||||
$(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
|
||||
},
|
||||
prepareOptions: function() {
|
||||
this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
|
||||
Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
|
||||
[this._extraDefaultOptions].flatten().compact().each(function(defs) {
|
||||
Object.extend(this.options, defs);
|
||||
}.bind(this));
|
||||
},
|
||||
prepareSubmission: function() {
|
||||
this._saving = true;
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.showSaving();
|
||||
},
|
||||
registerListeners: function() {
|
||||
this._listeners = { };
|
||||
var listener;
|
||||
$H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
|
||||
listener = this[pair.value].bind(this);
|
||||
this._listeners[pair.key] = listener;
|
||||
if (!this.options.externalControlOnly)
|
||||
this.element.observe(pair.key, listener);
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.observe(pair.key, listener);
|
||||
}.bind(this));
|
||||
},
|
||||
removeForm: function() {
|
||||
if (!this._form) return;
|
||||
this._form.remove();
|
||||
this._form = null;
|
||||
this._controls = { };
|
||||
},
|
||||
showSaving: function() {
|
||||
this._oldInnerHTML = this.element.innerHTML;
|
||||
this.element.innerHTML = this.options.savingText;
|
||||
this.element.addClassName(this.options.savingClassName);
|
||||
this.element.style.backgroundColor = this._originalBackground;
|
||||
this.element.show();
|
||||
},
|
||||
triggerCallback: function(cbName, arg) {
|
||||
if ('function' == typeof this.options[cbName]) {
|
||||
this.options[cbName](this, arg);
|
||||
}
|
||||
},
|
||||
unregisterListeners: function() {
|
||||
$H(this._listeners).each(function(pair) {
|
||||
if (!this.options.externalControlOnly)
|
||||
this.element.stopObserving(pair.key, pair.value);
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.stopObserving(pair.key, pair.value);
|
||||
}.bind(this));
|
||||
},
|
||||
wrapUp: function(transport) {
|
||||
this.leaveEditMode();
|
||||
// Can't use triggerCallback due to backward compatibility: requires
|
||||
// binding + direct element
|
||||
this._boundComplete(transport, this.element);
|
||||
}
|
||||
});
|
||||
|
||||
Object.extend(Ajax.InPlaceEditor.prototype, {
|
||||
dispose: Ajax.InPlaceEditor.prototype.destroy
|
||||
});
|
||||
|
||||
Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
|
||||
initialize: function($super, element, url, options) {
|
||||
this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
|
||||
$super(element, url, options);
|
||||
},
|
||||
|
||||
createEditField: function() {
|
||||
var list = document.createElement('select');
|
||||
list.name = this.options.paramName;
|
||||
list.size = 1;
|
||||
this._controls.editor = list;
|
||||
this._collection = this.options.collection || [];
|
||||
if (this.options.loadCollectionURL)
|
||||
this.loadCollection();
|
||||
else
|
||||
this.checkForExternalText();
|
||||
this._form.appendChild(this._controls.editor);
|
||||
},
|
||||
|
||||
loadCollection: function() {
|
||||
this._form.addClassName(this.options.loadingClassName);
|
||||
this.showLoadingText(this.options.loadingCollectionText);
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
var js = transport.responseText.strip();
|
||||
if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
|
||||
throw 'Server returned an invalid collection representation.';
|
||||
this._collection = eval(js);
|
||||
this.checkForExternalText();
|
||||
}.bind(this),
|
||||
onFailure: this.onFailure
|
||||
});
|
||||
new Ajax.Request(this.options.loadCollectionURL, options);
|
||||
},
|
||||
|
||||
showLoadingText: function(text) {
|
||||
this._controls.editor.disabled = true;
|
||||
var tempOption = this._controls.editor.firstChild;
|
||||
if (!tempOption) {
|
||||
tempOption = document.createElement('option');
|
||||
tempOption.value = '';
|
||||
this._controls.editor.appendChild(tempOption);
|
||||
tempOption.selected = true;
|
||||
}
|
||||
tempOption.update((text || '').stripScripts().stripTags());
|
||||
},
|
||||
|
||||
checkForExternalText: function() {
|
||||
this._text = this.getText();
|
||||
if (this.options.loadTextURL)
|
||||
this.loadExternalText();
|
||||
else
|
||||
this.buildOptionList();
|
||||
},
|
||||
|
||||
loadExternalText: function() {
|
||||
this.showLoadingText(this.options.loadingText);
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
this._text = transport.responseText.strip();
|
||||
this.buildOptionList();
|
||||
}.bind(this),
|
||||
onFailure: this.onFailure
|
||||
});
|
||||
new Ajax.Request(this.options.loadTextURL, options);
|
||||
},
|
||||
|
||||
buildOptionList: function() {
|
||||
this._form.removeClassName(this.options.loadingClassName);
|
||||
this._collection = this._collection.map(function(entry) {
|
||||
return 2 === entry.length ? entry : [entry, entry].flatten();
|
||||
});
|
||||
var marker = ('value' in this.options) ? this.options.value : this._text;
|
||||
var textFound = this._collection.any(function(entry) {
|
||||
return entry[0] == marker;
|
||||
}.bind(this));
|
||||
this._controls.editor.update('');
|
||||
var option;
|
||||
this._collection.each(function(entry, index) {
|
||||
option = document.createElement('option');
|
||||
option.value = entry[0];
|
||||
option.selected = textFound ? entry[0] == marker : 0 == index;
|
||||
option.appendChild(document.createTextNode(entry[1]));
|
||||
this._controls.editor.appendChild(option);
|
||||
}.bind(this));
|
||||
this._controls.editor.disabled = false;
|
||||
Field.scrollFreeActivate(this._controls.editor);
|
||||
}
|
||||
});
|
||||
|
||||
//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
|
||||
//**** This only exists for a while, in order to let ****
|
||||
//**** users adapt to the new API. Read up on the new ****
|
||||
//**** API and convert your code to it ASAP! ****
|
||||
|
||||
Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
|
||||
if (!options) return;
|
||||
function fallback(name, expr) {
|
||||
if (name in options || expr === undefined) return;
|
||||
options[name] = expr;
|
||||
};
|
||||
fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
|
||||
options.cancelLink == options.cancelButton == false ? false : undefined)));
|
||||
fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
|
||||
options.okLink == options.okButton == false ? false : undefined)));
|
||||
fallback('highlightColor', options.highlightcolor);
|
||||
fallback('highlightEndColor', options.highlightendcolor);
|
||||
};
|
||||
|
||||
Object.extend(Ajax.InPlaceEditor, {
|
||||
DefaultOptions: {
|
||||
ajaxOptions: { },
|
||||
autoRows: 3, // Use when multi-line w/ rows == 1
|
||||
cancelControl: 'link', // 'link'|'button'|false
|
||||
cancelText: 'cancel',
|
||||
clickToEditText: 'Click to edit',
|
||||
externalControl: null, // id|elt
|
||||
externalControlOnly: false,
|
||||
fieldPostCreation: 'activate', // 'activate'|'focus'|false
|
||||
formClassName: 'inplaceeditor-form',
|
||||
formId: null, // id|elt
|
||||
highlightColor: '#ffff99',
|
||||
highlightEndColor: '#ffffff',
|
||||
hoverClassName: '',
|
||||
htmlResponse: true,
|
||||
loadingClassName: 'inplaceeditor-loading',
|
||||
loadingText: 'Loading...',
|
||||
okControl: 'button', // 'link'|'button'|false
|
||||
okText: 'ok',
|
||||
paramName: 'value',
|
||||
rows: 1, // If 1 and multi-line, uses autoRows
|
||||
savingClassName: 'inplaceeditor-saving',
|
||||
savingText: 'Saving...',
|
||||
size: 0,
|
||||
stripLoadedTextTags: false,
|
||||
submitOnBlur: false,
|
||||
textAfterControls: '',
|
||||
textBeforeControls: '',
|
||||
textBetweenControls: ''
|
||||
},
|
||||
DefaultCallbacks: {
|
||||
callback: function(form) {
|
||||
return Form.serialize(form);
|
||||
},
|
||||
onComplete: function(transport, element) {
|
||||
// For backward compatibility, this one is bound to the IPE, and passes
|
||||
// the element directly. It was too often customized, so we don't break it.
|
||||
new Effect.Highlight(element, {
|
||||
startcolor: this.options.highlightColor, keepBackgroundImage: true });
|
||||
},
|
||||
onEnterEditMode: null,
|
||||
onEnterHover: function(ipe) {
|
||||
ipe.element.style.backgroundColor = ipe.options.highlightColor;
|
||||
if (ipe._effect)
|
||||
ipe._effect.cancel();
|
||||
},
|
||||
onFailure: function(transport, ipe) {
|
||||
alert('Error communication with the server: ' + transport.responseText.stripTags());
|
||||
},
|
||||
onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
|
||||
onLeaveEditMode: null,
|
||||
onLeaveHover: function(ipe) {
|
||||
ipe._effect = new Effect.Highlight(ipe.element, {
|
||||
startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
|
||||
restorecolor: ipe._originalBackground, keepBackgroundImage: true
|
||||
});
|
||||
}
|
||||
},
|
||||
Listeners: {
|
||||
click: 'enterEditMode',
|
||||
keydown: 'checkForEscapeOrReturn',
|
||||
mouseover: 'enterHover',
|
||||
mouseout: 'leaveHover'
|
||||
}
|
||||
});
|
||||
|
||||
Ajax.InPlaceCollectionEditor.DefaultOptions = {
|
||||
loadingCollectionText: 'Loading options...'
|
||||
};
|
||||
|
||||
// Delayed observer, like Form.Element.Observer,
|
||||
// but waits for delay after last key input
|
||||
// Ideal for live-search fields
|
||||
|
||||
Form.Element.DelayedObserver = Class.create({
|
||||
initialize: function(element, delay, callback) {
|
||||
this.delay = delay || 0.5;
|
||||
this.element = $(element);
|
||||
this.callback = callback;
|
||||
this.timer = null;
|
||||
this.lastValue = $F(this.element);
|
||||
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
|
||||
},
|
||||
delayedListener: function(event) {
|
||||
if(this.lastValue == $F(this.element)) return;
|
||||
if(this.timer) clearTimeout(this.timer);
|
||||
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
|
||||
this.lastValue = $F(this.element);
|
||||
},
|
||||
onTimerEvent: function() {
|
||||
this.timer = null;
|
||||
this.callback(this.element, $F(this.element));
|
||||
}
|
||||
});
|
||||
972
vendor/rails/actionpack/lib/action_view/helpers/javascripts/dragdrop.js
vendored
Normal file
972
vendor/rails/actionpack/lib/action_view/helpers/javascripts/dragdrop.js
vendored
Normal file
|
|
@ -0,0 +1,972 @@
|
|||
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
if(Object.isUndefined(Effect))
|
||||
throw("dragdrop.js requires including script.aculo.us' effects.js library");
|
||||
|
||||
var Droppables = {
|
||||
drops: [],
|
||||
|
||||
remove: function(element) {
|
||||
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
|
||||
},
|
||||
|
||||
add: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
greedy: true,
|
||||
hoverclass: null,
|
||||
tree: false
|
||||
}, arguments[1] || { });
|
||||
|
||||
// cache containers
|
||||
if(options.containment) {
|
||||
options._containers = [];
|
||||
var containment = options.containment;
|
||||
if(Object.isArray(containment)) {
|
||||
containment.each( function(c) { options._containers.push($(c)) });
|
||||
} else {
|
||||
options._containers.push($(containment));
|
||||
}
|
||||
}
|
||||
|
||||
if(options.accept) options.accept = [options.accept].flatten();
|
||||
|
||||
Element.makePositioned(element); // fix IE
|
||||
options.element = element;
|
||||
|
||||
this.drops.push(options);
|
||||
},
|
||||
|
||||
findDeepestChild: function(drops) {
|
||||
deepest = drops[0];
|
||||
|
||||
for (i = 1; i < drops.length; ++i)
|
||||
if (Element.isParent(drops[i].element, deepest.element))
|
||||
deepest = drops[i];
|
||||
|
||||
return deepest;
|
||||
},
|
||||
|
||||
isContained: function(element, drop) {
|
||||
var containmentNode;
|
||||
if(drop.tree) {
|
||||
containmentNode = element.treeNode;
|
||||
} else {
|
||||
containmentNode = element.parentNode;
|
||||
}
|
||||
return drop._containers.detect(function(c) { return containmentNode == c });
|
||||
},
|
||||
|
||||
isAffected: function(point, element, drop) {
|
||||
return (
|
||||
(drop.element!=element) &&
|
||||
((!drop._containers) ||
|
||||
this.isContained(element, drop)) &&
|
||||
((!drop.accept) ||
|
||||
(Element.classNames(element).detect(
|
||||
function(v) { return drop.accept.include(v) } ) )) &&
|
||||
Position.within(drop.element, point[0], point[1]) );
|
||||
},
|
||||
|
||||
deactivate: function(drop) {
|
||||
if(drop.hoverclass)
|
||||
Element.removeClassName(drop.element, drop.hoverclass);
|
||||
this.last_active = null;
|
||||
},
|
||||
|
||||
activate: function(drop) {
|
||||
if(drop.hoverclass)
|
||||
Element.addClassName(drop.element, drop.hoverclass);
|
||||
this.last_active = drop;
|
||||
},
|
||||
|
||||
show: function(point, element) {
|
||||
if(!this.drops.length) return;
|
||||
var drop, affected = [];
|
||||
|
||||
this.drops.each( function(drop) {
|
||||
if(Droppables.isAffected(point, element, drop))
|
||||
affected.push(drop);
|
||||
});
|
||||
|
||||
if(affected.length>0)
|
||||
drop = Droppables.findDeepestChild(affected);
|
||||
|
||||
if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
|
||||
if (drop) {
|
||||
Position.within(drop.element, point[0], point[1]);
|
||||
if(drop.onHover)
|
||||
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
|
||||
|
||||
if (drop != this.last_active) Droppables.activate(drop);
|
||||
}
|
||||
},
|
||||
|
||||
fire: function(event, element) {
|
||||
if(!this.last_active) return;
|
||||
Position.prepare();
|
||||
|
||||
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
|
||||
if (this.last_active.onDrop) {
|
||||
this.last_active.onDrop(element, this.last_active.element, event);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
if(this.last_active)
|
||||
this.deactivate(this.last_active);
|
||||
}
|
||||
}
|
||||
|
||||
var Draggables = {
|
||||
drags: [],
|
||||
observers: [],
|
||||
|
||||
register: function(draggable) {
|
||||
if(this.drags.length == 0) {
|
||||
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
||||
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
|
||||
this.eventKeypress = this.keyPress.bindAsEventListener(this);
|
||||
|
||||
Event.observe(document, "mouseup", this.eventMouseUp);
|
||||
Event.observe(document, "mousemove", this.eventMouseMove);
|
||||
Event.observe(document, "keypress", this.eventKeypress);
|
||||
}
|
||||
this.drags.push(draggable);
|
||||
},
|
||||
|
||||
unregister: function(draggable) {
|
||||
this.drags = this.drags.reject(function(d) { return d==draggable });
|
||||
if(this.drags.length == 0) {
|
||||
Event.stopObserving(document, "mouseup", this.eventMouseUp);
|
||||
Event.stopObserving(document, "mousemove", this.eventMouseMove);
|
||||
Event.stopObserving(document, "keypress", this.eventKeypress);
|
||||
}
|
||||
},
|
||||
|
||||
activate: function(draggable) {
|
||||
if(draggable.options.delay) {
|
||||
this._timeout = setTimeout(function() {
|
||||
Draggables._timeout = null;
|
||||
window.focus();
|
||||
Draggables.activeDraggable = draggable;
|
||||
}.bind(this), draggable.options.delay);
|
||||
} else {
|
||||
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
|
||||
this.activeDraggable = draggable;
|
||||
}
|
||||
},
|
||||
|
||||
deactivate: function() {
|
||||
this.activeDraggable = null;
|
||||
},
|
||||
|
||||
updateDrag: function(event) {
|
||||
if(!this.activeDraggable) return;
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
// Mozilla-based browsers fire successive mousemove events with
|
||||
// the same coordinates, prevent needless redrawing (moz bug?)
|
||||
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
|
||||
this._lastPointer = pointer;
|
||||
|
||||
this.activeDraggable.updateDrag(event, pointer);
|
||||
},
|
||||
|
||||
endDrag: function(event) {
|
||||
if(this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
}
|
||||
if(!this.activeDraggable) return;
|
||||
this._lastPointer = null;
|
||||
this.activeDraggable.endDrag(event);
|
||||
this.activeDraggable = null;
|
||||
},
|
||||
|
||||
keyPress: function(event) {
|
||||
if(this.activeDraggable)
|
||||
this.activeDraggable.keyPress(event);
|
||||
},
|
||||
|
||||
addObserver: function(observer) {
|
||||
this.observers.push(observer);
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
|
||||
removeObserver: function(element) { // element instead of observer fixes mem leaks
|
||||
this.observers = this.observers.reject( function(o) { return o.element==element });
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
|
||||
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
|
||||
if(this[eventName+'Count'] > 0)
|
||||
this.observers.each( function(o) {
|
||||
if(o[eventName]) o[eventName](eventName, draggable, event);
|
||||
});
|
||||
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
|
||||
},
|
||||
|
||||
_cacheObserverCallbacks: function() {
|
||||
['onStart','onEnd','onDrag'].each( function(eventName) {
|
||||
Draggables[eventName+'Count'] = Draggables.observers.select(
|
||||
function(o) { return o[eventName]; }
|
||||
).length;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Draggable = Class.create({
|
||||
initialize: function(element) {
|
||||
var defaults = {
|
||||
handle: false,
|
||||
reverteffect: function(element, top_offset, left_offset) {
|
||||
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
|
||||
new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
|
||||
queue: {scope:'_draggable', position:'end'}
|
||||
});
|
||||
},
|
||||
endeffect: function(element) {
|
||||
var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
|
||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
|
||||
queue: {scope:'_draggable', position:'end'},
|
||||
afterFinish: function(){
|
||||
Draggable._dragging[element] = false
|
||||
}
|
||||
});
|
||||
},
|
||||
zindex: 1000,
|
||||
revert: false,
|
||||
quiet: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
||||
delay: 0
|
||||
};
|
||||
|
||||
if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
|
||||
Object.extend(defaults, {
|
||||
starteffect: function(element) {
|
||||
element._opacity = Element.getOpacity(element);
|
||||
Draggable._dragging[element] = true;
|
||||
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
|
||||
}
|
||||
});
|
||||
|
||||
var options = Object.extend(defaults, arguments[1] || { });
|
||||
|
||||
this.element = $(element);
|
||||
|
||||
if(options.handle && Object.isString(options.handle))
|
||||
this.handle = this.element.down('.'+options.handle, 0);
|
||||
|
||||
if(!this.handle) this.handle = $(options.handle);
|
||||
if(!this.handle) this.handle = this.element;
|
||||
|
||||
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
|
||||
options.scroll = $(options.scroll);
|
||||
this._isScrollChild = Element.childOf(this.element, options.scroll);
|
||||
}
|
||||
|
||||
Element.makePositioned(this.element); // fix IE
|
||||
|
||||
this.options = options;
|
||||
this.dragging = false;
|
||||
|
||||
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
|
||||
Event.observe(this.handle, "mousedown", this.eventMouseDown);
|
||||
|
||||
Draggables.register(this);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
|
||||
Draggables.unregister(this);
|
||||
},
|
||||
|
||||
currentDelta: function() {
|
||||
return([
|
||||
parseInt(Element.getStyle(this.element,'left') || '0'),
|
||||
parseInt(Element.getStyle(this.element,'top') || '0')]);
|
||||
},
|
||||
|
||||
initDrag: function(event) {
|
||||
if(!Object.isUndefined(Draggable._dragging[this.element]) &&
|
||||
Draggable._dragging[this.element]) return;
|
||||
if(Event.isLeftClick(event)) {
|
||||
// abort on form elements, fixes a Firefox issue
|
||||
var src = Event.element(event);
|
||||
if((tag_name = src.tagName.toUpperCase()) && (
|
||||
tag_name=='INPUT' ||
|
||||
tag_name=='SELECT' ||
|
||||
tag_name=='OPTION' ||
|
||||
tag_name=='BUTTON' ||
|
||||
tag_name=='TEXTAREA')) return;
|
||||
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
|
||||
|
||||
Draggables.activate(this);
|
||||
Event.stop(event);
|
||||
}
|
||||
},
|
||||
|
||||
startDrag: function(event) {
|
||||
this.dragging = true;
|
||||
if(!this.delta)
|
||||
this.delta = this.currentDelta();
|
||||
|
||||
if(this.options.zindex) {
|
||||
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
|
||||
this.element.style.zIndex = this.options.zindex;
|
||||
}
|
||||
|
||||
if(this.options.ghosting) {
|
||||
this._clone = this.element.cloneNode(true);
|
||||
this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
|
||||
if (!this.element._originallyAbsolute)
|
||||
Position.absolutize(this.element);
|
||||
this.element.parentNode.insertBefore(this._clone, this.element);
|
||||
}
|
||||
|
||||
if(this.options.scroll) {
|
||||
if (this.options.scroll == window) {
|
||||
var where = this._getWindowScroll(this.options.scroll);
|
||||
this.originalScrollLeft = where.left;
|
||||
this.originalScrollTop = where.top;
|
||||
} else {
|
||||
this.originalScrollLeft = this.options.scroll.scrollLeft;
|
||||
this.originalScrollTop = this.options.scroll.scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
Draggables.notify('onStart', this, event);
|
||||
|
||||
if(this.options.starteffect) this.options.starteffect(this.element);
|
||||
},
|
||||
|
||||
updateDrag: function(event, pointer) {
|
||||
if(!this.dragging) this.startDrag(event);
|
||||
|
||||
if(!this.options.quiet){
|
||||
Position.prepare();
|
||||
Droppables.show(pointer, this.element);
|
||||
}
|
||||
|
||||
Draggables.notify('onDrag', this, event);
|
||||
|
||||
this.draw(pointer);
|
||||
if(this.options.change) this.options.change(this);
|
||||
|
||||
if(this.options.scroll) {
|
||||
this.stopScrolling();
|
||||
|
||||
var p;
|
||||
if (this.options.scroll == window) {
|
||||
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
|
||||
} else {
|
||||
p = Position.page(this.options.scroll);
|
||||
p[0] += this.options.scroll.scrollLeft + Position.deltaX;
|
||||
p[1] += this.options.scroll.scrollTop + Position.deltaY;
|
||||
p.push(p[0]+this.options.scroll.offsetWidth);
|
||||
p.push(p[1]+this.options.scroll.offsetHeight);
|
||||
}
|
||||
var speed = [0,0];
|
||||
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
|
||||
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
|
||||
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
|
||||
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
|
||||
this.startScrolling(speed);
|
||||
}
|
||||
|
||||
// fix AppleWebKit rendering
|
||||
if(Prototype.Browser.WebKit) window.scrollBy(0,0);
|
||||
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
finishDrag: function(event, success) {
|
||||
this.dragging = false;
|
||||
|
||||
if(this.options.quiet){
|
||||
Position.prepare();
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
Droppables.show(pointer, this.element);
|
||||
}
|
||||
|
||||
if(this.options.ghosting) {
|
||||
if (!this.element._originallyAbsolute)
|
||||
Position.relativize(this.element);
|
||||
delete this.element._originallyAbsolute;
|
||||
Element.remove(this._clone);
|
||||
this._clone = null;
|
||||
}
|
||||
|
||||
var dropped = false;
|
||||
if(success) {
|
||||
dropped = Droppables.fire(event, this.element);
|
||||
if (!dropped) dropped = false;
|
||||
}
|
||||
if(dropped && this.options.onDropped) this.options.onDropped(this.element);
|
||||
Draggables.notify('onEnd', this, event);
|
||||
|
||||
var revert = this.options.revert;
|
||||
if(revert && Object.isFunction(revert)) revert = revert(this.element);
|
||||
|
||||
var d = this.currentDelta();
|
||||
if(revert && this.options.reverteffect) {
|
||||
if (dropped == 0 || revert != 'failure')
|
||||
this.options.reverteffect(this.element,
|
||||
d[1]-this.delta[1], d[0]-this.delta[0]);
|
||||
} else {
|
||||
this.delta = d;
|
||||
}
|
||||
|
||||
if(this.options.zindex)
|
||||
this.element.style.zIndex = this.originalZ;
|
||||
|
||||
if(this.options.endeffect)
|
||||
this.options.endeffect(this.element);
|
||||
|
||||
Draggables.deactivate(this);
|
||||
Droppables.reset();
|
||||
},
|
||||
|
||||
keyPress: function(event) {
|
||||
if(event.keyCode!=Event.KEY_ESC) return;
|
||||
this.finishDrag(event, false);
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
endDrag: function(event) {
|
||||
if(!this.dragging) return;
|
||||
this.stopScrolling();
|
||||
this.finishDrag(event, true);
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
draw: function(point) {
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
if(this.options.ghosting) {
|
||||
var r = Position.realOffset(this.element);
|
||||
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
|
||||
}
|
||||
|
||||
var d = this.currentDelta();
|
||||
pos[0] -= d[0]; pos[1] -= d[1];
|
||||
|
||||
if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
|
||||
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
|
||||
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
|
||||
}
|
||||
|
||||
var p = [0,1].map(function(i){
|
||||
return (point[i]-pos[i]-this.offset[i])
|
||||
}.bind(this));
|
||||
|
||||
if(this.options.snap) {
|
||||
if(Object.isFunction(this.options.snap)) {
|
||||
p = this.options.snap(p[0],p[1],this);
|
||||
} else {
|
||||
if(Object.isArray(this.options.snap)) {
|
||||
p = p.map( function(v, i) {
|
||||
return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
|
||||
} else {
|
||||
p = p.map( function(v) {
|
||||
return (v/this.options.snap).round()*this.options.snap }.bind(this))
|
||||
}
|
||||
}}
|
||||
|
||||
var style = this.element.style;
|
||||
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
|
||||
style.left = p[0] + "px";
|
||||
if((!this.options.constraint) || (this.options.constraint=='vertical'))
|
||||
style.top = p[1] + "px";
|
||||
|
||||
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
|
||||
},
|
||||
|
||||
stopScrolling: function() {
|
||||
if(this.scrollInterval) {
|
||||
clearInterval(this.scrollInterval);
|
||||
this.scrollInterval = null;
|
||||
Draggables._lastScrollPointer = null;
|
||||
}
|
||||
},
|
||||
|
||||
startScrolling: function(speed) {
|
||||
if(!(speed[0] || speed[1])) return;
|
||||
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
|
||||
this.lastScrolled = new Date();
|
||||
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
|
||||
},
|
||||
|
||||
scroll: function() {
|
||||
var current = new Date();
|
||||
var delta = current - this.lastScrolled;
|
||||
this.lastScrolled = current;
|
||||
if(this.options.scroll == window) {
|
||||
with (this._getWindowScroll(this.options.scroll)) {
|
||||
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
|
||||
var d = delta / 1000;
|
||||
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
|
||||
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
|
||||
}
|
||||
|
||||
Position.prepare();
|
||||
Droppables.show(Draggables._lastPointer, this.element);
|
||||
Draggables.notify('onDrag', this);
|
||||
if (this._isScrollChild) {
|
||||
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
|
||||
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
|
||||
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
|
||||
if (Draggables._lastScrollPointer[0] < 0)
|
||||
Draggables._lastScrollPointer[0] = 0;
|
||||
if (Draggables._lastScrollPointer[1] < 0)
|
||||
Draggables._lastScrollPointer[1] = 0;
|
||||
this.draw(Draggables._lastScrollPointer);
|
||||
}
|
||||
|
||||
if(this.options.change) this.options.change(this);
|
||||
},
|
||||
|
||||
_getWindowScroll: function(w) {
|
||||
var T, L, W, H;
|
||||
with (w.document) {
|
||||
if (w.document.documentElement && documentElement.scrollTop) {
|
||||
T = documentElement.scrollTop;
|
||||
L = documentElement.scrollLeft;
|
||||
} else if (w.document.body) {
|
||||
T = body.scrollTop;
|
||||
L = body.scrollLeft;
|
||||
}
|
||||
if (w.innerWidth) {
|
||||
W = w.innerWidth;
|
||||
H = w.innerHeight;
|
||||
} else if (w.document.documentElement && documentElement.clientWidth) {
|
||||
W = documentElement.clientWidth;
|
||||
H = documentElement.clientHeight;
|
||||
} else {
|
||||
W = body.offsetWidth;
|
||||
H = body.offsetHeight
|
||||
}
|
||||
}
|
||||
return { top: T, left: L, width: W, height: H };
|
||||
}
|
||||
});
|
||||
|
||||
Draggable._dragging = { };
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var SortableObserver = Class.create({
|
||||
initialize: function(element, observer) {
|
||||
this.element = $(element);
|
||||
this.observer = observer;
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
|
||||
onStart: function() {
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
|
||||
onEnd: function() {
|
||||
Sortable.unmark();
|
||||
if(this.lastValue != Sortable.serialize(this.element))
|
||||
this.observer(this.element)
|
||||
}
|
||||
});
|
||||
|
||||
var Sortable = {
|
||||
SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
|
||||
|
||||
sortables: { },
|
||||
|
||||
_findRootElement: function(element) {
|
||||
while (element.tagName.toUpperCase() != "BODY") {
|
||||
if(element.id && Sortable.sortables[element.id]) return element;
|
||||
element = element.parentNode;
|
||||
}
|
||||
},
|
||||
|
||||
options: function(element) {
|
||||
element = Sortable._findRootElement($(element));
|
||||
if(!element) return;
|
||||
return Sortable.sortables[element.id];
|
||||
},
|
||||
|
||||
destroy: function(element){
|
||||
var s = Sortable.options(element);
|
||||
|
||||
if(s) {
|
||||
Draggables.removeObserver(s.element);
|
||||
s.droppables.each(function(d){ Droppables.remove(d) });
|
||||
s.draggables.invoke('destroy');
|
||||
|
||||
delete Sortable.sortables[s.element.id];
|
||||
}
|
||||
},
|
||||
|
||||
create: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
element: element,
|
||||
tag: 'li', // assumes li children, override with tag: 'tagname'
|
||||
dropOnEmpty: false,
|
||||
tree: false,
|
||||
treeTag: 'ul',
|
||||
overlap: 'vertical', // one of 'vertical', 'horizontal'
|
||||
constraint: 'vertical', // one of 'vertical', 'horizontal', false
|
||||
containment: element, // also takes array of elements (or id's); or false
|
||||
handle: false, // or a CSS class
|
||||
only: false,
|
||||
delay: 0,
|
||||
hoverclass: null,
|
||||
ghosting: false,
|
||||
quiet: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
format: this.SERIALIZE_RULE,
|
||||
|
||||
// these take arrays of elements or ids and can be
|
||||
// used for better initialization performance
|
||||
elements: false,
|
||||
handles: false,
|
||||
|
||||
onChange: Prototype.emptyFunction,
|
||||
onUpdate: Prototype.emptyFunction
|
||||
}, arguments[1] || { });
|
||||
|
||||
// clear any old sortable with same element
|
||||
this.destroy(element);
|
||||
|
||||
// build options for the draggables
|
||||
var options_for_draggable = {
|
||||
revert: true,
|
||||
quiet: options.quiet,
|
||||
scroll: options.scroll,
|
||||
scrollSpeed: options.scrollSpeed,
|
||||
scrollSensitivity: options.scrollSensitivity,
|
||||
delay: options.delay,
|
||||
ghosting: options.ghosting,
|
||||
constraint: options.constraint,
|
||||
handle: options.handle };
|
||||
|
||||
if(options.starteffect)
|
||||
options_for_draggable.starteffect = options.starteffect;
|
||||
|
||||
if(options.reverteffect)
|
||||
options_for_draggable.reverteffect = options.reverteffect;
|
||||
else
|
||||
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
|
||||
element.style.top = 0;
|
||||
element.style.left = 0;
|
||||
};
|
||||
|
||||
if(options.endeffect)
|
||||
options_for_draggable.endeffect = options.endeffect;
|
||||
|
||||
if(options.zindex)
|
||||
options_for_draggable.zindex = options.zindex;
|
||||
|
||||
// build options for the droppables
|
||||
var options_for_droppable = {
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
tree: options.tree,
|
||||
hoverclass: options.hoverclass,
|
||||
onHover: Sortable.onHover
|
||||
}
|
||||
|
||||
var options_for_tree = {
|
||||
onHover: Sortable.onEmptyHover,
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
hoverclass: options.hoverclass
|
||||
}
|
||||
|
||||
// fix for gecko engine
|
||||
Element.cleanWhitespace(element);
|
||||
|
||||
options.draggables = [];
|
||||
options.droppables = [];
|
||||
|
||||
// drop on empty handling
|
||||
if(options.dropOnEmpty || options.tree) {
|
||||
Droppables.add(element, options_for_tree);
|
||||
options.droppables.push(element);
|
||||
}
|
||||
|
||||
(options.elements || this.findElements(element, options) || []).each( function(e,i) {
|
||||
var handle = options.handles ? $(options.handles[i]) :
|
||||
(options.handle ? $(e).select('.' + options.handle)[0] : e);
|
||||
options.draggables.push(
|
||||
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
|
||||
Droppables.add(e, options_for_droppable);
|
||||
if(options.tree) e.treeNode = element;
|
||||
options.droppables.push(e);
|
||||
});
|
||||
|
||||
if(options.tree) {
|
||||
(Sortable.findTreeElements(element, options) || []).each( function(e) {
|
||||
Droppables.add(e, options_for_tree);
|
||||
e.treeNode = element;
|
||||
options.droppables.push(e);
|
||||
});
|
||||
}
|
||||
|
||||
// keep reference
|
||||
this.sortables[element.id] = options;
|
||||
|
||||
// for onupdate
|
||||
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
|
||||
|
||||
},
|
||||
|
||||
// return all suitable-for-sortable elements in a guaranteed order
|
||||
findElements: function(element, options) {
|
||||
return Element.findChildren(
|
||||
element, options.only, options.tree ? true : false, options.tag);
|
||||
},
|
||||
|
||||
findTreeElements: function(element, options) {
|
||||
return Element.findChildren(
|
||||
element, options.only, options.tree ? true : false, options.treeTag);
|
||||
},
|
||||
|
||||
onHover: function(element, dropon, overlap) {
|
||||
if(Element.isParent(dropon, element)) return;
|
||||
|
||||
if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
|
||||
return;
|
||||
} else if(overlap>0.5) {
|
||||
Sortable.mark(dropon, 'before');
|
||||
if(dropon.previousSibling != element) {
|
||||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, dropon);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
} else {
|
||||
Sortable.mark(dropon, 'after');
|
||||
var nextElement = dropon.nextSibling || null;
|
||||
if(nextElement != element) {
|
||||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, nextElement);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onEmptyHover: function(element, dropon, overlap) {
|
||||
var oldParentNode = element.parentNode;
|
||||
var droponOptions = Sortable.options(dropon);
|
||||
|
||||
if(!Element.isParent(dropon, element)) {
|
||||
var index;
|
||||
|
||||
var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
|
||||
var child = null;
|
||||
|
||||
if(children) {
|
||||
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
|
||||
|
||||
for (index = 0; index < children.length; index += 1) {
|
||||
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
|
||||
offset -= Element.offsetSize (children[index], droponOptions.overlap);
|
||||
} else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
|
||||
child = index + 1 < children.length ? children[index + 1] : null;
|
||||
break;
|
||||
} else {
|
||||
child = children[index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dropon.insertBefore(element, child);
|
||||
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
droponOptions.onChange(element);
|
||||
}
|
||||
},
|
||||
|
||||
unmark: function() {
|
||||
if(Sortable._marker) Sortable._marker.hide();
|
||||
},
|
||||
|
||||
mark: function(dropon, position) {
|
||||
// mark on ghosting only
|
||||
var sortable = Sortable.options(dropon.parentNode);
|
||||
if(sortable && !sortable.ghosting) return;
|
||||
|
||||
if(!Sortable._marker) {
|
||||
Sortable._marker =
|
||||
($('dropmarker') || Element.extend(document.createElement('DIV'))).
|
||||
hide().addClassName('dropmarker').setStyle({position:'absolute'});
|
||||
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
||||
}
|
||||
var offsets = Position.cumulativeOffset(dropon);
|
||||
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
|
||||
|
||||
if(position=='after')
|
||||
if(sortable.overlap == 'horizontal')
|
||||
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
|
||||
else
|
||||
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
|
||||
|
||||
Sortable._marker.show();
|
||||
},
|
||||
|
||||
_tree: function(element, options, parent) {
|
||||
var children = Sortable.findElements(element, options) || [];
|
||||
|
||||
for (var i = 0; i < children.length; ++i) {
|
||||
var match = children[i].id.match(options.format);
|
||||
|
||||
if (!match) continue;
|
||||
|
||||
var child = {
|
||||
id: encodeURIComponent(match ? match[1] : null),
|
||||
element: element,
|
||||
parent: parent,
|
||||
children: [],
|
||||
position: parent.children.length,
|
||||
container: $(children[i]).down(options.treeTag)
|
||||
}
|
||||
|
||||
/* Get the element containing the children and recurse over it */
|
||||
if (child.container)
|
||||
this._tree(child.container, options, child)
|
||||
|
||||
parent.children.push (child);
|
||||
}
|
||||
|
||||
return parent;
|
||||
},
|
||||
|
||||
tree: function(element) {
|
||||
element = $(element);
|
||||
var sortableOptions = this.options(element);
|
||||
var options = Object.extend({
|
||||
tag: sortableOptions.tag,
|
||||
treeTag: sortableOptions.treeTag,
|
||||
only: sortableOptions.only,
|
||||
name: element.id,
|
||||
format: sortableOptions.format
|
||||
}, arguments[1] || { });
|
||||
|
||||
var root = {
|
||||
id: null,
|
||||
parent: null,
|
||||
children: [],
|
||||
container: element,
|
||||
position: 0
|
||||
}
|
||||
|
||||
return Sortable._tree(element, options, root);
|
||||
},
|
||||
|
||||
/* Construct a [i] index for a particular node */
|
||||
_constructIndex: function(node) {
|
||||
var index = '';
|
||||
do {
|
||||
if (node.id) index = '[' + node.position + ']' + index;
|
||||
} while ((node = node.parent) != null);
|
||||
return index;
|
||||
},
|
||||
|
||||
sequence: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend(this.options(element), arguments[1] || { });
|
||||
|
||||
return $(this.findElements(element, options) || []).map( function(item) {
|
||||
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
|
||||
});
|
||||
},
|
||||
|
||||
setSequence: function(element, new_sequence) {
|
||||
element = $(element);
|
||||
var options = Object.extend(this.options(element), arguments[2] || { });
|
||||
|
||||
var nodeMap = { };
|
||||
this.findElements(element, options).each( function(n) {
|
||||
if (n.id.match(options.format))
|
||||
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
|
||||
n.parentNode.removeChild(n);
|
||||
});
|
||||
|
||||
new_sequence.each(function(ident) {
|
||||
var n = nodeMap[ident];
|
||||
if (n) {
|
||||
n[1].appendChild(n[0]);
|
||||
delete nodeMap[ident];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
serialize: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend(Sortable.options(element), arguments[1] || { });
|
||||
var name = encodeURIComponent(
|
||||
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
|
||||
|
||||
if (options.tree) {
|
||||
return Sortable.tree(element, arguments[1]).children.map( function (item) {
|
||||
return [name + Sortable._constructIndex(item) + "[id]=" +
|
||||
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
|
||||
}).flatten().join('&');
|
||||
} else {
|
||||
return Sortable.sequence(element, arguments[1]).map( function(item) {
|
||||
return name + "[]=" + encodeURIComponent(item);
|
||||
}).join('&');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if child is contained within element
|
||||
Element.isParent = function(child, element) {
|
||||
if (!child.parentNode || child == element) return false;
|
||||
if (child.parentNode == element) return true;
|
||||
return Element.isParent(child.parentNode, element);
|
||||
}
|
||||
|
||||
Element.findChildren = function(element, only, recursive, tagName) {
|
||||
if(!element.hasChildNodes()) return null;
|
||||
tagName = tagName.toUpperCase();
|
||||
if(only) only = [only].flatten();
|
||||
var elements = [];
|
||||
$A(element.childNodes).each( function(e) {
|
||||
if(e.tagName && e.tagName.toUpperCase()==tagName &&
|
||||
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
|
||||
elements.push(e);
|
||||
if(recursive) {
|
||||
var grandchildren = Element.findChildren(e, only, recursive, tagName);
|
||||
if(grandchildren) elements.push(grandchildren);
|
||||
}
|
||||
});
|
||||
|
||||
return (elements.length>0 ? elements.flatten() : []);
|
||||
}
|
||||
|
||||
Element.offsetSize = function (element, type) {
|
||||
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
|
||||
}
|
||||
1120
vendor/rails/actionpack/lib/action_view/helpers/javascripts/effects.js
vendored
Normal file
1120
vendor/rails/actionpack/lib/action_view/helpers/javascripts/effects.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
4225
vendor/rails/actionpack/lib/action_view/helpers/javascripts/prototype.js
vendored
Normal file
4225
vendor/rails/actionpack/lib/action_view/helpers/javascripts/prototype.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
179
vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb
vendored
Normal file
179
vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
# Provides methods for converting numbers into formatted strings.
|
||||
# Methods are provided for phone numbers, currency, percentage,
|
||||
# precision, positional notation, and file size.
|
||||
module NumberHelper
|
||||
# Formats a +number+ into a US phone number (e.g., (555) 123-9876). You can customize the format
|
||||
# in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:area_code</tt> - Adds parentheses around the area code.
|
||||
# * <tt>:delimiter</tt> - Specifies the delimiter to use (defaults to "-").
|
||||
# * <tt>:extension</tt> - Specifies an extension to add to the end of the
|
||||
# generated number.
|
||||
# * <tt>:country_code</tt> - Sets the country code for the phone number.
|
||||
#
|
||||
# ==== Examples
|
||||
# number_to_phone(1235551234) # => 123-555-1234
|
||||
# number_to_phone(1235551234, :area_code => true) # => (123) 555-1234
|
||||
# number_to_phone(1235551234, :delimiter => " ") # => 123 555 1234
|
||||
# number_to_phone(1235551234, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
|
||||
# number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
|
||||
#
|
||||
# number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
|
||||
# => +1.123.555.1234 x 1343
|
||||
def number_to_phone(number, options = {})
|
||||
number = number.to_s.strip unless number.nil?
|
||||
options = options.stringify_keys
|
||||
area_code = options["area_code"] || nil
|
||||
delimiter = options["delimiter"] || "-"
|
||||
extension = options["extension"].to_s.strip || nil
|
||||
country_code = options["country_code"] || nil
|
||||
|
||||
begin
|
||||
str = ""
|
||||
str << "+#{country_code}#{delimiter}" unless country_code.blank?
|
||||
str << if area_code
|
||||
number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4}$)/,"(\\1) \\2#{delimiter}\\3")
|
||||
else
|
||||
number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
|
||||
end
|
||||
str << " x #{extension}" unless extension.blank?
|
||||
str
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ into a currency string (e.g., $13.65). You can customize the format
|
||||
# in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 2).
|
||||
# * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_to_currency(1234567890.50) # => $1,234,567,890.50
|
||||
# number_to_currency(1234567890.506) # => $1,234,567,890.51
|
||||
# number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
|
||||
#
|
||||
# number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "")
|
||||
# # => £1234567890,50
|
||||
def number_to_currency(number, options = {})
|
||||
options = options.stringify_keys
|
||||
precision = options["precision"] || 2
|
||||
unit = options["unit"] || "$"
|
||||
separator = precision > 0 ? options["separator"] || "." : ""
|
||||
delimiter = options["delimiter"] || ","
|
||||
|
||||
begin
|
||||
parts = number_with_precision(number, precision).split('.')
|
||||
unit + number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ as a percentage string (e.g., 65%). You can customize the
|
||||
# format in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_to_percentage(100) # => 100.000%
|
||||
# number_to_percentage(100, :precision => 0) # => 100%
|
||||
#
|
||||
# number_to_percentage(302.24398923423, :precision => 5)
|
||||
# # => 302.24399%
|
||||
def number_to_percentage(number, options = {})
|
||||
options = options.stringify_keys
|
||||
precision = options["precision"] || 3
|
||||
separator = options["separator"] || "."
|
||||
|
||||
begin
|
||||
number = number_with_precision(number, precision)
|
||||
parts = number.split('.')
|
||||
if parts.at(1).nil?
|
||||
parts[0] + "%"
|
||||
else
|
||||
parts[0] + separator + parts[1].to_s + "%"
|
||||
end
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You
|
||||
# can customize the format using optional <em>delimiter</em> and <em>separator</em> parameters.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>delimiter</tt> - Sets the thousands delimiter (defaults to ",").
|
||||
# * <tt>separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_with_delimiter(12345678) # => 12,345,678
|
||||
# number_with_delimiter(12345678.05) # => 12,345,678.05
|
||||
# number_with_delimiter(12345678, ".") # => 12.345.678
|
||||
#
|
||||
# number_with_delimiter(98765432.98, " ", ",")
|
||||
# # => 98 765 432,98
|
||||
def number_with_delimiter(number, delimiter=",", separator=".")
|
||||
begin
|
||||
parts = number.to_s.split('.')
|
||||
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
|
||||
parts.join separator
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ with the specified level of +precision+ (e.g., 112.32 has a precision of 2). The default
|
||||
# level of precision is 3.
|
||||
#
|
||||
# ==== Examples
|
||||
# number_with_precision(111.2345) # => 111.235
|
||||
# number_with_precision(111.2345, 2) # => 111.24
|
||||
# number_with_precision(13, 5) # => 13.00000
|
||||
# number_with_precision(389.32314, 0) # => 389
|
||||
def number_with_precision(number, precision=3)
|
||||
"%01.#{precision}f" % number
|
||||
rescue
|
||||
number
|
||||
end
|
||||
|
||||
# Formats the bytes in +size+ into a more understandable representation
|
||||
# (e.g., giving it 1500 yields 1.5 KB). This method is useful for
|
||||
# reporting file sizes to users. This method returns nil if
|
||||
# +size+ cannot be converted into a number. You can change the default
|
||||
# precision of 1 using the precision parameter +precision+.
|
||||
#
|
||||
# ==== Examples
|
||||
# number_to_human_size(123) # => 123 Bytes
|
||||
# number_to_human_size(1234) # => 1.2 KB
|
||||
# number_to_human_size(12345) # => 12.1 KB
|
||||
# number_to_human_size(1234567) # => 1.2 MB
|
||||
# number_to_human_size(1234567890) # => 1.1 GB
|
||||
# number_to_human_size(1234567890123) # => 1.1 TB
|
||||
# number_to_human_size(1234567, 2) # => 1.18 MB
|
||||
# number_to_human_size(483989, 0) # => 4 MB
|
||||
def number_to_human_size(size, precision=1)
|
||||
size = Kernel.Float(size)
|
||||
case
|
||||
when size.to_i == 1; "1 Byte"
|
||||
when size < 1.kilobyte; "%d Bytes" % size
|
||||
when size < 1.megabyte; "%.#{precision}f KB" % (size / 1.0.kilobyte)
|
||||
when size < 1.gigabyte; "%.#{precision}f MB" % (size / 1.0.megabyte)
|
||||
when size < 1.terabyte; "%.#{precision}f GB" % (size / 1.0.gigabyte)
|
||||
else "%.#{precision}f TB" % (size / 1.0.terabyte)
|
||||
end.sub(/([0-9])\.?0+ /, '\1 ' )
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
1268
vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb
vendored
Normal file
1268
vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb
vendored
Normal file
File diff suppressed because it is too large
Load diff
20
vendor/rails/actionpack/lib/action_view/helpers/record_identification_helper.rb
vendored
Normal file
20
vendor/rails/actionpack/lib/action_view/helpers/record_identification_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
module ActionView
|
||||
module Helpers
|
||||
module RecordIdentificationHelper
|
||||
# See ActionController::RecordIdentifier.partial_path -- this is just a delegate to that for convenient access in the view.
|
||||
def partial_path(*args, &block)
|
||||
ActionController::RecordIdentifier.partial_path(*args, &block)
|
||||
end
|
||||
|
||||
# See ActionController::RecordIdentifier.dom_class -- this is just a delegate to that for convenient access in the view.
|
||||
def dom_class(*args, &block)
|
||||
ActionController::RecordIdentifier.dom_class(*args, &block)
|
||||
end
|
||||
|
||||
# See ActionController::RecordIdentifier.dom_id -- this is just a delegate to that for convenient access in the view.
|
||||
def dom_id(*args, &block)
|
||||
ActionController::RecordIdentifier.dom_id(*args, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
59
vendor/rails/actionpack/lib/action_view/helpers/record_tag_helper.rb
vendored
Normal file
59
vendor/rails/actionpack/lib/action_view/helpers/record_tag_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
module ActionView
|
||||
module Helpers
|
||||
module RecordTagHelper
|
||||
# Produces a wrapper DIV element with id and class parameters that
|
||||
# relate to the specified ActiveRecord object. Usage example:
|
||||
#
|
||||
# <% div_for(@person, :class => "foo") do %>
|
||||
# <%=h @person.name %>
|
||||
# <% end %>
|
||||
#
|
||||
# produces:
|
||||
#
|
||||
# <div id="person_123" class="person foo"> Joe Bloggs </div>
|
||||
#
|
||||
def div_for(record, *args, &block)
|
||||
content_tag_for(:div, record, *args, &block)
|
||||
end
|
||||
|
||||
# content_tag_for creates an HTML element with id and class parameters
|
||||
# that relate to the specified ActiveRecord object. For example:
|
||||
#
|
||||
# <% content_tag_for(:tr, @person) do %>
|
||||
# <td><%=h @person.first_name %></td>
|
||||
# <td><%=h @person.last_name %></td>
|
||||
# <% end %>
|
||||
#
|
||||
# would produce hthe following HTML (assuming @person is an instance of
|
||||
# a Person object, with an id value of 123):
|
||||
#
|
||||
# <tr id="person_123" class="person">....</tr>
|
||||
#
|
||||
# If you require the HTML id attribute to have a prefix, you can specify it:
|
||||
#
|
||||
# <% content_tag_for(:tr, @person, :foo) do %> ...
|
||||
#
|
||||
# produces:
|
||||
#
|
||||
# <tr id="foo_person_123" class="person">...
|
||||
#
|
||||
# content_tag_for also accepts a hash of options, which will be converted to
|
||||
# additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined
|
||||
# with the default class name for your object. For example:
|
||||
#
|
||||
# <% content_tag_for(:li, @person, :class => "bar") %>...
|
||||
#
|
||||
# produces:
|
||||
#
|
||||
# <li id="person_123" class="person bar">...
|
||||
#
|
||||
def content_tag_for(tag_name, record, *args, &block)
|
||||
prefix = args.first.is_a?(Hash) ? nil : args.shift
|
||||
options = args.first.is_a?(Hash) ? args.shift : {}
|
||||
concat content_tag(tag_name, capture(&block),
|
||||
options.merge({ :class => "#{dom_class(record)} #{options[:class]}".strip, :id => dom_id(record, prefix) })),
|
||||
block.binding
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
223
vendor/rails/actionpack/lib/action_view/helpers/sanitize_helper.rb
vendored
Normal file
223
vendor/rails/actionpack/lib/action_view/helpers/sanitize_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
require 'action_view/helpers/tag_helper'
|
||||
require 'html/document'
|
||||
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
# The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
|
||||
# These helper methods extend ActionView making them callable within your template files.
|
||||
module SanitizeHelper
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# This #sanitize helper will html encode all tags and strip all attributes that aren't specifically allowed.
|
||||
# It also strips href/src tags with invalid protocols, like javascript: especially. It does its best to counter any
|
||||
# tricks that hackers may use, like throwing in unicode/ascii/hex values to get past the javascript: filters. Check out
|
||||
# the extensive test suite.
|
||||
#
|
||||
# <%= sanitize @article.body %>
|
||||
#
|
||||
# You can add or remove tags/attributes if you want to customize it a bit. See ActionView::Base for full docs on the
|
||||
# available options. You can add tags/attributes for single uses of #sanitize by passing either the :attributes or :tags options:
|
||||
#
|
||||
# Normal Use
|
||||
#
|
||||
# <%= sanitize @article.body %>
|
||||
#
|
||||
# Custom Use (only the mentioned tags and attributes are allowed, nothing else)
|
||||
#
|
||||
# <%= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id class style)
|
||||
#
|
||||
# Add table tags to the default allowed tags
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
|
||||
# end
|
||||
#
|
||||
# Remove tags to the default allowed tags
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.after_initialize do
|
||||
# ActionView::Base.sanitized_allowed_tags.delete 'div'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Change allowed default attributes
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style'
|
||||
# end
|
||||
#
|
||||
def sanitize(html, options = {})
|
||||
self.class.white_list_sanitizer.sanitize(html, options)
|
||||
end
|
||||
|
||||
# Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
|
||||
def sanitize_css(style)
|
||||
self.class.white_list_sanitizer.sanitize_css(style)
|
||||
end
|
||||
|
||||
# Strips all HTML tags from the +html+, including comments. This uses the
|
||||
# html-scanner tokenizer and so its HTML parsing ability is limited by
|
||||
# that of html-scanner.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# strip_tags("Strip <i>these</i> tags!")
|
||||
# # => Strip these tags!
|
||||
#
|
||||
# strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
|
||||
# # => Bold no more! See more here...
|
||||
#
|
||||
# strip_tags("<div id='top-bar'>Welcome to my website!</div>")
|
||||
# # => Welcome to my website!
|
||||
def strip_tags(html)
|
||||
self.class.full_sanitizer.sanitize(html)
|
||||
end
|
||||
|
||||
# Strips all link tags from +text+ leaving just the link text.
|
||||
#
|
||||
# ==== Examples
|
||||
# strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
|
||||
# # => Ruby on Rails
|
||||
#
|
||||
# strip_links('Please e-mail me at <a href="mailto:me@email.com">me@email.com</a>.')
|
||||
# # => Please e-mail me at me@email.com.
|
||||
#
|
||||
# strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.')
|
||||
# # => Blog: Visit
|
||||
def strip_links(html)
|
||||
self.class.link_sanitizer.sanitize(html)
|
||||
end
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
def self.extended(base)
|
||||
class << base
|
||||
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
|
||||
|
||||
# we want these to be class methods on ActionView::Base, they'll get mattr_readers for these below.
|
||||
helper_def = [:sanitized_protocol_separator, :sanitized_uri_attributes, :sanitized_bad_tags, :sanitized_allowed_tags,
|
||||
:sanitized_allowed_attributes, :sanitized_allowed_css_properties, :sanitized_allowed_css_keywords,
|
||||
:sanitized_shorthand_css_properties, :sanitized_allowed_protocols, :sanitized_protocol_separator=].collect! do |prop|
|
||||
prop = prop.to_s
|
||||
"def #{prop}(#{:value if prop =~ /=$/}) white_list_sanitizer.#{prop.sub /sanitized_/, ''} #{:value if prop =~ /=$/} end"
|
||||
end.join("\n")
|
||||
eval helper_def
|
||||
end
|
||||
end
|
||||
|
||||
# Gets the HTML::FullSanitizer instance used by strip_tags. Replace with
|
||||
# any object that responds to #sanitize
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.full_sanitizer = MySpecialSanitizer.new
|
||||
# end
|
||||
#
|
||||
def full_sanitizer
|
||||
@full_sanitizer ||= HTML::FullSanitizer.new
|
||||
end
|
||||
|
||||
# Gets the HTML::LinkSanitizer instance used by strip_links. Replace with
|
||||
# any object that responds to #sanitize
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.link_sanitizer = MySpecialSanitizer.new
|
||||
# end
|
||||
#
|
||||
def link_sanitizer
|
||||
@link_sanitizer ||= HTML::LinkSanitizer.new
|
||||
end
|
||||
|
||||
# Gets the HTML::WhiteListSanitizer instance used by sanitize and sanitize_css.
|
||||
# Replace with any object that responds to #sanitize
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.white_list_sanitizer = MySpecialSanitizer.new
|
||||
# end
|
||||
#
|
||||
def white_list_sanitizer
|
||||
@white_list_sanitizer ||= HTML::WhiteListSanitizer.new
|
||||
end
|
||||
|
||||
# Adds valid HTML attributes that the #sanitize helper checks for URIs.
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_uri_attributes = 'lowsrc', 'target'
|
||||
# end
|
||||
#
|
||||
def sanitized_uri_attributes=(attributes)
|
||||
HTML::WhiteListSanitizer.uri_attributes.merge(attributes)
|
||||
end
|
||||
|
||||
# Adds to the Set of 'bad' tags for the #sanitize helper.
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_bad_tags = 'embed', 'object'
|
||||
# end
|
||||
#
|
||||
def sanitized_bad_tags=(attributes)
|
||||
HTML::WhiteListSanitizer.bad_tags.merge(attributes)
|
||||
end
|
||||
# Adds to the Set of allowed tags for the #sanitize helper.
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
|
||||
# end
|
||||
#
|
||||
def sanitized_allowed_tags=(attributes)
|
||||
HTML::WhiteListSanitizer.allowed_tags.merge(attributes)
|
||||
end
|
||||
|
||||
# Adds to the Set of allowed html attributes for the #sanitize helper.
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc'
|
||||
# end
|
||||
#
|
||||
def sanitized_allowed_attributes=(attributes)
|
||||
HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
|
||||
end
|
||||
|
||||
# Adds to the Set of allowed css properties for the #sanitize and #sanitize_css heleprs.
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_allowed_css_properties = 'expression'
|
||||
# end
|
||||
#
|
||||
def sanitized_allowed_css_properties=(attributes)
|
||||
HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes)
|
||||
end
|
||||
|
||||
# Adds to the Set of allowed css keywords for the #sanitize and #sanitize_css helpers.
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_allowed_css_keywords = 'expression'
|
||||
# end
|
||||
#
|
||||
def sanitized_allowed_css_keywords=(attributes)
|
||||
HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes)
|
||||
end
|
||||
|
||||
# Adds to the Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers.
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_shorthand_css_properties = 'expression'
|
||||
# end
|
||||
#
|
||||
def sanitized_shorthand_css_properties=(attributes)
|
||||
HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes)
|
||||
end
|
||||
|
||||
# Adds to the Set of allowed protocols for the #sanitize helper.
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_allowed_protocols = 'ssh', 'feed'
|
||||
# end
|
||||
#
|
||||
def sanitized_allowed_protocols=(attributes)
|
||||
HTML::WhiteListSanitizer.allowed_protocols.merge(attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
199
vendor/rails/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
vendored
Normal file
199
vendor/rails/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
require 'action_view/helpers/javascript_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# Provides a set of helpers for calling Scriptaculous JavaScript
|
||||
# functions, including those which create Ajax controls and visual effects.
|
||||
#
|
||||
# To be able to use these helpers, you must include the Prototype
|
||||
# JavaScript framework and the Scriptaculous JavaScript library in your
|
||||
# pages. See the documentation for ActionView::Helpers::JavaScriptHelper
|
||||
# for more information on including the necessary JavaScript.
|
||||
#
|
||||
# The Scriptaculous helpers' behavior can be tweaked with various options.
|
||||
# See the documentation at http://script.aculo.us for more information on
|
||||
# using these helpers in your application.
|
||||
module ScriptaculousHelper
|
||||
unless const_defined? :TOGGLE_EFFECTS
|
||||
TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
|
||||
end
|
||||
|
||||
# Returns a JavaScript snippet to be used on the Ajax callbacks for
|
||||
# starting visual effects.
|
||||
#
|
||||
# Example:
|
||||
# <%= link_to_remote "Reload", :update => "posts",
|
||||
# :url => { :action => "reload" },
|
||||
# :complete => visual_effect(:highlight, "posts", :duration => 0.5)
|
||||
#
|
||||
# If no element_id is given, it assumes "element" which should be a local
|
||||
# variable in the generated JavaScript execution context. This can be
|
||||
# used for example with drop_receiving_element:
|
||||
#
|
||||
# <%= drop_receiving_element (...), :loading => visual_effect(:fade) %>
|
||||
#
|
||||
# This would fade the element that was dropped on the drop receiving
|
||||
# element.
|
||||
#
|
||||
# For toggling visual effects, you can use :toggle_appear, :toggle_slide, and
|
||||
# :toggle_blind which will alternate between appear/fade, slidedown/slideup, and
|
||||
# blinddown/blindup respectively.
|
||||
#
|
||||
# You can change the behaviour with various options, see
|
||||
# http://script.aculo.us for more documentation.
|
||||
def visual_effect(name, element_id = false, js_options = {})
|
||||
element = element_id ? element_id.to_json : "element"
|
||||
|
||||
js_options[:queue] = if js_options[:queue].is_a?(Hash)
|
||||
'{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
|
||||
elsif js_options[:queue]
|
||||
"'#{js_options[:queue]}'"
|
||||
end if js_options[:queue]
|
||||
|
||||
[:endcolor, :direction, :startcolor, :scaleMode, :restorecolor].each do |option|
|
||||
js_options[option] = "'#{js_options[option]}'" if js_options[option]
|
||||
end
|
||||
|
||||
if TOGGLE_EFFECTS.include? name.to_sym
|
||||
"Effect.toggle(#{element},'#{name.to_s.gsub(/^toggle_/,'')}',#{options_for_javascript(js_options)});"
|
||||
else
|
||||
"new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
|
||||
end
|
||||
end
|
||||
|
||||
# Makes the element with the DOM ID specified by +element_id+ sortable
|
||||
# by drag-and-drop and make an Ajax call whenever the sort order has
|
||||
# changed. By default, the action called gets the serialized sortable
|
||||
# element as parameters.
|
||||
#
|
||||
# Example:
|
||||
# <%= sortable_element("my_list", :url => { :action => "order" }) %>
|
||||
#
|
||||
# In the example, the action gets a "my_list" array parameter
|
||||
# containing the values of the ids of elements the sortable consists
|
||||
# of, in the current order.
|
||||
#
|
||||
# Important: For this to work, the sortable elements must have id
|
||||
# attributes in the form "string_identifier". For example, "item_1". Only
|
||||
# the identifier part of the id attribute will be serialized.
|
||||
#
|
||||
# Additional +options+ are:
|
||||
#
|
||||
# <tt>:format</tt>:: A regular expression to determine what to send
|
||||
# as the serialized id to the server (the default
|
||||
# is <tt>/^[^_]*_(.*)$/</tt>).
|
||||
#
|
||||
# <tt>:constraint</tt>:: Whether to constrain the dragging to either <tt>:horizontal</tt>
|
||||
# or <tt>:vertical</tt> (or false to make it unconstrained).
|
||||
#
|
||||
# <tt>:overlap</tt>:: Calculate the item overlap in the <tt>:horizontal</tt> or
|
||||
# <tt>:vertical</tt> direction.
|
||||
#
|
||||
# <tt>:tag</tt>:: Which children of the container element to treat as
|
||||
# sortable (default is <tt>li</tt>).
|
||||
#
|
||||
# <tt>:containment</tt>:: Takes an element or array of elements to treat as
|
||||
# potential drop targets (defaults to the original
|
||||
# target element).
|
||||
#
|
||||
# <tt>:only</tt>:: A CSS class name or arry of class names used to filter
|
||||
# out child elements as candidates.
|
||||
#
|
||||
# <tt>:scroll</tt>:: Determines whether to scroll the list during drag
|
||||
# operationsif the list runs past the visual border.
|
||||
#
|
||||
# <tt>:tree</tt>:: Determines whether to treat nested lists as part of the
|
||||
# main sortable list. This means that you can create multi-
|
||||
# layer lists, and not only sort items at the same level,
|
||||
# but drag and sort items between levels.
|
||||
#
|
||||
# <tt>:hoverclass</tt>:: If set, the Droppable will have this additional CSS class
|
||||
# when an accepted Draggable is hovered over it.
|
||||
#
|
||||
# <tt>:handle</tt>:: Sets whether the element should only be draggable by an
|
||||
# embedded handle. The value may be a string referencing a
|
||||
# CSS class value (as of script.aculo.us V1.5). The first
|
||||
# child/grandchild/etc. element found within the element
|
||||
# that has this CSS class value will be used as the handle.
|
||||
#
|
||||
# <tt>:ghosting</tt>:: Clones the element and drags the clone, leaving the original
|
||||
# in place until the clone is dropped (defaut is <tt>false</tt>).
|
||||
#
|
||||
# <tt>:dropOnEmpty</tt>:: If set to true, the Sortable container will be made into
|
||||
# a Droppable, that can receive a Draggable (as according to
|
||||
# the containment rules) as a child element when there are no
|
||||
# more elements inside (defaut is <tt>false</tt>).
|
||||
#
|
||||
# <tt>:onChange</tt>:: Called whenever the sort order changes while dragging. When
|
||||
# dragging from one Sortable to another, the callback is
|
||||
# called once on each Sortable. Gets the affected element as
|
||||
# its parameter.
|
||||
#
|
||||
# <tt>:onUpdate</tt>:: Called when the drag ends and the Sortable's order is
|
||||
# changed in any way. When dragging from one Sortable to
|
||||
# another, the callback is called once on each Sortable. Gets
|
||||
# the container as its parameter.
|
||||
#
|
||||
# See http://script.aculo.us for more documentation.
|
||||
def sortable_element(element_id, options = {})
|
||||
javascript_tag(sortable_element_js(element_id, options).chop!)
|
||||
end
|
||||
|
||||
def sortable_element_js(element_id, options = {}) #:nodoc:
|
||||
options[:with] ||= "Sortable.serialize(#{element_id.to_json})"
|
||||
options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
|
||||
options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
|
||||
|
||||
[:tag, :overlap, :constraint, :handle].each do |option|
|
||||
options[option] = "'#{options[option]}'" if options[option]
|
||||
end
|
||||
|
||||
options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
|
||||
options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
|
||||
|
||||
%(Sortable.create(#{element_id.to_json}, #{options_for_javascript(options)});)
|
||||
end
|
||||
|
||||
# Makes the element with the DOM ID specified by +element_id+ draggable.
|
||||
#
|
||||
# Example:
|
||||
# <%= draggable_element("my_image", :revert => true)
|
||||
#
|
||||
# You can change the behaviour with various options, see
|
||||
# http://script.aculo.us for more documentation.
|
||||
def draggable_element(element_id, options = {})
|
||||
javascript_tag(draggable_element_js(element_id, options).chop!)
|
||||
end
|
||||
|
||||
def draggable_element_js(element_id, options = {}) #:nodoc:
|
||||
%(new Draggable(#{element_id.to_json}, #{options_for_javascript(options)});)
|
||||
end
|
||||
|
||||
# Makes the element with the DOM ID specified by +element_id+ receive
|
||||
# dropped draggable elements (created by draggable_element).
|
||||
# and make an AJAX call By default, the action called gets the DOM ID
|
||||
# of the element as parameter.
|
||||
#
|
||||
# Example:
|
||||
# <%= drop_receiving_element("my_cart", :url =>
|
||||
# { :controller => "cart", :action => "add" }) %>
|
||||
#
|
||||
# You can change the behaviour with various options, see
|
||||
# http://script.aculo.us for more documentation.
|
||||
def drop_receiving_element(element_id, options = {})
|
||||
javascript_tag(drop_receiving_element_js(element_id, options).chop!)
|
||||
end
|
||||
|
||||
def drop_receiving_element_js(element_id, options = {}) #:nodoc:
|
||||
options[:with] ||= "'id=' + encodeURIComponent(element.id)"
|
||||
options[:onDrop] ||= "function(element){" + remote_function(options) + "}"
|
||||
options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
|
||||
|
||||
options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
|
||||
options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
|
||||
|
||||
%(Droppables.add(#{element_id.to_json}, #{options_for_javascript(options)});)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
133
vendor/rails/actionpack/lib/action_view/helpers/tag_helper.rb
vendored
Normal file
133
vendor/rails/actionpack/lib/action_view/helpers/tag_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
require 'cgi'
|
||||
require 'erb'
|
||||
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
# Provides methods to generate HTML tags programmatically when you can't use
|
||||
# a Builder. By default, they output XHTML compliant tags.
|
||||
module TagHelper
|
||||
include ERB::Util
|
||||
|
||||
BOOLEAN_ATTRIBUTES = Set.new(%w(disabled readonly multiple))
|
||||
|
||||
# Returns an empty HTML tag of type +name+ which by default is XHTML
|
||||
# compliant. Set +open+ to true to create an open tag compatible
|
||||
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
|
||||
# hash to +options+. Set +escape+ to false to disable attribute value
|
||||
# escaping.
|
||||
#
|
||||
# ==== Options
|
||||
# The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
|
||||
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
|
||||
# symbols or strings for the attribute names.
|
||||
#
|
||||
# ==== Examples
|
||||
# tag("br")
|
||||
# # => <br />
|
||||
#
|
||||
# tag("br", nil, true)
|
||||
# # => <br>
|
||||
#
|
||||
# tag("input", { :type => 'text', :disabled => true })
|
||||
# # => <input type="text" disabled="disabled" />
|
||||
#
|
||||
# tag("img", { :src => "open & shut.png" })
|
||||
# # => <img src="open & shut.png" />
|
||||
#
|
||||
# tag("img", { :src => "open & shut.png" }, false, false)
|
||||
# # => <img src="open & shut.png" />
|
||||
def tag(name, options = nil, open = false, escape = true)
|
||||
"<#{name}#{tag_options(options, escape) if options}" + (open ? ">" : " />")
|
||||
end
|
||||
|
||||
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
|
||||
# HTML attributes by passing an attributes hash to +options+.
|
||||
# Instead of passing the content as an argument, you can also use a block
|
||||
# in which case, you pass your +options+ as the second parameter.
|
||||
# Set escape to false to disable attribute value escaping.
|
||||
#
|
||||
# ==== Options
|
||||
# The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
|
||||
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
|
||||
# symbols or strings for the attribute names.
|
||||
#
|
||||
# ==== Examples
|
||||
# content_tag(:p, "Hello world!")
|
||||
# # => <p>Hello world!</p>
|
||||
# content_tag(:div, content_tag(:p, "Hello world!"), :class => "strong")
|
||||
# # => <div class="strong"><p>Hello world!</p></div>
|
||||
# content_tag("select", options, :multiple => true)
|
||||
# # => <select multiple="multiple">...options...</select>
|
||||
#
|
||||
# <% content_tag :div, :class => "strong" do -%>
|
||||
# Hello world!
|
||||
# <% end -%>
|
||||
# # => <div class="strong"><p>Hello world!</p></div>
|
||||
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
|
||||
if block_given?
|
||||
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
|
||||
content = capture(&block)
|
||||
content_tag = content_tag_string(name, content, options, escape)
|
||||
block_is_within_action_view?(block) ? concat(content_tag, block.binding) : content_tag
|
||||
else
|
||||
content = content_or_options_with_block
|
||||
content_tag_string(name, content, options, escape)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a CDATA section with the given +content+. CDATA sections
|
||||
# are used to escape blocks of text containing characters which would
|
||||
# otherwise be recognized as markup. CDATA sections begin with the string
|
||||
# <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
|
||||
#
|
||||
# ==== Examples
|
||||
# cdata_section("<hello world>")
|
||||
# # => <![CDATA[<hello world>]]>
|
||||
#
|
||||
# cdata_section(File.read("hello_world.txt"))
|
||||
# # => <![CDATA[<hello from a text file]]>
|
||||
def cdata_section(content)
|
||||
"<![CDATA[#{content}]]>"
|
||||
end
|
||||
|
||||
# Returns an escaped version of +html+ without affecting existing escaped entities.
|
||||
#
|
||||
# ==== Examples
|
||||
# escape_once("1 > 2 & 3")
|
||||
# # => "1 < 2 & 3"
|
||||
#
|
||||
# escape_once("<< Accept & Checkout")
|
||||
# # => "<< Accept & Checkout"
|
||||
def escape_once(html)
|
||||
html.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
|
||||
end
|
||||
|
||||
private
|
||||
def content_tag_string(name, content, options, escape = true)
|
||||
tag_options = tag_options(options, escape) if options
|
||||
"<#{name}#{tag_options}>#{content}</#{name}>"
|
||||
end
|
||||
|
||||
def tag_options(options, escape = true)
|
||||
unless options.blank?
|
||||
attrs = []
|
||||
if escape
|
||||
options.each do |key, value|
|
||||
next unless value
|
||||
key = key.to_s
|
||||
value = BOOLEAN_ATTRIBUTES.include?(key) ? key : escape_once(value)
|
||||
attrs << %(#{key}="#{value}")
|
||||
end
|
||||
else
|
||||
attrs = options.map { |key, value| %(#{key}="#{value}") }
|
||||
end
|
||||
" #{attrs.sort * ' '}" unless attrs.empty?
|
||||
end
|
||||
end
|
||||
|
||||
def block_is_within_action_view?(block)
|
||||
eval("defined? _erbout", block.binding)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
479
vendor/rails/actionpack/lib/action_view/helpers/text_helper.rb
vendored
Normal file
479
vendor/rails/actionpack/lib/action_view/helpers/text_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
require 'action_view/helpers/tag_helper'
|
||||
require 'html/document'
|
||||
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
# The TextHelper module provides a set of methods for filtering, formatting
|
||||
# and transforming strings, which can reduce the amount of inline Ruby code in
|
||||
# your views. These helper methods extend ActionView making them callable
|
||||
# within your template files.
|
||||
module TextHelper
|
||||
# The preferred method of outputting text in your views is to use the
|
||||
# <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
|
||||
# do not operate as expected in an eRuby code block. If you absolutely must
|
||||
# output text within a non-output code block (i.e., <% %>), you can use the concat method.
|
||||
#
|
||||
# ==== Examples
|
||||
# <%
|
||||
# concat "hello", binding
|
||||
# # is the equivalent of <%= "hello" %>
|
||||
#
|
||||
# if (logged_in == true):
|
||||
# concat "Logged in!", binding
|
||||
# else
|
||||
# concat link_to('login', :action => login), binding
|
||||
# end
|
||||
# # will either display "Logged in!" or a login link
|
||||
# %>
|
||||
def concat(string, binding)
|
||||
eval(ActionView::Base.erb_variable, binding) << string
|
||||
end
|
||||
|
||||
# If +text+ is longer than +length+, +text+ will be truncated to the length of
|
||||
# +length+ (defaults to 30) and the last characters will be replaced with the +truncate_string+
|
||||
# (defaults to "...").
|
||||
#
|
||||
# ==== Examples
|
||||
# truncate("Once upon a time in a world far far away", 14)
|
||||
# # => Once upon a...
|
||||
#
|
||||
# truncate("Once upon a time in a world far far away")
|
||||
# # => Once upon a time in a world f...
|
||||
#
|
||||
# truncate("And they found that many people were sleeping better.", 25, "(clipped)")
|
||||
# # => And they found that many (clipped)
|
||||
#
|
||||
# truncate("And they found that many people were sleeping better.", 15, "... (continued)")
|
||||
# # => And they found... (continued)
|
||||
def truncate(text, length = 30, truncate_string = "...")
|
||||
if text.nil? then return end
|
||||
l = length - truncate_string.chars.length
|
||||
(text.chars.length > length ? text.chars[0...l] + truncate_string : text).to_s
|
||||
end
|
||||
|
||||
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
|
||||
# a +highlighter+ string. The highlighter can be specialized by passing +highlighter+
|
||||
# as a single-quoted string with \1 where the phrase is to be inserted (defaults to
|
||||
# '<strong class="highlight">\1</strong>')
|
||||
#
|
||||
# ==== Examples
|
||||
# highlight('You searched for: rails', 'rails')
|
||||
# # => You searched for: <strong class="highlight">rails</strong>
|
||||
#
|
||||
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
|
||||
# # => You searched for: ruby, rails, dhh
|
||||
#
|
||||
# highlight('You searched for: rails', ['for', 'rails'], '<em>\1</em>')
|
||||
# # => You searched <em>for</em>: <em>rails</em>
|
||||
#
|
||||
# highlight('You searched for: rails', 'rails', "<a href='search?q=\1'>\1</a>")
|
||||
# # => You searched for: <a href='search?q=rails>rails</a>
|
||||
def highlight(text, phrases, highlighter = '<strong class="highlight">\1</strong>')
|
||||
if text.blank? || phrases.blank?
|
||||
text
|
||||
else
|
||||
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
|
||||
text.gsub(/(#{match})/i, highlighter)
|
||||
end
|
||||
end
|
||||
|
||||
# Extracts an excerpt from +text+ that matches the first instance of +phrase+.
|
||||
# The +radius+ expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
|
||||
# defined in +radius+ (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
|
||||
# then the +excerpt_string+ will be prepended/appended accordingly. If the +phrase+
|
||||
# isn't found, nil is returned.
|
||||
#
|
||||
# ==== Examples
|
||||
# excerpt('This is an example', 'an', 5)
|
||||
# # => "...s is an examp..."
|
||||
#
|
||||
# excerpt('This is an example', 'is', 5)
|
||||
# # => "This is an..."
|
||||
#
|
||||
# excerpt('This is an example', 'is')
|
||||
# # => "This is an example"
|
||||
#
|
||||
# excerpt('This next thing is an example', 'ex', 2)
|
||||
# # => "...next t..."
|
||||
#
|
||||
# excerpt('This is also an example', 'an', 8, '<chop> ')
|
||||
# # => "<chop> is also an example"
|
||||
def excerpt(text, phrase, radius = 100, excerpt_string = "...")
|
||||
if text.nil? || phrase.nil? then return end
|
||||
phrase = Regexp.escape(phrase)
|
||||
|
||||
if found_pos = text.chars =~ /(#{phrase})/i
|
||||
start_pos = [ found_pos - radius, 0 ].max
|
||||
end_pos = [ found_pos + phrase.chars.length + radius, text.chars.length ].min
|
||||
|
||||
prefix = start_pos > 0 ? excerpt_string : ""
|
||||
postfix = end_pos < text.chars.length ? excerpt_string : ""
|
||||
|
||||
prefix + text.chars[start_pos..end_pos].strip + postfix
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Attempts to pluralize the +singular+ word unless +count+ is 1. If +plural+
|
||||
# is supplied, it will use that when count is > 1, if the ActiveSupport Inflector
|
||||
# is loaded, it will use the Inflector to determine the plural form, otherwise
|
||||
# it will just add an 's' to the +singular+ word.
|
||||
#
|
||||
# ==== Examples
|
||||
# pluralize(1, 'person')
|
||||
# # => 1 person
|
||||
#
|
||||
# pluralize(2, 'person')
|
||||
# # => 2 people
|
||||
#
|
||||
# pluralize(3, 'person', 'users')
|
||||
# # => 3 users
|
||||
#
|
||||
# pluralize(0, 'person')
|
||||
# # => 0 people
|
||||
def pluralize(count, singular, plural = nil)
|
||||
"#{count || 0} " + if count == 1 || count == '1'
|
||||
singular
|
||||
elsif plural
|
||||
plural
|
||||
elsif Object.const_defined?("Inflector")
|
||||
Inflector.pluralize(singular)
|
||||
else
|
||||
singular + "s"
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps the +text+ into lines no longer than +line_width+ width. This method
|
||||
# breaks on the first whitespace character that does not exceed +line_width+
|
||||
# (which is 80 by default).
|
||||
#
|
||||
# ==== Examples
|
||||
# word_wrap('Once upon a time', 4)
|
||||
# # => Once\nupon\na\ntime
|
||||
#
|
||||
# word_wrap('Once upon a time', 8)
|
||||
# # => Once upon\na time
|
||||
#
|
||||
# word_wrap('Once upon a time')
|
||||
# # => Once upon a time
|
||||
#
|
||||
# word_wrap('Once upon a time', 1)
|
||||
# # => Once\nupon\na\ntime
|
||||
def word_wrap(text, line_width = 80)
|
||||
text.split("\n").collect do |line|
|
||||
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
|
||||
end * "\n"
|
||||
end
|
||||
|
||||
begin
|
||||
require_library_or_gem "redcloth" unless Object.const_defined?(:RedCloth)
|
||||
|
||||
# Returns the text with all the Textile[http://www.textism.com/tools/textile] codes turned into HTML tags.
|
||||
#
|
||||
# You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile].
|
||||
# <i>This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
|
||||
# is available</i>.
|
||||
#
|
||||
# ==== Examples
|
||||
# textilize("*This is Textile!* Rejoice!")
|
||||
# # => "<p><strong>This is Textile!</strong> Rejoice!</p>"
|
||||
#
|
||||
# textilize("I _love_ ROR(Ruby on Rails)!")
|
||||
# # => "<p>I <em>love</em> <acronym title="Ruby on Rails">ROR</acronym>!</p>"
|
||||
#
|
||||
# textilize("h2. Textile makes markup -easy- simple!")
|
||||
# # => "<h2>Textile makes markup <del>easy</del> simple!</h2>"
|
||||
#
|
||||
# textilize("Visit the Rails website "here":http://www.rubyonrails.org/.)
|
||||
# # => "<p>Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>.</p>"
|
||||
def textilize(text)
|
||||
if text.blank?
|
||||
""
|
||||
else
|
||||
textilized = RedCloth.new(text, [ :hard_breaks ])
|
||||
textilized.hard_breaks = true if textilized.respond_to?("hard_breaks=")
|
||||
textilized.to_html
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the text with all the Textile codes turned into HTML tags,
|
||||
# but without the bounding <p> tag that RedCloth adds.
|
||||
#
|
||||
# You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile].
|
||||
# <i>This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
|
||||
# is available</i>.
|
||||
#
|
||||
# ==== Examples
|
||||
# textilize_without_paragraph("*This is Textile!* Rejoice!")
|
||||
# # => "<strong>This is Textile!</strong> Rejoice!"
|
||||
#
|
||||
# textilize_without_paragraph("I _love_ ROR(Ruby on Rails)!")
|
||||
# # => "I <em>love</em> <acronym title="Ruby on Rails">ROR</acronym>!"
|
||||
#
|
||||
# textilize_without_paragraph("h2. Textile makes markup -easy- simple!")
|
||||
# # => "<h2>Textile makes markup <del>easy</del> simple!</h2>"
|
||||
#
|
||||
# textilize_without_paragraph("Visit the Rails website "here":http://www.rubyonrails.org/.)
|
||||
# # => "Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>."
|
||||
def textilize_without_paragraph(text)
|
||||
textiled = textilize(text)
|
||||
if textiled[0..2] == "<p>" then textiled = textiled[3..-1] end
|
||||
if textiled[-4..-1] == "</p>" then textiled = textiled[0..-5] end
|
||||
return textiled
|
||||
end
|
||||
rescue LoadError
|
||||
# We can't really help what's not there
|
||||
end
|
||||
|
||||
begin
|
||||
require_library_or_gem "bluecloth" unless Object.const_defined?(:BlueCloth)
|
||||
|
||||
# Returns the text with all the Markdown codes turned into HTML tags.
|
||||
# <i>This method is only available if BlueCloth[http://www.deveiate.org/projects/BlueCloth]
|
||||
# is available</i>.
|
||||
#
|
||||
# ==== Examples
|
||||
# markdown("We are using __Markdown__ now!")
|
||||
# # => "<p>We are using <strong>Markdown</strong> now!</p>"
|
||||
#
|
||||
# markdown("We like to _write_ `code`, not just _read_ it!")
|
||||
# # => "<p>We like to <em>write</em> <code>code</code>, not just <em>read</em> it!</p>"
|
||||
#
|
||||
# markdown("The [Markdown website](http://daringfireball.net/projects/markdown/) has more information.")
|
||||
# # => "<p>The <a href="http://daringfireball.net/projects/markdown/">Markdown website</a>
|
||||
# # has more information.</p>"
|
||||
#
|
||||
# markdown('')
|
||||
# # => '<p><img src="http://rubyonrails.com/images/rails.png" alt="The ROR logo" title="Ruby on Rails" /></p>'
|
||||
def markdown(text)
|
||||
text.blank? ? "" : BlueCloth.new(text).to_html
|
||||
end
|
||||
rescue LoadError
|
||||
# We can't really help what's not there
|
||||
end
|
||||
|
||||
# Returns +text+ transformed into HTML using simple formatting rules.
|
||||
# Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a
|
||||
# paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
|
||||
# considered as a linebreak and a <tt><br /></tt> tag is appended. This
|
||||
# method does not remove the newlines from the +text+.
|
||||
#
|
||||
# ==== Examples
|
||||
# my_text = """Here is some basic text...
|
||||
# ...with a line break."""
|
||||
#
|
||||
# simple_format(my_text)
|
||||
# # => "<p>Here is some basic text...<br />...with a line break.</p>"
|
||||
#
|
||||
# more_text = """We want to put a paragraph...
|
||||
#
|
||||
# ...right there."""
|
||||
#
|
||||
# simple_format(more_text)
|
||||
# # => "<p>We want to put a paragraph...</p><p>...right there.</p>"
|
||||
def simple_format(text)
|
||||
content_tag 'p', text.to_s.
|
||||
gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
|
||||
gsub(/\n\n+/, "</p>\n\n<p>"). # 2+ newline -> paragraph
|
||||
gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
|
||||
end
|
||||
|
||||
# Turns all URLs and e-mail addresses into clickable links. The +link+ parameter
|
||||
# will limit what should be linked. You can add HTML attributes to the links using
|
||||
# +href_options+. Options for +link+ are <tt>:all</tt> (default),
|
||||
# <tt>:email_addresses</tt>, and <tt>:urls</tt>. If a block is given, each URL and
|
||||
# e-mail address is yielded and the result is used as the link text.
|
||||
#
|
||||
# ==== Examples
|
||||
# auto_link("Go to http://www.rubyonrails.org and say hello to david@loudthinking.com")
|
||||
# # => "Go to <a href=\"http://www.rubyonrails.org\">http://www.rubyonrails.org</a> and
|
||||
# # say hello to <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>"
|
||||
#
|
||||
# auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :urls)
|
||||
# # => "Visit <a href=\"http://www.loudthinking.com/\">http://www.loudthinking.com/</a>
|
||||
# # or e-mail david@loudthinking.com"
|
||||
#
|
||||
# auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :email_addresses)
|
||||
# # => "Visit http://www.loudthinking.com/ or e-mail <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>"
|
||||
#
|
||||
# post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com."
|
||||
# auto_link(post_body, :all, :target => '_blank') do |text|
|
||||
# truncate(text, 15)
|
||||
# end
|
||||
# # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>.
|
||||
# Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
|
||||
#
|
||||
def auto_link(text, link = :all, href_options = {}, &block)
|
||||
return '' if text.blank?
|
||||
case link
|
||||
when :all then auto_link_email_addresses(auto_link_urls(text, href_options, &block), &block)
|
||||
when :email_addresses then auto_link_email_addresses(text, &block)
|
||||
when :urls then auto_link_urls(text, href_options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a Cycle object whose _to_s_ method cycles through elements of an
|
||||
# array every time it is called. This can be used for example, to alternate
|
||||
# classes for table rows. You can use named cycles to allow nesting in loops.
|
||||
# Passing a Hash as the last parameter with a <tt>:name</tt> key will create a
|
||||
# named cycle. You can manually reset a cycle by calling reset_cycle and passing the
|
||||
# name of the cycle.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Alternate CSS classes for even and odd numbers...
|
||||
# @items = [1,2,3,4]
|
||||
# <table>
|
||||
# <% @items.each do |item| %>
|
||||
# <tr class="<%= cycle("even", "odd") -%>">
|
||||
# <td>item</td>
|
||||
# </tr>
|
||||
# <% end %>
|
||||
# </table>
|
||||
#
|
||||
#
|
||||
# # Cycle CSS classes for rows, and text colors for values within each row
|
||||
# @items = x = [{:first => 'Robert', :middle => 'Daniel', :last => 'James'},
|
||||
# {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'},
|
||||
# {:first => 'June', :middle => 'Dae', :last => 'Jones'}]
|
||||
# <% @items.each do |item| %>
|
||||
# <tr class="<%= cycle("even", "odd", :name => "row_class") -%>">
|
||||
# <td>
|
||||
# <% item.values.each do |value| %>
|
||||
# <%# Create a named cycle "colors" %>
|
||||
# <span style="color:<%= cycle("red", "green", "blue", :name => "colors") -%>">
|
||||
# <%= value %>
|
||||
# </span>
|
||||
# <% end %>
|
||||
# <% reset_cycle("colors") %>
|
||||
# </td>
|
||||
# </tr>
|
||||
# <% end %>
|
||||
def cycle(first_value, *values)
|
||||
if (values.last.instance_of? Hash)
|
||||
params = values.pop
|
||||
name = params[:name]
|
||||
else
|
||||
name = "default"
|
||||
end
|
||||
values.unshift(first_value)
|
||||
|
||||
cycle = get_cycle(name)
|
||||
if (cycle.nil? || cycle.values != values)
|
||||
cycle = set_cycle(name, Cycle.new(*values))
|
||||
end
|
||||
return cycle.to_s
|
||||
end
|
||||
|
||||
# Resets a cycle so that it starts from the first element the next time
|
||||
# it is called. Pass in +name+ to reset a named cycle.
|
||||
#
|
||||
# ==== Example
|
||||
# # Alternate CSS classes for even and odd numbers...
|
||||
# @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]]
|
||||
# <table>
|
||||
# <% @items.each do |item| %>
|
||||
# <tr class="<%= cycle("even", "odd") -%>">
|
||||
# <% item.each do |value| %>
|
||||
# <span style="color:<%= cycle("#333", "#666", "#999", :name => "colors") -%>">
|
||||
# <%= value %>
|
||||
# </span>
|
||||
# <% end %>
|
||||
#
|
||||
# <% reset_cycle("colors") %>
|
||||
# </tr>
|
||||
# <% end %>
|
||||
# </table>
|
||||
def reset_cycle(name = "default")
|
||||
cycle = get_cycle(name)
|
||||
cycle.reset unless cycle.nil?
|
||||
end
|
||||
|
||||
class Cycle #:nodoc:
|
||||
attr_reader :values
|
||||
|
||||
def initialize(first_value, *values)
|
||||
@values = values.unshift(first_value)
|
||||
reset
|
||||
end
|
||||
|
||||
def reset
|
||||
@index = 0
|
||||
end
|
||||
|
||||
def to_s
|
||||
value = @values[@index].to_s
|
||||
@index = (@index + 1) % @values.size
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# The cycle helpers need to store the cycles in a place that is
|
||||
# guaranteed to be reset every time a page is rendered, so it
|
||||
# uses an instance variable of ActionView::Base.
|
||||
def get_cycle(name)
|
||||
@_cycles = Hash.new unless defined?(@_cycles)
|
||||
return @_cycles[name]
|
||||
end
|
||||
|
||||
def set_cycle(name, cycle_object)
|
||||
@_cycles = Hash.new unless defined?(@_cycles)
|
||||
@_cycles[name] = cycle_object
|
||||
end
|
||||
|
||||
AUTO_LINK_RE = %r{
|
||||
( # leading text
|
||||
<\w+.*?>| # leading HTML tag, or
|
||||
[^=!:'"/]| # leading punctuation, or
|
||||
^ # beginning of line
|
||||
)
|
||||
(
|
||||
(?:https?://)| # protocol spec, or
|
||||
(?:www\.) # www.*
|
||||
)
|
||||
(
|
||||
[-\w]+ # subdomain or domain
|
||||
(?:\.[-\w]+)* # remaining subdomains or domain
|
||||
(?::\d+)? # port
|
||||
(?:/(?:(?:[~\w\+@%-]|(?:[,.;:][^\s$]))+)?)* # path
|
||||
(?:\?[\w\+@%&=.;-]+)? # query string
|
||||
(?:\#[\w\-]*)? # trailing anchor
|
||||
)
|
||||
([[:punct:]]|\s|<|$) # trailing text
|
||||
}x unless const_defined?(:AUTO_LINK_RE)
|
||||
|
||||
# Turns all urls into clickable links. If a block is given, each url
|
||||
# is yielded and the result is used as the link text.
|
||||
def auto_link_urls(text, href_options = {})
|
||||
extra_options = tag_options(href_options.stringify_keys) || ""
|
||||
text.gsub(AUTO_LINK_RE) do
|
||||
all, a, b, c, d = $&, $1, $2, $3, $4
|
||||
if a =~ /<a\s/i # don't replace URL's that are already linked
|
||||
all
|
||||
else
|
||||
text = b + c
|
||||
text = yield(text) if block_given?
|
||||
%(#{a}<a href="#{b=="www."?"http://www.":b}#{c}"#{extra_options}>#{text}</a>#{d})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Turns all email addresses into clickable links. If a block is given,
|
||||
# each email is yielded and the result is used as the link text.
|
||||
def auto_link_email_addresses(text)
|
||||
body = text.dup
|
||||
text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
|
||||
text = $1
|
||||
|
||||
if body.match(/<a\b[^>]*>(.*)(#{Regexp.escape(text)})(.*)<\/a>/)
|
||||
text
|
||||
else
|
||||
display_text = (block_given?) ? yield(text) : text
|
||||
%{<a href="mailto:#{text}">#{display_text}</a>}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
524
vendor/rails/actionpack/lib/action_view/helpers/url_helper.rb
vendored
Normal file
524
vendor/rails/actionpack/lib/action_view/helpers/url_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
require 'action_view/helpers/javascript_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
# Provides a set of methods for making links and getting URLs that
|
||||
# depend on the routing subsystem (see ActionController::Routing).
|
||||
# This allows you to use the same format for links in views
|
||||
# and controllers.
|
||||
module UrlHelper
|
||||
include JavaScriptHelper
|
||||
|
||||
# Returns the URL for the set of +options+ provided. This takes the
|
||||
# same options as url_for in ActionController (see the
|
||||
# documentation for ActionController::Base#url_for). Note that by default
|
||||
# <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative /controller/action
|
||||
# instead of the fully qualified URL like http://example.com/controller/action.
|
||||
#
|
||||
# When called from a view, url_for returns an HTML escaped url. If you
|
||||
# need an unescaped url, pass :escape => false in the +options+.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:anchor</tt> -- specifies the anchor name to be appended to the path.
|
||||
# * <tt>:only_path</tt> -- if true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified)
|
||||
# * <tt>:trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
|
||||
# is currently not recommended since it breaks caching.
|
||||
# * <tt>:host</tt> -- overrides the default (current) host if provided
|
||||
# * <tt>:protocol</tt> -- overrides the default (current) protocol if provided
|
||||
# * <tt>:user</tt> -- Inline HTTP authentication (only plucked out if :password is also present)
|
||||
# * <tt>:password</tt> -- Inline HTTP authentication (only plucked out if :user is also present)
|
||||
# * <tt>:escape</tt> -- Determines whether the returned URL will be HTML escaped or not (<tt>true</tt> by default)
|
||||
#
|
||||
# ==== Relying on named routes
|
||||
#
|
||||
# If you instead of a hash pass a record (like an Active Record or Active Resource) as the options parameter,
|
||||
# you'll trigger the named route for that record. The lookup will happen on the name of the class. So passing
|
||||
# a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as
|
||||
# admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route).
|
||||
#
|
||||
# ==== Examples
|
||||
# <%= url_for(:action => 'index') %>
|
||||
# # => /blog/
|
||||
#
|
||||
# <%= url_for(:action => 'find', :controller => 'books') %>
|
||||
# # => /books/find
|
||||
#
|
||||
# <%= url_for(:action => 'login', :controller => 'members', :only_path => false, :protocol => 'https') %>
|
||||
# # => https://www.railsapplication.com/members/login/
|
||||
#
|
||||
# <%= url_for(:action => 'play', :anchor => 'player') %>
|
||||
# # => /messages/play/#player
|
||||
#
|
||||
# <%= url_for(:action => 'checkout', :anchor => 'tax&ship') %>
|
||||
# # => /testing/jump/#tax&ship
|
||||
#
|
||||
# <%= url_for(:action => 'checkout', :anchor => 'tax&ship', :escape => false) %>
|
||||
# # => /testing/jump/#tax&ship
|
||||
#
|
||||
# <%= url_for(Workshop.new) %>
|
||||
# # relies on Workshop answering a new_record? call (and in this case returning true)
|
||||
# # => /workshops
|
||||
#
|
||||
# <%= url_for(@workshop) %>
|
||||
# # calls @workshop.to_s
|
||||
# # => /workshops/5
|
||||
def url_for(options = {})
|
||||
case options
|
||||
when Hash
|
||||
show_path = options[:host].nil? ? true : false
|
||||
options = { :only_path => show_path }.update(options.symbolize_keys)
|
||||
escape = options.key?(:escape) ? options.delete(:escape) : true
|
||||
url = @controller.send(:url_for, options)
|
||||
when String
|
||||
escape = true
|
||||
url = options
|
||||
when NilClass
|
||||
url = @controller.send(:url_for, nil)
|
||||
else
|
||||
escape = false
|
||||
url = polymorphic_path(options)
|
||||
end
|
||||
|
||||
escape ? escape_once(url) : url
|
||||
end
|
||||
|
||||
# Creates a link tag of the given +name+ using a URL created by the set
|
||||
# of +options+. See the valid options in the documentation for
|
||||
# url_for. It's also possible to pass a string instead
|
||||
# of an options hash to get a link tag that uses the value of the string as the
|
||||
# href for the link, or use +:back+ to link to the referrer - a JavaScript back
|
||||
# link will be used in place of a referrer if none exists. If nil is passed as
|
||||
# a name, the link itself will become the name.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:confirm => 'question?'</tt> -- This will add a JavaScript confirm
|
||||
# prompt with the question specified. If the user accepts, the link is
|
||||
# processed normally, otherwise no action is taken.
|
||||
# * <tt>:popup => true || array of window options</tt> -- This will force the
|
||||
# link to open in a popup window. By passing true, a default browser window
|
||||
# will be opened with the URL. You can also specify an array of options
|
||||
# that are passed-thru to JavaScripts window.open method.
|
||||
# * <tt>:method => symbol of HTTP verb</tt> -- This modifier will dynamically
|
||||
# create an HTML form and immediately submit the form for processing using
|
||||
# the HTTP verb specified. Useful for having links perform a POST operation
|
||||
# in dangerous actions like deleting a record (which search bots can follow
|
||||
# while spidering your site). Supported verbs are :post, :delete and :put.
|
||||
# Note that if the user has JavaScript disabled, the request will fall back
|
||||
# to using GET. If you are relying on the POST behavior, you should check
|
||||
# for it in your controller's action by using the request object's methods
|
||||
# for post?, delete? or put?.
|
||||
# * The +html_options+ will accept a hash of html attributes for the link tag.
|
||||
#
|
||||
# Note that if the user has JavaScript disabled, the request will fall back
|
||||
# to using GET. If :href=>'#' is used and the user has JavaScript disabled
|
||||
# clicking the link will have no effect. If you are relying on the POST
|
||||
# behavior, your should check for it in your controller's action by using the
|
||||
# request object's methods for post?, delete? or put?.
|
||||
#
|
||||
# You can mix and match the +html_options+ with the exception of
|
||||
# :popup and :method which will raise an ActionView::ActionViewError
|
||||
# exception.
|
||||
#
|
||||
# ==== Examples
|
||||
# link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?"
|
||||
# # => <a href="http://www.rubyonrails.org/" onclick="return confirm('Are you sure?');">Visit Other Site</a>
|
||||
#
|
||||
# link_to "Help", { :action => "help" }, :popup => true
|
||||
# # => <a href="/testing/help/" onclick="window.open(this.href);return false;">Help</a>
|
||||
#
|
||||
# link_to "View Image", { :action => "view" }, :popup => ['new_window_name', 'height=300,width=600']
|
||||
# # => <a href="/testing/view/" onclick="window.open(this.href,'new_window_name','height=300,width=600');return false;">View Image</a>
|
||||
#
|
||||
# link_to "Delete Image", { :action => "delete", :id => @image.id }, :confirm => "Are you sure?", :method => :delete
|
||||
# # => <a href="/testing/delete/9/" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form');
|
||||
# f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;
|
||||
# var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method');
|
||||
# m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;">Delete Image</a>
|
||||
def link_to(name, options = {}, html_options = nil)
|
||||
url = case options
|
||||
when String
|
||||
options
|
||||
when :back
|
||||
@controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
|
||||
else
|
||||
self.url_for(options)
|
||||
end
|
||||
|
||||
if html_options
|
||||
html_options = html_options.stringify_keys
|
||||
href = html_options['href']
|
||||
convert_options_to_javascript!(html_options, url)
|
||||
tag_options = tag_options(html_options)
|
||||
else
|
||||
tag_options = nil
|
||||
end
|
||||
|
||||
href_attr = "href=\"#{url}\"" unless href
|
||||
"<a #{href_attr}#{tag_options}>#{name || url}</a>"
|
||||
end
|
||||
|
||||
# Generates a form containing a single button that submits to the URL created
|
||||
# by the set of +options+. This is the safest method to ensure links that
|
||||
# cause changes to your data are not triggered by search bots or accelerators.
|
||||
# If the HTML button does not work with your layout, you can also consider
|
||||
# using the link_to method with the <tt>:method</tt> modifier as described in
|
||||
# the link_to documentation.
|
||||
#
|
||||
# The generated FORM element has a class name of <tt>button-to</tt>
|
||||
# to allow styling of the form itself and its children. You can control
|
||||
# the form submission and input element behavior using +html_options+.
|
||||
# This method accepts the <tt>:method</tt> and <tt>:confirm</tt> modifiers
|
||||
# described in the link_to documentation. If no <tt>:method</tt> modifier
|
||||
# is given, it will default to performing a POST operation. You can also
|
||||
# disable the button by passing <tt>:disabled => true</tt> in +html_options+.
|
||||
# If you are using RESTful routes, you can pass the <tt>:method</tt>
|
||||
# to change the HTTP verb used to submit the form.
|
||||
#
|
||||
# ==== Options
|
||||
# The +options+ hash accepts the same options at url_for.
|
||||
#
|
||||
# There are a few special +html_options+:
|
||||
# * <tt>:method</tt> -- specifies the anchor name to be appended to the path.
|
||||
# * <tt>:disabled</tt> -- specifies the anchor name to be appended to the path.
|
||||
# * <tt>:confirm</tt> -- This will add a JavaScript confirm
|
||||
# prompt with the question specified. If the user accepts, the link is
|
||||
# processed normally, otherwise no action is taken.
|
||||
#
|
||||
# ==== Examples
|
||||
# <%= button_to "New", :action => "new" %>
|
||||
# # => "<form method="post" action="/controller/new" class="button-to">
|
||||
# # <div><input value="New" type="submit" /></div>
|
||||
# # </form>"
|
||||
#
|
||||
# button_to "Delete Image", { :action => "delete", :id => @image.id },
|
||||
# :confirm => "Are you sure?", :method => :delete
|
||||
# # => "<form method="post" action="/images/delete/1" class="button-to">
|
||||
# # <div>
|
||||
# # <input type="hidden" name="_method" value="delete" />
|
||||
# # <input onclick="return confirm('Are you sure?');"
|
||||
# # value="Delete" type="submit" />
|
||||
# # </div>
|
||||
# # </form>"
|
||||
def button_to(name, options = {}, html_options = {})
|
||||
html_options = html_options.stringify_keys
|
||||
convert_boolean_attributes!(html_options, %w( disabled ))
|
||||
|
||||
method_tag = ''
|
||||
if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
|
||||
method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
|
||||
end
|
||||
|
||||
form_method = method.to_s == 'get' ? 'get' : 'post'
|
||||
|
||||
request_token_tag = ''
|
||||
if form_method == 'post' && protect_against_forgery?
|
||||
request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
|
||||
end
|
||||
|
||||
if confirm = html_options.delete("confirm")
|
||||
html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
|
||||
end
|
||||
|
||||
url = options.is_a?(String) ? options : self.url_for(options)
|
||||
name ||= url
|
||||
|
||||
html_options.merge!("type" => "submit", "value" => name)
|
||||
|
||||
"<form method=\"#{form_method}\" action=\"#{escape_once url}\" class=\"button-to\"><div>" +
|
||||
method_tag + tag("input", html_options) + request_token_tag + "</div></form>"
|
||||
end
|
||||
|
||||
|
||||
# Creates a link tag of the given +name+ using a URL created by the set of
|
||||
# +options+ unless the current request URI is the same as the links, in
|
||||
# which case only the name is returned (or the given block is yielded, if
|
||||
# one exists). You can give link_to_unless_current a block which will
|
||||
# specialize the default behavior (e.g., show a "Start Here" link rather
|
||||
# than the link's text).
|
||||
#
|
||||
# ==== Examples
|
||||
# Let's say you have a navigation menu...
|
||||
#
|
||||
# <ul id="navbar">
|
||||
# <li><%= link_to_unless_current("Home", { :action => "index" }) %></li>
|
||||
# <li><%= link_to_unless_current("About Us", { :action => "about" }) %></li>
|
||||
# </ul>
|
||||
#
|
||||
# If in the "about" action, it will render...
|
||||
#
|
||||
# <ul id="navbar">
|
||||
# <li><a href="/controller/index">Home</a></li>
|
||||
# <li>About Us</li>
|
||||
# </ul>
|
||||
#
|
||||
# ...but if in the "home" action, it will render:
|
||||
#
|
||||
# <ul id="navbar">
|
||||
# <li><a href="/controller/index">Home</a></li>
|
||||
# <li><a href="/controller/about">About Us</a></li>
|
||||
# </ul>
|
||||
#
|
||||
# The implicit block given to link_to_unless_current is evaluated if the current
|
||||
# action is the action given. So, if we had a comments page and wanted to render a
|
||||
# "Go Back" link instead of a link to the comments page, we could do something like this...
|
||||
#
|
||||
# <%=
|
||||
# link_to_unless_current("Comment", { :controller => 'comments', :action => 'new}) do
|
||||
# link_to("Go back", { :controller => 'posts', :action => 'index' })
|
||||
# end
|
||||
# %>
|
||||
def link_to_unless_current(name, options = {}, html_options = {}, &block)
|
||||
link_to_unless current_page?(options), name, options, html_options, &block
|
||||
end
|
||||
|
||||
# Creates a link tag of the given +name+ using a URL created by the set of
|
||||
# +options+ unless +condition+ is true, in which case only the name is
|
||||
# returned. To specialize the default behavior (i.e., show a login link rather
|
||||
# than just the plaintext link text), you can pass a block that
|
||||
# accepts the name or the full argument list for link_to_unless.
|
||||
#
|
||||
# ==== Examples
|
||||
# <%= link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) %>
|
||||
# # If the user is logged in...
|
||||
# # => <a href="/controller/reply/">Reply</a>
|
||||
#
|
||||
# <%=
|
||||
# link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) do |name|
|
||||
# link_to(name, { :controller => "accounts", :action => "signup" })
|
||||
# end
|
||||
# %>
|
||||
# # If the user is logged in...
|
||||
# # => <a href="/controller/reply/">Reply</a>
|
||||
# # If not...
|
||||
# # => <a href="/accounts/signup">Reply</a>
|
||||
def link_to_unless(condition, name, options = {}, html_options = {}, &block)
|
||||
if condition
|
||||
if block_given?
|
||||
block.arity <= 1 ? yield(name) : yield(name, options, html_options)
|
||||
else
|
||||
name
|
||||
end
|
||||
else
|
||||
link_to(name, options, html_options)
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a link tag of the given +name+ using a URL created by the set of
|
||||
# +options+ if +condition+ is true, in which case only the name is
|
||||
# returned. To specialize the default behavior, you can pass a block that
|
||||
# accepts the name or the full argument list for link_to_unless (see the examples
|
||||
# in link_to_unless).
|
||||
#
|
||||
# ==== Examples
|
||||
# <%= link_to_if(@current_user.nil?, "Login", { :controller => "sessions", :action => "new" }) %>
|
||||
# # If the user isn't logged in...
|
||||
# # => <a href="/sessions/new/">Login</a>
|
||||
#
|
||||
# <%=
|
||||
# link_to_if(@current_user.nil?, "Login", { :controller => "sessions", :action => "new" }) do
|
||||
# link_to(@current_user.login, { :controller => "accounts", :action => "show", :id => @current_user })
|
||||
# end
|
||||
# %>
|
||||
# # If the user isn't logged in...
|
||||
# # => <a href="/sessions/new/">Login</a>
|
||||
# # If they are logged in...
|
||||
# # => <a href="/accounts/show/3">my_username</a>
|
||||
def link_to_if(condition, name, options = {}, html_options = {}, &block)
|
||||
link_to_unless !condition, name, options, html_options, &block
|
||||
end
|
||||
|
||||
# Creates a mailto link tag to the specified +email_address+, which is
|
||||
# also used as the name of the link unless +name+ is specified. Additional
|
||||
# HTML attributes for the link can be passed in +html_options+.
|
||||
#
|
||||
# mail_to has several methods for hindering email harvesters and customizing
|
||||
# the email itself by passing special keys to +html_options+.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:encode</tt> - This key will accept the strings "javascript" or "hex".
|
||||
# Passing "javascript" will dynamically create and encode the mailto: link then
|
||||
# eval it into the DOM of the page. This method will not show the link on
|
||||
# the page if the user has JavaScript disabled. Passing "hex" will hex
|
||||
# encode the +email_address+ before outputting the mailto: link.
|
||||
# * <tt>:replace_at</tt> - When the link +name+ isn't provided, the
|
||||
# +email_address+ is used for the link label. You can use this option to
|
||||
# obfuscate the +email_address+ by substituting the @ sign with the string
|
||||
# given as the value.
|
||||
# * <tt>:replace_dot</tt> - When the link +name+ isn't provided, the
|
||||
# +email_address+ is used for the link label. You can use this option to
|
||||
# obfuscate the +email_address+ by substituting the . in the email with the
|
||||
# string given as the value.
|
||||
# * <tt>:subject</tt> - Preset the subject line of the email.
|
||||
# * <tt>:body</tt> - Preset the body of the email.
|
||||
# * <tt>:cc</tt> - Carbon Copy addition recipients on the email.
|
||||
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
|
||||
#
|
||||
# ==== Examples
|
||||
# mail_to "me@domain.com"
|
||||
# # => <a href="mailto:me@domain.com">me@domain.com</a>
|
||||
#
|
||||
# mail_to "me@domain.com", "My email", :encode => "javascript"
|
||||
# # => <script type="text/javascript">eval(unescape('%64%6f%63...%6d%65%6e'))</script>
|
||||
#
|
||||
# mail_to "me@domain.com", "My email", :encode => "hex"
|
||||
# # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
|
||||
#
|
||||
# mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email"
|
||||
# # => <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a>
|
||||
#
|
||||
# mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com",
|
||||
# :subject => "This is an example email"
|
||||
# # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
|
||||
def mail_to(email_address, name = nil, html_options = {})
|
||||
html_options = html_options.stringify_keys
|
||||
encode = html_options.delete("encode").to_s
|
||||
cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body")
|
||||
|
||||
string = ''
|
||||
extras = ''
|
||||
extras << "cc=#{CGI.escape(cc).gsub("+", "%20")}&" unless cc.nil?
|
||||
extras << "bcc=#{CGI.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
|
||||
extras << "body=#{CGI.escape(body).gsub("+", "%20")}&" unless body.nil?
|
||||
extras << "subject=#{CGI.escape(subject).gsub("+", "%20")}&" unless subject.nil?
|
||||
extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty?
|
||||
|
||||
email_address = email_address.to_s
|
||||
|
||||
email_address_obfuscated = email_address.dup
|
||||
email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at")
|
||||
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
|
||||
|
||||
if encode == "javascript"
|
||||
tmp = "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');"
|
||||
for i in 0...tmp.length
|
||||
string << sprintf("%%%x",tmp[i])
|
||||
end
|
||||
"<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>"
|
||||
elsif encode == "hex"
|
||||
email_address_encoded = ''
|
||||
email_address_obfuscated.each_byte do |c|
|
||||
email_address_encoded << sprintf("&#%d;", c)
|
||||
end
|
||||
|
||||
protocol = 'mailto:'
|
||||
protocol.each_byte { |c| string << sprintf("&#%d;", c) }
|
||||
|
||||
for i in 0...email_address.length
|
||||
if email_address[i,1] =~ /\w/
|
||||
string << sprintf("%%%x",email_address[i])
|
||||
else
|
||||
string << email_address[i,1]
|
||||
end
|
||||
end
|
||||
content_tag "a", name || email_address_encoded, html_options.merge({ "href" => "#{string}#{extras}" })
|
||||
else
|
||||
content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" })
|
||||
end
|
||||
end
|
||||
|
||||
# True if the current request URI was generated by the given +options+.
|
||||
#
|
||||
# ==== Examples
|
||||
# Let's say we're in the <tt>/shop/checkout</tt> action.
|
||||
#
|
||||
# current_page?(:action => 'process')
|
||||
# # => false
|
||||
#
|
||||
# current_page?(:controller => 'shop', :action => 'checkout')
|
||||
# # => true
|
||||
#
|
||||
# current_page?(:action => 'checkout')
|
||||
# # => true
|
||||
#
|
||||
# current_page?(:controller => 'library', :action => 'checkout')
|
||||
# # => false
|
||||
def current_page?(options)
|
||||
url_string = CGI.escapeHTML(url_for(options))
|
||||
request = @controller.request
|
||||
if url_string =~ /^\w+:\/\//
|
||||
url_string == "#{request.protocol}#{request.host_with_port}#{request.request_uri}"
|
||||
else
|
||||
url_string == request.request_uri
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def convert_options_to_javascript!(html_options, url = '')
|
||||
confirm, popup = html_options.delete("confirm"), html_options.delete("popup")
|
||||
|
||||
method, href = html_options.delete("method"), html_options['href']
|
||||
|
||||
html_options["onclick"] = case
|
||||
when popup && method
|
||||
raise ActionView::ActionViewError, "You can't use :popup and :method in the same link"
|
||||
when confirm && popup
|
||||
"if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;"
|
||||
when confirm && method
|
||||
"if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method)} };return false;"
|
||||
when confirm
|
||||
"return #{confirm_javascript_function(confirm)};"
|
||||
when method
|
||||
"#{method_javascript_function(method, url, href)}return false;"
|
||||
when popup
|
||||
popup_javascript_function(popup) + 'return false;'
|
||||
else
|
||||
html_options["onclick"]
|
||||
end
|
||||
end
|
||||
|
||||
def confirm_javascript_function(confirm)
|
||||
"confirm('#{escape_javascript(confirm)}')"
|
||||
end
|
||||
|
||||
def popup_javascript_function(popup)
|
||||
popup.is_a?(Array) ? "window.open(this.href,'#{popup.first}','#{popup.last}');" : "window.open(this.href);"
|
||||
end
|
||||
|
||||
def method_javascript_function(method, url = '', href = nil)
|
||||
action = (href && url.size > 0) ? "'#{url}'" : 'this.href'
|
||||
submit_function =
|
||||
"var f = document.createElement('form'); f.style.display = 'none'; " +
|
||||
"this.parentNode.appendChild(f); f.method = 'POST'; f.action = #{action};"
|
||||
|
||||
unless method == :post
|
||||
submit_function << "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); "
|
||||
submit_function << "m.setAttribute('name', '_method'); m.setAttribute('value', '#{method}'); f.appendChild(m);"
|
||||
end
|
||||
|
||||
if protect_against_forgery?
|
||||
submit_function << "var s = document.createElement('input'); s.setAttribute('type', 'hidden'); "
|
||||
submit_function << "s.setAttribute('name', '#{request_forgery_protection_token}'); s.setAttribute('value', '#{escape_javascript form_authenticity_token}'); f.appendChild(s);"
|
||||
end
|
||||
submit_function << "f.submit();"
|
||||
end
|
||||
|
||||
# Processes the _html_options_ hash, converting the boolean
|
||||
# attributes from true/false form into the form required by
|
||||
# HTML/XHTML. (An attribute is considered to be boolean if
|
||||
# its name is listed in the given _bool_attrs_ array.)
|
||||
#
|
||||
# More specifically, for each boolean attribute in _html_options_
|
||||
# given as:
|
||||
#
|
||||
# "attr" => bool_value
|
||||
#
|
||||
# if the associated _bool_value_ evaluates to true, it is
|
||||
# replaced with the attribute's name; otherwise the attribute is
|
||||
# removed from the _html_options_ hash. (See the XHTML 1.0 spec,
|
||||
# section 4.5 "Attribute Minimization" for more:
|
||||
# http://www.w3.org/TR/xhtml1/#h-4.5)
|
||||
#
|
||||
# Returns the updated _html_options_ hash, which is also modified
|
||||
# in place.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# convert_boolean_attributes!( html_options,
|
||||
# %w( checked disabled readonly ) )
|
||||
def convert_boolean_attributes!(html_options, bool_attrs)
|
||||
bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) }
|
||||
html_options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue