//= require jquery-ui/widgets/sortable
// Extend datatables API with searchable options
// (http://stackoverflow.com/questions/39912395/datatables-dynamically-set-columns-searchable)
$.fn.dataTable.Api.register('isColumnSearchable()', function(colSelector) {
var idx = this.column(colSelector).index();
return this.settings()[0].aoColumns[idx].bSearchable;
});
$.fn.dataTable.Api.register('setColumnSearchable()', function(colSelector, value) {
if (value !== this.isColumnSearchable(colSelector)) {
var idx = this.column(colSelector).index();
this.settings()[0].aoColumns[idx].bSearchable = value;
if (value === true) {
this.rows().invalidate();
}
}
return value;
});
var rowsSelected = [];
// Tells whether we're currently viewing or editing table
var currentMode = 'viewMode';
// Tells what action will execute by pressing on save button (update/create)
var saveAction = 'update';
var selectedSample;
// Helps saving correct table state
var myData;
var loadFirstTime = true;
var table;
var originalHeader;
var view_assigned;
function dataTableInit() {
// Make a copy of original samples table header
originalHeader = $('#samples thead').children().clone();
view_assigned = 'assigned';
table = $('#samples').DataTable({
order: [[2, 'desc']],
dom: "R<'row'<'col-sm-9-custom toolbar'l><'col-sm-3-custom'f>>tpi",
stateSave: true,
processing: true,
serverSide: true,
sScrollX: '100%',
sScrollXInner: '100%',
scrollY: '64vh',
scrollCollapse: true,
colReorder: {
fixedColumnsLeft: 2,
realtime: false
},
destroy: true,
ajax: {
url: $('#samples').data('source'),
data: function ( d ) {
d.assigned = view_assigned;
},
global: false,
type: 'POST'
},
columnDefs: [{
targets: 0,
searchable: false,
orderable: false,
className: 'dt-body-center',
sWidth: '1%',
render: function() {
return "";
}
}, {
targets: 1,
searchable: false,
orderable: true,
sWidth: '1%'
}, {
targets: 2,
render: function(data, type, row) {
return "" + data + '';
}
}],
rowCallback: function(row, data) {
// Get row ID
var rowId = data.DT_RowId;
// If row ID is in the list of selected row IDs
if ($.inArray(rowId, rowsSelected) !== -1) {
$(row).find('input[type="checkbox"]').prop('checked', true);
$(row).addClass('selected');
}
},
columns: (function() {
var numOfColumns = $('#samples').data('num-columns');
var columns = [];
for (var i = 0; i < numOfColumns; i++) {
var visible = (i <= 6);
columns.push({
data: String(i),
defaultContent: '',
visible: visible,
searchable: visible
});
}
return columns;
})(),
fnDrawCallback: function() {
animateSpinner(this, false);
changeToViewMode();
updateButtons();
// Show number of selected samples info
$('#samples_info').append('');
$('#selected_info').html(' ('+rowsSelected.length+' entries selected)');
},
preDrawCallback: function() {
rowsSelected = [];
animateSpinner(this);
$('.sample-info-link').off('click');
},
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 team = $('#samples').attr('data-team-id');
var user = $('#samples').attr('data-user-id');
$.ajax({
url: '/state_load/' + team + '/' + user,
data: {team: team},
async: false,
dataType: 'json',
type: 'POST',
success: function(json) {
myData = json.state;
}
});
return myData;
},
stateSaveCallback: function(settings, data) {
// Send an Ajax request to the server with the state object
var team = $('#samples').attr('data-team-id');
var user = $('#samples').attr('data-user-id');
// Save correct data
if (loadFirstTime == true) {
data = myData;
}
$.ajax({
url: '/state_save/' + team + '/' + user,
data: {team: team, state: data},
dataType: 'json',
type: 'POST'
});
loadFirstTime = false;
},
fnInitComplete: function(oSettings, json) {
// Reload correct column order and visibility (if you refresh page)
for (var i = 0; i < table.columns()[0].length; i++) {
var visibility = false;
if (myData.columns[i]) {
visibility = myData.columns[i].visible;
}
if (typeof (visibility) === 'string') {
visibility = (visibility === 'true');
}
table.column(i).visible(visibility);
table.setColumnSearchable(i, visibility);
}
oSettings._colReorder.fnOrder(myData.ColReorder);
table.on('mousedown', function() {
$('#samples-columns-dropdown').removeClass('open');
});
initHeaderTooltip();
}
});
// Append button to inner toolbar in table
$('div.toolbarButtons').appendTo('div.toolbar');
$('div.toolbarButtons').show();
$('.delete_samples_submit').click(function() {
animateLoading();
});
$('#assignSamples, #unassignSamples').click(function() {
animateLoading();
});
// Handle click on table cells with checkboxes
$('#samples').on('click', 'tbody td, thead th:first-child', function(e) {
if (!$(e.target).is('.sample-info-link')) {
// Skip if clicking on samples info link
$(this).parent().find('input[type="checkbox"]').trigger('click');
}
});
// Handle clicks on checkbox
$('#samples tbody').on('click', "input[type='checkbox']", function(e) {
if (currentMode !== 'viewMode') {
return false;
}
// Get row ID
var $row = $(this).closest('tr');
var data = table.row($row).data();
var rowId = data.DT_RowId;
// Determine whether row ID is in the list of selected row IDs
var index = $.inArray(rowId, rowsSelected);
// If checkbox is checked and row ID is not in list of selected row IDs
if (this.checked && index === -1) {
rowsSelected.push(rowId);
// Otherwise, if checkbox is not checked and row ID is in list of selected row IDs
} else if (!this.checked && index !== -1) {
rowsSelected.splice(index, 1);
}
if (this.checked) {
$row.addClass('selected');
} else {
$row.removeClass('selected');
}
updateDataTableSelectAllCtrl(table);
e.stopPropagation();
updateButtons();
// Update number of selected samples info
$('#selected_info').html(' ('+ rowsSelected.length +' entries selected)');
});
// Handle click on "Select all" control
$('.dataTables_scrollHead input[name="select_all"]').on('click', function(e) {
if (this.checked) {
$('#samples tbody input[type="checkbox"]:not(:checked)').trigger('click');
} else {
$('#samples tbody input[type="checkbox"]:checked').trigger('click');
}
// Prevent click event from propagating to parent
e.stopPropagation();
});
// Handle table draw event
table.on('draw', function() {
updateDataTableSelectAllCtrl(table);
sampleInfoListener();
// Prevent sample row toggling when selecting user smart annotation link
SmartAnnotation.preventPropagation('.atwho-user-popover');
});
table.on('column-reorder', function() {
sampleInfoListener();
});
return table;
}
table = dataTableInit();
// Timeout for table header scrolling
setTimeout(function () {
table.columns.adjust();
}, 10);
// Updates "Select all" control in a data table
function updateDataTableSelectAllCtrl(table) {
var $table = table.table().node();
var $header = table.table().header();
var $chkbox_all = $('tbody input[type="checkbox"]', $table);
var $chkbox_checked = $('tbody input[type="checkbox"]:checked', $table);
var chkbox_select_all = $('input[name="select_all"]', $header).get(0);
// If none of the checkboxes are checked
if($chkbox_checked.length === 0){
chkbox_select_all.checked = false;
if('indeterminate' in chkbox_select_all){
chkbox_select_all.indeterminate = false;
}
// If all of the checkboxes are checked
} else if ($chkbox_checked.length === $chkbox_all.length){
chkbox_select_all.checked = true;
if('indeterminate' in chkbox_select_all){
chkbox_select_all.indeterminate = false;
}
// If some of the checkboxes are checked
} else {
chkbox_select_all.checked = true;
if('indeterminate' in chkbox_select_all){
chkbox_select_all.indeterminate = true;
}
}
}
// Append selected samples to form
$("form#form-samples").submit(function(e){
var form = this;
if (currentMode == "viewMode")
appendSamplesIdToForm(form);
});
// Append selected samples and headers form
$("form#form-export").submit(function(e){
var form = this;
if (currentMode == "viewMode") {
// Remove all hidden fields
$("#form-export").find("input[name=sample_ids\\[\\]]").remove();
$("#form-export").find("input[name=header_ids\\[\\]]").remove();
// Append samples
appendSamplesIdToForm(form);
// Append visible column information
$("table#samples thead tr").children("th").each(function(i) {
var th = $(this);
var val;
if ($(th).attr("id") == "sample-name")
val = -1;
else if ($(th).attr("id") == "sample-type")
val = -2;
else if ($(th).attr("id") == "sample-group")
val = -3;
else if ($(th).attr("id") == "added-by")
val = -4;
else if ($(th).attr("id") == "added-on")
val = -5;
else if ($(th).hasClass("custom-field"))
val = th.attr("id");
if (val)
$(form).append(
$('')
.attr('type', 'hidden')
.attr('name', 'header_ids[]')
.val(val)
);
});
}
});
function appendSamplesIdToForm(form) {
$.each(rowsSelected, function(index, rowId){
$(form).append(
$('')
.attr('type', 'hidden')
.attr('name', 'sample_ids[]')
.val(rowId)
);
});
}
function initHeaderTooltip() {
// Fix compatibility of fixed table header and column names modal-tooltip
$('.modal-tooltip').off();
$('.modal-tooltip').hover(function() {
var $tooltip = $(this).find('.modal-tooltiptext');
var offsetLeft = $tooltip.offset().left;
(offsetLeft + 200) > $(window).width() ? offsetLeft -= 150 : offsetLeft;
var offsetTop = $tooltip.offset().top;
$('body').append($tooltip);
$tooltip.css('background-color', '#d2d2d2');
$tooltip.css('border-radius', '6px');
$tooltip.css('color', '#333');
$tooltip.css('display', 'block');
$tooltip.css('left', offsetLeft + 'px');
$tooltip.css('padding', '5px');
$tooltip.css('position', 'absolute');
$tooltip.css('text-align', 'center');
$tooltip.css('top', offsetTop + 'px');
$tooltip.css('visibility', 'visible');
$tooltip.css('width', '200px');
$tooltip.css('word-wrap', 'break-word');
$(this).data('dropdown-tooltip', $tooltip);
}, function() {
$(this).append($(this).data('dropdown-tooltip'));
$(this).data('dropdown-tooltip').removeAttr('style');
});
}
//Show sample info
function sampleInfoListener() {
$(".sample_info").on("click", function(e){
$("#modal-info-sample").remove();
var that = $(this);
$.ajax({
method: "GET",
url: that.attr("data-href") + '.json',
dataType: "json"
}).done(function(xhr, settings, data) {
$("body").append($.parseHTML(data.responseJSON.html));
$("#modal-info-sample").modal('show',{
backdrop: true,
keyboard: false,
}).on('hidden.bs.modal', function () {
$(this).find(".modal-body #sample-info-table").DataTable().destroy();
$(this).remove();
});
$('#sample-info-table').DataTable({
dom: "RBltpi",
stateSave: false,
buttons: [],
processing: true,
colReorder: {
fixedColumnsLeft: 1000000 // Disable reordering
},
columnDefs: [{
targets: 0,
searchable: false,
orderable: false
}],
fnDrawCallback: function(settings, json) {
animateSpinner(this, false);
},
preDrawCallback: function(settings) {
animateSpinner(this);
}
});
}).fail(function(error){
// TODO
}).always(function(data){
// TODO
});
e.preventDefault();
return false;
});
}
// Edit sample
function onClickEdit() {
if (rowsSelected.length != 1) return;
var row = table.row("#" + rowsSelected[0]);
var node = row.node();
var rowData = row.data();
$(node).find("td input").trigger("click");
selectedSample = node;
clearAllErrors();
changeToEditMode();
updateButtons();
saveAction = "update";
$.ajax({
url: rowData["sampleEditUrl"],
type: "GET",
dataType: "json",
success: function (data) {
// Show save and cancel buttons in first two columns
$(node).children("td").eq(0).html($("#saveSample").clone());
$(node).children("td").eq(1).html($("#cancelSave").clone());
// Sample name column
var colIndex = getColumnIndex("#sample-name");
if (colIndex) {
$(node).children("td").eq(colIndex).html(changeToInputField("sample", "name", data["sample"]["name"]));
}
// Sample type column
var colIndex = getColumnIndex("#sample-type");
if (colIndex) {
var selectType = createSampleTypeSelect(data["sample_types"], data["sample"]["sample_type"]);
$(node).children("td").eq(colIndex).html(selectType);
$("select[name=sample_type_id]").selectpicker();
}
// Sample group column
var colIndex = getColumnIndex("#sample-group");
if (colIndex) {
var selectGroup = createSampleGroupSelect(data["sample_groups"], data["sample"]["sample_group"]);
$(node).children("td").eq(colIndex).html(selectGroup);
$("select[name=sample_group_id]").selectpicker();
}
// Take care of custom fields
var cfields = data["sample"]["custom_fields"];
$(node).children("td").each(function(i) {
var td = $(this);
var rawIndex = table.column.index("fromVisible", i);
var colHeader = table.column(rawIndex).header();
if ($(colHeader).hasClass("custom-field")) {
// Check if custom field on this sample exists
var cf = cfields[$(colHeader).attr("id")];
if (cf)
td.html(changeToInputField("sample_custom_fields", cf["sample_custom_field_id"], cf["value"]));
else
td.html(changeToInputField("custom_fields", $(colHeader).attr("id"), ""));
}
});
// initialize smart annotation
SmartAnnotation.init($('[data-object="sample_custom_fields"]'));
_.each($('[data-object="custom_fields"]'), function(el) {
if(_.isUndefined($(el).data('atwho'))) {
SmartAnnotation.init(el);
}
});
// Adjust columns width in table header
adjustTableHeader();
},
error: function (e, data, status, xhr) {
if (e.status == 403) {
HelperModule.flashAlertMsg(
I18n.t('samples.js.permission_error'), e.responseJSON.style
);
changeToViewMode();
updateButtons();
}
}
});
}
// Save sample
function onClickSave() {
if (saveAction == "update") {
var row = table.row(selectedSample);
var node = row.node();
var rowData = row.data();
} else if (saveAction == "create")
var node = selectedSample;
// First fetch all the data in input fields
data = {
request_url: $('#samples').data('current-uri'),
sample_id: $(selectedSample).attr("id"),
sample: {},
custom_fields: {}, // These fields are not currently bound to this sample
sample_custom_fields: {} // These fields are already in database (linked to this sample)
};
// Direct sample attributes
// Sample name
$(node).find("td input[data-object = sample]").each(function() {
data["sample"][$(this).attr("name")] = $(this).val();
});
// Sample type
$(node).find("td select[name = sample_type_id]").each(function() {
data["sample"]["sample_type_id"] = $(this).val();
});
// Sample group
$(node).find("td select[name = sample_group_id]").each(function() {
data["sample"]["sample_group_id"] = $(this).val();
});
// Custom fields (new fields)
$(node).find("td input[data-object = custom_fields]").each(function () {
// Send data only and only if string is not empty
if ($(this).val().trim()) {
data["custom_fields"][$(this).attr("name")] = $(this).val();
}
});
// Sample custom fields (existent fields)
$(node).find("td input[data-object = sample_custom_fields]").each(function () {
data["sample_custom_fields"][$(this).attr("name")] = $(this).val();
});
var url = (saveAction == "update" ? rowData["sampleUpdateUrl"] : $("table#samples").data("create-sample"))
var type = (saveAction == "update" ? "PUT" : "POST")
$.ajax({
url: url,
type: type,
dataType: "json",
data: data,
success: function (data) {
HelperModule.flashAlertMsg(data.flash, 'success');
onClickCancel();
},
error: function (e, eData, status, xhr) {
var data = e.responseJSON;
clearAllErrors();
if (e.status == 404) {
HelperModule.flashAlertMsg(
I18n.t('samples.js.not_found_error'), 'danger'
);
changeToViewMode();
updateButtons();
}
else if (e.status == 403) {
HelperModule.flashAlertMsg(
I18n.t('samples.js.permission_error'), 'danger'
);
changeToViewMode();
updateButtons();
}
else if (e.status == 400) {
if (data["init_fields"]) {
var init_fields = data["init_fields"];
// Validate sample name
if (init_fields["name"]) {
var input = $(selectedSample).find("input[name=name]");
if (input) {
input.closest(".form-group").addClass("has-error");
input.parent().append("" + init_fields["name"] + "
");
}
}
};
// Validate custom fields
$.each(data["custom_fields"] || [], function(key, val) {
$.each(val, function(key, val) {
var input = $(selectedSample).find("input[name=" + key + "]");
if (input) {
input.closest(".form-group").addClass("has-error");
input.parent().append("" + val["value"][0] + "
");
}
});
});
// Validate sample custom fields
$.each(data["sample_custom_fields"] || [], function(key, val) {
$.each(val, function(key, val) {
var input = $(selectedSample).find("input[name=" + key + "]");
if (input) {
input.closest(".form-group").addClass("has-error");
input.parent().append("" + val["value"][0] + "
");
}
});
});
}
}
});
}
// Enable/disable edit button
function updateButtons() {
if (currentMode=="viewMode") {
$("#importSamplesButton").removeClass("disabled");
$("#importSamplesButton").prop("disabled",false);
$("#addSample").removeClass("disabled");
$("#addSample").prop("disabled",false);
$("#addNewColumn").removeClass("disabled");
$("#addNewColumn").prop("disabled",false);
$('#samples-columns-dropdown')
.find('.dropdown-toggle')
.prop("disabled",false);
$("th").removeClass('disable-click');
if (rowsSelected.length == 1) {
$("#editSample").prop("disabled", false);
$("#editSample").removeClass("disabled");
$("#deleteSamplesButton").prop("disabled", false);
$("#deleteSamplesButton").removeClass("disabled");
$("#exportSamplesButton").removeClass("disabled");
$("#exportSamplesButton").prop("disabled",false);
$("#exportSamplesButton").on("click", function() {
$('#modal-export-samples-success')
.modal('show')
.on('hidden.bs.modal', function() {
animateSpinner(null, true);
$('#form-export').submit();
});
});
$("#assignSamples").removeClass("disabled");
$("#assignSamples").prop("disabled", false);
$("#unassignSamples").removeClass("disabled");
$("#unassignSamples").prop("disabled", false);
}
else if (rowsSelected.length == 0) {
$("#editSample").prop("disabled", true);
$("#editSample").addClass("disabled");
$("#deleteSamplesButton").prop("disabled", true);
$("#deleteSamplesButton").addClass("disabled");
$("#exportSamplesButton").addClass("disabled");
$("#exportSamplesButton").prop("disabled",true);
$("#exportSamplesButton").off("click");
$("#assignSamples").addClass("disabled");
$("#assignSamples").prop("disabled", true);
$("#unassignSamples").addClass("disabled");
$("#unassignSamples").prop("disabled", true);
}
else {
$("#editSample").prop("disabled", true);
$("#editSample").addClass("disabled");
$("#deleteSamplesButton").prop("disabled", false);
$("#deleteSamplesButton").removeClass("disabled");
$("#exportSamplesButton").removeClass("disabled");
$("#exportSamplesButton").prop("disabled",false);
$("#exportSamplesButton").on("click", function() {
$('#modal-export-samples-success')
.modal('show')
.on('hidden.bs.modal', function() {
animateSpinner(null, true);
$('#form-export').submit();
});
});
$("#assignSamples").removeClass("disabled");
$("#assignSamples").prop("disabled", false);
$("#unassignSamples").removeClass("disabled");
$("#unassignSamples").prop("disabled", false);
}
}
else if (currentMode=="editMode") {
$("#importSamplesButton").addClass("disabled");
$("#importSamplesButton").prop("disabled",true);
$("#addSample").addClass("disabled");
$("#addSample").prop("disabled",true);
$("#editSample").addClass("disabled");
$("#editSample").prop("disabled",true);
$("#addNewColumn").addClass("disabled");
$("#addNewColumn").prop("disabled", true);
$("#exportSamplesButton").addClass("disabled");
$("#exportSamplesButton").off("click");
$("#deleteSamplesButton").addClass("disabled");
$("#deleteSamplesButton").prop("disabled",true);
$("#assignSamples").addClass("disabled");
$("#assignSamples").prop("disabled", true);
$("#unassignSamples").addClass("disabled");
$("#unassignSamples").prop("disabled", true);
$('#samples-columns-dropdown')
.find('.dropdown-toggle')
.prop("disabled",true);
$("th").addClass('disable-click');
}
}
// Clear all has-error tags
function clearAllErrors() {
// Remove any validation errors
$(selectedSample).find(".has-error").each(function() {
$(this).removeClass("has-error");
$(this).find("span").remove();
});
// Remove any alerts
$("#alert-container").find("div").remove();
}
// Restore previous table
function onClickCancel() {
table.draw('page');
changeToViewMode();
updateButtons();
}
function onClickAddSample() {
changeToEditMode();
updateButtons();
saveAction = "create";
$.ajax({
url: $("table#samples").data("new-sample"),
type: "GET",
dataType: "json",
success: function (data) {
var tr = document.createElement("tr")
$("table#samples thead tr").children("th").each(function(i) {
var th = $(this);
if ($(th).attr("id") == "checkbox") {
var td = createTdElement("");
$(td).html($("#saveSample").clone());
tr.appendChild(td);
}
else if ($(th).attr("id") == "assigned") {
var td = createTdElement("");
$(td).html($("#cancelSave").clone());
tr.appendChild(td);
}
else if ($(th).attr("id") == "sample-name") {
var input = changeToInputField("sample", "name", "");
tr.appendChild(createTdElement(input));
}
else if ($(th).attr("id") == "sample-type") {
var colIndex = getColumnIndex("#sample-type")
if (colIndex) {
var selectType = createSampleTypeSelect(data["sample_types"]);
var td = createTdElement("");
td.appendChild(selectType[0]);
tr.appendChild(td);
}
}
else if ($(th).attr("id") == "sample-group") {
var colIndex = getColumnIndex("#sample-group")
if (colIndex) {
var selectGroup = createSampleGroupSelect(data["sample_groups"]);
var td = createTdElement("");
td.appendChild(selectGroup[0]);
tr.appendChild(td);
}
}
else if ($(th).hasClass("custom-field")) {
var input = changeToInputField("custom_fields", th.attr("id"), "");
tr.appendChild(createTdElement(input));
}
else {
// Column we don't care for, just add empty td
tr.appendChild(createTdElement(""));
}
});
$("table#samples").prepend(tr);
selectedSample = tr;
// Init dropdown with icons
$("select[name=sample_group_id]").selectpicker();
$("select[name=sample_type_id]").selectpicker();
// initialize smart annotation
_.each($('[data-object="custom_fields"]'), function(el) {
if(_.isUndefined($(el).data('atwho'))) {
SmartAnnotation.init(el);
}
});
// Adjust columns width in table header
adjustTableHeader();
},
error: function (e, eData, status, xhr) {
if (e.status == 403)
HelperModule.flashAlertMsg(
I18n.t('samples.js.permission_error'), 'danger'
);
changeToViewMode();
updateButtons();
}
});
}
$('#assignedSamples').on('click', function () {
view_assigned = 'assigned';
table.draw();
});
$('#allSamples').on('click', function () {
view_assigned = 'all';
table.draw();
});
// Handle enter key
$(document).off('keypress').keypress(function(event) {
var keycode = (event.keyCode ? event.keyCode : event.which);
if (currentMode === 'editMode' && keycode === '13') {
$('#saveSample').click();
return false;
}
});
// Helper functions
function getColumnIndex(id) {
if (id < 0) {
return false;
}
return table.column(id).index('visible');
}
// Takes object and surrounds it with input
function changeToInputField(object, name, value) {
return "