How can I disable inline edit of a cell (field) for a subset of rows; but enable edit for the rest?

How can I disable inline edit of a cell (field) for a subset of rows; but enable edit for the rest?

rprobinsonrprobinson Posts: 14Questions: 3Answers: 0

I'm using the inline edit feature that allows my users to update a specific cell (Quantity) in each row displayed. The rows are sorted by date and will have days in the past present (up to 6 days) and 50 days into the future. I've inline edit is working correctly but I need to make a change for a new requirement.

The requirement is the user can change the Quantity for any row in the future, but not today or the past.

Best solution would prevent the user from entering a Quantity value for rows in the past and today; if that's not possible, next solution could allow the edit but display a "Can't update values for today or past" and refresh the Quantity with the original value.

Unclear how best to proceed - please advise.

I'm using Datatable 1.10.15 (along with all the plugins associated with the release) and Editor 1.6.3

This question has an accepted answers - jump to answer

Answers

  • allanallan Posts: 61,864Questions: 1Answers: 10,136 Site admin

    Best solution would prevent the user from entering a Quantity value for rows in the past and today

    The key here is the event handler where you call the inline() method. Let's take this example - it uses:

        $('#example').on( 'click', 'tbody td:not(:first-child)', function (e) {
            editor.inline( this );
        } );
    

    But say I don't want to allow inline editing of any row where the salary is > 500'000:

        $('#example').on( 'click', 'tbody td:not(:first-child)', function (e) {
            var rowData = table.row( this.parentNode ).data();
    
            if ( rowData.salary <= 500000 ) {
                editor.inline( this );
            }
        } );
    

    i.e. it simply doesn't call inline() if my condition isn't met.

    Regards,
    Allan

  • rprobinsonrprobinson Posts: 14Questions: 3Answers: 0

    No luck after adding the click event - the cell can be edited still for today and past rows. Here's what I added:

                // From Allan on how to prevent edit of row in past or today
                $('#example').on('click', 'tbody td:not(:first-child)', function (e) {
                    var rowData = table.row(this.parentNode).data();
                    var selectedRowIdx = parseInt(rowData[planningHeadings.displayRowIdx]);
                    if (selectedRowIdx > 0) {  // this indicates rows in the future
                        debugger;                    // got here for rows with future dates
                        editor.inline(this);
                    } else {
                        debugger;                   // got here for rows in past or today
                    }
    
                });
    

    Your thoughts on what to try next?

    Note - the current code I have was not using the table click event at all. When I create the editor instance, I specify what columns (fields) can be edited or read only. The editor was handling the fields correctly (edit/can't edit). Unclear if I now have a mix of approaches (table click event code I added with editor events). Below is the code for creating editor instance.

        // At runtime, determine what columns we have and set fields for read only or edit
    
        function setupEditor(dataColumnHeaders, planningHeadings) {
            if (userAuthorization == USER_AUTHORIZATION.UPDATE) {
                // debugger;
                // Build the fields needed by the editor
                var fields = [];
    
                for (var i = 0; i < dataColumnHeaders.length; i++) {
                    switch (i) {
                        case planningHeadings.capacityColumnIdx:  <-- this is the cell they can change, I refered to this as a Quantity in my initial question description.
                            fields[i] = {
                                label: planningHeadings.columnHdrs[i].title,
                                // name: i.toString()   change recommended by allan
                                name: i,
                                attr: {
                                    type: 'number'
                                }
                            }
                            break;
    
                        default:
                            fields[i] = {
                                label: planningHeadings.columnHdrs[i].title,
                                // name: i.toString()   change recommended by allan
                                name: i,
                                type: "readonly"
                            }
                    }
    
                }
                editor = new $.fn.dataTable.Editor({
                    // ajax: "../php/staff.php",
                    table: "#example",
                    fields: fields,
                    idSrc: "0"
                });
    
                editor
                    .on('open', function (e, mode, action) {
                        if (mode === 'main') {
                            table.keys.disable();
                        }
                    })
                    .on('close', function () {
                        table.keys.enable();
                    });
    
                editor.on('postEdit', function (e, json, data) {
                    applyData(e, table, json.data);
    
                });
    
                editor.on('preSubmit', function (e, o, action) {
                    var val = editor.field(planningHeadings.capacityColumnIdx).val();
                    var editField = this.field(planningHeadings.capacityColumnIdx);
    
                    // debugger;
                    if (val.match(/[^\d]/) || val == "") {
                        editField.error("Numbers only");
                        return false;
                    }
                    else if (val < 0) {
                        editField.error("Positive numbers only");
                        return false;
                    }
                });
            }
        }
    
  • rprobinsonrprobinson Posts: 14Questions: 3Answers: 0

    Other observation, I noticed with the click event added, I get different behavior for the field. Primarily when entering data - before I could select a field and start typing. Now it appends to the end the characters being entered. Which probably indicates the mix approaches is having an unwanted change in behavior.

  • allanallan Posts: 61,864Questions: 1Answers: 10,136 Site admin

    You say you added the click event - what were you using before to activate the inline editing?

    Thanks,
    Allan

  • rprobinsonrprobinson Posts: 14Questions: 3Answers: 0

    I'm assuming the option for keys on the table create to have an editor is the magic. I previously posted the logic for the editor setup - that's the function called from this one below. Keep in mind that the user selects from a drop down on the web page a cost center of interest. That causes the populateView() to be called. Each cost center will have different number of columns, with a subset of them consistent for all.

    The table create logic looks like this:
    function populateView() {

            // if ($.fn.DataTable.isDataTable("#example")) {
            if (table != null) {
                // debugger;
                if (userAuthorization == USER_AUTHORIZATION.UPDATE) {
                    editor.destroy();
                }
                // Need to disable the Editor while we start over.
                $('#example').DataTable().clear().destroy();
                $('#example').empty();
            }
            // Get the cost center of interest
            // debugger;
            var ddlCostCenter = document.getElementById("ddlCostCenter");
            var costCenter = $("#ddlCostCenter").val();
            if (costCenter ==  null) {
                // Display a message
                BootstrapDialog.show({
                    type: BootstrapDialog.TYPE_DANGER,
                    title: 'No Data',
                    message: 'No cost centers retrieved for intial page launch.\nSelect Clear to view all or setup your cost centers in user settings and setup page.',
                    buttons: [{
                        label: 'Close',
                        action: function (dialogRef) {
                            dialogRef.close();
                        }
                    }]
                });
                return;
            }
    
            selectedCostCenter = ddlCostCenter.options[ddlCostCenter.selectedIndex].text;
            changeMode(CONST_MODE.VIEW);
    
    
            // Now get the data
            $.ajax({
                "url": getPlanningData,
                data: { "costCenter": costCenter },
                async: false,
                "success": function (json) {
    
                    // debugger;
                    var columnOff = ["{'bSortable': false}"]
                    planningHeadings = buildColumnHeaders(json.dataColumnHeaders, costCenter);
                    planningData = json.rawData;
                    var planningSummary = json.summaryData;
    
                    populateSummary(planningSummary[0]);
    
                    drawCallbackCounter = 0;        // reset our counter.  Temp work around until we can figure out why callback is getting called
                                                    // multiple times
    
                    // In theory, the editor is created first, then the table
                    setupEditor(json.dataColumnHeaders, planningHeadings);
                    // debugger;
                    table = $('#example').DataTable({
                        // dom: "Bfrtip", lfrtip
                        dom: "lrtip",
                        "fnPreRowSelect": function (e, nodes) {
                            debugger;
                            if (e.currentTarget.className.indexOf('no_select') != -1) {
                                return false;
                            }
                            return true;
                        },
                        data: planningData,
                        columns: planningHeadings.columnHdrs,
                        columnDefs: [
                            { "asSorting": [] },
                            {
                                "createdCell": function (td, cellData, rowData, row, col) {
                                    var color;
                                    if (cellData <= 0 || cellData == '') {
                                        color = holidayBGColor;
                                        $(td).css('background', color);
                                    }
                                },
                                "targets": [planningHeadings.forecastColumnIdx]
                            },
    
                            {   // Total Demand calculated column
                                "createdCell": function (td, cellData, rowData, row, col) {
                                    // debugger;
                                    var breakoutTotal = 0;
                                    var demandTotal = 0;
    
    
                                    for (var i = planningHeadings.startBreakOutColumnIdx; i <= planningHeadings.endBreakoutColumnIdx; i++) {
                                        breakoutTotal = breakoutTotal + parseInt(rowData[i]);
                                    }
                                    //                 (FC% * FC Column Qty)                                                                                                                 + (TD% * Total demand quantity by date)
                                    demandTotal = Math.ceil((Number(rowData[planningHeadings.percentageFCColumnIdx]) * Number(rowData[planningHeadings.forecastColumnIdx])) + (Number(rowData[planningHeadings.percentageDemandColumnIdx]) * breakoutTotal));
                                    rowData[planningHeadings.totalDemandColumnIdx] = demandTotal.toString();
                                    $(td).html(demandTotal);
                                },
                                "targets": [planningHeadings.totalDemandColumnIdx]
                            },
                            {   // Balance calculated column
                                "createdCell": function (td, cellData, rowData, row, col) {
                                    // debugger;
                                    var balance = 0;
                                    var previousDayBalance = 0;
                                    var crntRowIdx = parseInt(rowData[planningHeadings.displayRowIdx]);        // Actual displayed value, not the true idx
    
    
                                    // The very first balance record ever for a cost center the formula = “today’s” capacity – “today’s” total demand
                                    // Otherwise Formula = previous day’s balance + “today’s” capacity – “today’s” total demand
                                    if (crntRowIdx == 0) {
                                        balance = parseInt(rowData[planningHeadings.capacityColumnIdx]) - parseInt(rowData[planningHeadings.totalDemandColumnIdx]);
                                        previousRowData = rowData;
    
                                    } else if (crntRowIdx > 0) {
                                        previousDayBalance = parseInt(previousRowData[planningHeadings.balanceColumnIdx]);
                                        balance = previousDayBalance + parseInt(rowData[planningHeadings.capacityColumnIdx]) - parseInt(rowData[planningHeadings.totalDemandColumnIdx]);
                                        previousRowData = rowData;
                                    }
                                    rowData[planningHeadings.balanceColumnIdx] = balance.toString();
                                    $(td).html(balance);
                                },
                                "targets": [planningHeadings.balanceColumnIdx]
                            },
                            {
                                "targets": [planningHeadings.rowIdx],
                                "visible": false
                            },                            
                            {   // Planning date, show just mm/dd, and not mm/dd/yyyy
                                "render": function (data, type, full) {
                                    // debugger;
                                    return (data) ? moment(data).format('MM/DD') : '';
                                    // $(td).html(demandTotal);
                                },
                                "targets": [planningHeadings.dateColumnIdx]
                            },
                             { "orderable": false, "targets": '_all' }
                        ],
                        rowId: '0',
                        ordering: false,
                        paging: false,
                        fixedHeader: {
                            header: true,
                            footer: true
                        },
                        keys: {
                            columns: ':not(:first-child)',
                            editor: editor,
                            editorKeys: 'tab-only'
                        },
                        select: {
                            style: 'os',
                            selector: ':not(readonly)',
                            blurable: true
                        },
                        "drawCallback": function (settings) {
                            // debugger;
                            drawCallbackCounter = drawCallbackCounter + 1;
                            if (drawCallbackCounter == 4) {
                                var api = this.api();
                                postProcessing(true, api, null);
                            }
                        }
                    });
    
                },
                error: function (response) {
                    // debugger;
                    alert("error : " + response);
                }
            });
        }
    
  • allanallan Posts: 61,864Questions: 1Answers: 10,136 Site admin
    Answer ✓

    Ah! Yes - if you are using KeyTable, then it will call inline() for you.

    What to do is ignore what I suggested about using a click event listener before in that case... We still need some logic to decide if inline editing should activate or not, but now we'll do it in preOpen:

    editor.on( 'preOpen', function ( e, mode, action ) {
      var rowData = table.row( editor.modifier().row ).data();
    
      if ( ... ) {
        return false;
      }
    } );
    

    Returning false from preOpen will cancel the editing view from displaying.

    Regards,
    Allan

  • rprobinsonrprobinson Posts: 14Questions: 3Answers: 0

    That works. Thanks. Onward and upward :)

This discussion has been closed.