diff --git a/Gemfile b/Gemfile index 109bd9c..c05979c 100644 --- a/Gemfile +++ b/Gemfile @@ -186,7 +186,7 @@ gem "brakeman" # Automatic Ruby code style checking tool. (https://github.com/rubocop-hq/rubocop) - gem "rubocop-dmp_roadmap" + gem "rubocop-dmp_roadmap", ">= 1.1.0" # Helper gem to require bundler-audit (http://github.com/stewartmckee/bundle-audit) gem "bundle-audit" diff --git a/Gemfile.lock b/Gemfile.lock index 827f65f..0f2186a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -315,15 +315,15 @@ rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - rubocop-dmp_roadmap (1.0.0) + rubocop-dmp_roadmap (1.1.0) rubocop (>= 0.58.2) rubocop-rails_config (>= 0.2.2) rubocop-rspec (>= 1.27.0) - rubocop-rails_config (0.2.2) + rubocop-rails_config (0.2.3) railties (>= 3.0) rubocop (~> 0.56) - rubocop-rspec (1.27.0) - rubocop (>= 0.56.0) + rubocop-rspec (1.28.0) + rubocop (>= 0.58.0) ruby-progressbar (1.9.0) ruby_dep (1.5.0) ruby_dig (0.0.2) @@ -435,8 +435,7 @@ responders (~> 2.0) rspec-collection_matchers rspec-rails - rubocop-dmp_roadmap - rubocop-rspec + rubocop-dmp_roadmap (>= 1.1.0) ruby_dig selenium-webdriver (>= 3.13.1) shoulda diff --git a/app/controllers/org_admin/phases_controller.rb b/app/controllers/org_admin/phases_controller.rb index fc8e64b..79a3f97 100644 --- a/app/controllers/org_admin/phases_controller.rb +++ b/app/controllers/org_admin/phases_controller.rb @@ -1,5 +1,9 @@ +# frozen_string_literal: true + module OrgAdmin + class PhasesController < ApplicationController + include Versionable after_action :verify_authorized @@ -9,18 +13,19 @@ phase = Phase.includes(:template, :sections).order(:number).find(params[:id]) authorize phase if !phase.template.latest? - flash[:notice] = _('You are viewing a historical version of this template. You will not be able to make changes.') + # rubocop:disable Metrics/LineLength + flash[:notice] = _("You are viewing a historical version of this template. You will not be able to make changes.") + # rubocop:enable Metrics/LineLength end - section = params.fetch(:section, nil) - render('container', + render("container", locals: { - partial_path: 'show', + partial_path: "show", template: phase.template, phase: phase, prefix_section: phase.prefix_section, sections: phase.template_sections.order(:number), suffix_sections: phase.suffix_sections.order(:number), - current_section: section.present? ? Section.find_by(id: section, phase_id: phase.id) : nil + current_section: Section.find_by(id: params[:section], phase_id: phase.id) }) end @@ -28,20 +33,23 @@ def edit phase = Phase.includes(:template).find(params[:id]) authorize phase - section = params.fetch(:section, nil) # User cannot edit a phase if its a customization so redirect to show if phase.template.customization_of.present? || !phase.template.latest? - redirect_to org_admin_template_phase_path(template_id: phase.template, id: phase.id, section: section) + redirect_to org_admin_template_phase_path( + template_id: phase.template, + id: phase.id, + section: params[:section] + ) else - render('container', + render("container", locals: { - partial_path: 'edit', + partial_path: "edit", template: phase.template, phase: phase, prefix_section: phase.prefix_section, sections: phase.sections.order(:number).select(:id, :title, :modifiable), suffix_sections: phase.suffix_sections.order(:number), - current_section: section.present? ? Section.find_by(id: section, phase_id: phase.id) : nil + current_section: Section.find_by(id: params[:section], phase_id: phase.id) }) end end @@ -51,7 +59,7 @@ def preview phase = Phase.includes(:template).find(params[:id]) authorize phase - render('/org_admin/phases/preview', + render("/org_admin/phases/preview", locals: { template: phase.template, phase: phase @@ -70,15 +78,21 @@ number: (nbr.present? ? nbr + 1 : 1) ) authorize phase - render('/org_admin/templates/container', + local_referrer = if request.referrer.present? + request.referrer + else + org_admin_templates_path + end + render("/org_admin/templates/container", locals: { - partial_path: 'new', + partial_path: "new", template: template, phase: phase, - referrer: request.referrer.present? ? request.referrer : org_admin_templates_path + referrer: local_referrer }) else - render org_admin_templates_path, alert: _('You canot add a phase to a historical version of a template.') + render org_admin_templates_path, + alert: _("You canot add a phase to a historical version of a template.") end end @@ -92,17 +106,18 @@ phase = get_new(phase) phase.modifiable = true if phase.save - flash[:notice] = success_message(_('phase'), _('created')) + flash[:notice] = success_message(_("phase"), _("created")) else - flash[:alert] = failed_create_error(phase, _('phase')) + flash[:alert] = failed_create_error(phase, _("phase")) end rescue StandardError => e - flash[:alert] = _('Unable to create a new version of this template.') + flash[:alert] = _("Unable to create a new version of this template.") end if flash[:alert].present? redirect_to edit_org_admin_template_path(id: phase.template_id) else - redirect_to edit_org_admin_template_phase_path(template_id: phase.template.id, id: phase.id) + redirect_to edit_org_admin_template_phase_path(template_id: phase.template.id, + id: phase.id) end end @@ -114,14 +129,15 @@ begin phase = get_modifiable(phase) if phase.update(phase_params) - flash[:notice] = success_message(_('phase'), _('updated')) + flash[:notice] = success_message(_("phase"), _("updated")) else - flash[:alert] = failed_update_error(phase, _('phase')) + flash[:alert] = failed_update_error(phase, _("phase")) end rescue StandardError => e - flash[:alert] = _('Unable to create a new version of this template.') + flash[:alert] = _("Unable to create a new version of this template.") end - redirect_to edit_org_admin_template_phase_path(template_id: phase.template.id, id: phase.id) + redirect_to edit_org_admin_template_phase_path(template_id: phase.template.id, + id: phase.id) end def sort @@ -140,12 +156,12 @@ phase = get_modifiable(phase) template = phase.template if phase.destroy! - flash[:notice] = success_message(_('phase'), _('deleted')) + flash[:notice] = success_message(_("phase"), _("deleted")) else - flash[:alert] = failed_destroy_error(phase, _('phase')) + flash[:alert] = failed_destroy_error(phase, _("phase")) end rescue StandardError => e - flash[:alert] = _('Unable to create a new version of this template.') + flash[:alert] = _("Unable to create a new version of this template.") end if flash[:alert].present? @@ -156,8 +172,11 @@ end private - def phase_params - params.require(:phase).permit(:title, :description, :number) - end + + def phase_params + params.require(:phase).permit(:title, :description, :number) + end + end + end diff --git a/app/controllers/org_admin/questions_controller.rb b/app/controllers/org_admin/questions_controller.rb index dcdead5..c1968d6 100644 --- a/app/controllers/org_admin/questions_controller.rb +++ b/app/controllers/org_admin/questions_controller.rb @@ -1,66 +1,81 @@ +# frozen_string_literal: true + module OrgAdmin + class QuestionsController < ApplicationController + include Versionable respond_to :html after_action :verify_authorized - # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/question/[:id] def show - question = Question.includes(:annotations, :question_options, section: { phase: :template }).find(params[:id]) + question = Question.includes(:annotations, + :question_options, + section: { phase: :template }) + .find(params[:id]) authorize question - render partial: 'show', locals: { + render partial: "show", locals: { template: question.section.phase.template, section: question.section, question: question } end - # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/question/[:id]/edit def edit - question = Question.includes(:annotations, :question_options, section: { phase: :template }).find(params[:id]) + question = Question.includes(:annotations, + :question_options, + section: { phase: :template }) + .find(params[:id]) authorize question - render partial: 'edit', locals: { + render partial: "edit", locals: { template: question.section.phase.template, section: question.section, question: question } end - # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/questions/new def new section = Section.includes(:questions, phase: :template).find(params[:section_id]) nbr = section.questions.maximum(:number) - question = Question.new({ section_id: section.id, question_format: QuestionFormat.find_by(title: 'Text area'), number: nbr.present? ? nbr + 1 : 1 }) + question_format = QuestionFormat.find_by(title: "Text area") + question = Question.new(section_id: section.id, + question_format: question_format, + number: nbr.present? ? nbr + 1 : 1) authorize question - render partial: 'form', locals: { + render partial: "form", locals: { template: section.phase.template, section: section, question: question, - method: 'post', - url: org_admin_template_phase_section_questions_path(template_id: section.phase.template.id, phase_id: section.phase.id, id: section.id) + method: "post", + url: org_admin_template_phase_section_questions_path( + template_id: section.phase.template.id, + phase_id: section.phase.id, + id: section.id) } end - # POST /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/questions def create - question = Question.new(question_params.merge({ section_id: params[:section_id] })) + question = Question.new(question_params.merge(section_id: params[:section_id])) authorize question begin question = get_new(question) section = question.section if question.save - flash[:notice] = success_message(_('question'), _('created')) + flash[:notice] = success_message(_("question"), _("created")) else - flash[:alert] = failed_create_error(question, _('question')) + flash[:alert] = failed_create_error(question, _("question")) end rescue StandardError => e - flash[:alert] = _('Unable to create a new version of this template.') + flash[:alert] = _("Unable to create a new version of this template.") end - redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id, section: section.id) + redirect_to edit_org_admin_template_phase_path( + template_id: section.phase.template.id, + id: section.phase.id, + section: section.id + ) end - # PUT /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/questions/[:id] def update question = Question.find(params[:id]) authorize question @@ -76,30 +91,29 @@ attrs[:theme_ids] = [] end if question.update(attrs) - flash[:notice] = success_message(_('question'), _('updated')) + flash[:notice] = success_message(_("question"), _("updated")) else - flash[:alert] = failed_update_error(question, _('question')) + flash[:alert] = failed_update_error(question, _("question")) end rescue StandardError => e puts e.message - flash[:alert] = _('Unable to create a new version of this template.') + flash[:alert] = _("Unable to create a new version of this template.") end if question.section.phase.template.customization_of.present? - redirect_to org_admin_template_phase_path({ + redirect_to org_admin_template_phase_path( template_id: question.section.phase.template.id, id: question.section.phase.id, section: question.section.id - }) + ) else - redirect_to edit_org_admin_template_phase_path({ + redirect_to edit_org_admin_template_phase_path( template_id: question.section.phase.template.id, id: question.section.phase.id, section: question.section.id - }) + ) end end - # DELETE /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/questions/[:id] def destroy question = Question.find(params[:id]) authorize question @@ -107,18 +121,18 @@ question = get_modifiable(question) section = question.section if question.destroy! - flash[:notice] = success_message(_('question'), _('deleted')) + flash[:notice] = success_message(_("question"), _("deleted")) else - flash[:alert] = failed_destroy_error(question, 'question') + flash[:alert] = failed_destroy_error(question, "question") end rescue StandardError => e - flash[:alert] = _('Unable to create a new version of this template.') + flash[:alert] = _("Unable to create a new version of this template.") end - redirect_to edit_org_admin_template_phase_path({ + redirect_to edit_org_admin_template_phase_path( template_id: section.phase.template.id, id: section.phase.id, section: section.id - }) + ) end private @@ -140,20 +154,27 @@ if attrs[:annotations_attributes].present? attrs[:annotations_attributes].each_key do |key| old_annotation = question.annotations.select do |a| - a.org_id.to_s == attrs[:annotations_attributes][key][:org_id] && a.type.to_s == attrs[:annotations_attributes][key][:type] + a.org_id.to_s == attrs[:annotations_attributes][key][:org_id] && + a.type.to_s == attrs[:annotations_attributes][key][:type] end - attrs[:annotations_attributes][key][:id] = old_annotation.first.id unless old_annotation.empty? + unless old_annotation.empty? + attrs[:annotations_attributes][key][:id] = old_annotation.first.id + end end end - # TODO: This question_options id swap feel fragile. We cannot really match on any of the - # data elements because the user may have changed them so we rely on its position - # within the array/query since they should be equivalent. + # TODO: This question_options id swap feel fragile. We cannot really match on any + # of the data elements because the user may have changed them so we rely on its + # position within the array/query since they should be equivalent. if attrs[:question_options_attributes].present? attrs[:question_options_attributes].each_key do |key| - attrs[:question_options_attributes][key][:id] = question.question_options[key.to_i].id.to_s if question.question_options[key.to_i].present? + next unless question.question_options[key.to_i].present? + hash = attrs.dig(:question_options_attributes, key) + hash[:id] = question.question_options[key.to_i].id.to_s end end attrs end + end + end diff --git a/app/controllers/org_admin/sections_controller.rb b/app/controllers/org_admin/sections_controller.rb index 25ae97b..d220adb 100644 --- a/app/controllers/org_admin/sections_controller.rb +++ b/app/controllers/org_admin/sections_controller.rb @@ -1,5 +1,9 @@ +# frozen_string_literal: true + module OrgAdmin + class SectionsController < ApplicationController + include Versionable respond_to :html @@ -9,8 +13,10 @@ def index authorize Section.new phase = Phase.includes(:template, :sections).find(params[:phase_id]) - edit = phase.template.latest? && (current_user.can_modify_templates? && (phase.template.org_id == current_user.org_id)) - render partial: 'index', + edit = phase.template.latest? && + (current_user.can_modify_templates? && + (phase.template.org_id == current_user.org_id)) + render partial: "index", locals: { template: phase.template, phase: phase, @@ -27,8 +33,9 @@ def show section = Section.find(params[:id]) authorize section - section = Section.includes(questions: [:annotations, :question_options]).find(params[:id]) - render partial: 'show', locals: { + section = Section.includes(questions: [:annotations, :question_options]) + .find(params[:id]) + render partial: "show", locals: { template: Template.find(params[:template_id]), section: section } @@ -36,10 +43,18 @@ # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:id]/edit def edit - section = Section.includes({ phase: :template }, { questions: [:question_options, { annotations: :org }] }).find(params[:id]) + section = Section.includes(phase: :template, + questions: [:question_options, { annotations: :org }]) + .find(params[:id]) authorize section - # User cannot edit a section if its not modifiable or the template is not the latest redirect to show - render partial: (section.modifiable? && section.phase.template.latest? ? 'edit' : 'show'), + # User cannot edit a section if its not modifiable or the template is not the + # latest redirect to show + partial_name = if section.modifiable? && section.phase.template.latest? + "edit" + else + "show" + end + render partial: partial_name, locals: { template: section.phase.template, phase: section.phase, @@ -56,18 +71,29 @@ begin section = get_new(section) if section.save - flash[:notice] = success_message(_('section'), _('created')) - redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id, section: section.id) + flash[:notice] = success_message(_("section"), _("created")) + redirect_to edit_org_admin_template_phase_path( + id: section.phase.id, + template_id: section.phase.template.id, + section: section.id) else - flash[:alert] = failed_create_error(section, _('section')) - redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id) + flash[:alert] = failed_create_error(section, _("section")) + redirect_to edit_org_admin_template_phase_path( + template_id: section.phase.template.id, + id: section.phase.id + ) end rescue StandardError => e - flash[:alert] = _('Unable to create a new version of this template.') - redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id) + flash[:alert] = _("Unable to create a new version of this template.") + redirect_to edit_org_admin_template_phase_path( + template_id: section.phase.template.id, + id: section.phase.id + ) end else - flash[:alert] = _('Unable to create a new section because the phase you specified does not exist.') + # rubocop:disable Metrics/LineLength + flash[:alert] = _("Unable to create a new section because the phase you specified does not exist.") + # rubocop:enable Metrics/LineLength redirect_to edit_org_admin_template_path(template_id: params[:template_id]) end end @@ -79,18 +105,24 @@ begin section = get_modifiable(section) if section.update(section_params) - flash[:notice] = success_message(_('section'), _('saved')) + flash[:notice] = success_message(_("section"), _("saved")) else - flash[:alert] = failed_update_error(section, _('section')) + flash[:alert] = failed_update_error(section, _("section")) end rescue StandardError => e - flash[:alert] = _('Unable to create a new version of this template.') + flash[:alert] = _("Unable to create a new version of this template.") end if flash[:alert].present? - redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id, section: section.id) + redirect_to edit_org_admin_template_phase_path( + template_id: section.phase.template.id, + id: section.phase.id, section: section.id + ) else - redirect_to edit_org_admin_template_phase_path(template_id: section.phase.template.id, id: section.phase.id, section: section.id) + redirect_to edit_org_admin_template_phase_path( + template_id: section.phase.template.id, + id: section.phase.id, section: section.id + ) end end @@ -102,18 +134,24 @@ section = get_modifiable(section) phase = section.phase if section.destroy! - flash[:notice] = success_message(_('section'), _('deleted')) + flash[:notice] = success_message(_("section"), _("deleted")) else - flash[:alert] = failed_destroy_error(section, _('section')) + flash[:alert] = failed_destroy_error(section, _("section")) end rescue StandardError => e - flash[:alert] = _('Unable to create a new version of this template.') + flash[:alert] = _("Unable to create a new version of this template.") end if flash[:alert].present? - redirect_to(edit_org_admin_template_phase_path(template_id: phase.template.id, id: phase.id)) + redirect_to(edit_org_admin_template_phase_path( + template_id: phase.template.id, + id: phase.id + )) else - redirect_to(edit_org_admin_template_phase_path(template_id: phase.template.id, id: phase.id)) + redirect_to(edit_org_admin_template_phase_path( + template_id: phase.template.id, + id: phase.id + )) end end @@ -122,5 +160,7 @@ def section_params params.require(:section).permit(:title, :description) end + end + end diff --git a/app/controllers/org_admin/templates_controller.rb b/app/controllers/org_admin/templates_controller.rb index 8fa19c9..c1fcce4 100644 --- a/app/controllers/org_admin/templates_controller.rb +++ b/app/controllers/org_admin/templates_controller.rb @@ -1,5 +1,9 @@ +# frozen_string_literal: true + module OrgAdmin + class TemplatesController < ApplicationController + include Paginable include Versionable after_action :verify_authorized @@ -10,16 +14,19 @@ def index authorize Template templates = Template.latest_version.where(customization_of: nil) - published = templates.select{|t| t.published? || t.draft? }.length + published = templates.select { |t| t.published? || t.draft? }.length @orgs = Org.all - @title = _('All Templates') + @title = _("All Templates") @templates = templates.includes(:org) - @query_params = { sort_field: 'templates.title', sort_direction: 'asc' } + @query_params = { sort_field: "templates.title", sort_direction: "asc" } @all_count = templates.length @published_count = published.present? ? published : 0 - @unpublished_count = published.present? ? (templates.length - published): templates.length - + @unpublished_count = if published.present? + (templates.length - published) + else + templates.length + end render :index end @@ -28,17 +35,25 @@ # ----------------------------------------------------- def organisational authorize Template - templates = Template.latest_version_per_org(current_user.org.id).where(customization_of: nil, org_id: current_user.org.id) - published = templates.select{|t| t.published? || t.draft? }.length + templates = Template.latest_version_per_org(current_user.org.id) + .where(customization_of: nil, org_id: current_user.org.id) + published = templates.select { |t| t.published? || t.draft? }.length - @orgs = current_user.can_super_admin? ? Org.all : nil - @title = current_user.can_super_admin? ? _('%{org_name} Templates') % { org_name: current_user.org.name } : _('Own Templates') + @orgs = current_user.can_super_admin? ? Org.all : nil + @title = if current_user.can_super_admin? + _("%{org_name} Templates") % { org_name: current_user.org.name } + else + _("Own Templates") + end @templates = templates - @query_params = { sort_field: 'templates.title', sort_direction: 'asc' } + @query_params = { sort_field: "templates.title", sort_direction: "asc" } @all_count = templates.length @published_count = published.present? ? published : 0 - @unpublished_count = published.present? ? (templates.length - published): templates.length - + @unpublished_count = if published.present? + templates.length - published + else + templates.length + end render :index end @@ -47,21 +62,26 @@ # ----------------------------------------------------- def customisable authorize Template - customizations = Template.latest_customized_version_per_org(current_user.org.id).where(org_id: current_user.org.id) + customizations = Template.latest_customized_version_per_org(current_user.org.id) + .where(org_id: current_user.org.id) funder_templates = Template.latest_customizable.includes(:org) - # We use this to validate the counts below in the event that a template was customized but the base template - # org is no longer a funder + # We use this to validate the counts below in the event that a template was + # customized but the base template org is no longer a funder funder_template_families = funder_templates.collect(&:family_id) published = customizations.select { |t| t.published? || t.draft? }.length - @orgs = current_user.can_super_admin? ? Org.all : [] - @title = _('Customizable Templates') - @templates = funder_templates - @customizations = customizations - @query_params = { sort_field: 'templates.title', sort_direction: 'asc' } - @all_count = funder_templates.length - @published_count = published.present? ? published : 0 - @unpublished_count = published.present? ? (customizations.length - published) : customizations.length + @orgs = current_user.can_super_admin? ? Org.all : [] + @title = _("Customizable Templates") + @templates = funder_templates + @customizations = customizations + @query_params = { sort_field: "templates.title", sort_direction: "asc" } + @all_count = funder_templates.length + @published_count = published.present? ? published : 0 + @unpublished_count = if published.present? + (customizations.length - published) + else + customizations.length + end @not_customized_count = funder_templates.length - customizations.length render :index @@ -72,33 +92,44 @@ template = Template.find(params[:id]) authorize template # Load the info needed for the overview section if the authorization check passes! - phases = template.phases.includes(sections: { questions: :question_options }). - order('phases.number', 'sections.number', 'questions.number', 'question_options.number'). - select('phases.title', 'phases.description', 'sections.title', 'questions.text', 'question_options.text') + phases = template.phases + .includes(sections: { questions: :question_options }) + .order("phases.number", "sections.number", "questions.number", + "question_options.number") + .select("phases.title", "phases.description", "sections.title", + "questions.text", "question_options.text") if !template.latest? - flash[:notice] = _('You are viewing a historical version of this template. You will not be able to make changes.') + # rubocop:disable Metrics/LineLength + flash[:notice] = _("You are viewing a historical version of this template. You will not be able to make changes.") + # rubocop:enable Metrics/LineLength end - render 'container', locals: { - partial_path: 'show', + render "container", locals: { + partial_path: "show", template: template, phases: phases, referrer: get_referrer(template, request.referrer) } end # GET /org_admin/templates/:id/edit - # ----------------------------------------------------- def edit template = Template.includes(:org, :phases).find(params[:id]) authorize template # Load the info needed for the overview section if the authorization check passes! phases = template.phases.includes(sections: { questions: :question_options }). - order('phases.number', 'sections.number', 'questions.number', 'question_options.number'). - select('phases.title', 'phases.description', 'sections.title', 'questions.text', 'question_options.text') + order("phases.number", + "sections.number", + "questions.number", + "question_options.number"). + select("phases.title", + "phases.description", + "sections.title", + "questions.text", + "question_options.text") if !template.latest? redirect_to org_admin_template_path(id: template.id) else - render 'container', locals: { - partial_path: 'edit', + render "container", locals: { + partial_path: "edit", template: template, phases: phases, referrer: get_referrer(template, request.referrer) } @@ -106,28 +137,33 @@ end # GET /org_admin/templates/new - # ----------------------------------------------------- def new authorize Template - render 'container', locals: { - partial_path: 'new', + render "container", locals: { + partial_path: "new", template: Template.new(org: current_user.org), - referrer: request.referrer.present? ? request.referrer : org_admin_templates_path } + referrer: request.referrer.present? ? request.referrer : org_admin_templates_path + } end # POST /org_admin/templates - # ----------------------------------------------------- def create authorize Template # creates a new template with version 0 and new family_id template = Template.new(template_params) template.org_id = current_user.org.id - template.links = (params["template-links"].present? ? ActiveSupport::JSON.decode(params["template-links"]) : {"funder": [], "sample_plan": []}) + template.links = if params["template-links"].present? + ActiveSupport::JSON.decode(params["template-links"]) + else + { "funder": [], "sample_plan": [] } + end if template.save - redirect_to edit_org_admin_template_path(template), notice: success_message(template_type(template), _('created')) + redirect_to edit_org_admin_template_path(template), + notice: success_message(template_type(template), _("created")) else flash[:alert] = failed_create_error(template, template_type(template)) - render partial: "org_admin/templates/new", locals: { template: template, hash: hash } + render partial: "org_admin/templates/new", + locals: { template: template, hash: hash } end end @@ -138,56 +174,71 @@ authorize template begin template.assign_attributes(template_params) - template.links = ActiveSupport::JSON.decode(params["template-links"]) if params["template-links"].present? + if params["template-links"].present? + template.links = ActiveSupport::JSON.decode(params["template-links"]) + end if template.save - render(status: :ok, json: { msg: success_message(template_type(template), _('saved'))}) + render(status: :ok, + json: { msg: success_message(template_type(template), _("saved")) }) else - # Note failed_update_error may return HTML tags (e.g.
) and therefore the client should parse them accordingly - render(status: :bad_request, json: { msg: failed_update_error(template, template_type(template))}) + # Note failed_update_error may return HTML tags (e.g.
) and therefore the + # client should parse them accordingly + render(status: :bad_request, + json: { msg: failed_update_error(template, template_type(template)) }) end rescue ActiveSupport::JSON.parse_error - render(status: :bad_request, json: { msg: _("Error parsing links for a #{template_type(template)}") }) and return + render(status: :bad_request, + json: { msg: _("Error parsing links for a #{template_type(template)}") }) + return rescue => e render(status: :forbidden, json: { msg: e.message }) and return end end # DELETE /org_admin/templates/:id - # ----------------------------------------------------- def destroy template = Template.find(params[:id]) authorize template versions = Template.includes(:plans).where(family_id: template.family_id) - if versions.select{|t| t.plans.length > 0 }.empty? + if versions.select { |t| t.plans.length > 0 }.empty? versions.each do |version| if version.destroy! - flash[:notice] = success_message(template_type(template), _('removed')) + flash[:notice] = success_message(template_type(template), _("removed")) else flash[:alert] = failed_destroy_error(template, template_type(template)) end end else + # rubocop:disable Metrics/LineLength flash[:alert] = _("You cannot delete a #{template_type(template)} that has been used to create plans.") + # rubocop:enable Metrics/LineLength end - redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path + if request.referrer.present? + redirect_to request.referrer + else + redirect_to org_admin_templates_path + end end # GET /org_admin/templates/:id/history - # ----------------------------------------------------- def history template = Template.find(params[:id]) authorize template templates = Template.where(family_id: template.family_id) - render 'history', locals: { + local_referrer = if template.customization_of.present? + customisable_org_admin_templates_path + else + organisational_org_admin_templates_path + end + render "history", locals: { templates: templates, - query_params: { sort_field: 'templates.version', sort_direction: 'desc' }, - referrer: template.customization_of.present? ? customisable_org_admin_templates_path : organisational_org_admin_templates_path, + query_params: { sort_field: "templates.version", sort_direction: "desc" }, + referrer: local_referrer, current: templates.maximum(:version) } end # POST /org_admin/templates/:id/customize - # ----------------------------------------------------- def customize template = Template.find(params[:id]) authorize template @@ -196,18 +247,25 @@ customisation = template.customize!(current_user.org) redirect_to org_admin_template_path(customisation) rescue StandardError => e - flash[:alert] = _('Unable to customize that template.') - redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path + flash[:alert] = _("Unable to customize that template.") + if request.referrer.present? + redirect_to request.referrer + else + redirect_to org_admin_templates_path + end end else - flash[:notice] = _('That template is not customizable.') - redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path + flash[:notice] = _("That template is not customizable.") + if request.referrer.present? + redirect_to request.referrer + else + redirect_to org_admin_templates_path + end end end # POST /org_admin/templates/:id/transfer_customization # the funder template's id is passed through here - # ----------------------------------------------------- def transfer_customization template = Template.includes(:org).find(params[:id]) authorize template @@ -216,17 +274,24 @@ new_customization = template.upgrade_customization! redirect_to org_admin_template_path(new_customization) rescue StandardError => e - flash[:alert] = _('Unable to transfer your customizations.') - redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path + flash[:alert] = _("Unable to transfer your customizations.") + if request.referrer.present? + redirect_to request.referrer + else + redirect_to org_admin_templates_path + end end else - flash[:notice] = _('That template is no longer customizable.') - redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path + flash[:notice] = _("That template is no longer customizable.") + if request.referrer.present? + redirect_to request.referrer + else + redirect_to org_admin_templates_path + end end end - # POST /org_admin/templates/:id/copy (AJAX) - # ----------------------------------------------------- + # POST /org_admin/templates/:id/copy (AJAX) def copy template = Template.find(params[:id]) authorize template @@ -236,18 +301,22 @@ redirect_to edit_org_admin_template_path(new_copy) rescue StandardError => e flash[:alert] = failed_create_error(template, template_type(template)) - redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path + if request.referrer.present? + redirect_to request.referrer + else + redirect_to org_admin_templates_path + end end end # PATCH /org_admin/templates/:id/publish (AJAX) - # ----------------------------------------------------- def publish template = Template.find(params[:id]) authorize template + # rubocop:disable Metrics/LineLength if template.latest? # Now make the current version published - if template.update_attributes!({ published: true }) + if template.update_attributes!(published: true) flash[:notice] = _("Your #{template_type(template)} has been published and is now available to users.") else flash[:alert] = _("Unable to publish your #{template_type(template)}.") @@ -255,42 +324,48 @@ else flash[:alert] = _("You can not publish a historical version of this #{template_type(template)}.") end + # rubocop:enable Metrics/LineLength redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path end # PATCH /org_admin/templates/:id/unpublish (AJAX) - # ----------------------------------------------------- def unpublish template = Template.find(params[:id]) authorize template versions = Template.where(family_id: template.family_id) versions.each do |version| - unless version.update_attributes!({ published: false }) + unless version.update_attributes!(published: false) flash[:alert] = _("Unable to unpublish your #{template_type(template)}.") end end - flash[:notice] = _("Successfully unpublished your #{template_type(template)}") unless flash[:alert].present? + unless flash[:alert].present? + flash[:notice] = _("Successfully unpublished your #{template_type(template)}") + end redirect_to request.referrer.present? ? request.referrer : org_admin_templates_path end # GET /org_admin/template_options (AJAX) # Collect all of the templates available for the org+funder combination - # -------------------------------------------------------------------------- - def template_options() - org_id = (plan_params[:org_id] == '-1' ? '' : plan_params[:org_id]) - funder_id = (plan_params[:funder_id] == '-1' ? '' : plan_params[:funder_id]) + def template_options + org_id = (plan_params[:org_id] == "-1" ? "" : plan_params[:org_id]) + funder_id = (plan_params[:funder_id] == "-1" ? "" : plan_params[:funder_id]) authorize Template.new templates = [] if org_id.present? || funder_id.present? unless funder_id.blank? - # Load the funder's template(s) minus the default template (that gets swapped in below if NO other templates are available) - templates = Template.latest_customizable.where(org_id: funder_id).select{ |t| !t.is_default? } + # Load the funder's template(s) minus the default template (that gets swapped + # in below if NO other templates are available) + templates = Template.latest_customizable + .where(org_id: funder_id).select { |t| !t.is_default? } unless org_id.blank? # Swap out any organisational cusotmizations of a funder template templates = templates.map do |tmplt| - customization = Template.published.latest_customized_version(tmplt.family_id, org_id).first - # Only provide the customized version if its still up to date with the funder template! + customization = Template.published + .latest_customized_version(tmplt.family_id, + org_id).first + # Only provide the customized version if its still up to date with the + # funder template! if customization.present? && !customization.upgrade_customization? customization else @@ -303,7 +378,9 @@ # If the no funder was specified OR the funder matches the org if funder_id.blank? || funder_id == org_id # Retrieve the Org's templates - templates << Template.published.organisationally_visible.where(org_id: org_id, customization_of: nil).to_a + templates << Template.published + .organisationally_visible + .where(org_id: org_id, customization_of: nil).to_a end templates = templates.flatten.uniq end @@ -312,13 +389,14 @@ if templates.empty? default = Template.default if default.present? - customization = Template.published.latest_customized_version(default.family_id, org_id).first + customization = Template.published.latest_customized_version(default.family_id, + org_id).first templates << (customization.present? ? customization : default) end end - - templates = (templates.count > 0 ? templates.sort{|x,y| x.title <=> y.title} : []) - render json: {"templates": templates.collect{|t| {id: t.id, title: t.title} }}.to_json + render json: { + templates: templates.sort(&:title).collect { |t| { id: t.id, title: t.title } } + } end private @@ -332,19 +410,25 @@ end def template_type(template) - template.customization_of.present? ? _('customisation') : _('template') + template.customization_of.present? ? _("customisation") : _("template") end def get_referrer(template, referrer) - if referrer.present? - if referrer.end_with?(new_org_admin_template_path) || referrer.end_with?(edit_org_admin_template_path) || referrer.end_with?(org_admin_template_path) - template.customization_of.present? ? customisable_org_admin_templates_path : organisational_org_admin_templates_path + return org_admin_templates_path unless referrer.present? + if referrer.end_with?(new_org_admin_template_path) || + referrer.end_with?(edit_org_admin_template_path) || + referrer.end_with?(org_admin_template_path) + + if template.customization_of.present? + customisable_org_admin_templates_path else - request.referrer + organisational_org_admin_templates_path end else - org_admin_templates_path + request.referrer end end + end -end \ No newline at end of file + +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2c557b4..2091f6b 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,4 +1,7 @@ +# frozen_string_literal: true + class UsersController < ApplicationController + helper PaginableHelper helper PermsHelper include ConditionalUserMailer @@ -36,7 +39,7 @@ render json: { "user" => { "id" => user.id, - "html" => render_to_string(partial: 'users/admin_grant_permissions', + "html" => render_to_string(partial: "users/admin_grant_permissions", locals: { user: user, perms: perms }, formats: [:html]) } @@ -51,7 +54,7 @@ @user = User.find(params[:id]) authorize @user perms_ids = params[:perm_ids].blank? ? [] : params[:perm_ids].map(&:to_i) - perms = Perm.where( id: perms_ids) + perms = Perm.where(id: perms_ids) privileges_changed = false current_user.perms.each do |perm| if @user.perms.include? perm @@ -75,17 +78,18 @@ if @user.save if privileges_changed - deliver_if(recipients: @user, key: 'users.admin_privileges') do |r| + deliver_if(recipients: @user, key: "users.admin_privileges") do |r| UserMailer.admin_privileges(r).deliver_now end end render(json: { code: 1, - msg: success_message(_('permissions'), _('saved')), - current_privileges: render_to_string(partial: 'users/current_privileges', locals: { user: @user }, formats: [:html]) + msg: success_message(_("permissions"), _("saved")), + current_privileges: render_to_string(partial: "users/current_privileges", + locals: { user: @user }, formats: [:html]) }) else - render(json: { code: 0, msg: failed_update_error(@user, _('user')) }) + render(json: { code: 0, msg: failed_update_error(@user, _("user")) }) end end @@ -103,7 +107,8 @@ pref.save # Include active tab in redirect path - redirect_to "#{edit_user_registration_path}\#notification-preferences", notice: success_message(_('preferences'), _('saved')) + redirect_to "#{edit_user_registration_path}\#notification-preferences", + notice: success_message(_("preferences"), _("saved")) end # PUT /users/:id/org_swap @@ -114,18 +119,23 @@ begin org = Org.find(org_swap_params[:org_id]) rescue ActiveRecord::RecordNotFound - redirect_to(request.referer, alert: _('Please select an organisation from the list')) and return + redirect_to(request.referer, + alert: _("Please select an organisation from the list")) and return end + # rubocop:disable Metrics/LineLength if org.present? current_user.org = org if current_user.save - redirect_to request.referer, notice: _('Your organisation affiliation has been changed. You may now edit templates for %{org_name}.') % {org_name: current_user.org.name} + redirect_to request.referer, + notice: _("Your organisation affiliation has been changed. You may now edit templates for %{org_name}.") % { org_name: current_user.org.name } else - redirect_to request.referer, alert: _('Unable to change your organisation affiliation at this time.') + redirect_to request.referer, + alert: _("Unable to change your organisation affiliation at this time.") end else - redirect_to request.referer, alert: _('Unknown organisation.') + redirect_to request.referer, alert: _("Unknown organisation.") end + # rubocop:enable Metrics/LineLength end # PUT /users/:id/activate @@ -140,12 +150,18 @@ user.save! render json: { code: 1, - msg: _('Successfully %{action} %{username}\'s account.') % { action: user.active ? _('activated') : _('deactivated'), username: user.name(false) } + msg: _("Successfully %{action} %{username}'s account.") % { + action: user.active ? _("activated") : _("deactivated"), + username: user.name(false) + } } rescue Exception render json: { code: 0, - msg: _('Unable to %{action} %{username}') % { action: user.active ? _('activate') : _('deactivate'), username: user.name(false) } + msg: _("Unable to %{action} %{username}") % { + action: user.active ? _("activate") : _("deactivate"), + username: user.name(false) + } } end end @@ -160,6 +176,7 @@ end private + def org_swap_params params.require(:user).permit(:org_id, :org_name) end @@ -167,8 +184,8 @@ ## # html forms return our boolean values as strings, this converts them to true/false def booleanize_hash(node) - #leaf: convert to boolean and return - #hash: iterate over leaves + # leaf: convert to boolean and return + # hash: iterate over leaves unless node.is_a?(Hash) return node == "true" end diff --git a/app/helpers/annotations_helper.rb b/app/helpers/annotations_helper.rb index 46e3317..16b1c10 100644 --- a/app/helpers/annotations_helper.rb +++ b/app/helpers/annotations_helper.rb @@ -2,13 +2,15 @@ module AnnotationsHelper + # rubocop:disable all TOOLTIPS_FOR_TEXT = { - example_answer: _('You can add an example answer to help users respond. These will be presented above the answer box and can be copied/ pasted.'), + example_answer: _("You can add an example answer to help users respond. These will be presented above the answer box and can be copied/ pasted."), guidance: _("Enter specific guidance to accompany this question. If you have guidance by themes too, this will be pulled in based on your selections below so it's best not to duplicate too much text.") } - + # rubocop:enable all def tooltip_for_annotation_text(annotation) TOOLTIPS_FOR_TEXT[annotation.type.to_sym] end + end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 63a1196..eb143c2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ApplicationHelper def resource_name @@ -20,9 +22,9 @@ # for details def active_page?(path, exact_match = false) if exact_match - return request.fullpath == path + request.fullpath == path else - return request.fullpath.include?(path) + request.fullpath.include?(path) end end @@ -40,7 +42,8 @@ def unique_dom_id(record, prefix = nil) klass = dom_class(record, prefix) - record_id = record_key_for_dom_id(record) || SecureRandom.hex(4) + record_id = record_key_for_dom_id(record) || record.object_id "#{klass}_#{record_id}" end + end diff --git a/app/models/question.rb b/app/models/question.rb index 4981c5f..801660d 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # == Schema Information # # Table name: questions @@ -24,6 +26,7 @@ # class Question < ActiveRecord::Base + include ValidationMessages include ActsAsSortable @@ -106,10 +109,14 @@ copy.section_id = options.fetch(:section_id, nil) copy.save!(validate: false) if options.fetch(:save, false) options[:question_id] = copy.id - self.question_options.each{ |question_option| copy.question_options << question_option.deep_copy(options) } - self.annotations.each{ |annotation| copy.annotations << annotation.deep_copy(options) } - self.themes.each{ |theme| copy.themes << theme } - return copy + self.question_options.each do |question_option| + copy.question_options << question_option.deep_copy(options) + end + self.annotations.each do |annotation| + copy.annotations << annotation.deep_copy(options) + end + self.themes.each { |theme| copy.themes << theme } + copy end # TODO: consider moving this to a view helper instead and use the built in @@ -129,14 +136,14 @@ group.guidances.each do |g| g.themes.each do |theme| if theme_ids.include? theme.id - guidances["#{group.name} " + _('guidance on') + " #{theme.title}"] = g + guidances["#{group.name} " + _("guidance on") + " #{theme.title}"] = g end end end end end - return guidances + guidances end # get example answer belonging to the currents user for this question @@ -144,16 +151,16 @@ # org_ids - The ids for the organisations # # Returns ActiveRecord::Relation - def example_answers(org_ids) + def example_answers(org_ids) annotations.where(org_id: Array(org_ids), type: Annotation.types[:example_answer]) .order(:created_at) - end + end - alias get_example_answers example_answers + alias get_example_answers example_answers - deprecate :get_example_answers, - deprecator: Cleanup::Deprecators::GetDeprecator.new + deprecate :get_example_answers, + deprecator: Cleanup::Deprecators::GetDeprecator.new # get guidance belonging to the current user's org for this question(need org # to distinguish customizations) @@ -171,10 +178,17 @@ deprecator: Cleanup::Deprecators::GetDeprecator.new def annotations_per_org(org_id) - example_answer = annotations.find_by(org_id: org_id, type: Annotation.types[:example_answer]) - guidance = annotations.find_by(org_id: org_id, type: Annotation.types[:guidance]) - example_answer = annotations.build({ type: :example_answer, text: '', org_id: org_id }) unless example_answer.present? - guidance = annotations.build({ type: :guidance, text: '', org_id: org_id }) unless guidance.present? - return [example_answer, guidance] + example_answer = annotations.find_by(org_id: org_id, + type: Annotation.types[:example_answer]) + guidance = annotations.find_by(org_id: org_id, + type: Annotation.types[:guidance]) + unless example_answer.present? + example_answer = annotations.build(type: :example_answer, text: "", org_id: org_id) + end + unless guidance.present? + guidance = annotations.build(type: :guidance, text: "", org_id: org_id) + end + [example_answer, guidance] end + end diff --git a/app/views/org_admin/annotations/_form.html.erb b/app/views/org_admin/annotations/_form.html.erb index ed35b24..da2089f 100644 --- a/app/views/org_admin/annotations/_form.html.erb +++ b/app/views/org_admin/annotations/_form.html.erb @@ -2,7 +2,8 @@
<%= f.label(:type, f.object.type.humanize, class: "control-label") %>
- <%= f.text_area(:text, class: 'question') %> + <%= f.text_area(:text, class: 'question', + id: "question_annotations_attributes_#{unique_dom_id(f.object)}_text") %>
<%= f.hidden_field(:id) %> diff --git a/app/views/org_admin/questions/_show.html.erb b/app/views/org_admin/questions/_show.html.erb index f8c37d7..06e2932 100644 --- a/app/views/org_admin/questions/_show.html.erb +++ b/app/views/org_admin/questions/_show.html.erb @@ -73,16 +73,21 @@ <% else %>

<%= _('Annotations') %>

- <%= form_for(question, url: org_admin_template_phase_section_question_path(template_id: template.id, phase_id: question.section.phase.id, section_id: question.section.id, id: question.id), html: { method: 'put', class: 'question_form' }) do |f| %> + <%= form_for(question, html: { method: 'put', class: 'question_form' }, + url: org_admin_template_phase_section_question_path(template_id: template.id, + phase_id: question.section.phase.id, + section_id: question.section.id, + id: question.id)) do |f| %> <%# example_answer and guidance annotations as nested fields %> <% question.annotations_per_org(current_user.org_id).each do |annotation| %> <%= f.fields_for(:annotations, annotation) do |annotation_fields| %> - <%= render partial: 'org_admin/annotations/form', locals: { f: annotation_fields } %> + <%= render partial: 'org_admin/annotations/form', + locals: { f: annotation_fields } %> <% end %> <% end %>
- <%= f.submit _('Save'), class: "btn btn-default", role:'button' %> + <%= f.submit _('Save'), class: "btn btn-default", role: 'button' %>
<% end %> diff --git a/lib/assets/javascripts/views/org_admin/phases/new_edit.js b/lib/assets/javascripts/views/org_admin/phases/new_edit.js index 819b1dd..911f9a8 100644 --- a/lib/assets/javascripts/views/org_admin/phases/new_edit.js +++ b/lib/assets/javascripts/views/org_admin/phases/new_edit.js @@ -17,12 +17,24 @@ const parentSelector = '.section-group'; const initQuestion = (context) => { - const target = $(context); + const target = $(`#${context}`); if (isObject(target)) { - // For some reason the toolbar options are retained after the call to Tinymce.init() on - // the views/notifications/edit.js file. Tried 'Object.assign' instead of '$.extend' but it - // made no difference - Tinymce.init({ selector: `#${context} .question` }); + // For some reason the toolbar options are retained after the call to + // Tinymce.init() on the views/notifications/edit.js file. Tried 'Object.assign' + // instead of '$.extend' but it made no difference. + Tinymce.init({ + selector: `#${context} .question`, + init_instance_callback(editor) { + // When the text editor changes to blank, set the corresponding destroy + // field to true (if present). + editor.on('Change', () => { + const $texteditor = $(editor.targetElm); + const $fieldset = $texteditor.parents('fieldset'); + const $hiddenField = $fieldset.find('input[type=hidden][name$="[_destroy]"]'); + $hiddenField.val(editor.getContent() === ''); + }); + }, + }); ariatiseForm({ selector: `#${context} .question_form` }); initQuestionOption(context); // Swap in the question_formats when the user selects an option based question type @@ -31,6 +43,12 @@ }); } }; + + $('.question_container').each((i, element) => { + const questionId = $(element).attr('id'); + initQuestion(questionId); + }); + const getQuestionPanel = (target) => { let panelBody; if (isObject(target)) { @@ -47,7 +65,19 @@ // For some reason the toolbar options are retained after the call to Tinymce.init() on // the views/notifications/edit.js file. Tried 'Object.assign' instead of '$.extend' but it // made no difference - Tinymce.init({ selector: `${selector} .section` }); + Tinymce.init({ + selector: `${selector} .section`, + init_instance_callback(editor) { + // When the text editor changes to blank, set the corresponding destroy + // field to true (if present). + editor.on('Change', () => { + const $texteditor = $(editor.targetElm); + const $fieldset = $texteditor.parents('fieldset'); + const $hiddenField = $fieldset.find('input[type=hidden][id$="_destroy"]'); + $hiddenField.val(editor.getContent() === ''); + }); + }, + }); ariatiseForm({ selector: `${selector} .edit_section` }); const questionForm = $(selector).find('.question_form'); if (questionForm.length > 0) { @@ -129,7 +159,7 @@ }); // Handle the section that has focus on initial page load - const currentSection = $('.sectiongroup .in'); + const currentSection = $('.section-group .in'); if (currentSection.length > 0) { initSection(`#${currentSection.attr('id')}`); } diff --git a/lib/assets/javascripts/views/org_admin/templates/edit.js b/lib/assets/javascripts/views/org_admin/templates/edit.js index b8b2f47..f5e1742 100644 --- a/lib/assets/javascripts/views/org_admin/templates/edit.js +++ b/lib/assets/javascripts/views/org_admin/templates/edit.js @@ -6,7 +6,19 @@ import { scrollTo } from '../../../utils/scrollTo'; $(() => { - Tinymce.init({ selector: '.template' }); + Tinymce.init({ + selector: '.template', + init_instance_callback(editor) { + // When the text editor changes to blank, set the corresponding destroy + // field to true (if present). + editor.on('Change', () => { + const $texteditor = $(editor.targetElm); + const $fieldset = $texteditor.parents('fieldset'); + const $hiddenField = $fieldset.find('input[type=hidden][id$="_destroy"]'); + $hiddenField.val(editor.getContent() === ''); + }); + }, + }); enableValidations($('form.edit_template .form-group:not(.link-input)')); // Only enable validations on the links section if it has values initially $('.link').each((idx, el) => { diff --git a/lib/assets/javascripts/views/org_admin/templates/new.js b/lib/assets/javascripts/views/org_admin/templates/new.js index 19dc3ab..1c31f90 100644 --- a/lib/assets/javascripts/views/org_admin/templates/new.js +++ b/lib/assets/javascripts/views/org_admin/templates/new.js @@ -3,7 +3,19 @@ import { eachLinks } from '../../../utils/links'; $(() => { - Tinymce.init({ selector: '.template' }); + Tinymce.init({ + selector: '.template', + init_instance_callback(editor) { + // When the text editor changes to blank, set the corresponding destroy + // field to true (if present). + editor.on('Change', () => { + const $texteditor = $(editor.targetElm); + const $fieldset = $texteditor.parents('fieldset'); + const $hiddenField = $fieldset.find('input[type=hidden][id$="_destroy"]'); + $hiddenField.val(editor.getContent() === ''); + }); + }, + }); enableValidations($('.new_template')); $('.new_template').on('submit', (e) => { const links = {}; diff --git a/spec/features/annotations/annotations_editing_spec.rb b/spec/features/annotations/annotations_editing_spec.rb index e0de192..fcbbf2f 100644 --- a/spec/features/annotations/annotations_editing_spec.rb +++ b/spec/features/annotations/annotations_editing_spec.rb @@ -43,7 +43,7 @@ # NOTE: This is annotation 2, since Annotation was copied upon clicking "Customise" within("fieldset#fields_annotation_2") do - tinymce_fill_in("question_annotations_attributes_0_text", "Noo bar") + tinymce_fill_in("question_annotations_attributes_annotation_2_text", "Noo bar") end # NOTE: This is question 2, since Annotation was copied upon clicking "Customise" within('#edit_question_2') do @@ -68,7 +68,7 @@ click_link section.title # NOTE: This is annotation 2, since Annotation was copied upon clicking "Customise" within("fieldset#fields_annotation_2") do - tinymce_fill_in("question_annotations_attributes_0_text", "") + tinymce_fill_in("question_annotations_attributes_annotation_2_text", "") end # NOTE: This is question 2, since Annotation was copied upon clicking "Customise" within('#edit_question_2') do diff --git a/spec/features/templates/templates_editings_spec.rb b/spec/features/templates/templates_editings_spec.rb index 147ef37..bd02044 100644 --- a/spec/features/templates/templates_editings_spec.rb +++ b/spec/features/templates/templates_editings_spec.rb @@ -34,7 +34,8 @@ end click_link template.sections.first.title within("#edit_question_2") do - tinymce_fill_in(:question_annotations_attributes_1_text, "Foo bar") + textarea_id = page.body.match(/question\_annotations\_attributes\_annotation\_(\d+)\_text/) + tinymce_fill_in(:"question_annotations_attributes_annotation_#{$1}_text", "Foo bar") click_button 'Save' end # Make sure annotation has been updated