app/helpers/dry_crud/form/builder.rb in dry_crud-2.1.1 vs app/helpers/dry_crud/form/builder.rb in dry_crud-2.1.2
- old
+ new
@@ -1,314 +1,339 @@
# encoding: UTF-8
-module DryCrud::Form
+module DryCrud
+ module Form
- # A form builder that automatically selects the corresponding input field
- # for ActiveRecord column types. Convenience methods for each column type
- # allow one to customize the different fields.
- #
- # All field methods may be prefixed with +labeled_+ in order to render
- # a standard label, required mark and an optional help block with them.
- #
- # Use #labeled_input_field or #input_field to render a input field
- # corresponding to the given attribute.
- #
- # See the Control class for how to customize the html rendered for a
- # single input field.
- class Builder < ActionView::Helpers::FormBuilder
+ # A form builder that automatically selects the corresponding input field
+ # for ActiveRecord column types. Convenience methods for each column type
+ # allow one to customize the different fields.
+ #
+ # All field methods may be prefixed with +labeled_+ in order to render
+ # a standard label, required mark and an optional help block with them.
+ #
+ # Use #labeled_input_field or #input_field to render a input field
+ # corresponding to the given attribute.
+ #
+ # See the Control class for how to customize the html rendered for a
+ # single input field.
+ class Builder < ActionView::Helpers::FormBuilder
- class_attribute :control_class
- self.control_class = Control
+ class_attribute :control_class
+ self.control_class = Control
- attr_reader :template
+ attr_reader :template
- delegate :association, :column_type, :column_property, :captionize,
- :ti, :ta, :link_to, :content_tag, :safe_join, :capture,
- :add_css_class, :assoc_and_id_attr,
- to: :template
+ delegate :association, :column_type, :column_property, :captionize,
+ :ti, :ta, :link_to, :content_tag, :safe_join, :capture,
+ :add_css_class, :assoc_and_id_attr,
+ to: :template
- ### INPUT FIELDS
+ ### INPUT FIELDS
- # Render multiple input controls together with a label for the given
- # attributes.
- def labeled_input_fields(*attrs)
- options = attrs.extract_options!
- safe_join(attrs) { |a| labeled_input_field(a, options.dup) }
- end
+ # Render multiple input controls together with a label for the given
+ # attributes.
+ def labeled_input_fields(*attrs)
+ options = attrs.extract_options!
+ safe_join(attrs) { |a| labeled_input_field(a, options.dup) }
+ end
- # Render a corresponding input control and label for the given attribute.
- # The input field is chosen based on the ActiveRecord column type.
- #
- # The following options may be passed:
- # * <tt>:addon</tt> - Addon content displayd just after the input field.
- # * <tt>:help</tt> - A help text displayd below the input field.
- # * <tt>:span</tt> - Number of columns the input field should span.
- # * <tt>:caption</tt> - Different caption for the label.
- # * <tt>:field_method</tt> - Different method to create the input field.
- #
- # Use additional html_options for the input element.
- def labeled_input_field(attr, html_options = {})
- control_class.new(self, attr, html_options).render_labeled
- end
+ # Render a corresponding input control and label for the given attribute.
+ # The input field is chosen based on the ActiveRecord column type.
+ #
+ # The following options may be passed:
+ # * <tt>:addon</tt> - Addon content displayd just after the input field.
+ # * <tt>:help</tt> - A help text displayd below the input field.
+ # * <tt>:span</tt> - Number of columns the input field should span.
+ # * <tt>:caption</tt> - Different caption for the label.
+ # * <tt>:field_method</tt> - Different method to create the input field.
+ #
+ # Use additional html_options for the input element.
+ def labeled_input_field(attr, html_options = {})
+ control_class.new(self, attr, html_options).render_labeled
+ end
- # Render a corresponding input control for the given attribute.
- # The input field is chosen based on the ActiveRecord column type.
- #
- # The following options may be passed:
- # * <tt>:addon</tt> - Addon content displayd just after the input field.
- # * <tt>:help</tt> - A help text displayd below the input field.
- # * <tt>:span</tt> - Number of columns the input field should span.
- # * <tt>:field_method</tt> - Different method to create the input field.
- #
- # Use additional html_options for the input element.
- def input_field(attr, html_options = {})
- control_class.new(self, attr, html_options).render_content
- end
+ # Render a corresponding input control for the given attribute.
+ # The input field is chosen based on the ActiveRecord column type.
+ #
+ # The following options may be passed:
+ # * <tt>:addon</tt> - Addon content displayd just after the input field.
+ # * <tt>:help</tt> - A help text displayd below the input field.
+ # * <tt>:span</tt> - Number of columns the input field should span.
+ # * <tt>:field_method</tt> - Different method to create the input field.
+ #
+ # Use additional html_options for the input element.
+ def input_field(attr, html_options = {})
+ control_class.new(self, attr, html_options).render_content
+ end
- # Render a standard string field with column contraints.
- def string_field(attr, html_options = {})
- html_options[:maxlength] ||= column_property(@object, attr, :limit)
- text_field(attr, html_options)
- end
+ # Render a standard string field with column contraints.
+ def string_field(attr, html_options = {})
+ html_options[:maxlength] ||= column_property(@object, attr, :limit)
+ text_field(attr, html_options)
+ end
- # Render a boolean field.
- def boolean_field(attr, html_options = {})
- add_css_class(html_options, 'form-control')
- check_box(attr, html_options)
- end
+ # Render a boolean field.
+ def boolean_field(attr, html_options = {})
+ content_tag(:div, class: 'checkbox') do
+ content_tag(:label) do
+ detail = html_options.delete(:detail) || ' '.html_safe
+ safe_join([check_box(attr, html_options), ' ', detail])
+ end
+ end
+ end
- # Customize the standard text area to have 5 rows by default.
- def text_area(attr, html_options = {})
- html_options[:rows] ||= 5
- super
- end
-
- alias_method :integer_field, :number_field
- alias_method :float_field, :number_field
- alias_method :decimal_field, :number_field
-
- if Rails.version < '4.0'
- # Render a field to select a date. You might want to customize this.
- def date_field(attr, html_options = {})
- html_options[:type] = 'date'
- text_field(attr, html_options)
+ # Add form-control class to all input fields.
+ %w(text_field password_field email_field text_area
+ number_fielc date_field time_field datetime_field).each do |method|
+ define_method(method) do |attr, html_options = {}|
+ add_css_class(html_options, 'form-control')
+ super(attr, html_options)
+ end
end
- # Render a field to enter a time. You might want to customize this.
- def time_field(attr, html_options = {})
- html_options[:type] = 'time'
- text_field(attr, html_options)
+ # Customize the standard text area to have 5 rows by default.
+ def text_area(attr, html_options = {})
+ add_css_class(html_options, 'form-control')
+ html_options[:rows] ||= 5
+ super(attr, html_options)
end
- # Render a field to enter a date and time.
- # You might want to customize this.
- def datetime_field(attr, html_options = {})
- html_options[:type] = 'datetime'
- text_field(attr, html_options)
+ alias_method :integer_field, :number_field
+ alias_method :float_field, :number_field
+ alias_method :decimal_field, :number_field
+
+ if Rails.version < '4.0'
+ # Render a field to select a date. You might want to customize this.
+ def date_field(attr, html_options = {})
+ html_options[:type] = 'date'
+ text_field(attr, html_options)
+ end
+
+ # Render a field to enter a time. You might want to customize this.
+ def time_field(attr, html_options = {})
+ html_options[:type] = 'time'
+ text_field(attr, html_options)
+ end
+
+ # Render a field to enter a date and time.
+ # You might want to customize this.
+ def datetime_field(attr, html_options = {})
+ html_options[:type] = 'datetime'
+ text_field(attr, html_options)
+ end
end
- end
- # Render a select element for a :belongs_to association defined by attr.
- # Use additional html_options for the select element.
- # To pass a custom element list, specify the list with the :list key or
- # define an instance variable with the pluralized name of the association.
- def belongs_to_field(attr, html_options = {})
- list = association_entries(attr, html_options).to_a
- if list.present?
- collection_select(attr, list, :id, :to_s,
- select_options(attr, html_options),
- html_options)
- else
- static_text(ta(:none_available, association(@object, attr)).html_safe)
+ # Render a select element for a :belongs_to association defined by attr.
+ # Use additional html_options for the select element.
+ # To pass a custom element list, specify the list with the :list key or
+ # define an instance variable with the pluralized name of the
+ # association.
+ def belongs_to_field(attr, html_options = {})
+ list = association_entries(attr, html_options).to_a
+ if list.present?
+ add_css_class(html_options, 'form-control')
+ collection_select(attr, list, :id, :to_s,
+ select_options(attr, html_options),
+ html_options)
+ else
+ static_text(
+ ta(:none_available, association(@object, attr)).html_safe)
+ end
end
- end
- # Render a multi select element for a :has_many or :has_and_belongs_to_many
- # association defined by attr.
- # Use additional html_options for the select element.
- # To pass a custom element list, specify the list with the :list key or
- # define an instance variable with the pluralized name of the association.
- def has_many_field(attr, html_options = {})
- html_options[:multiple] = true
- add_css_class(html_options, 'multiselect')
- belongs_to_field(attr, html_options)
- end
+ # rubocop:disable PredicateName
- ### VARIOUS FORM ELEMENTS
+ # Render a multi select element for a :has_many or
+ # :has_and_belongs_to_many association defined by attr.
+ # Use additional html_options for the select element.
+ # To pass a custom element list, specify the list with the :list key or
+ # define an instance variable with the pluralized name of the
+ # association.
+ def has_many_field(attr, html_options = {})
+ html_options[:multiple] = true
+ add_css_class(html_options, 'multiselect')
+ belongs_to_field(attr, html_options)
+ end
+ # rubocop:enable PredicateName
- # Render the error messages for the current form.
- def error_messages
- @template.render('shared/error_messages',
- errors: @object.errors,
- object: @object)
- end
+ ### VARIOUS FORM ELEMENTS
- # Renders the given content with an addon.
- def with_addon(content, addon)
- content_tag(:div, class: 'input-group') do
- content + content_tag(:span, addon, class: 'input-group-addon')
+ # Render the error messages for the current form.
+ def error_messages
+ @template.render('shared/error_messages',
+ errors: @object.errors,
+ object: @object)
end
- end
- # Renders a static text where otherwise form inputs appear.
- def static_text(text)
- content_tag(:p, text, class: 'form-control-static')
- end
+ # Renders the given content with an addon.
+ def with_addon(content, addon)
+ content_tag(:div, class: 'input-group') do
+ content + content_tag(:span, addon, class: 'input-group-addon')
+ end
+ end
- # Generates a help block for fields
- def help_block(text)
- content_tag(:p, text, class: 'help-block')
- end
+ # Renders a static text where otherwise form inputs appear.
+ def static_text(text)
+ content_tag(:p, text, class: 'form-control-static')
+ end
- # Render a submit button and a cancel link for this form.
- def standard_actions(submit_label = ti('button.save'), cancel_url = nil)
- content_tag(:div, class: 'col-md-offset-2 col-md-8') do
- safe_join([submit_button(submit_label), cancel_link(cancel_url)], ' ')
+ # Generates a help block for fields
+ def help_block(text)
+ content_tag(:p, text, class: 'help-block')
end
- end
- # Render a standard submit button with the given label.
- def submit_button(label = ti('button.save'))
- button(label, class: 'btn btn-primary', data: { disable_with: label })
- end
+ # Render a submit button and a cancel link for this form.
+ def standard_actions(submit_label = ti('button.save'), cancel_url = nil)
+ content_tag(:div, class: 'col-md-offset-2 col-md-8') do
+ safe_join([submit_button(submit_label),
+ cancel_link(cancel_url)],
+ ' ')
+ end
+ end
- # Render a cancel link pointing to the given url.
- def cancel_link(url = nil)
- url ||= cancel_url
- link_to(ti('button.cancel'), url, class: 'cancel')
- end
+ # Render a standard submit button with the given label.
+ def submit_button(label = ti('button.save'))
+ button(label, class: 'btn btn-primary', data: { disable_with: label })
+ end
- # Depending if the given attribute must be present, return
- # only an initial selection prompt or a blank option, respectively.
- def select_options(attr, options = {})
- prompt = options.delete(:prompt)
- blank = options.delete(:include_blank)
- if options[:multiple]
- {}
- elsif prompt
- { prompt: prompt }
- elsif blank
- { include_blank: blank }
- else
- assoc = association(@object, attr)
- if required?(attr)
- { prompt: ta(:please_select, assoc) }
+ # Render a cancel link pointing to the given url.
+ def cancel_link(url = nil)
+ url ||= cancel_url
+ link_to(ti('button.cancel'), url, class: 'cancel')
+ end
+
+ # Depending if the given attribute must be present, return
+ # only an initial selection prompt or a blank option, respectively.
+ def select_options(attr, options = {})
+ prompt = options.delete(:prompt)
+ blank = options.delete(:include_blank)
+ if options[:multiple]
+ {}
+ elsif prompt
+ { prompt: prompt }
+ elsif blank
+ { include_blank: blank }
else
- { include_blank: ta(:no_entry, assoc) }
+ assoc = association(@object, attr)
+ if required?(attr)
+ { prompt: ta(:please_select, assoc) }
+ else
+ { include_blank: ta(:no_entry, assoc) }
+ end
end
end
- end
- # Returns true if the given attribute must be present.
- def required?(attr)
- attr, attr_id = assoc_and_id_attr(attr)
- validators = @object.class.validators_on(attr) +
- @object.class.validators_on(attr_id)
- validators.any? do |v|
- v.kind == :presence &&
- !v.options.key?(:if) &&
- !v.options.key?(:unless)
+ # Returns true if the given attribute must be present.
+ def required?(attr)
+ attr, attr_id = assoc_and_id_attr(attr)
+ validators = @object.class.validators_on(attr) +
+ @object.class.validators_on(attr_id)
+ validators.any? do |v|
+ v.kind == :presence &&
+ !v.options.key?(:if) &&
+ !v.options.key?(:unless)
+ end
end
- end
- # Render a label for the given attribute with the passed content.
- # The content may be given as an argument or as a block:
- # labeled(:attr) { #content }
- # labeled(:attr, content)
- #
- # The following options may be passed:
- # * <tt>:span</tt> - Number of columns the content should span.
- # * <tt>:caption</tt> - Different caption for the label.
- def labeled(attr, content = {}, options = {}, &block)
- if block_given?
- options = content
- content = capture(&block)
+ # Render a label for the given attribute with the passed content.
+ # The content may be given as an argument or as a block:
+ # labeled(:attr) { #content }
+ # labeled(:attr, content)
+ #
+ # The following options may be passed:
+ # * <tt>:span</tt> - Number of columns the content should span.
+ # * <tt>:caption</tt> - Different caption for the label.
+ def labeled(attr, content = {}, options = {}, &block)
+ if block_given?
+ options = content
+ content = capture(&block)
+ end
+ control = control_class.new(self, attr, options)
+ control.render_labeled(content)
end
- control = control_class.new(self, attr, options)
- control.render_labeled(content)
- end
- # Dispatch methods starting with 'labeled_' to render a label and the
- # corresponding input field.
- # E.g. labeled_boolean_field(:checked, class: 'bold')
- # To add an additional help text, use the help option.
- # E.g. labeled_boolean_field(:checked, help: 'Some Help')
- def method_missing(name, *args)
- field_method = labeled_field_method?(name)
- if field_method
- build_labeled_field(field_method, *args)
- else
- super(name, *args)
+ # Dispatch methods starting with 'labeled_' to render a label and the
+ # corresponding input field.
+ # E.g. labeled_boolean_field(:checked, class: 'bold')
+ # To add an additional help text, use the help option.
+ # E.g. labeled_boolean_field(:checked, help: 'Some Help')
+ def method_missing(name, *args)
+ field_method = labeled_field_method?(name)
+ if field_method
+ build_labeled_field(field_method, *args)
+ else
+ super(name, *args)
+ end
end
- end
- # Overriden to fullfill contract with method_missing 'labeled_' methods.
- def respond_to?(name)
- labeled_field_method?(name).present? || super(name)
- end
+ # Overriden to fullfill contract with method_missing 'labeled_' methods.
+ def respond_to?(name)
+ labeled_field_method?(name).present? || super(name)
+ end
- private
+ private
- # Checks if the passed name corresponds to a field method with a
- # 'labeled_' prefix.
- def labeled_field_method?(name)
- prefix = 'labeled_'
- if name.to_s.start_with?(prefix)
- field_method = name.to_s[prefix.size..-1]
- field_method if respond_to?(field_method)
+ # Checks if the passed name corresponds to a field method with a
+ # 'labeled_' prefix.
+ def labeled_field_method?(name)
+ prefix = 'labeled_'
+ if name.to_s.start_with?(prefix)
+ field_method = name.to_s[prefix.size..-1]
+ field_method if respond_to?(field_method)
+ end
end
- end
- # Renders the corresponding field together with a label, required mark and
- # an optional help block.
- def build_labeled_field(field_method, *args)
- options = args.extract_options!
- options[:field_method] = field_method
- control_class.new(self, *(args << options)).render_labeled
- end
-
- # Returns the list of association entries, either from options[:list] or
- # the instance variable with the pluralized association name.
- # Otherwise, if the association defines a #options_list or #list scope,
- # this is used to load the entries.
- # As a last resort, all entries from the association class are returned.
- def association_entries(attr, options)
- list = options.delete(:list)
- unless list
- assoc = association(@object, attr)
- list = @template.send(:instance_variable_get,
- :"@#{assoc.name.to_s.pluralize}")
- list ||= load_association_entries(assoc)
+ # Renders the corresponding field together with a label, required mark
+ # and an optional help block.
+ def build_labeled_field(field_method, *args)
+ options = args.extract_options!
+ options[:field_method] = field_method
+ control_class.new(self, *(args << options)).render_labeled
end
- list
- end
- # Automatically load the entries for the given association.
- def load_association_entries(assoc)
- klass = assoc.klass
- list = if Rails.version >= '4.0'
- klass.all.merge(assoc.scope)
- else
- klass.where(assoc.options[:conditions])
- .order(assoc.options[:order])
- end
- # Use special scopes if they are defined
- if klass.respond_to?(:options_list)
- list.options_list
- elsif klass.respond_to?(:list)
- list.list
- else
+ # Returns the list of association entries, either from options[:list] or
+ # the instance variable with the pluralized association name.
+ # Otherwise, if the association defines a #options_list or #list scope,
+ # this is used to load the entries.
+ # As a last resort, all entries from the association class are returned.
+ def association_entries(attr, options)
+ list = options.delete(:list)
+ unless list
+ assoc = association(@object, attr)
+ list = @template.send(:instance_variable_get,
+ :"@#{assoc.name.to_s.pluralize}")
+ list ||= load_association_entries(assoc)
+ end
list
end
- end
- # Get the cancel url for the given object considering options:
- # 1. Use :cancel_url_new or :cancel_url_edit option, if present
- # 2. Use :cancel_url option, if present
- def cancel_url
- url = @object.new_record? ? options[:cancel_url_new] :
- options[:cancel_url_edit]
- url || options[:cancel_url]
- end
+ # Automatically load the entries for the given association.
+ def load_association_entries(assoc)
+ klass = assoc.klass
+ list = if Rails.version >= '4.0'
+ klass.all.merge(assoc.scope)
+ else
+ klass.where(assoc.options[:conditions])
+ .order(assoc.options[:order])
+ end
+ # Use special scopes if they are defined
+ if klass.respond_to?(:options_list)
+ list.options_list
+ elsif klass.respond_to?(:list)
+ list.list
+ else
+ list
+ end
+ end
+ # Get the cancel url for the given object considering options:
+ # 1. Use :cancel_url_new or :cancel_url_edit option, if present
+ # 2. Use :cancel_url option, if present
+ def cancel_url
+ url = @object.new_record? ? options[:cancel_url_new] :
+ options[:cancel_url_edit]
+ url || options[:cancel_url]
+ end
+
+ end
end
end