# frozen_string_literal: true require_relative '../css_classes_manager' module QuestionsCrafter module Ui module Components class FormBuilder < ::Phlex::HTML include Phlex::Rails::Helpers::FormWith include Phlex::Rails::Helpers::Checkbox include ActionView::Helpers::TextHelper # For pluralize def initialize(questions_object:, form_url:, form_method:, read_only:, render_top_error_bar: false, custom_html_classes: {}) @questions_object = questions_object @class_prefix = QuestionsCrafter::Utils.html_class_name_prefix(questions: questions_object) @errors = nil if @questions_object.changed? && @questions_object.invalid? # TODO: The changed? method is not a strong one.. check if there's a better way @errors = @questions_object.errors end @form_url = form_url @form_method = form_method @read_only = read_only @render_top_error_bar = render_top_error_bar @css_manager = CssClassesManager.new(custom_html_classes) end def errors @errors || [] end def read_only @read_only end def questions_object @questions_object end def render_top_error_bar @render_top_error_bar end def css_classes(section, key = nil) @css_manager.get(section, key) end private def required_fields_message_notice div(class: css_classes(:required_fields_notice, :container)) do # TODO: Localize p(class: css_classes(:required_fields_notice, :text)) { "* Indica campo requerido" } end end def view_template form_wrapper_class = css_classes(:form, :wrapper) div(class: "#{form_wrapper_class} questions-#{form_wrapper_class}") do div do render_errors if errors.any? && render_top_error_bar end div do form end end div(class: css_classes(:form, :back_link_wrapper)) do a(href: @questions_object.class.back_link, class: css_classes(:back_link)) { "Back" } end end # TODO: FOR NOW we disable turbo for the submitting of this form, how we'll implement this will be a matter of a next iteration def form form_with(model: @questions_object, url: @form_url, method: @form_method, class: css_classes(:form, :main), data: { turbo: false }, html: { readonly: read_only }) do |form| required_fields_message_notice div(class: css_classes(:form, :fields_wrapper)) do render_fields(form: form) end unless read_only div(class: css_classes(:form, :submit_wrapper)) do form.submit("Enviar", class: css_classes(:submit_button)) end end end end def render_fields(form:) @questions_object.questions.each do |question| render_question(question: question, form: form) end end def render_question(form:, question:) div(class: css_classes(:question, :wrapper)) do label(for: "sign_up_questions_#{question[:name]}", class: css_classes(:question, :label)) do plain question[:title] if question[:required] # TODO: allow the required class span(class: css_classes(:question, :required_notice)) { " *" } end end case question[:type] when :string render_string(form: form, question: question) when :text render_text(form: form, question: question) when :radio render_radio(form: form, question: question) when :boolean render_boolean(form: form, question: question) when :integer render_integer(form: form, question: question) when :select render_select(form: form, question: question) when :checkbox_group render_checkbox_group(form: form, question: question) else # Fallback for any other type render_string(form: form, question: question) end if question[:description].present? div(class: css_classes(:question, :description)) { question[:description] } end if errors.include?(question[:name]) # TODO: Are we only showing one error here? div(class: css_classes(:question, :error)) { errors[question[:name]].first } end end end def render_string(form:, question:) input( type: "text", name: "sign_up_questions[#{question[:name]}]", id: "sign_up_questions_#{question[:name]}", class: css_classes(:input_fields, :text_input), placeholder: "Enter your #{question[:title].downcase}", value: question_value(question: question), readonly: read_only, disabled: read_only ) end def render_text(form:, question:) textarea( name: "sign_up_questions[#{question[:name]}]", id: "sign_up_questions_#{question[:name]}", class: css_classes(:input_fields, :textarea), placeholder: "Enter your #{question[:title].downcase}", rows: 3, readonly: read_only, disabled: read_only ) do question_value(question: question) end end # TODO: Make this a select with options or make a different type :radio_options or :select_options def render_radio(form:, question:) div(class: css_classes(:radio_group, :container)) do question[:options].each do |value, label| wrapper_class = question[:options].size <= 3 ? css_classes(:radio_group, :item_wrapper_inline) : css_classes(:radio_group, :item_wrapper) div(class: wrapper_class) do input( type: "radio", name: "sign_up_questions[#{question[:name]}]", id: "sign_up_questions_#{question[:name]}_#{value}", value: value, class: css_classes(:radio_group, :input), checked: question_value(question: question) == value, disabled: read_only ) label( for: "sign_up_questions_#{question[:name]}_#{value}", class: css_classes(:radio_group, :label) ) { label } end end end end def render_boolean(form:, question:) div(class: css_classes(:checkbox, :wrapper)) do input( type: "checkbox", name: "sign_up_questions[#{question[:name]}]", id: "sign_up_questions_#{question[:name]}", value: "1", class: css_classes(:checkbox, :input), checked: question_value(question: question).present?, disabled: read_only ) label( for: "sign_up_questions_#{question[:name]}", class: css_classes(:checkbox, :label) ) { question[:description] || "Yes" } end end def render_integer(form:, question:) input( type: "number", name: "sign_up_questions[#{question[:name]}]", id: "sign_up_questions_#{question[:name]}", class: css_classes(:input_fields, :number_input), value: question_value(question: question), readonly: read_only, disabled: read_only ) end def render_select(form:, question:) select( name: "sign_up_questions[#{question[:name]}]", id: "sign_up_questions_#{question[:name]}", class: css_classes(:input_fields, :select), disabled: read_only ) do option(value: "", disabled: true, selected: question_value(question: question).blank?) { "Seleccione una opci\u00f3n" } question[:options].each do |value, label| option(value: value, selected: question_value(question: question) == value) { label } end end end def render_checkbox_group(form:, question:) div(class: css_classes(:checkbox_group, :container)) do question[:options].each do |value, label| div(class: css_classes(:checkbox_group, :item_wrapper)) do input( type: "checkbox", name: "sign_up_questions[#{question[:name]}][]", id: "sign_up_questions_#{question[:name]}_#{value}", value: value, class: css_classes(:checkbox_group, :input), checked: Array(question_value(question: question)).include?(value), disabled: read_only ) label( for: "sign_up_questions_#{question[:name]}_#{value}", class: css_classes(:checkbox_group, :label) ) { label } end end end end def question_value(question:) value = questions_object.send(question[:name]) return value unless question[:type] == :checkbox_group normalize_checkbox_group_value(value) end def normalize_checkbox_group_value(value) case value when Array value when String if value.start_with?("[") && value.end_with?("]") JSON.parse(value) rescue [] else value.present? ? [ value ] : [] end when NilClass, FalseClass [] else Array(value.presence) end end def render_errors div(class: css_classes(:error_display, :container)) do h4(class: css_classes(:error_display, :title)) { "#{pluralize(@questions_object.errors.count, 'error')} prohibited this form from being saved:" } ul(class: css_classes(:error_display, :list)) do @questions_object.errors.full_messages.each do |message| li { message } end end end end end end end end