Search code examples
javascriptjqueryjqgridfree-jqgrid

Free jqGrid: Field undefined error when extending postdata with local data


I have been upgrading my projects to jQuery 1.12.0 and the latest version of free jqGrid. I am now seeing an field undefined error here in jquery.jqGrid.src.js:

this.execute = function() {
    var match = _query, results = [];
    if (match === null) {
        return self;
    }
    $.each(_data, function() {
        if (eval(match)) { results.push(this); }
    });
    _data = results;
    return self;
};

The match string looks like:

(String(ErrorCells).toUpperCase() !== String("0").toUpperCase())

I'm filtering out rows with errors (ErrorCells is used as a bit field), but it looks like an explicit this.ErrorCells is necessary. I can't wrap my head around what exactly changed and if there is a resolution without modifying the jqGrid source.

**** UPDATE ****

Below you can see that in the $.each handler, ErrorCells is undefined, but this.ErrorCells is.

enter image description here

This is the code extending the postdata with the filtering criteria:

$.extend(postdata, { // - set filter condition
    filters: '',
    searchField: 'ErrorCells',
    searchOper: 'ne',
    searchString: '0'
});

$grid.jqGrid('setGridParam', { search: true, postData: postdata });
$grid.trigger('reloadGrid', [{ page: 1}]);      

and this is the grid configuration:

$grid.jqGrid({
    url: "Loader.svc/GetData",                  // - web invoke post
    editurl: "clientArray",                     // - normally the endpoint to post edits, in this case we just edit local data
    postData: { "uniqueFolder": uniqueFolder, "xlsName": xlsName, "solIds": GetSolIdString(), "subcompany": _selectedConfiguration.MYSUBCOMPANY, "requireSolId": _selectedConfiguration.MYISBRANCHIDREQ },
    datatype: "json",
    loadonce: true,                             // - load data once on server side, then switch to local data handling
    mtype: "POST",
    ajaxGridOptions: { contentType: "application/json", cache: false },

    serializeGridData: function(postData) {
        return JSON.stringify(postData);
    },
    jsonReader: {
        id: "Id",
        root: function(obj) { return obj.rows; },
        page: function(obj) { return obj.page; },
        total: function(obj) { return obj.total; },
        records: function(obj) { return obj.records; }
    },
    headertitles: true,
    search: true,
    rowNum: 15,                         // - rows per page
    rowList: [5, 10, 15, 20],           // - options for rows per page
    pager: "#pager",                    // - element anchor for navigation panel
    gridview: true,                     // - row at once binding (faster performance)
    autoencode: true,                   // - encode html data
    ignoreCase: true,                   // - searches are case-insensitive
    //sortname: "IDNumber",             // - sort column for initial load
    viewrecords: true,                  // - displays the beginning and ending record number in the grid, out of the total number of records in the query
    sortorder: "asc",
    caption: "Card Personalization",
    height: "100%",

    shrinkToFit: false,                 // - defines how the width of the columns of the grid should be re-calculated, taking into consideration the width of the grid 
    autowidth: true,
    multiselect: false,
    loadui: "disable",

    colNames: ["IDType", "IDNumber", "LastName", "FirstName", "MiddleInitial", "EmbossLine1", "EmbossLine2", "Address1", "Address2", "Address3", "City", "Country", "State",
                   "PostalCode", "DateOfBirth", "EmailAddress", "HomePhone", "OfficePhone", "MobilePhone", "PhotoReference", "SalaryID", "SolID", "IDImage", "ErrorCells", "ErrorDescriptions", "IDThumb"],
    colModel: [
        { name: "IDType", width: 80, align: "left", editable: true, sortable: true, sorttype: "integer",
            stype: "select",
            edittype: "select",
            formatter: "select",
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            searchoptions: { sopt: ["eq"], value: GetIdTypes(true) },
            editrules: { required: true, integer: true, minValue: 1, maxValue: 50 },
            editoptions: { value: GetIdTypes(false), dataInit: function(elem) { $(elem).width(150); } },
            searchOptions: { defaultValue: 4 },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(0, rawObject); }
        },
        { name: "IDNumber", width: 80, align: "left", editable: true, sortable: true,
            editrules: { required: true, custom: true, custom_func: validateIDNumber },
            editoptions: { maxlength: 30 },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(1, rawObject); }
        },
        { name: "LastName", width: 100, align: "left", editable: true, sortable: true,
            editrules: { required: true, custom: true, custom_func: validateName },
            editoptions: { maxlength: 200 },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(2, rawObject); }
        },
        { name: "FirstName", width: 100, align: "left", editable: true, sortable: true,
            editrules: { required: true, custom: true, custom_func: validateName },
            editoptions: { maxlength: 200 },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(3, rawObject); }
        },
        { name: "MiddleInitial", width: 70, align: "left", editable: true, sortable: true, search: false,
            editrules: { required: false, custom: true, custom_func: validateName },
            editoptions: { maxlength: 40 },
            formoptions: { elmprefix: "&nbsp;" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(4, rawObject); }
        },
        { name: "EmbossLine1", width: 100, align: "left", editable: true, sortable: true, search: false,
            editrules: { required: $('#cbFirstLineEmbossing').prop('checked'), custom: true, custom_func: validateEmbossline },
            editoptions: { maxlength: 19, dataInit: function(el) { $(el).css('text-transform', 'uppercase'); } },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(5, rawObject); }
        },
        { name: "EmbossLine2", width: 100, align: "left", editable: true, sortable: true, search: false,
            editrules: { required: $('#cbSecondLineEmbossing').prop('checked'), custom: true, custom_func: validateEmbossline },
            editoptions: { maxlength: 19, dataInit: function(el) { $(el).css('text-transform', 'uppercase'); } },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(6, rawObject); }
        },
        { name: "Address1", width: 200, align: "left", editable: true, sortable: true, search: false,
            editrules: { required: true, custom: true, custom_func: validateAddress },
            editoptions: { maxlength: 50 },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(7, rawObject); }
        },
        { name: "Address2", width: 200, align: "left", editable: true, sortable: true, search: false,
            editrules: { required: false, custom: true, custom_func: validateAddress },
            editoptions: { maxlength: 50 },
            formoptions: { elmprefix: "&nbsp;" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(8, rawObject); }
        },
        { name: "Address3", width: 200, align: "left", editable: true, sortable: true, search: false,
            editrules: { required: false, custom: true, custom_func: validateAddress },
            editoptions: { maxlength: 50 },
            formoptions: { elmprefix: "&nbsp;" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(9, rawObject); }
        },
        { name: "City", width: 100, align: "left", editable: true, sortable: true,
            editrules: { required: true, custom: true, custom_func: validateCity },
            editoptions: { maxlength: 50 },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(10, rawObject); }
        },
        { name: "Country", width: 50, align: "left", editable: true, edittype: "select", stype: "select", sortable: true,
            editrules: { required: true, custom: true, custom_func: validateCountry },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            searchoptions: {
                value: GetCountries(true),
                dataInit: function(elem) {
                    var v = $(elem).val();
                    if (v !== '')
                        setStateValues(v, true);
                },
                dataEvents: [
                    { type: "change", fn: function(e) { changeStateSelect($(e.target).val(), e.target); } },
                    { type: "keyup", fn: function(e) { $(e.target).trigger('change'); } }
                ]
            },
            editoptions: {
                maxlength: 2,
                value: GetCountries(false),
                dataInit: function(elem) { // - populate the state dropdown based on selected country
                    var v = $(elem).val();
                    if (v !== '')
                        setStateValues(v, false);

                    setTimeout(function() {
                        var $element = $(elem),
                            required = $element.val() === 'US';
                        $grid.jqGrid('setColProp', 'PostalCode', { editrules: { required: required} });
                        $('#PostalCode').siblings('.mystar').html(required ? '*' : '&nbsp;');
                        $element.width(150);
                    }, 100);
                },
                dataEvents: [
                    { type: "change", fn: function(e) {
                        changeStateSelect($(e.target).val(), e.target);
                        var required = $(e.target).val() === 'US';
                        $grid.jqGrid('setColProp', 'PostalCode', { editrules: { required: required} });
                        $('#PostalCode').siblings('.mystar').html(required ? '*' : '&nbsp;');
                    } 
                    },
                    { type: "keyup", fn: function(e) { $(e.target).trigger('change'); } }
                ]
            },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(11, rawObject); }
        },
        { name: "State", width: 50, align: "left", editable: true, edittype: "select", stype: "select", sortable: true,
            editrules: { required: true, custom: true, custom_func: validateState },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            editoptions: {
                value: {},
                maxlength: 4,
                dataInit: function(elem) { $(elem).width(150); }
            },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(12, rawObject); }
        },
        { name: "PostalCode", width: 70, align: "left", editable: true, sortable: true, search: false,
            editrules: { required: false, custom: true, custom_func: validatePostal },
            editoptions: { maxlength: 30 },
            formoptions: { elmprefix: "<span class='mystar' style='color:red'>&nbsp;</span>" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(13, rawObject); }
        },
        { name: "DateOfBirth", width: 80, align: "left", editable: true, sortable: true, sorttype: "date", search: false,
            editrules: { required: true, date: true, custom: true, custom_func: validateDate },
            formoptions: { elmprefix: "<font color='red'>*</font>", elmsuffix: " format: mm/dd/yyyy" },
            editoptions: {
                size: 10,
                maxlength: 10,
                dataInit: function(element) {
                    $(element).datepicker({
                        dateFormat: "mm/dd/yy", changeMonth: true, changeYear: true, yearRange: "-100y:c+nn", maxDate: "-1d"
                    }).mask('99/99/9999');
                }
            },
            datefmt: 'mm/dd/yyyy',
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(14, rawObject); }
        },
        { name: "EmailAddress", width: 150, align: "left", editable: true, sortable: true, search: false,
            editrules: { required: false, custom: true, custom_func: validateEmail },
            editoptions: { maxlength: 100 },
            formoptions: { elmprefix: "&nbsp;" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(15, rawObject); }
        },
        { name: "HomePhone", width: 100, align: "left", editable: true, sortable: true, search: false,
            editrules: { required: false, custom: true, custom_func: validatePhone },
            editoptions: { maxlength: 30 },
            formoptions: { elmprefix: "&nbsp;" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(16, rawObject); }
        },
        { name: "OfficePhone", width: 100, align: "left", editable: true, sortable: true, search: false,
            editrules: { required: false, custom: true, custom_func: validatePhone },
            editoptions: { maxlength: 30 },
            formoptions: { elmprefix: "&nbsp;" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(17, rawObject); }
        },
        { name: "MobilePhone", width: 100, align: "left", editable: true, sortable: true,
            editrules: { required: true, custom: true, custom_func: validatePhone },
            editoptions: { maxlength: 30 },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(18, rawObject); }
        },
        { name: "PhotoReference", width: 100, align: "left", editable: true, sortable: true, search: false,
            editrules: { required: true },
            editoptions: { maxlength: 30 },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(19, rawObject); }
        },
        { name: "SalaryID", width: 100, align: "left", editable: true, sortable: true, search: true,
            editrules: { required: false, custom: true, custom_func: validateSalaryIdLocal },
            editoptions: { maxlength: 30 },
            formoptions: { elmprefix: "&nbsp;" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(20, rawObject); }
        },
        { name: "SolID", width: 50, align: "left", editable: true, sortable: true, edittype: "select", stype: "select",
            editrules: { required: true /*, custom: true, custom_func: validateSolId*/ },
            editoptions: { value: GetSolIds(_selectedConfiguration, false), maxlength: 4 },
            searchoptions: { sopt: ["eq"], value: GetSolIds(_selectedConfiguration, true) },
            formoptions: { elmprefix: "<font color='red'>*</font>" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(21, rawObject); }
        },
        { name: "IDImage", width: 100, align: "left", editable: true, sortable: true, search: true,
            editrules: { required: false, custom: true, custom_func: _validateIDImagePath },
            editoptions: { maxlength: 150, readonly: true },
            formoptions: { elmprefix: "&nbsp;" },
            cellattr: function(rowId, tv, rawObject, cm, rdata) { return SetCellError(22, rawObject); }
        },
        { name: "ErrorCells", hidden: true, editoptions: { defaultValue: '0'} },
        { name: "ErrorDescriptions", hidden: true },
        { name: "IDThumb", width: 50, fixed: true,
            formatter: function(cellvalue, options, rowObject) {
                return '<a id="imgSource_' + options.rowId + '" href="" data-lightbox="image_' + options.rowId + '" data-title=""><img id="imgThumb_' + options.rowId + '" src="" width="30" height="30"></a>';
            }
        }],
    ondblClickRow: function(rowid, ri, ci) { // - double click event handler
        var p = $grid[0].p;
        if (p.selrow !== rowid) // prevent the row from be unselected on double-click
            $grid.jqGrid('setSelection', rowid);

        $grid.jqGrid('editGridRow', rowid, editSettings); // - form edit
    },
    onSelectRow: function(id) { // - select row event handler
        if (id && id !== lastSel) {
            $grid.jqGrid('restoreRow', lastSel);
            lastSel = id;
        }
    },
    beforeRequest: function() { // - on initial load, display empty grid for editing
        if (_initialLoad)
            $grid.jqGrid('setGridParam', { datatype: "local", loadonce: false });
    },
    loadError: function(xhr, st, errorThrown) { // - todo: handle error from server during load, need to spruce this up
        _initialDataLoad = false;

        $('#fileUploadOK').hide();
        ShowButtons(false, IsGridValid()); // - refresh button states if grid is loaded
        $grid.jqGrid('setGridParam', { datatype: "local", loadonce: false }); // - fail, lets rebind an empty grid for manual entry
        Toggle(true); // - slide toggle the top section open 
    },
    loadComplete: function(data) {
        $('#filterInvalid').prop('disabled', false);

        $grid.jqGrid('hideCol', 'cb'); // - hide the check boxes for multi-select 

        if (_initialLoad) { // - set the column header tooltips on initial empty bind
            setTooltipsOnColumnHeader($grid, 1, 'IDType (required)\nExample:\n1: International Passport\n2: National ID\n3: Drivers License\n4: Other ');
            setTooltipsOnColumnHeader($grid, 2, 'IDNumber (required)\nIDNumber for corresponding IDType.\nMax length 30.');
            setTooltipsOnColumnHeader($grid, 3, 'LastName (required)\nMax length 50.');
            setTooltipsOnColumnHeader($grid, 4, 'FirstName (required)\nMax length 50.');
            setTooltipsOnColumnHeader($grid, 5, 'MiddleInitial (optional)\nMax length 40.');
            setTooltipsOnColumnHeader($grid, 6, 'EmbossLine1 (optional)\nThis is the text to be embossed on the first line of the card.  If it is blank, it will default to the FirstName LastName.\nMax length 19.');
            setTooltipsOnColumnHeader($grid, 7, 'EmbossLine2 (optional)\nIf 2nd Line Embossing is needed, specify the value here with no more than 19 characters and inform GTP that 2nd line embossing is requested for the order.  (Note: not all vendors are able to emboss line 2)\nMax length 19.');
            setTooltipsOnColumnHeader($grid, 8, 'Address1 (required)\nMax length 50.');
            setTooltipsOnColumnHeader($grid, 9, 'Address2 (optional)\nMax length 50.');
            setTooltipsOnColumnHeader($grid, 10, 'Address3 (optional)\nMax length 50.');
            setTooltipsOnColumnHeader($grid, 11, 'City (required)\nMax length 50.');
            setTooltipsOnColumnHeader($grid, 12, 'Country (required)\nSee CountryCodeList sheet for valid Country codes\nMax length 2.');
            setTooltipsOnColumnHeader($grid, 13, 'State (required)\nSee StateCountryCodeList sheet for valid State/Country combinations.');
            setTooltipsOnColumnHeader($grid, 14, 'PostalCode (optional)\nRequired for US addresses.\nMax length 30.');
            setTooltipsOnColumnHeader($grid, 15, 'DateOfBirth (required)\nMM/DD/YYY format.');
            setTooltipsOnColumnHeader($grid, 16, 'EmailAddress (optional)\nMax length 100.');
            setTooltipsOnColumnHeader($grid, 17, 'HomePhone (optional)\nPrefix with country code; no leading zeros.\nMax length 30.');
            setTooltipsOnColumnHeader($grid, 18, 'OfficePhone (optional)\nPrefix with country code; no leading zeros.\nMax length 30.');
            setTooltipsOnColumnHeader($grid, 19, 'MobilePhone (required)\nPrefix with country code; no leading zeros.\nMax length 30.');
            setTooltipsOnColumnHeader($grid, 20, 'Photo Reference (required for Photo Card orders.\nMax length 30.');
            setTooltipsOnColumnHeader($grid, 21, 'SalaryID (optional)\nCan be used for account or for picture id to map to a jpeg file.\nMax length 30.');
            setTooltipsOnColumnHeader($grid, 22, 'SolID (required by some banks)\nFour digit value with leading zeros.');
            setTooltipsOnColumnHeader($grid, 23, 'IDImage (optional)\nName of associated ID image file.');
            _initialLoad = false;
        } else
            $('#txtCardCount').val($grid.getGridParam('records')); // - set the total card count

        refreshIDImages();

        if (_selectedConfiguration) {
            ZapColumn($grid, 'SolID', _selectedConfiguration.MYISBRANCHIDREQ === 'N');
            ZapColumn($grid, 'PhotoReference', !$('#cbPhotoID').prop('checked'));
        }

        if (_initialDataLoad) {
            IsImageUploadValid();
            $('#fileUploadOK').show();  // - display upload status

            var setErrors = new GridErrors($grid);
            setErrors.setEmboss = true;
            setErrors.setPhotoReference = true;
            setErrors.setSolID = true;
            setErrors.execute();
        }
        var gridValid = IsGridValid();
        ShowButtons(true, gridValid);

        if (_initialDataLoad) {
            ResetFilter(gridValid);
            $grid.trigger('reloadGrid', [{ page: 1}]);
        }
        _initialDataLoad = false;
    }
}).jqGrid('navGrid', '#pager', { refresh: false, search: false }, editSettings, addSettings, delSettings, {
    multipleSearch: true,
    overlay: false,
    onClose: function(form) {
        // if we close the search dialog while the datepicker is open
        // the datepicker will stay opened. To fix this we have to hide
        // the div used by datepicker
        $('div#ui-datepicker-div.ui-datepicker').hide();
    }
}).jqGrid('filterToolbar', {
    stringResult: true,
    searchOnEnter: false,
    beforeSearch: function() { return false; }
});                 //.jqGrid('setFrozenColumns');     // - this freezes columns during horiz. scrolling

}

This is the data (kind of, just showing an example of ErrorCells) enter image description here

Any advice appreciated, thanks.


Solution

  • The problem seems to me independent from the used jQuery version. The reason of described problem is the usage of old style filter

    $.extend(postdata, { // - set filter condition
        filters: '',
        searchField: 'ErrorCells',
        searchOper: 'ne',
        searchString: '0'
    });
    

    instead of new-style filter

    $.extend(postdata, { // - set filter condition
        filters: JSON.stringify({
            groupOp: "AND",
            groups: [],
            rules: [{field: "ErrorCells", op: "ne", data: "0"}]
        })
    });
    

    or

    $.extend(postdata, { // - set filter condition
        filters: {
            groupOp: "AND",
            groups: [],
            rules: [{field: "ErrorCells", op: "ne", data: "0"}]
        }
    });
    

    By the way you use multipleSearch: true and the old style filter could not be displayed.

    Nevertheless free jqGrid should in general work with old style filter too. I fixed the problem and posted the commit to GitHub. You need to download the latest sources from GitHub.

    Thank you for reporting the bug!

    Please verify that the problem is fixed with the latest sources, but after that I would recommend you to change the old-style filter to the new-style (see the code above).