Question regarding registering commands to control +Z and undo and redo buttons

Posted by: georgeg on 31 July 2025, 6:12 pm EST

    • Post Options:
    • Link

    Posted 31 July 2025, 6:12 pm EST - Updated 31 July 2025, 6:27 pm EST

    How would I connect my custom command for my quick math window to the undo and redo controls in addition to cntrol+Z and control+Y buttons.

    In my initialization function that initializes the SpreadJS and Designer controls:

     //Configuration for added features goes here:
     var config = GC.Spread.Sheets.Designer.DefaultConfig;
     config.commandMap = config.commandMap || {};
     config.commandMap["applyMath"] = applyMathCommand();
     config.contextMenu = config.contextMenu || [];
     config.contextMenu.push("applyMath");
    
     // Initialize a workbook
     // Initialize the SpreadJS Designer inside the HTML element with ID 'spreadJsSpreadsheet' using the specified configuration,
     // then retrieve the associated workbook instance for further interaction or manipulation.
     //var spread = new GC.Spread.Sheets.Workbook(document.getElementById('spreadJsSpreadsheet'), { sheetCount: 1 });
     var designer = new GC.Spread.Sheets.Designer.Designer(document.getElementById('spreadJsSpreadsheet'), config);
     var spread = designer.getWorkbook();
     spread.options.tabEditable = false;
     spread.options.newTabVisible = false;
    
     spread.bind(GC.Spread.Sheets.Events.SelectionChanged, function () {
         designer.refresh(); // Ensures command is re-evaluated
     });

    then in my applyMathCommand() function I have:

    //------------------------------------------------------------------------------
    // applyMathCommand()
    // Defines a custom SpreadJS Designer command named "Apply Math Operation".
    // This command becomes available in the context menu when right-clicking on
    // the viewport area. It checks if the current selection contains any non-empty
    // cells and enables the command accordingly.
    //
    // When executed, it applies a simple math operation (incrementing numeric cells
    // by 1) and supports undo by saving the original cell values to restore them
    // if the user triggers Undo (e.g., Ctrl+Z).
    //
    // Returns:
    //   A command object compatible with GC.Spread.Sheets.Designer.commandMap.
    //
    // Usage:
    //   config.commandMap["applyMath"] = applyMathCommand();
    //------------------------------------------------------------------------------
    function applyMathCommand() {
        return {
            title: "Apply Math Operation",
            text: "Apply Math Operation",
            iconClass: "custom-icon",
            commandName: "applyMath",
            visibleContext: "ClickViewport",
            canUndo: true,
            isEnabled: function (context) {
                var sheet = context.getWorkbook().getActiveSheet();
                var selections = sheet.getSelections();
                if (!selections || selections.length === 0) return false;
    
                for (var s = 0; s < selections.length; s++) {
                    var sel = selections[s];
                    for (var r = sel.row; r < sel.row + sel.rowCount; r++) {
                        for (var c = sel.col; c < sel.col + sel.colCount; c++) {
                            let val = sheet.getValue(r, c);
                            if (val !== null && val !== "") {
                                return true;
                            }
                        }
                    }
                }
                return false;
            },
            execute: function (context, options) {
                var sheet = context.getWorkbook().getActiveSheet();
    
                // Double-check that selection has data before proceeding
                if (!selectionHasData(sheet)) {
                    console.warn("Selected cells are blank. Command aborted.");
                    return;
                }
    
                // Open the popup window (assuming it's already initialized as a Kendo Window)
                let mathOps = $("#math-ops-pop-up-window").data("kendoWindow");
                if (mathOps) {
                    mathOps.center();
                    mathOps.open();
                } else {
                    console.warn("Popup window 'math-ops-pop-up-window' not found or not initialized.");
                }
            }
        };
    }//end applyMathCommmand().
    
    //HELPER FUNCTION FOR ABOVE USED IN EXECUTE:
    // Checks if the current selection in the worksheet contains any non-empty cell values.
    // This function is useful for validating whether a command should proceed,
    // ensuring there's actual data to operate on (e.g. before applying math operations).
    // 
    // It iterates over all selected ranges in the sheet, then over each cell within those ranges,
    // checking if any cell contains a value that is not null and not an empty string.
    // 
    // Returns true if at least one cell has usable data; false otherwise.
    function selectionHasData(sheet) {
        var selections = sheet.getSelections(); // Get all selected ranges
        if (!selections || selections.length === 0) return false; // Return false if nothing is selected
    
        for (var s = 0; s < selections.length; s++) {
            var sel = selections[s]; // Current selection range
            for (var r = sel.row; r < sel.row + sel.rowCount; r++) {
                for (var c = sel.col; c < sel.col + sel.colCount; c++) {
                    let val = sheet.getValue(r, c); // Get cell value
                    if (val !== null && val !== "") { // Check if value is not null or empty
                        return true; // Found valid data, return true
                    }
                }
            }
        }
        return false; // No data found in selection
    }
    

    Now I think there is a way to connect to the listeners or something for the redo or undo buttons in the Designer and the Cntrol+Z command. I tried CoPilot on this and it took me down a terrible path where if froze up the selection box for the SpreadJS…Oof.

    how do I link the custom command I have working in the context menu to trigger or have the undo, redo, and Control+Z to responed?

  • Posted 1 August 2025, 5:08 am EST - Updated 1 August 2025, 5:14 am EST

    Hi,

    Based on your description and the original implementation, we understand that the goal is to:

    • Execute a math operation (eg, incrementing numeric cells by 1),
    • Register the action as undoable,
    • Ensure that Ctrl+Z/Ctrl+Y and the Designer’s Undo/Redo buttons behave as expected.

    To fulfill this, we recommend separating the UI trigger (Designer command) from the logic (SpreadJS Command). The Designer command should call a SpreadJS command that:

    • Uses Commands. startTransaction and Commands.endTransaction to wrap changes and include the actions in the Undo/Redo context.
    • Performs a mathematical operation.

    Please refer to the following code snippet that demonstrates the above algorithm:

    execute: function (context, options) {
        const spread = context.getWorkbook();
        const sheet = spread.getActiveSheet();
        if (!selectionHasData(sheet)) {
            console.warn("Selected cells are blank. Command aborted.");
            return;
        }
        spread.commandManager().execute({cmd: "applyMathSpread", sheetName: sheet.name(), selections: sheet.getSelections()});
    }
    function addCommand(spread) {
        const command = {
            canUndo: true,
            execute: function (context, options, isUndo) {
                const Commands = GC.Spread.Sheets.Commands;
                options.cmd = "applyMathSpread";
                if (isUndo) {
                    Commands.undoTransaction(context, options);
                    return true;
                } else {
                    const sheet = context.getSheetFromName(options.sheetName);
                    const selections = options.selections;
                    Commands.startTransaction(context, options);
                    sheet.suspendPaint();
                    sheet.suspendEvent();
                    sheet.suspendCalcService();
                    for (let s = 0; s < selections.length; s++) {
                        const sel = selections[s];
                        const shArray = sheet.getArray(sel.row, sel.col, sel.rowCount, sel.colCount);
                        let isNumber = false;
                        for(let r = 0; r < sel.rowCount; r++) {
                            for(let c = 0; c < sel.colCount; c++) {
                                let val = shArray[r][c];
                                if(typeof val === "number" && !isNaN(val)) {
                                    shArray[r][c] = val + 1;
                                    if(!isNumber) isNumber = true;
                                }
                            }
                        }
                        if(isNumber) sheet.setArray(sel.row, sel.col, shArray);
                    }
                    sheet.resumeCalcService();
                    sheet.resumeEvent();
                    sheet.resumePaint();
                    Commands.endTransaction(context, options);
                    return true;
                }
            }
        };
        spread.commandManager().register("applyMathSpread", command);
    }

    You can further refer to the attached sample that uses the above code snippets and registers the math command under an Undo/Redo context (see below).

    Please let us know if you encounter any further issues or require additional assistance.

    Kind Regards,

    Chirag Gupta

    Attachment: https://jscodemine.mescius.io/share/lZ24ZEcF40WyWw2BWGDvRg/?defaultOpen={"OpenedFileName"%3A["%2Findex.html"%2C"%2Fapp.js"]%2C"ActiveFile"%3A"%2Fapp.js"}

    References:

    Working:

  • Posted 1 August 2025, 11:47 am EST - Updated 1 August 2025, 12:01 pm EST

    Ok this is tricky because my solution opens a kendoWindow to do select from three math operations all the execute handler in my: applyMathCommand(); does is check if the sheet has data or if the selected cells have values then opens up a kendoWindow where the user can select their math operation:

            execute: function (context, options) {
                var sheet = context.getWorkbook().getActiveSheet();
    
                // Double-check that selection has data before proceeding
                if (!selectionHasData(sheet)) {
                    console.warn("Selected cells are blank. Command aborted.");
                    return;
                }
    
                // Open the popup window (assuming it's already initialized as a Kendo Window)<<-- Here.
                let mathOps = $("#math-ops-pop-up-window").data("kendoWindow");
                if (mathOps) {
                    mathOps.center();
                    mathOps.open();
                } else {
                    console.warn("Popup window 'math-ops-pop-up-window' not found or not initialized.");
                }
            }

    If the sheet is not empty, and the cells selected have values then—> let mathOps = $(“#math-ops-pop-up-window”).data(“kendoWindow”);

    When the user selects the context menu option…

    …the above execute code does its thing.



    …and the kendoWindow (pop-up) appears (figure 2.)…And the user:

    1. Selects what math op they want…
    2. types in the amount in the kendoNumericTextBox
    3. optionally selects whether the numeric amount is a percent value of the operand being operated upon.
    4. clicks Apply if happy.( the Cancel button makes the window disappear).

    Once the user clicks Apply the kendoButton click event handler calls this function:

    // Executes a mathematical operation (Add, Subtract, Multiply) on selected cells
    // in the active worksheet using a numeric input provided by the user.
    //
    // Retrieves the SpreadJS Designer and workbook instance.
    // Validates the user's input to ensure it is a valid number.
    // Determines whether the input should be treated as a percentage.
    // Applies the selected operation to numeric cells in the active selection.
    // Displays alerts if non-numeric values are encountered.
    // Resets UI controls and closes the math operation popup on completion.
    function performMathOp() {
        var designer = GC.Spread.Sheets.Designer.findControl(document.getElementById('spreadJsSpreadsheet'));
        var spread = designer.getWorkbook();
        var sheet = spread.getActiveSheet();
        var number = $("#numberInput").data("kendoNumericTextBox").value();
    
        // Detect if percentage toggle is active -- GREY not selected and BLUE == selected.
        var isPercent = _view.get("percentSelected");
    
    
        
    
        //Validate Input:
        // Check if value is null, undefined, or not a number
        if (number === null || number === undefined || isNaN(number)) {
    
            pksUI.spreadJSAlertDialog("Validation Error:", "<span style='color: red;'>Please enter a valid number before applying the operation.</span>");
            // Reset controls to default state
            console.error("In performMathOp: Mathamatical opperation attempted on a non-numeric value.");
            JL("jsLogger").error("In performMathOp: Mathamatical opperation attempted on a non-numeric value.");
            resetControls();
    
            return; // Exit early to prevent execution
        }
    
        //Select the arithmetic operation from the selected radio button.
        var selectedOp = document.querySelector('input[name="math_op"]:checked');
        var operation = selectedOp ? selectedOp.value : null;
        var retVal = true;
        var selectedRanges = spread.getActiveSheet().getSelections();
    
         selectedRanges.forEach(function (range) {
            for (var r = range.row; r < range.row + range.rowCount; r++) {
                for (var c = range.col; c < range.col + range.colCount; c++) {
                    var val = sheet.getValue(r, c);
                    if (typeof val === "number") {
    
                        //If the percent toggle button in true/BLUE then convert.
                        //Ergo...modify to make the number in the input a percent value 30 is percent then it needs to be converted to .30.
                        var modifier = isPercent ? val * (number / 100) : number;//ternary operator.
    
                        //Now do operation:
                        if (operation === "Multiply") {
                            sheet.setValue(r, c, val * modifier);
                        } else if (operation === "Add") {
                            sheet.setValue(r, c, val + modifier);
                        } else if (operation === "Subtract") {
                            sheet.setValue(r, c, val - modifier);
                        }
                    } else if (val !== null && val !== undefined) {
    
                        var columnName = sheet.getValue(0, c); // Column name from first row
    
                        pksUI.spreadJSAlertDialog(
                            "Validation Error:",
                            `<span style='color: red;'> Cannot perform arithmetic on a non-numeric value at  row ${r + 1} of column ${c + 1}: <strong><i>${columnName}</i></strong>.</span>`
                        );
                        // Reset controls to default state
                        resetControls();
                        return;
                    }
                }
            }
    
        });
    
        let mathOps = $("#math-ops-pop-up-window").data("kendoWindow");
        mathOps.close();
    
        return;
    }

    Seems like its not so easy as registering the commands separately. How can I salvage this? The kendo Click action actually calls this function:

        //Kendo related controls:
        $("#Apply").kendoButton({
            icon: "check-circle",
            themeColor: "primary",
            click: function () {
                performMathOp();
    
            }
        });

    Big challenge I have is this is all tangled up (tightly coupled with the Kendo controls. Let me see if I can’t peice this together from what you have, and in the mean time if there is a simple solution to this please share it.

    Thanks!

    -George

  • Posted 1 August 2025, 12:25 pm EST - Updated 1 August 2025, 12:30 pm EST

    Also… like I said I wasn’t clear enough to reveal the intermediate step of the pop-up window (see above)…so another aproach is necessary…because selecting the cells then applying a static addition operation or any other operation directly isn’t what I am doing. This:

    won’t work because the user has to set up his arithmetic operation in a pop-up window. It doesn’t trigger the operation directly.

  • Posted 2 August 2025, 1:57 am EST - Updated 2 August 2025, 2:02 am EST

    Hi,

    We understand that the popup window is triggered when the math operation is selected from the SpreadJS context menu, and the actual mathematical operation to be performed on the selection is chosen from the popup. The previously shared solution was a basic prototype of a single increment operation. But as per your request, we have implemented a multi-operation command executor.

    Please refer to the attached sample that demonstrates the working of the SpreadJS command manager with a popup command integrating undo/redo context (see below).

    Please feel free to reach out if you encounter any further issues.

    Kind Regards,

    Chirag Gupta

    Attachment: https://jscodemine.mescius.io/share/lZ24ZEcF40WyWw2BWGDvRg/?defaultOpen={"OpenedFileName"%3A["%2Findex.html"%2C"%2Fapp.js"%2C"%2Fstyles.css"]%2C"ActiveFile"%3A"%2Fapp.js"}

    Working:

Need extra support?

Upgrade your support plan and get personal unlimited phone support with our customer engagement team

Learn More

Forum Channels