require 'action_view'
require 'custom_helpers.rb'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_helper'
require 'action_view/helpers/javascript_helper'
# InPlaceControls - Do in-place-style editors with other form controls
module Flvorful
module SuperInplaceControls
include CustomHelpers
# =MAIN
# These controls were designed to be used with habtm & hm associations. Just use the object and the foreign key as the attribute (see examples). You can specify a chain of methods to use for the final display text (so you dont see the foreign key id)
#
# Controller methods. Call just like the prototype helper method in_place_edit_for
#
# =OPTIONS
# final_text - this is an array of methods calls or a special symbol to be executed after the update
# example:
# in_place_edit_for( :prospect, "status_id", { :final_text => ["status", "title"] })
# This will chain the methods together to display the final text for the update.
# The method call will look like: object.status.title
#
# special symbols
# collection - Use this as your final text if you want to use a checkbox collection on a HABTM or HM association. This will call a method stack similar to: object.collections.map { |e| e.title || e.name }.join(", ")
# example: in_place_edit_for :product, :category_ids, {:final_text => :collection }
#
# highlight_endcolor - the end color for the "highlight" visual effect.
# example: in_place_edit_for( :prospect, "status_id", { :final_text => ["status", "title"], highlight_endcolor => "'#ffffff'" })
#
# highlight_startcolor - the start color for the "highlight" visual effect.
# example: in_place_edit_for( :prospect, "status_id", { :final_text => ["status", "title"], :highlight_startcolor => "'#ffffff'" })
#
# error_messages - the error messages div that errors will be put into. defaults to error_messages
# example: in_place_edit_for( :prospect, "status_id", { :error_messages => "my_error_div", :highlight_startcolor => "'#ffffff'" })
#
# error_visual_effect - The visual effect to use for displaying your error_message div. Defaults to :slide_down.
# example: in_place_edit_for( :prospect, "status_id", { :error_visual_effect => :appear })
#
# =TODO
# final_text_operation - this is a helper method that you want to call once the final_text is generated
# example: in_place_edit_for( :prospect, "status_id", { :final_text => ["status", "title"] }, { :final_text_operation => ["truncate", 30] })
# This will call truncate on the final_text and pass in 30 as the argument.
# Everything after the first argument (the method to call) is sent as an argument to the method. The method call is similar to: truncate(, 30)
#
#
module ControllerMethods
def in_place_edit_for(object, attribute, options = {})
define_method("set_#{object}_#{attribute}") do
@item = object.to_s.camelize.constantize.find(params[:id])
id_string = "#{object}_#{attribute}_#{@item.id}"
field_id = "#{object}_#{attribute}"
error_messages = options[:error_messages] || "error_messages"
highlight_endcolor = options[:highlight_endcolor] || "#ffffff"
highlight_startcolor = options[:highlight_startcolor] || "#ffff99"
error_visual_effect = options[:error_visual_effect] || :slide_down
if @item.update_attributes(attribute => params[object][attribute])
unless options[:final_text].nil?
if options[:final_text] == :collection
final_text = @item.send(attribute.to_s.gsub("_ids", "").pluralize).map { |e| e.title || e.name }.join(", ")
else
methods = options[:final_text]
sum_of_methods = @item
methods.each do |meth|
sum_of_methods = sum_of_methods.send(meth)
end
final_text = sum_of_methods
end
else
final_text = @item.send(attribute).to_s
end
final_text = " " if final_text.blank?
render :update do |page|
page.replace_html "#{id_string}", final_text
page.hide "#{id_string}_form"
unless error_messages.nil? || !@error_div_on
page.hide error_messages
page.select("##{id_string}_form ##{field_id}").map { |e| e.remove_class_name "fieldWithError" }
end
page.show "#{id_string}"
page.visual_effect :highlight, "#{id_string}", :duration => 0.5, :endcolor => "#{highlight_endcolor}", :startcolor => "#{highlight_startcolor}"
end
else
unless error_messages.nil? || !@error_div_on
errors_html = "
Errors
" + "" + @item.errors.full_messages.map { |e| "- #{e}
" }.join("\n ") + "
"
render :update do |page|
page.select("##{id_string}_form ##{field_id}").map { |e| e.add_class_name "fieldWithError" }
page.show "#{id_string}_form"
#page[:error_messages].add_class_name "full_errors"
page.replace_html error_messages, errors_html
page.visual_effect error_visual_effect, error_messages
end
else
raise @item.inspect
end
end
end
end
end
# These methods are mixed into the view as helpers.
# Common options for the helpers:
# :action - the action to submit the change to
# :saving_text - text to show while the request is processing. Default is "Saving..."
# :object - the object to create the control for, if other than an instance variable. (Useful for iterations.)
# :display_text - the text to be display before the update. Used when you want to use an
# in_place control for an association field. Defaults to method
# :br - add a
tag after the field and before the submit tag. Defaults to
# false/nil
module HelperMethods
# Creates an "active" date select control that submits any changes to the server
# using an in_place_edit-style action.
# Extra Options:
# :time - Allows users to select the Time as well as the date. Defaults to false.
# By default the value of the object's attribute will be selected, or blank.
# Example:
# <%= in_place_date_select :product, :display_begin_date %>
def in_place_date_select(object, method, options = {})
options[:time] ||= false
in_place_field(:date_select, object, method, options)
end
# Creates an "active" select box control that submits any changes to the server
# using an in_place_edit-style action.
# Extra Options:
# :choices - (required) An array of choices (see method "select")
# By default the value of the object's attribute will be selected, or blank.
# Example:
# <%= in_place_select :employee, :manager_id, :choices => Manager.find_all.map { |e| [e.name, e.id] } %>
def in_place_select(object, method, options = {})
check_for_choices(options)
in_place_field(:select, object, method, options)
end
# Creates an "active" text field control that submits any changes to the server
# using an in_place_edit-style action.
# By default the value of the object's attribute will be filled in or blank.
# Example:
# <%= in_place_text_field :product, :title %>
def in_place_text_field(object, method, options = {})
in_place_field(:text_field, object, method, options)
end
# Creates an "active" text area control that submits any changes to the server
# using an in_place_edit-style action.
# By default the value of the object's attribute will be filled in or blank.
# Example:
# <%= in_place_text_area :product, :description %>
def in_place_text_area(object, method, options = {})
in_place_field(:text_area, object, method, options)
end
# Creates an "active" collection of checkbox controls that submits any changes to the server
# using an in_place_edit-style action.
# Extra Options:
# :choices - (required) An array of choices (see method "select")
# By default the value of the object's attribute will be selected, or blank.
# Example:
# <%= in_place_check_box_collection :product, :category_ids, :choices => Category.find(:all).map { |e| [e.id, e.title] } %>
def in_place_check_box_collection(object, method, options = {})
check_for_choices(options)
options[:display_text] = :collection
in_place_field(:check_box, object, method, options)
end
# Creates an "active" collection of radio controls that submits any changes to the server
# using an in_place_edit-style action.
# Extra Options:
# :choices - (required) An array of choices (see method "select")
# :columns - breaks up the collection into :columns number of columns
# By default the value of the object's attribute will be selected, or blank.
# Example:
# <%= in_place_radio_collection :product, :price, :choices => %w(199 299 399 499 599 699 799 899 999).map { |e| [e, e] } %>
def in_place_radio_collection(object, method, options = {})
check_for_choices(options)
in_place_field(:radio, object, method, options)
end
# Creates an div container for error_messages
def inplace_error_div(div_id = "error_messages", div_class = "error_messages")
@error_div_on = true
content_tag(:div, "", :id => div_id, :class => div_class, :style => "display:none")
end
protected
def check_for_choices(options)
raise ArgumentError, "Missing choices for select! Specify options[:choices] for in_place_select" if options[:choices].nil?
end
def in_place_field(field_type, object, method, options)
object_name = object.to_s
method_name = method.to_s
@object = self.instance_variable_get("@#{object}") || options[:object]
display_text = set_display_text(@object, method_name, options)
ret = html_for_inplace_display(object_name, method_name, @object, display_text)
ret << form_for_inplace_display(object_name, method_name, field_type, @object, options)
end
def set_blank_text(text, number_of_spaces = 7)
blank = ""
number_of_spaces.times { |e| blank += " " }
text = blank if text.blank?
text
end
def set_display_text(object, attribute, options)
display_text = ""
#raise options.inspect + "|" + object.to_s + "|" + attribute.to_s
if options[:display_text] == :collection
display_text = object.send(attribute.to_s.gsub("_ids", "").pluralize).map { |e| e.title || e.name }.join(", ")
else
display_text = options[:display_text] || object.send(attribute)
end
#display_text = set_blank_text(display_text)
h display_text
end
def id_string_for(object_name, method_name, object)
"#{object_name}_#{method_name}_#{object.id}"
end
def html_for_inplace_display(object_name, method_name, object, display_text)
id_string = id_string_for(object_name, method_name, object)
content_tag(:span, display_text,
:onclick => update_page do |page|
page.hide "#{id_string}"
page.show "#{id_string }_form"
end,
:onmouseover => visual_effect(:highlight, id_string),
:title => "Click to Edit",
:id => id_string ,
:class => "inplace_span #{"empty_inplace" if display_text.blank?}"
)
end
def form_for_inplace_display(object_name, method_name, input_type, object, opts)
retval = ""
id_string = id_string_for(object_name, method_name, object)
set_method = opts[:action] || "set_#{object_name}_#{method_name}"
save_button_text = opts[:save_button_text] || "OK"
loader_message = opts[:saving_text] || "Saving..."
retval << form_remote_tag(:url => { :action => set_method, :id => object.id },
:method => opts[:http_method] || :post,
:loading => update_page do |page|
page.show "loader_#{id_string}"
page.hide "#{id_string}_form"
end,
:complete => update_page do |page|
page.hide "loader_#{id_string}"
end,
:html => {:class => "in_place_editor_form", :id => "#{id_string}_form", :style => "display:none" } )
retval << field_for_inplace_editing(object_name, method_name, object, opts, input_type )
retval << content_tag(:br) if opts[:br]
retval << submit_tag( save_button_text, :class => "inplace_submit")
retval << link_to_function( "Cancel", update_page do |page|
page.show "#{id_string}"
page.hide "#{id_string}_form"
end, {:class => "inplace_cancel" })
retval << ""
retval << invisible_loader( loader_message, "loader_#{id_string}", "inplace_loader")
retval << content_tag(:br)
end
def field_for_inplace_editing(object_name, method_name, object, options , input_type)
options[:class] = "inplace_#{input_type}"
htm_opts = {:class => options[:class] }
case input_type
when :text_field
text_field(object_name, method_name, options )
when :text_area
text_area(object_name, method_name, options )
when :select
select(object_name, method_name, options[:choices], options, htm_opts )
when :check_box
options[:label_class] = "inplace_#{input_type}_label"
checkbox_collection(object_name, method_name, object, options[:choices], options )
when :radio
options[:label_class] = "inplace_#{input_type}_label"
radio_collection(object_name, method_name, object, options[:choices], options )
when :date_select
calendar_date_select( object_name, method_name, options)
end
end
end
end
end