diff --git a/app/assets/javascripts/samples/sample_datatable.js b/app/assets/javascripts/samples/sample_datatable.js
index fe7725be7..362b90234 100644
--- a/app/assets/javascripts/samples/sample_datatable.js
+++ b/app/assets/javascripts/samples/sample_datatable.js
@@ -26,7 +26,8 @@ function dataTableInit() {
processing: true,
serverSide: true,
colReorder: {
- fixedColumnsLeft: 2
+ fixedColumnsLeft: 2,
+ realtime: false
},
destroy: true,
ajax: {
@@ -89,55 +90,54 @@ function dataTableInit() {
},
preDrawCallback: function() {
animateSpinner(this);
- $(".sample_info").off("click");
+ $('.sample_info').off('click');
},
- stateLoadCallback: function (settings) {
+ stateLoadCallback: function(settings) {
// Send an Ajax request to the server to get the data. Note that
// this is a synchronous request since the data is expected back from the
// function
- var org = $("#samples").attr("data-organization-id")
- var user = $("#samples").attr("data-user-id")
+ var org = $('#samples').attr('data-organization-id');
+ var user = $('#samples').attr('data-user-id');
- $.ajax( {
- url: '/state_load/'+org+'/'+user,
+ $.ajax({
+ url: '/state_load/' + org + '/' + user,
data: {org: org},
async: false,
- dataType: "json",
- type: "POST",
- success: function (json) {
+ dataType: 'json',
+ type: 'POST',
+ success: function(json) {
myData = json.state;
}
- } );
- return myData
+ });
+ return myData;
},
- stateSaveCallback: function (settings, data) {
+ stateSaveCallback: function(settings, data) {
// Send an Ajax request to the server with the state object
- var org = $("#samples").attr("data-organization-id")
- var user = $("#samples").attr("data-user-id")
-
+ var org = $('#samples').attr('data-organization-id');
+ var user = $('#samples').attr('data-user-id');
// Save correct data
if (loadFirstTime == true) {
data = myData;
}
- $.ajax( {
- url: '/state_save/'+org+'/'+user,
+ $.ajax({
+ url: '/state_save/' + org + '/' + user,
data: {org: org, state: data},
- dataType: "json",
- type: "POST"
- } );
+ dataType: 'json',
+ type: 'POST'
+ });
loadFirstTime = false;
},
fnInitComplete: function(oSettings, json) {
// Reload correct column order and visibility (if you refresh page)
- oSettings._colReorder.fnOrder(myData.ColReorder);
for (var i = 0; i < table.columns()[0].length; i++) {
var visibility = myData.columns[i].visible;
- if (typeof(visibility) === "string"){
- var visibility = (visibility === "true");
+ if (typeof (visibility) === 'string') {
+ visibility = (visibility === 'true');
}
table.column(i).visible(visibility);
}
+ oSettings._colReorder.fnOrder(myData.ColReorder);
}
});
@@ -664,7 +664,7 @@ function onClickAddSample() {
else if ($(th).attr("id") == "sample-type") {
var colIndex = getColumnIndex("#sample-type")
if (colIndex) {
- var selectType = createSampleTypeSelect(data["sample_types"], -1);
+ var selectType = createSampleTypeSelect(data["sample_types"]);
var td = createTdElement("");
td.appendChild(selectType[0]);
tr.appendChild(td);
@@ -673,7 +673,7 @@ function onClickAddSample() {
else if ($(th).attr("id") == "sample-group") {
var colIndex = getColumnIndex("#sample-group")
if (colIndex) {
- var selectGroup = createSampleGroupSelect(data["sample_groups"], -1);
+ var selectGroup = createSampleGroupSelect(data["sample_groups"]);
var td = createTdElement("");
td.appendChild(selectGroup[0]);
tr.appendChild(td);
@@ -736,14 +736,21 @@ function createTdElement(content) {
/**
* Creates select dropdown for sample type
- * @param data List of sample types
- * @param selected Selected sample type id
+ * @param {Object[]} data List of sample types
+ * @param {number} selected Selected sample type id
+ * @return {Object} select dropdown
*/
function createSampleTypeSelect(data, selected) {
+ selected = _.isUndefined(selected) ? 1 : selected + 1;
+
var $selectType = $(' ')
.attr('name', 'sample_type_id').addClass('show-tick');
- var $option = $(' ')
+ var $option = $(" ")
+ .attr('value', -2)
+ .text(I18n.t('samples.table.add_sample_type'));
+ $selectType.append($option);
+ $option = $(' ')
.attr('value', -1).text(I18n.t('samples.table.no_type'))
$selectType.append($option);
@@ -752,7 +759,7 @@ function createSampleTypeSelect(data, selected) {
.attr('value', val.id).text(val.name);
$selectType.append($option);
});
- $selectType.val(selected);
+ $selectType.makeDropdownOptionsLinks(selected, 'add-mode');
return $selectType;
}
@@ -762,11 +769,15 @@ function createSampleTypeSelect(data, selected) {
* @param selected Selected sample group id
*/
function createSampleGroupSelect(data, selected) {
+ selected = _.isUndefined(selected) ? 1 : selected + 1;
+
var $selectGroup = $(' ')
.attr('name', 'sample_group_id').addClass('show-tick');
- var $span = $(" ").addClass('glyphicon glyphicon-asterisk');
- var $option = $(' ')
+ var $option = $(" ")
+ .text(I18n.t('samples.table.add_sample_group'));
+ $selectGroup.append($option);
+ $option = $(' ')
.attr('value', -1).text(I18n.t('samples.table.no_group'))
.attr('data-icon', 'glyphicon glyphicon-asterisk');
$selectGroup.append($option);
@@ -780,7 +791,7 @@ function createSampleGroupSelect(data, selected) {
$selectGroup.append($option);
});
- $selectGroup.val(selected);
+ $selectGroup.makeDropdownOptionsLinks(selected, 'add-mode');
return $selectGroup;
}
@@ -802,10 +813,15 @@ function changeToEditMode() {
table.button(0).enable(false);
}
+/*
+ * Sample columns dropdown
+ */
(function(table) {
'use strict';
+ var dropdown = $('#samples-columns-dropdown');
var dropdownList = $('#samples-columns-list');
+ var columnEditMode = false;
function createNewColumn() {
// Make an Ajax request to custom_fields_controller
@@ -837,11 +853,14 @@ function changeToEditMode() {
$('#samples').data('num-columns',
$('#samples').data('num-columns') + 1);
originalHeader.append(
- '
' +
+ ' ' +
data.name + ' ');
var colOrder = table.colReorder.order();
colOrder.push(colOrder.length);
- table.colReorder.reset();
// Remove all event handlers as we re-initialize them later with
// new table
$('#samples').off();
@@ -852,8 +871,9 @@ function changeToEditMode() {
$('div.toolbarButtons').appendTo('div.samples-table');
$('div.toolbarButtons').hide();
table = dataTableInit();
- table.colReorder.order(colOrder, true);
- loadColumnsNames();
+ table.on('init.dt', function() {
+ loadColumnsNames();
+ });
},
url: url
});
@@ -900,15 +920,34 @@ function changeToEditMode() {
if (index > 1) {
var colIndex = $(el).attr('data-column-index');
var visible = table.column(colIndex).visible();
+ var editable = $(el).is('[data-editable]');
+ var deletable = $(el).is('[data-deletable]');
+
var visClass = (visible) ? 'glyphicon-eye-open' : 'glyphicon-eye-close';
var visLi = (visible) ? '' : 'col-invisible';
- var html = ' ' +
- el.innerText + ' ' +
- ' ' +
- ' ' +
- ' ' +
- ' ';
+ var editClass = (editable) ? '' : 'disabled';
+ var delClass = (deletable) ? '' : 'disabled';
+ var html =
+ '' +
+ ' ' +
+ '' + el.innerText + ' ' +
+ ' ' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '' +
+ ' ' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ';
dropdownList.append(html);
}
});
@@ -971,12 +1010,202 @@ function changeToEditMode() {
});
}
+ function initEditColumns() {
+ function cancelEditMode() {
+ dropdownList.find('.text-edit').hide();
+ dropdownList.find('.controls .ok,.cancel').hide();
+ dropdownList.find('.text').css('display', ''); // show() doesn't work
+ dropdownList.find('.controls .vis,.edit,.del').css('display', ''); // show() doesn't work
+ columnEditMode = false;
+ }
+
+ function editColumn(li) {
+ var id = li.attr('data-id');
+ var text = li.find('.text');
+ var textEdit = li.find('.text-edit');
+ var newName = textEdit.val().trim();
+ var url = li.attr('data-edit-url');
+
+ $.ajax({
+ url: url,
+ type: 'PUT',
+ data: {custom_field: {name: newName}},
+ dataType: 'json',
+ success: function() {
+ text.text(newName);
+ $(table.columns().header()).filter('#' + id).text(newName);
+ cancelEditMode();
+ },
+ error: function(xhr) {
+ // TODO
+ }
+ });
+ }
+
+ // On edit buttons click (bind onto master dropdown list)
+ dropdownList.on('click', '.edit:not(.disabled)', function(event) {
+ event.stopPropagation();
+
+ cancelEditMode();
+
+ var self = $(this);
+ var li = self.closest('li');
+ var text = li.find('.text');
+ var textEdit = li.find('.text-edit');
+ var controls = li.find('.controls .vis,.edit,.del');
+ var controlsEdit = li.find('.controls .ok,.cancel');
+
+ // Toggle edit mode
+ columnEditMode = true;
+ li.addClass('editing');
+
+ // Set the text-edit's value
+ textEdit.val(text.text().trim());
+
+ // Toggle elements
+ text.hide();
+ controls.hide();
+ textEdit.css('display', ''); // show() doesn't work
+ controlsEdit.css('display', ''); // show() doesn't work
+
+ // Focus input
+ textEdit.focus();
+ });
+
+ // On hiding dropdown, cancel edit mode throughout dropdown
+ dropdown.on('hidden.bs.dropdown', function() {
+ cancelEditMode();
+ });
+
+ // On ok buttons click
+ dropdownList.on('click', '.ok', function(event) {
+ event.stopPropagation();
+ var self = $(this);
+ var li = self.closest('li');
+ editColumn(li);
+ });
+
+ // On enter click while editing column text
+ dropdownList.on('keydown', 'input.text-edit', function(event) {
+ if (event.keyCode === 13) {
+ event.preventDefault();
+ var self = $(this);
+ var li = self.closest('li');
+ editColumn(li);
+ }
+ });
+
+ // On cancel buttons click
+ dropdownList.on('click', '.cancel', function(event) {
+ event.stopPropagation();
+ var self = $(this);
+ var li = self.closest('li');
+
+ columnEditMode = false;
+ li.removeClass('editing');
+
+ li.find('.text-edit').hide();
+ li.find('.controls .ok,.cancel').hide();
+ li.find('.text').css('display', ''); // show() doesn't work
+ li.find('.controls .vis,.edit,.del').css('display', ''); // show() doesn't work
+ });
+ }
+
+ function initDeleteColumns() {
+ var modal = $('#deleteCustomField');
+
+ dropdownList.on('click', '.del', function(event) {
+ event.stopPropagation();
+
+ var self = $(this);
+ var li = self.closest('li');
+ var url = li.attr('data-destroy-html-url');
+
+ $.ajax({
+ url: url,
+ type: 'GET',
+ dataType: 'json',
+ success: function(data) {
+ var modalBody = modal.find('.modal-body');
+
+ // Inject the body's HTML into modal
+ modalBody.html(data.html);
+
+ // Show the modal
+ modal.modal('show');
+ },
+ error: function(xhr) {
+ // TODO
+ }
+ });
+ });
+
+ modal.find('.modal-footer [data-action=delete]').on('click', function() {
+ var modalBody = modal.find('.modal-body');
+ var form = modalBody.find('[data-role=destroy-custom-field-form]');
+ var id = form.attr('data-id');
+
+ form
+ .on('ajax:success', function() {
+ // Destroy datatable
+ table.destroy();
+
+ // Subtract number of columns
+ $('#samples').data(
+ 'num-columns',
+ $('#samples').data('num-columns') - 1
+ );
+
+ // Remove column from table (=table header) & rows
+ var th = originalHeader.find('#' + id);
+ var index = th.index();
+ th.remove();
+ $('#samples tbody td:nth-child(' + (index + 1) + ')').remove();
+
+ // Remove all event handlers as we re-initialize them later with
+ // new table
+ $('#samples').off();
+ $('#samples thead').empty();
+ $('#samples thead').append(originalHeader);
+
+ // Preserve save/delete buttons as we need them after new table
+ // will be created
+ $('div.toolbarButtons').appendTo('div.samples-table');
+ $('div.toolbarButtons').hide();
+
+ // Re-initialize datatable
+ table = dataTableInit();
+ loadColumnsNames();
+
+ // Hide modal
+ modal.modal('hide');
+ })
+ .on('ajax:error', function() {
+ // TODO
+ });
+
+ form.submit();
+ });
+
+ modal.on('hidden.bs.modal', function() {
+ // Remove event handlers, clear contents
+ var modalBody = modal.find('.modal-body');
+ modalBody.off();
+ modalBody.html('');
+ });
+ }
+
// initialze dropdown after the table is loaded
function initDropdown() {
table.on('init.dt', function() {
initNewColumnForm();
- loadColumnsNames();
initSorting();
+ toggleColumnVisibility();
+ initEditColumns();
+ initDeleteColumns();
+ });
+ $('#samples-columns-dropdown').on('show.bs.dropdown', function() {
+ loadColumnsNames();
});
}
diff --git a/app/assets/javascripts/samples/sample_types_groups.js b/app/assets/javascripts/samples/sample_types_groups.js
index a54d72fa4..9caad1f20 100644
--- a/app/assets/javascripts/samples/sample_types_groups.js
+++ b/app/assets/javascripts/samples/sample_types_groups.js
@@ -233,6 +233,16 @@
});
}
+/**
+ * Opens adding mode when redirected from samples page, when clicking link for
+ * adding sample type or group link
+ */
+ function sampleTypeGroupEditMode() {
+ if (getParam('add-mode')) {
+ $('#create-resource').click();
+ }
+ }
+
function initSampleTypesGroups() {
showNewSampleTypeGroupForm();
newSampleTypeFormCancel();
@@ -245,6 +255,7 @@
initSampleGroupColor();
bindNewSampleGroupAction();
appendCarretToColorPickerDropdown();
+ sampleTypeGroupEditMode();
}
// initialize sample types/groups actions
diff --git a/app/assets/javascripts/samples/samples.js b/app/assets/javascripts/samples/samples.js
index ca17e8200..e648e1872 100644
--- a/app/assets/javascripts/samples/samples.js
+++ b/app/assets/javascripts/samples/samples.js
@@ -1,28 +1,5 @@
//= require datatables
-// Create custom field ajax
-$("#modal-create-custom-field").on("show.bs.modal", function(event) {
- // Clear input when modal is opened
- input = $(this).find("input#name-input");
- input.val("");
- input.closest(".form-group").removeClass("has-error");
- input.closest(".form-group").find(".help-block").remove();
-});
-$("#modal-create-custom-field").on("shown.bs.modal", function(event) {
- $(this).find("input#name-input").focus();
-});
-
-$("form#new_custom_field").on("ajax:success", function(ev, data, status) {
- $("#modal-create-custom-field").modal("hide");
-
- // Reload page with URL parameter of newly created field
- window.location.href = addParam(window.location.href, "new_col");
-});
-
-$("form#new_custom_field").on("ajax:error", function(e, data, status, xhr) {
- $('form').renderFormErrors('custom_field', data.responseJSON, true, e);
-});
-
// Create import samples ajax
$("#modal-import-samples").on("show.bs.modal", function(event) {
formGroup = $(this).find(".form-group");
diff --git a/app/assets/javascripts/sitewide/utils.js b/app/assets/javascripts/sitewide/utils.js
index f42f515c2..50224bb57 100644
--- a/app/assets/javascripts/sitewide/utils.js
+++ b/app/assets/javascripts/sitewide/utils.js
@@ -215,3 +215,32 @@ function initPageTutorialSteps(pageFirstStepN, pageLastStepN, nextPagePath,
});
}
}
+
+/**
+ * Add redirection links on dropdown elements. You must specify 'href'
+ * attribute yourself, and the dropdown elments which don't have them, will get
+ * '#' by default.
+ * @param {number} selectedIdx Index of element to be selected
+ * @param {string} urlParam URL parameter to pass to the link URLs
+ * @return {Object} This
+ */
+$.fn.makeDropdownOptionsLinks = function(selectedIdx, urlParam) {
+ selectedIdx = _.isUndefined(selectedIdx) ? 1 : selectedIdx;
+
+ $(this).change(function() {
+ window.location.href = addParam($(this).find('option:selected')
+ .attr('href'), urlParam);
+ });
+
+ $(this).find('option')
+ .each(function() {
+ if ($(this).is('[href]')) {
+ $(this).addClass('link-look');
+ } else {
+ $(this).attr('href', '#');
+ }
+ })
+ .eq(selectedIdx).attr('selected', true);
+
+ return this;
+};
diff --git a/app/assets/stylesheets/constants.scss b/app/assets/stylesheets/constants.scss
index 838ca5e31..b887e7c14 100644
--- a/app/assets/stylesheets/constants.scss
+++ b/app/assets/stylesheets/constants.scss
@@ -35,6 +35,9 @@ $color-mojo: #cf4b48;
$color-apple-blossom: #a94442;
$color-milano-red: #a70b05;
+// Colors for specific intents
+$color-visited-link: #23527c;
+
//==============================================================================
// Other
//==============================================================================
diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss
index 9014f3ef1..cb1e0c311 100644
--- a/app/assets/stylesheets/themes/scinote.scss
+++ b/app/assets/stylesheets/themes/scinote.scss
@@ -235,6 +235,18 @@ body {
a {
color: $color-theme-primary;
+
+ // Override to make link look and behave as a normal link (e.g. used for
+ // elements which have links)
+ &.link-look {
+ color: $color-theme-primary !important;
+
+ :hover,
+ :focus {
+ color: $color-visited-link !important;
+ text-decoration: underline !important;
+ }
+ }
}
.jumbotron {
@@ -1611,15 +1623,6 @@ textarea.textarea-sm {
margin-left: 10px;
}
- .vis {
- cursor: pointer;
- margin-right: 5px;
- }
-
- .edit {
- margin-right: 5px;
- }
-
.grippy {
background-repeat: none;
display: inline-block;
@@ -1628,7 +1631,7 @@ textarea.textarea-sm {
width: 10px;
}
- .grippy-img {
+ li:not(.editing) .grippy-img {
background: url(asset-path('custom/grippy.png'));
}
@@ -1639,6 +1642,14 @@ textarea.textarea-sm {
position: absolute;
}
+ .text-edit {
+ display: inline-block;
+ margin-left: 2px;
+ margin-top: -2.5px;
+ position: absolute;
+ width: 180px;
+ }
+
.col-invisible {
color: $color-alto;
}
@@ -1646,5 +1657,37 @@ textarea.textarea-sm {
.controls {
display: inline-block;
margin-top: 5px;
+
+ span {
+ cursor: pointer;
+
+ &.disabled {
+ visibility: hidden;
+ }
+
+ &:hover {
+ color: $color-emperor;
+ }
+ }
+
+ .ok {
+ color: $color-theme-secondary;
+ margin-right: 5px;
+ }
+
+ .cancel {
+ margin-right: 28px;
+ }
+
+ .vis {
+ margin-right: 5px;
+ }
+
+ .edit {
+ margin-right: 5px;
+ }
+
+ .del {
+ }
}
}
diff --git a/app/controllers/custom_fields_controller.rb b/app/controllers/custom_fields_controller.rb
index 19b6d1dba..f549e60a2 100644
--- a/app/controllers/custom_fields_controller.rb
+++ b/app/controllers/custom_fields_controller.rb
@@ -1,6 +1,9 @@
class CustomFieldsController < ApplicationController
- before_action :load_vars_nested, only: [:create]
- before_action :check_create_permissions, only: [:create]
+ before_action :load_vars, only: [:update, :destroy, :destroy_html]
+ before_action :load_vars_nested, only: [:create, :destroy_html]
+ before_action :check_create_permissions, only: :create
+ before_action :check_update_permissions, only: :update
+ before_action :check_destroy_permissions, only: [:destroy, :destroy_html]
def create
@custom_field = CustomField.new(custom_field_params)
@@ -9,12 +12,19 @@ class CustomFieldsController < ApplicationController
respond_to do |format|
if @custom_field.save
- format.json {
+ format.json do
render json: {
id: @custom_field.id,
- name: @custom_field.name
+ name: @custom_field.name,
+ edit_url:
+ organization_custom_field_path(@organization, @custom_field),
+ destroy_html_url:
+ organization_custom_field_destroy_html_path(
+ @organization, @custom_field
+ )
},
- status: :ok }
+ status: :ok
+ end
else
format.json do
render json: @custom_field.errors.to_json,
@@ -24,22 +34,71 @@ class CustomFieldsController < ApplicationController
end
end
- private
-
- def load_vars_nested
- @organization = Organization.find_by_id(params[:organization_id])
-
- unless @organization
- render_404
+ def update
+ respond_to do |format|
+ format.json do
+ @custom_field.update_attributes(custom_field_params)
+ if @custom_field.save
+ render json: { status: :ok }
+ else
+ render json: @custom_field.errors.to_json,
+ status: :unprocessable_entity
+ end
+ end
end
end
- def check_create_permissions
- unless can_create_custom_field_in_organization(@organization)
- render_403
+ def destroy_html
+ respond_to do |format|
+ format.json do
+ render json: {
+ html: render_to_string(
+ partial: 'samples/delete_custom_field_modal_body.html.erb'
+ )
+ }
+ end
end
end
+ def destroy
+ respond_to do |format|
+ format.json do
+ if @custom_field.destroy
+ render json: { status: :ok }
+ else
+ render json: { status: :unprocessable_entity }
+ end
+ end
+ end
+ end
+
+ private
+
+ def load_vars
+ @custom_field = CustomField.find_by_id(params[:id])
+ @custom_field = CustomField.find_by_id(
+ params[:custom_field_id]
+ ) unless @custom_field
+ render_404 unless @custom_field
+ end
+
+ def load_vars_nested
+ @organization = Organization.find_by_id(params[:organization_id])
+ render_404 unless @organization
+ end
+
+ def check_create_permissions
+ render_403 unless can_create_custom_field_in_organization(@organization)
+ end
+
+ def check_update_permissions
+ render_403 unless can_edit_custom_field(@custom_field)
+ end
+
+ def check_destroy_permissions
+ render_403 unless can_delete_custom_field(@custom_field)
+ end
+
def custom_field_params
params.require(:custom_field).permit(:name)
end
diff --git a/app/helpers/permission_helper.rb b/app/helpers/permission_helper.rb
index 43972e678..175afa9e7 100644
--- a/app/helpers/permission_helper.rb
+++ b/app/helpers/permission_helper.rb
@@ -674,6 +674,16 @@ module PermissionHelper
is_normal_user_or_admin_of_organization(organization)
end
+ def can_edit_custom_field(custom_field)
+ custom_field.user == current_user ||
+ is_admin_of_organization(custom_field.organization)
+ end
+
+ def can_delete_custom_field(custom_field)
+ custom_field.user == current_user ||
+ is_admin_of_organization(custom_field.organization)
+ end
+
# ---- PROTOCOL PERMISSIONS ----
def can_view_organization_protocols(organization)
diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb
index e06ac8baa..f43975f57 100644
--- a/app/models/custom_field.rb
+++ b/app/models/custom_field.rb
@@ -13,7 +13,7 @@ class CustomField < ActiveRecord::Base
belongs_to :last_modified_by,
foreign_key: 'last_modified_by_id',
class_name: 'User'
- has_many :sample_custom_fields, inverse_of: :custom_field
+ has_many :sample_custom_fields, inverse_of: :custom_field, dependent: :destroy
after_create :update_samples_table_state
diff --git a/app/models/samples_table.rb b/app/models/samples_table.rb
index 5d4f92fde..40ce10181 100644
--- a/app/models/samples_table.rb
+++ b/app/models/samples_table.rb
@@ -14,7 +14,7 @@ class SamplesTable < ActiveRecord::Base
index = org_status['columns'].count
org_status['columns'][index] = SampleDatatable::
SAMPLES_TABLE_DEFAULT_STATE['columns'].first
- org_status['ColReorder'] << index
+ org_status['ColReorder'] << index.to_s
samples_table.first.update(status: org_status)
end
diff --git a/app/views/samples/_delete_custom_field_modal.html.erb b/app/views/samples/_delete_custom_field_modal.html.erb
new file mode 100644
index 000000000..61ab9bc6d
--- /dev/null
+++ b/app/views/samples/_delete_custom_field_modal.html.erb
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/app/views/samples/_delete_custom_field_modal_body.html.erb b/app/views/samples/_delete_custom_field_modal_body.html.erb
new file mode 100644
index 000000000..0df63cde8
--- /dev/null
+++ b/app/views/samples/_delete_custom_field_modal_body.html.erb
@@ -0,0 +1,12 @@
+<%= bootstrap_form_for @custom_field, url: organization_custom_field_path(@organization, @custom_field, format: :json), remote: :true, method: :delete, data: { role: "destroy-custom-field-form", id: @custom_field.id } do |f| %>
+ <%= t("samples.modal_delete_custom_field.message", cf: @custom_field.name) %>
+
+
+
+ <%= t("samples.modal_delete_custom_field.alert_heading") %>
+
+ <%= t("samples.modal_delete_custom_field.alert_line_1", nr: @custom_field.sample_custom_fields.count) %>
+ <%= t("samples.modal_delete_custom_field.alert_line_2") %>
+
+
+<% end %>
\ No newline at end of file
diff --git a/app/views/shared/_samples.html.erb b/app/views/shared/_samples.html.erb
index 866101ed9..b25b3ad1e 100644
--- a/app/views/shared/_samples.html.erb
+++ b/app/views/shared/_samples.html.erb
@@ -1,5 +1,7 @@
<%= render partial: "samples/import_samples_modal" %>
<%= render partial: "samples/delete_samples_modal" %>
+<%= render partial: "samples/delete_custom_field_modal" %>
+
@@ -138,7 +140,15 @@
<%= t("samples.table.added_on") %>
<%= t("samples.table.added_by") %>
<% all_custom_fields.each do |cf| %>
- <%= cf.name %>
+
+ <%= 'data-deletable' if can_delete_custom_field(cf) %>
+ <%= "data-edit-url='#{organization_custom_field_path(@organization, cf)}'" %>
+ <%= "data-destroy-html-url='#{organization_custom_field_destroy_html_path(@organization, cf)}'" %>
+ >
+ <%= cf.name %>
+
<% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index c02101663..4193789d4 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -832,6 +832,13 @@ en:
modal_add_custom_field:
title_html: "Add new column to team %{organization} "
create: "Add new column"
+ modal_delete_custom_field:
+ title: "Delete a column"
+ message: "Are you sure you wish to permanently delete selected column %{cf}? This action is irreversible."
+ alert_heading: "Deleting a column has following consequences:"
+ alert_line_1: "you will lose information in this column for %{nr} samples;"
+ alert_line_2: "the column will be deleted for all team members."
+ delete: "Delete column"
modal_add_new_sample_group:
title_html: "Add new sample group to team %{organization} "
create: "Add new sample group"
@@ -847,6 +854,8 @@ en:
added_by: "Added by"
no_group: "No sample group"
no_type: "No sample type"
+ add_sample_type: "Add new sample type"
+ add_sample_group: "Add new sample group"
new:
head_title: "%{organization} | Add new sample"
title: "Add new sample to team %{organization}"
diff --git a/config/routes.rb b/config/routes.rb
index 83f6e2158..8bf857f52 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -79,7 +79,9 @@ Rails.application.routes.draw do
get 'sample_group_element', to: 'sample_groups#sample_group_element'
get 'destroy_confirmation', to: 'sample_groups#destroy_confirmation'
end
- resources :custom_fields, only: [:create]
+ resources :custom_fields, only: [:create, :update, :destroy] do
+ get 'destroy_html'
+ end
member do
post 'parse_sheet'
post 'import_samples'