<template> <div class="step-table-container"> <div class="step-element-header" :class="{ 'editing-name': editingName, 'step-element--locked': locked }"> <div v-if="reorderElementUrl" class="step-element-grip" @click="$emit('reorder')"> <i class="sn-icon sn-icon-sort"></i> </div> <div v-else class="step-element-grip-placeholder"></div> <div v-if="!locked || element.attributes.orderable.name" :key="reloadHeader" class="step-element-name"> <InlineEdit :value="element.attributes.orderable.name" :characterLimit="255" :placeholder="''" :allowBlank="false" :autofocus="editingName" :attributeName="`${i18n.t('Table')} ${i18n.t('name')}`" @editingEnabled="enableNameEdit" @editingDisabled="disableNameEdit" @update="updateName" /> </div> <div class="step-element-controls"> <button v-if="element.attributes.orderable.urls.update_url" class="btn icon-btn btn-light" @click="enableNameEdit" tabindex="0"> <i class="sn-icon sn-icon-edit"></i> </button> <button v-if="element.attributes.orderable.urls.duplicate_url" class="btn icon-btn btn-light" tabindex="0" @click="duplicateElement"> <i class="sn-icon sn-icon-duplicate"></i> </button> <button v-if="element.attributes.orderable.urls.delete_url" class="btn icon-btn btn-light" @click="showDeleteModal" tabindex="0"> <i class="sn-icon sn-icon-delete"></i> </button> </div> </div> <div class="step-table" :class="{'edit': editingTable, 'view': !editingTable, 'locked': !element.attributes.orderable.urls.update_url}" tabindex="0" @keyup.enter="!editingTable && enableTableEdit()"> <div class="enable-edit-mode" v-if="!editingTable && element.attributes.orderable.urls.update_url" @click="enableTableEdit"> <div class="enable-edit-mode__icon" tabindex="0"> <i class="sn-icon sn-icon-edit"></i> </div> </div> <div ref="hotTable" class="hot-table-container" @click="!editingTable && enableTableEdit()"> </div> <div v-if="editingTable" class="edit-message"> {{ i18n.t('protocols.steps.table.edit_message') }} </div> </div> <div class="edit-buttons" v-if="editingTable"> <button class="btn icon-btn btn-primary btn-sm" @click="updateTable"> <i class="sn-icon sn-icon-check"></i> </button> <button class="btn icon-btn btn-light btn-sm" @click="disableTableEdit"> <i class="sn-icon sn-icon-close"></i> </button> </div> <deleteElementModal v-if="confirmingDelete" @confirm="deleteElement" @cancel="closeDeleteModal"/> <tableNameModal v-if="nameModalOpen" :element="element" @update="updateEmptyName" @cancel="nameModalOpen = false" /> </div> </template> <script> import DeleteMixin from '../mixins/components/delete.js' import DuplicateMixin from '../mixins/components/duplicate.js' import deleteElementModal from '../modals/delete_element.vue' import InlineEdit from '../../shared/inline_edit.vue' import TableNameModal from '../modals/table_name_modal.vue' export default { name: 'StepTable', components: { deleteElementModal, InlineEdit, TableNameModal }, mixins: [DeleteMixin, DuplicateMixin], props: { element: { type: Object, required: true }, inRepository: { type: Boolean, required: true }, reorderElementUrl: { type: String }, isNew: { type: Boolean, default: false }, assignableMyModuleId: { type: Number, required: false } }, data() { return { editingName: false, editingTable: false, tableObject: null, nameModalOpen: false, reloadHeader: 0, updatingTableData: false } }, computed: { locked() { return !this.element.attributes.orderable.urls.update_url } }, updated() { if(!this.updatingTableData) this.loadTableData(); }, beforeUpdate() { if(!this.updatingTableData) this.tableObject.destroy(); }, mounted() { this.loadTableData(); if (this.isNew) this.enableTableEdit(); }, methods: { enableTableEdit() { if(this.locked) { return; } if (!this.element.attributes.orderable.name) { this.openNameModal(); return; } this.editingTable = true; this.$nextTick(() => this.tableObject.selectCell(0,0)); }, disableTableEdit() { this.editingTable = false; this.updatingTableData = false; }, enableNameEdit() { this.editingName = true; }, disableNameEdit() { this.editingName = false; }, updateName(name) { this.element.attributes.orderable.name = name; this.update(); }, openNameModal() { this.tableObject.deselectCell(); this.nameModalOpen = true; }, updateEmptyName(name) { this.disableNameEdit(); // force reload header to properly reset name inline edit this.reloadHeader = this.reloadHeader + 1; this.element.attributes.orderable.name = name; this.$emit('update', this.element, false, () => { this.nameModalOpen = false; this.enableTableEdit(); }); }, updateTable() { if (this.editingTable == false) return; this.update(); this.editingTable = false; }, update() { this.element.attributes.orderable.contents = JSON.stringify({ data: this.tableObject.getData() }); let metadata = this.element.attributes.orderable.metadata || {}; if (metadata.plateTemplate) { this.element.attributes.orderable.metadata = JSON.stringify({ cells: this.tableObject .getCellsMeta() .filter(e => !!e) .map((x) => { const {row, col} = x; return { row: row, col: col, className: x.className || '', } }) }); } else { this.element.attributes.orderable.metadata = JSON.stringify({ cells: this.tableObject .getCellsMeta() .filter(e => !!e) .map((x) => { const {row, col} = x; const plugins = this.tableObject.plugin; const cellId = plugins.utils.translateCellCoords({row, col}); const calculated = plugins.matrix.getItem(cellId)?.value || this.tableObject.getDataAtCell(row, col) || null; return { row: row, col: col, className: x.className || '', calculated: calculated } }) }); } this.$emit('update', this.element) this.ajax_update_url() this.updatingTableData = false; }, ajax_update_url() { $.ajax({ url: this.element.attributes.orderable.urls.update_url, method: 'PUT', data: this.element.attributes.orderable, }) }, loadTableData() { let container = this.$refs.hotTable; let data = JSON.parse(this.element.attributes.orderable.contents); let metadata = this.element.attributes.orderable.metadata || {}; let formulasEnabled = metadata.plateTemplate ? false : true; this.tableObject = new Handsontable(container, { data: data.data, width: '100%', startRows: 5, startCols: 5, rowHeaders: tableColRowName.tableRowHeaders(metadata.plateTemplate), colHeaders: tableColRowName.tableColHeaders(metadata.plateTemplate), cell: metadata.cells || [], contextMenu: this.editingTable, formulas: formulasEnabled, preventOverflow: 'horizontal', readOnly: !this.editingTable, afterUnlisten: () => { this.updatingTableData = true; setTimeout(this.updateTable, 100) // delay makes cancel button work } }); } } } </script>