class Decidim::FormBuilder

This custom FormBuilder adds fields needed to deal with translatable fields, following the conventions set on `Decidim::TranslatableAttributes`.

Public Instance Methods

areas_select(name, collection, options = {}, html_options = {}) click to toggle source

Public: Generates a select field for areas.

name - The name of the field (usually area_id) collection - A collection of areas or area_types.

If it's areas, we sort the selectable options alphabetically.

Returns a String.

# File lib/decidim/form_builder.rb, line 238
def areas_select(name, collection, options = {}, html_options = {})
  selectables = if collection.first.is_a?(Decidim::Area)
                  assemblies = collection
                               .map { |a| [a.name[I18n.locale.to_s], a.id] }
                               .sort_by { |arr| arr[0] }

                  @template.options_for_select(
                    assemblies,
                    selected: options[:selected]
                  )
                else
                  @template.option_groups_from_collection_for_select(
                    collection,
                    :areas,
                    :translated_name,
                    :id,
                    :translated_name,
                    selected: options[:selected]
                  )
                end

  select(name, selectables, options, html_options)
end
categories_select(name, collection, options = {}, html_options = {}) click to toggle source

Public: Generates a select field with the categories. Only leaf categories can be set as selected.

name - The name of the field (usually category_id) collection - A collection of categories. options - An optional Hash with options:

  • prompt - An optional String with the text to display as prompt.

  • disable_parents - A Boolean to disable parent categories. Defaults to `true`.

html_options - HTML options for the select

Returns a String.

# File lib/decidim/form_builder.rb, line 212
def categories_select(name, collection, options = {}, html_options = {})
  options = {
    disable_parents: true
  }.merge(options)

  disable_parents = options[:disable_parents]

  selected = object.send(name)
  selected = selected.first if selected.is_a?(Array) && selected.length > 1
  categories = categories_for_select(collection)
  disabled = if disable_parents
               disabled_categories_for(collection)
             else
               []
             end

  select(name, @template.options_for_select(categories, selected: selected, disabled: disabled), options, html_options)
end
check_box(attribute, options = {}, checked_value = "1", unchecked_value = "0") click to toggle source

Public: Override so checkboxes are rendered before the label.

# File lib/decidim/form_builder.rb, line 351
def check_box(attribute, options = {}, checked_value = "1", unchecked_value = "0")
  custom_label(attribute, options[:label], options[:label_options], field_before_label: true) do
    options.delete(:label)
    options.delete(:label_options)
    @template.check_box(@object_name, attribute, objectify_options(options), checked_value, unchecked_value)
  end + error_and_help_text(attribute, options)
end
collection_check_boxes(attribute, collection, value_attribute, text_attribute, options = {}, html_options = {}) click to toggle source

Public: generates a check boxes input from a collection and adds help text and errors.

attribute - the name of the field collection - the collection from which we will render the check boxes value_attribute - a Symbol or a Proc defining how to find the value

attribute

text_attribute - a Symbol or a Proc defining how to find the text

attribute

options - a Hash with options html_options - a Hash with options

Renders a collection of check boxes. rubocop:disable Metrics/ParameterLists

Calls superclass method
# File lib/decidim/form_builder.rb, line 27
def collection_check_boxes(attribute, collection, value_attribute, text_attribute, options = {}, html_options = {})
  super + error_and_help_text(attribute, options)
end
collection_radio_buttons(attribute, collection, value_attribute, text_attribute, options = {}, html_options = {}) click to toggle source

Public: generates a radio buttons input from a collection and adds help text and errors.

attribute - the name of the field collection - the collection from which we will render the radio buttons value_attribute - a Symbol or a Proc defining how to find the value attribute text_attribute - a Symbol or a Proc defining how to find the text attribute options - a Hash with options html_options - a Hash with options

Renders a collection of radio buttons. rubocop:disable Metrics/ParameterLists

Calls superclass method
# File lib/decidim/form_builder.rb, line 44
def collection_radio_buttons(attribute, collection, value_attribute, text_attribute, options = {}, html_options = {})
  super + error_and_help_text(attribute, options)
end
create_language_selector(locales, tabs_id, name) click to toggle source

rubocop:enable Metrics/ParameterLists

# File lib/decidim/form_builder.rb, line 49
def create_language_selector(locales, tabs_id, name)
  if Decidim.available_locales.count > 4
    language_selector_select(locales, tabs_id, name)
  else
    language_tabs(locales, tabs_id, name)
  end
end
data_picker(attribute, options = {}, prompt_params = {}) { |item| ... } click to toggle source

Public: Generates a picker field for selection (either simple or multiselect).

attribute - The name of the object's attribute. options - A Hash with options:

  • multiple: Multiple mode, to allow selection of multiple items.

  • label: Show label?

  • name: (optional) The name attribute of the input elements.

prompt_params - Hash with options:

  • url: The url where the ajax endpoint that will fill the content of the selector popup (the prompt).

  • text: Text in the button to open the Data Picker selector.

Also it should receive a block that returns a Hash with :url and :text for each selected scope

Returns an html String.

# File lib/decidim/form_builder.rb, line 332
def data_picker(attribute, options = {}, prompt_params = {})
  picker_options = {
    id: "#{@object_name}_#{attribute}",
    class: "picker-#{options[:multiple] ? "multiple" : "single"}",
    name: options[:name] || "#{@object_name}[#{attribute}]"
  }
  picker_options[:class] += " is-invalid-input" if error?(attribute)
  picker_options[:class] += " picker-autosort" if options[:autosort]

  items = object.send(attribute).collect { |item| [item, yield(item)] }

  template = ""
  template += label(attribute, label_for(attribute) + required_for_attribute(attribute)) unless options[:label] == false
  template += @template.render("decidim/widgets/data_picker", picker_options: picker_options, prompt_params: prompt_params, items: items)
  template += error_and_help_text(attribute, options)
  template.html_safe
end
date_field(attribute, options = {}) click to toggle source

Public: Override so the date fields are rendered using foundation datepicker library

# File lib/decidim/form_builder.rb, line 361
def date_field(attribute, options = {})
  value = object.send(attribute)
  data = { datepicker: "" }
  data[:startdate] = I18n.l(value, format: :decidim_short) if value.present? && value.is_a?(Date)
  datepicker_format = ruby_format_to_datepicker(I18n.t("date.formats.decidim_short"))
  data[:"date-format"] = datepicker_format

  template = text_field(
    attribute,
    options.merge(data: data)
  )
  help_text = I18n.t("decidim.datepicker.help_text", datepicker_format: datepicker_format)
  template += error_and_help_text(attribute, options.merge(help_text: help_text))
  template.html_safe
end
datetime_field(attribute, options = {}) click to toggle source

Public: Generates a timepicker field using foundation datepicker library

# File lib/decidim/form_builder.rb, line 379
def datetime_field(attribute, options = {})
  value = object.send(attribute)
  data = { datepicker: "", timepicker: "" }
  data[:startdate] = I18n.l(value, format: :decidim_short) if value.present? && value.is_a?(ActiveSupport::TimeWithZone)
  datepicker_format = ruby_format_to_datepicker(I18n.t("time.formats.decidim_short"))
  data[:"date-format"] = datepicker_format

  template = text_field(
    attribute,
    options.merge(data: data)
  )
  help_text = I18n.t("decidim.datepicker.help_text", datepicker_format: datepicker_format)
  template += content_tag(:span, help_text, class: "help-text")
  template.html_safe
end
editor(name, options = {}) click to toggle source

Public: generates a hidden field and a container for WYSIWYG editor

name - The name of the field options - The set of options to send to the field

:label - The Boolean value to create or not the input label (optional) (default: true)
:toolbar - The String value to configure WYSIWYG toolbar. It should be 'basic' or
           or 'full' (optional) (default: 'basic')
:lines - The Integer to indicate how many lines should editor have (optional) (default: 10)
:disabled - Whether the editor should be disabled

Renders a container with both hidden field and editor container

# File lib/decidim/form_builder.rb, line 179
def editor(name, options = {})
  options[:disabled] ||= false
  toolbar = options.delete(:toolbar) || "basic"
  lines = options.delete(:lines) || 10
  label_text = options[:label].to_s
  label_text = label_for(name) if label_text.blank?
  options.delete(:required)
  hashtaggable = options.delete(:hashtaggable)
  hidden_options = extract_validations(name, options).merge(options)

  content_tag(:div, class: "editor #{"hashtags__container" if hashtaggable}") do
    template = ""
    template += label(name, label_text + required_for_attribute(name)) if options.fetch(:label, true)
    template += hidden_field(name, hidden_options)
    template += content_tag(:div, nil, class: "editor-container #{"js-hashtags" if hashtaggable}", data: {
                              toolbar: toolbar,
                              disabled: options[:disabled]
                            }, style: "height: #{lines}rem")
    template += error_for(name, options) if error?(name)
    template.html_safe
  end
end
form_field_for(attribute, options = {}) click to toggle source
# File lib/decidim/form_builder.rb, line 495
def form_field_for(attribute, options = {})
  if attribute == :body
    text_area(attribute, options.merge(rows: 10))
  else
    text_field(attribute, options)
  end
end
hashtaggable_text_field(type, name, locale, options = {}) click to toggle source

Public: Generates a field for hashtaggable type. type - The form field's type, like `text_area` or `text_input` name - The name of the field handlers - The social handlers to be created options - The set of options to send to the field

Renders form fields for each locale.

# File lib/decidim/form_builder.rb, line 114
def hashtaggable_text_field(type, name, locale, options = {})
  options[:hashtaggable] = true if type.to_sym == :editor

  content_tag(:div, class: "hashtags__container") do
    if options[:value]
      send(type, name_with_locale(name, locale), options.merge(label: options[:label], value: options[:value][locale]))
    else
      send(type, name_with_locale(name, locale), options.merge(label: options[:label]))
    end
  end
end
label_for(attribute) click to toggle source

Public: Returns the translated name for the given attribute.

attribute - The String name of the attribute to return the name.

# File lib/decidim/form_builder.rb, line 487
def label_for(attribute)
  if object.class.respond_to?(:human_attribute_name)
    object.class.human_attribute_name(attribute)
  else
    attribute.to_s.humanize
  end
end
resources_select(name, collection, options = {}) click to toggle source

Public: Generates a select field for resource types.

name - The name of the field (usually resource_type) collection - A collection of resource types.

The options are sorted alphabetically.

Returns a String.

# File lib/decidim/form_builder.rb, line 269
def resources_select(name, collection, options = {})
  resources =
    collection
    .map { |r| [I18n.t(r.split("::").last.underscore, scope: "decidim.components.component_order_selector.order"), r] }
    .sort

  select(name, @template.options_for_select(resources, selected: options[:selected]), options)
end
scopes_picker(attribute, options = {}) { |nil| ... } click to toggle source

Public: Generates a picker field for scope selection.

attribute - The name of the field (usually scope_id) options - An optional Hash with options:

  • multiple - Multiple mode, to allow multiple scopes selection.

  • label - Show label?

  • checkboxes_on_top - Show checked picker values on top (default) or below the picker prompt (only for multiple pickers)

  • namespace - prepend a custom name to the html element's DOM id.

Also it should receive a block that returns a Hash with :url and :text for each selected scope (and for null scope for prompt)

Returns a String.

# File lib/decidim/form_builder.rb, line 290
def scopes_picker(attribute, options = {})
  id = if self.options.has_key?(:namespace)
         "#{self.options[:namespace]}_#{sanitize_for_dom_selector(@object_name)}"
       else
         "#{sanitize_for_dom_selector(@object_name)}_#{attribute}"
       end

  picker_options = {
    id: id,
    class: "picker-#{options[:multiple] ? "multiple" : "single"}",
    name: "#{@object_name}[#{attribute}]"
  }

  picker_options[:class] += " is-invalid-input" if error?(attribute)

  prompt_params = yield(nil)
  scopes = selected_scopes(attribute).map { |scope| [scope, yield(scope)] }
  template = ""
  template += "<label>#{label_for(attribute) + required_for_attribute(attribute)}</label>" unless options[:label] == false
  template += @template.render("decidim/scopes/scopes_picker_input",
                               picker_options: picker_options,
                               prompt_params: prompt_params,
                               scopes: scopes,
                               values_on_top: !options[:multiple] || options[:checkboxes_on_top])
  template += error_and_help_text(attribute, options)
  template.html_safe
end
social_field(type, name, handlers, options = {}) click to toggle source

Public: Generates an form field for each social.

type - The form field's type, like `text_area` or `text_input` name - The name of the field handlers - The social handlers to be created options - The set of options to send to the field

Renders form fields for each locale.

# File lib/decidim/form_builder.rb, line 134
def social_field(type, name, handlers, options = {})
  tabs_id = sanitize_tabs_selector(options[:tabs_id] || "#{object_name}-#{name}-tabs")

  label_tabs = content_tag(:div, class: "label--tabs") do
    field_label = label_i18n(name, options[:label] || label_for(name))

    tabs_panels = "".html_safe
    if options[:label] != false
      tabs_panels = content_tag(:ul, class: "tabs tabs--lang", id: tabs_id, data: { tabs: true }) do
        handlers.each_with_index.inject("".html_safe) do |string, (handler, index)|
          string + content_tag(:li, class: tab_element_class_for("title", index)) do
            title = I18n.t(".#{handler}", scope: "activemodel.attributes.#{object_name}")
            tab_content_id = sanitize_tabs_selector "#{tabs_id}-#{name}-panel-#{index}"
            content_tag(:a, title, href: "##{tab_content_id}")
          end
        end
      end
    end

    safe_join [field_label, tabs_panels]
  end

  tabs_content = content_tag(:div, class: "tabs-content", data: { tabs_content: tabs_id }) do
    handlers.each_with_index.inject("".html_safe) do |string, (handler, index)|
      tab_content_id = sanitize_tabs_selector "#{tabs_id}-#{name}-panel-#{index}"
      string + content_tag(:div, class: tab_element_class_for("panel", index), id: tab_content_id) do
        send(type, "#{handler}_handler", options.merge(label: false))
      end
    end
  end

  safe_join [label_tabs, tabs_content]
end
text_area(attribute, options = {}) click to toggle source

Discard the pattern attribute which is not allowed for textarea elements.

# File lib/decidim/form_builder.rb, line 504
def text_area(attribute, options = {})
  field(attribute, options) do |opts|
    opts.delete(:pattern)
    @template.send(
      :text_area,
      @object_name,
      attribute,
      objectify_options(opts)
    )
  end
end
translated(type, name, options = {}) click to toggle source

Public: Generates an form field for each locale.

type - The form field's type, like `text_area` or `text_input` name - The name of the field options - The set of options to send to the field

Renders form fields for each locale.

# File lib/decidim/form_builder.rb, line 64
def translated(type, name, options = {})
  return translated_one_locale(type, name, locales.first, options.merge(label: (options[:label] || label_for(name)))) if locales.count == 1

  tabs_id = sanitize_tabs_selector(options[:tabs_id] || "#{object_name}-#{name}-tabs")

  label_tabs = content_tag(:div, class: "label--tabs") do
    field_label = label_i18n(name, options[:label] || label_for(name))

    language_selector = "".html_safe
    language_selector = create_language_selector(locales, tabs_id, name) if options[:label] != false

    safe_join [field_label, language_selector]
  end

  hashtaggable = options.delete(:hashtaggable)
  tabs_content = content_tag(:div, class: "tabs-content", data: { tabs_content: tabs_id }) do
    locales.each_with_index.inject("".html_safe) do |string, (locale, index)|
      tab_content_id = "#{tabs_id}-#{name}-panel-#{index}"
      string + content_tag(:div, class: tab_element_class_for("panel", index), id: tab_content_id) do
        if hashtaggable
          hashtaggable_text_field(type, name, locale, options.merge(label: false))
        elsif type.to_sym == :editor
          send(type, name_with_locale(name, locale), options.merge(label: false, hashtaggable: hashtaggable))
        else
          send(type, name_with_locale(name, locale), options.merge(label: false))
        end
      end
    end
  end

  safe_join [label_tabs, tabs_content]
end
translated_one_locale(type, name, locale, options = {}) click to toggle source
# File lib/decidim/form_builder.rb, line 97
def translated_one_locale(type, name, locale, options = {})
  return hashtaggable_text_field(type, name, locale, options) if options.delete(:hashtaggable)

  send(
    type,
    "#{name}_#{locale.to_s.gsub("-", "__")}",
    options.merge(label: options[:label] || label_for(name))
  )
end
upload(attribute, options = {}) click to toggle source

Public: Generates a file upload field and sets the form as multipart. If the file is an image it displays the default image if present or the current one. By default it also generates a checkbox to delete the file. This checkbox can be hidden if `options` is passed as `false`.

attribute - The String name of the attribute to buidl the field. options - A Hash with options to build the field.

* optional: Whether the file can be optional or not.

rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/PerceivedComplexity

# File lib/decidim/form_builder.rb, line 405
def upload(attribute, options = {})
  self.multipart = true
  options[:optional] = options[:optional].nil? ? true : options[:optional]
  label_text = options[:label] || label_for(attribute)
  alt_text = label_text

  file = object.send attribute
  template = ""
  template += label(attribute, label_text + required_for_attribute(attribute))
  template += upload_help(attribute, options)
  template += @template.file_field @object_name, attribute

  template += extension_allowlist_help(options[:extension_allowlist]) if options[:extension_allowlist].present?
  template += image_dimensions_help(options[:dimensions_info]) if options[:dimensions_info].present?

  if file_is_image?(file)
    template += if file.present?
                  @template.content_tag :label, I18n.t("current_image", scope: "decidim.forms")
                else
                  @template.content_tag :label, I18n.t("default_image", scope: "decidim.forms")
                end
    template += @template.link_to @template.image_tag(file.url, alt: alt_text), file.url, target: "_blank", rel: "noopener"
  elsif file_is_present?(file)
    template += @template.label_tag I18n.t("current_file", scope: "decidim.forms")
    template += @template.link_to file.file.filename, file.url, target: "_blank", rel: "noopener"
  end

  if file_is_present?(file) && options[:optional]
    template += content_tag :div, class: "field" do
      safe_join([
                  @template.check_box(@object_name, "remove_#{attribute}"),
                  label("remove_#{attribute}", I18n.t("remove_this_file", scope: "decidim.forms"))
                ])
    end
  end

  if object.errors[attribute].any?
    template += content_tag :p, class: "is-invalid-label" do
      safe_join object.errors[attribute], "<br/>".html_safe
    end
  end

  template.html_safe
end
upload_help(attribute, options = {}) click to toggle source

rubocop:enable Metrics/CyclomaticComplexity rubocop:enable Metrics/PerceivedComplexity

# File lib/decidim/form_builder.rb, line 452
def upload_help(attribute, options = {})
  humanizer = FileValidatorHumanizer.new(object, attribute)

  help_scope = begin
    if options[:help_i18n_scope].present?
      options[:help_i18n_scope]
    elsif humanizer.uploader.is_a?(Decidim::ImageUploader)
      "decidim.forms.file_help.image"
    else
      "decidim.forms.file_help.file"
    end
  end

  help_messages = begin
    if options[:help_i18n_messages].present?
      Array(options[:help_i18n_messages])
    else
      %w(message_1 message_2)
    end
  end

  content_tag(:div, class: "help-text") do
    inner = "<p>#{I18n.t("explanation", scope: help_scope)}</p>".html_safe
    inner + content_tag(:ul) do
      messages = help_messages.each.map { |msg| I18n.t(msg, scope: help_scope) }
      messages += humanizer.messages

      messages.map { |msg| content_tag(:li, msg) }.join("\n").html_safe
    end.html_safe
  end
end

Private Instance Methods

abide_error_element(attribute) click to toggle source

Private: Builds a span to be shown when there's a validation error in a field. It looks for the text that will be the content in a similar way `human_attribute_name` does it.

attribute - The name of the attribute of the field.

Returns a String.

# File lib/decidim/form_builder.rb, line 682
def abide_error_element(attribute)
  defaults = []
  defaults << :"decidim.forms.errors.#{object.class.model_name.i18n_key}.#{attribute}"
  defaults << :"decidim.forms.errors.#{attribute}"
  defaults << :"forms.errors.#{attribute}"
  defaults << :"decidim.forms.errors.error"

  options = { count: 1, default: defaults }

  text = I18n.t(defaults.shift, **options)
  content_tag(:span, text, class: "form-error")
end
attribute_required?(attribute) click to toggle source

Private: Tries to find if an attribute is required in the form object.

Returns Boolean.

# File lib/decidim/form_builder.rb, line 592
def attribute_required?(attribute)
  validator = find_validator(attribute, ActiveModel::Validations::PresenceValidator) ||
              find_validator(attribute, TranslatablePresenceValidator)

  return unless validator

  # Check if the if condition is present and it evaluates to true
  if_condition = validator.options[:if]
  validator_if_condition = if_condition.nil? ||
                           (string_or_symbol?(if_condition) ? object.send(if_condition) : if_condition.call(object))

  # Check if the unless condition is present and it evaluates to false
  unless_condition = validator.options[:unless]
  validator_unless_condition = unless_condition.nil? ||
                               (string_or_symbol?(unless_condition) ? !object.send(unless_condition) : !unless_condition.call(object))

  validator_if_condition && validator_unless_condition
end
categories_for_select(scope) click to toggle source
# File lib/decidim/form_builder.rb, line 695
def categories_for_select(scope)
  sorted_main_categories = scope.first_class.includes(:subcategories).sort_by do |category|
    [category.weight, translated_attribute(category.name, category.participatory_space.organization)]
  end

  sorted_main_categories.flat_map do |category|
    parent = [[translated_attribute(category.name, category.participatory_space.organization), category.id]]

    sorted_subcategories = category.subcategories.sort_by do |subcategory|
      [subcategory.weight, translated_attribute(subcategory.name, subcategory.participatory_space.organization)]
    end

    sorted_subcategories.each do |subcategory|
      parent << ["- #{translated_attribute(subcategory.name, subcategory.participatory_space.organization)}", subcategory.id]
    end

    parent
  end
end
custom_label(attribute, text, options, field_before_label: false, show_required: true) { || ... } click to toggle source

Private: Override method from FoundationRailsHelper to render the text of the label before the input, instead of after.

attribute - The String name of the attribute we're build the label. text - The String text to use as label. options - A Hash to build the label.

Returns a String. rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/PerceivedComplexity

# File lib/decidim/form_builder.rb, line 658
def custom_label(attribute, text, options, field_before_label: false, show_required: true)
  return block_given? ? yield.html_safe : "".html_safe if text == false

  text = default_label_text(object, attribute) if text.nil? || text == true
  text += required_for_attribute(attribute) if show_required

  text = if field_before_label && block_given?
           safe_join([yield, text.html_safe])
         elsif block_given?
           safe_join([text.html_safe, yield])
         end

  label(attribute, text, options || {})
end
disabled_categories_for(scope) click to toggle source
# File lib/decidim/form_builder.rb, line 715
def disabled_categories_for(scope)
  scope.first_class.joins(:subcategories).pluck(:id)
end
error_and_help_text(attribute, options = {}) click to toggle source

Private: Returns the help text and error tags at the end of the field. Modified to change the tag to a valid HTML tag inside the <label> element.

# File lib/decidim/form_builder.rb, line 795
def error_and_help_text(attribute, options = {})
  html = ""
  html += content_tag(:span, options[:help_text], class: "help-text") if options[:help_text]
  html += error_for(attribute, options) || ""
  html.html_safe
end
extension_allowlist_help(extension_allowlist) click to toggle source
# File lib/decidim/form_builder.rb, line 814
def extension_allowlist_help(extension_allowlist)
  content_tag :p, class: "extensions-help help-text" do
    safe_join([
                content_tag(:span, I18n.t("extension_allowlist", scope: "decidim.forms.files")),
                " ",
                safe_join(extension_allowlist.map { |ext| content_tag(:b, ext) }, ", ")
              ])
  end
end
extract_validations(attribute, options) click to toggle source

Private: Builds a Hash of options to be injected at the HTML output as HTML5 validations.

attribute - The String name of the attribute to extract the validations. options - A Hash of options to extract validations.

Returns a Hash.

# File lib/decidim/form_builder.rb, line 575
def extract_validations(attribute, options)
  min_length = options.delete(:minlength) || length_for_attribute(attribute, :minimum) || 0
  max_length = options.delete(:maxlength) || length_for_attribute(attribute, :maximum)

  validation_options = {}
  validation_options[:pattern] = "^(.|[\n\r]){#{min_length},#{max_length}}$" if min_length.to_i.positive? || max_length.to_i.positive?
  validation_options[:required] = options[:required] || attribute_required?(attribute)
  validation_options[:minlength] ||= min_length if min_length.to_i.positive?
  validation_options[:maxlength] ||= max_length if max_length.to_i.positive?
  validation_options
end
field(attribute, options, html_options = nil, &block) click to toggle source

Private: Override from FoundationRailsHelper in order to render inputs inside the label and to automatically inject validations from the object.

attribute - The String name of the attribute to buidl the field. options - A Hash with options to build the field. html_options - An optional Hash with options to pass to the html element.

Returns a String

# File lib/decidim/form_builder.rb, line 527
def field(attribute, options, html_options = nil, &block)
  label = options.delete(:label)
  label_options = options.delete(:label_options)
  custom_label(attribute, label, label_options) do
    field_with_validations(attribute, options, html_options, &block)
  end
end
field_with_validations(attribute, options, html_options) { |class_options| ... } click to toggle source

Private: Builds a form field and detects validations from the form object.

attribute - The String name of the attribute to build the field. options - A Hash with options to build the field. html_options - An optional Hash with options to pass to the html element.

Returns a String.

# File lib/decidim/form_builder.rb, line 543
def field_with_validations(attribute, options, html_options)
  class_options = html_options || options

  if error?(attribute)
    class_options[:class] = class_options[:class].to_s
    class_options[:class] += " is-invalid-input"
  end

  help_text = options.delete(:help_text)
  prefix = options.delete(:prefix)
  postfix = options.delete(:postfix)

  class_options = extract_validations(attribute, options).merge(class_options)

  content = yield(class_options)
  content += abide_error_element(attribute) if class_options[:pattern] || class_options[:required]
  content = content.html_safe

  html = wrap_prefix_and_postfix(content, prefix, postfix)
  html + error_and_help_text(attribute, options.merge(help_text: help_text))
end
file_is_image?(file) click to toggle source

Private: Returns whether the file is an image or not.

# File lib/decidim/form_builder.rb, line 750
def file_is_image?(file)
  return unless file && file.respond_to?(:url)
  return file.content_type.start_with? "image" if file.content_type.present?

  Mime::Type.lookup_by_extension(File.extname(file.url)[1..-1]).to_s.start_with? "image" if file.url.present?
end
file_is_present?(file) click to toggle source

Private: Returns whether the file exists or not.

# File lib/decidim/form_builder.rb, line 758
def file_is_present?(file)
  return unless file && file.respond_to?(:url)

  file.present?
end
find_validator(attribute, klass) click to toggle source

Private: Finds a validator.

attribute - The attribute to validate. klass - The Class of the validator to find.

Returns a klass object.

# File lib/decidim/form_builder.rb, line 642
def find_validator(attribute, klass)
  return unless object.respond_to?(:_validators)

  object._validators[attribute.to_sym].find { |validator| validator.class == klass }
end
image_dimensions_help(dimensions_info) click to toggle source
# File lib/decidim/form_builder.rb, line 824
def image_dimensions_help(dimensions_info)
  content_tag :p, class: "image-dimensions-help help-text" do
    safe_join([
                content_tag(:span, I18n.t("dimensions_info", scope: "decidim.forms.images")),
                " ",
                content_tag(:span) do
                  safe_join(dimensions_info.map do |_version, info|
                    processor = @template.content_tag(:span, I18n.t("processors.#{info[:processor]}", scope: "decidim.forms.images"))
                    dimensions = @template.content_tag(:b, I18n.t("dimensions", scope: "decidim.forms.images", width: info[:dimensions].first, height: info[:dimensions].last))
                    safe_join([processor, "  ", dimensions, ". ".html_safe])
                  end)
                end
              ])
  end
end
label_i18n(attribute, text = nil, options = {}) click to toggle source
# File lib/decidim/form_builder.rb, line 737
def label_i18n(attribute, text = nil, options = {})
  errored = error?(attribute) || locales.any? { |locale| error?(name_with_locale(attribute, locale)) }

  if errored
    options[:class] ||= ""
    options[:class] += " is-invalid-label"
  end
  text += required_for_attribute(attribute)

  label(attribute, (text || "").html_safe, options)
end
language_selector_select(locales, tabs_id, name) click to toggle source
# File lib/decidim/form_builder.rb, line 869
def language_selector_select(locales, tabs_id, name)
  content_tag(:div) do
    content_tag(:select, id: tabs_id, class: "language-change") do
      locales.each_with_index.inject("".html_safe) do |string, (locale, index)|
        title = if error?(name_with_locale(name, locale))
                  I18n.with_locale(locale) { I18n.t("name_with_error", scope: "locale") }
                else
                  I18n.with_locale(locale) { I18n.t("name", scope: "locale") }
                end
        tab_content_id = sanitize_tabs_selector "#{tabs_id}-#{name}-panel-#{index}"
        string + content_tag(:option, title, value: "##{tab_content_id}")
      end
    end
  end
end
language_tabs(locales, tabs_id, name) click to toggle source
# File lib/decidim/form_builder.rb, line 885
def language_tabs(locales, tabs_id, name)
  content_tag(:ul, class: "tabs tabs--lang", id: tabs_id, data: { tabs: true }) do
    locales.each_with_index.inject("".html_safe) do |string, (locale, index)|
      string + content_tag(:li, class: tab_element_class_for("title", index)) do
        title = I18n.with_locale(locale) { I18n.t("name", scope: "locale") }
        element_class = nil
        element_class = "is-tab-error" if error?(name_with_locale(name, locale))
        tab_content_id = sanitize_tabs_selector "#{tabs_id}-#{name}-panel-#{index}"
        content_tag(:a, title, href: "##{tab_content_id}", class: element_class)
      end
    end
  end
end
length_for_attribute(attribute, type) click to toggle source

Private: Tries to find a length validator in the form object.

attribute - The attribute to look for the validations. type - A Symbol for the type of length to fetch. Currently only :minimum & :maximum are supported.

Returns an Integer or Nil.

# File lib/decidim/form_builder.rb, line 621
def length_for_attribute(attribute, type)
  length_validator = find_validator(attribute, ActiveModel::Validations::LengthValidator)
  return length_validator.options[type] if length_validator

  length_validator = find_validator(attribute, ProposalLengthValidator)
  if length_validator
    length = length_validator.options[type]
    return length.call(object) if length.respond_to?(:call)

    return length
  end

  nil
end
locales() click to toggle source
# File lib/decidim/form_builder.rb, line 725
def locales
  @locales ||= if @template.respond_to?(:available_locales)
                 Set.new([@template.current_locale] + @template.available_locales).to_a
               else
                 Decidim.available_locales
               end
end
name_with_locale(name, locale) click to toggle source
# File lib/decidim/form_builder.rb, line 733
def name_with_locale(name, locale)
  "#{name}_#{locale.to_s.gsub("-", "__")}"
end
required_for_attribute(attribute) click to toggle source
# File lib/decidim/form_builder.rb, line 764
def required_for_attribute(attribute)
  if attribute_required?(attribute)
    visible_title = content_tag(:span, "*", "aria-hidden": true)
    screenreader_title = content_tag(
      :span,
      I18n.t("required", scope: "forms"),
      class: "show-for-sr"
    )
    return content_tag(
      :span,
      visible_title + screenreader_title,
      title: I18n.t("required", scope: "forms"),
      data: { tooltip: true, disable_hover: false, keep_on_hover: true },
      "aria-haspopup": true,
      class: "label-required"
    ).html_safe
  end
  "".html_safe
end
ruby_format_to_datepicker(ruby_date_format) click to toggle source
# File lib/decidim/form_builder.rb, line 802
def ruby_format_to_datepicker(ruby_date_format)
  ruby_date_format.gsub("%d", "dd").gsub("%m", "mm").gsub("%Y", "yyyy").gsub("%H", "hh").gsub("%M", "ii")
end
sanitize_for_dom_selector(name) click to toggle source
# File lib/decidim/form_builder.rb, line 810
def sanitize_for_dom_selector(name)
  name.to_s.parameterize.underscore
end
sanitize_tabs_selector(id) click to toggle source
# File lib/decidim/form_builder.rb, line 806
def sanitize_tabs_selector(id)
  id.tr("[", "-").tr("]", "-")
end
selected_scopes(attribute) click to toggle source

Private: Returns an array of scopes related to object attribute

# File lib/decidim/form_builder.rb, line 785
def selected_scopes(attribute)
  selected = object.send(attribute) || []
  selected = selected.values if selected.is_a?(Hash)
  selected = [selected] unless selected.is_a?(Array)
  selected = Decidim::Scope.where(id: selected.map(&:to_i)) unless selected.first.is_a?(Decidim::Scope)
  selected
end
string_or_symbol?(obj) click to toggle source
# File lib/decidim/form_builder.rb, line 611
def string_or_symbol?(obj)
  obj.is_a?(String) || obj.is_a?(Symbol)
end
tab_element_class_for(type, index) click to toggle source
# File lib/decidim/form_builder.rb, line 719
def tab_element_class_for(type, index)
  element_class = "tabs-#{type}"
  element_class += " is-active" if index.zero?
  element_class
end
tag_from_options(name, options) click to toggle source

Private: Creates a tag from the given options for the field prefix and suffix. Overridden from Foundation Rails helper to make the generated HTML valid since these elements are printed within <label> elements and <div>'s are not allowed there.

# File lib/decidim/form_builder.rb, line 844
def tag_from_options(name, options)
  return "".html_safe unless options && options[:value].present?

  content_tag(:span,
              content_tag(:span, options[:value], class: name),
              class: column_classes(options).to_s)
end
wrap_prefix_and_postfix(block, prefix_options, postfix_options) click to toggle source

Private: Wraps the prefix and postfix for the field. Overridden from Foundation Rails helper to make the generated HTML valid since these elements are printed within <label> elements and <div>'s are not allowed there.

# File lib/decidim/form_builder.rb, line 856
def wrap_prefix_and_postfix(block, prefix_options, postfix_options)
  prefix = tag_from_options("prefix", prefix_options)
  postfix = tag_from_options("postfix", postfix_options)

  input_size = calculate_input_size(prefix_options, postfix_options)
  klass = column_classes(input_size.marshal_dump).to_s
  input = content_tag(:span, block, class: klass)

  return block unless input_size.changed?

  content_tag(:span, prefix + input + postfix, class: "row collapse")
end