Source: common/components/table-row-editor.vue

<template>

    <q-card flat style="display: flex; flex-direction: column" v-if="loaded" class="max-width" @keydown="handleSaveCancelKeydown">
        <q-card-actions class="q-pb-none" v-if="!parent.asForm || parent.singleRow">
            <div style="display: flex; justify-content: space-between; width: 100%; align-items: center; min-width: 600px;">
                <div>
                    <q-btn dense v-if="isAdmin" flat icon="info" color="primary" @click="parent.showRowInfo(parent.editingRow, false)">
                      <q-tooltip>{{ $t("Row info") }}</q-tooltip>
                     </q-btn> {{ parent.editMode == "add" ? $t('Adding new row') : '' }}
                </div>	
                <div>
                    <q-btn dense v-if="$store.formChanged" flat icon="save" color="positive" :label="$t('Save')" @click="save" />
                    <q-btn dense v-if="$store.formChanged" flat icon="undo" color="negative" :label="$t('Undo')" @click="cancel" />
                    <q-btn dense v-if="!$store.formChanged" flat icon="close" color="negative" :label="$t('Close')" @click="close" />
                </div>
            </div>
        </q-card-actions>
        <div v-if="actions"><q-btn v-for="action in actions" :key="action.label" dense flat :icon="action.icon" :label="action.label" @click="action.action" /></div>
        
        <q-card-section style="flex-grow: 1; overflow-y: auto;" class="q-pt-none">
            <q-form v-if="loaded" ref="form" autofocus>
                <div :class="{ 'form-disabled': rows && rows.length == 0 && parent.editMode != 'add'}">
                    <table-row-editor-part :row="parent.editingRow" :editColumns="editColumns" @selectionUpdated="selectionUpdated"/>
                    <table-row-editor-part v-if="isARow" :row="isARow" :editColumns="isAColumns" />
                </div>
            </q-form>
        </q-card-section>
    </q-card>
</template>
<script>

/**
 * Generic row editor component
 * 
 * @component
 * @name TableRowEditor
 * @example
 * <TableRowEditor :parent="this" :mutiRow="true" :rows="rows" />
 */
import { loadComponent } from '@/common/component-loader';

export default {
    name: "TableRowEditor",
    components: {
        tableRowEditorPart : loadComponent('table-row-editor-part'),
    },

    props: ['parent', 'multiRow', 'rows', 'actions'],
    emits: ['cancel', 'save'],
    watch: {
        "rows.length": async function (val) {
            if (val == 0) {
                this.parent.editingRow = await this.createEmptyRow(this.parent.columns);
            } else if (this.parent.editMode != "add") {
                if (this.parent.editingRowIndex >= this.rows.length) this.parent.editingRowIndex = this.rows.length - 1;
                await this.parent.editRow(this.rows[this.parent.editingRowIndex]);               
            }
            this.copyObject(this.parent.editingRow, this.parent.editingRowSaved);
            this.$store.formChanged = false;
        },
        "parent.editingRow": {
            handler(val) {
                if (val) {
                    this.$store.formChanged = !this.equalObjects(this.parent.editingRow, this.parent.editingRowSaved)
                        || (this.parent.isA && !this.equalObjects(this.isARow, this.isARowSaved));
                }
            },
            deep: true,
            immediate: true
        },
        "isARow": {
            handler(val) {
                if (!this.parent.isA) return;
                if (val) {
                    this.$store.formChanged = !this.equalObjects(this.parent.editingRow, this.parent.editingRowSaved)
                    || !this.equalObjects(this.isARow, this.isARowSaved);
                }
            },
            deep: true,
            immediate: true
        },
    },
    data() {
        return {
            editColumns: [],
            x: {},
            form: null, 
            oldIsAKey: null,
            isARow: {},
            isARowSaved: {},
            isAColumns: [],
            specialization: null,
            specializationSaved: null,
            loaded: false
        };
    },
    created() {
    },
    /**
     * Initialize the component
     */
    async mounted() {

        if (this.multiRow) {
            if (this.parent.rows.length > 0) {
                await this.parent.editRow(this.parent.rows[0]);
            } else {
                await this.parent.addRow();
            }
        }

        this.copyObject(this.parent.editingRow, this.parent.editingRowSaved);

        let ec = [...this.parent.columns];
        this.swapIdAndValColumns(ec);

        this.editColumns = ec.filter(col => this.showColInEdit(col));
        
        await this.handleIsA();

        this.copyObject(this.isARow, this.isARowSaved);
        this.specializationSaved = this.specialization;

        await this.$nextTick(); 

        this.loaded = true; 
        setTimeout(() => {
            this.focus();
            this.form = this.$refs.form;
            this.$store.formChanged = false;
        }, 200);

    },

    methods: {  
        /**
         * focus the first input field in the form
         */            
        focus() {
            this.$refs.form.focus();
            return;
        },

        /**
         * Determines whether a column should be shown in edit mode.
         * @param {Object} col - The column object.
         * @returns {boolean} - True if the column should be shown in edit mode, false otherwise.
         */
        showColInEdit(col) {
            if (this.parent.isA) {
                if (col.name == this.parent.isA.masterKey) return false;
            }
            return !col.invisible && !col.name.endsWith('_val') && col.name != this.parent.masterKey ;
        },

        /**
         * Save the current row and adjust the editing row
         * if this is a isA row, save the isA row as well
         * @returns {Promise<void>}
         */

        async save() {
            if (!await this.validateForm(this.$refs.form)) return;

            if (await this.parent.saveRow()) {
                if (this.parent.isA) {

                    if (this.parent.editingRow[this.parent.isA.column] != this.parent.editingRowSaved[this.parent.isA.column] && this.isARowSaved["id"]) {
                        // specializations changed, delete the old row
                        if (await this.delete("Table/" + this.specializationSaved + "/" + this.isARowSaved["id"]) == null) {
                            // delete failed, return;
                            return;
                        }
                        this.isARow["id"] = null;
                    }
                    if (this.specialization) {
                        this.isARow[this.parent.isA.masterKey] = this.parent.editingRow["id"];
                        let ret= await this.parent.saveRowToDb(this.parent.editMode, this.specialization, this.isAColumns, this.isARow);
                        //let ret = await this.put("Table/" + this.specialization, this.isARow);
                        if (ret != null) {
                            this.isARow["id"] = ret;
                        } else {
                            return;
                        }
                    }
                }

                if (this.parent.editMode == "add") {
                    if (this.parent.singleRow) {
                        this.close();
                    } else {
                        await this.parent.addRow();
                    }
                } 

                if (this.parent.editMode == "edit") {
                    this.close();
                } else {
                    this.copyObject(this.parent.editingRow, this.parent.editingRowSaved);
                    this.copyObject(this.isARow, this.isARowSaved);
                    this.specializationSaved = this.specialization;
                }
                this.$store.formChanged = false;
            }
        },

        /**
         * Close the form
         */
        close() {
            this.$emit('cancel');
        },

        /**
         * Cancel the edit operation and revert to the original values
         */
        async cancel() {
            if (this.multiRow && this.parent.editingRowIndex >= this.rows.length) {
                this.parent.editingRowIndex = this.rows.length - 1;
            }
            await this.handleIsA();
            this.copyObject(this.parent.editingRowSaved, this.parent.editingRow);
            this.copyObject(this.isARowSaved, this.isARow);
            this.parent.editMode = 'edit';
        },


        /**
         * Dropwdown selection updated
         * @param {Object} col - The column object.
         */
        async selectionUpdated(col) {
            await this.handleIsA();
        },

        /**
         * Handle the isA column
         *
         */
        async handleIsA() {

            if (this.parent.isA) {
                let value = this.parent.editingRow[this.parent.isA.column];	
                if (value != this.oldIsAKey) {
                    this.isARow = {};
                    this.isAColumns = [];
                    this.specialization = this.parent.isA.specializations[value];
                    if (this.specialization) {
                        let ret = await this.get("Table/" + this.specialization + "/" + this.parent.editingRow["id"]);

                        this.isAColumns = this.parent.setupColumns(ret.attributes);
                        await this.parent.loadLookups(this.isAColumns);  


                        let ec = [...this.isAColumns];
                        this.swapIdAndValColumns(ec);

                        if (ret.data.length > 0) {
                            this.isARow = this.parent.rowToObject(ret.data[0], this.isAColumns);
                        } else {
                            this.isARow = await this.createEmptyRow(this.isAColumns);
                        }

                        this.isAColumns = ec.filter(col => this.showColInEdit(col));
                    }
                    this.oldIsAKey = this.parent.editingRow[this.parent.isA.column];
                }
            }
        },
        
        /**
         * delete the current row and adjust the editing row
         */
        async deleteRow() {
            if (await this.parent.deleteRow(this.rows[this.parent.editingRowIndex])) {
                if (this.rows.length == 0) {
                    this.parent.editingRow = await this.createEmptyRow(this.parent.columns);
                } else if (this.parent.editingRowIndex >= this.rows.length) {
                    this.parent.editingRowIndex--;
                    await this.parent.editRow(this.rows[this.parent.editingRowIndex]);
                }
                this.copyObject(this.parent.editingRow, this.parent.editingRowSaved);
                this.$store.formChanged = false;   
            } 
        }
    }
};
</script>

<style scoped>
.form-disabled * {
  pointer-events: none;
  opacity: 0.8;
}
</style>