mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-03-06 12:43:06 +08:00
Merge branch 'improved-sample-table' of https://github.com/biosistemika/scinote-web into zd_improved_samples_table_SCI_774_775_778_777
This commit is contained in:
commit
8dd8a7e7cd
15 changed files with 507 additions and 97 deletions
|
@ -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 = $('<select></select>')
|
||||
.attr('name', 'sample_type_id').addClass('show-tick');
|
||||
|
||||
var $option = $('<option></option>')
|
||||
var $option = $("<option href='/organizations/1/sample_types'></option>")
|
||||
.attr('value', -2)
|
||||
.text(I18n.t('samples.table.add_sample_type'));
|
||||
$selectType.append($option);
|
||||
$option = $('<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 = $('<select></select>')
|
||||
.attr('name', 'sample_group_id').addClass('show-tick');
|
||||
|
||||
var $span = $("<span></span>").addClass('glyphicon glyphicon-asterisk');
|
||||
var $option = $('<option></option>')
|
||||
var $option = $("<option href='/organizations/1/sample_groups'></option>")
|
||||
.text(I18n.t('samples.table.add_sample_group'));
|
||||
$selectGroup.append($option);
|
||||
$option = $('<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(
|
||||
'<th class="custom-field" id="' + data.id + '">' +
|
||||
'<th class="custom-field" id="' + data.id + '" ' +
|
||||
'data-editable data-deletable ' +
|
||||
'data-edit-url="' + data.edit_url + '" ' +
|
||||
'data-destroy-html-url="' + data.destroy_html_url + '"' +
|
||||
'>' +
|
||||
data.name + '</th>');
|
||||
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 = '<li data-position="' + colIndex + '" class="' + visLi +
|
||||
'"><i class="grippy"></i> <span class="text">' +
|
||||
el.innerText + '</span> <span class="pull-right controls">' +
|
||||
'<span class="vis glyphicon ' + visClass + '"></span> ' +
|
||||
'<span class="edit glyphicon glyphicon-pencil"></span> ' +
|
||||
'<span class="del glyphicon glyphicon-trash"></span>' +
|
||||
'</span></li>';
|
||||
var editClass = (editable) ? '' : 'disabled';
|
||||
var delClass = (deletable) ? '' : 'disabled';
|
||||
var html =
|
||||
'<li ' +
|
||||
'data-position="' + colIndex + '" ' +
|
||||
'data-id="' + $(el).attr('id') + '" ' +
|
||||
'data-edit-url=' + $(el).attr('data-edit-url') + ' ' +
|
||||
'data-destroy-html-url=' + $(el).attr('data-destroy-html-url') + ' ' +
|
||||
'class="' + visLi + '"' +
|
||||
'>' +
|
||||
'<i class="grippy"></i> ' +
|
||||
'<span class="text">' + el.innerText + '</span> ' +
|
||||
'<input type="text" class="text-edit form-control" style="display: none;" />' +
|
||||
'<span class="pull-right controls">' +
|
||||
'<span class="ok glyphicon glyphicon-ok" style="display: none;"></span>' +
|
||||
'<span class="cancel glyphicon glyphicon-remove" style="display: none;"></span>' +
|
||||
'<span class="vis glyphicon ' + visClass + '"></span> ' +
|
||||
'<span class="edit glyphicon glyphicon-pencil ' + editClass + '">' +
|
||||
'</span> ' +
|
||||
'<span class="del glyphicon glyphicon-trash ' + delClass + '">' +
|
||||
'</span>' +
|
||||
'</span>' +
|
||||
'</li>';
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -35,6 +35,9 @@ $color-mojo: #cf4b48;
|
|||
$color-apple-blossom: #a94442;
|
||||
$color-milano-red: #a70b05;
|
||||
|
||||
// Colors for specific intents
|
||||
$color-visited-link: #23527c;
|
||||
|
||||
//==============================================================================
|
||||
// Other
|
||||
//==============================================================================
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
16
app/views/samples/_delete_custom_field_modal.html.erb
Normal file
16
app/views/samples/_delete_custom_field_modal.html.erb
Normal file
|
@ -0,0 +1,16 @@
|
|||
<div class="modal fade" id="deleteCustomField" tabindex="-1" role="dialog" aria-labelledby="deleteCustomFieldLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><%= t("samples.modal_delete_custom_field.title") %></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-action="delete"><%= t("samples.modal_delete_custom_field.delete") %></button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><%= t("general.cancel")%></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
12
app/views/samples/_delete_custom_field_modal_body.html.erb
Normal file
12
app/views/samples/_delete_custom_field_modal_body.html.erb
Normal file
|
@ -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| %>
|
||||
<p><%= t("samples.modal_delete_custom_field.message", cf: @custom_field.name) %></p>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
|
||||
<%= t("samples.modal_delete_custom_field.alert_heading") %>
|
||||
<ul>
|
||||
<li><%= t("samples.modal_delete_custom_field.alert_line_1", nr: @custom_field.sample_custom_fields.count) %></li>
|
||||
<li><%= t("samples.modal_delete_custom_field.alert_line_2") %></li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
|
@ -1,5 +1,7 @@
|
|||
<%= render partial: "samples/import_samples_modal" %>
|
||||
<%= render partial: "samples/delete_samples_modal" %>
|
||||
<%= render partial: "samples/delete_custom_field_modal" %>
|
||||
|
||||
<!-- Modal for parsing sample sheets should be empty at first -->
|
||||
<div class="modal fade" id="modal-parse-samples" tabindex="-1" role="dialog" aria-labelledby=="modal-parse-samples-label"></div>
|
||||
|
||||
|
@ -138,7 +140,15 @@
|
|||
<th id="added-on"><%= t("samples.table.added_on") %></th>
|
||||
<th id="added-by"><%= t("samples.table.added_by") %></th>
|
||||
<% all_custom_fields.each do |cf| %>
|
||||
<th class="custom-field" id="<%= cf.id %>"><%= cf.name %></th>
|
||||
<th class="custom-field"
|
||||
id="<%= cf.id %>"
|
||||
<%= 'data-editable' if can_edit_custom_field(cf) %>
|
||||
<%= '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 %>
|
||||
</th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -832,6 +832,13 @@ en:
|
|||
modal_add_custom_field:
|
||||
title_html: "Add new column to team <strong>%{organization}</strong>"
|
||||
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 <strong>%{organization}</strong>"
|
||||
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}"
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in a new issue