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

More survey improvements #3133

Merged
merged 22 commits into from
Apr 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e90d1ed
Remove try's from survey template
deivid-rodriguez Apr 3, 2018
a552dde
Move enabling logic out of component
deivid-rodriguez Apr 3, 2018
38e8afe
Simplify and refactor some logic
deivid-rodriguez Apr 3, 2018
f514736
Extract answer rendering to a partial
deivid-rodriguez Apr 3, 2018
c1e215a
Remove now unneeded early returns
deivid-rodriguez Apr 3, 2018
cb89891
Slightly improve a test
deivid-rodriguez Apr 3, 2018
12a0ff2
Test that answer information is properly saved
deivid-rodriguez Apr 3, 2018
029a465
Split different answer types to separate columns
deivid-rodriguez Apr 3, 2018
8b80423
Prefer symbols for adding errors to an attribute
deivid-rodriguez Apr 3, 2018
759e414
Ensure numbering is kept after errors
deivid-rodriguez Apr 3, 2018
e0f4d8d
Fix question type select incorrectly disabled
deivid-rodriguez Apr 3, 2018
e3e32a0
Better block variable name
deivid-rodriguez Apr 3, 2018
95dffa7
Refactor survey answer form specs
deivid-rodriguez Apr 3, 2018
e513a27
Consolidate survey answer validations
deivid-rodriguez Apr 3, 2018
3a13d5c
Use proper forms around answers
deivid-rodriguez Apr 3, 2018
9c2a442
Extract repeated method call to a variable
deivid-rodriguez Apr 5, 2018
6adeee0
Normalize comment spacing
deivid-rodriguez Apr 5, 2018
810071f
Fix incorrect disabling of max choices selector
deivid-rodriguez Apr 5, 2018
a47160c
Fix incorrect disabling of translated fields
deivid-rodriguez Apr 5, 2018
8977a53
Fix bad migration
deivid-rodriguez Apr 5, 2018
0b317e1
Proper column type conversion
deivid-rodriguez Apr 5, 2018
665c5e4
Normalize `input[type=*]` selectors
deivid-rodriguez Apr 4, 2018
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ controller or added a new module you need to rename `feature` to `component`.
- **decidim-surveys**: Add rich text description to questions [\#3066](https://github.com/decidim/decidim/pull/3066).
- **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)

**Changed**:

Expand Down Expand Up @@ -67,5 +68,8 @@ controller or added a new module you need to rename `feature` to `component`.
- **decidim-surveys**: Multiple choice questions without answer options can no longer be created [\#3087](https://github.com/decidim/decidim/pull/3087)
- **decidim-surveys**: Multiple choice questions with empty answer options can no longer be created [\#3087](https://github.com/decidim/decidim/pull/3087)
- **decidim-surveys**: Preserve deleted status of questions accross submission failures [\#3089](https://github.com/decidim/decidim/pull/3089)
- **decidim-surveys**: Question type selector not disabled when survey has already been answered [\#3133](https://github.com/decidim/decidim/pull/3133)
- **decidim-surveys**: Max choices selector not disabled when survey has already been answered [\#3133](https://github.com/decidim/decidim/pull/3133)
- **decidim-surveys**: Translated fields not disabled when survey has already been answered [\#3133](https://github.com/decidim/decidim/pull/3133)

Please check [0.10-stable](https://github.com/decidim/decidim/blob/0.10-stable/CHANGELOG.md) for previous changes.
5 changes: 5 additions & 0 deletions decidim-core/app/assets/javascripts/decidim/editor.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

const createQuillEditor = (container) => {
const toolbar = $(container).data("toolbar");
const disabled = $(container).data("disabled");

let quillToolbar = [
["bold", "italic", "underline"],
Expand Down Expand Up @@ -35,6 +36,10 @@
theme: "snow"
});

if (disabled) {
quill.disable();
}

quill.on("text-change", () => {
const text = quill.getText();

Expand Down
9 changes: 6 additions & 3 deletions decidim-core/lib/decidim/form_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,25 @@ def translated(type, name, options = {})
#
# 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)
# :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)
# :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
def editor(name, options = {})
options[:toolbar] ||= "basic"
options[:lines] ||= 10
options[:disabled] ||= false

content_tag(:div, class: "editor") do
template = ""
template += label(name, options[:label].to_s || name) if options[:label] != false
template += hidden_field(name, options)
template += content_tag(:div, nil, class: "editor-container", data: {
toolbar: options[:toolbar]
toolbar: options[:toolbar],
disabled: options[:disabled]
}, style: "height: #{options[:lines]}rem")
template += error_for(name, options) if error?(name)
template.html_safe
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
this.wrapperSelector = options.wrapperSelector;
this.dependentFieldsSelector = options.dependentFieldsSelector;
this.dependentInputSelector = options.dependentInputSelector;
this.enablingValues = options.enablingValues;
this.enablingCondition = options.enablingCondition;
this._bindEvent();
this._run();
}
Expand All @@ -14,9 +14,8 @@
const $controllerField = this.controllerField;
const $dependentFields = $controllerField.parents(this.wrapperSelector).find(this.dependentFieldsSelector);
const $dependentInputs = $dependentFields.find(this.dependentInputSelector);
const value = $controllerField.val();

if (this.enablingValues.indexOf(value) > -1) {
if (this.enablingCondition($controllerField)) {
$dependentInputs.prop("disabled", false);
$dependentFields.show();
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@

const dynamicFieldsForAnswerOptions = {};

const isMultipleChoiceOption = ($selectField) => {
const value = $selectField.val();

return value === "single_option" || value === "multiple_option"
}

const setupInitialQuestionAttributes = ($target) => {
const fieldId = $target.attr("id");
const $fieldQuestionTypeSelect = $target.find(questionTypeSelector);
Expand All @@ -90,25 +96,27 @@
wrapperSelector: fieldSelector,
dependentFieldsSelector: answerOptionsWrapperSelector,
dependentInputSelector: `${answerOptionFieldSelector} input`,
enablingValues: ["single_option", "multiple_option"]
enablingCondition: ($field) => {
return isMultipleChoiceOption($field);
}
});

createFieldDependentInputs({
controllerField: $fieldQuestionTypeSelect,
wrapperSelector: fieldSelector,
dependentFieldsSelector: maxChoicesWrapperSelector,
dependentInputSelector: "select",
enablingValues: ["multiple_option"]
enablingCondition: ($field) => {
return $field.val() === "multiple_option"
}
});

dynamicFieldsForAnswerOptions[fieldId] = createDynamicFieldsForAnswerOptions(fieldId);

const dynamicFields = dynamicFieldsForAnswerOptions[fieldId];

const onQuestionTypeChange = () => {
const value = $fieldQuestionTypeSelect.val();

if (value === "single_option" || value === "multiple_option") {
if (isMultipleChoiceOption($fieldQuestionTypeSelect)) {
const nOptions = $fieldQuestionTypeSelect.parents(fieldSelector).find(answerOptionFieldSelector).length;

if (nOptions === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def update_survey_questions
position: form_question.position,
mandatory: form_question.mandatory,
question_type: form_question.question_type,
answer_options: form_question.answer_options_to_persist.map { |answer| { "body" => answer.body } },
answer_options: form_question.answer_options_to_persist.map { |option| { "body" => option.body } },
max_choices: form_question.max_choices
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def answer_survey
user: @current_user,
survey: @survey,
question: form_answer.question,
body: form_answer.body
body: form_answer.body,
choices: form_answer.choices
)
end
end
Expand Down
18 changes: 7 additions & 11 deletions decidim-surveys/app/forms/decidim/surveys/survey_answer_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@ class SurveyAnswerForm < Decidim::Form

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

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

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

delegate :mandatory_body?, :mandatory_choices?, to: :question

def question
@question ||= survey.questions.find(question_id)
end

def label
base = "#{question.position + 1}. #{translated_attribute(question.body)}"
base = "#{id}. #{translated_attribute(question.body)}"
base += " #{mandatory_label}" if question.mandatory?
base += " (#{max_choices_label})" if question.max_choices
base
Expand All @@ -38,15 +41,8 @@ def survey
@survey ||= Survey.where(component: current_component).first
end

def body_not_blank
return if body.nil?
errors.add("body", :blank) if body.all?(&:blank?)
end

def max_answers
return if body.nil?

errors.add("body", :too_many_choices) if body.size > question.max_choices
errors.add(:choices, :too_many) if choices.size > question.max_choices
end

def mandatory_label
Expand Down
4 changes: 2 additions & 2 deletions decidim-surveys/app/forms/decidim/surveys/survey_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ class SurveyForm < Decidim::Form
#
# Returns nothing.
def map_model(model)
self.answers = model.questions.map do |question|
SurveyAnswerForm.from_params(question_id: question.id)
self.answers = model.questions.each_with_index.map do |question, id|
SurveyAnswerForm.new(id: id + 1, question_id: question.id)
end
end
end
Expand Down
4 changes: 3 additions & 1 deletion decidim-surveys/app/models/decidim/surveys/survey_answer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ 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"

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

validate :user_survey_same_organization
validate :question_belongs_to_survey

Expand Down
12 changes: 12 additions & 0 deletions decidim-surveys/app/models/decidim/surveys/survey_question.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ class SurveyQuestion < Surveys::ApplicationRecord
belongs_to :survey, class_name: "Survey", foreign_key: "decidim_survey_id"

validates :question_type, inclusion: { in: TYPES }

def multiple_choice?
%w(single_option multiple_option).include?(question_type)
end

def mandatory_body?
mandatory? && !multiple_choice?
end

def mandatory_choices?
mandatory? && multiple_choice?
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="card-divider">
<h2 class="card-title">
<span><%= t(".answer_option") %></span>
<% if survey.questions_editable? %>
<% if editable %>
<button class="button small alert hollow remove-answer-option button--title">
<%= t(".remove") %>
</button>
Expand All @@ -20,15 +20,15 @@
:body,
tabs_id: tabs_id_for_question_answer_option(question, answer_option),
label: t(".statement"),
disabled: !survey.questions_editable?
disabled: !editable
)
%>
</div>
</div>

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

<%= form.hidden_field :deleted, value: false, disabled: !survey.questions_editable? %>
<%= form.hidden_field :deleted, value: false, disabled: !editable %>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<% if survey.questions_editable? %>
<template>
<%= fields_for "survey[questions][#{blank_question.to_param}]", blank_question do |question_form| %>
<%= render "question", form: question_form, id: tabs_id_for_question(blank_question) %>
<%= render "question", form: question_form, id: tabs_id_for_question(blank_question), editable: survey.questions_editable? %>
<% end %>
</template>
<% else %>
Expand All @@ -36,7 +36,7 @@
<div class="survey-questions-list">
<% @form.questions.each do |question| %>
<%= fields_for "survey[questions][]", question do |question_form| %>
<%= render "question", form: question_form, id: tabs_id_for_question(question) %>
<%= render "question", form: question_form, id: tabs_id_for_question(question), editable: survey.questions_editable? %>
<% end %>
<% end %>
</div>
Expand All @@ -46,4 +46,6 @@
<% end %>
</div>

<%= javascript_include_tag "decidim/surveys/admin/surveys" %>
<% if survey.questions_editable? %>
<%= javascript_include_tag "decidim/surveys/admin/surveys" %>
<% end %>
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
<div class="card-divider question-divider">
<h2 class="card-title">
<span>
<% if survey.questions_editable? %>
<% if editable %>
<%== "#{icon("move")} #{t(".question")}" %>
<% else %>
<%= t(".question") %>
<% end %>
</span>

<% if survey.questions_editable? %>
<% if editable %>
<button class="button small alert hollow move-up-question button--title">
<%== "#{icon("arrow-top")} #{t(".up")}" %>
</button>
Expand All @@ -35,7 +35,7 @@
:body,
tabs_id: id,
label: t(".statement"),
disabled: !survey.questions_editable?
disabled: !editable
)
%>
</div>
Expand All @@ -48,7 +48,7 @@
toolbar: :full,
tabs_id: id,
label: t(".description"),
disabled: !survey.questions_editable?
disabled: !editable
)
%>
</div>
Expand All @@ -57,7 +57,7 @@
<%=
form.check_box(
:mandatory,
disabled: !survey.questions_editable?,
disabled: !editable,
label: t("activemodel.attributes.survey_question.mandatory")
)
%>
Expand All @@ -68,34 +68,35 @@
form.select(
:question_type,
options_from_collection_for_select(question_types, :first, :last, question.question_type),
disabled: !survey.questions_editable?
{},
disabled: !editable
)
%>
</div>

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

<%= form.hidden_field :position, value: question.position || 0, disabled: !survey.questions_editable? %>
<%= form.hidden_field :deleted, disabled: !survey.questions_editable? %>
<%= form.hidden_field :position, value: question.position || 0, disabled: !editable %>
<%= form.hidden_field :deleted, disabled: !editable %>

<div class="survey-question-answer-options">
<template>
<%= fields_for "survey[questions][#{question.to_param}][answer_options][]", blank_answer_option do |answer_option_form| %>
<%= render "answer_option", form: answer_option_form, question: question %>
<%= render "answer_option", form: answer_option_form, question: question, editable: editable %>
<% end %>
</template>

<div class="survey-question-answer-options-list">
<% question.answer_options.each do |answer_option| %>
<%= fields_for "survey[questions][#{question.to_param}][answer_options][]", answer_option do |answer_option_form| %>
<%= render "answer_option", form: answer_option_form, question: question %>
<%= render "answer_option", form: answer_option_form, question: question, editable: editable %>
<% end %>
<% end %>
</div>

<% if survey.questions_editable? %>
<% if editable %>
<button class="button add-answer-option"><%= t(".add_answer_option") %></button>
<% end %>
</div>
Expand All @@ -105,8 +106,8 @@
form.select(
:max_choices,
(2..question.number_of_options),
prompt: t(".any"),
disabled: !survey.questions_editable?
{ prompt: t(".any") },
disabled: !editable
)
%>
</div>
Expand Down
Loading