All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.dirigible.ide-csv.js.editor.js Maven / Gradle / Ivy

/*
 * Copyright (c) 2024 Eclipse Dirigible contributors
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 *
 * SPDX-FileCopyrightText: Eclipse Dirigible contributors
 * SPDX-License-Identifier: EPL-2.0
 */
agGrid.initialiseAgGridWithAngular1(angular);
const csvView = angular.module('csv-editor', ['ideUI', 'ideView', 'ideWorkspace', 'agGrid']);
csvView.controller('CsvViewController', function ($scope, $window, messageHub, workspaceApi, ViewParameters) {
    let contents;
    let manual = false;
    let isFileChanged = false;
    $scope.menuStyle = { 'display': 'none' };
    $scope.menuContext = { // Used for context menu content visibility
        viewport: false,
        row: false,
        column: false
    };
    $scope.errorMessage = 'An unknown error was encountered. Please see console for more information.';
    $scope.state = {
        isBusy: true,
        error: false,
        busyText: 'Loading...',
    };
    let focusedCellIndex = -1;
    let focusedColumnIndex = -1;
    let headerEditMode = false;
    let csvData = {
        columns: [],
        data: []
    };
    $scope.delimiter = ',';
    $scope.gridLoaded = false;
    $scope.search = { text: '' };
    $scope.gridOptions = {
        defaultColDef: {
            sortable: true,
            filter: true,
            resizable: true,
            editable: true,
            flex: 1
        },
        undoRedoCellEditing: true,
        undoRedoCellEditingLimit: 10,
        columnDefs: undefined,
        rowData: undefined,
        rowDragManaged: true,
        suppressMoveWhenRowDragging: true,
        rowDragMultiRow: true,
        animateRows: false,
        rowSelection: 'multiple',
        suppressExcelExport: true,
        suppressPropertyNamesCheck: true, // Because of custom properties
        onColumnResized: function (params) {
            if (params.finished && manual) manual = false;
        },
        onGridReady: function (/*$event*/) {
            if (!$scope.gridLoaded) { // Execute this only once on first grid load
                $scope.gridLoaded = true;
                loadFileContents();
            }
            sizeToFit();
        },
        onCellValueChanged: function (/*$event*/) {
            fileChanged();
        },
        onColumnMoved: function (/*$event*/) {
            fileChanged();
        },
        onRowDragEnd: function (/*$event*/) {
            fileChanged();
        },
        onSortChanged: function (/*$event*/) {
            fileChanged();
        }
    };
    $scope.papaConfig = {
        columnIndex: 0, // Custom property, needed for duplicated column names
        delimitersToGuess: [',', '\t', '|', ';', '#', '~', Papa.RECORD_SEP, Papa.UNIT_SEP],
        header: true,
        skipEmptyLines: true,
        dynamicTyping: true,
        transformHeader: function (headerName) {
            return `${headerName}_${this.columnIndex++}`;
        },
        complete: function () {
            this.columnIndex = 0;
        }
    };
    $scope.rowsCount = 0;

    function setRowsCount(rowsCount) {
        $scope.rowsCount = rowsCount;
    }

    angular.element($window).bind('focus', function () {
        messageHub.setFocusedEditor($scope.dataParameters.file);
        messageHub.setStatusCaret('');
    });

    function sizeToFit() {
        manual = false;
        $scope.gridOptions.api.sizeColumnsToFit();
    }

    function parseContent() {
        let parsedData = Papa.parse(contents, $scope.papaConfig);
        if ($scope.papaConfig.header) {
            if (parsedData.meta.fields.length == 0) {
                parsedData = Papa.parse('"Column"', $scope.papaConfig);
            }
            csvData.data = parsedData.data;
            csvData.columns = parsedData.meta.fields;
        }
        else {
            if (parsedData.data.length == 0) {
                parsedData = Papa.parse('"Column"', $scope.papaConfig);
            }
            csvData.data = generateCorrectCsvData(parsedData.data);

            let columns = [];
            for (const property in csvData.data[0]) {
                columns.push(property)
            }
            csvData.columns = columns;
        }
        if ($scope.papaConfig.delimiter === undefined) {
            $scope.delimiter = parsedData.meta.delimiter;
        }
        setRowsCount(csvData.data.length);
    }

    function loadFileContents() {
        if ($scope.dataParameters.file) {
            workspaceApi.loadContent('', $scope.dataParameters.file).then(function (response) {
                if (response.status === 200) {
                    contents = response.data;
                    parseContent();
                    loadGrid();
                    $scope.$apply(function () {
                        $scope.state.isBusy = false;
                    });
                } else if (response.status === 404) {
                    messageHub.closeEditor($scope.dataParameters.file);
                } else {
                    $scope.$apply(function () {
                        $scope.state.error = true;
                        $scope.errorMessage = 'There was a problem with loading the file';
                        $scope.state.isBusy = false;
                    });
                }
            });
        }
    }

    function fileChanged() {
        isFileChanged = true;
        messageHub.setEditorDirty($scope.dataParameters.file, isFileChanged);
        setRowsCount(csvData.data.length);
    }

    function loadGrid() {
        let columnDefs = csvData.columns.map(
            (name, index) => (
                {
                    headerName: name.split(/\_(?=[^\_]+$)/)[0], // Get the name without the index
                    field: name,
                    cid: index, // Custom property
                    headerComponentParams: {
                        template:
                            `'
                    }
                }
            )
        );
        columnDefs[0].rowDrag = true; // Adding drag handle to first column only
        columnDefs[0].headerCheckboxSelection = true; // Adding checkbox to first column only
        $scope.gridOptions.api.setHeaderHeight(
            (($scope.papaConfig.header) ? undefined : 0)
        );
        $scope.gridOptions.api.setColumnDefs(columnDefs);
        $scope.gridOptions.api.setRowData(csvData.data);
    }

    /*
     * When parsing a csv with PapaParse without header = true,
     * the data we get is structured differently and cannot
     * be used with AG-Grid easily.
     * This function takes the headerless data and transforms it,
     * as if it did have headers.
     */
    function generateCorrectCsvData(rawData) {
        let data = [];
        for (let i = 0; i < rawData.length; i++) {
            let obj = {};
            for (let j = 0; j < rawData[i].length; j++) {
                obj[`c_${j}`] = rawData[i][j];
            }
            data.push(obj);
        }
        return data;
    }

    function saveContents(text) {
        workspaceApi.saveContent('', $scope.dataParameters.file, text).then(function (response) {
            if (response.status === 200) {
                messageHub.announceFileSaved({
                    name: $scope.dataParameters.file.substring($scope.dataParameters.file.lastIndexOf('/') + 1),
                    path: $scope.dataParameters.file.substring($scope.dataParameters.file.indexOf('/', 1)),
                    contentType: $scope.dataParameters.contentType,
                    workspace: $scope.dataParameters.file.substring(1, $scope.dataParameters.file.indexOf('/', 1)),
                });
                messageHub.setStatusMessage(`File '${$scope.dataParameters.file}' saved`);
                messageHub.setEditorDirty($scope.dataParameters.file, false);
                $scope.$apply(function () {
                    $scope.state.isBusy = false;
                    $scope.isFileChanged = false;
                });
            } else {
                messageHub.setStatusError(`Error saving '${$scope.dataParameters.file}'`);
                messageHub.showAlertError('Error while saving the file', 'Please look at the console for more information');
                $scope.$apply(function () {
                    $scope.state.isBusy = false;
                });
            }
        });
    }

    function showColumnInput() {
        let columnInput = $(`#iid_${focusedColumnIndex}`);
        let columnText = $(`#tid_${focusedColumnIndex}`);
        columnInput.val(columnText.text());
        columnInput.css({
            'display': 'inline-block'
        });
        columnText.css({
            'display': 'none'
        });
        columnInput.on('keypress', function (e) {
            if (e.which == 13) {
                hideColumnInput();
            }
        });
        // Unless we do this, we will not be able to use the arrow keys in the input box.
        $scope.gridOptions.navigateToNextHeader = function () { };
        let defs = $scope.gridOptions.api.getColumnDefs();
        defs[focusedColumnIndex].suppressMovable = true;
        $scope.gridOptions.api.setColumnDefs(defs);
    }

    function hideColumnInput() {
        if (headerEditMode) {
            let columnInput = $(`#iid_${focusedColumnIndex}`);
            let newTitle = columnInput.val();
            let columnText = $(`#tid_${focusedColumnIndex}`);
            columnInput.css({
                'display': 'none'
            });
            columnText.css({
                'display': 'inline-block'
            });
            columnInput.off();
            let columnDefs = $scope.gridOptions.api.getColumnDefs();
            for (let i = 0; i < columnDefs.length; i++) {
                if (columnDefs[i].cid == focusedColumnIndex) {
                    columnDefs[i].sortable = true;
                    columnDefs[i].filter = true;
                    if (newTitle != columnText.text()) {
                        columnDefs[i].headerName = newTitle;
                        fileChanged();
                    }
                    break;
                }
            }
            $scope.gridOptions.api.setColumnDefs(columnDefs);
            // Unless we do this, we will not be able to use the arrow keys to navigate the grid.
            $scope.gridOptions.navigateToNextHeader = undefined;
            let defs = $scope.gridOptions.api.getColumnDefs();
            defs[focusedColumnIndex].suppressMovable = false;
            $scope.gridOptions.api.setColumnDefs(defs);
            headerEditMode = false;
        }
    };

    $scope.handleClick = function (event) {
        if (headerEditMode && event.which !== 3) {
            try {
                if (!event.target.className.includes('header-input')) hideColumnInput();
            } catch (error) {
                if (error.toString() != 'Error: Permission denied to access property "className"') { // Firefox bug
                    console.log(error);
                }
            }
        }
    };

    $scope.contextMenuContent = function (element) {
        let items;
        if (
            element.className.includes('ag-header-cell-label') ||
            element.className.includes('ag-header-cell-text') ||
            element.className.includes('ag-cell-label-container')
        ) {
            focusedColumnIndex = parseInt(element.attributes.cid.value);
            items = [{
                id: 'addColumn',
                label: 'Add Column',
                icon: 'sap-icon--add'
            }, {
                id: 'editColumn',
                label: 'Edit Column',
                icon: 'sap-icon--edit'
            }, {
                id: 'deleteColumn',
                label: 'Delete Column',
                divider: true,
                icon: 'sap-icon--delete'
            }];
        } else if (element.className.includes('ag-cell')) {
            focusedCellIndex = $scope.gridOptions.api.getFocusedCell().rowIndex;
            items = [{
                id: 'addRowAbove',
                label: 'Add Row Above',
            }, {
                id: 'addRowBelow',
                label: 'Add Row Below',
            }, {
                id: 'deleteRows',
                label: 'Delete Row(s)',
                divider: true,
                icon: 'sap-icon--delete'
            }];
        } else if (element.className.includes('ag-center-cols-viewport') || element.className.includes('ag-row')) {
            items = [{
                id: 'addRow',
                label: 'Add Row',
                divider: true,
                icon: 'sap-icon--add'
            }];
        } else return;
        return {
            callbackTopic: 'csvEditor.contextmenu',
            hasIcons: true,
            items: items,
        }
    };

    $scope.downloadCsv = function () {
        $scope.search.text = '';
        $scope.gridOptions.api.setQuickFilter(undefined);
        $scope.gridOptions.api.setFilterModel(undefined);
        $scope.gridOptions.api.exportDataAsCsv({
            skipColumnHeaders: (($scope.papaConfig.header) ? false : true),
            columnSeparator: $scope.delimiter
        });
    };

    $scope.save = function (_keySet, event) {
        if (event) event.preventDefault();
        $scope.state.busyText = 'Saving...';
        $scope.state.isBusy = true;
        $scope.search.text = '';
        $scope.gridOptions.api.setQuickFilter(undefined);
        $scope.gridOptions.api.setFilterModel(undefined);
        contents = $scope.gridOptions.api.getDataAsCsv({
            skipColumnHeaders: (($scope.papaConfig.header) ? false : true),
            columnSeparator: $scope.delimiter
        });
        saveContents(contents);
    };

    $scope.searchCsv = function () {
        $scope.gridOptions.api.setQuickFilter($scope.search.text);
    };

    $scope.hasHeader = function () {
        parseContent();
        loadGrid();
    };

    $scope.addRowAbove = function () {
        let row = {};
        let columns = $scope.gridOptions.columnApi.getAllColumns();
        for (let i = 0; i < columns.length; i++) {
            row[columns[i].userProvidedColDef.field] = '';
        }
        csvData.data.splice(focusedCellIndex, 0, row);
        $scope.gridOptions.api.setRowData(csvData.data);
        fileChanged();
    };

    $scope.addRowBelow = function () {
        let row = {};
        let columns = $scope.gridOptions.columnApi.getAllColumns();
        for (let i = 0; i < columns.length; i++) {
            row[columns[i].userProvidedColDef.field] = '';
        }
        csvData.data.splice(focusedCellIndex + 1, 0, row);
        $scope.gridOptions.api.setRowData(csvData.data);
        fileChanged();
    };

    $scope.addRow = function () {
        let row = {};
        let columns = $scope.gridOptions.columnApi.getAllColumns();
        for (let i = 0; i < columns.length; i++) {
            row[columns[i].userProvidedColDef.field] = '';
        }
        csvData.data.push(row);
        $scope.gridOptions.api.setRowData(csvData.data);
        fileChanged();
    };

    $scope.deleteRow = function () {
        let rows = $scope.gridOptions.api.getSelectedNodes();
        let indexes = [];
        for (let i = 0; i < rows.length; i++) {
            indexes.push(rows[i].rowIndex);
        }
        if (!indexes.includes(focusedCellIndex)) {
            indexes.push(focusedCellIndex);
        }
        indexes.sort(function (a, b) { return a - b; });
        for (let i = indexes.length - 1; i >= 0; i--) {
            csvData.data.splice(indexes[i], 1);
        }
        $scope.gridOptions.api.setRowData(csvData.data);
        fileChanged();
    };

    $scope.addColumn = function () {
        let columnDefs = $scope.gridOptions.api.getColumnDefs();
        let column = {
            headerName: 'New column',
            field: `New column_${columnDefs.length}`,
            cid: columnDefs.length, // Custom property
            headerComponentParams: {
                template:
                    `'
            }
        };
        columnDefs.push(column);
        $scope.gridOptions.api.setColumnDefs(columnDefs);
        fileChanged();
    };

    $scope.editColumn = function () {
        headerEditMode = true;
        let columnDefs = $scope.gridOptions.api.getColumnDefs();
        for (let i = 0; i < columnDefs.length; i++) {
            if (columnDefs[i].cid == focusedColumnIndex) {
                columnDefs[i].sortable = false;
                columnDefs[i].filter = false;
                break;
            }
        }
        $scope.gridOptions.api.setColumnDefs(columnDefs);
        showColumnInput();
    };

    $scope.deleteColumn = function () {
        let columnDefs = $scope.gridOptions.api.getColumnDefs();
        let field = '';
        for (let i = 0; i < columnDefs.length; i++) {
            if (columnDefs[i].cid == focusedColumnIndex) {
                field = columnDefs[i].field;
                columnDefs.splice(i, 1);
                break;
            }
        }
        for (let i = 0; i < csvData.data.length; i++) {
            delete csvData.data[i][field];
        }
        $scope.gridOptions.api.setRowData(csvData.data);
        $scope.gridOptions.api.setColumnDefs(columnDefs);
        fileChanged();
    };

    messageHub.onEditorFocusGain(function (msg) {
        if (msg.resourcePath === $scope.dataParameters.file) messageHub.setStatusCaret('');
    });

    messageHub.onEditorReloadParameters(
        function (event) {
            $scope.$apply(() => {
                if (event.resourcePath === $scope.dataParameters.file) {
                    $scope.dataParameters = ViewParameters.get();
                }
            });
        }
    );

    messageHub.onDidReceiveMessage(
        'editor.file.save.all',
        function () {
            if (!$scope.state.error && isFileChanged) {
                let extension = JSON.stringify($scope.extension, null, 4);
                if (contents !== extension) {
                    $scope.save();
                }
            }
        },
        true,
    );

    messageHub.onDidReceiveMessage(
        'editor.file.save',
        function (msg) {
            if (!$scope.state.error && isFileChanged) {
                let file = msg.data && typeof msg.data === 'object' && msg.data.file;
                if (file && file === $scope.dataParameters.file) $scope.save();
            }
        },
        true,
    );

    messageHub.onDidReceiveMessage(
        'csvEditor.contextmenu',
        function (msg) {
            if (msg.data.itemId === 'addColumn') {
                $scope.addColumn();
            } else if (msg.data.itemId === 'editColumn') {
                $scope.editColumn();
            } else if (msg.data.itemId === 'deleteColumn') {
                $scope.deleteColumn();
            } else if (msg.data.itemId === 'addRowAbove') {
                $scope.addRowAbove();
            } else if (msg.data.itemId === 'addRowBelow') {
                $scope.addRowBelow();
            } else if (msg.data.itemId === 'deleteRows') {
                $scope.deleteRow();
            } else if (msg.data.itemId === 'addRow') {
                $scope.addRow();
            }
        }
    );

    $scope.dataParameters = ViewParameters.get();
    if (!$scope.dataParameters.hasOwnProperty('file')) {
        $scope.state.error = true;
        $scope.errorMessage = "The 'file' data parameter is missing.";
    } else if (!$scope.dataParameters.hasOwnProperty('contentType')) {
        $scope.state.error = true;
        $scope.errorMessage = "The 'contentType' data parameter is missing.";
    } else {
        if ($scope.dataParameters.hasOwnProperty('header')) $scope.papaConfig.header = $scope.dataParameters.header;
        if ($scope.dataParameters.hasOwnProperty('quotechar')) $scope.papaConfig.quoteChar = $scope.dataParameters.quotechar;
        if ($scope.dataParameters.hasOwnProperty('delimiter')) {
            $scope.papaConfig.delimiter = $scope.dataParameters.delimiter;
            $scope.delimiter = $scope.dataParameters.delimiter;
        }
    }
});




© 2015 - 2025 Weber Informatics LLC | Privacy Policy