diff --git a/app/assets/javascripts/protocols/handson.js b/app/assets/javascripts/protocols/handson.js new file mode 100644 index 000000000..9ce984acb --- /dev/null +++ b/app/assets/javascripts/protocols/handson.js @@ -0,0 +1,35 @@ +/* global tableColRowName*/ + +(function() { + const handsontableInitDataElem = $('#handson'); + const HANDSONTABLE_INIT_ROWS_CNT = handsontableInitDataElem.data('init-rows-cnt'); + const HANDSONTABLE_INIT_COLS_CNT = handsontableInitDataElem.data('init-cols-cnt'); + + $("[data-role='hot-table']").each(function() { + var hot; + var $container = $(this).find("[data-role='step-hot-table']"); + var contents = $(this).find('.hot-contents'); + var metadataJson = $(this).find('.hot-metadata'); + var metadata = JSON.parse(metadataJson.val() || '{}'); + + $container.handsontable({ + startRows: HANDSONTABLE_INIT_ROWS_CNT, + startCols: HANDSONTABLE_INIT_COLS_CNT, + rowHeaders: tableColRowName.tableRowHeaders(metadata.plateTemplate), + colHeaders: tableColRowName.tableColHeaders(metadata.plateTemplate), + fillHandle: false, + formulas: true, + data: JSON.parse(contents.attr('value')).data, + cell: metadata.cells || [], + readOnly: true + }); + + hot = $container.handsontable('getInstance'); + + setTimeout(() => { + hot.render(); + }, 500); + }); + + window.print(); +}()); diff --git a/app/assets/javascripts/reports/content.js b/app/assets/javascripts/reports/content.js index 01373f0f2..ee0aff54a 100644 --- a/app/assets/javascripts/reports/content.js +++ b/app/assets/javascripts/reports/content.js @@ -1,4 +1,35 @@ (function() { + + function tableColHeaders(isPlateTemplate) { + if (isPlateTemplate) { + return function(visualColumnIndex) { + return visualColumnIndex + 1; + }; + } + + return true; + } + + function tableRowHeaders(isPlateTemplate) { + if (isPlateTemplate) { + return function(visualColumnIndex) { + var ordA = 'A'.charCodeAt(0); + var ordZ = 'Z'.charCodeAt(0); + var len = (ordZ - ordA) + 1; + var num = visualColumnIndex; + + var colName = ''; + while (num >= 0) { + colName = String.fromCharCode((num % len) + ordA) + colName; + num = Math.floor(num / len) - 1; + } + return colName; + }; + } + + return true; + } + /** * Initialize the hands on table on the given * element with the specified data. @@ -7,11 +38,12 @@ function initializeHandsonTable(el) { var input = el.siblings('input.hot-table-contents'); var inputObj = JSON.parse(input.attr('value')); - var metadata = el.siblings('input.hot-table-metadata'); + var metadataJson = el.siblings('input.hot-table-metadata'); var data = inputObj.data; var headers; var parentEl; var order; + var metadata; // Special handling if this is a repository table if (input.hasClass('hot-repository-items')) { @@ -32,15 +64,16 @@ el.handsontable('getInstance').getPlugin('columnSorting').sort(3, order); } else { + metadata = JSON.parse(metadataJson.val() || '{}'); el.handsontable({ disableVisualSelection: true, - rowHeaders: true, - colHeaders: true, + rowHeaders: tableRowHeaders(metadata.plateTemplate), + colHeaders: tableColHeaders(metadata.plateTemplate), editor: false, copyPaste: false, formulas: true, data: data, - cell: JSON.parse(metadata.val() || '{}').cells || [] + cell: metadata.cells || [] }); } } diff --git a/app/assets/javascripts/sitewide/table_col_row_name.js b/app/assets/javascripts/sitewide/table_col_row_name.js new file mode 100644 index 000000000..126058bfe --- /dev/null +++ b/app/assets/javascripts/sitewide/table_col_row_name.js @@ -0,0 +1,42 @@ +/* eslint-disable no-unused-vars, no-use-before-define */ + +var tableColRowName = (function() { + function tableColHeaders(isPlateTemplate) { + if (isPlateTemplate) { + return function(visualColumnIndex) { + return visualColumnIndex + 1; + }; + } + + return true; + } + + function tableRowHeaders(isPlateTemplate) { + if (isPlateTemplate) { + return function(visualColumnIndex) { + var ordA = 'A'.charCodeAt(0); + var ordZ = 'Z'.charCodeAt(0); + var len = (ordZ - ordA) + 1; + var num = visualColumnIndex; + + var colName = ''; + while (num >= 0) { + colName = String.fromCharCode((num % len) + ordA) + colName; + num = Math.floor(num / len) - 1; + } + return colName; + }; + } + + return true; + } + + return { + tableColHeaders: function(isPlateTemplate) { + return tableColHeaders(isPlateTemplate); + }, + tableRowHeaders: function(isPlateTemplate) { + return tableRowHeaders(isPlateTemplate); + } + }; +}()); diff --git a/app/controllers/step_elements/tables_controller.rb b/app/controllers/step_elements/tables_controller.rb index dbb7645a3..4326503a7 100644 --- a/app/controllers/step_elements/tables_controller.rb +++ b/app/controllers/step_elements/tables_controller.rb @@ -5,7 +5,7 @@ module StepElements before_action :load_table, only: %i(update destroy duplicate) def create - predefined_table_dimensions = params[:tableDimensions].map(&:to_i) + predefined_table_dimensions = create_table_params[:tableDimensions].map(&:to_i) name = if predefined_table_dimensions[0] == predefined_table_dimensions[1] t('protocols.steps.table.default_name', position: @step.step_tables.length + 1) @@ -18,6 +18,7 @@ module StepElements name: name, contents: { data: Array.new(predefined_table_dimensions[0], Array.new(predefined_table_dimensions[1], '')) }.to_json, + metadata: { plateTemplate: create_table_params[:plateTemplate] == 'true' }, created_by: current_user, team: @step.protocol.team )) @@ -36,7 +37,9 @@ module StepElements ActiveRecord::Base.transaction do @table.assign_attributes(table_params.except(:metadata)) begin - @table.metadata = JSON.parse(table_params[:metadata]) if table_params[:metadata].present? + if table_params[:metadata].present? + @table.metadata = @table.metadata.merge(JSON.parse(table_params[:metadata])) + end rescue JSON::ParserError @table.metadata = {} end @@ -79,6 +82,10 @@ module StepElements params.permit(:name, :contents, :metadata) end + def create_table_params + params.permit(:plateTemplate, tableDimensions: []) + end + def load_table @table = @step.tables.find_by(id: params[:id]) return render_404 unless @table diff --git a/app/javascript/vue/protocol/step.vue b/app/javascript/vue/protocol/step.vue index dc25cfe51..0acfe0cfc 100644 --- a/app/javascript/vue/protocol/step.vue +++ b/app/javascript/vue/protocol/step.vue @@ -76,7 +76,7 @@ @@ -437,8 +437,8 @@ } }); }, - createElement(elementType, tableDimensions = [5,5]) { - $.post(this.urls[`create_${elementType}_url`], { tableDimensions: tableDimensions }, (result) => { + createElement(elementType, tableDimensions = [5,5], plateTemplate = false) { + $.post(this.urls[`create_${elementType}_url`], { tableDimensions: tableDimensions, plateTemplate: plateTemplate }, (result) => { result.data.isNew = true; this.elements.push(result.data) this.$emit('stepUpdated') diff --git a/app/javascript/vue/protocol/step_elements/table.vue b/app/javascript/vue/protocol/step_elements/table.vue index 838401def..9cc6d7564 100644 --- a/app/javascript/vue/protocol/step_elements/table.vue +++ b/app/javascript/vue/protocol/step_elements/table.vue @@ -181,13 +181,14 @@ let container = this.$refs.hotTable; let data = JSON.parse(this.element.attributes.orderable.contents); let metadata = this.element.attributes.orderable.metadata || {}; + this.tableObject = new Handsontable(container, { data: data.data, width: '100%', startRows: 5, startCols: 5, - rowHeaders: true, - colHeaders: true, + rowHeaders: tableColRowName.tableRowHeaders(metadata.plateTemplate), + colHeaders: tableColRowName.tableColHeaders(metadata.plateTemplate), cell: metadata.cells || [], contextMenu: this.editingTable, formulas: true, diff --git a/app/views/layouts/protocols/print.html.erb b/app/views/layouts/protocols/print.html.erb index e591d7dfa..9e63605e9 100644 --- a/app/views/layouts/protocols/print.html.erb +++ b/app/views/layouts/protocols/print.html.erb @@ -10,31 +10,10 @@ <%= stylesheet_link_tag 'layouts/print_protocol', media: 'print, screen' %> <%= yield %> - +
+ <%= javascript_include_tag 'protocols/handson', nonce: true %> diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 0f51a7ef9..033c78e2a 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -63,6 +63,7 @@ Rails.application.config.assets.precompile += %w(protocols/edit.js) Rails.application.config.assets.precompile += %w(protocols/import_export/eln_table.js) Rails.application.config.assets.precompile += %w(protocols/import_export/import.js) Rails.application.config.assets.precompile += %w(protocols/import_export/export.js) +Rails.application.config.assets.precompile += %w(protocols/handson.js) Rails.application.config.assets.precompile += %w(layouts/print_protocol.css) Rails.application.config.assets.precompile += %w(datatables.js) Rails.application.config.assets.precompile += %w(search/index.js)