Permalink
Browse files

Merge pull request #3 from whitequark/master

Added ActiveRecord and improved styling hooks
  • Loading branch information...
activestylus committed Sep 8, 2013
2 parents 8376a5d + 825610e commit 5c1c269a5df9cd9af990e7d023a174640ce6c812
View
@@ -2,7 +2,10 @@
Forms made easy for the **[Padrino Framework](http://www.padrinorb.com)**
-Takes the pain out of building forms. Generates inputs, labels and field hints with a minimal and flexible DSL. Currently only supports **[DataMapper](http://datamapper.org/)**, but plugging more ORMs in will be trivial due to modular design (See below)
+Takes the pain out of building forms. Generates inputs, labels and field hints with a minimal and flexible DSL.
+Currently only supports **[DataMapper](http://datamapper.org/)** and
+**[ActiveRecord](http://api.rubyonrails.org/classes/ActiveRecord/Base.html)**, but plugging more ORMs in will
+be trivial due to modular design (see below).
## Installation
@@ -30,7 +33,8 @@ The heart of **PadrinoFields** is the **:input** method
= f.input :password
= f.submit
-This will generate a form with labels for username and password - supplying the appropriate inputs, labels and error messages on missing/invalid fields. **PadrinoFields** looks at your database columns to generate default inputs.
+This will generate a form with labels for username and password - supplying the appropriate inputs, labels and error
+messages on missing/invalid fields. **PadrinoFields** looks at your database columns to generate default inputs.
### Field Customization
@@ -59,15 +63,16 @@ Pass html attribute straight to the input:
### Options / Collections
-Options can be arrays or ranges, and when a **:options** is given the **:select** input will be rendered by default, so we don't need to pass the **:as => :select** option.
+Options can be arrays or ranges, and when a **:options** is given the **:select** input will be rendered by default,
+so we don't need to pass the **:as => :select** option.
= f.input :user, :options => User.all.map(&:name)
Use ranges as options for your select tags
= f.input :year, :options => (1950..Time.now.year)
-Options may also be rendered as **:radios** and **:checks**
+Options may also be rendered as **:radios** and **:checkboxes**
= f.input :user, :options => User.all.map(&:name), :as => :radios
@@ -104,12 +109,13 @@ Mapping | Input | Column Type
**:date** |date field |date, datetime, timestamps
**:select** |select |-
**:radios** |radio buttons |-
-**:checks** |check boxes |-
+**:checkboxes** |check boxes |-
### Validations
-By default all inputs are optional. **PadrinoFields** looks at your model validations to see if a field's presence is required and will mark it by prepending a * to the label.
+By default all inputs are optional. **PadrinoFields** looks at your model validations to see if a field's presence is
+required and will mark it by prepending a * to the label.
# app/models/person.rb
class Person
@@ -124,6 +130,9 @@ You can also do it manually with the **:required** option
= f.input :email, :required => true
+Note that if you use ActiveRecord, you will need to include `gem 'validation_reflection'` in your Gemfile
+in order for this feature to work.
+
### Settings
You can override a few default settings by creating a lib file as follows:
@@ -144,7 +153,8 @@ You can override a few default settings by creating a lib file as follows:
## ORM Support
-So far only Datamapper is supported, but since the code is decoupled it should be relatively easy to support others:
+So far only Datamapper and ActiveRecord are supported, but since the code is decoupled it
+should be relatively easy to support others:
1. Clone **padrino-fields/orms/datamapper.rb** and extend your ORM in a similar fashion
2. Clone **test/fixtures/datamapper** and substitute the models with your ORM
View
@@ -5,7 +5,7 @@
require 'i18n'
require 'enumerator'
-FileSet.glob_require('padrino-fields/**/*.rb', __FILE__)
+FileSet.glob_require('padrino-fields/*.rb', __FILE__)
# Load our locales
I18n.load_path += Dir["#{File.dirname(__FILE__)}/padrino-helpers/locale/*.yml"]
@@ -1,6 +1,7 @@
require 'padrino-helpers'
require File.expand_path(File.dirname(__FILE__) + '/form_helpers')
require File.expand_path(File.dirname(__FILE__) + '/orms/datamapper') if defined?(DataMapper)
+require File.expand_path(File.dirname(__FILE__) + '/orms/active_record') if defined?(ActiveRecord)
require File.expand_path(File.dirname(__FILE__) + '/settings')
module Padrino
@@ -10,6 +11,7 @@ class PadrinoFieldsBuilder < AbstractFormBuilder #:nodoc:
include PadrinoFields::Settings
include PadrinoFields::DataMapperWrapper if defined?(DataMapper)
+ include PadrinoFields::ActiveRecordWrapper if defined?(ActiveRecord)
@@settings = PadrinoFields::Settings
@@ -18,25 +20,33 @@ class PadrinoFieldsBuilder < AbstractFormBuilder #:nodoc:
def input(attribute, options={})
options.reverse_merge!(:caption => options.delete(:caption)) if options[:caption]
type = options[:as] || klazz.form_column_type_for(attribute)
- field_html = ""
- if type == :boolean || options[:as] == :boolean
+ label_html, field_html = "", ""
+ if (type == :boolean || options[:as] == :boolean) && !@@settings.control_container
field_html << default_input(attribute,type,options)
field_html << setup_label(attribute,type,labelize(options))
else
- field_html << setup_label(attribute,type,labelize(options))
+ label_html << setup_label(attribute,type,labelize(options))
field_html << default_input(attribute,type, options)
end
field_html << hint(options[:hint]) if options[:hint]
- field_html << @template.error_message_on(@object,attribute,{}) if @object.errors.any?
- @template.content_tag(@@settings.container, :class => css_class(attribute,type,options[:disabled])) do
- field_html
+ field_html << @template.error_message_on(@object,attribute,@@settings.error_message_options) if @object.errors.any?
+
+ if @@settings.control_container
+ field_html = @template.content_tag(@@settings.control_container, :class => @@settings.control_container_class) do
+ field_html
+ end
+ end
+
+ @template.content_tag(@@settings.container, :class =>
+ "#{@@settings.container_class} #{css_class(attribute,type,options[:disabled], @object.errors[attribute].any?)}") do
+ label_html + field_html
end
end
def default_input(attribute,type,options={})
input_options = options.keep_if {|key, value| key != :as}
if options[:options] || options[:grouped_options]
- if type==:radios || type == :checks
+ if type==:radios || type == :checkboxes
collect_inputs_as(attribute,type,input_options)
else
select(attribute,input_options)
@@ -107,16 +117,16 @@ def collection_input(attribute,type,item, options={})
unchecked_value = options.delete(:uncheck_value) || '0'
options.reverse_merge!(:id => field_id(attribute), :value => '1')
options.reverse_merge!(:checked => true) if values_matches_field?(attribute, options[:value])
- klass = css_class(attribute,type,options[:disabled])
- name = type == :checks ? field_name(attribute) + '[]' : field_name(attribute)
+ klass = css_class(attribute, type.to_s.singularize, options[:disabled])
+ name = type == :checkboxes ? field_name(attribute) + '[]' : field_name(attribute)
if item.is_a?(Array)
text, value = item[0], item[1]
else
text = item; value = item;
end
id = field_id(attribute) + "_" + domize(text)
opts = html_options(attribute,type,options.merge(:id => id, :class => klass, :value => value))
- if type == :checks
+ if type == :checkboxes
html = @template.hidden_field_tag(options[:name] || name, :value => unchecked_value, :id => nil)
input_item = @template.check_box_tag(name, opts)
else
@@ -136,14 +146,16 @@ def setup_label(attribute, type, options={})
text << marker if required?(attribute) && @@settings.label_required_marker_position == :prepend
text << field_human_name(attribute)
text << marker if required?(attribute) && @@settings.label_required_marker_position == :append
- options.reverse_merge!(:caption => text, :class => css_class(attribute,type,options[:disabled]))
+ options.reverse_merge!(:caption => text, :class =>
+ "#{@@settings.label_class} #{css_class(attribute,type,options[:disabled])}")
@template.label_tag(field_id(attribute), options)
end
- def css_class(attribute,type,disabled=false)
+ def css_class(attribute,type,disabled=false,error=false)
klass = type.to_s
klass << ' required' if required?(attribute)
- klass << ' required' if disabled
+ klass << ' disabled' if disabled
+ klass << ' error' if error
klass
end
@@ -164,7 +176,7 @@ def labelize(options)
end
def hint(text)
- @template.content_tag(:span, :class=>'hint') { text }
+ @template.content_tag(@@settings.hint_container, :class => @@settings.hint_container_class) { text }
end
def default_radios(attribute,type,options)
@@ -180,4 +192,4 @@ def klazz
end # StandardFormBuilder
end # FormBuilder
end # Helpers
-end # Padrino
+end # Padrino
@@ -0,0 +1,36 @@
+require 'active_record'
+
+module PadrinoFields
+ module ActiveRecordWrapper
+ module ClassMethods
+ def form_attribute_is_required?(attribute)
+ if respond_to?(:reflect_on_validations_for)
+ reflect_on_validations_for(attribute).any? { |v| v.macro == :validates_presence_of }
+ else
+ false
+ end
+ end
+
+ def form_column_type_for(attribute)
+ if column_reflection = columns_hash[attribute.to_s]
+ column_type = column_reflection.type
+
+ case column_type
+ when :integer, :float, :decimal
+ :number
+ when :date, :time, :datetime, :timestamp
+ :date
+ when :primary_key
+ :serial
+ else
+ column_type
+ end
+ else
+ :string
+ end
+ end
+ end # ClassMethods
+ end # ActiveRecord
+end # PadrinoFields
+
+ActiveRecord::Base.send :extend, PadrinoFields::ActiveRecordWrapper::ClassMethods
@@ -4,6 +4,27 @@ module Settings
mattr_accessor :container
@@container = :p
+ mattr_accessor :container_class
+ @@container_class = ""
+
+ mattr_accessor :control_container
+ @@control_container = nil
+
+ mattr_accessor :control_container_class
+ @@control_container_class = ""
+
+ mattr_accessor :hint_container
+ @@hint_container = :span
+
+ mattr_accessor :hint_container_class
+ @@hint_container_class = "hint"
+
+ mattr_accessor :error_message_options
+ @@error_message_options = {}
+
+ mattr_accessor :label_class
+ @@label_class = ""
+
mattr_accessor :label_required_marker
@@label_required_marker = "<abbr>*</abbr>"
View
@@ -116,20 +116,20 @@ def setup
end
should "return a collection of checkboxes" do
- actual = field.input(:string, :options => ["Ann","Bob"], :as => :checks)
- assert_has_tag("label", :class => "checks", :for => "person_string_ann", content:"Ann") { actual }
+ actual = field.input(:string, :options => ["Ann","Bob"], :as => :checkboxes)
+ assert_has_tag("label", :class => "checkboxes", :for => "person_string_ann", content:"Ann") { actual }
assert_has_tag("input", type:"checkbox", value:"Ann", id:"person_string_ann", name:"person[string][]") { actual }
- assert_has_tag("label", :class => "checks", :for => "person_string_bob", content:"Bob") { actual }
+ assert_has_tag("label", :class => "checkboxes", :for => "person_string_bob", content:"Bob") { actual }
assert_has_tag("input", type:"checkbox", value:"Bob", id:"person_string_bob", name:"person[string][]") { actual }
assert_has_tag("input", type:"hidden", name:"person[string][]", value:"0") { actual }
end
should "return a collection of checkboxes using a multi-dimensional array" do
- actual = field.input(:string, :options => [["Ann",1],["Bob",2]], :as => :checks)
- assert_has_tag("label", :class => "checks", :for => "person_string_ann", content:"Ann") { actual }
+ actual = field.input(:string, :options => [["Ann",1],["Bob",2]], :as => :checkboxes)
+ assert_has_tag("label", :class => "checkboxes", :for => "person_string_ann", content:"Ann") { actual }
assert_has_tag("input", type:"checkbox", value:"1", id:"person_string_ann", name:"person[string][]") { actual }
assert_has_tag("input", type:"hidden", name:"person[string][]", value:"0") { actual }
- assert_has_tag("label", :class => "checks", :for => "person_string_bob", content:"Bob") { actual }
+ assert_has_tag("label", :class => "checkboxes", :for => "person_string_bob", content:"Bob") { actual }
assert_has_tag("input", type:"checkbox", value:"2", id:"person_string_bob", name:"person[string][]") { actual }
assert_has_tag("input", type:"hidden", name:"person[string][]", value:"0") { actual }
end

0 comments on commit 5c1c269

Please sign in to comment.