import { ComponentModel, objectParameter, structure } from './cykLang' import loglevel from 'loglevel' const logger = loglevel.getLogger('cykTableEdit.ts') logger.setLevel('debug') import { buildQColumns, calComputedCols, callCommandFunction, Command, loadRecords } from './cykTableView'; import { parseOptions } from './cykTableOptions'; import { computed, Ref, ref } from 'vue'; import { data2boolean, Expression, FunctionData, ObjectData, PrimitiveData, Tag, Variable, variable2json, Variables, XmlError } from '@cyklang/core'; import { AlertException } from './cykRun'; import { CloseFormInstructionType } from './WindowManager'; import { DirtyManager, VariableReact } from './cykReact'; /** * */ export class RowObject { objectData: ObjectData occupied: boolean = false dirtyManager: DirtyManager | undefined saveEdit: FunctionData | undefined cancelEdit: FunctionData | undefined deleteFunction: FunctionData | undefined editDialog: FunctionData | undefined insertable: boolean | undefined appendable: boolean | undefined newRow: FunctionData | undefined inputNewRow: FunctionData | undefined mapComponents: Map | undefined constructor(objectData: ObjectData, isNewRecord?: boolean) { this.objectData = objectData this.dirtyManager = new DirtyManager(objectData, isNewRecord) } /** * * @returns */ async do_save(): Promise { if (!this.saveEdit) return true try { this.occupied = true const params = new Variables() const result_data = await this.saveEdit.callFunction(params, structure.scope) if (result_data && result_data.type.isPrimitive()) { const result = Boolean((result_data as PrimitiveData).value) if (result) { this.dirtyManager?.resetDirty() return true } } return false } catch (err) { AlertException(new XmlError(String(err), this.objectData.tag || new Tag(''))) throw err } finally { this.occupied = false } } async do_delete(): Promise { if (!this.deleteFunction) return false try { this.occupied = true const params = new Variables() const result_data = await this.deleteFunction.callFunction(params, structure.scope) if (result_data && result_data.type.isPrimitive()) { const result = Boolean((result_data as PrimitiveData).value) if (result) { this.dirtyManager?.resetDirty() return true } } return false } catch (err) { AlertException(new XmlError(String(err), this.objectData.tag || new Tag(''))) throw err } finally { this.occupied = false } } } export function useCykTableEdit(props: { componentArg: ComponentModel | undefined }) { const selection: Ref> = ref([]); if ( props.componentArg === undefined || props.componentArg.model === undefined || props.componentArg.objectData === undefined ) { throw Error('QTable componentArg undefined'); } if ( props.componentArg.model.data === null || props.componentArg.model.data === undefined ) throw ' model.data undefined'; if (props.componentArg.model.data.type.isPrimitive()) throw ' model must be an object'; const isLoading = ref(true); const filter = ref(''); const rowKeyName = ref(''); const optSelected: Ref = ref(); const qColumns: any[] = [] const rowObjects: Ref = ref([]); const commands: Command[] = []; const tableCommands: Command[] = []; const tableCmds: ObjectData[] = []; const queryFunct: Ref = ref(); //------------------------------------------------------------------- // function row_key_fn // returns row key const row_key_fn = (row: RowObject) => { let result = ''; const keyElts = rowKeyName.value.split(';') for (let ind = 0; ind < keyElts.length; ind++) { if (ind > 0) result += '\t' result += row.objectData.variables.getString(keyElts[ind]) } return result; }; const setOptSelected = (row: ObjectData | undefined, cmdName: string) => { if (!optSelected || optSelected.value === undefined) return; let varSelectedRow = optSelected.value?.variables.getVariable('row'); if (varSelectedRow === undefined) { varSelectedRow = optSelected.value?.variables.addVariable( 'row', optSelected.value?.scope.structure.objectDataType ); } if (row) { varSelectedRow.data = row; } let varSelectedCommand = optSelected.value?.variables.getVariable('command'); if (varSelectedCommand === undefined) { varSelectedCommand = optSelected.value?.variables.addVariable( 'command', optSelected.value.scope.structure.stringDataType ); } varSelectedCommand.data = new PrimitiveData( varSelectedCommand.dataType, cmdName ); }; /** * * @param row * @param cmdObject */ async function clickCmdButton( row: ObjectData | undefined, cmdObject: ObjectData ) { try { // const scope = (props.componentArg?.model?.data as ObjectData)?.scope || optSelected.value?.scope const scope = cmdObject.scope const cmdName = objectParameter(cmdObject, "name", "") setOptSelected(row, cmdName) const cmdAction = objectParameter(cmdObject, "action", "") let cmdOnClick = cmdObject.variables.getFunction("onclick") if (cmdAction && cmdAction !== '__reload__' && !cmdOnClick) { const express = new Expression(scope) const dataAction = await express.evaluate(cmdAction) if (!(dataAction instanceof FunctionData)) { throw 'action function not found: ' + cmdAction } cmdOnClick = dataAction } if (cmdOnClick) { const params = new Variables() if (row) { const parametersDefinition = cmdOnClick.parametersDefinition if (!parametersDefinition) throw 'function onclick() or action should have the selected row as argument' const parameterDefinition = parametersDefinition.array[0]; if (!parameterDefinition.dataType.isObject()) throw 'function onclick() or action first argument should be an object' const varParam = params.addVariable(parameterDefinition.name, parameterDefinition.dataType) varParam.data = row } const dataReturned = await cmdOnClick.callFunction(params, scope) const reload = data2boolean(dataReturned) if (reload) { // context.emit('reload') } } else if (cmdAction === '__reload__') { // context.emit('reload') } else { // close this form and return row and command selected const closeformInstType = await optSelected.value?.scope.lookupInstructionType( 'closeform' ) as CloseFormInstructionType; if (closeformInstType === undefined) throw 'closeform instruction type not found'; await closeformInstType.windowManager.closeForm(); } } catch (err) { AlertException(err) } } const pagine = ref({ descending: false, page: 1, rowsPerPage: 15, }) if (props.componentArg.objectData.tag.attributes.ROWS_PER_PAGE) { pagine.value.rowsPerPage = Number(props.componentArg.objectData.tag.attributes.ROWS_PER_PAGE) logger.debug('attribute ROWS_PER_PAGE : ' + pagine.value.rowsPerPage) } const LS_ROWS_PER_PAGE = 'cyk:ROWS-PER-PAGE' const item = localStorage.getItem(LS_ROWS_PER_PAGE) if (item) { pagine.value.rowsPerPage = Number(item) } const pagination = computed({ get() { // logger.debug('pagination.get: ', pagine.value) return pagine.value }, set(nval: any) { // logger.debug('pagination.set: ', nval ) pagine.value.page = nval.page pagine.value.rowsPerPage = nval.rowsPerPage localStorage.setItem(LS_ROWS_PER_PAGE, String(pagine.value.rowsPerPage)) } }); //------------------------------------------------------------------- // options validation const optionsObject = (props.componentArg?.model?.data as ObjectData); const { // eslint-disable-next-line @typescript-eslint/no-unused-vars dbResult, optEntity, optCommands, optColumns, optTableCommands, optCmds, onloadFunct, optTableRowModel, optRenumberRows, optStyle } = parseOptions(optionsObject, optSelected, queryFunct); /** * function newRowObject * @param objectData */ const newRowObject = async (objectData: ObjectData, isNewRecord?: boolean): Promise => { try { const rowObject = new RowObject(objectData, isNewRecord) const params = new Variables() const varRow = new Variable(structure.objectDataType, objectData) if (!optTableRowModel) throw 'optTableRowModel undefined' const parameterDefinition = optTableRowModel.parametersDefinition?.getByOrder(0) if (!parameterDefinition) throw 'table_row_model function should take an object parameter' if (!parameterDefinition.dataType.isObject()) throw `table_row_model function parameter type should be object but is ${parameterDefinition.dataType.name}` params.push(parameterDefinition.name, varRow) const tableRowModel = (await optTableRowModel.callFunction(params, structure.scope)) as ObjectData const save_edit = tableRowModel.variables.getFunction('save_edit') if (save_edit) { rowObject.saveEdit = save_edit } const cancel_edit = tableRowModel.variables.getFunction('cancel_edit') if (cancel_edit) { rowObject.cancelEdit = cancel_edit } const deleteFunction = tableRowModel.variables.getFunction('delete') if (deleteFunction) { let deletable = true const varDeletable = tableRowModel.variables.getVariable('deletable') if (varDeletable) { deletable = varDeletable.getBoolean() || false } if (rowObject.dirtyManager?.isNewRecord) { deletable = false } if (deletable) { rowObject.deleteFunction = deleteFunction } } const editDialog = tableRowModel.variables.getFunction('edit_dialog') if (editDialog) { let editable = true const varEditable = tableRowModel.variables.getVariable('editable') if (varEditable) { editable = varEditable.getBoolean() || false } if (editable) { rowObject.editDialog = editDialog } } const newRow = tableRowModel.variables.getFunction('new_row') if (newRow) { rowObject.newRow = newRow const varInsertable = tableRowModel.variables.getVariable('insertable') if (varInsertable) { const insertable = varInsertable.getBoolean() rowObject.insertable = insertable } const varAppendable = tableRowModel.variables.getVariable('appendable') if (varAppendable) { const appendable = varAppendable.getBoolean() rowObject.appendable = appendable } const inputNewRow = tableRowModel.variables.getFunction('input_new_row') if (inputNewRow) { rowObject.inputNewRow = inputNewRow } } const mapComponents = new Map() rowObject.mapComponents = mapComponents const variableReact = new VariableReact() const dataColumns = tableRowModel.variables.getData('columns') if (!dataColumns || !dataColumns.type.isObject()) throw 'dataColumns undefined or is not object' const variables = (dataColumns as ObjectData).variables await variables.forEachAsync(async ({ variable }) => { if (variable.data !== undefined && variable.data !== null && variable.data.type.isObject()) { const objectData = variable.data as ObjectData try { let model let modelString = '' let columnName = '' if (objectData.tag === undefined) { // logger.debug('buildComponentArray componentArg', componentArg) // logger.debug('buildComponentArray objectData.tag undefined', objectData) } if (objectData.tag.attributes.MODEL !== undefined) { modelString = objectData.tag.attributes.MODEL } const varModel = objectData.variables.getVariable('model') if (varModel !== undefined && varModel.getString() !== undefined) { modelString = varModel.getString() } if (modelString !== undefined) { const parts = modelString.split('.') columnName = parts[parts.length - 1] const express = new Expression(objectData.scope); // logger.debug('buildComponentArray modelString: ' + modelString) model = await express.LValue(modelString); if (!model) throw `${modelString} undefined` } const component = new ComponentModel(objectData, model, variableReact, rowObject.dirtyManager); mapComponents.set(columnName, component) } catch (err) { AlertException(new XmlError(String(err), objectData.tag)) } } }) return rowObject } catch (err) { throw err } } //------------------------------------------------------------------- // do_loading //------------------------------------------------------------------- const do_loading = async () => { // logger.debug('loading...'); try { //------------------------------------------------------------------- // buildQColumns const { dbColumns, columns } = await buildQColumns( 'dialog', dbResult, optEntity, optColumns, optionsObject, optCommands, rowKeyName, qColumns, ); //------------------------------------------------------------------- // q-table rows building // loadRecords(rowObjects, dbResult, optEntity, optionsObject); rowObjects.value.length = 0; const dbRecordset = dbResult.variables.getData(optEntity) as ObjectData; if (dbRecordset === null) { const xmlError = new XmlError('optEntity ' + optEntity + ' not found in query result', optionsObject.tag) AlertException(xmlError) } else { for (let ind = 0; ind < dbRecordset.variables.length(); ind++) { const variable = dbRecordset.variables.at(ind)?.variable if (!variable) continue const data = variable.data; if (data === null || data === undefined) { const excep = new XmlError('recordset with an empty line', optionsObject.tag); AlertException(excep) throw excep } const record = data as ObjectData const rowObject = await newRowObject(record) rowObjects.value.push(rowObject) }; } if (optTableCommands) { optTableCommands.variables.forEach(({ variable }) => { const data = variable.data if (data && data !== null && data instanceof ObjectData) { const visible = objectParameter(data, "visible", true) if (visible) { tableCmds.push(data) } } }) } isLoading.value = false; // logger.debug('QTable.setup selection:', selection.value) } catch (err) { logger.error(err); } }; const onload = async () => { if (props.componentArg?.objectData) try { if (onloadFunct) { await onloadFunct.callFunction(new Variables(), props.componentArg.objectData.scope) } } catch (err) { AlertException(new XmlError(String(err), props.componentArg.objectData.tag)) } } return { isLoading, newRowObject, rowObjects, row_key_fn, qColumns, filter, selection, pagination, tableCmds, optCmds, clickCmdButton, setOptSelected, optionsObject, optSelected, onload, optTableRowModel, optRenumberRows, optStyle, do_loading }; }