require 'ramaze'
require 'ramaze/gestalt'
module Ramaze
module Helper
##
# The BlueForm helper tries to be an even better way to build forms
# programmatically. By using a simple block you can quickly create all the
# required elements for your form.
#
# See {Ramaze::Helper::BlueForm::Form} for all the available methods.
#
# ## Form Data
#
# As stated earlier it's possible to pass an object to the form_for()
# method. What kind of object this is, a database result object or an
# OpenStruct object doesn't matter as long as the attributes can be accessed
# outside of the object (this can be done using attr_readers). This makes it
# extremely easy to directly pass a result object from your favourite ORM.
# Example:
#
# @data = User[1]
#
# form_for(@data, :method => :post) do |f|
# f.input_text 'Username', :username
# end
#
# The object comes handy when you want to do server-side form validation:
# if the form can not be validated, just send back the object with keys
# containing what the user has filled. The fields will be populated with
# these values, so the user doesn't have to retype everything.
#
# If you don't want to use an object you can simply set the first parameter
# to nil.
#
# ## HTML Output
#
# The form helper uses Gestalt, Ramaze's custom HTML builder that works
# somewhat like Erector. The output is very minimalistic, elements such as
# legends and fieldsets have to be added manually.
#
# If you need to add elements not covered by Form methods (e.g. `
`
# tags), you can access the form Gestalt instance with the g() method and
# generate your tags like this :
#
# form_for(@result, :method => :post) do |f|
# f.g.div(:class => "awesome") do
# ...
# end
# end
#
# Each combination of a label and input element will be wrapped in
# `
` tags.
#
# When using the form helper as a block in your templates it's important to
# remember that the result is returned and not displayed in the browser
# directly. When using Etanni this would result in something like the
# following:
#
# #{form_for(@result, :method => :post) do |f|
# f.input_text 'Text label', :textname, 'Chunky bacon!'
# end}
#
# @example Creating a basic form
# form_for(@data, :method => :post) do |f|
# f.input_text 'Username', :username
# end
#
# @example Adding custom elements inside a form
# form_for(@result, :method => :post) do |f|
# f.fieldset do
# f.g.div(:class => "control-group") do
# f.input_text 'Text label', :textname, { :placeholder => 'Chunky bacon!',
# :class => :bigsize }
# end
# end
# end
#
module BlueForm
##
# The form method generates the basic structure of the form. It should be
# called using a block and it's return value should be manually sent to
# the browser (since it does not echo the value).
#
# @param [Object] form_values Object containing the values for each form
# field.
# @param [Hash] options Hash containing any additional form attributes
# such as the method, action, enctype and so on.
# @param [Block] block Block containing the elements of the form such as
# password fields, textareas and so on.
#
def form_for(form_values, options = {}, &block)
form = Form.new(form_values, options)
form.build(form_errors, &block)
form
end
##
# Manually add a new error to the form_errors key in the flash hash. The
# first parameter is the name of the form field and the second parameter
# is the custom message.
#
# @param [String] name The name of the form field to which the error
# belongs.
# @param [String] message The custom error message to show.
#
def form_error(name, message)
if respond_to?(:flash)
old = flash[:form_errors] || {}
flash[:form_errors] = old.merge(name.to_s => message.to_s)
else
form_errors[name.to_s] = message.to_s
end
end
##
# Returns the hash containing all existing errors and allows other methods
# to set new errors by using this method as if it were a hash.
#
# @return [Array] All form errors.
#
def form_errors
if respond_to?(:flash)
flash[:form_errors] ||= {}
else
@form_errors ||= {}
end
end
##
# Retrieve all the form errors for the specified model and add them to the
# flash hash.
#
# @param [Object] obj An object of a model that contains form errors.
#
def form_errors_from_model(obj)
if obj.respond_to?(:errors)
obj.errors.each do |key, value|
if value.respond_to?(:first)
value = value.first
end
form_error(key.to_s, value % key)
end
end
end
##
# Main form class that contains all the required methods to generate form
# specific tags, such as textareas and select boxes. Do note that this
# class is not thread-safe so you should modify it only within one thread
# of execution.
#
class Form
attr_reader :g
attr_reader :form_values
##
# Constructor method that generates an instance of the Form class.
#
# @param [Object] form_values Object containing the values for each form
# field.
# @param [Hash] options A hash containing any additional form attributes.
# @return [Object] An instance of the Form class.
#
def initialize(form_values, options)
@form_values = form_values
@form_args = options.dup
@g = Gestalt.new
end
##
# Builds the form by generating the opening/closing tags and executing
# the methods in the block.
#
# @param [Hash] form_errors Hash containing all form errors (if any).
#
def build(form_errors = {})
# Convert all the keys in form_errors to strings and
# retrieve the correct values in case
@form_errors = {}
form_errors.each do |key, value|
if value.respond_to?(:first)
value = value.first
end
@form_errors[key.to_s] = value
end
@g.form(@form_args) do
if block_given?
yield self
end
end
end
##
# Generate a `