From ccf76b853142c2dc31f76fb41a5793c5948168b1 Mon Sep 17 00:00:00 2001 From: Oleksii Kriuchykhin Date: Fri, 26 Jul 2019 13:00:25 +0200 Subject: [PATCH] Move repository_datatable to JS file [SCI-3679] --- ...tatable.js.erb => repository_datatable.js} | 1220 ++++++++--------- .../repositories/_repository_table.html.erb | 5 +- 2 files changed, 597 insertions(+), 628 deletions(-) rename app/assets/javascripts/repositories/{repository_datatable.js.erb => repository_datatable.js} (81%) diff --git a/app/assets/javascripts/repositories/repository_datatable.js.erb b/app/assets/javascripts/repositories/repository_datatable.js similarity index 81% rename from app/assets/javascripts/repositories/repository_datatable.js.erb rename to app/assets/javascripts/repositories/repository_datatable.js index e33966771..a4ba33608 100644 --- a/app/assets/javascripts/repositories/repository_datatable.js.erb +++ b/app/assets/javascripts/repositories/repository_datatable.js @@ -1,3 +1,9 @@ +/* + globals I18n _ SmartAnnotation FilePreviewModal truncateLongString animateSpinner Promise + ActiveStorage HelperModule animateLoading RepositoryItemEditForm onClickCancel + hideAssignUnasignModal +*/ + //= require jquery-ui/widgets/sortable //= require repositories/forms/repository_item_edit.js @@ -6,31 +12,7 @@ var RepositoryDatatable = (function(global) { var TABLE_ID = ''; var TABLE = null; - - /** - * This variable is declared on window object, - * it holds the objects of edited rows, probably we can use this also - * for multi row editing in the future. - */ - global.SCINOTE_REPOSITORY_EDITED_ROWS = []; // an array of edited rows - - // 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 SCINOTE_REPOSITORY_EDITED_ROWS = []; // an array of edited rows var rowsSelected = []; @@ -45,14 +27,452 @@ var RepositoryDatatable = (function(global) { var myData; var loadFirstTime = true; - var originalHeader; - // Tells whether to filter only assigned repository records var viewAssigned; + var dropdownList; + + // 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)) { + let idx = this.column(colSelector).index(); + this.settings()[0].aoColumns[idx].bSearchable = value; + if (value === true) { + this.rows().invalidate(); + } + } + 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') { + $('#addRepositoryRecord').removeClass('disabled'); + $('#addRepositoryRecord').prop('disabled', false); + $('.dataTables_length select').prop('disabled', false); + $('#repository-acitons-dropdown').removeClass('disabled'); + $('#repository-acitons-dropdown').prop('disabled', false); + $('#addNewColumn').removeClass('disabled'); + $('#addNewColumn').prop('disabled', false); + $('#repository-columns-dropdown') + .find('.dropdown-toggle') + .prop('disabled', false); + $('th').removeClass('disable-click'); + $('.repository-row-selector').removeClass('disabled'); + $('.repository-row-selector').prop('disabled', false); + if (rowsSelected.length === 0) { + $('#copyRepositoryRecords').prop('disabled', true); + $('#copyRepositoryRecords').addClass('disabled'); + $('#editRepositoryRecord').prop('disabled', true); + $('#editRepositoryRecord').addClass('disabled'); + $('#deleteRepositoryRecordsButton').prop('disabled', true); + $('#deleteRepositoryRecordsButton').addClass('disabled'); + $('#exportRepositoriesButton').parent('li').addClass('disabled'); + $('#exportRepositoriesButton').prop('disabled', true); + $('#exportRepositoriesButton').off('click'); + $('#export-repositories').off('click'); + $('#assignRepositoryRecords').addClass('disabled'); + $('#assignRepositoryRecords').prop('disabled', true); + $('#unassignRepositoryRecords').addClass('disabled'); + $('#unassignRepositoryRecords').prop('disabled', true); + } else { + if (rowsSelected.length === 1 && $('#exportRepositoriesButton').get(0)) { + $('#editRepositoryRecord').prop('disabled', false); + $('#editRepositoryRecord').removeClass('disabled'); + + // If we switched from 2 selections to 1, then this is not needed + let events = $.data($('#exportRepositoriesButton').get(0), 'events'); + if (!events || !events.click) { + $('#exportRepositoriesButton').parent('li').removeClass('disabled'); + $('#exportRepositoriesButton').prop('disabled', false); + $('#exportRepositoriesButton').off('click').on('click', function() { + $('#exportRepositoryModal').modal('show'); + }); + $('#export-repositories').off('click').on('click', function() { + animateSpinner(null, true); + $('#form-export').submit(); + }); + } + } else { + $('#editRepositoryRecord').prop('disabled', true); + $('#editRepositoryRecord').addClass('disabled'); + } + $('#deleteRepositoryRecordsButton').prop('disabled', false); + $('#deleteRepositoryRecordsButton').removeClass('disabled'); + $('#copyRepositoryRecords').prop('disabled', false); + $('#copyRepositoryRecords').removeClass('disabled'); + $('#assignRepositoryRecords').removeClass('disabled'); + $('#assignRepositoryRecords').prop('disabled', false); + $('#unassignRepositoryRecords').removeClass('disabled'); + $('#unassignRepositoryRecords').prop('disabled', false); + } + } else if (currentMode === 'editMode') { + $('#repository-acitons-dropdown').addClass('disabled'); + $('#repository-acitons-dropdown').prop('disabled', true); + $('.dataTables_length select').prop('disabled', true); + $('#addRepositoryRecord').addClass('disabled'); + $('#addRepositoryRecord').prop('disabled', true); + $('#editRepositoryRecord').addClass('disabled'); + $('#editRepositoryRecord').prop('disabled', true); + $('#addNewColumn').addClass('disabled'); + $('#addNewColumn').prop('disabled', true); + $('#deleteRepositoryRecordsButton').addClass('disabled'); + $('#deleteRepositoryRecordsButton').prop('disabled', true); + $('#exportRepositoriesButton').off('click'); + $('#export-repositories').off('click'); + $('#assignRepositoryRecords').addClass('disabled'); + $('#assignRepositoryRecords').prop('disabled', true); + $('#unassignRepositoryRecords').addClass('disabled'); + $('#unassignRepositoryRecords').prop('disabled', true); + $('#repository-columns-dropdown').find('.dropdown-toggle').prop('disabled', true); + $('th').addClass('disable-click'); + $('.repository-row-selector').addClass('disabled'); + $('.repository-row-selector').prop('disabled', true); + } + } + + // Updates "Select all" control in a data table + function updateDataTableSelectAllCtrl() { + var $table = TABLE.table().node(); + var $header = TABLE.table().header(); + var $chkboxAll = $('.repository-row-selector', $table); + var $chkboxChecked = $('.repository-row-selector:checked', $table); + var chkboxSelectAll = $('input[name="select_all"]', $header).get(0); + + // If none of the checkboxes are checked + if ($chkboxChecked.length === 0) { + chkboxSelectAll.checked = false; + if ('indeterminate' in chkboxSelectAll) { + chkboxSelectAll.indeterminate = false; + } + + // If all of the checkboxes are checked + } else if ($chkboxChecked.length === $chkboxAll.length) { + chkboxSelectAll.checked = true; + if ('indeterminate' in chkboxSelectAll) { + chkboxSelectAll.indeterminate = false; + } + + // If some of the checkboxes are checked + } else { + chkboxSelectAll.checked = true; + if ('indeterminate' in chkboxSelectAll) { + chkboxSelectAll.indeterminate = true; + } + } + } + + // Helper functions + function listItemDropdown(options, currentValue, columnId) { + var html = ''; + 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) { + var $row; + var data; + var rowId; + var index; + + if (currentMode !== 'viewMode') { + return; + } + // Get row ID + $row = $(this).closest('tr'); + data = TABLE.row($row).data(); + rowId = data.DT_RowId; + + // Determine whether row ID is in the list of selected row IDs + 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(); + + e.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) { + 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(); + }); + } + + function appendInput(form, val, name) { + $(form).append( + $('').attr('type', 'hidden').attr('name', name).val(val) + ); + } + + // Takes object and surrounds it with input + function changeToInputField(object, name, value) { + return "
"; + } + + // Takes object and surrounds it with input + function changeToInputFileField(object, name, value) { + return "
" + + "
" + + "
" + + "" + + "" + + '
'; + } + + 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; + var offsetTop = $tooltip.offset().top; + if ((offsetLeft + 200) > $(window).width()) { + offsetLeft -= 150; + } + $('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'); + }); + } + + function bindExportActions() { + $('form#form-export').submit(function() { + var form = this; + if (currentMode === 'viewMode') { + // Remove all hidden fields + $(form).find('input[name=row_ids\\[\\]]').remove(); + $(form).find('input[name=header_ids\\[\\]]').remove(); + + // Append visible column information + $('table' + TABLE_ID + ' thead tr th').each(function() { + var th = $(this); + var val; + switch ($(th).attr('id')) { + case 'checkbox': + val = -1; + break; + case 'assigned': + val = -2; + break; + case 'row-id': + val = -3; + break; + case 'row-name': + val = -4; + break; + case 'added-by': + val = -5; + break; + case 'added-on': + val = -6; + break; + default: + val = th.attr('id'); + } + + if (val) { + appendInput(form, val, 'header_ids[]'); + } + }); + + // Append records + $.each(rowsSelected, function(index, rowId) { + appendInput(form, rowId, 'row_ids[]'); + }); + } + }); + } + + function disableCheckboxToggleOnAssetDownload() { + $('.file-preview-link').on('click', function(ev) { + ev.stopPropagation(); + }); + } + + // Return td element with content + function createTdElement(content) { + var td = document.createElement('td'); + td.innerHTML = content; + return td; + } + + function initialListItemsRequest(columnId) { + var massageResponse = []; + $.ajax({ + url: $(TABLE_ID).data('list-items-path'), + type: 'POST', + dataType: 'json', + async: false, + data: { + q: '', + column_id: columnId + } + }).done(function(data) { + $.each(data.list_items, function(index, el) { + massageResponse.push([el.id, el.data]); + }); + }); + return listItemDropdown(massageResponse, '-1', columnId); + } + + function initSelectPicker() { + $('.selectpicker') + .selectpicker({ liveSearch: true }) + .ajaxSelectPicker({ + ajax: { + url: $(TABLE_ID).data('list-items-path'), + type: 'POST', + dataType: 'json', + data: function() { + var params = { + q: '{{{q}}}', + column_id: $(this.valueOf().plugin.$element).attr('column_id') + }; + + return params; + } + }, + locale: { + emptyTitle: 'Nothing selected' + }, + preprocessData: function(data) { + var items = []; + if (Object.prototype.hasOwnProperty.call(data, 'list_items')) { + items.push({ + value: '-1', + text: '', + disabled: false + }); + $.each(data.list_items, function(index, el) { + items.push( + { + value: el.id, + text: el.data, + disabled: false + } + ); + }); + } + return items; + }, + emptyRequest: true, + clearOnEmpty: false, + preserveSelected: false + }).on('change.bs.select', function(el) { + $(this).closest('td').attr('list_item_id', el.target.value); + $(this).closest('td').attr('column_id', $(this).attr('column_id')); + }) + .trigger('change.bs.select'); + } + + // Adjust columns width in table header + function adjustTableHeader() { + TABLE.columns.adjust(); + $('.dropdown-menu').parent() + .on('shown.bs.dropdown hidden.bs.dropdown', function() { + TABLE.columns.adjust(); + }); + } + + // Clear all has-error tags + function clearAllErrors() { + // Remove any validation errors + $(selectedRecord).find('.has-error').each(function() { + $(this).removeClass('has-error'); + $(this).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 dataTableInit() { - // Make a copy of original repository table header - originalHeader = $(TABLE_ID + ' thead').children().clone(); viewAssigned = 'assigned'; TABLE = $(TABLE_ID).DataTable({ dom: "R<'row'<'col-sm-9-custom toolbar'l><'col-sm-3-custom'f>>tpi", @@ -97,8 +517,8 @@ var RepositoryDatatable = (function(global) { targets: 3, visible: true, render: function(data, type, row) { - return "" + data + ''; + return "" + data + ''; } }], oLanguage: { @@ -113,14 +533,12 @@ var RepositoryDatatable = (function(global) { $(row).addClass('selected'); } }, - <% # Next 2 options are provided by server-side default state - # (and get overriden once state load from server kicks in) %> - order: <%= default_table_order_as_js_array %>, + order: $(TABLE_ID).data('default-order'), columns: (function() { var numOfColumns = $(TABLE_ID).data('num-columns'); - var columns = <%= default_table_columns %>; - for (var i = 0; i < numOfColumns; i++) { - if (columns[i] == undefined) { + var columns = $(TABLE_ID).data('default-table-columns'); + for (let i = 0; i < numOfColumns; i += 1) { + if (columns[i] === undefined) { // This should only occur for custom columns columns[i] = { visible: true, searchable: true }; } @@ -128,7 +546,7 @@ var RepositoryDatatable = (function(global) { columns[i].defaultContent = ''; } return columns; - })(), + }()), fnDrawCallback: function() { animateSpinner(this, false); changeToViewMode(); @@ -140,9 +558,7 @@ var RepositoryDatatable = (function(global) { // Show number of selected rows near pages info $('#repository-table_info').append(''); - $('#selected_info').html(' (' + - rowsSelected.length + - ' entries selected)'); + $('#selected_info').html(' (' + rowsSelected.length + ' entries selected)'); initRowSelection(); }, preDrawCallback: function() { @@ -173,15 +589,16 @@ var RepositoryDatatable = (function(global) { 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) { - data = myData; + stateData = myData; } $.ajax({ url: '/repositories/' + repositoryId + '/state_save', - data: { state: data }, + data: { state: stateData }, dataType: 'json', type: 'POST' }); @@ -193,8 +610,8 @@ var RepositoryDatatable = (function(global) { TABLE.column(1).visible(true); // Reload correct column order and visibility (if you refresh page) - for (var i = 2; i < TABLE.columns()[0].length; i++) { - var visibility = false; + 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; } @@ -216,7 +633,7 @@ var RepositoryDatatable = (function(global) { // 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 ) { + if (oSettings._colReorder) { oSettings._colReorder.fnOrder(myData.ColReorder); } initRowSelection(); @@ -259,175 +676,50 @@ var RepositoryDatatable = (function(global) { return TABLE; } - function bindExportActions() { - $('form#form-export').submit(function() { - var form = this; - if (currentMode === 'viewMode') { - // Remove all hidden fields - $(form).find('input[name=row_ids\\[\\]]').remove(); - $(form).find('input[name=header_ids\\[\\]]').remove(); - - // Append visible column information - $('table' + TABLE_ID + ' thead tr th').each(function() { - var th = $(this); - var val; - switch ($(th).attr('id')) { - case 'checkbox': - val = -1; - break; - case 'assigned': - val = -2; - break; - case 'row-id': - val = -3; - break - case 'row-name': - val = -4; - break; - case 'added-by': - val = -5; - break; - case 'added-on': - val = -6; - break; - default: - val = th.attr('id'); - } - - if (val) { - appendInput(form, val, 'header_ids[]'); - } - }); - - // Append records - $.each(rowsSelected, function(index, rowId) { - appendInput(form, rowId, 'row_ids[]'); - }); - } - }); - } - - function disableCheckboxToggleOnAssetDownload() { - $('.file-preview-link').on('click', function(ev) { - ev.stopPropagation(); - }); - } - - function appendInput(form, val, name) { - $(form).append( - $('') - .attr('type', 'hidden') - .attr('name', name) - .val(val) - ); - } - - function initRowSelection() { - // Handle clicks on checkbox - $('.dt-body-center .repository-row-selector').change(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(); - - e.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) { - 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(); - }); - } - - // Updates "Select all" control in a data table - function updateDataTableSelectAllCtrl() { - var $table = TABLE.table().node(); - var $header = TABLE.table().header(); - var $chkboxAll = $('.repository-row-selector', $table); - var $chkboxChecked = $('.repository-row-selector:checked', $table); - var chkboxSelectAll = $('input[name="select_all"]', $header).get(0); - - // If none of the checkboxes are checked - if ($chkboxChecked.length === 0) { - chkboxSelectAll.checked = false; - if ('indeterminate' in chkboxSelectAll) { - chkboxSelectAll.indeterminate = false; - } - - // If all of the checkboxes are checked - } else if ($chkboxChecked.length === $chkboxAll.length) { - chkboxSelectAll.checked = true; - if ('indeterminate' in chkboxSelectAll) { - chkboxSelectAll.indeterminate = false; - } - - // If some of the checkboxes are checked - } else { - chkboxSelectAll.checked = true; - if ('indeterminate' in chkboxSelectAll) { - chkboxSelectAll.indeterminate = true; - } - } - } - function checkAvailableColumns() { $.ajax({ url: $(TABLE_ID).data('available-columns'), type: 'GET', dataType: 'json', success: function(data) { - var columns_ids = data.columns; - var present_columns = $(TABLE_ID).data('repository-columns-ids'); - if( !_.isEqual(columns_ids.sort(), present_columns.sort()) ) { + 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(data) { + 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(); changeToEditMode(); updateButtons(); saveAction = 'create'; - var tr = document.createElement('tr'); + if (TABLE.column(1).visible() === false) { TABLE.column(1).visible(true); } @@ -446,20 +738,20 @@ var RepositoryDatatable = (function(global) { } 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') { + } 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') { + } 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') { + } 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]); + addSelectedFile($(th).attr('data-type'), '', $(td).find('input')[0]); } else { // Column we don't care for, just add empty td tr.appendChild(createTdElement('')); @@ -477,10 +769,10 @@ var RepositoryDatatable = (function(global) { }); // Init selectpicker - _initSelectPicker(); + initSelectPicker(); // Adjust columns width in table header adjustTableHeader(); - } + }; global.onClickToggleAssignedRecords = function() { $('.repository-assign-group > .btn').click(function() { @@ -497,7 +789,7 @@ var RepositoryDatatable = (function(global) { }); promiseReload.then(function() { initRowSelection(); - }) + }); }); $('#all-repo-records').on('click', function() { var promiseReload; @@ -507,30 +799,30 @@ var RepositoryDatatable = (function(global) { }); promiseReload.then(function() { initRowSelection(); - }) + }); }); }; global.openAssignRecordsModal = function() { $.post( $('#assignRepositoryRecords').data('assign-url-modal'), - { selected_rows: rowsSelected }) - .done( + { selected_rows: rowsSelected } + ).done( function(data) { $(data.html).appendTo('body').promise().done(function() { $('#assignRepositoryRecordModal').modal('show'); }); } ); - } + }; global.hideAssignUnasignModal = function(id) { $(id).modal('hide').promise().done( function() { $(id).remove(); } - ) - } + ); + }; global.submitAssignRepositoryRecord = function(option) { animateSpinner(); @@ -538,8 +830,7 @@ var RepositoryDatatable = (function(global) { url: $('#assignRepositoryRecordModal').data('assign-url'), type: 'POST', dataType: 'json', - data: { selected_rows: rowsSelected, - downstream: (option === 'downstream') }, + data: { selected_rows: rowsSelected, downstream: (option === 'downstream') }, success: function(data) { hideAssignUnasignModal('#assignRepositoryRecordModal'); HelperModule.flashAlertMsg(data.flash, 'success'); @@ -558,15 +849,15 @@ var RepositoryDatatable = (function(global) { global.openUnassignRecordsModal = function() { $.post( $('#unassignRepositoryRecords').data('unassign-url'), - { selected_rows: rowsSelected }) - .done( + { selected_rows: rowsSelected } + ).done( function(data) { $(data.html).appendTo('body').promise().done(function() { $('#unassignRepositoryRecordModal').modal('show'); }); } ); - } + }; global.submitUnassignRepositoryRecord = function(option) { animateSpinner(); @@ -574,8 +865,7 @@ var RepositoryDatatable = (function(global) { url: $('#unassignRepositoryRecordModal').data('unassign-url'), type: 'POST', dataType: 'json', - data: { selected_rows: rowsSelected, - downstream: (option === 'downstream') }, + data: { selected_rows: rowsSelected, downstream: (option === 'downstream') }, success: function(data) { hideAssignUnasignModal('#unassignRepositoryRecordModal'); HelperModule.flashAlertMsg(data.flash, 'success'); @@ -597,7 +887,7 @@ var RepositoryDatatable = (function(global) { url: $('table' + TABLE_ID).data('delete-record'), type: 'POST', dataType: 'json', - data: {selected_rows: rowsSelected}, + data: { selected_rows: rowsSelected }, success: function(data) { HelperModule.flashAlertMsg(data.flash, data.color); rowsSelected = []; @@ -633,18 +923,22 @@ var RepositoryDatatable = (function(global) { } } }); - } + }; // Edit record global.onClickEdit = function() { + var row; + var node; + var rowData; + checkAvailableColumns(); if (rowsSelected.length !== 1) { return; } - var row = TABLE.row('#' + rowsSelected[0]); - var node = row.node(); - var rowData = row.data(); + row = TABLE.row('#' + rowsSelected[0]); + node = row.node(); + rowData = row.data(); $(node).find('td input').trigger('click'); selectedRecord = node; @@ -659,6 +953,8 @@ var RepositoryDatatable = (function(global) { type: 'GET', dataType: 'json', success: function(data) { + var editForm = new RepositoryItemEditForm(data, node); + if (TABLE.column(1).visible() === false) { TABLE.column(1).visible(true); } @@ -666,9 +962,8 @@ var RepositoryDatatable = (function(global) { $(node).children('td').eq(0).html($('#saveRecord').clone()); $(node).children('td').eq(1).html($('#cancelSave').clone()); - var editForm = new RepositoryItemEditForm(data, node); editForm.renderForm(TABLE); - _initSelectPicker(); + initSelectPicker(); editForm.initializeSelectpickerValues(node); // initialize smart annotation @@ -693,10 +988,9 @@ var RepositoryDatatable = (function(global) { } } }); - } + }; function submitForm(url, formData) { - var url; var type; if (saveAction === 'update') { type = 'PUT'; @@ -718,9 +1012,9 @@ var RepositoryDatatable = (function(global) { animateSpinner(null, false); }, error: function(e) { + var data = e.responseJSON; animateSpinner(null, false); SmartAnnotation.closePopup(); - var data = e.responseJSON; clearAllErrors(); if (e.status === 404) { @@ -737,37 +1031,34 @@ var RepositoryDatatable = (function(global) { updateButtons(); } else if (e.status === 400) { if (data.default_fields) { - var defaultFields = data.default_fields; + let defaultFields = data.default_fields; // Validate record name if (defaultFields.name) { - var input = $(selectedRecord).find('input[name = name]'); + let input = $(selectedRecord).find('input[name = name]'); if (input) { input.closest('.form-group').addClass('has-error'); - input.parent().append("" + - defaultFields.name + '
'); + input.parent().append("" + defaultFields.name + '
'); } } } // Validate custom cells - $.each(data.repository_cells || [], function(key, val) { - $.each(val, function(key, val) { - var input = $(selectedRecord).find('input[name=' + key + ']'); + $.each(data.repository_cells || [], function(_, val) { + $.each(val, function(key, val2) { + let input = $(selectedRecord).find('input[name=' + key + ']'); if (input) { - var message = Array.isArray(val.data) ? val.data[0] : val.data; + let message = Array.isArray(val2.data) ? val2.data[0] : val2.data; // handle custom input field - if(input.attr('type') === 'file') { - var container = input.closest('.repository-input-file-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 + '
'); + container.append("" + message + '
'); } else { input.closest('.form-group').addClass('has-error'); - input.parent().append("" + - message + '
'); + input.parent().append("" + message + '
'); } } }); @@ -810,7 +1101,7 @@ var RepositoryDatatable = (function(global) { // 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 ) { + if ($(this)[0].files.length === 1) { filesToUploadCntr += 1; } }); @@ -824,7 +1115,7 @@ var RepositoryDatatable = (function(global) { // 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 ) { + if ($(this)[0].files.length === 1) { let upload = new ActiveStorage.DirectUpload($(this)[0].files[0], directUploadUrl); let colId = $(this).attr('name'); @@ -868,261 +1159,17 @@ var RepositoryDatatable = (function(global) { $('#deleteRepositoryRecord').modal('show'); }; - // Enable/disable edit button - function updateButtons() { - if (currentMode === 'viewMode') { - $('#addRepositoryRecord').removeClass('disabled'); - $('#addRepositoryRecord').prop('disabled', false); - $('.dataTables_length select').prop('disabled', false); - $('#repository-acitons-dropdown').removeClass('disabled'); - $('#repository-acitons-dropdown').prop('disabled', false); - $('#addNewColumn').removeClass('disabled'); - $('#addNewColumn').prop('disabled', false); - $('#repository-columns-dropdown') - .find('.dropdown-toggle') - .prop('disabled', false); - $('th').removeClass('disable-click'); - $('.repository-row-selector').removeClass('disabled'); - $('.repository-row-selector').prop('disabled', false); - if (rowsSelected.length === 0) { - $('#copyRepositoryRecords').prop('disabled', true); - $('#copyRepositoryRecords').addClass('disabled'); - $('#editRepositoryRecord').prop('disabled', true); - $('#editRepositoryRecord').addClass('disabled'); - $('#deleteRepositoryRecordsButton').prop('disabled', true); - $('#deleteRepositoryRecordsButton').addClass('disabled'); - $('#exportRepositoriesButton').parent('li').addClass('disabled'); - $('#exportRepositoriesButton').prop('disabled', true); - $('#exportRepositoriesButton').off('click'); - $('#export-repositories').off('click'); - $('#assignRepositoryRecords').addClass('disabled'); - $('#assignRepositoryRecords').prop('disabled', true); - $('#unassignRepositoryRecords').addClass('disabled'); - $('#unassignRepositoryRecords').prop('disabled', true); - } else { - if (rowsSelected.length === 1 && - $('#exportRepositoriesButton').get(0)) { - $('#editRepositoryRecord').prop('disabled', false); - $('#editRepositoryRecord').removeClass('disabled'); - - // If we switched from 2 selections to 1, then this is not needed - var events = $._data($('#exportRepositoriesButton').get(0), 'events'); - if (!events || !events.click) { - $('#exportRepositoriesButton').parent('li').removeClass('disabled'); - $('#exportRepositoriesButton').prop('disabled', false); - $('#exportRepositoriesButton').off('click').on('click', function() { - $('#exportRepositoryModal').modal('show'); - }); - $('#export-repositories').off('click').on('click', function() { - animateSpinner(null, true); - $('#form-export').submit(); - }); - } - } else { - $('#editRepositoryRecord').prop('disabled', true); - $('#editRepositoryRecord').addClass('disabled'); - } - $('#deleteRepositoryRecordsButton').prop('disabled', false); - $('#deleteRepositoryRecordsButton').removeClass('disabled'); - $('#copyRepositoryRecords').prop('disabled', false); - $('#copyRepositoryRecords').removeClass('disabled'); - $('#assignRepositoryRecords').removeClass('disabled'); - $('#assignRepositoryRecords').prop('disabled', false); - $('#unassignRepositoryRecords').removeClass('disabled'); - $('#unassignRepositoryRecords').prop('disabled', false); - } - } else if (currentMode === 'editMode') { - $('#repository-acitons-dropdown').addClass('disabled'); - $('#repository-acitons-dropdown').prop('disabled', true); - $('.dataTables_length select').prop('disabled', true); - $('#addRepositoryRecord').addClass('disabled'); - $('#addRepositoryRecord').prop('disabled', true); - $('#editRepositoryRecord').addClass('disabled'); - $('#editRepositoryRecord').prop('disabled', true); - $('#addNewColumn').addClass('disabled'); - $('#addNewColumn').prop('disabled', true); - $('#deleteRepositoryRecordsButton').addClass('disabled'); - $('#deleteRepositoryRecordsButton').prop('disabled', true); - $('#exportRepositoriesButton').off('click'); - $('#export-repositories').off('click'); - $('#assignRepositoryRecords').addClass('disabled'); - $('#assignRepositoryRecords').prop('disabled', true); - $('#unassignRepositoryRecords').addClass('disabled'); - $('#unassignRepositoryRecords').prop('disabled', true); - $('#repository-columns-dropdown').find('.dropdown-toggle') - .prop('disabled', true); - $('th').addClass('disable-click'); - $('.repository-row-selector').addClass('disabled'); - $('.repository-row-selector').prop('disabled', true); - } - } - - // Clear all has-error tags - function clearAllErrors() { - // Remove any validation errors - $(selectedRecord).find('.has-error').each(function() { - $(this).removeClass('has-error'); - $(this).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 = []; - } - - // 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); - } - // Handle enter key $(document).off('keypress').keypress(function(event) { var keycode = (event.keyCode ? event.keyCode : event.which); if (currentMode === 'editMode' && keycode === '13') { $('#saveRecord').click(); - return false; } }); - // Helper functions - function _listItemDropdown(options, current_value, column_id) { - var html = ''; - return html; - } - - function initialListItemsRequest(column_id) { - var massage_response = []; - $.ajax({ - url: '<%= Rails.application.routes.url_helpers.repository_list_items_path %>', - type: 'POST', - dataType: 'json', - async: false, - data: { - q: '', - column_id: column_id - } - }).done(function(data) { - $.each(data.list_items, function(index, el) { - massage_response.push([el.id, el.data]); - }); - }); - return _listItemDropdown(massage_response, '-1', column_id); - } - - 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 _initSelectPicker() { - $('.selectpicker') - .selectpicker({liveSearch: true}) - .ajaxSelectPicker({ - ajax: { - url: '<%= Rails.application.routes.url_helpers.repository_list_items_path %>', - type: 'POST', - dataType: 'json', - data: function () { - var params = { - q: '{{{q}}}', - column_id: $(this.valueOf().plugin.$element).attr('column_id') - }; - - return params; - } - }, - locale: { - emptyTitle: 'Nothing selected' - }, - preprocessData: function(data){ - var items = []; - if(data.hasOwnProperty('list_items')){ - items.push({ - 'value': '-1', - 'text': '', - 'disabled': false - }); - $.each(data.list_items, function(index, el) { - items.push( - { - 'value': el.id, - 'text': el.data, - 'disabled': false - } - ) - }); - } - return items; - }, - emptyRequest: true, - clearOnEmpty: false, - preserveSelected: false - }).on('change.bs.select', function(el) { - $(this).closest('td').attr('list_item_id', el.target.value); - $(this).closest('td').attr('column_id', $(this).attr('column_id')); - }).trigger('change.bs.select'); - } - - 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 "
"; - } - - // Takes object and surrounds it with input - function changeToInputFileField(object, name, value) { - return "
"+ - "
" + - "
" + - "" + - "" + - "
"; - } - - global.clearFileInput = function (el) { + global.clearFileInput = function(el) { var parent = $(el).closest('div.repository-input-file-field'); - var input = parent.find('input:file')[0]; + var input = parent.find('input:file')[0]; // hide clear button $(parent.find('a[data-action="removeAsset"]')[0]).hide(); // reset value @@ -1130,157 +1177,45 @@ var RepositoryDatatable = (function(global) { // add flag $(input).attr('remove', true); // clear fileName - $(parent.find('.file-name-label')[0]) - .text(I18n.t('general.file.no_file_chosen')); + $(parent.find('.file-name-label')[0]).text(I18n.t('general.file.no_file_chosen')); $(parent.find('.form-group')[0]).removeClass('has-error'); parent.removeClass('has-error'); $(parent.find('.help-block')[0]).remove(); - } + }; - // Takes an object and creates custom html element - function changeToFormField(object, name, column_type, cell, list_columns) { - var value = cell.value || ''; - if (column_type === 'RepositoryListValue') { - var column = _.findWhere(list_columns, { column_id: parseInt(name) }); - var list_items = column.list_items || cell.list_items; - return _listItemDropdown(list_items, value, parseInt(name)); - } else if (column_type === 'RepositoryAssetValue') { - return changeToInputFileField('repository_cell_file', name, value); - } else { - return changeToInputField(object, name, value); + function generateColumnNameTooltip(name) { + var maxLength = $(TABLE_ID).data('max-dropdown-length'); + if ($.trim(name).length > maxLength) { + return ''; } - } - - // Return td element with content - function createTdElement(content) { - var td = document.createElement('td'); - td.innerHTML = content; - return td; - } - - // Adjust columns width in table header - function adjustTableHeader() { - TABLE.columns.adjust(); - $('.dropdown-menu').parent() - .on('shown.bs.dropdown hidden.bs.dropdown', function() { - TABLE.columns.adjust(); - }); - } - - 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); - } - - 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'); - }); + return name; } /* * Repository columns dropdown */ - var dropdown, dropdownList; - - // loads the columns names in the dropdown list - function loadColumnsNames() { - // Save scroll position - var scrollPosition = dropdownList.scrollTop(); - // Clear the list - dropdownList.find('li[data-position]').remove(); - _.each(TABLE.columns().header(), function(el, index) { - if (index > 1) { - var colIndex = $(el).attr('data-column-index'); - var visible = TABLE.column(colIndex).visible(); - - var visClass = (visible) ? 'fa-eye' : 'fa-eye-slash'; - var visLi = (visible) ? '' : 'col-invisible'; - - var thederName; - if ($(el).find('.modal-tooltiptext').length > 0) { - thederName = $(el).find('.modal-tooltiptext').text(); - } else { - thederName = el.innerText; - } - - var listItem = dropdownList - .find('.repository-columns-list-template') - .clone(); - - listItem.attr('data-position', colIndex); - listItem.attr('data-id', $(el).attr('id')); - listItem.addClass(visLi); - listItem.removeClass('repository-columns-list-template hide'); - listItem.find('.text').html(generateColumnNameTooltip(thederName)); - if(thederName !== 'Name') { - listItem.find('.vis').addClass(visClass); - listItem.find('.vis') - .attr('title', $(TABLE_ID).data('columns-visibility-text')); - } - dropdownList.append(listItem); - } - }); - // Restore scroll position - dropdownList.scrollTop(scrollPosition); - toggleColumnVisibility(); - // toggles grip img - customLiHoverEffect(); - } - function customLiHoverEffect() { var liEl = dropdownList.find('li'); liEl.mouseover(function() { - $(this) - .find('.grippy') - .addClass('grippy-img'); + $(this).find('.grippy').addClass('grippy-img'); }).mouseout(function() { - $(this) - .find('.grippy') - .removeClass('grippy-img'); + $(this).find('.grippy').removeClass('grippy-img'); }); } function toggleColumnVisibility() { var lis = dropdownList.find('.vis'); lis.on('click', function(event) { - event.stopPropagation(); var self = $(this); var li = self.closest('li'); var column = TABLE.column(li.attr('data-position')); - if(column.header.id !== 'row-name') { + event.stopPropagation(); + + if (column.header.id !== 'row-name') { if (column.visible()) { self.addClass('fa-eye-slash'); self.removeClass('fa-eye'); @@ -1296,7 +1231,7 @@ var RepositoryDatatable = (function(global) { } } // Re-filter/search if neccesary - var searchText = $('div.dataTables_filter input').val(); + let searchText = $('div.dataTables_filter input').val(); if (!_.isEmpty(searchText)) { TABLE.search(searchText).draw(); } @@ -1305,6 +1240,48 @@ var RepositoryDatatable = (function(global) { }); } + // loads the columns names in the dropdown list + function loadColumnsNames() { + // Save scroll position + var scrollPosition = dropdownList.scrollTop(); + // Clear the list + dropdownList.find('li[data-position]').remove(); + _.each(TABLE.columns().header(), function(el, index) { + if (index > 1) { + let colIndex = $(el).attr('data-column-index'); + let visible = TABLE.column(colIndex).visible(); + + let visClass = (visible) ? 'fa-eye' : 'fa-eye-slash'; + let visLi = (visible) ? '' : 'col-invisible'; + + let thederName; + if ($(el).find('.modal-tooltiptext').length > 0) { + thederName = $(el).find('.modal-tooltiptext').text(); + } else { + thederName = el.innerText; + } + + let listItem = dropdownList.find('.repository-columns-list-template').clone(); + + listItem.attr('data-position', colIndex); + listItem.attr('data-id', $(el).attr('id')); + listItem.addClass(visLi); + listItem.removeClass('repository-columns-list-template hide'); + listItem.find('.text').html(generateColumnNameTooltip(thederName)); + if (thederName !== 'Name') { + listItem.find('.vis').addClass(visClass); + listItem.find('.vis').attr('title', $(TABLE_ID).data('columns-visibility-text')); + } + dropdownList.append(listItem); + } + }); + // Restore scroll position + dropdownList.scrollTop(scrollPosition); + toggleColumnVisibility(); + // toggles grip img + customLiHoverEffect(); + } + function initSorting() { dropdownList.sortable({ items: 'li:not(.repository-columns-list-template)', @@ -1336,20 +1313,9 @@ var RepositoryDatatable = (function(global) { } } - function generateColumnNameTooltip(name) { - var maxLength = $(TABLE_ID).data('max-dropdown-length'); - if ($.trim(name).length > maxLength) { - return ''; - } - return name; - } - // initialze dropdown after the table is loaded function initDropdown() { TABLE.on('init.dt', function() { - dropdown = $('#repository-columns-dropdown'); dropdownList = $('#repository-columns-list'); initSorting(); toggleColumnVisibility(); @@ -1392,4 +1358,4 @@ var RepositoryDatatable = (function(global) { destroy: destroy, redrawTableOnSidebarToggle: redrawTableOnSidebarToggle }); -})(window); +}(window)); diff --git a/app/views/repositories/_repository_table.html.erb b/app/views/repositories/_repository_table.html.erb index 0f864551a..7af68aadf 100644 --- a/app/views/repositories/_repository_table.html.erb +++ b/app/views/repositories/_repository_table.html.erb @@ -16,7 +16,10 @@ data-columns-visibility-text="<%= I18n.t('repositories.columns_visibility') %>" 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-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 %>">