#--
# *** This code is copyright 2004 by Gavin Kistner
# *** It is covered under the license viewable at http://phrogz.net/JS/_ReuseLicense.txt
# *** Reuse or modification is free provided you abide by the terms of that license.
# *** (Including the first two lines above in your source code usually satisfies the conditions.)
#++
# Implements various <tt>to_html</tt> methods for classes in the ValidForm library, producing valid HTML.
# Also outputs necessary custom attributes to support client-side validation using FormAutoValidate[http://phrogz.net/tmp/FormAutoValidate/formautovalidate_docs.html]
#
# Relies on basiclibrary.rb for Hash#swapkey! and Time#custom_format
#
# Author::     Gavin Kistner  (mailto:!@phrogz.net)
# Copyright::  Copyright (c)2004 Gavin Kistner
# License::    See http://Phrogz.net/JS/_ReuseLicense.txt for details
# Version::    1.1
# Full Code::  link:../ValidForm_html.rb

require 'cgi'
require 'ValidForm'

class ValidForm; end # fixes an RDoc error where it lists classes twice

##########################################################################################
# A wrapper module for the #hashes_toattributes method.
##########################################################################################
module ValidForm::XMLAttributes

	# Takes one or more hashes as arguments.
	# Returns a string of space-delimited <tt>_key_="_value_"</tt> pairs, where _value_ has had HTML entities escaped.
	# 
	# Keys and Values which are +nil+ or an empty string are not included in the output.
	#
	#   attributes = { :style=>'font-weight:bold; color:orange', :size=>12 }
	#   hashes_toattributes( {'id'=>'foo', :name=>'bar'},attributes )
	#    => ' id="foo" name="bar" size="12" style="font-weight:bold; color:orange"'
	def hashes_toattributes(*hashes)
		html=""
		hashes.each{ |hash|
			hash.each{ |key,val|
				next unless key && key!='' && val!=nil && val!=''
				keyS = key.to_s
				valS = val.to_s
				raise "id '#{valS}' is not a legal XML identifier." if keyS.downcase=='id' && !/^[a-z_]\w*$/.match(valS)
				html << %{ #{keyS}="#{CGI.escapeHTML(valS)}"}
			}
		}
		html
	end
end




module ValidForm::Container

	def to_html_withwrapper(withValidation,indentLevel,preCode,endCode,block) #:nodoc:
		html = preCode
		if block
			if block.arity<1
				html << block.call
			else
				@fields.each{ |f|
					html << block.call(f)
				} if @fields && @fields.respond_to?(:each)
			end
		else
			@fields.each{ |f|
				html << f.to_html(withValidation,indentLevel+1)
			} if @fields && @fields.respond_to?(:each)
		end
		html << endCode
	end
	
	def field_tohtml_withwrapper(withValidation,indentLevel,preCode,endCode,block) #:nodoc:
		html = preCode
		if block
			if block.arity<1
				html << block.call
			else
				@fields.each{ |f|
					html << block.call(f)
				} if @fields && @fields.respond_to?(:each)
			end
		else
			@fields.each{ |f|
				html << f.field_tohtml(withValidation,indentLevel+1)
			} if @fields && @fields.respond_to?(:each)
		end
		html << endCode
	end

end




class ValidForm
	include ValidForm::XMLAttributes

	# _withValidation_:: Boolean value to control whether the custom attributes for client-side validation (using FormAutoValidate[http://phrogz.net/tmp/FormAutoValidate/formautovalidate_docs.html]) are included or not.
	# _indentLevel_:: Number of tabs to indent the HTML code (for source code readability).
	#
	# If a block is supplied that takes no arguments, it will be called once and should yield a string result to be placed between the <tt><form></tt>...<tt></form></tt> tags.
	#
	# If a block is supplied that takes arguments, it will be called for each immediate-child field in the form, and the string result returned from the block put between the tags. Equivalent to:
	#   myForm.to_html(...){
	#   	html=''
	#   	myForm.fields.each{ |f|
	#   		html << #...
	#   	}
	#   	html
	#   }
	#
	# With no block passed, returns an HTML representation of the form, including all fieldsets and their fields. Equivalent to:
	#   myForm.to_html(...){ |f|
	#   	f.to_html
	#   }
	# ...except that the calls to f.to_html will have passed along the values of _withValidation_ and _indentLevel_+1, properly indenting them.
	def to_html(withValidation=true,indentLevel=0,&block)
		tabs = "\t"*indentLevel
		@attributes[:autovalidate]="true" if withValidation

		preCode = tabs + %{<form#{hashes_toattributes({:method=>@method,:action=>@action},@attributes)}>\n}
		endCode = tabs << "</form>\n"

		to_html_withwrapper(withValidation,indentLevel,preCode,endCode,block)
	end
	
	# Populates the form with values passed to the page (using +CGI+) based on the +name+ (or failing that +id+) attribute of each field.
	# After populating the form, calls #validate and returns the value from it.
	def populate_and_validate
		puts '<html>'
		$cgi = CGI.new
		self.everyField(true).each{	|f|
			next if f.is_a?(ValidForm::Option) || !f.respond_to?(:value=)
			vals = $cgi[f.name || f.id].to_a
			f.value = (vals.empty? ? nil : (vals.length==1 ? vals[0] : vals)) if vals
		}
		self.validate
	end
end









class ValidForm::Fieldset
	include ValidForm::XMLAttributes

	# _withValidation_:: Boolean value to control whether the custom attributes for client-side validation (using FormAutoValidate[http://phrogz.net/tmp/FormAutoValidate/formautovalidate_docs.html]) are included or not.
	# _indentLevel_:: Number of tabs to indent the HTML code (for source code readability).
	#
	# See ValidForm#to_html for a description on how to use (or ignore) the <i>&block</i> parameter.
	def to_html(withValidation=true,indentLevel=0,&block)
		tabs = "\t"*indentLevel

		preCode = tabs + %{<fieldset#{hashes_toattributes({:id=>@id},@attributes)}>\n}
		preCode << tabs + %{\t<legend>#@label</legend>\n\n} if @label
		endCode = tabs << "</fieldset>\n"

		to_html_withwrapper(withValidation,indentLevel,preCode,endCode,block)
	end
	
	# Loads values passed to the page from CGI into form fields based on their name attributes, and then invokes #val
end









class ValidForm::Field
	include ValidForm::XMLAttributes
	
	# Code to put before the output of each #field_tohtml call
	FIELD_WRAPPER_START = '<span class="field">'

	# Code to put after the output of each #field_tohtml call
	FIELD_WRAPPER_CLOSE = '</span>'

	# _withValidation_:: Boolean value to control whether the custom attributes for client-side validation (using FormAutoValidate[http://phrogz.net/tmp/FormAutoValidate/formautovalidate_docs.html]) are included or not.
	# _indentLevel_:: Number of tabs to indent the HTML code (for source code readability).
	#
	# Returns an HTML representation of the field, including a label.
	#
	# See also #label_tohtml and #field_tohtml.
	def to_html(withValidation=true,indentLevel=0)
		label_tohtml(indentLevel) << field_tohtml(withValidation,indentLevel) << "\n"
	end

	# _indentLevel_:: Number of tabs to indent the HTML code (for source code readability).
	#
	# Returns an HTML representation of the label for the field.
	def label_tohtml(indentLevel=0)
		htmlClass=[]
		htmlClass << 'reqd' if @validationRules[:required]
		htmlClass << 'failedvalidation' if @errors
		@label ? ("\t"*indentLevel << %{<label id="lbl_#@id" for="#@id"#{" class=\"#{htmlClass.join(' ')}\"" unless htmlClass.empty?}>#@label</label>\n}) : ''
	end

	# _withValidation_:: Boolean value to control whether the custom attributes for client-side validation (using FormAutoValidate[http://phrogz.net/tmp/FormAutoValidate/formautovalidate_docs.html]) are included or not.
	# _indentLevel_:: Number of tabs to indent the HTML code (for source code readability).
	#
	# Returns an HTML representation of the field itself. All subclasses of ::Field override this with their own correct implementation.
	def field_tohtml(withValidation=true,indentLevel=0)
		''
	end

	def validation_tohtml(field=self) #:nodoc:
		rs = field.validationRules.dup
		if rs[:minvalue] || rs[:maxvalue]
			rs[:minvalue] = Time.parse(rs[:minvalue]) if rs[:minvalue].is_a?(String)
			rs[:maxvalue] = Time.parse(rs[:maxvalue]) if rs[:maxvalue].is_a?(String)
			case rs[:type]
				when :date
					rs[:minvalue] = rs[:minvalue].custom_format('#M#/#D#/#YYYY#') if rs[:minvalue]
					rs[:maxvalue] = rs[:maxvalue].custom_format('#M#/#D#/#YYYY#') if rs[:maxvalue]
				when :time
					rs[:minvalue] = rs[:minvalue].custom_format('#h#:#mm#:#ss##ampm#') if rs[:minvalue]
					rs[:maxvalue] = rs[:maxvalue].custom_format('#h#:#mm#:#ss##ampm#') if rs[:maxvalue]
				when :datetime
					rs[:minvalue] = rs[:minvalue].custom_format('#M#/#D#/#YYYY# #h#:#mm#:#ss##ampm#') if rs[:minvalue]
					rs[:maxvalue] = rs[:maxvalue].custom_format('#M#/#D#/#YYYY# #h#:#mm#:#ss##ampm#') if rs[:maxvalue]
			end
		end

		if rs[:match]
			re = rs[:match].to_s
			re.sub!(/\(\?([^:-]*)-?[^:]*:/,'')
			rs[:mustmatchcasesensitive]='true' unless /i/.match($1)
			rs[:match] = re.sub!(/\)$/,'')
		end
		if rs[:nomatch]
			re = rs[:nomatch].to_s
			re.sub!(/\(\?([^:-]*)-?[^:]*:/,'')
			rs[:mustmatchcasesensitive]='true' unless /i/.match($1)
			rs[:nomatch] = re.sub!(/\)$/,'')
		end
		
		rs.swapkey!('validateas',:type).swapkey!('mustmatch',:match).swapkey!('mustnotmatch',:nomatch).swapkey!('mustmatchmessage',:typefail_msg)
		rs.merge!({:nicename=>field.label})
		hashes_toattributes(rs)
	end
	
	def errors_tohtml
		html=''
		@errors.each{ |e|
			html << '<br><span class="errormessage">' << e.msg + "</span>\n"
		} if @errors
		html
	end
end



class ValidForm::Text
	# Returns an HTML representation of the field itself. See also ::Field#to_html.
	# If +name+ is +nil+, +id+ is used for the name attribute.
	#   email = ValidForm::Text.new('email',nil,'Hello World')
	#   email.multiline    => false
	#   email.field_tohtml => '<span class="field"><input type="text" name="email" id="email" value="Hello World"></span>'
	#
	#   email.multiline = true
	#   email.field_tohtml => '<span class="field"><textarea name="email" id="email">Hello World</textarea></span>'
	def field_tohtml(withValidation=true,indentLevel=0)
		html = "\t"*indentLevel << FIELD_WRAPPER_START
		if @multiline then
			html << %{<textarea#{hashes_toattributes({:id=>@id,:name=>@name || @id},@attributes)}#{validation_tohtml if withValidation}>#@value</textarea>}
		else
			html << %{<input type="text"#{hashes_toattributes({:id=>@id,:name=>@name || @id,:value=>@value},@attributes)}#{validation_tohtml if withValidation}>}
		end
		html << errors_tohtml
		html << FIELD_WRAPPER_CLOSE << "\n"
	end
end



class ValidForm::Password
	# Returns an HTML representation of the field itself. See also ::Field#to_html.
	def field_tohtml(withValidation=true,indentLevel=0)
		super.gsub('type="text"','type="password"')
	end
end



class ValidForm::Hidden
	# Returns an HTML representation of the field itself. See also ::Field#to_html.
	def field_tohtml(withValidation=true,indentLevel=0)
		super.gsub('type="text"','type="hidden"')
	end
end



class ValidForm::Submit
	def to_html(*params) #:nodoc:
		field_tohtml(*params)
	end

	# Returns an HTML representation of the field itself. See also ::Field#to_html.
	def field_tohtml(withValidation=true,indentLevel=0)
		"\t"*indentLevel <<
			FIELD_WRAPPER_START <<
			%{<button type="submit"#{hashes_toattributes({:id=>@id,:name=>@name,:value=>@value},@attributes)}>#@label</button>} <<
			FIELD_WRAPPER_CLOSE << "\n"
	end
end



class ValidForm::Reset
	def to_html(*params) #:nodoc:
		field_tohtml(*params)
	end

	# Returns an HTML representation of the field itself. See also ::Field#to_html.
	def field_tohtml(withValidation=true,indentLevel=0)
		"\t"*indentLevel <<
			FIELD_WRAPPER_START <<
			%{<button type="reset"#{hashes_toattributes({:id=>@id,:name=>@name,:value=>@value},@attributes)}>#@label</button>} <<
			FIELD_WRAPPER_CLOSE << "\n"
	end
end



class ValidForm::Option
	# Returns an HTML representation of the field itself. See also ::Field#to_html.
	def field_tohtml(withValidation=true,indentLevel=0)
		tabs="\t"*indentLevel
		case @optionSet
			when ValidForm::CheckboxSet
				tabs +
					%{<input type="checkbox"#{hashes_toattributes({:id=>@id,:name=>@optionSet.name || @optionSet.id,:value=>@value || @id},@attributes)}#{' checked' if @chosen}#{validation_tohtml(@optionSet) if withValidation}> } <<
					label_tohtml << "<br>\n"

			when ValidForm::RadioSet
				tabs +
					%{<input type="radio"#{hashes_toattributes({:id=>@id,:name=>@optionSet.name || @optionSet.id,:value=>@value || @id},@attributes)}#{' checked' if @chosen}#{validation_tohtml(@optionSet) if withValidation}> } <<
					label_tohtml << "<br>\n"

			when ValidForm::OptionSet
				tabs << %{<option#{hashes_toattributes({:id=>@id,:value=>@value || @id},@attributes)}#{' selected' if @chosen}>#{@label || @value || @id}</option>\n}

			else
				raise StandardError,'How did you get an option into a non-OptionSet?'

		end
	end
end













class ValidForm::OptionSet

	# Returns an HTML representation of the field itself.
	# See also ::Field#to_html and ::CheckboxSet#field_tohtml and ::Radioset#field_tohtml.
	#
	# See ValidForm#to_html for a description on how to use (or ignore) the <i>&block</i> parameter.
	def field_tohtml(withValidation=true,indentLevel=0,&block)
		tabs = "\t"*indentLevel
		preCode = tabs + FIELD_WRAPPER_START << %{<select#{' multiple' if @chooseMultiple}#{hashes_toattributes({:id=>@id,:name=>@name || @id},@attributes)}#{validation_tohtml if withValidation}>\n}
		endCode = tabs << '</select>' << errors_tohtml << FIELD_WRAPPER_CLOSE << "\n"

		field_tohtml_withwrapper(withValidation,indentLevel,preCode,endCode,block)
	end
	
	alias_method :straight_values,:values
	# Returns an Array of the value of every option in the set that is chosen.
	#
	# If no option in the set is chosen, a zero-length Array is returned; if only one option is chosen, a single-element Array is returned. See also #value.
	#
	# All values are returned as strings (when <tt>ValidForm_html</tt> is included).
	def values
		self.straight_values.collect!{ |v| v.to_s }
	end


	alias_method :straight_value=,:value=
	# _optionValue_:: May be a single value, a comma-delimited String of values, or an Array of values.
	# Chooses the option(s) whose value is in _optionValue_; unchooses all other options in the set.
	#
	# All values are converted to strings and compared as strings (when <tt>ValidForm_html</tt> is included).
	def value=( optionValue )
		if optionValue.is_a? Array
			optionValue.collect!{ |v| v.to_s }
		else
			optionValue=optionValue.to_s.split(',')
		end
		self.straight_value=optionValue
	end

	protected :straight_values, :straight_value=


end


class ValidForm::CheckboxSet
	# Returns an HTML representation of the set, including all child checkboxes (if no block is passed). See also ::Field#to_html and ::OptionSet#field_tohtml.
	def field_tohtml(withValidation=true,indentLevel=0,&block)
		tabs = "\t"*indentLevel
		preCode = tabs + FIELD_WRAPPER_START + "\n"
		endCode = tabs + errors_tohtml + tabs + FIELD_WRAPPER_CLOSE + "\n"

		field_tohtml_withwrapper(withValidation,indentLevel,preCode,endCode,block)
	end
end


class ValidForm::RadioSet
	# Returns an HTML representation of the set, including all child radio buttons (if no block is passed). See also ::Field#to_html and ::OptionSet#field_tohtml.
	def field_tohtml(withValidation=true,indentLevel=0,&block)
		tabs = "\t"*indentLevel
		preCode = tabs + FIELD_WRAPPER_START + "\n"
		endCode = tabs + errors_tohtml + tabs + FIELD_WRAPPER_CLOSE + "\n"

		field_tohtml_withwrapper(withValidation,indentLevel,preCode,endCode,block)
	end
end

