From 81f951630f81ee4ccfc214951ac4bac2cc6eb5e3 Mon Sep 17 00:00:00 2001
From: quentinchampenois <26109239+Quentinchampenois@users.noreply.github.com>
Date: Wed, 6 Mar 2024 12:19:22 +0100
Subject: [PATCH 1/3] fix: Remove not needed form_builder override
---
lib/decidim/form_builder.rb | 944 ------------------------------------
1 file changed, 944 deletions(-)
delete mode 100644 lib/decidim/form_builder.rb
diff --git a/lib/decidim/form_builder.rb b/lib/decidim/form_builder.rb
deleted file mode 100644
index 43cd605..0000000
--- a/lib/decidim/form_builder.rb
+++ /dev/null
@@ -1,944 +0,0 @@
-# frozen_string_literal: true
-
-require "foundation_rails_helper/form_builder"
-
-module Decidim
- # This custom FormBuilder adds fields needed to deal with translatable fields,
- # following the conventions set on `Decidim::TranslatableAttributes`.
- class FormBuilder < FoundationRailsHelper::FormBuilder
- include ActionView::Context
- include Decidim::TranslatableAttributes
- include Decidim::Map::Autocomplete::FormBuilder
-
- # 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
- def collection_check_boxes(attribute, collection, value_attribute, text_attribute, options = {}, html_options = {})
- super + error_and_help_text(attribute, options)
- end
- # rubocop:enable Metrics/ParameterLists
-
- # 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
- def collection_radio_buttons(attribute, collection, value_attribute, text_attribute, options = {}, html_options = {})
- super + error_and_help_text(attribute, options)
- end
- # rubocop:enable Metrics/ParameterLists
-
- def create_language_selector(locales, tabs_id, name)
- if locales.count > 4
- language_selector_select(locales, tabs_id, name)
- else
- language_tabs(locales, tabs_id, name)
- end
- end
-
- # 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.
- 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), required: options[:required])
-
- 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
-
- 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
-
- # 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.
- 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
-
- # 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.
- 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), required: options[:required])
-
- 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
-
- # 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
- # :editor_images - Allow attached images (optional) (default: false)
- #
- # Renders a container with both hidden field and editor container
- 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]
- }.merge(editor_images_options(options)), style: "height: #{lines}rem")
- template += error_for(name, options) if error?(name)
- template.html_safe
- end
- end
-
- # 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.
- 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
-
- # 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.
- 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
-
- # 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.
- 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
-
- # 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.
- 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 += "" 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
-
- # 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.
- 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
-
- # Public: Override so checkboxes are rendered before the label.
- 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
-
- # Public: Override so the date fields are rendered using foundation
- # datepicker library
- 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
-
- # Public: Generates a timepicker field using foundation
- # datepicker library
- def datetime_field(attribute, options = {})
- value = object.send(attribute)
- data = { datepicker: "", timepicker: "" }
- if value.present?
- case value
- when ActiveSupport::TimeWithZone
- data[:startdate] = I18n.l(value, format: :decidim_short)
- when Time, DateTime
- data[:startdate] = I18n.l(value.in_time_zone(Time.zone), format: :decidim_short)
- end
- end
- 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
-
- # Public: Generates a file upload field for Decidim::Attachment type of attachment.
- # It is similar to upload method, but it changes some options so that attachment can
- # have title and different upload validations.
- #
- # attribute - The String name of the attribute to build the field.
- # options - A Hash with options to build the field. See upload method for
- # more detailed information.
- def attachment(attribute, options = {})
- object_attachment = object.attachment.present?
- record = object_attachment ? object.attachment : object
- options = {
- titled: true,
- resource_class: "Decidim::Attachment",
- show_current: false,
- max_file_size: max_file_size(record, :file),
- label: I18n.t("decidim.forms.upload.labels.add_attachment"),
- button_edit_label: I18n.t("decidim.forms.upload.labels.edit_image"),
- extension_allowlist: Decidim.organization_settings(record).upload_allowed_file_extensions
- }.merge(options)
-
- # Upload help uses extension allowlist from the options so we need to call this AFTER setting the defaults.
- options[:help] = upload_help(record, attribute, options) if options[:help].blank?
-
- upload(attribute, options)
- end
-
- # 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 button to replace the file.
- #
- # attribute - The String name of the attribute to build the field.
- # options - A Hash with options to build the field.
- # * max_file_size: Maximum size for the file (If you really want to change max
- # file size you should probably change it in validator).
- # * resouce_name: Name of the resource (e.g. user)
- # * resource_class: Attribute's resource class (e.g. Decidim::User)
- # * resouce_class: Class of the resource (e.g. user)
- # * required: Whether the file is required or not (false by default).
- # * titled: Whether the file can have title or not.
- # * show_current: Whether the current file is displayed next to the button.
- # * help: Array of help messages which are displayed inside of the upload modal.
- # * dimensions_info: Hash about resize dimensions (e.g. {:medium=>{:processor=>:resize_to_fit, :dimensions=>[600, 160]}})
- # produces 'Resized to fit 600 x 160 px.'
- # * extension_allowlist: Array of allowed file extensions (e.g. %w(jpeg jpg png))
- # * label: Label for the attribute
- # * button_label: Label for the button
- # * button_edit_label: Button label when file is already selected.
- def upload(attribute, options = {})
- self.multipart = true
- max_file_size = options[:max_file_size] || max_file_size(object, attribute)
- button_label = options[:button_label] || choose_button_label(attribute)
- help_messages = options[:help] || upload_help(object, attribute, options)
-
- options = {
- attribute: attribute,
- resource_name: @object_name,
- resource_class: options[:resource_class]&.to_s || resource_class(attribute),
- required: false,
- titled: false,
- show_current: true,
- max_file_size: max_file_size,
- help: help_messages,
- label: label_for(attribute),
- button_label: button_label,
- button_edit_label: I18n.t("decidim.forms.upload.labels.replace")
- }.merge(options)
-
- ::Decidim::ViewModel.cell(
- "decidim/upload_modal",
- self,
- options
- ).call
- end
-
- def max_file_size(record, attribute)
- Decidim::FileValidatorHumanizer.new(record, attribute).max_file_size
- end
-
- def choose_button_label(attribute)
- @choose_button_label ||= begin
- if resource_class(attribute).attached_config[attribute].uploader <= Decidim::ImageUploader
- I18n.t("decidim.forms.upload.labels.add_image")
- else
- I18n.t("decidim.forms.upload.labels.add_file")
- end
- rescue NoMethodError
- I18n.t("decidim.forms.upload.labels.add_file")
- end
- end
-
- def upload_help(record, attribute, options = {})
- humanizer = FileValidatorHumanizer.new(record, attribute)
-
- help_scope = 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
-
- help_messages = if options[:help_i18n_messages].present?
- Array(options[:help_i18n_messages])
- else
- %w(message_1 message_2)
- end
-
- help_messages = help_messages.each.map { |msg| I18n.t(msg, scope: help_scope) } + humanizer.messages
- help_messages += extension_allowlist_help(options[:extension_allowlist]) if options[:extension_allowlist]
- help_messages += image_dimensions_help(options[:dimensions_info]) if options[:dimensions_info]
- help_messages
- end
-
- # Public: Returns the translated name for the given attribute.
- #
- # attribute - The String name of the attribute to return the name.
- 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
-
- def form_field_for(attribute, options = {})
- if attribute == :body
- text_area(attribute, options.merge(rows: 10))
- else
- text_field(attribute, options)
- end
- end
-
- # Discard the pattern attribute which is not allowed for textarea elements.
- 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
-
- private
-
- # 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 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
- def field(attribute, options, html_options = nil, &block)
- label = options.delete(:label)
- label_options = options.delete(:label_options) || {}
- custom_label(attribute, label, { required: options[:required] }.merge(label_options)) do
- field_with_validations(attribute, options, html_options, &block)
- end
- end
-
- # 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.
- 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
-
- # rubocop: disable Metrics/CyclomaticComplexity
- # rubocop: disable Metrics/PerceivedComplexity
-
- # 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.
- 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
- # rubocop: enable Metrics/CyclomaticComplexity
- # rubocop: enable Metrics/PerceivedComplexity
-
- # Private: Tries to find if an attribute is required in the form object.
- #
- # Returns Boolean.
- 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
-
- def string_or_symbol?(obj)
- obj.is_a?(String) || obj.is_a?(Symbol)
- end
-
- # 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.
- 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 Object.const_defined?("ProposalLengthValidator")
- if length_validator
- length = length_validator.options[type]
- return length.call(object) if length.respond_to?(:call)
-
- return length
- end
-
- nil
- end
-
- # Private: Finds a validator.
- #
- # attribute - The attribute to validate.
- # klass - The Class of the validator to find.
- #
- # Returns a klass object.
- def find_validator(attribute, klass)
- return unless object.respond_to?(:_validators)
-
- object._validators[attribute.to_sym].find { |validator| validator.instance_of?(klass) }
- end
-
- # 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
- def custom_label(attribute, text, options, field_before_label: false, show_required: true)
- return block_given? ? yield.html_safe : "".html_safe if text == false
-
- required = options.is_a?(Hash) && options.delete(:required)
- text = default_label_text(object, attribute) if text.nil? || text == true
- if show_required
- text +=
- if required
- required_indicator
- else
- required_for_attribute(attribute)
- end
- end
-
- text = if field_before_label && block_given?
- safe_join([yield, text.html_safe])
- elsif block_given?
- safe_join([text.html_safe, yield])
- else
- text
- end
-
- label(attribute, text, options || {})
- end
- # rubocop:enable Metrics/PerceivedComplexity
- # rubocop:enable Metrics/CyclomaticComplexity
-
- # 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.
- 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
-
- 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
-
- def disabled_categories_for(scope)
- scope.first_class.joins(:subcategories).pluck(:id)
- end
-
- def tab_element_class_for(type, index)
- element_class = "tabs-#{type}"
- element_class += " is-active" if index.zero?
- element_class
- end
-
- 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
-
- def name_with_locale(name, locale)
- "#{name}_#{locale.to_s.gsub("-", "__")}"
- end
-
- 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 +=
- if options.delete(:required)
- required_indicator
- else
- required_for_attribute(attribute)
- end
-
- label(attribute, (text || "").html_safe, options)
- end
-
- def required_for_attribute(attribute)
- return required_indicator if attribute_required?(attribute)
-
- "".html_safe
- end
-
- def required_indicator
- visible_title = content_tag(:span, "*", "aria-hidden": true)
- screenreader_title = content_tag(
- :span,
- I18n.t("required", scope: "forms"),
- class: "show-for-sr"
- )
- content_tag(
- :span,
- visible_title + screenreader_title,
- title: I18n.t("required", scope: "forms"),
- data: { tooltip: true, disable_hover: false, keep_on_hover: true },
- class: "label-required"
- ).html_safe
- end
-
- # Private: Returns an array of scopes related to object attribute
- 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
-
- # 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