mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-04 04:04:36 +08:00
implement search on list items dropdown, update, create actions [fixes SCI-2070]
This commit is contained in:
parent
0baa7a1f99
commit
d69754af04
13 changed files with 299 additions and 41 deletions
2
Gemfile
2
Gemfile
|
@ -26,7 +26,7 @@ gem 'momentjs-rails', '~> 2.17.1'
|
|||
# JS datetime picker
|
||||
gem 'bootstrap3-datetimepicker-rails', '~> 4.15.35'
|
||||
# Select elements for Bootstrap
|
||||
gem 'bootstrap-select-rails', '~> 1.6.3'
|
||||
gem 'bootstrap-select-rails', '~> 1.12.4'
|
||||
gem 'uglifier', '>= 1.3.0'
|
||||
# jQuery & plugins
|
||||
gem 'jquery-turbolinks'
|
||||
|
|
|
@ -127,7 +127,7 @@ GEM
|
|||
bootstrap-sass (3.3.7)
|
||||
autoprefixer-rails (>= 5.2.1)
|
||||
sass (>= 3.3.4)
|
||||
bootstrap-select-rails (1.6.3)
|
||||
bootstrap-select-rails (1.12.4)
|
||||
bootstrap3-datetimepicker-rails (4.15.35)
|
||||
momentjs-rails (>= 2.8.1)
|
||||
bootstrap_form (2.7.0)
|
||||
|
@ -518,7 +518,7 @@ DEPENDENCIES
|
|||
better_errors
|
||||
binding_of_caller
|
||||
bootstrap-sass (~> 3.3.5)
|
||||
bootstrap-select-rails (~> 1.6.3)
|
||||
bootstrap-select-rails (~> 1.12.4)
|
||||
bootstrap3-datetimepicker-rails (~> 4.15.35)
|
||||
bootstrap_form
|
||||
bullet
|
||||
|
|
|
@ -31,11 +31,12 @@
|
|||
//= require tinymce-jquery
|
||||
//= require jsPlumb-2.0.4-min
|
||||
//= require jsnetworkx
|
||||
//= require dataTables.noSearchHidden
|
||||
//= require bootstrap-select
|
||||
//= require_directory ./sitewide
|
||||
//= require jquery.dataTables.yadcf
|
||||
//= require datatables
|
||||
//= require dataTables.noSearchHidden
|
||||
//= require bootstrap-select
|
||||
//= require ajax-bootstrap-select.min
|
||||
//= require underscore
|
||||
//= require i18n.js
|
||||
//= require i18n/translations
|
||||
|
|
|
@ -432,14 +432,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')) {
|
||||
} 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 {
|
||||
// Column we don't care for, just add empty td
|
||||
tr.appendChild(createTdElement(''));
|
||||
}
|
||||
});
|
||||
|
||||
$('table' + TABLE_ID).prepend(tr);
|
||||
selectedRecord = tr;
|
||||
|
||||
|
@ -451,6 +457,8 @@ var RepositoryDatatable = (function(global) {
|
|||
});
|
||||
// Adjust columns width in table header
|
||||
adjustTableHeader();
|
||||
// Init selectpicker
|
||||
_initSelectPicker();
|
||||
}
|
||||
|
||||
global.onClickToggleAssignedRecords = function() {
|
||||
|
@ -584,6 +592,7 @@ var RepositoryDatatable = (function(global) {
|
|||
|
||||
// Take care of custom cells
|
||||
var cells = data.repository_row.repository_cells;
|
||||
var list_columns = data.repository_row.repository_column_items;
|
||||
$(node).children('td').each(function(i) {
|
||||
var td = $(this);
|
||||
var rawIndex = TABLE.column.index('fromVisible', i);
|
||||
|
@ -592,13 +601,17 @@ var RepositoryDatatable = (function(global) {
|
|||
// Check if cell on this record exists
|
||||
var cell = cells[$(colHeader).attr('id')];
|
||||
if (cell) {
|
||||
td.html(changeToInputField('repository_cell',
|
||||
td.html(changeToFormField('repository_cell',
|
||||
$(colHeader).attr('id'),
|
||||
cell.value));
|
||||
cell,
|
||||
list_columns));
|
||||
} else {
|
||||
td.html(changeToInputField('repository_cell',
|
||||
$(colHeader).attr('id'), ''));
|
||||
td.html(changeToFormField('repository_cell',
|
||||
$(colHeader).attr('id'),
|
||||
'',
|
||||
list_columns));
|
||||
}
|
||||
_initSelectPicker();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -647,13 +660,18 @@ var RepositoryDatatable = (function(global) {
|
|||
// Record name
|
||||
data.repository_row.name = $('td input[data-object = repository_row]').val();
|
||||
|
||||
// Custom cells
|
||||
// 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()) {
|
||||
data.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');
|
||||
data.repository_cells[$(el).attr('column_id')] = value;
|
||||
});
|
||||
|
||||
var url;
|
||||
var type;
|
||||
|
@ -845,6 +863,88 @@ var RepositoryDatatable = (function(global) {
|
|||
});
|
||||
|
||||
// Helper functions
|
||||
function _listItemDropdown(options, current_value, column_id) {
|
||||
var html = '<select class="form-control selectpicker" ';
|
||||
html += 'data-abs-min-length="2" data-live-search="true" ';
|
||||
html += 'column_id="' + column_id +'">';
|
||||
html += '<option value="-1"></option>';
|
||||
$.each(options, function(index, value) {
|
||||
var selected = (current_value === value[1]) ? 'selected' : '';
|
||||
html += '<option value="' + value[0] + '" ' + selected + '>';
|
||||
html += value[1] + '</option>';
|
||||
});
|
||||
html += '</select>';
|
||||
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 _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'));
|
||||
});;
|
||||
}
|
||||
|
||||
function getColumnIndex(id) {
|
||||
if (id < 0) {
|
||||
return false;
|
||||
|
@ -858,6 +958,24 @@ var RepositoryDatatable = (function(global) {
|
|||
object + "' name='" + name + "' value='" + value + "'></input></div>";
|
||||
}
|
||||
|
||||
// Takes an object and creates custom html element
|
||||
function changeToFormField(object, name, cell, list_columns) {
|
||||
if (cell === '') {
|
||||
var column = _.findWhere(list_columns, { column_id: parseInt(name) });
|
||||
if (column) {
|
||||
return _listItemDropdown(column.list_items, '', parseInt(name));
|
||||
} else {
|
||||
return changeToInputField(object, name, '');
|
||||
}
|
||||
} else {
|
||||
if (cell.type === 'RepositoryListValue') {
|
||||
return _listItemDropdown(cell.list_items, cell.value, parseInt(name));
|
||||
} else {
|
||||
return changeToInputField(object, name, cell.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return td element with content
|
||||
function createTdElement(content) {
|
||||
var td = document.createElement('td');
|
|
@ -16,6 +16,7 @@
|
|||
@import "bootstrap-tagsinput";
|
||||
@import "bootstrap-tagsinput-typeahead";
|
||||
@import "handsontable.full.min";
|
||||
@import "ajax-bootstrap-select.min";
|
||||
@import "extend/bootstrap";
|
||||
@import "font-awesome";
|
||||
@import "themes/scinote";
|
||||
|
|
26
app/controllers/repository_list_items_controller.rb
Normal file
26
app/controllers/repository_list_items_controller.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
class RepositoryListItemsController < ApplicationController
|
||||
before_action :load_vars, only: :search
|
||||
|
||||
def search
|
||||
column_list_items = @repository_column.repository_list_items
|
||||
.where('data ILIKE ?',
|
||||
"%#{search_params[:q]}%")
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.select(:id, :data)
|
||||
|
||||
render json: { list_items: column_list_items }, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search_params
|
||||
params.permit(:q, :column_id)
|
||||
end
|
||||
|
||||
def load_vars
|
||||
@repository_column = RepositoryColumn.find_by_id(search_params[:column_id])
|
||||
repository = @repository_column.repository if @repository_column
|
||||
render_404 and return unless @repository_column&.data_type == "RepositoryListValue"
|
||||
render_403 unless can_manage_repository_rows?(repository.team)
|
||||
end
|
||||
end
|
|
@ -38,15 +38,31 @@ class RepositoryRowsController < ApplicationController
|
|||
column = @repository.repository_columns.detect do |c|
|
||||
c.id == key.to_i
|
||||
end
|
||||
cell_value = RepositoryTextValue.new(
|
||||
data: value,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
if column.data_type == 'RepositoryListValue'
|
||||
next if value == '-1'
|
||||
# check if item existx else revert the transaction
|
||||
list_item = RepositoryListItem.where(repository_column: column)
|
||||
.find(value)
|
||||
cell_value = RepositoryListValue.new(
|
||||
repository_list_item_id: list_item.id,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
else
|
||||
cell_value = RepositoryTextValue.new(
|
||||
data: value,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
end
|
||||
if cell_value.save
|
||||
record_annotation_notification(record, cell_value.repository_cell)
|
||||
else
|
||||
|
@ -91,7 +107,8 @@ class RepositoryRowsController < ApplicationController
|
|||
json = {
|
||||
repository_row: {
|
||||
name: escape_input(@record.name),
|
||||
repository_cells: {}
|
||||
repository_cells: {},
|
||||
repository_column_items: fetch_columns_list_items
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +116,9 @@ class RepositoryRowsController < ApplicationController
|
|||
@record.repository_cells.each do |cell|
|
||||
json[:repository_row][:repository_cells][cell.repository_column_id] = {
|
||||
repository_cell_id: cell.id,
|
||||
value: escape_input(cell.value.data)
|
||||
value: escape_input(cell.value.data),
|
||||
type: cell.value_type,
|
||||
list_items: fetch_list_items(cell)
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -125,14 +144,26 @@ class RepositoryRowsController < ApplicationController
|
|||
end
|
||||
if existing
|
||||
# Cell exists and new value present, so update value
|
||||
existing.value.data = value
|
||||
if existing.value.save
|
||||
record_annotation_notification(@record, existing)
|
||||
if existing.value_type == 'RepositoryListValue'
|
||||
item = RepositoryListItem.where(
|
||||
repository_column: existing.repository_column
|
||||
).find(value) unless value == '-1'
|
||||
if item
|
||||
existing.repository_list_value
|
||||
.update_attribute(:repository_list_item_id, item)
|
||||
else
|
||||
existing.delete
|
||||
end
|
||||
else
|
||||
errors[:repository_cells] << {
|
||||
"#{existing.repository_column_id}":
|
||||
existing.value.errors.messages
|
||||
}
|
||||
existing.value.data = value
|
||||
if existing.value.save
|
||||
record_annotation_notification(@record, existing)
|
||||
else
|
||||
errors[:repository_cells] << {
|
||||
"#{existing.repository_column_id}":
|
||||
existing.value.errors.messages
|
||||
}
|
||||
end
|
||||
end
|
||||
else
|
||||
# Looks like it is a new cell, so we need to create new value, cell
|
||||
|
@ -140,15 +171,31 @@ class RepositoryRowsController < ApplicationController
|
|||
column = @repository.repository_columns.detect do |c|
|
||||
c.id == key.to_i
|
||||
end
|
||||
cell_value = RepositoryTextValue.new(
|
||||
data: value,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: @record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
if column.data_type == 'RepositoryListValue'
|
||||
next if value == '-1'
|
||||
# check if item existx else revert the transaction
|
||||
list_item = RepositoryListItem.where(repository_column: column)
|
||||
.find(value)
|
||||
cell_value = RepositoryListValue.new(
|
||||
repository_list_item_id: list_item.id,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: @record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
else
|
||||
cell_value = RepositoryTextValue.new(
|
||||
data: value,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: @record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
end
|
||||
if cell_value.save
|
||||
record_annotation_notification(@record,
|
||||
cell_value.repository_cell)
|
||||
|
@ -161,6 +208,7 @@ class RepositoryRowsController < ApplicationController
|
|||
end
|
||||
# Clean up empty cells, not present in updated record
|
||||
@record.repository_cells.each do |cell|
|
||||
next if cell.value_type == 'RepositoryListValue'
|
||||
cell.value.destroy unless cell_params
|
||||
.key?(cell.repository_column_id.to_s)
|
||||
end
|
||||
|
@ -293,4 +341,28 @@ class RepositoryRowsController < ApplicationController
|
|||
column: link_to(cell.repository_column.name, table_url))
|
||||
)
|
||||
end
|
||||
|
||||
def fetch_list_items(cell)
|
||||
return [] if cell.value_type != 'RepositoryListValue'
|
||||
RepositoryListItem.where(repository: @repository)
|
||||
.where(repository_column: cell.repository_column)
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.pluck(:id, :data)
|
||||
end
|
||||
|
||||
def fetch_columns_list_items
|
||||
collection = []
|
||||
@repository.repository_columns
|
||||
.list_type
|
||||
.preload(:repository_list_items)
|
||||
.each do |column|
|
||||
collection << {
|
||||
column_id: column.id,
|
||||
list_items: column.repository_list_items
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.pluck(:id, :data)
|
||||
}
|
||||
end
|
||||
collection
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,8 @@ class RepositoryColumn < ApplicationRecord
|
|||
|
||||
after_create :update_repository_table_state
|
||||
|
||||
scope :list_type, -> { where(data_type: 'RepositoryListValue') }
|
||||
|
||||
def update_repository_table_state
|
||||
RepositoryTableState.update_state(self, nil, created_by)
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<th id="added-on"><%= t("repositories.table.added_on") %></th>
|
||||
<th id="added-by"><%= t("repositories.table.added_by") %></th>
|
||||
<% repository.repository_columns.order(:id).each do |column| %>
|
||||
<th class="repository-column" id="<%= column.id %>"
|
||||
<th class="repository-column" id="<%= column.id %>" data-type="<%= column.data_type %>"
|
||||
<%= 'data-editable' if can_update_or_delete_repository_column?(column) %>
|
||||
<%= 'data-deletable' if can_update_or_delete_repository_column?(column) %>
|
||||
<%= "data-edit-url='#{edit_repository_repository_column_path(repository, column)}'" %>
|
||||
|
|
|
@ -472,7 +472,6 @@ Rails.application.routes.draw do
|
|||
as: 'columns_destroy_html'
|
||||
|
||||
resources :repository_columns, only: %i(create edit update destroy)
|
||||
|
||||
resources :repository_rows, only: %i(create edit update)
|
||||
member do
|
||||
post 'parse_sheet'
|
||||
|
@ -480,6 +479,9 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
post 'repository_list_items', to: 'repository_list_items#search',
|
||||
defaults: { format: 'json' }
|
||||
|
||||
get 'repository_rows/:id', to: 'repository_rows#show',
|
||||
as: :repository_row,
|
||||
defaults: { format: 'json' }
|
||||
|
|
|
@ -26,7 +26,6 @@ describe AssetTextDatum, type: :model do
|
|||
it 'should have uniq asset' do
|
||||
create :asset_text_datum, asset: asset
|
||||
new_atd = build :asset_text_datum, asset: asset
|
||||
# binding.pry
|
||||
expect(new_atd).to_not be_valid
|
||||
end
|
||||
end
|
||||
|
|
21
vendor/assets/javascripts/ajax-bootstrap-select.min.js
vendored
Normal file
21
vendor/assets/javascripts/ajax-bootstrap-select.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
16
vendor/assets/stylesheets/ajax-bootstrap-select.min.css
vendored
Normal file
16
vendor/assets/stylesheets/ajax-bootstrap-select.min.css
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*!
|
||||
* Ajax Bootstrap Select
|
||||
*
|
||||
* Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
|
||||
*
|
||||
* @version 1.4.3
|
||||
* @author Adam Heim - https://github.com/truckingsim
|
||||
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
|
||||
* @copyright 2017 Adam Heim
|
||||
* @license Released under the MIT license.
|
||||
*
|
||||
* Contributors:
|
||||
* Mark Carver - https://github.com/markcarver
|
||||
*
|
||||
* Last build: 2017-11-15 1:19:47 PM EST
|
||||
*/.bootstrap-select .status{background:#f0f0f0;clear:both;color:#999;font-size:11px;font-style:italic;font-weight:500;line-height:1;margin-bottom:-5px;padding:10px 20px}
|
Loading…
Add table
Reference in a new issue