From 338b710d814ea14acec60346194cd90073b62329 Mon Sep 17 00:00:00 2001 From: Oleksii Kriuchykhin Date: Mon, 18 Nov 2019 14:04:53 +0100 Subject: [PATCH] Refactor inventory edit mode [SCI-4070] --- app/assets/javascripts/application.js.erb | 1 + .../forms/repository_item_edit.js | 656 +++++++---------- .../repositories/renderers/edit_renderers.js | 32 + .../repositories/renderers/new_renderers.js | 24 + .../repositories/repository_datatable.js | 694 ++++++------------ .../javascripts/repositories/row_editor.js | 146 ++++ .../repositories/validators/base_validator.js | 5 + .../stylesheets/themes/repositories.scss | 22 + app/helpers/repository_datatable_helper.rb | 8 - .../repository_table_state_service.rb | 13 +- .../repositories/_repository_table.html.erb | 4 +- app/views/repositories/show.html.erb | 54 +- config/initializers/constants.rb | 2 +- 13 files changed, 764 insertions(+), 897 deletions(-) create mode 100644 app/assets/javascripts/repositories/renderers/edit_renderers.js create mode 100644 app/assets/javascripts/repositories/renderers/new_renderers.js create mode 100644 app/assets/javascripts/repositories/row_editor.js create mode 100644 app/assets/javascripts/repositories/validators/base_validator.js diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index fefa13e71..10312b0dc 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -44,6 +44,7 @@ //= require protocols/header //= require marvinjslauncher //= require_directory ./repositories/renderers +//= require_directory ./repositories/validators //= require turbolinks diff --git a/app/assets/javascripts/repositories/forms/repository_item_edit.js b/app/assets/javascripts/repositories/forms/repository_item_edit.js index 004b31e42..a398437f8 100644 --- a/app/assets/javascripts/repositories/forms/repository_item_edit.js +++ b/app/assets/javascripts/repositories/forms/repository_item_edit.js @@ -1,396 +1,260 @@ -/* global Promise _ ActiveStorage RepositoryItemEditForm */ - -//= require sugar.min -//= require jquerymy-1.2.14.min - -(function(global) { - 'use strict'; - - /** - * RepositoryItemEditForm generates the html inputs for - * repository item and returns the form data object - * - * @param {Object} itemData - repository item data fetched from the API - * @param {Object} repositoryItemElement - row node in the table - */ - global.RepositoryItemEditForm = function(itemData, repositoryItemElement) { - this.itemData = itemData; - this.repositoryItemElement = repositoryItemElement; - this.formData = this.composeFormData(itemData); - } - - /** - * Generates the input fields - * - * @param {Object} table - datatable.js object - * @returns {undefinded} - */ - RepositoryItemEditForm.prototype.renderForm = function(table) { - var colIndex = getColumnIndex(table, '#row-name'); - var cells = this.itemData.repository_row.repository_cells; - var listColumns = this.itemData.repository_row.repository_column_items; - var formData = this.formData; - - if (colIndex) { - $(this.repositoryItemElement).children('td').eq(colIndex) - .html(changeToInputField('repository_row', - 'name', - this.itemData.repository_row.name, - 'rowName')); - } - - $(this.repositoryItemElement).children('td').each(function(i) { - var td = $(this); - var rawIndex = table.column.index('fromVisible', i); - var colHeader = table.column(rawIndex).header(); - - if ($(colHeader).hasClass('repository-column')) { - var type = $(colHeader).attr('data-type'); - var colHeaderId = $(colHeader).attr('id'); - var cell = cells[colHeaderId] || ''; - td.html(changeToFormField('repository_cell', - colHeaderId, - type, - cell, - listColumns)); - addSelectedFile(type, colHeaderId); - appendNewElementToFormData(cell, colHeaderId, formData); - } - }); - initializeDataBinding(this.repositoryItemElement, formData); - } - - /** - * Parse received data in to a from object - * - * @param {Object} itemData - json representations of repository item - * - * @returns {Object} - */ - RepositoryItemEditForm.prototype.composeFormData = function(itemData) { - var formBindingsData = {}; - formBindingsData['rowName'] = itemData.repository_row.name; - $.each(itemData.repository_row.repository_cells, function(i, cell) { - var tableCellId = 'colId-' + cell.cell_column_id; - if(cell.type === 'RepositoryAssetValue') { - formBindingsData[tableCellId] = new File([""], cell.value.file_name); - } else { - formBindingsData[tableCellId] = cell.value; - } - }); - return formBindingsData; - } - - /** - * Handles select picker default value - * - * @param {Object} node - * @returns {undefinded} - */ - RepositoryItemEditForm.prototype.initializeSelectpickerValues = function(node) { - $($(node).find('.bootstrap-select')).each(function(_, dropdown) { - var selectedValue = $($(dropdown).find('select')[0]).data('selected-value'); - var selectPicker = $($(dropdown).find('select')[0]); - var value = '-1' - $(dropdown).find('option').each(function(_, option) { - $(option).removeAttr('selected'); - if($(option).val() === selectedValue.toString()) { - $(option).attr('selected', true); - value = $(option).attr('value'); - } - }); - $(dropdown).parent().attr("list_item_id", value); - selectPicker.val(value); - selectPicker.selectpicker('refresh'); - }); - } - - /** - * Creates a FormData object with the repository row data ready to be - * sended on the server - * - * @param {Object} tableID - * @param {Object} selectedRecord - * - * @returns (Object) - */ - RepositoryItemEditForm.prototype.parseToFormObject = function(tableID, selectedRecord) { - var formData = this.formData; - var formDataObj = new FormData(); - var removeFileColumns = []; - var filesToUploadCntr = 0; - var filesUploadedCntr = 0; - const directUploadUrl = $(tableID).data('directUploadUrl'); - - formDataObj.append('request_url', $(tableID).data('current-uri')); - formDataObj.append('repository_row_id', $(selectedRecord).attr('id')); - - return new Promise((resolve, reject) => { - $(_.keys(this.formData)).each(function(_, element) { - var value = formData[element]; - if (element === 'rowName') { - formDataObj.append('repository_row_name', value); - } else { - let colId = element.replace('colId-', ''); - let $el = $('#' + element); - // don't save anything if element is not visible - if ($el.length === 0) { - return; - } - if ($el.attr('type') === 'file') { - // handle deleting of element - if ($el.attr('remove') === 'true') { - removeFileColumns.push(colId); - formDataObj.append('repository_cells[' + colId + ']', null); - } else if ($el[0].files.length > 0) { - filesToUploadCntr += 1; - } - } else if (value.length >= 0) { - formDataObj.append('repository_cells[' + colId + ']', value); - } - } - }); - - formDataObj.append('remove_file_columns', JSON.stringify(removeFileColumns)); - - // No files for upload, so return earlier - if (filesToUploadCntr === 0) { - resolve(formDataObj); - return; - } - - // Second run, just for files - $(_.keys(this.formData)).each(function(_, element) { - let $el = $('#' + element); - let colId = element.replace('colId-', ''); - - if ($el.attr('type') === 'file' && $el.attr('remove') !== 'true') { - let upload = new ActiveStorage.DirectUpload($el[0].files[0], directUploadUrl); - - upload.create(function(error, blob) { - if (error) { - reject(error); - } else { - formDataObj.append('repository_cells[' + colId + ']', blob.signed_id); - filesUploadedCntr += 1; - - if (filesUploadedCntr === filesToUploadCntr) { - resolve(formDataObj); - } - } - }); - } - }); - }); - }; - - /** - * |-----------------| - * | Private methods | - * |-----------------| - */ - - /** - * Takes object and surrounds it with input - * - * @param {Object} object - * @param {String} name - * @param {String} value - * @param {String} id - * - * @returns (String) - */ - function changeToInputField(object, name, value, id) { - return "
"; - } - - /** - * Takes object and creates an input file field, contains a hidden - * input field which is triggered on button click and we get the uploaded - * file from there. - * - * @param {Object} object - * @param {String} name - * @param {String} value - * @param {String} id - * - * @returns (String) - */ - function changeToInputFileField(object, name, value, id) { - var fileName = (value.file_name) ? value.file_name : I18n.t('general.file.no_file_chosen'); - var buttonLabel = I18n.t('general.file.choose'); - var html = "
" + - "

" + truncateLongString(fileName, 20) + - "

"; - if(value.file_name) { - html += "
'; - html += ''; - $.each(options, function(index, value) { - var selected = ''; - if (current_value === value[1]) { - selected = 'selected'; - val = value[0]; - } - html += ''; - }); - html += ''; - return (val) ? $(html).attr('data-selected-value', val)[0] : html; - } - - /** - * Takes an object and creates custom html element - * - * @param {String} object - * @param {String} name - * @param {String} column_type - * @param {Object} cell - * @param {Object} listColumns - * - * @returns (String) - */ - function changeToFormField(object, name, column_type, cell, listColumns) { - var cellId = generateInputFieldReference(name); - var value = cell.value || ''; - if (column_type === 'RepositoryListValue') { - var column = _.findWhere(listColumns, - { column_id: parseInt(name, 10) }); - var list_items = column.list_items || cell.list_items; - return _listItemDropdown(list_items, value, parseInt(name, 10), cellId); - } else if (column_type === 'RepositoryAssetValue') { - return changeToInputFileField('repository_cell_file', name, value, cellId); - } else { - return changeToInputField(object, name, value, cellId); - } - } - - /** - * Append the change listener to file field - * - * @param {String} type - * @param {String} name - * - * @returns {undefined} - */ - function addSelectedFile(type, name) { - var button = $('button[data-id="' + - generateInputFieldReference(name) + - '"]'); - if (type === 'RepositoryAssetValue') { - var fileInput = $(button.parent().find('input[type="file"]')[0]); - button.on('click', function(ev) { - ev.preventDefault(); - ev.stopPropagation(); - fileInput.trigger('click'); - initFileHandler(fileInput); - }); - } - } - - /** - * Handle extraction of file from the input field - * - * @param {Object} $inputField - * - * @returns {undefined} - */ - function initFileHandler($inputField) { - $inputField.on('change', function() { - var input = $(this); - var $label = $($(this).closest('.repository-input-file-field') - .find('.file-name-label')[0]); - var file = this.files[0]; - if (file) { - $label.text(truncateLongString(file.name, 20)); - input.attr('remove', false); - $($label.closest('.repository-input-file-field') - .find('[data-action="removeAsset"]')[0]).show(); - } - }) - } - - /** - * Initializes the data binding for form object - * - * @param {Object} rowNode - * @param {Object} data - * - * @returns {undefined} - */ - function initializeDataBinding(rowNode, data) { - var uiBindings = {}; - $.each(_.keys(data), function(i, element) { - uiBindings['#' + element] = element; - }) - $(rowNode).my({ui: uiBindings}, data); - } - - /** - * Generates the input tag id that will be used in the formData object - * - * @param {String} columnId - * - * @returns {String} - */ - function generateInputFieldReference(columnId) { - return 'colId-' + columnId; - } - - /** - * Appends aditional fields to form data object - * @param {Object} cell - * @param {String} columnId - * @param {Object} formData - * - * @returns {undefined} - */ - function appendNewElementToFormData(cell, columnId, formData) { - if (!cell.repository_cell_id) { - formData[generateInputFieldReference(columnId)] = undefined; - } - } -}(window)); +// /* global Promise _ ActiveStorage RepositoryItemEditForm */ +// +// //= require sugar.min +// //= require jquerymy-1.2.14.min +// +// (function(global) { +// 'use strict'; +// +// /** +// * Creates a FormData object with the repository row data ready to be +// * sended on the server +// * +// * @param {Object} tableID +// * @param {Object} selectedRecord +// * +// * @returns (Object) +// */ +// RepositoryItemEditForm.prototype.parseToFormObject = function(tableID, selectedRecord) { +// var formData = this.formData; +// var formDataObj = new FormData(); +// var removeFileColumns = []; +// var filesToUploadCntr = 0; +// var filesUploadedCntr = 0; +// const directUploadUrl = $(tableID).data('directUploadUrl'); +// +// formDataObj.append('request_url', $(tableID).data('current-uri')); +// formDataObj.append('repository_row_id', $(selectedRecord).attr('id')); +// +// return new Promise((resolve, reject) => { +// $(_.keys(this.formData)).each(function(_, element) { +// var value = formData[element]; +// if (element === 'rowName') { +// formDataObj.append('repository_row_name', value); +// } else { +// let colId = element.replace('colId-', ''); +// let $el = $('#' + element); +// // don't save anything if element is not visible +// if ($el.length === 0) { +// return; +// } +// if ($el.attr('type') === 'file') { +// // handle deleting of element +// if ($el.attr('remove') === 'true') { +// removeFileColumns.push(colId); +// formDataObj.append('repository_cells[' + colId + ']', null); +// } else if ($el[0].files.length > 0) { +// filesToUploadCntr += 1; +// } +// } else if (value.length >= 0) { +// formDataObj.append('repository_cells[' + colId + ']', value); +// } +// } +// }); +// +// formDataObj.append('remove_file_columns', JSON.stringify(removeFileColumns)); +// +// // No files for upload, so return earlier +// if (filesToUploadCntr === 0) { +// resolve(formDataObj); +// return; +// } +// +// // Second run, just for files +// $(_.keys(this.formData)).each(function(_, element) { +// let $el = $('#' + element); +// let colId = element.replace('colId-', ''); +// +// if ($el.attr('type') === 'file' && $el.attr('remove') !== 'true') { +// let upload = new ActiveStorage.DirectUpload($el[0].files[0], directUploadUrl); +// +// upload.create(function(error, blob) { +// if (error) { +// reject(error); +// } else { +// formDataObj.append('repository_cells[' + colId + ']', blob.signed_id); +// filesUploadedCntr += 1; +// +// if (filesUploadedCntr === filesToUploadCntr) { +// resolve(formDataObj); +// } +// } +// }); +// } +// }); +// }); +// }; +// +// /** +// * Takes object and creates an input file field, contains a hidden +// * input field which is triggered on button click and we get the uploaded +// * file from there. +// * +// * @param {Object} object +// * @param {String} name +// * @param {String} value +// * @param {String} id +// * +// * @returns (String) +// */ +// function changeToInputFileField(object, name, value, id) { +// var fileName = (value.file_name) ? value.file_name : I18n.t('general.file.no_file_chosen'); +// var buttonLabel = I18n.t('general.file.choose'); +// var html = "
" + +// "

" + truncateLongString(fileName, 20) + +// "

"; +// if(value.file_name) { +// html += "
'; +// html += ''; +// $.each(options, function(index, value) { +// var selected = ''; +// if (current_value === value[1]) { +// selected = 'selected'; +// val = value[0]; +// } +// html += ''; +// }); +// html += ''; +// return (val) ? $(html).attr('data-selected-value', val)[0] : html; +// } +// +// /** +// * Takes an object and creates custom html element +// * +// * @param {String} object +// * @param {String} name +// * @param {String} column_type +// * @param {Object} cell +// * @param {Object} listColumns +// * +// * @returns (String) +// */ +// function changeToFormField(object, name, column_type, cell, listColumns) { +// var cellId = generateInputFieldReference(name); +// var value = cell.value || ''; +// if (column_type === 'RepositoryListValue') { +// var column = _.findWhere(listColumns, +// { column_id: parseInt(name, 10) }); +// var list_items = column.list_items || cell.list_items; +// return _listItemDropdown(list_items, value, parseInt(name, 10), cellId); +// } else if (column_type === 'RepositoryAssetValue') { +// return changeToInputFileField('repository_cell_file', name, value, cellId); +// } else { +// return changeToInputField(object, name, value, cellId); +// } +// } +// +// /** +// * Append the change listener to file field +// * +// * @param {String} type +// * @param {String} name +// * +// * @returns {undefined} +// */ +// function addSelectedFile(type, name) { +// var button = $('button[data-id="' + +// generateInputFieldReference(name) + +// '"]'); +// if (type === 'RepositoryAssetValue') { +// var fileInput = $(button.parent().find('input[type="file"]')[0]); +// button.on('click', function(ev) { +// ev.preventDefault(); +// ev.stopPropagation(); +// fileInput.trigger('click'); +// initFileHandler(fileInput); +// }); +// } +// } +// +// /** +// * Handle extraction of file from the input field +// * +// * @param {Object} $inputField +// * +// * @returns {undefined} +// */ +// function initFileHandler($inputField) { +// $inputField.on('change', function() { +// var input = $(this); +// var $label = $($(this).closest('.repository-input-file-field') +// .find('.file-name-label')[0]); +// var file = this.files[0]; +// if (file) { +// $label.text(truncateLongString(file.name, 20)); +// input.attr('remove', false); +// $($label.closest('.repository-input-file-field') +// .find('[data-action="removeAsset"]')[0]).show(); +// } +// }) +// } +// +// /** +// * Generates the input tag id that will be used in the formData object +// * +// * @param {String} columnId +// * +// * @returns {String} +// */ +// function generateInputFieldReference(columnId) { +// return 'colId-' + columnId; +// } +// +// /** +// * Appends aditional fields to form data object +// * @param {Object} cell +// * @param {String} columnId +// * @param {Object} formData +// * +// * @returns {undefined} +// */ +// function appendNewElementToFormData(cell, columnId, formData) { +// if (!cell.repository_cell_id) { +// formData[generateInputFieldReference(columnId)] = undefined; +// } +// } +// }(window)); diff --git a/app/assets/javascripts/repositories/renderers/edit_renderers.js b/app/assets/javascripts/repositories/renderers/edit_renderers.js new file mode 100644 index 000000000..250e656a9 --- /dev/null +++ b/app/assets/javascripts/repositories/renderers/edit_renderers.js @@ -0,0 +1,32 @@ +$.fn.dataTable.render.editRowName = function(formId, cell) { + let $cell = $(cell.node()); + let text = $cell.find('a').first().text(); + + $cell.html(` +
+ +
+ `); +}; + +$.fn.dataTable.render.editRepositoryAssetValue = function(formId, columnId, cell) { + return ''; +}; + +$.fn.dataTable.render.editRepositoryTextValue = function(formId, columnId, cell) { + SmartAnnotation.init(cell); + return ''; +}; + +$.fn.dataTable.render.editRepositoryListValue = function(formId, columnId, cell) { + return ''; +}; + +$.fn.dataTable.render.editRepositoryStatusValue = function(formId, columnId, cell) { + return ''; +}; diff --git a/app/assets/javascripts/repositories/renderers/new_renderers.js b/app/assets/javascripts/repositories/renderers/new_renderers.js new file mode 100644 index 000000000..121daec70 --- /dev/null +++ b/app/assets/javascripts/repositories/renderers/new_renderers.js @@ -0,0 +1,24 @@ +$.fn.dataTable.render.newRowName = function(formId) { + return ``; +}; + +$.fn.dataTable.render.newRepositoryAssetValue = function(formId, columnId) { + return ''; +}; + +$.fn.dataTable.render.newRepositoryTextValue = function(formId, columnId) { + return ''; +}; + +$.fn.dataTable.render.newRepositoryListValue = function(formId, columnId) { + return ''; +}; + +$.fn.dataTable.render.newRepositoryStatusValue = function(formId, columnId) { + return ''; +}; diff --git a/app/assets/javascripts/repositories/repository_datatable.js b/app/assets/javascripts/repositories/repository_datatable.js index 5b2660628..bc987bf3e 100644 --- a/app/assets/javascripts/repositories/repository_datatable.js +++ b/app/assets/javascripts/repositories/repository_datatable.js @@ -1,31 +1,27 @@ /* globals I18n _ SmartAnnotation FilePreviewModal truncateLongString animateSpinner Promise - ActiveStorage HelperModule animateLoading RepositoryItemEditForm onClickCancel - hideAssignUnasignModal + HelperModule animateLoading onClickCancel + hideAssignUnasignModal RepositoryDatatableRowEditor */ //= require jquery-ui/widgets/sortable -//= require repositories/forms/repository_item_edit.js +//= require repositories/row_editor.js + var RepositoryDatatable = (function(global) { 'use strict'; var TABLE_ID = ''; + var TABLE_WRAPPER = $('.repository-table'); var TABLE = null; - var SCINOTE_REPOSITORY_EDITED_ROWS = []; // an array of edited rows + var EDITABLE = false; 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 selectedRecord; - - // Helps saving correct table state - var myData; - var loadFirstTime = true; + // var selectedRecord; // Tells whether to filter only assigned repository records var viewAssigned; @@ -50,22 +46,11 @@ var RepositoryDatatable = (function(global) { return value; }); - function changeToViewMode() { - currentMode = 'viewMode'; - // Table specific stuff - TABLE.button(0).enable(true); - FilePreviewModal.init(); - } - - function changeToEditMode() { - currentMode = 'editMode'; - // Table specific stuff - TABLE.button(0).enable(false); - } - // Enable/disable edit button function updateButtons() { if (currentMode === 'viewMode') { + $('#saveCancel').hide(); + $('#editDeleteCopy').show(); $('#addRepositoryRecord').removeClass('disabled').prop('disabled', false); $('.dataTables_length select').prop('disabled', false); $('#repository-acitons-dropdown').removeClass('disabled').prop('disabled', false); @@ -108,6 +93,8 @@ var RepositoryDatatable = (function(global) { $('#unassignRepositoryRecords').removeClass('disabled').prop('disabled', false); } } else if (currentMode === 'editMode') { + $('#editDeleteCopy').hide(); + $('#saveCancel').show(); $('#repository-acitons-dropdown').addClass('disabled').prop('disabled', true); $('.dataTables_length select').prop('disabled', true); $('#addRepositoryRecord').addClass('disabled').prop('disabled', true); @@ -124,6 +111,28 @@ var RepositoryDatatable = (function(global) { } } + function clearRowSelection() { + $('.dt-body-center .repository-row-selector').prop('checked', false); + $('.dt-body-center .repository-row-selector').closest('tr').removeClass('selected'); + rowsSelected = []; + } + + function changeToViewMode() { + currentMode = 'viewMode'; + // Table specific stuff + TABLE.button(0).enable(true); + FilePreviewModal.init(); + updateButtons(); + } + + function changeToEditMode() { + currentMode = 'editMode'; + // Table specific stuff + TABLE.button(0).enable(false); + clearRowSelection(); + updateButtons(); + } + // Updates "Select all" control in a data table function updateDataTableSelectAllCtrl() { var $table = TABLE.table().node(); @@ -170,23 +179,9 @@ var RepositoryDatatable = (function(global) { return html; } - function addSelectedFile(type, cell, input) { - if (type === 'RepositoryAssetValue') { - if (cell.value != null) { - const dT = new ClipboardEvent('').clipboardData // Firefox workaround exploiting https://bugzilla.mozilla.org/show_bug.cgi?id=1422655 - || new DataTransfer(); // specs compliant (as of March 2018 only Chrome) - dT.items.add(new File([_], cell.value.file_name)); - input.files = dT.files; - } - $(input).on('change', function() { - this.dataset.changed = 'true'; - }); - } - } - function initRowSelection() { // Handle clicks on checkbox - $('.dt-body-center .repository-row-selector').change(function(e) { + $('.dt-body-center .repository-row-selector').change(function(ev) { var $row; var data; var rowId; @@ -219,21 +214,77 @@ var RepositoryDatatable = (function(global) { updateDataTableSelectAllCtrl(); - e.stopPropagation(); + ev.stopPropagation(); updateButtons(); // Update number of selected records info $('#selected_info').html(' (' + rowsSelected.length + ' entries selected)'); }); // Handle click on "Select all" control - $('.dataTables_scrollHead input[name="select_all"]').change(function(e) { + $('.dataTables_scrollHead input[name="select_all"]').change(function(ev) { if (this.checked) { $('.repository-row-selector:not(:checked)').trigger('click'); } else { $('.repository-row-selector:checked').trigger('click'); } // Prevent click event from propagating to parent - e.stopPropagation(); + ev.stopPropagation(); + }); + } + + function checkAvailableColumns() { + $.ajax({ + url: $(TABLE_ID).data('available-columns'), + type: 'GET', + dataType: 'json', + success: function(data) { + var columnsIds = data.columns; + var presentColumns = $(TABLE_ID).data('repository-columns-ids'); + if (!_.isEqual(columnsIds.sort(), presentColumns.sort())) { + alert($(TABLE_ID).data('columns-changed')); + animateSpinner(); + location.reload(); + } + }, + error: function() { + location.reload(); + } + }); + } + + function initItemEditIcon() { + $(TABLE_ID).on('click', '.repository-row-edit-icon', function(ev) { + let rowId = $(ev.target).closest('tr').attr('id'); + let row = TABLE.row(`#${rowId}`); + + $(row.node()).find('.repository-row-selector').trigger('click'); + + checkAvailableColumns(); + + $(TABLE_ID).find('.repository-row-edit-icon').remove(); + + RepositoryDatatableRowEditor.switchRowToEditMode(row); + changeToEditMode(); + }); + } + + function initSaveButton() { + TABLE_WRAPPER.on('click', '#saveRecord', function() { + $(TABLE_ID).find('.repository-row-edit-form').submit(); + }); + } + + function initCancelButton() { + TABLE_WRAPPER.on('click', '#cancelSave', function() { + if ($('#assigned').text().length === 0) { + TABLE.column(1).visible(false); + } + TABLE.ajax.reload(function() { + initRowSelection(); + }, false); + changeToViewMode(); + SmartAnnotation.closePopup(); + animateSpinner(null, false); }); } @@ -244,10 +295,10 @@ var RepositoryDatatable = (function(global) { } // Takes object and surrounds it with input - function changeToInputField(object, name, value) { - return "
"; - } + // function changeToInputField(object, name, value) { + // return "
"; + // } // Takes object and surrounds it with input function changeToInputFileField(object, name, value) { @@ -357,13 +408,6 @@ var RepositoryDatatable = (function(global) { }); } - // Return td element with content - function createTdElement(content) { - var td = document.createElement('td'); - td.innerHTML = content; - return td; - } - function initialListItemsRequest(columnId) { var massageResponse = []; $.ajax({ @@ -443,22 +487,16 @@ var RepositoryDatatable = (function(global) { } // Clear all has-error tags - function clearAllErrors() { - // Remove any validation errors - $(selectedRecord) - .find('.has-error') - .removeClass('has-error') - .find('span') - .remove(); - // Remove any alerts - $('#alert-container').find('div').remove(); - } - - function clearRowSelection() { - $('.dt-body-center .repository-row-selector').prop('checked', false); - $('.dt-body-center .repository-row-selector').closest('tr').removeClass('selected'); - rowsSelected = []; - } + // function clearAllErrors() { + // // Remove any validation errors + // $(selectedRecord) + // .find('.has-error') + // .removeClass('has-error') + // .find('span') + // .remove(); + // // Remove any alerts + // $('#alert-container').find('div').remove(); + // } function dataTableInit() { viewAssigned = 'assigned'; @@ -471,6 +509,7 @@ var RepositoryDatatable = (function(global) { sScrollXInner: '100%', scrollY: '64vh', scrollCollapse: true, + order: [[2, 'asc']], colReorder: { fixedColumnsLeft: 2, realtime: false @@ -499,7 +538,14 @@ var RepositoryDatatable = (function(global) { targets: 1, searchable: false, orderable: true, - sWidth: '1%' + sWidth: '1%', + render: function(data) { + let content = data; + if (EDITABLE) { + content = '' + content; + } + return content; + } }, { // Name column is clickable targets: 3, @@ -522,21 +568,16 @@ var RepositoryDatatable = (function(global) { }, rowCallback: function(row, data) { // Get row ID - var rowId = data.DT_RowId; + let 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'); } }, - order: $(TABLE_ID).data('default-order'), columns: (function() { var columns = $(TABLE_ID).data('default-table-columns'); var customColumns = $(TABLE_ID).find('thead th[data-type]'); - for (let i = 0; i < columns.length; i += 1) { - columns[i].data = String(i); - columns[i].defaultContent = ''; - } customColumns.each((i, column) => { columns.push({ visible: true, @@ -547,10 +588,9 @@ var RepositoryDatatable = (function(global) { }); return columns; }()), - fnDrawCallback: function() { + drawCallback: function() { animateSpinner(this, false); changeToViewMode(); - updateButtons(); updateDataTableSelectAllCtrl(); FilePreviewModal.init(); // Prevent row toggling when selecting user smart annotation link @@ -565,101 +605,66 @@ var RepositoryDatatable = (function(global) { animateSpinner(this); $('.record-info-link').off('click'); }, - stateLoadCallback: function() { - // 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 + stateLoadCallback: function(settings, callback) { var repositoryId = $(TABLE_ID).data('repository-id'); $.ajax({ url: '/repositories/' + repositoryId + '/state_load', data: {}, - async: false, dataType: 'json', type: 'POST', success: function(json) { - myData = json.state; - - // Fix the order - convert it from index-keyed JS object that - // is returned from the server state into true JS array; - // e.g. {0: [2, 'asc'], 1: [3, 'desc']} - // is converted into [[2, 'asc'], [3, 'desc']] - myData.order = _.toArray(myData.order); + callback(json.state); } }); - return myData; }, stateSaveCallback: function(settings, data) { - var stateData = data; // Send an Ajax request to the server with the state object - var repositoryId = $(TABLE_ID).data('repository-id'); - // Save correct data - if (loadFirstTime === true) { - stateData = myData; - } + let repositoryId = $(TABLE_ID).data('repository-id'); + $.ajax({ url: '/repositories/' + repositoryId + '/state_save', - data: { state: stateData }, + contentType: 'application/json', + data: JSON.stringify({ state: data }), dataType: 'json', type: 'POST' }); - loadFirstTime = false; }, - fnInitComplete: function(oSettings) { - // First two columns are fixed - TABLE.column(0).visible(true); - TABLE.column(1).visible(true); - - // Reload correct column order and visibility (if you refresh page) - for (let i = 2; i < TABLE.columns()[0].length; i += 1) { - let visibility = false; - if (myData.columns && myData.columns[i]) { - visibility = myData.columns[i].visible; - } - if (typeof (visibility) === 'string') { - visibility = (visibility === 'true'); - } - TABLE.column(i).visible(visibility); - TABLE.setColumnSearchable(i, visibility); - } - - // Re-order table as per loaded state - if (myData.order) { - TABLE.order(myData.order); - TABLE.draw(); - } - - // Datatables triggers this action about 3 times - // sometimes on the first iteration the oSettings._colReorder is null - // and the fnOrder rises an error that breaks the table - // here I added a null guard for that case. - // @todo we need to find out why the tables are loaded multiple times - if (oSettings._colReorder) { - oSettings._colReorder.fnOrder(myData.ColReorder); - } + fnInitComplete: function() { initRowSelection(); bindExportActions(); disableCheckboxToggleOnAssetDownload(); FilePreviewModal.init(); initHeaderTooltip(); + + // Append button to inner toolbar in table + $('div.toolbarButtonsDatatable').appendTo('div.toolbar'); + $('div.toolbarButtonsDatatable').show(); + + // Append buttons for task inventory + $('div.toolbarButtons').appendTo('div.toolbar'); + $('div.toolbarButtons').show(); + + if (EDITABLE) { + RepositoryDatatableRowEditor.initFormSubmitAction(TABLE); + initItemEditIcon(); + initSaveButton(); + initCancelButton(); + } } }); // hack to replace filter placeholder - $('.dataTables_filter .form-control').attr('placeholder', $('.dataTables_filter label').text()) - $('.dataTables_filter label').contents().filter(function(){ + $('.dataTables_filter .form-control').attr('placeholder', $('.dataTables_filter label').text()); + $('.dataTables_filter label').contents().filter(function() { return this.nodeType === 3; }).remove(); // Handle click on table cells with checkboxes - $(TABLE_ID).on('click', 'tbody td', function(e) { - if ($(e.target).is('.repository-row-selector')) { - // Skip if clicking on selector checkbox - return; - } - if (!$(e.target).is('.record-info-link')) { - // Skip if clicking on samples info link - $(this).parent().find('.repository-row-selector').trigger('click'); - } + $(TABLE_ID).on('click', 'tbody td', function(ev) { + // Skip if clicking on selector checkbox, edit icon or link + if ($(ev.target).is('.repository-row-selector, .repository-row-edit-icon, a')) return; + + $(this).parent().find('.repository-row-selector').trigger('click'); }); TABLE.on('column-reorder', function() { @@ -681,107 +686,15 @@ var RepositoryDatatable = (function(global) { // Append buttons for task inventory $('div.toolbarButtons').appendTo('div.toolbar'); $('div.toolbarButtons').show(); - }, 10); + }, 100); return TABLE; } - function checkAvailableColumns() { - $.ajax({ - url: $(TABLE_ID).data('available-columns'), - type: 'GET', - dataType: 'json', - success: function(data) { - var columnsIds = data.columns; - var presentColumns = $(TABLE_ID).data('repository-columns-ids'); - if (!_.isEqual(columnsIds.sort(), presentColumns.sort())) { - alert($(TABLE_ID).data('columns-changed')); - animateSpinner(); - location.reload(); - } - }, - error: function() { - location.reload(); - } - }); - } - - // Restore previous table - global.onClickCancel = function() { - if ($('#assigned').text().length === 0) { - TABLE.column(1).visible(false); - } - TABLE.ajax.reload(function() { - initRowSelection(); - }, false); - changeToViewMode(); - updateButtons(); - SmartAnnotation.closePopup(); - SCINOTE_REPOSITORY_EDITED_ROWS = []; - animateSpinner(null, false); - }; - global.onClickAddRecord = function() { - var tr = document.createElement('tr'); - checkAvailableColumns(); + RepositoryDatatableRowEditor.addNewRow(TABLE); changeToEditMode(); - updateButtons(); - - saveAction = 'create'; - - if (TABLE.column(1).visible() === false) { - TABLE.column(1).visible(true); - } - $('table' + TABLE_ID + ' thead tr').children('th').each(function() { - var th = $(this); - var td; - var input; - if ($(th).attr('id') === 'checkbox') { - td = createTdElement(''); - $(td).html($('#saveRecord').clone()); - tr.appendChild(td); - } else if ($(th).attr('id') === 'assigned') { - td = createTdElement(''); - $(td).html($('#cancelSave').clone()); - tr.appendChild(td); - } else if ($(th).attr('id') === 'row-name') { - input = changeToInputField('repository_row', 'name', ''); - tr.appendChild(createTdElement(input)); - } else if ($(th).hasClass('repository-column') - && $(th).attr('data-type') === 'RepositoryTextValue') { - input = changeToInputField('repository_cell', th.attr('id'), ''); - tr.appendChild(createTdElement(input)); - } else if ($(th).hasClass('repository-column') - && $(th).attr('data-type') === 'RepositoryListValue') { - input = initialListItemsRequest($(th).attr('id')); - tr.appendChild(createTdElement(input)); - } else if ($(th).hasClass('repository-column') - && $(th).attr('data-type') === 'RepositoryAssetValue') { - input = changeToInputFileField('repository_cell_file', th.attr('id'), ''); - td = createTdElement(input); - tr.appendChild(td); - addSelectedFile($(th).attr('data-type'), '', $(td).find('input')[0]); - } else { - // Column we don't care for, just add empty td - tr.appendChild(createTdElement('')); - } - }); - - $('table' + TABLE_ID).prepend(tr); - selectedRecord = tr; - - // initialize smart annotation - _.each($('[data-object="repository_cell"]'), function(el) { - if (_.isUndefined($(el).data('atwho'))) { - SmartAnnotation.init(el); - } - }); - - // Init selectpicker - initSelectPicker(); - // Adjust columns width in table header - adjustTableHeader(); }; global.onClickToggleAssignedRecords = function() { @@ -903,10 +816,10 @@ var RepositoryDatatable = (function(global) { rowsSelected = []; onClickCancel(); }, - error: function(e) { - if (e.status === 403) { + error: function(ev) { + if (ev.status === 403) { HelperModule.flashAlertMsg( - I18n.t('repositories.js.permission_error'), e.responseJSON.style + I18n.t('repositories.js.permission_error'), ev.responseJSON.style ); } } @@ -925,10 +838,10 @@ var RepositoryDatatable = (function(global) { rowsSelected = []; onClickCancel(); }, - error: function(e) { - if (e.status === 403) { + error: function(ev) { + if (ev.status === 403) { HelperModule.flashAlertMsg( - I18n.t('repositories.js.permission_error'), e.responseJSON.style + I18n.t('repositories.js.permission_error'), ev.responseJSON.style ); } } @@ -937,232 +850,96 @@ var RepositoryDatatable = (function(global) { // Edit record global.onClickEdit = function() { - var row; - var node; - var rowData; - checkAvailableColumns(); + if (rowsSelected.length !== 1) { return; } - row = TABLE.row('#' + rowsSelected[0]); - node = row.node(); - rowData = row.data(); + let row = TABLE.row('#' + rowsSelected[0]); - $(node).find('td input').trigger('click'); - selectedRecord = node; + $(TABLE_ID).find('.repository-row-edit-icon').remove(); - clearAllErrors(); changeToEditMode(); - updateButtons(); - saveAction = 'update'; - - $.ajax({ - url: rowData.recordEditUrl, - type: 'GET', - dataType: 'json', - success: function(data) { - var editForm = new RepositoryItemEditForm(data, node); - - if (TABLE.column(1).visible() === false) { - TABLE.column(1).visible(true); - } - // Show save and cancel buttons in first two columns - $(node).children('td').eq(0).html($('#saveRecord').clone()); - $(node).children('td').eq(1).html($('#cancelSave').clone()); - - editForm.renderForm(TABLE); - initSelectPicker(); - editForm.initializeSelectpickerValues(node); - - // initialize smart annotation - _.each($('[data-object="repository_cell"]'), function(el) { - if (_.isUndefined($(el).data('atwho'))) { - SmartAnnotation.init(el); - } - }); - // Adjust columns width in table header - adjustTableHeader(); - updateButtons(); - - SCINOTE_REPOSITORY_EDITED_ROWS.push(editForm); - }, - error: function(e) { - if (e.status === 403) { - HelperModule.flashAlertMsg( - I18n.t('repositories.js.permission_error'), e.responseJSON.style - ); - changeToViewMode(); - updateButtons(); - } - } - }); + RepositoryDatatableRowEditor.switchRowToEditMode(row); + adjustTableHeader(); }; - function submitForm(url, formData) { - var type; - if (saveAction === 'update') { - type = 'PUT'; - } else { - type = 'POST'; - } - $.ajax({ - url: url, - type: type, - dataType: 'json', - data: formData, - processData: false, - contentType: false, - success: function(data) { - HelperModule.flashAlertMsg(data.flash, 'success'); - SmartAnnotation.closePopup(); - SCINOTE_REPOSITORY_EDITED_ROWS = []; - onClickCancel(); - animateSpinner(null, false); - }, - error: function(e) { - var data = e.responseJSON; - animateSpinner(null, false); - SmartAnnotation.closePopup(); - clearAllErrors(); - - if (e.status === 404) { - HelperModule.flashAlertMsg( - I18n.t('repositories.js.not_found_error'), 'danger' - ); - changeToViewMode(); - updateButtons(); - } else if (e.status === 403) { - HelperModule.flashAlertMsg( - I18n.t('repositories.js.permission_error'), 'danger' - ); - changeToViewMode(); - updateButtons(); - } else if (e.status === 400) { - if (data.default_fields) { - let defaultFields = data.default_fields; - - // Validate record name - if (defaultFields.name) { - let input = $(selectedRecord).find('input[name = name]'); - - if (input) { - input.closest('.form-group').addClass('has-error'); - input.parent().append("" + defaultFields.name + '
'); - } - } - } - - // Validate custom cells - $.each(data.repository_cells || [], function(_, val) { - $.each(val, function(key, val2) { - let input = $(selectedRecord).find('input[name=' + key + ']'); - if (input) { - let message = Array.isArray(val2.data) ? val2.data[0] : val2.data; - // handle custom input field - if (input.attr('type') === 'file') { - let container = input.closest('.repository-input-file-field'); - $(container.find('.form-group')[0]).addClass('has-error'); - container.addClass('has-error'); - container.append("" + message + '
'); - } else { - input.closest('.form-group').addClass('has-error'); - input.parent().append("" + message + '
'); - } - } - }); - }); - } - } - }); - } - - function buildNewFormData() { - return new Promise((resolve, reject) => { - var node = selectedRecord; - var formData = new FormData(); - var filesToUploadCntr = 0; - var filesUploadedCntr = 0; - const directUploadUrl = $(TABLE_ID).data('directUploadUrl'); - - // First fetch all the data in input fields - formData.append('request_url', $(TABLE_ID).data('current-uri')); - formData.append('repository_row_id', $(selectedRecord).attr('id')); - - // Direct record attributes - // Record name - formData.append('repository_row_name', $('td input[data-object = repository_row]').val()); - - // Custom cells text type - $(node).find('td input[data-object = repository_cell]').each(function() { - // Send data only and only if cell is not empty - if ($(this).val().trim()) { - formData.append('repository_cells[' + $(this).attr('name') + ']', $(this).val()); - } - }); - - // Custom cells list type - $(node).find('td[column_id]').each(function(index, el) { - var value = $(el).attr('list_item_id'); - formData.append('repository_cells[' + $(el).attr('column_id') + ']', value); - }); - - // Custom cells file type, first run, count files ready for upload - $(node).find('td input[data-object = repository_cell_file]').each(function() { - // Send data only and only if cell is not empty - if ($(this)[0].files.length === 1) { - filesToUploadCntr += 1; - } - }); - - // No files for upload, so return earlier - if (filesToUploadCntr === 0) { - resolve(formData); - return; - } - - // Custom cells file type, second run, upload files - $(node).find('td input[data-object = repository_cell_file]').each(function() { - // Send data only and only if cell is not empty - if ($(this)[0].files.length === 1) { - let upload = new ActiveStorage.DirectUpload($(this)[0].files[0], directUploadUrl); - let colId = $(this).attr('name'); - - upload.create(function(error, blob) { - if (error) { - reject(error); - } else { - formData.append('repository_cells[' + colId + ']', blob.signed_id); - filesUploadedCntr += 1; - - if (filesUploadedCntr === filesToUploadCntr) { - resolve(formData); - } - } - }); - } - }); - }); - } - - // Save record - global.onClickSave = function() { - animateSpinner(null, true); - if (saveAction === 'update') { - let row = TABLE.row(selectedRecord); - let rowData = row.data(); - SCINOTE_REPOSITORY_EDITED_ROWS[0].parseToFormObject( - TABLE_ID, selectedRecord - ).then(formData => { - submitForm(rowData.recordUpdateUrl, formData); - }); - } else if (saveAction === 'create') { - buildNewFormData().then(formData => { - submitForm($('table' + TABLE_ID).data('create-record'), formData); - }); - } - }; + // function submitForm(url, formData) { + // var type; + // if (saveAction === 'update') { + // type = 'PUT'; + // } else { + // type = 'POST'; + // } + // $.ajax({ + // url: url, + // type: type, + // dataType: 'json', + // data: formData, + // processData: false, + // contentType: false, + // success: function(data) { + // HelperModule.flashAlertMsg(data.flash, 'success'); + // SmartAnnotation.closePopup(); + // SCINOTE_REPOSITORY_EDITED_ROWS = []; + // onClickCancel(); + // animateSpinner(null, false); + // }, + // error: function(ev) { + // var data = ev.responseJSON; + // animateSpinner(null, false); + // SmartAnnotation.closePopup(); + // clearAllErrors(); + // + // if (ev.status === 404) { + // HelperModule.flashAlertMsg( + // I18n.t('repositories.js.not_found_error'), 'danger' + // ); + // changeToViewMode(); + // } else if (ev.status === 403) { + // HelperModule.flashAlertMsg( + // I18n.t('repositories.js.permission_error'), 'danger' + // ); + // changeToViewMode(); + // } else if (ev.status === 400) { + // if (data.default_fields) { + // let defaultFields = data.default_fields; + // + // // Validate record name + // if (defaultFields.name) { + // let input = $(selectedRecord).find('input[name = name]'); + // + // if (input) { + // input.closest('.form-group').addClass('has-error'); + // input.parent().append("" + defaultFields.name + '
'); + // } + // } + // } + // + // // Validate custom cells + // $.each(data.repository_cells || [], function(_, val) { + // $.each(val, function(key, val2) { + // let input = $(selectedRecord).find('input[name=' + key + ']'); + // if (input) { + // let message = Array.isArray(val2.data) ? val2.data[0] : val2.data; + // // handle custom input field + // if (input.attr('type') === 'file') { + // let container = input.closest('.repository-input-file-field'); + // $(container.find('.form-group')[0]).addClass('has-error'); + // container.addClass('has-error'); + // container.append("" + message + '
'); + // } else { + // input.closest('.form-group').addClass('has-error'); + // input.parent().append("" + message + '
'); + // } + // } + // }); + // }); + // } + // } + // }); + // } // Delete record global.onClickDelete = function() { @@ -1343,6 +1120,7 @@ var RepositoryDatatable = (function(global) { function init(id) { TABLE_ID = id; + EDITABLE = $(TABLE_ID).data('editable'); TABLE = dataTableInit(); initDropdown(); } diff --git a/app/assets/javascripts/repositories/row_editor.js b/app/assets/javascripts/repositories/row_editor.js new file mode 100644 index 000000000..60ed2493c --- /dev/null +++ b/app/assets/javascripts/repositories/row_editor.js @@ -0,0 +1,146 @@ +/* + globals HelperModule animateSpinner SmartAnnotation +*/ + +var RepositoryDatatableRowEditor = (function() { + const NAME_COLUMN_ID = 'row-name'; + const TABLE_ROW = ''; + const TABLE_CELL = ''; + + var TABLE; + + // Initialize SmartAnnotation + function initSmartAnnotation($row) { + $row.find('[data-object="repository_cell"]').each(function(el) { + if (el.data('atwho')) { + SmartAnnotation.init(el); + } + }); + } + + function initFormSubmitAction(table) { + TABLE = table; + let $table = $(TABLE.table().node()); + + $table.on('ajax:beforeSend', '.repository-row-edit-form', function() { + let $row = $(this).closest('tr'); + let valid = true; + + $row.find('.has-error').removeClass('has-error').find('span').remove(); + + $row.find('input').each(function() { + let dataType = $(this).data('type'); + if (!dataType) return; + + valid = $.fn.dataTable.render[dataType + 'Validator']($(this)); + }); + if (!valid) return false; + + animateSpinner(null, true); + }); + + $table.on('ajax:success', '.repository-row-edit-form', function(ev, data) { + TABLE.ajax.reload(); + HelperModule.flashAlertMsg(data.flash, 'success'); + }); + + $table.on('ajax:error', '.repository-row-edit-form', function(ev, data) { + HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger'); + }); + + $table.on('ajax:complete', '.repository-row-edit-form', function() { + animateSpinner(null, false); + }); + } + + function addNewRow(table) { + TABLE = table; + + let $row = $(TABLE_ROW); + const formId = 'repositoryNewRowForm'; + let actionUrl = $(TABLE.table().node()).data('createRecord'); + let rowForm = $(` +
+
+ `); + + $row.prepend(rowForm); + + // First two columns are always present and visible + $row.append($(TABLE_CELL)); + $row.append($(TABLE_CELL)); + + table.columns().every(function() { + let column = this; + let $header = $(column.header()); + + if (column.index() < 2) return; + if (!column.visible()) return; + + let columnId = $header.attr('id'); + + if (columnId === NAME_COLUMN_ID) { + $row.append($(TABLE_CELL).html($.fn.dataTable.render.newRowName(formId))); + } else { + let dataType = $header.data('type'); + if (dataType) { + $row.append($(TABLE_CELL).html($.fn.dataTable.render['new' + dataType](formId, columnId))); + } else { + $row.append($(TABLE_CELL)); + } + } + }); + + $(TABLE.table().node()).find('tbody').prepend($row); + + initSmartAnnotation($row); + + TABLE.columns.adjust(); + } + + function switchRowToEditMode(row) { + let $row = $(row.node()); + let itemId = row.id(); + let formId = `repositoryRowForm${itemId}`; + let rowForm = $(` +
+ +
+ `); + + $row.prepend(rowForm); + + TABLE.cells(row.index(), row.columns().eq(0)).every(function() { + let columnId = $(TABLE.columns(this.index().column).header()).attr('id'); + let cell = this; + + if (columnId === NAME_COLUMN_ID) { + $.fn.dataTable.render.editRowName(formId, cell); + } else { + let dataType = $(this.column().header()).data('type'); + if (dataType) $.fn.dataTable.render['edit' + dataType](formId, columnId, cell); + } + + return true; + }); + + initSmartAnnotation($row); + + TABLE.columns.adjust(); + } + + return Object.freeze({ + initFormSubmitAction: initFormSubmitAction, + switchRowToEditMode: switchRowToEditMode, + addNewRow: addNewRow + }); +}()); diff --git a/app/assets/javascripts/repositories/validators/base_validator.js b/app/assets/javascripts/repositories/validators/base_validator.js new file mode 100644 index 000000000..ebed78115 --- /dev/null +++ b/app/assets/javascripts/repositories/validators/base_validator.js @@ -0,0 +1,5 @@ +/* global GLOBAL_CONSTANTS textValidator */ + +$.fn.dataTable.render.RowNameValidator = function($input) { + return textValidator(undefined, $input, 1, GLOBAL_CONSTANTS.NAME_MAX_LENGTH); +}; diff --git a/app/assets/stylesheets/themes/repositories.scss b/app/assets/stylesheets/themes/repositories.scss index 26d5d388d..04ddc7230 100644 --- a/app/assets/stylesheets/themes/repositories.scss +++ b/app/assets/stylesheets/themes/repositories.scss @@ -83,6 +83,28 @@ } } +.repository-table { + tbody { + tr:hover { + background-color: $color-concrete; + } + + .editing { + border: 1px solid; + } + + .repository-row-edit-icon { + opacity: 0; + padding-right: 10px; + } + + tr:hover .repository-row-edit-icon { + cursor: pointer; + opacity: 1; + } + } +} + .new-input-file-field-div { display: flex; flex-direction: row; diff --git a/app/helpers/repository_datatable_helper.rb b/app/helpers/repository_datatable_helper.rb index 6ced02dcb..8f653f59c 100644 --- a/app/helpers/repository_datatable_helper.rb +++ b/app/helpers/repository_datatable_helper.rb @@ -56,14 +56,6 @@ module RepositoryDatatableHelper can_manage_repository_rows?(repository) end - # The order must be converted from Ruby Hash into a JS array - - # because arrays in JS are in truth regular JS objects with indexes as keys - def default_table_order_as_js_array - Constants::REPOSITORY_TABLE_DEFAULT_STATE[:order].keys.sort.map do |k| - Constants::REPOSITORY_TABLE_DEFAULT_STATE[:order][k] - end.to_s - end - def default_table_columns Constants::REPOSITORY_TABLE_DEFAULT_STATE[:columns].keys.sort.map do |k| col = Constants::REPOSITORY_TABLE_DEFAULT_STATE[:columns][k] diff --git a/app/services/repository_table_state_service.rb b/app/services/repository_table_state_service.rb index da1ee0350..a0254bbe9 100644 --- a/app/services/repository_table_state_service.rb +++ b/app/services/repository_table_state_service.rb @@ -18,14 +18,17 @@ class RepositoryTableStateService loaded.state['order'] && loaded.state['columns'] && loaded.state['ColReorder'] && - loaded.state.dig('columns', '1', 'visible') == 'true' && - loaded.state.dig('columns', '3', 'visible') == 'true' + loaded.state.dig('columns', 1, 'visible') == true && + loaded.state.dig('columns', 3, 'visible') == true loaded end def update_state(state) saved_state = load_state + state[:order][0] = [3, 'asc'] if state.dig(:order, 0, 0).to_i < 2 + return if saved_state.state.except('time') == state.except('time') + saved_state.update(state: state) end @@ -33,7 +36,7 @@ class RepositoryTableStateService # Destroy any state object before recreating a new one RepositoryTableState.where(user: @user, repository: @repository).destroy_all - return RepositoryTableState.create( + RepositoryTableState.create( user: @user, repository: @repository, state: generate_default_state @@ -47,9 +50,7 @@ class RepositoryTableStateService Constants::REPOSITORY_TABLE_DEFAULT_STATE[:length] # This state should be strings-only - state = HashUtil.deep_stringify_keys_and_values( - Constants::REPOSITORY_TABLE_DEFAULT_STATE - ) + state = Constants::REPOSITORY_TABLE_DEFAULT_STATE.with_indifferent_access repository.repository_columns.each_with_index do |_, index| real_index = default_columns_num + index state['columns'][real_index.to_s] = diff --git a/app/views/repositories/_repository_table.html.erb b/app/views/repositories/_repository_table.html.erb index 7af68aadf..2607f8004 100644 --- a/app/views/repositories/_repository_table.html.erb +++ b/app/views/repositories/_repository_table.html.erb @@ -17,9 +17,9 @@ data-columns-delete-text="<%= I18n.t('repositories.columns_delete') %>" data-available-columns="<%= repository_available_columns_path(repository) %>" data-columns-changed="<%= I18n.t('repositories.columns_changed') %>" - data-default-order="<%= default_table_order_as_js_array %>" data-default-table-columns="<%= default_table_columns %>" - data-list-items-path="<%= Rails.application.routes.url_helpers.repository_list_items_path %>"> + data-list-items-path="<%= Rails.application.routes.url_helpers.repository_list_items_path %>" + data-editable="<%= can_manage_repository_rows?(repository) %>"> diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index ef664aeb2..b769f6d09 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -103,17 +103,6 @@
- -