lib/ramaze/helper/form.rb in Pistos-ramaze-2009.02 vs lib/ramaze/helper/form.rb in Pistos-ramaze-2009.04.08

- old
+ new

@@ -1,284 +1,115 @@ +require 'ramaze/gestalt' + module Ramaze module Helper module Form - # Pass it an object for your ORM and options for the <form> tag - # Usage: - # form_for(User, :action => '/create') - # form_for(Tag, :action => '/find', :method => 'GET') - def form_for(object, options = {}) - Ramaze::Form.pick(object, options) + def form_text(label, name, value = nil) + form_input(label, :type => :text, :name => name, :value => value) end - end - end - class Form - attr_accessor :object, :options + def form_checkbox(label, name, checked = false) + hash = {:type => :checkbox, :name => name} + hash[:checked] = 'checked' if checked + form_input(label, hash) + end - YEARS, MONTHS, DAYS, HOURS, MINUTES, SECONDS = - (1900..2100), (1..12), (1..31), (0..23), (0..59), (0..59) + def form_password(label, name) + form_input(label, :type => :password, :name => name) + end - DATE_GENERIC = [ - [ :day, DAYS ], - [ :month, MONTHS ], - [ :year, YEARS ] ] + def form_textarea(label, name, value = nil) + form_build(:textarea, label, :name => name){ value } + end - TIME_GENERIC = [ - [ :day, DAYS ], - [ :month, MONTHS ], - [ :year, YEARS ], - [ :hour, HOURS ], - [ :min, MINUTES ], - [ :sec, SECONDS ] ] + def form_file(label, name) + form_input(label, :type => :file, :name => name) + end - # TODO: - # How _elegant_ ... - # Tries to find the right module for extending the Form instance. - # It's problematic since the boundaries of what an model instance or model - # class looks like is very fuzzy, also a problem is that the ORM may not be - # available/required. - # - # Maybe we can abstract that a bit by going through an array of procs for - # testing? - def self.pick(object, options = {}) - if defined?(Sequel::Model) - if object.is_a?(Sequel::Model) - options[:layer] ||= Layer::Sequel - InstanceForm.new(object, options) - elsif object.ancestors.include?(Sequel::Model) - options[:layer] ||= Layer::Sequel - ClassForm.new(object, options) - end - else - raise "Unknown ORM for: %p" % object + def form_hidden(name, value = nil) + Ramaze::Gestalt.build{ input(:type => :hidden, :name => name, :value => value) } end - end - # Create new instance of Form plus the layer for the ORM - def initialize(object, options = {}) - @object, @options = object, options - if layer = options.delete(:layer) - extend layer + def form_submit(value = nil) + hash = {:type => :submit}.merge(form_tabindex) + hash[:value] = value if value + Ramaze::Gestalt.build{ tr{ td(:colspan => 2){ input(hash) }}} end - end - # Generate and return the final form - def to_s - out = "<form #{form_attributes}>" - out << "<fieldset>" - out << generate - out << "</fieldset>" - out << '<input type="submit" />' - out << '<input type="reset" />' - out << "</form>" - end + def form_select(label, name, values, hash = {}) + name = name.to_sym + id = "form-#{name}" - # Decide on the strucuture of the tag based on the hash - def field_for(hash) - return if hash[:primary_key] - args = args_for(hash) + s_args = {:name => name, :id => id}.merge(form_tabindex) + s_args[:multiple] = :multiple if hash[:multiple] + s_args[:size] = hash[:size] || 1 - inner = - case type = hash[:type] - when :integer - field_integer(*args) - when :boolean - field_boolean(*args) - when :text - field_textarea(*args) - when :varchar - field_input(*args) - when :date - field_date(*args) - when :time - field_time(*args) - else - Log.warn "Unknown field: %p" % hash - field_input(*args) + has_selected, selected = hash.key?(:selected), hash[:selected] + error = form_errors[name.to_s] + + g = Ramaze::Gestalt.new + g.tr do + g.td do + g.label(:for => id){ "#{label}:" } + g.span(:class => 'error'){ error } if error + end + g.td do + g.select(s_args) do + values.each do |key, value| + value ||= key + o_args = {:value => value} + o_args[:selected] = :selected if has_selected and value == selected + g.option(o_args){ key } + end + end + end end - "<label>#{args.first}: </label>\n#{inner}" - end + g.to_s + end - private + def form_input(label, hash) + form_build(:input, label, hash) + end - # inject to attributes for the <form> - def form_attributes - options.inject([]){|s,(k,v)| s << "#{k}='#{v}'" }.join(' ') - end + def form_build(tag_name, label, hash, &block) + name = hash[:name].to_sym + form_id = "form-#{name}" + opts = hash.merge(form_tabindex.merge(:id => form_id)) + error = form_errors[name.to_s] - # Start tag with name and attributes - def start_tag(name, hash) - hash.inject("<#{name}"){|s,(k,v)| s << " #{k}='#{v}'" } - end - - # Make a closed tag with name and attributes - def closed_tag(name, hash) - start_tag(name, hash) << ' />' - end - - # Textarea with attributes from hash and the value from @object - def textarea(value, hash = {}) - start_tag(:textarea, hash) << ">#{value}</textarea>" - end - - # <input> with optional attributes from hash - def input(hash = {}) - closed_tag(:input, hash) - end - - # <input type="checkbox" with optional attributes from hash. - def checkbox(hash = {}) - hash[:type] = :checkbox - input(hash) - end - - # <option value="value"> with optional attributes from hash - def option(value, hash = {}) - start_tag(:option, hash) << ">#{value}</option>" - end - - # Yield method names and values for the Date instance - def field_date_generic - DATE_GENERIC.map{|(sel, range)| - yield(sel, range).join - }.join("\n") - end - - # Yield method names and values for the Time/DateTime instance - def field_time_generic - TIME_GENERIC.map{|(sel, range)| - yield(sel, range).join - }.join("\n") - end - - # Here go all the layers that are extended for specific ORMs - module Layer - # Layer for Sequel, only generate needs to be implemented, may change in - # future if we abstract more for different ORMs - module Sequel - # A bit nasty, get the @columns of the object and generate its - # field_for - def generate - columns = object_class.schema.instance_variable_get('@columns') - columns.map{|hash| field_for(hash) }.flatten.join("<br />\n") + Ramaze::Gestalt.build do + tr do + td do + label(:for => form_id){ "#{label}:" } + span(:class => "error"){ error } if error + end + td do + tag(tag_name, opts, &block) + end + end end end - end - end - # Form for the model class itself, very similar to an empty instance. - class ClassForm < Form - # <input name="name" /> - def field_input(name) - input :name => name - end + def form_tabindex + @tabindex ||= 0 + @tabindex += 1 - # <textarea name="name"></textarea> - def field_textarea(name) - textarea '', :name => name - end - - # <input name="name" /> - def field_integer(name) - input :name => name - end - - # <input type="checkbox" name="name" /> - def field_boolean(name) - checkbox :name => name - end - - # <select> with lots of <option>s - def field_date(name) - field_date_generic{|sel, range| - [ "<select name='#{name}[#{sel}]'>", - range.map{|d| option(d, :value => d) }, - "</select>" ] - } - end - - # <select> with lots of <option>s - def field_time(name) - field_time_generic{|sel, range| - [ "<select name='#{name}[#{sel}]'>", - range.map{|d| option(d, :value => d) }, - "</select>" ] - } - end - - # picks the :name - def args_for(hash) - [ hash[:name] ] - end - - # Should be that way, at least for Sequel - def object_class - @object - end - end - - # Form for instances of the model class - class InstanceForm < Form - # returns <input type='text' name='name' value='value' /> - def field_input(name, value) - "<input type='text' name='#{name}' value='#{value}'/>" - end - - # returns <textarea name='name'>#{value}</textarea> - - def field_textarea(name, value) - "<textarea name='#{name}'>#{value}</textarea>" - end - - # returns <input type="text" name="name" value="value" /> - - def field_integer(name, value) - field_input(name, value) - end - - # <input type="checkbox" ... - def field_boolean(name, value) - if value - checkbox :name => name, :value => value, :checked => :checked - else - checkbox :name => name, :value => value + {:tabindex => @tabindex} end - end - def field_date(name, value) - field_date_generic do |sel, range| - [ "<select name='#{name}[#{sel}]'>", - option_range_selected(range, value.send(sel)), - "</select>" ] + def form_error(name, message) + form_errors[name.to_s] = message.to_s end - end - def field_time(name, value) - field_time_generic do |sel, range| - [ "<select name='#{name}[#{sel}]'>", - option_range_selected(range, value.send(sel)), - "</select>" ] + def form_errors + @form_errors ||= {} end - end - def option_range_selected(range, value) - range.map do |r| - if r == value - option(r, :value => r, :selected => :selected) - else - option(r, :value => r) + def form_errors_from_model(obj) + obj.errors.each do |key, value| + form_error(key, value.first % key) end end - end - - def args_for(hash) - name = hash[:name] - [ name, @object.send(name) ] - end - - # Class for @object, atm Sequel specific? - def object_class - @object.class end end end