jQuery - jqGrid - undesired behavior in onSelectRow event
Asked Answered
S

1

1

I had a previous question regarding having buttons available in each row of a jqGrid, that would activate when the row was selected. With help from @Oleg I was able to get it to work like I thought it should.

In further testing however, I have identified undesired behavior in that onSelectRow event. What I found was that if I have a grid with 3 rows in it, and I click on row 2 (which activates the buttons), then (without clicking on the row 2 buttons) click on row 3 (which deactivates row 2 buttons & activates row 3 buttons), and change my mind again & click row 1 or 2 (doesn't matter), and at THAT point then click the "Re-Send" (submit) button, what happens is that the selected row is then re-sent 3 separate times.

Following are the timestamps from 4 rows that were written from 1 "Re-Send" click yesterday.

2013-05-28 16:49:04.817
2013-05-28 16:49:04.653
2013-05-28 16:49:04.560
2013-05-28 16:49:04.467

I put some logging in the page & confirmed that it does call the POST 4 separate times, once for each click on a row in the grid.

The following sections contain most of the configuration/settings I currently have where the jqGrid gets built.

colNames: ["Destination", "Message Text", "Send Time","Message Action"],
colModel:[
    {name:"Destination",index:"Destination",width:col1width,align:"left", xmlmap:"Rowset>Row>Destination",sortable:false},
    {name:"MessageText",index:"MessageText",width:col2width,align:"left",xmlmap:"Rowset>Row>MessageText",sortable:false},
    {name:"SendTime",index:"SendTime",width:col3width,align:"center",formatter:"date",formatoptions: {"srcformat":"ISO8601Long", "newformat":"Y-m-d H:i:s"},xmlmap:"Rowset>Row>SendTime",sortable:false},
    {name: "msgAct",
    width: col4width,
    align: "center",
    formatter: function() {
        return "<input name='resendMsg' style='height:25px;width:65px;' type='submit' value='Re-Send' disabled='disabled' />" +
               "<input name='cancelMsg' style='height:25px;width:55px;' type='submit' value='Cancel' disabled='disabled' />" 
            }}
    ],
viewrecords: true,
caption: capMsg,
rownum: 0,
height: "100%",
width: gridwidth,
toolbar: [true, "top"],
pager: jQuery("#pager1"),
sortname: "SendTime",
hidegrid: false,                // hides the ability to collapse grid
defaults: {
    altrows: true, 
    recordtext: "View {0} - {1} of {2}",
    emptyrecords:  "No records to view",
    loadonce: true,
    pgtext: "Page  {0} of {1}"
    },

Following is the onSelectRow event.

onSelectRow:  function(id) {
    var tr = $(this).jqGrid("getInd",id,true);
    var gridRow = $(this).jqGrid("getRowData",id);
    var srow = $(this).jqGrid("getGridParam","selrow");
    // disable all resendMsg & cancelMsg buttons in the grid
    $(this).find("input[name=resendMsg]").attr("disabled","disabled");
    $(this).find("input[name=cancelMsg]").attr("disabled", "disabled");
    // now enable the buttons for the current row only
    $(tr).find("input[name=resendMsg]").removeAttr("disabled");
    $(tr).find("input[name=cancelMsg]").removeAttr("disabled");
    // disable dropdowns & sendMsg submit button
    // catch the Cancel button click
    $(tr).find("input[name=cancelMsg]").click(function() {
        // disable all resendMsg & cancelMsg buttons in the grid
        $(this).find("input[name=resendMsg]").attr("disabled","disabled");
        $(this).find("input[name=cancelMsg]").attr("disabled", "disabled");
        // enable the dropdowns & clear the selection, reload grid
        ReloadGrid();
        });
    // catch the Re-Send button click
    $(tr).find("input[name=resendMsg]").click(function() {
        ReSendMessage(gridRow.Destination, gridRow.MessageText);
        // disable all resendMsg & cancelMsg buttons in the grid
        $(this).find("input[name=resendMsg]").attr("disabled","disabled");
        $(this).find("input[name=cancelMsg]").attr("disabled", "disabled");
        // enable the dropdowns, clear the selection and exit
        $("#myGrid").jqGrid("resetSelection");
        });
    },

and the remainder of the jqGrid code:

gridview: true,
xmlReader:  { 
    root: "Rowsets", 
            row: "Row",
    repeatitems: false,
    id: "SendTime"
    },
loadComplete: function() { 
    // increase row height
    $("td",".jqgrow").height(40);            // data grid rows
     // alternate background of every other row
    $("tr.jqgrow:odd").css({"background-color": "#DDDDDC", "background-image": "none"});
    $("th.ui-th-column").css("font-weight","bold");
    }
});

It's like the onSelectRow event is accumulating the number of clicks & then calling the click event's functions however many times a row was selected but a button wasn't clicked.

I have tested though, that if I click on a row, and then click on either of the submit buttons, that it processes as expected (a "Re-Send" submits the row 1 time, and a "Cancel" clears the selection & does nothing else).

I don't know if it is possible, but can you prevent a subsequent onSelectRow from firing if a row's already selected? Can you clear the previous selection (or reset it) to prevent the onSelectRow (and the click event for the button) from firing multiple times?

I'd appreciate any thoughts, comments or suggestions as to how to fix this behavior.

EDIT

Including the code for beforeSelectRow as noted in a response below.

$("#myGrid").bind("jqGridBeforeSelectRow", function(e, id, eventOriginal) {
    var gsr = $("#myGrid").jqGrid("getGridParam","selrow");
    console.log(" ****  beforeSelectRow - (before if) lastSel = " + lastSel + "  id = " + id + "   gsr = " + gsr);
    if (id && id !== lastSel) {
        console.log("id && id !== lastSel");
        console.log("   id = " + id +  "    lastSel = " + lastSel);
        };
    if (id !== lastSel) {
        if (lastSel == -1) {   // first time thru
            lastSel = id;
            console.log(" ****  beforeSelectRow - first time thru - new val = " + lastSel + "  gsr = " + gsr);
            return true;
            }
          else {
            console.log(" ****  beforeSelectRow - lastSel - " + lastSel + "   <>  id = "  +id + "    gsr = " + gsr);
            return false;
            }
        }
    else {
        console.log(" ****  beforeSelectRow - otherwise they matched -  lastSel  = " + lastSel + "     id = " + id + "  gsr = " + gsr);
        return true;
        }
    });

After moving the .click event from the onSelectRow into the loadComplete (for the Cancel button as a test), I tried the above code both inside & outside the code for the grid. It performs the same either way.

The issue is that the Cancel button is supposed to reset the selection $("#myGrid").resetSelection(); and reload the grid. The code executes & doesn't give an error, but the very next time beforeSelectRow fires (when the grid is reloaded), the id is still the same as it was when beforeSelectRow and onSelectRow fired when I clicked on the row, which means that I can never select a new row until the entire page is reloaded. Only on page load is when beforeSelectRow doesn't fire.

EDIT

Following is the code for the Cancel button, which is now located inside the loadComplete event.

// catch the Cancel button click
$(this).find("input[name=cancelMsg]").click(function() {
    // disable all resendMsg & cancelMsg buttons in the grid
    $(this).find("input[name=resendMsg]").attr("disabled","disabled");
    $(this).find("input[name=cancelMsg]").attr("disabled", "disabled");
    // enable the dropdowns & clear the selection, reload grid
    console.log("ReloadGrid (inside Cancel function) ");
    lastSel = -1;
    $("#myGrid").trigger("reloadGrid");
Sapodilla answered 29/5, 2013 at 13:35 Comment(0)
L
2

It's wrong to bind button (to use .click) inside of onSelectRow callback.

Instead of you can move the code with registering click event handler inside of loadComplete. You can just move the code from onSelectRow to loadComplete and replace $(tr) to $(this) to search inside of all buttons of the grid and not only the buttons of selected row. You can still set or remove disabled attribute inside of onSelectRow callback.

More better would be to make no individual binding for every button. One can just use onCellSelect or beforeSelectRow which will be called from click handler bound on whole grid. See the answer and this one for code examples.

UPDATED: I am not sure that I understand correctly your problem, but I hope that the demo will demonstrate the solution of the problem. I don't used any explicit click handler. The most important change (which could important if you use Internet Explorer) is adding class='cbox' to the formatter to prevent return in the line of jqGrid code. The most important part of the code is below

colModel: [
    { name: "text", width: 500 },
    { name: "msgAct", width: 150,
        formatter: function () {
            return "<input name='resendMsg' class='cbox' style='height:25px;width:65px;' type='submit' value='Re-Send' disabled='disabled'/>" +
                "<input name='cancelMsg'  class='cbox' style='height:25px;width:55px;' type='submit' value='Cancel' disabled='disabled'/>"
        }}
],
onSelectRow: function (rowid) {
    var tr = $(this).jqGrid("getInd", rowid, true);
    $(this).find("input[name=resendMsg],input[name=cancelMsg]").attr("disabled", "disabled");
    $(tr).find("input[name=resendMsg],input[name=cancelMsg]").removeAttr("disabled");
},
beforeSelectRow: function (rowid, e) {
    var $self = $(this),
        $td = $(e.target).closest("td"),
        iCol = $.jgrid.getCellIndex($td[0]),
        name = $(e.target).attr("name");
    if (this.p.colModel[iCol].name === "msgAct") {
        if (name === "resendMsg") {
            alert("'Re-Send' button clicked in the row with id=" + rowid +
                "\ntext in the row =\"" + $self.jqGrid("getCell", rowid, "text") + "\"");
        } else if (name === "cancelMsg") {
            alert("'Cancel' button clicked in the row with id=" + rowid);
            setTimeout(function () {
                $self.trigger("reloadGrid");
            }, 50);
        }
    }
    return true;
}

You can replace alerts inside of beforeSelectRow to the action which you need.

Lawford answered 3/6, 2013 at 5:29 Comment(8)
Moving the .click into the loadComplete helped to have it get called only once. However, I've noticed that the rowID is still the value it was, and isn't resetting, even though I am calling $("#myGrid").ResetSelection(); from inside the .click event handler that's moved into the loadComplete. That means that if I have 3 rows displayed & select row 3, then hit the cancel button on the row, the beforeSelectRow still has row 3's id in it & unless I reload the page, I can never select any other row until the page is reloaded. I have included the beforeSelectRow code above.Sapodilla
@steve_o: You wrote about problems with "reload the grid" inside of beforeSelectRow, but the code which you posed don't contain any trigger reloadGrid. Could you include the code which shows how you do this? Do you call trigger reloadGrid inside of setTimeout? Do you reset variables like lastSel before reloading? I don't understand some parts of the code which you posted. Why you access selrow inside of jqGridBeforeSelectRow instead of usage id parameter?Lawford
Added the code above. To answer your questions: I do not call trigger reloadGrid inside of setTimeout. Variable lastSel is set to -1 within the Cancel click event. The selrow inside the jqGridBeforeSelectRow is for information as used in the console.log statements. I was displaying id in the beforeSelectRow and could see that it wasn't being reset - selrow was being displayed to show me that the selected row had indeed been reset. I'm not sure how the id ever gets reset.Sapodilla
@steve_o: Look at UPDATED part of my answer and the demo.Lawford
Thank you for the help. I've got it working to some degree. However, what I notice is that onSelectRow fires every time the grid loads after the first time. I took your suggestion & moved the button actions into the beforeSelectRow. Some of the code I omitted was disabling dropdown boxes in the onSelectRow. Since (after the first time) onSelectRow fires every time they're never being re-enabled (as the grid is). Since onSelectRow fires, I think that I need to detect selected also. Thoughts? Thank you again for ALL of your help!Sapodilla
I may have resolved the issue. Unless there is a better way to do it, I put the code to turn on everything turned off in the onSelectRow inside the resend/cancel if statements in beforeSelectRow. In there, I'd re-enable anything previously disabled, and disable anything that was currently enabled, reset the selection and then reload the grid. After that (still inside the if resend or if cancel) I would return false from beforeSelectRow. Not finished testing it, but it seems to do what I am asking. Thank you again for all your help!!Sapodilla
@steve_o: You are welcome! Sorry, but you can see on the demo that no onSelectRow will be fired by loading the grid. If you have the effect that you should examine your code where you explicitly select some row.Lawford
- your demo code is actually what helped me figure out how to fix it. I put a log message into both onSelectRow and beforeSelectRow to see what was happening. On page load, no onSelectRow fired. However, once I selected a row, no matter whether I hit Re-Send or Cancel - onSelectRow would always fire. What I ended up doing was when Re-Send or Cancel button was handled in the beforeSelectRow was where I did the reloading of the grid & had it return FALSE. Otherwise, beforeSelectRow would return TRUE. Thank you again!Sapodilla

© 2022 - 2024 — McMap. All rights reserved.