Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Free text field for multiple choice answers #3134

Merged
merged 19 commits into from
Apr 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ controller or added a new module you need to rename `feature` to `component`.
- **decidim-proposals**: Add discard draft button in wizard [\#3064](https://github.com/decidim/decidim/pull/3064)
- **decidim-surveys**: Allow multiple choice questions to specify a maximum number of options to be checked [\#3091](https://github.com/decidim/decidim/pull/3091)
- **decidim-surveys**: Client side survey errors are now displayed [\#3133](https://github.com/decidim/decidim/pull/3133)
- **decidim-surveys**: Allow multiple choice questions to have "free text options" where the user can customize the selected answer [\#3134](https://github.com/decidim/decidim/pull/3134)

**Changed**:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// = link decidim/surveys/surveys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
((exports) => {
class OptionAttachedInputsComponent {
constructor(options = {}) {
this.wrapperField = options.wrapperField;
this.controllerFieldSelector = options.controllerFieldSelector;
this.dependentInputSelector = options.dependentInputSelector;
this.controllerSelector = this.wrapperField.find(this.controllerFieldSelector);
this._bindEvent();
this._run();
}

_run() {
this.controllerSelector.each((idx, el) => {
const $field = $(el);
const enabled = $field.is(":checked");

$field.parents("label").find(this.dependentInputSelector).prop("disabled", !enabled);
});
}

_bindEvent() {
this.controllerSelector.on("change", () => {
this._run();
});
}
}

exports.Decidim = exports.Decidim || {};
exports.Decidim.createOptionAttachedInputs = (options) => {
return new OptionAttachedInputsComponent(options);
};
})(window);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// = require ./option_attached_inputs.component

((exports) => {
const { createOptionAttachedInputs } = exports.Decidim;

$(".radio-button-collection, .check-box-collection").each((idx, el) => {
createOptionAttachedInputs({
wrapperField: $(el),
controllerFieldSelector: "input[type=radio], input[type=checkbox]",
dependentInputSelector: "input[type=text], input[type=hidden]"
});
});
})(window);
53 changes: 36 additions & 17 deletions decidim-surveys/app/commands/decidim/surveys/admin/update_survey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,46 @@ def call

def update_survey_questions
@form.questions.each do |form_question|
question_attributes = {
body: form_question.body,
description: form_question.description,
position: form_question.position,
mandatory: form_question.mandatory,
question_type: form_question.question_type,
answer_options: form_question.answer_options_to_persist.map { |option| { "body" => option.body } },
max_choices: form_question.max_choices
}
update_survey_question(form_question)
end
end

def update_survey_question(form_question)
question_attributes = {
body: form_question.body,
description: form_question.description,
position: form_question.position,
mandatory: form_question.mandatory,
question_type: form_question.question_type,
max_choices: form_question.max_choices
}

if form_question.id.present?
question = @survey.questions.find_by(id: form_question.id)
if form_question.deleted?
question.destroy!
else
question.update!(question_attributes)
end
update_nested_model(form_question, question_attributes, @survey.questions) do |question|
form_question.answer_options.each do |form_answer_option|
answer_option_attributes = {
body: form_answer_option.body,
free_text: form_answer_option.free_text
}

update_nested_model(form_answer_option, answer_option_attributes, question.answer_options)
end
end
end

def update_nested_model(form, attributes, parent_association)
record = parent_association.find_by(id: form.id) || parent_association.build(attributes)

yield record if block_given?

if record.persisted?
if form.deleted?
record.destroy!
else
@survey.questions.create!(question_attributes)
record.assign_attributes(attributes)
end
end

record.save!
end

def update_survey
Expand Down
15 changes: 12 additions & 3 deletions decidim-surveys/app/commands/decidim/surveys/answer_survey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,22 @@ def call
def answer_survey
SurveyAnswer.transaction do
@form.answers.each do |form_answer|
SurveyAnswer.create!(
answer = SurveyAnswer.new(
user: @current_user,
survey: @survey,
question: form_answer.question,
body: form_answer.body,
choices: form_answer.choices
body: form_answer.body
)

form_answer.selected_choices.each do |choice|
answer.choices.build(
body: choice.body,
custom_body: choice.custom_body,
decidim_survey_answer_option_id: choice.answer_option_id
)
end

answer.save!
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ class SurveyForm < Decidim::Form
attribute :questions, Array[SurveyQuestionForm]

validates :title, :tos, translatable_presence: true

def map_model(model)
self.questions = model.questions.map do |question|
SurveyQuestionForm.from_model(question)
end
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ module Admin
class SurveyQuestionAnswerOptionForm < Decidim::Form
include TranslatableAttributes

attribute :deleted, Boolean
attribute :deleted, Boolean, default: false
attribute :free_text, Boolean

translatable_attribute :body, String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,6 @@ class SurveyQuestionForm < Decidim::Form
validates :max_choices, numericality: { only_integer: true, greater_than: 1, less_than_or_equal_to: ->(form) { form.number_of_options } }, allow_blank: true
validates :body, translatable_presence: true, unless: :deleted

def map_model(model)
self.answer_options = model.answer_options.each_with_index.map do |option, id|
SurveyQuestionAnswerOptionForm.new(option.merge(id: id + 1, deleted: false))
end
end

def answer_options_to_persist
answer_options.reject(&:deleted)
end

def to_param
id || "survey-question-id"
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module Decidim
module Surveys
# This class holds a Form to update survey question answer options
class SurveyAnswerChoiceForm < Decidim::Form
attribute :body, String
attribute :custom_body, String
attribute :answer_option_id, Integer

validates :answer_option_id, presence: true
end
end
end
14 changes: 11 additions & 3 deletions decidim-surveys/app/forms/decidim/surveys/survey_answer_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ class SurveyAnswerForm < Decidim::Form

attribute :question_id, String
attribute :body, String
attribute :choices, Array[String]
attribute :choices, Array[SurveyAnswerChoiceForm]

validates :body, presence: true, if: :mandatory_body?
validates :choices, presence: true, if: :mandatory_choices?
validates :selected_choices, presence: true, if: :mandatory_choices?

validate :max_answers, if: -> { question.max_choices }

Expand All @@ -33,6 +33,14 @@ def label
# Returns nothing.
def map_model(model)
self.question_id = model.decidim_survey_question_id

self.choices = model.choices.map do |choice|
SurveyAnswerChoiceForm.from_model(choice)
end
end

def selected_choices
choices.select(&:body)
end

private
Expand All @@ -42,7 +50,7 @@ def survey
end

def max_answers
errors.add(:choices, :too_many) if choices.size > question.max_choices
errors.add(:choices, :too_many) if selected_choices.size > question.max_choices
end

def mandatory_label
Expand Down
2 changes: 1 addition & 1 deletion decidim-surveys/app/forms/decidim/surveys/survey_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class SurveyForm < Decidim::Form
# Returns nothing.
def map_model(model)
self.answers = model.questions.each_with_index.map do |question, id|
SurveyAnswerForm.new(id: id + 1, question_id: question.id)
SurveyAnswerForm.from_model(SurveyAnswer.new(id: id + 1, question: question))
end
end
end
Expand Down
6 changes: 6 additions & 0 deletions decidim-surveys/app/models/decidim/surveys/survey_answer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ class SurveyAnswer < Surveys::ApplicationRecord
belongs_to :survey, class_name: "Survey", foreign_key: "decidim_survey_id"
belongs_to :question, class_name: "SurveyQuestion", foreign_key: "decidim_survey_question_id"

has_many :choices,
class_name: "SurveyAnswerChoice",
foreign_key: "decidim_survey_answer_id",
dependent: :destroy,
inverse_of: :answer

validates :body, presence: true, if: -> { question.mandatory_body? }
validates :options, presence: true, if: -> { question.mandatory_choices? }

Expand Down
15 changes: 15 additions & 0 deletions decidim-surveys/app/models/decidim/surveys/survey_answer_choice.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module Decidim
module Surveys
class SurveyAnswerChoice < Surveys::ApplicationRecord
belongs_to :answer,
class_name: "SurveyAnswer",
foreign_key: "decidim_survey_answer_id"

belongs_to :answer_option,
class_name: "SurveyAnswerOption",
foreign_key: "decidim_survey_answer_option_id"
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module Decidim
module Surveys
class SurveyAnswerOption < Surveys::ApplicationRecord
belongs_to :question, class_name: "SurveyQuestion", foreign_key: "decidim_survey_question_id"
end
end
end
6 changes: 6 additions & 0 deletions decidim-surveys/app/models/decidim/surveys/survey_question.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ class SurveyQuestion < Surveys::ApplicationRecord

belongs_to :survey, class_name: "Survey", foreign_key: "decidim_survey_id"

has_many :answer_options,
class_name: "SurveyAnswerOption",
foreign_key: "decidim_survey_question_id",
dependent: :destroy,
inverse_of: :question

validates :question_type, inclusion: { in: TYPES }

def multiple_choice?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,21 @@
)
%>
</div>

<div class="row column">
<%=
form.check_box(
:free_text,
label: t(".free_text"),
disabled: !survey.questions_editable?
)
%>
</div>
</div>

<% if answer_option.persisted? %>
<%= form.hidden_field :id, disabled: !editable %>
<% end %>

<%= form.hidden_field :deleted, value: false, disabled: !editable %>
<%= form.hidden_field :deleted, disabled: !editable %>
</div>
56 changes: 45 additions & 11 deletions decidim-surveys/app/views/decidim/surveys/surveys/_answer.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<% field_id = "survey_#{survey.id}_question_#{answer.question.id}_answer_body" %>
<% field_id = "survey_answers_#{answer_idx}" %>
<%= label_tag field_id, answer.label, class: "survey-question" %>

<% if translated_attribute(answer.question.description).present? %>
Expand All @@ -9,25 +9,59 @@

<% case answer.question.question_type %>
<% when "short_answer" %>
<%= answer_form.text_field :body, label: false, id: field_id %>
<%= answer_form.text_field :body, label: false, id: "#{field_id}_body" %>
<% when "long_answer" %>
<%= answer_form.text_area :body, label: false, id: field_id, rows: 10 %>
<%= answer_form.text_area :body, label: false, id: "#{field_id}_body", rows: 10 %>
<% when "single_option" %>
<div id="<%= field_id %>_answer_options" class="radio-button-collection">
<div class="radio-button-collection">
<% choice = answer.choices.first %>

<% answer.question.answer_options.each_with_index do |answer_option, idx| %>
<%= label_tag "#{field_id}_option_#{idx}" do %>
<%= radio_button_tag "survey[answers][#{answer_idx}][choices][]", translated_attribute(answer_option["body"]), answer.choices.include?(translated_attribute(answer_option["body"])), id: "#{field_id}_option_#{idx}" %>
<%= translated_attribute(answer_option["body"]) %>
<% choice_id = "#{field_id}_choices_#{idx}" %>

<%= label_tag "#{choice_id}_body" do %>
<%= radio_button_tag "survey[answers][#{answer_idx}][choices][][body]",
translated_attribute(answer_option.body),
choice.try(:body),
id: "#{choice_id}_body" %>

<%= translated_attribute(answer_option.body) %>

<% if answer_option.free_text %>
<%= text_field_tag "survey[answers][#{answer_idx}][choices][][custom_body]",
choice.try(:custom_body),
id: "#{choice_id}_custom_body",
disabled: true %>
<% end %>

<%= hidden_field_tag "survey[answers][#{answer_idx}][choices][][answer_option_id]",
answer_option.id,
id: "#{choice_id}_answer_option",
disabled: true %>
<% end %>
<% end %>
</div>
<% when "multiple_option" %>
<div id="<%= field_id %>_answer_options" class="check-box-collection">
<div class="check-box-collection">
<% answer.question.answer_options.each_with_index do |answer_option, idx| %>
<%= label_tag "#{field_id}_option_#{idx}" do %>
<%= check_box_tag "survey[answers][#{answer_idx}][choices][]", translated_attribute(answer_option["body"]), answer.choices.include?(translated_attribute(answer_option["body"])), id: "#{field_id}_option_#{idx}" %>
<%= translated_attribute(answer_option["body"]) %>
<% choice = answer.selected_choices.find { |choice| choice.answer_option_id == answer_option.id } %>

<%= label_tag "#{field_id}_choices_#{idx}" do %>
<%= check_box_tag "survey[answers][#{answer_idx}][choices][#{idx}][body]",
translated_attribute(answer_option.body),
choice.present? %>

<%= translated_attribute(answer_option.body) %>

<% if answer_option.free_text %>
<%= text_field_tag "survey[answers][#{answer_idx}][choices][#{idx}][custom_body]",
choice.try(:custom_body),
disabled: true %>
<% end %>

<%= hidden_field_tag "survey[answers][#{answer_idx}][choices][#{idx}][answer_option_id]", answer_option.id %>
<% end %>

<% end %>
</div>
<% end %>
Expand Down
Loading