diff --git a/app/controllers/plans_controller.rb b/app/controllers/plans_controller.rb index 3710ff5..f844cbd 100644 --- a/app/controllers/plans_controller.rb +++ b/app/controllers/plans_controller.rb @@ -38,9 +38,13 @@ def create @plan = Plan.new authorize @plan - + @plan.principal_investigator = current_user.surname.blank? ? nil : "#{current_user.firstname} #{current_user.surname}" - @plan.data_contact = current_user.email + @plan.principal_investigator_email = current_user.email + + orcid = current_user.identifier_for(IdentifierScheme.find_by(name: 'orcid')) + @plan.principal_investigator_identifier = orcid.identifier unless orcid.nil? + @plan.funder_name = plan_params[:funder_name] @plan.visibility = (plan_params['visibility'].blank? ? Rails.application.config.default_plan_visibility : @@ -81,15 +85,15 @@ if !default.nil? && default == @plan.template # We used the generic/default template - msg += _('This plan is based on the default template.') + msg += " #{_('This plan is based on the default template.')}" elsif !@plan.template.customization_of.nil? # We used a customized version of the the funder template - msg += "#{_('This plan is based on the')} #{plan_params[:funder_name]} #{_('template with customisations by the')} #{plan_params[:org_name]}" + msg += " #{_('This plan is based on the')} #{plan_params[:funder_name]} #{_('template with customisations by the')} #{plan_params[:org_name]}" else # We used the specified org's or funder's template - msg += "#{_('This plan is based on the')} #{@plan.template.org.name} template." + msg += " #{_('This plan is based on the')} #{@plan.template.org.name} template." end flash[:notice] = msg @@ -122,7 +126,8 @@ # Get all Guidance Groups applicable for the plan and group them by org @all_guidance_groups = @plan.get_guidance_group_options @all_ggs_grouped_by_org = @all_guidance_groups.sort.group_by(&:org) - + @selected_guidance_groups = @plan.guidance_groups + # Important ones come first on the page - we grab the user's org's GGs and "Organisation" org type GGs @important_ggs = [] @important_ggs << [current_user.org, @all_ggs_grouped_by_org.delete(current_user.org)] @@ -131,12 +136,19 @@ @important_ggs << [org,ggs] @all_ggs_grouped_by_org.delete(org) end + + # If this is one of the already selected guidance groups its important! + if !(ggs & @selected_guidance_groups).empty? + @important_ggs << [org,ggs] unless @important_ggs.include?([org,ggs]) + @all_ggs_grouped_by_org.delete(org) + end end # Sort the rest by org name for the accordion + @important_ggs = @important_ggs.sort_by{|org,gg| org.name} @all_ggs_grouped_by_org = @all_ggs_grouped_by_org.sort_by {|org,gg| org.name} - - @selected_guidance_groups = @plan.guidance_groups.pluck(:id) + @selected_guidance_groups = @selected_guidance_groups.collect{|gg| gg.id} + @based_on = (@plan.template.customization_of.nil? ? @plan.template : Template.where(dmptemplate: @plan.template.customization_of).first) respond_to :html @@ -152,43 +164,42 @@ # if we have a phase then we are editing that phase. # # GET /plans/1/edit - def edit - @plan = Plan.find(params[:id]) - authorize @plan - - @visibility = @plan.visibility.present? ? @plan.visibility.to_s : Rails.application.config.default_plan_visibility - - # If there was no phase specified use the template's 1st phase - @phase = (params[:phase].nil? ? @plan.template.phases.first : Phase.find(params[:phase])) - @show_phase_tab = params[:phase] - @readonly = !@plan.editable_by?(current_user.id) - - # Get all Guidance Groups applicable for the plan and group them by org - @all_guidance_groups = @plan.get_guidance_group_options - @all_ggs_grouped_by_org = @all_guidance_groups.sort.group_by(&:org) - - # Important ones come first on the page - we grab the user's org's GGs and "Organisation" org type GGs - @important_ggs = [] - @important_ggs << [current_user.org, @all_ggs_grouped_by_org.delete(current_user.org)] - @all_ggs_grouped_by_org.each do |org, ggs| - if org.organisation? - @important_ggs << [org,ggs] - @all_ggs_grouped_by_org.delete(org) - end - end - - # Sort the rest by org name for the accordion - @all_ggs_grouped_by_org = @all_ggs_grouped_by_org.sort_by {|org,gg| org.name} - - @selected_guidance_groups = @plan.guidance_groups.pluck(:id) - @based_on = (@plan.template.customization_of.nil? ? @plan.template : Template.where(dmptemplate: @plan.template.customization_of).first) - - flash[:notice] = "#{_('This is a')} #{_('test plan')}" if params[:test] - @is_test = params[:test] ||= false - - respond_to :html - end - +# def edit +# @plan = Plan.find(params[:id]) +# authorize @plan +# +# @visibility = @plan.visibility.present? ? @plan.visibility.to_s : Rails.application.config.default_plan_visibility +# +# # If there was no phase specified use the template's 1st phase +# @phase = (params[:phase].nil? ? @plan.template.phases.first : Phase.find(params[:phase])) +# @show_phase_tab = params[:phase] +# @readonly = !@plan.editable_by?(current_user.id) +# +# # Get all Guidance Groups applicable for the plan and group them by org +# @all_guidance_groups = @plan.get_guidance_group_options +# @all_ggs_grouped_by_org = @all_guidance_groups.sort.group_by(&:org) +# +# # Important ones come first on the page - we grab the user's org's GGs and "Organisation" org type GGs +# @important_ggs = [] +# @important_ggs << [current_user.org, @all_ggs_grouped_by_org.delete(current_user.org)] +# @all_ggs_grouped_by_org.each do |org, ggs| +# if org.organisation? +# @important_ggs << [org,ggs] +# @all_ggs_grouped_by_org.delete(org) +# end +# end +# +# # Sort the rest by org name for the accordion +# @all_ggs_grouped_by_org = @all_ggs_grouped_by_org.sort_by {|org,gg| org.name} +# +# @selected_guidance_groups = @plan.guidance_groups.pluck(:id) +# @based_on = (@plan.template.customization_of.nil? ? @plan.template : Template.where(dmptemplate: @plan.template.customization_of).first) +# +# flash[:notice] = "#{_('This is a')} #{_('test plan')}" if params[:test] +# @is_test = params[:test] ||= false +# +# respond_to :html +# end # PUT /plans/1 # PUT /plans/1.json @@ -197,6 +208,12 @@ authorize @plan attrs = plan_params +# attrs[:visibility] = attrs[:visibility].to_sym + + # Save the guidance group selections + guidance_group_ids = params[:guidance_group_ids].blank? ? [] : params[:guidance_group_ids].map(&:to_i) + save_guidance_selections(guidance_group_ids) + respond_to do |format| if @plan.update_attributes(attrs) format.html { redirect_to @plan, :editing => false, notice: success_message(_('plan'), _('saved')) } @@ -209,35 +226,22 @@ end - +# TODO: Do we need this is selections are saved with rest of form? def update_guidance_choices @plan = Plan.find(params[:id]) authorize @plan guidance_group_ids = params[:guidance_group_ids].blank? ? [] : params[:guidance_group_ids].map(&:to_i) - all_guidance_groups = @plan.get_guidance_group_options - plan_groups = @plan.guidance_groups - guidance_groups = GuidanceGroup.where( id: guidance_group_ids) - all_guidance_groups.each do |group| - # case where plan group exists but not in selection - if plan_groups.include?(group) && ! guidance_groups.include?(group) - # remove from plan groups - @plan.guidance_groups.delete(group) - end - # case where plan group dosent exist and in selection - if !plan_groups.include?(group) && guidance_groups.include?(group) - # add to plan groups - @plan.guidance_groups << group - end - end + save_guidance_selections(guidance_group_ids) @plan.save flash[:notice] = success_message(_('guidance choices'), _('saved')) redirect_to action: "show" end - + def share @plan = Plan.find(params[:id]) authorize @plan - #@plan_data = @plan.to_hash + @visibility = @plan.visibility.present? ? @plan.visibility.to_s : Rails.application.config.default_plan_visibility + @allow_visibility = (@plan.num_answered_questions >= 1 && !@plan.is_test?) end @@ -405,9 +409,20 @@ authorize plan plan.visibility = "#{plan_params[:visibility]}" if plan.save - render json: {code: 1, msg: ''} + render json: {code: 1, msg: success_message(_('plan\'s visibility'), _('changed'))} else - render json: {code: 0, msg: _("Unable to change the plan's Test status")} + render json: {code: 0, msg: _("Unable to change the plan's status")} + end + end + + def set_test + plan = Plan.find(params[:id]) + authorize plan + plan.visibility = "#{plan_params[:visibility]}" + if plan.save + render json: {code: 1, msg: (plan.is_test? ? _('Your project is now a test.') : _('Your project is no longer a test.') )} + else + render json: {code: 0, msg: _("Unable to change the plan's test status")} end end @@ -415,9 +430,32 @@ private def plan_params - params.require(:plan).permit(:org_id, :org_name, :funder_id, :funder_name, :template_id, :title, :visibility) + params.require(:plan).permit(:org_id, :org_name, :funder_id, :funder_name, :template_id, :title, :visibility, + :grant_number, :description, :identifier, :principal_investigator, + :principal_investigator_email, :principal_investigator_identifier, + :data_contact, :data_contact_email, :guidance_group_ids) end + def save_guidance_selections(guidance_group_ids) + all_guidance_groups = @plan.get_guidance_group_options + plan_groups = @plan.guidance_groups + guidance_groups = GuidanceGroup.where(id: guidance_group_ids) + all_guidance_groups.each do |group| + # case where plan group exists but not in selection + if plan_groups.include?(group) && ! guidance_groups.include?(group) + # remove from plan groups + @plan.guidance_groups.delete(group) + end + # case where plan group dosent exist and in selection + if !plan_groups.include?(group) && guidance_groups.include?(group) + # add to plan groups + @plan.guidance_groups << group + end + end + @plan.save + end + + # different versions of the same template have the same dmptemplate_id # but different version numbers so for each set of templates with the # same dmptemplate_id choose the highest version number. diff --git a/app/controllers/roles_controller.rb b/app/controllers/roles_controller.rb index ef91cca..abe77f2 100644 --- a/app/controllers/roles_controller.rb +++ b/app/controllers/roles_controller.rb @@ -6,6 +6,7 @@ registered = true @role = Role.new(role_params) authorize @role + access_level = params[:role][:access_level].to_i set_access_level(access_level) if params[:user].present? @@ -45,12 +46,12 @@ access_level = params[:role][:access_level].to_i set_access_level(access_level) if @role.update_attributes(role_params) - flash[:notice] = success_message(_('sharing details'), _('saved')) UserMailer.permissions_change_notification(@role, current_user).deliver_now - redirect_to controller: 'plans', action: 'share', id: @role.plan.id + render json: {code: 1, msg: "Successfully changed the permissions for #{@role.user.email}. They have been notified via email."} else - flash[:alert] = failed_create_error(@role, _('role')) - render action: "edit" +# flash[:alert] = failed_create_error(@role, _('role')) + #format.html{ render action: "edit" } + render json: {code: 1, msg: flash[:alert]} end end diff --git a/app/helpers/plans_helper.rb b/app/helpers/plans_helper.rb index c4f3af4..d61258d 100644 --- a/app/helpers/plans_helper.rb +++ b/app/helpers/plans_helper.rb @@ -42,8 +42,10 @@ return "#{_('Institution')}" when 'publicly_visible' return "#{_('Public')}" + when 'privately_visible' + return "#{_('Private')}" else - return "#{_('Private')}" # Both Test and Private + return "#{_('N/A')}" # Test Plans end end diff --git a/app/models/plan.rb b/app/models/plan.rb index ab84aca..6579f97 100644 --- a/app/models/plan.rb +++ b/app/models/plan.rb @@ -32,7 +32,7 @@ :exported_plans, :project, :title, :template, :grant_number, :identifier, :principal_investigator, :principal_investigator_identifier, :description, :data_contact, :funder_name, :visibility, :exported_plans, - :roles, :users, :org, :data_contact_email, :data_contact_phone, + :roles, :users, :org, :data_contact_email, :principal_investigator_email, :as => [:default, :admin] accepts_nested_attributes_for :roles diff --git a/app/policies/plan_policy.rb b/app/policies/plan_policy.rb index e6d6d15..448a3c4 100644 --- a/app/policies/plan_policy.rb +++ b/app/policies/plan_policy.rb @@ -56,6 +56,10 @@ @plan.administerable_by?(@user.id) end + def set_test? + @plan.administerable_by?(@user.id) + end + # TODO: These routes are no lonmger used =begin def section_answers? diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index db53c0d..86bdedf 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -63,14 +63,17 @@ <%= render "layouts/header" %> -
> - - <%= _('Notice:') %> - <%= raw notice %> -
-
> - - <%= raw alert %> + <% + has_alert = (alert || flash[:alert] || flash[:error]) + has_notice = (notice || flash[:notice]) + %> + +
> + + <%= has_alert ? _('Error:') : _('Notice:') %> + <%= raw (has_alert ? alert : notice) %>
diff --git a/app/views/plans/_edit_details.html.erb b/app/views/plans/_edit_details.html.erb index 9f964b4..58c1ccc 100644 --- a/app/views/plans/_edit_details.html.erb +++ b/app/views/plans/_edit_details.html.erb @@ -1,95 +1,162 @@ -<% javascript('views/plans/edit.js') %> +<% javascript('views/plans/show.js') %> <%= form_for plan, html: {method: :put, class: "roadmap-form"} do |f| %>
-
+
- <%= f.label "#{_('Project Title')} #{_('(required)')}", for: :title %> - <%= f.text_field :title, class: "input-large has-tooltip", 'data-toggle': "tooltip", - 'title': _('If applying for funding, state the name exactly as in the grant proposal.') %> + <%= f.label "#{_('Project Title')}", for: :title, class: 'required' %> + <%= f.text_field :title, class: "input-extra-large left-indent required", 'data-toggle': "tooltip", + 'data-content': _('If applying for funding, state the name exactly as in the grant proposal.') %> + +
+ <%= f.hidden_field :visibility %> + /> + +
- + +
+ <%= f.label "#{_('Funder')}", for: :funder_name %> + <%= f.text_field :funder_name, class: "input-large left-indent" %> +
+
<%= f.label :grant_number %> - <%= f.text_field :grant_number, class: 'input-small has-tooltip', 'data-toggle': "tooltip", - 'title': _('Grant reference number if applicable [POST-AWARD DMPs ONLY]') %> -
- -
- <%= f.label :principal_investigator %> - <%= f.text_field :principal_investigator, class: 'input-medium has-tooltip', 'data-toggle': "tooltip", - 'title': _('Name of Principal Investigator(s) or main researcher(s) on the project.') %> + <%= f.text_field :grant_number, class: 'input-small left-indent', 'data-toggle': "tooltip", + 'data-content': _('Grant reference number if applicable [POST-AWARD DMPs ONLY]') %>
<%= f.label _('Project Abstract'), for: :description %> - <%= f.text_area :description, { rows: 7, class: 'input-large has-tooltip', - 'data-toggle': "tooltip", 'data-html': "true", - 'title': _("

Questions to consider:

  • - What is the nature of your research project?
  • - What research questions are you addressing?
  • - For what purpose are the data being collected or created?

Guidance:

Briefly summarise the type of study (or studies) to help others understand the purposes for which the data are being collected or created.

")} %> + <%= f.text_area :description, class: "left-indent" %> + <%= tinymce(selector: "#plan_description", content_css: asset_path('application.css')) %> +
-
-
- <%= f.label _('Plan ID'), for: :identifier %> - <%= f.text_field :identifier, class: 'input-medium has-tooltip', 'data-toggle': "tooltip", - 'title': _('A pertinent ID as determined by the funder and/or institution.') %> + <%= f.label _('ID'), for: :identifier %> + <%= f.text_field :identifier, class: 'input-medium left-indent', 'data-toggle': "tooltip", + 'data-content': _('A pertinent ID as determined by the funder and/or institution.') %>
+
+ +
+ +
+ +
+ <%= f.label _('Name'), for: :principal_investigator, 'aria-label': _('Principal Investigator Name') %> + <%= f.text_field :principal_investigator, class: 'input-medium left-indent' %> +
+ +
+ <%= f.label _('ORCID ID'), for: :principal_investigator_identifier, 'aria-label': _('Principal Investigator ORCID ID') %> + <%= f.text_field :principal_investigator_identifier, class: 'input-medium left-indent' %> +
+ +
+ <%= f.label _('Email'), for: :principal_investigator_email, 'aria-label': _('Principal Investigator Email') %> + <%= f.email_field :principal_investigator_email, class: 'input-medium left-indent' %> + +
+
<%= f.label(:data_contact, raw("#{_('Data Contact Person')}"), class: 'no-colon') %>
-
- <%= f.label _('Name'), for: :data_contact %> - <%= f.text_field :data_contact, class: 'input-medium has-tooltip', 'data-toggle': "tooltip", - 'title': _('Name (if different to above), telephone and email contact details') %> +
+ /> + +
+
+ <%= f.label _('Name'), for: :data_contact, 'aria-label': _('Data Contact Name') %> + <%= f.text_field :data_contact, class: 'input-medium left-indent' %> +
+ +
+ <%= f.label _('Email'), for: :data_contact_email, 'aria-label': _('Data Contact Email') %> + <%= f.email_field :data_contact_email, class: 'input-medium left-indent' %> +


-
 
- <%= submit_tag(_('Save'), class: 'form-submit') %> + <%= render partial: 'shared/accessible_submit_button', + locals: {id: 'save-details-button', + val: _('Save'), + disabled_initially: true, + classes: 'small-input-button left-indent', + tooltip: _('You must provide a project title and any email addresses must be valid.')} %>
-
- <%= f.hidden_field(:visibility, value: visibility) %> - -
- <%= f.label(:visibility, _('Plan Visibility')) %> -
(<%= _('Limited to finished plans') %>)
-
-
- <%= radio_button_tag(:vis, 0, !['publicly_visible', 'organisationally_visible'].include?(visibility), - class: 'right-indent', - id:'privately_visible') %> - <%= raw display_visibility('privately_visible') %> -
-
- <%= radio_button_tag(:vis, 0, 'organisationally_visible' == visibility, - class: 'right-indent', - id: 'organisationally_visible') %> - <%= raw display_visibility('organisationally_visible') %> -
-
- <%= radio_button_tag(:vis, 0, 'publicly_visible' == visibility, - class: 'right-indent', - id: 'publicly_visible') %> - <%= raw display_visibility('publicly_visible') %> -
-
-
-
-
<%= _('Plan Guidance Configuration') %>
+

<%= _('Plan Guidance Configuration') %>

<%= _('To help you write your plan, %{application_name} can show you guidance from a variety of organisations.') % {application_name: Rails.configuration.branding[:application][:name]} %>

-
-

<% _('Select up to 3 organisations to see their guidance.') %>

- <% #@important_ggs.inspect %> +

<%= _('Select up to 3 organisations to see their guidance.') %>

+ +
    + <% @important_ggs.each do |org, groups| %> + <% if groups && groups.size == 1 %> +
  • + <%= check_box_tag "guidance_group_ids[]", groups[0].id, @selected_guidance_groups.include?(groups[0].id) %> + <%= f.label org.name, for: groups[0].id, class: 'inline checkbox-label regular-text' %> +
  • + <% elsif groups %> +
  • + + +
  • + <% groups.each do |group| %> +
  • + └─ + <%= check_box_tag "guidance_group_ids[]", group.id, @selected_guidance_groups.include?(group.id) %> + <%= f.label group.name, for: group.id, class: "left-indent checkbox-label regular-text" %> +
  • + <% end %> + <% end%> + <% end %> +
+ + <%= _('See guidance from additional organisations') %> + +
-<% end %> \ No newline at end of file +<% end %> + +<%= tinymce :content_css => asset_path('application.css') %> \ No newline at end of file diff --git a/app/views/plans/_plan_title.html.erb b/app/views/plans/_plan_title.html.erb index 93b7c28..f445526 100644 --- a/app/views/plans/_plan_title.html.erb +++ b/app/views/plans/_plan_title.html.erb @@ -1,8 +1 @@ -

<%= plan.title %>

- -<% if plan.visibility == 'is_test' %> -
- - <%= _('This is a') %> <%= _('test plan') %>. -
-<% end %> +

<%= plan.title %>

\ No newline at end of file diff --git a/app/views/plans/share.html.erb b/app/views/plans/share.html.erb index 22b513a..c479a9c 100644 --- a/app/views/plans/share.html.erb +++ b/app/views/plans/share.html.erb @@ -1,5 +1,5 @@ -<%- model_class = Plan -%> <% javascript('views/plans/share.js') %> + <%= render :partial => "plan_title", locals: {plan: @plan} %> @@ -29,18 +29,35 @@
-

<%= _('Collaborators')%>

+ +

<%= _('Set plan visibility') %>

+

<%= _('Public or organisational visibility is intended for finished plans. You must answer at least one question to enable these options.') %>

+ + <%= form_for @plan, html: {method: :put, class: "roadmap-form"} do |f| %> +
+ <%= f.hidden_field :id %> +
+ <%= f.radio_button :visibility, :privately_visible, disabled: !@allow_visibility %> + <%= f.label :visibility, _('Private: restricted to me and my collaborators'), + class: "checkbox-label regular-text#{(@allow_visibility ? '' : ' disabled')}" %> +
+
+ <%= f.radio_button :visibility, :organisationally_visible, disabled: !@allow_visibility %> + <%= f.label :visibility, _('Organisation: anyone at my organisation can view'), + class: "checkbox-label regular-text#{(@allow_visibility ? '' : ' disabled')}" %> +
+
+ <%= f.radio_button :visibility, :publicly_visible, disabled: !@allow_visibility %> + <%= f.label :visibility, _('Public: anyone can view'), + class: "checkbox-label regular-text#{(@allow_visibility ? '' : ' disabled')}" %> +
+
+ <% end %> + +

<%= _('Manage collaborators')%>

-

<%= _('You can give other people access to your plan here. There are three permission levels.') %> -

    -
  • <%= _('Users with "read only" access can only read the plan.') %>
  • -
  • <%= _('Editors can contribute to the plan.') %>
  • -
  • <%= _('Co-owners can also contribute to the plan, but additionally can edit the plan details and control access to the plan.') %>
  • -
-

-

<%= _('Add each collaborator in turn by entering their email address below, choosing a permission level and clicking "Add collaborator".') %>

-

<%= _('Those you invite will receive an email notification that they have access to this plan, inviting them to register with %{application_name} if they don\'t already have an account. A notification is also issued when a user\'s permission level is changed.') % { application_name: Rails.configuration.branding[:application][:name] } %>

+

<%= _('Invite specific people to read, edit, or administer your plan. Invitees will receive an email notification that they have access to this plan.') %>

<% if @plan.roles.any? then %> @@ -48,7 +65,7 @@ - + @@ -58,12 +75,13 @@ @@ -80,22 +98,42 @@
<%= _('Email address')%> <%= _('Permissions')%><%= _('Action') %>
<%= role.user.name %> <% if role.creator? %> - <%= 'Owner' %> + <%= 'Owner' %> <% elsif !role.creator? && role.user == current_user %> - <%= display_role(role) %> + <%= display_role(role) %> <% elsif !role.creator? && role.user != current_user %> <%= form_for role, url: {controller: :roles, action: :update, id: role.id }, html: {method: :put} do |f| %>
+ <%= f.hidden_field :id %> <%= f.select :access_level, {"#{_('Co-owner')}": 3, "#{_('Editor')}": 2, "#{_('Read only')}": 1}, {}, {id: "#{role.id}-can-edit", class: "toggle-existing-user-access has-tooltip", 'data-toggle': "tooltip", 'title': _('Editors can contribute to plans. Co-owners have additional rights to edit plan details and control access.') } %>
<% end %> @@ -71,7 +89,7 @@
<% unless role.creator? || role.user == current_user then %> - <%= link_to _('Remove user access'), role, method: :delete, data: { confirm: _('Are you sure?') }, :class => "a-orange" %> + <%= link_to _('Remove'), role, method: :delete, data: { confirm: _('Are you sure?') }, :class => "a-orange" %> <% end %>
<% end %> -

<%= _('Add collaborator') %>

+

<%= _('Invite collaborators') %>

<% new_role = Role.new %> <% new_role.plan = @plan %> <%= form_for new_role, url: {controller: :roles, action: :create }, - html: {method: :post, class: 'roadmap-form'} do |f| %>

-
+ html: {method: :post, class: 'roadmap-form'} do |f| %> +
<%= f.hidden_field :plan_id %> <%= f.fields_for :user do |user| %> <%= user.label :email, _('Email') %> - <%= user.email_field :email, for: :user, name: "user", class: "left-indent" %> + <%= user.email_field :email, for: :user, name: "user", class: "left-indent input-medium" %> + <% end %> - <%= f.label :access_level, _('Permissions') %> - <%= f.select :access_level, [[_('Co-owner'), 3], [ _('Editor') , 2], [ _('Read only'), 1]], {}, {class: 'has-tooltip left-indent', 'data-toggle': "tooltip", 'title': _('Editors can contribute to plans. Co-owners have additional rights to edit plan details and control access.') } %> - + +

+
+ <%= f.radio_button :access_level, 1 %> + <%= f.label :access_level, _('Read Only: can view but not make changes'), class: 'checkbox-label regular-text' %> +
+
+ <%= f.radio_button :access_level, 2 %> + <%= f.label :access_level, _('Editor: can comment and make changes'), class: 'checkbox-label regular-text' %> +
+
+ <%= f.radio_button :access_level, 3 %> + <%= f.label :access_level, _('Co-owner: can edit project details, change visibility, and add collaborators'), class: 'checkbox-label regular-text' %> +
+
+ <%= render partial: 'shared/accessible_submit_button', + locals: {id: 'add-collaborator-button', + val: _('Add collaborator'), + disabled_initially: true, + classes: 'small-input-button left-indent', + tooltip: _('Enter a valid email and permission level.')} %> +
<% end %>

diff --git a/app/views/plans/show.html.erb b/app/views/plans/show.html.erb index 1fda9ce..1677d5f 100644 --- a/app/views/plans/show.html.erb +++ b/app/views/plans/show.html.erb @@ -11,7 +11,9 @@ <% @plan.template.phases.each do |phase| %> <% end %> diff --git a/app/views/shared/_accessible_submit_button.html.erb b/app/views/shared/_accessible_submit_button.html.erb index 497c1e8..7d53835 100644 --- a/app/views/shared/_accessible_submit_button.html.erb +++ b/app/views/shared/_accessible_submit_button.html.erb @@ -14,7 +14,7 @@ $("#<%= id %>").on('click focus', function(e){ if($(this).attr('aria-disabled') == 'true'){ e.preventDefault(); - $(this).siblings(".error-tooltip-right").attr('role', 'tooltip'); + $(this).siblings(".error-tooltip-right").attr('role', 'alert'); }else{ $(this).siblings(".error-tooltip-right").attr('role', ''); } diff --git a/config/application.rb b/config/application.rb index ba95598..020d725 100644 --- a/config/application.rb +++ b/config/application.rb @@ -97,11 +97,11 @@ views/notes/index.js views/orgs/admin_edit.js views/orgs/shibboleth_ds.js - views/plans/edit.js views/plans/export_configure.js views/plans/index.js views/plans/new.js views/plans/share.js + views/plans/show.js views/shared/login_form.js views/shared/register_form.js views/static_pages/utils.js) diff --git a/config/routes.rb b/config/routes.rb index 9d0e26f..3343ab9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -215,6 +215,7 @@ get 'export' post 'invite' post 'visibility', constraints: {format: [:json]} + post 'set_test', constraints: {format: [:json]} end collection do diff --git a/db/migrate/20170710182442_add_principal_investigator_email_to_plans.rb b/db/migrate/20170710182442_add_principal_investigator_email_to_plans.rb new file mode 100644 index 0000000..2690d54 --- /dev/null +++ b/db/migrate/20170710182442_add_principal_investigator_email_to_plans.rb @@ -0,0 +1,5 @@ +class AddPrincipalInvestigatorEmailToPlans < ActiveRecord::Migration + def change + add_column :plans, :principal_investigator_email, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 6c19344..f6477e1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -142,11 +142,11 @@ create_table "org_identifiers", force: :cascade do |t| t.string "identifier", limit: 255 + t.integer "identifier_scheme_id", limit: 4 t.string "attrs", limit: 255 t.datetime "created_at" t.datetime "updated_at" t.integer "org_id", limit: 4 - t.integer "identifier_scheme_id", limit: 4 end add_index "org_identifiers", ["identifier_scheme_id"], name: "fk_rails_189ad2e573", using: :btree @@ -392,8 +392,8 @@ t.string "email", limit: 255, default: "", null: false t.string "orcid_id", limit: 255 t.string "shibboleth_id", limit: 255 - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "encrypted_password", limit: 255, default: "" t.string "reset_password_token", limit: 255 t.datetime "reset_password_sent_at" diff --git a/lib/assets/javascripts/dmproadmap/forms.js b/lib/assets/javascripts/dmproadmap/forms.js index 4cfa730..afca2cf 100644 --- a/lib/assets/javascripts/dmproadmap/forms.js +++ b/lib/assets/javascripts/dmproadmap/forms.js @@ -1,39 +1,56 @@ // --------------------------------------------------------------------------- -function toggleAutocompleteError(autocomplete, idbox, errorMessage){ - if(autocomplete.length > 0 && idbox.length > 0){ - var err = $(idbox).siblings("span.error-tooltip"); - if(err.length <= 0){ - err = $(idbox).siblings("span.error-tooltip-right"); - } - - // If an error element is available and the error message is not empty and the field - // is not empty - if(err.length > 0 && (errorMessage === '' || $(autocomplete).val().trim().length <= 0)){ - err.html('').attr('role', ''); - $(autocomplete).removeClass('red-border'); - }else{ - err.html(errorMessage).attr('role', 'tooltip'); - $(autocomplete).addClass('red-border'); - } - } +function remoteSave(target, method, data){ + $("div.roadmap-info-box span:not(.aria-only)").parent().css('visibility', 'hidden').fadeOut() + + // Update the visbility when the user clicks on the radio button + $.ajax({ + url: target, + type: method, + data: data, + contentType: 'application/json', + accepts: 'application/json' + }).done(function(data){ + $("#notification-area span:not(.aria-only)").html(data['msg']).css('width', 'auto') + .attr('role', (data['code'] === 1 ? 'status' : 'alert')) + .attr('class', (data['code'] === 1 ? 'roadmap-info-box' : 'roadmap-alert-box')) + .parent().css('visibility', 'visible').fadeIn(); + }); } // --------------------------------------------------------------------------- -function toggleInputError(input, errorMessage){ +function toggleAutocompleteError(autocomplete, idbox, errorMessage){ + if(autocomplete.length > 0 && idbox.length > 0){ + var err = $(idbox).siblings("span.error-tooltip"); + if(err.length <= 0){ + err = $(idbox).siblings("span.error-tooltip-right"); + } + + // If an error element is available and the error message is not empty and the field + // is not empty + if(err.length > 0 && (errorMessage === '' || $(autocomplete).val().trim().length <= 0)){ + err.html('').attr('role', ''); + $(autocomplete).removeClass('red-border'); + }else{ + err.html(errorMessage).attr('role', 'tooltip'); + $(autocomplete).addClass('red-border'); + } + } +} + +// --------------------------------------------------------------------------- +function toggleInputError(input, errorMessage, allowBlank = true){ var err = $(input).siblings("span.error-tooltip"); if(err.length <= 0){ err = $(input).siblings("span.error-tooltip-right"); } -console.log(err.length + ' - ' + errorMessage + ' - ' + $(input).val().trim().length); - // If an error element is available and the error message is not empty and the field - // is not empty - if(err.length > 0 && (errorMessage === '' || $(input).val().trim().length <= 0)){ + // is not empty (unless its a required field!) + if(err.length > 0 && (errorMessage === '' || (allowBlank && $(input).val().trim().length <= 0))){ err.html('').attr('role', ''); $(input).removeClass('red-border'); }else{ - err.html(errorMessage).attr('role', 'tooltip'); + err.html(errorMessage).attr('role', 'alert'); $(input).addClass('red-border'); } } @@ -53,6 +70,6 @@ if(filter.test(sEmail)){ return ''; }else{ - return __('Invalid Email'); + return __('Invalid email address'); } } \ No newline at end of file diff --git a/lib/assets/javascripts/views/plans/index.js b/lib/assets/javascripts/views/plans/index.js index d6261b5..3488362 100644 --- a/lib/assets/javascripts/views/plans/index.js +++ b/lib/assets/javascripts/views/plans/index.js @@ -1,23 +1,12 @@ $(document).ready(function(){ $("input[type='checkbox']").on('click, change', function(e){ - var self = this; var id = $(this).attr("id").replace("is_test-", ""); var params = {plan: {visibility: $(this).is(':checked') ? 'is_test' : 'privately_visible'}}; - - // Update the visbility to test or private - $.post("/plans/" + id + "/visibility", params, function(data){ - if(data['code'] === 1){ - var msg = ($(self).is(':checked') ? __('The plan is now a test.') : __('The plan is no longer a test.')); - // If the save was successful make sure the Visibility text gets updated to 'Private' - $("div.roadmap-info-box span:not(.aria-only)").html(msg).attr('role', 'status') - .css('width', 'auto').parent().css('visibility', 'visible'); - $(self).parent().siblings("#visibility-" + id).html(__('Private')); - }else{ - // Display an error message - $("div.roadmap-alert-box span:not(.aria-only)").show().html(data['msg']) - .attr('role', 'alert').css('width', 'auto').css('visibility', 'visible'); - e.preventDefault(); - } - }); + remoteSave("/plans/" + id + "/set_test", 'POST', JSON.stringify(params)); + if($(this).is(':checked')){ + $("#visibility-" + id + " span").html(__('N/A')).attr('title', ''); + }else{ + $("#visibility-" + id + " span").html(__('Private')) + } }); }); \ No newline at end of file diff --git a/lib/assets/javascripts/views/plans/share.js b/lib/assets/javascripts/views/plans/share.js index 3ea4591..9aa05d6 100644 --- a/lib/assets/javascripts/views/plans/share.js +++ b/lib/assets/javascripts/views/plans/share.js @@ -1,9 +1,35 @@ $(document).ready(function(){ - /*---------------- - Listener for changes in access-level for a plan shared with a user - TODO partial update instead of forcing a page reload - ------------------*/ - $(".toggle-access-level").change(function(){ - $(this).closest('form').submit(); - }); + /*---------------- + Listener for changes in access-level for a plan shared with a user + TODO partial update instead of forcing a page reload + ------------------*/ + $(".toggle-existing-user-access").change(function(){ + var params = {role: {access_level: $(this).find("option:checked").val()}}; + remoteSave("/roles/" + $(this).closest("form").find("#role_id").val(), 'PUT', JSON.stringify(params)); + }); + + // Run the input validations when the focus changes + $("#role_user_email").on('blur', function(){ + toggleInputError(this, validateEmail($(this).val().trim())); + }); + + // See if we should enable the add collaborator button + $("#role_user_email").on('change keyup', function(){ + toggleAddCollaboratorSubmit(); + }); + $("input[name='role[access_level]']").on('click', function(){ + toggleAddCollaboratorSubmit(); + }); + + $("input[name='plan[visibility]']").on('click, change', function(e){ + var params = {plan: {visibility: $("input[name='plan[visibility]']:checked").val()}}; + remoteSave("/plans/" + $("#plan_id").val() + "/visibility", 'POST', JSON.stringify(params)); + }); + + // Display the submit button only if there is a valid email and password + function toggleAddCollaboratorSubmit(){ + var disabled = (validateEmail($("#role_user_email").val()) != '' || + $("input[name='role[access_level]']:checked").val() == undefined); + $("#add-collaborator-button").attr('aria-disabled', disabled); + } }); \ No newline at end of file diff --git a/lib/assets/javascripts/views/plans/show.js b/lib/assets/javascripts/views/plans/show.js new file mode 100644 index 0000000..157ca48 --- /dev/null +++ b/lib/assets/javascripts/views/plans/show.js @@ -0,0 +1,59 @@ +$(document).ready(function(){ + toggleDataContact(); + + // Run the input validations when the focus changes + $("input[type='email']").on('blur', function(){ + toggleInputError(this, validateEmail($(this).val().trim())); + }).on('change keyup', function(){ + toggleProjectDetailsSubmit(); + }); + + $("#plan_title").on('blur', function(){ + toggleInputError(this, ($(this).val().trim().length <= 0 ? __('The title cannot be blank') : ''), false); + }); + + $("#show-data-contact").click(function(){ + toggleDataContact(); + }) + + $("#is_test").click(function(){ + $("#plan_visibility").val($(this).is(":checked") ? 'is_test' : 'privately_visible'); + }); + + $("#show-other-guidance-orgs").click(function(){ + if($("#other-guidance-orgs").css('display') === 'block'){ + $("#other-guidance-orgs").hide(); + $(this).html($(this).html().replace('Hide', __('See'))); + }else{ + $("#other-guidance-orgs").show(); + $(this).html($(this).html().replace('See', __('Hide'))); + } + }); + + // Check form validation on page load + toggleProjectDetailsSubmit(); + + function toggleDataContact(){ + if($("#show-data-contact").is(':checked')){ + $(".data-contact-info").hide(); + $(".data-contact-info input").val(''); + }else{ + $(".data-contact-info").show(); + } + } + + function toggleProjectDetailsSubmit(){ + var piEmail = $("#plan_principal_investigator_email").val(); + var dcEmail = $("#plan_data_contact_email").val(); + var disabled = $("#plan_title").val() == undefined; + + if(piEmail.trim() != '' && !disabled){ + disabled = validateEmail(piEmail) != ''; + } + if(dcEmail.trim() != '' && !disabled){ + disabled = validateEmail(dcEmail) != ''; + } + + $("#save-details-button").attr('aria-disabled', disabled); + } +}); \ No newline at end of file diff --git a/lib/assets/stylesheets/dmproadmap.scss b/lib/assets/stylesheets/dmproadmap.scss index 5a89958..14bc085 100644 --- a/lib/assets/stylesheets/dmproadmap.scss +++ b/lib/assets/stylesheets/dmproadmap.scss @@ -19,9 +19,18 @@ $highlight-color: $black; $highlight-background-color: #F36F24; -$error-color: #FFF; -$error-background: #827D7E; -$disabled-button-color: #CCC; +/* MESSAGES */ +$error-color: $white; +$error-background: #990000; + +$notice-color: $white; +$notice-background: #196719; + +$tooltip-color: $white; +$tooltip-background: #333; + +/* BUTTONS */ +$disabled-button-color: #808080; $cancel-button-color: #F17D04; /* HEADER STYLING */ @@ -33,4 +42,6 @@ /* HEADER LOGO POSITIONING */ $header-logo-top-margin: 20px; -$header-logo-left-margin: 10px; \ No newline at end of file +$header-logo-left-margin: 10px; + +$body-background: $light-grey; \ No newline at end of file diff --git a/lib/assets/stylesheets/dmproadmap/base.scss b/lib/assets/stylesheets/dmproadmap/base.scss index 3234ee0..607d53f 100644 --- a/lib/assets/stylesheets/dmproadmap/base.scss +++ b/lib/assets/stylesheets/dmproadmap/base.scss @@ -6,7 +6,7 @@ /* Main page layout: header, content, footer */ body { - background-color: $white; + background: $body-background; color: $black; font-family: $font-family; font-size: 14px; @@ -19,9 +19,14 @@ font-weight: lighter; text-rendering: optimizelegibility; color: $primary-color; + margin-top: 10px; + margin-bottom: 15px; } -h1 { font-size: 40px; } +h1 { + width: 65%; + font-size: 40px; +} h2 { font-size: 32px; } h3 { font-size: 26px; } h4 { font-size: 20px; } @@ -135,12 +140,13 @@ position: absolute; font-family: $font-family; font-size: 14px; - color: $white; - background-color: $dark-grey; + color: $tooltip-color; + background: $tooltip-background; border-radius: 3px; padding: 15px 20px; z-index: 10; - a { + a, a:hover, a:visited, a:focus, + h1, h2, h3, h4, h5 { color: $white; } } @@ -154,8 +160,8 @@ position: absolute; top: 15px; width: 300px; - background-color: $dark-grey; - color: $white; + background: $tooltip-background; + color: $tooltip-color; padding: 6px 10px; border-radius: 3px; z-index: 9; @@ -351,8 +357,7 @@ position: relative; float: right; margin-top: 10px; - padding: 8px 30px; - border: 1px solid $dark-grey; + padding: 8px 30px 10px 60px; border-radius: 5px; vertical-align: middle; width: auto; @@ -372,17 +377,17 @@ } } div.roadmap-info-box { - color: $black; - background-color: $white; + color: $notice-color; + background: $notice-background; .fa { - color: green; + color: $notice-color; } } div.roadmap-alert-box { - color: $black; - background-color: $white; + color: $error-color; + background: $error-background; .fa { - color: red; + color: $error-color; } } @@ -394,10 +399,13 @@ div.page { position: relative; width: 100%; - background-color: $white; + background: $body-background; border-radius: 3px; div.content { + background-color: $white; + border-radius: 3px; + padding: 10px 15px 25px 15px; /* By default, page content is one column, but use the below styles to create a 2 column style */ div.column-left { diff --git a/lib/assets/stylesheets/dmproadmap/forms.scss b/lib/assets/stylesheets/dmproadmap/forms.scss index fef96ea..ce5c7ef 100644 --- a/lib/assets/stylesheets/dmproadmap/forms.scss +++ b/lib/assets/stylesheets/dmproadmap/forms.scss @@ -41,6 +41,10 @@ height: 65px; } +label.disabled { + color: $disabled-button-color; +} + .checkbox-label { display: inline-block; font-size: 1em; @@ -60,12 +64,26 @@ border: 0; } +div.under-input { + display: block; + margin: -5px 0 10px; + + label { + font-weight: normal; + } +} /* Roadmap Form Styling */ /* ------------------------------------------------ */ form.roadmap-form { text-align: top; + div.form-separator { + width: 75%; + margin: 25px auto; + border-bottom: 1px solid $dark-grey; + } + fieldset.padded { padding: 10px 10px 25px 10px; } @@ -78,6 +96,16 @@ text-align: left; margin-bottom: 25px; + div.form-separator { + margin-left: 15px; + } + + .mce-tinymce { + display: inline-block; + margin: 10px 5px 15px 15px; + width: 70%; + } + label, input[type="checkbox"], .combobox-container, @@ -93,6 +121,14 @@ vertical-align: top; } + label:not(.checkbox-label) { + display: block; + } + + input, select { + margin-bottom: 15px; + } + .checkbox-label { font-weight: normal; display: inline-block; @@ -111,6 +147,14 @@ width: 51%; } + div.under-input { + margin-left: 25%; + + label { + width: auto; + } + } + div { label, .button-spacer, @@ -161,7 +205,7 @@ border-bottom: none; } - label { + label:not(.regular-text) { font-weight: bold; } label:not(.no-colon):not(.checkbox-label):after { @@ -175,15 +219,6 @@ clear: both; } - div.inline { - display: inline-block; - } - .left-indent { - margin-left: 15px; - } - .right-indent { - margin-right: 5px; - } .input-full-width { width: 95%; } @@ -207,10 +242,6 @@ width: 550px; } - /* Override the button color for the Org Admin sections */ - input[type="submit"].admin{ - background-color: $primary-color; - } input.form-submit[aria-disabled='true'] { background-color: $disabled-button-color; } @@ -250,43 +281,41 @@ display: none; width: 45%; background: $error-background; - border-radius: 3px; + border-radius: 6px; color: $error-color; - padding: 4px 6px; + padding: 6px 25px 7px 10px; } - .error-tooltip[role='tooltip'], .submit-tooltip[role='tooltip'] { - top: 55px; + .error-tooltip[role='alert'], .submit-tooltip[role='tooltip'] { + top: 65px; left: 0; display: inline; position: absolute; z-index: 9; } - .error-tooltip[role='tooltip']:before, .submit-tooltip[role='tooltip']:before { + .error-tooltip[role='alert']:before, .submit-tooltip[role='tooltip']:before { display: inline; position: absolute; - top: -5px; - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-bottom: 5px solid $error-background; + top: -30px; + margin: 0 auto; + @include icon(caret-up); + color: $error-background; + font-size: 44px; } - .error-tooltip-right[role="tooltip"] { + .error-tooltip-right[role="alert"], .error-tooltip-right[role="tooltip"] { top: 0; - left: 500px; + left: 400px; display: inline; } - .error-tooltip-right[role='tooltip']:before { + .error-tooltip-right[role='alert']:before, .error-tooltip-right[role="tooltip"] { display: inline; - position: absolute; - left: -5px; - width: 0; - height: 0; - border-top: 5px solid transparent; - border-right: 5px solid transparent; - border-bottom: 5px solid $error-background; + position: relative; + left: -23px; + top: 11px; + @include icon(caret-left); + color: $error-background; + font-size: 44px; } } @@ -621,11 +650,6 @@ div.show-plan { width: 60%; - hr { - width: 45%; - margin: 5px 0 15px 0; - } - div.side-by-side { margin: 10px auto 0 auto; } @@ -637,16 +661,20 @@ .edit-plan-left { display: inline-block; width: 60%; - - hr { - width: 65%; - } } .edit-plan-right { vertical-align: top; display: inline-block; width: 35%; + h2 { + margin-top: 0; + } + + ul { + list-style: none; + } + .fa { vertical-align: top; font-size: 18px;