Author Topic: Filter, add row, then save, remove filter - nbr of rows double  (Read 8213 times)

nebels

  • Pro Enterprise
  • Newbie
  • *
  • Posts: 12
    • View Profile
Hi there,

Our testers have found an interesting behaviour which I'd like to bring to your attention. I can reproduce the problem by using the 'Batch editing' demo and replacing the sample code with the code below. Then do an 'Edit and Run' (which is BTW a great feature - thanks!).

The following step will then reproduce the issue:


  • record the number of rows - Displaying ... n items
  • set up a filter - eg. filter value 'a'
  • Add 1 rew record
  • Save the changes
  • Remove the filter
  • check the number of rows
expected value: n+1 items
actual value : 2n+1 items; plus the grid now contains every 'original row' twice.


This is a grid issue - not an underlying data issue - a complete refresh on the browser will then display n+1 items 

Please note - the entire grid is refreshed (our application requires this) - see 'success' callback in $.ajax in saveChanges function.   

Best regards and thanks in advance




(function() {

   function filterhandler(evt, ui) {
      debugger;
        var $grid = $("#grid_editing");
        var $toolbar = $grid.find('.pq-toolbar'),
            $value = $toolbar.find(".filterValue"),
            value = $value.val(),
            condition = $toolbar.find(".filterCondition").val(),
            dataIndx = $toolbar.find(".filterColumn").val(),
            filterObject;

        if (dataIndx == "") {//search through all fields when no field selected.
            filterObject = [];
            var CM = $grid.pqGrid("getColModel");
            for (var i = 0, len = CM.length; i < len; i++) {
                var dataIndx = CM.dataIndx;
                filterObject.push({ dataIndx: dataIndx, condition: condition, value: value });
            }
        }
        else {//search through selected field.
            filterObject = [{ dataIndx: dataIndx, condition: condition, value: value}];
        }
        $grid.pqGrid("filter", {
            oper: 'replace',
            data: filterObject
        });
    }
   
    //called when save changes button is clicked.
    function saveChanges() {
        var grid = $grid.pqGrid('getInstance').grid;

        //debugger;
        //attempt to save editing cell.
        if (grid.saveEditCell() === false) {
            return false;
        }

        var isDirty = grid.isDirty();
        if (isDirty) {
            //validate the new added rows.
            var addList = grid.getChanges().addList;
            //debugger;
            for (var i = 0; i < addList.length; i++) {
                var rowData = addList;
                var isValid = grid.isValid({ "rowData": rowData }).valid;
                if (!isValid) {
                    return;
                }
            }
            var changes = grid.getChanges({ format: "byVal" });

            //post changes to server
            $.ajax({
                dataType: "json",
                type: "POST",
                async: true,
                beforeSend: function (jqXHR, settings) {
                    grid.showLoading();
                },
                url: "/pro/products/batch", //for ASP.NET, java
                data: { list: JSON.stringify(changes) },
                success: function (changes) {
                    //debugger;
                    grid.commit();
                    grid.refreshDataAndView();
                    grid.history({ method: 'reset' });
                },
                complete: function () {
                    grid.hideLoading();
                }
            });
        }
    }
    var obj = {
        hwrap: false,
        resizable: true,
        rowBorders: false,
        virtualX: true,
        numberCell: { show: true },
        filterModel: { mode: 'OR', type: "local" },
        trackModel: {on: true}, //to turn on the track changes.
        toolbar: {
            items: [
                { type: 'button', icon: 'ui-icon-plus', label: 'New Product', listener:
                { "click": function (evt, ui) {
                    //append empty row at the end.
                    var rowData = { UnitPrice: 1, ProductID: 34, UnitPrice: 0.2 }; //empty row
                    var rowIndx = $grid.pqGrid("addRow", { rowData: rowData, checkEditable: true });
                    $grid.pqGrid("goToPage", { rowIndx: rowIndx });
                    $grid.pqGrid("editFirstCellInRow", { rowIndx: rowIndx });
                }
                }
                },
                { type: 'separator' },
                { type: 'button', icon: 'ui-icon-disk', label: 'Save Changes', cls: 'changes', listener:
                { "click": function (evt, ui) {
                    saveChanges();
                }
                },
                    options: { disabled: true }
                },
                { type: 'button', icon: 'ui-icon-cancel', label: 'Reject Changes', cls: 'changes', listener:
                { "click": function (evt, ui) {
                    $grid.pqGrid("rollback");
                    $grid.pqGrid("history", { method: 'resetUndo' });
                }
                },
                    options: { disabled: true }
                },
                { type: 'button', icon: 'ui-icon-cart', label: 'Get Changes', cls: 'changes', listener:
                { "click": function (evt, ui) {
                    var changes = $grid.pqGrid("getChanges", { format: 'raw' });
                    try {
                        console.log(changes);
                    }
                    catch (ex) { }
                    alert("Please see the log of changes in your browser console.");
                }
                },
                    options: { disabled: true }
                },
                { type: 'separator' },
                { type: 'button', icon: 'ui-icon-arrowreturn-1-s', label: 'Undo', cls: 'changes', listener:
                { "click": function (evt, ui) {
                    $grid.pqGrid("history", { method: 'undo' });
                }
                },
                    options: { disabled: true }
                },
                { type: 'button', icon: 'ui-icon-arrowrefresh-1-s', label: 'Redo', listener:
                { "click": function (evt, ui) {
                    $grid.pqGrid("history", { method: 'redo' });
                }
                },
                    options: { disabled: true }
                },
                { type: "<span style='margin:5px;'>Filter</span>" },
                { type: 'textbox', attr: 'placeholder="Enter your keyword"', cls: "filterValue", listeners: [{ 'change': filterhandler}] },
                { type: 'select', cls: "filterColumn",
                    listeners: [{ 'change': filterhandler}],
                    options: function (ui) {
                        var CM = ui.colModel;
                        var opts = [{ '': '[ All Fields ]'}];
                        for (var i = 0; i < CM.length; i++) {
                            var column = CM;
                            var obj = {};
                            obj[column.dataIndx] = column.title;
                            opts.push(obj);
                        }
                        return opts;
                    }
                },
                { type: 'select', style: "margin:0px 5px;", cls: "filterCondition",
                    listeners: [{ 'change': filterhandler}],
                    options: [
                        { "begin": "Begins With" },
                        { "contain": "Contains" },
                        { "end": "Ends With" },
                        { "notcontain": "Does not contain" },
                        { "equal": "Equal To" },
                        { "notequal": "Not Equal To" },
                        { "empty": "Empty" },
                        { "notempty": "Not Empty" },
                        { "less": "Less Than" },
                        { "great": "Great Than" }
                    ]
                }
            ]
        },
        scrollModel: {
            autoFit: true
        },
        selectionModel: {
            type: 'cell'
        },
        swipeModel: { on: false },
        editModel: {
            onBlur: 'validate',
            saveKey: $.ui.keyCode.ENTER
        },
        editor: {
            select: true
        },
        title: "<b>Batch Editing</b>",
        history: function (evt, ui) {
            var $grid = $(this);
            if (ui.canUndo != null) {
                $("button.changes", $grid).button("option", { disabled: !ui.canUndo });
            }
            if (ui.canRedo != null) {
                $("button:contains('Redo')", $grid).button("option", "disabled", !ui.canRedo);
            }
            $("button:contains('Undo')", $grid).button("option", { label: 'Undo (' + ui.num_undo + ')' });
            $("button:contains('Redo')", $grid).button("option", { label: 'Redo (' + ui.num_redo + ')' });
        },
        colModel: [
            { title: "Product ID", dataType: "integer", dataIndx: "ProductID", editable: false, width: 80 },
            { title: "Product Name", width: 165, dataType: "string", dataIndx: "ProductName",
                validations: [
                    { type: 'minLen', value: 1, msg: "Required" },
                    { type: 'maxLen', value: 40, msg: "length should be <= 40" }
                ]
            },
            { title: "Quantity Per Unit", width: 140, dataType: "string", align: "right", dataIndx: "QuantityPerUnit",
                validations: [
                    { type: 'minLen', value: 1, msg: "Required." },
                    { type: 'maxLen', value: 20, msg: "length should be <= 20" }
                ]
            },
            { title: "Unit Price", width: 100, dataType: "float", align: "right", dataIndx: "UnitPrice",
                validations: [{ type: 'gt', value: 0.5, msg: "should be > 0.5"}],
                render: function (ui) {
                    //debugger;
                    var cellData = ui.cellData;
                    if (cellData != null) {
                        return "$" + parseFloat(ui.cellData).toFixed(2);
                    }
                    else {
                        return "";
                    }
                }
            },
            { hidden: true },
            { title: "Units In Stock", width: 100, dataType: "integer", align: "right", dataIndx: "UnitsInStock",
                validations: [{ type: 'gte', value: 1, msg: "should be >= 1" },
                    { type: 'lte', value: 1000, msg: "should be <= 1000" }
                ]
            },
            { title: "Discontinued", width: 100, dataType: "bool", align: "center", dataIndx: "Discontinued",
                editor: { type: "checkbox", subtype: 'triple', style: "margin:3px 5px;" },
                validations: [{ type: 'nonEmpty', msg: "Required"}]
            },
            { title: "", editable: false, minWidth: 83, sortable: false,
                render: function (ui) {
                    return "<button type='button' class='delete_btn'>Delete</button>";
                }
            }
        ],
        pageModel: { type: "local", rPP: 20 },
        dataModel: {
            dataType: "JSON",
            location: "remote",
            recIndx: "ProductID",
            url: "/pro/products/get", //for ASP.NET
            //url: "/pro/products.php", //for PHP
            getData: function (response) {
                return { data: response.data };
            }
        },
        refresh: function () {
            $("#grid_editing").find("button.delete_btn").button({ icons: { primary: 'ui-icon-scissors'} })
                .unbind("click")
                .bind("click", function (evt) {
                    var $tr = $(this).closest("tr");
                    var obj = $grid.pqGrid("getRowIndx", { $tr: $tr });
                    var rowIndx = obj.rowIndx;
                    $grid.pqGrid("addClass", { rowIndx: rowIndx, cls: 'pq-row-delete' });

                    var ans = window.confirm("Are you sure to delete row No " + (rowIndx + 1) + "?");
                    $grid.pqGrid("removeClass", { rowIndx: rowIndx, cls: 'pq-row-delete' });
                    if (ans) {
                        $grid.pqGrid("deleteRow", { rowIndx: rowIndx });
                    }
                });
        }
    };
    var $grid = $("#grid_editing").pqGrid(obj);

})
 
« Last Edit: August 21, 2015, 04:25:31 am by nebels »

paramvir

  • Administrator
  • Hero Member
  • *****
  • Posts: 6320
    • View Profile
Re: Filter, add row, then save, remove filter - nbr of rows double
« Reply #1 on: August 24, 2015, 10:39:28 am »
Thanks for reporting this issue, though the code posted by you seems to be incomplete.

There is a complete working example for editing+filtering+sorting here, it works fine while following the steps mentioned by you:

http://paramquery.com/pro/demos/edit_filter

Please check it and let know if the issue still persists.
« Last Edit: August 24, 2015, 10:42:11 am by paramquery »

nebels

  • Pro Enterprise
  • Newbie
  • *
  • Posts: 12
    • View Profile
Re: Filter, add row, then save, remove filter - nbr of rows double
« Reply #2 on: August 25, 2015, 12:49:16 am »
Hello there,

Thanks for your reply - I had a look through the example you quoted  - unfortunately it does not suit our requirements from a layout perspective. We need the filter item in the top row - I did use the example given in your no-pro version to create the sample code I sent in my previous post.
Which brings me to the issue of incompleteness. I do agree - after having copied and pasted the the code from my previous post back into the sample things did not work. :-(  The culprit was the forum editor - considering some of the elements in my code as markup.  Sorry I should have realised this before - and used the code tag.
Attached again the sample code, now working, - I tested the 'preview'-ed version which will show up the issue when following the step as outlined in my previous post.

Thanks and sorry once again for the hassle

 
Code: [Select]

$(function () {
    function filterhandler(evt, ui) {
        //debugger;
        var $grid = $("#grid_editing");
        var $toolbar = $grid.find('.pq-toolbar'),
            $value = $toolbar.find(".filterValue"),
            value = $value.val(),
            condition = $toolbar.find(".filterCondition").val(),
            dataIndx = $toolbar.find(".filterColumn").val(),
            filterObject;

        if (dataIndx == "") {//search through all fields when no field selected.
            filterObject = [];
            var CM = $grid.pqGrid("getColModel");
            for (var i = 0, len = CM.length; i < len; i++) {
                var dataIndx = CM[i].dataIndx;
                filterObject.push({ dataIndx: dataIndx, condition: condition, value: value });
            }
        }
        else {//search through selected field.
            filterObject = [{ dataIndx: dataIndx, condition: condition, value: value}];
        }
        $grid.pqGrid("filter", {
            oper: 'replace',
            data: filterObject
        });
    }

    //called when save changes button is clicked.
    function saveChanges() {
        var grid = $grid.pqGrid('getInstance').grid;

        //debugger;
        //attempt to save editing cell.
        if (grid.saveEditCell() === false) {
            return false;
        }

        var isDirty = grid.isDirty();
        if (isDirty) {
            //validate the new added rows.
            var addList = grid.getChanges().addList;
            //debugger;
            for (var i = 0; i < addList.length; i++) {
                var rowData = addList[i];
                var isValid = grid.isValid({ "rowData": rowData }).valid;
                if (!isValid) {
                    return;
                }
            }
            var changes = grid.getChanges({ format: "byVal" });

            //post changes to server
            $.ajax({
                dataType: "json",
                type: "POST",
                async: true,
                beforeSend: function (jqXHR, settings) {
                    grid.showLoading();
                },
                url: "/pro/products/batch", //for ASP.NET, java
                data: { list: JSON.stringify(changes) },
                success: function (changes) {
                    //debugger;
                    grid.commit();
                    grid.refreshDataAndView();
                    grid.history({ method: 'reset' });
                },
                complete: function () {
                    grid.hideLoading();
                }
            });
        }
    }
    var obj = {
        hwrap: false,
        resizable: true,
        rowBorders: false,
        virtualX: true,
        numberCell: { show: true },
        filterModel: { mode: 'OR', type: "local" },
        trackModel: {on: true}, //to turn on the track changes.
        toolbar: {
            items: [
                { type: 'button', icon: 'ui-icon-plus', label: 'New Product', listener:
                { "click": function (evt, ui) {
                    //append empty row at the end.
                    var rowData = { UnitPrice: 1, ProductID: 34, UnitPrice: 0.2 }; //empty row
                    var rowIndx = $grid.pqGrid("addRow", { rowData: rowData, checkEditable: true });
                    $grid.pqGrid("goToPage", { rowIndx: rowIndx });
                    $grid.pqGrid("editFirstCellInRow", { rowIndx: rowIndx });
                }
                }
                },
                { type: 'separator' },
                { type: 'button', icon: 'ui-icon-disk', label: 'Save Changes', cls: 'changes', listener:
                { "click": function (evt, ui) {
                    saveChanges();
                }
                },
                    options: { disabled: true }
                },
                { type: 'button', icon: 'ui-icon-cancel', label: 'Reject Changes', cls: 'changes', listener:
                { "click": function (evt, ui) {
                    $grid.pqGrid("rollback");
                    $grid.pqGrid("history", { method: 'resetUndo' });
                }
                },
                    options: { disabled: true }
                },
                { type: 'button', icon: 'ui-icon-cart', label: 'Get Changes', cls: 'changes', listener:
                { "click": function (evt, ui) {
                    var changes = $grid.pqGrid("getChanges", { format: 'raw' });
                    try {
                        console.log(changes);
                    }
                    catch (ex) { }
                    alert("Please see the log of changes in your browser console.");
                }
                },
                    options: { disabled: true }
                },
                { type: 'separator' },
                { type: 'button', icon: 'ui-icon-arrowreturn-1-s', label: 'Undo', cls: 'changes', listener:
                { "click": function (evt, ui) {
                    $grid.pqGrid("history", { method: 'undo' });
                }
                },
                    options: { disabled: true }
                },
                { type: 'button', icon: 'ui-icon-arrowrefresh-1-s', label: 'Redo', listener:
                { "click": function (evt, ui) {
                    $grid.pqGrid("history", { method: 'redo' });
                }
                },
                    options: { disabled: true }
                },
                { type: "<span style='margin:5px;'>Filter</span>" },
                { type: 'textbox', attr: 'placeholder="Enter your keyword"', cls: "filterValue", listeners: [{ 'change': filterhandler}] },
                { type: 'select', cls: "filterColumn",
                    listeners: [{ 'change': filterhandler}],
                    options: function (ui) {
                        var CM = ui.colModel;
                        var opts = [{ '': '[ All Fields ]'}];
                        for (var i = 0; i < CM.length; i++) {
                            var column = CM[i];
                            var obj = {};
                            obj[column.dataIndx] = column.title;
                            opts.push(obj);
                        }
                        return opts;
                    }
                },
                { type: 'select', style: "margin:0px 5px;", cls: "filterCondition",
                    listeners: [{ 'change': filterhandler}],
                    options: [
                        { "begin": "Begins With" },
                        { "contain": "Contains" },
                        { "end": "Ends With" },
                        { "notcontain": "Does not contain" },
                        { "equal": "Equal To" },
                        { "notequal": "Not Equal To" },
                        { "empty": "Empty" },
                        { "notempty": "Not Empty" },
                        { "less": "Less Than" },
                        { "great": "Great Than" }
                    ]
                }
            ]
        },
        scrollModel: {
            autoFit: true
        },
        selectionModel: {
            type: 'cell'
        },
        swipeModel: { on: false },
        editModel: {
            onBlur: 'validate',
            saveKey: $.ui.keyCode.ENTER
        },
        editor: {
            select: true
        },
        title: "<b>Batch Editing</b>",
        history: function (evt, ui) {
            var $grid = $(this);
            if (ui.canUndo != null) {
                $("button.changes", $grid).button("option", { disabled: !ui.canUndo });
            }
            if (ui.canRedo != null) {
                $("button:contains('Redo')", $grid).button("option", "disabled", !ui.canRedo);
            }
            $("button:contains('Undo')", $grid).button("option", { label: 'Undo (' + ui.num_undo + ')' });
            $("button:contains('Redo')", $grid).button("option", { label: 'Redo (' + ui.num_redo + ')' });
        },
        colModel: [
            { title: "Product ID", dataType: "integer", dataIndx: "ProductID", editable: false, width: 80 },
            { title: "Product Name", width: 165, dataType: "string", dataIndx: "ProductName",
                validations: [
                    { type: 'minLen', value: 1, msg: "Required" },
                    { type: 'maxLen', value: 40, msg: "length should be <= 40" }
                ]
            },
            { title: "Quantity Per Unit", width: 140, dataType: "string", align: "right", dataIndx: "QuantityPerUnit",
                validations: [
                    { type: 'minLen', value: 1, msg: "Required." },
                    { type: 'maxLen', value: 20, msg: "length should be <= 20" }
                ]
            },
            { title: "Unit Price", width: 100, dataType: "float", align: "right", dataIndx: "UnitPrice",
                validations: [{ type: 'gt', value: 0.5, msg: "should be > 0.5"}],
                render: function (ui) {
                    //debugger;
                    var cellData = ui.cellData;
                    if (cellData != null) {
                        return "$" + parseFloat(ui.cellData).toFixed(2);
                    }
                    else {
                        return "";
                    }
                }
            },
            { hidden: true },
            { title: "Units In Stock", width: 100, dataType: "integer", align: "right", dataIndx: "UnitsInStock",
                validations: [{ type: 'gte', value: 1, msg: "should be >= 1" },
                    { type: 'lte', value: 1000, msg: "should be <= 1000" }
                ]
            },
            { title: "Discontinued", width: 100, dataType: "bool", align: "center", dataIndx: "Discontinued",
                editor: { type: "checkbox", subtype: 'triple', style: "margin:3px 5px;" },
                validations: [{ type: 'nonEmpty', msg: "Required"}]
            },
            { title: "", editable: false, minWidth: 83, sortable: false,
                render: function (ui) {
                    return "<button type='button' class='delete_btn'>Delete</button>";
                }
            }
        ],
        pageModel: { type: "local", rPP: 20 },
        dataModel: {
            dataType: "JSON",
            location: "remote",
            recIndx: "ProductID",
            url: "/pro/products/get", //for ASP.NET
            //url: "/pro/products.php", //for PHP
            getData: function (response) {
                return { data: response.data };
            }
        },
        refresh: function () {
            $("#grid_editing").find("button.delete_btn").button({ icons: { primary: 'ui-icon-scissors'} })
                .unbind("click")
                .bind("click", function (evt) {
                    var $tr = $(this).closest("tr");
                    var obj = $grid.pqGrid("getRowIndx", { $tr: $tr });
                    var rowIndx = obj.rowIndx;
                    $grid.pqGrid("addClass", { rowIndx: rowIndx, cls: 'pq-row-delete' });

                    var ans = window.confirm("Are you sure to delete row No " + (rowIndx + 1) + "?");
                    $grid.pqGrid("removeClass", { rowIndx: rowIndx, cls: 'pq-row-delete' });
                    if (ans) {
                        $grid.pqGrid("deleteRow", { rowIndx: rowIndx });
                    }
                });
        }
    };
    var $grid = $("#grid_editing").pqGrid(obj);
});




paramvir

  • Administrator
  • Hero Member
  • *****
  • Posts: 6320
    • View Profile
Re: Filter, add row, then save, remove filter - nbr of rows double
« Reply #3 on: August 26, 2015, 08:12:24 am »
Thanks for re posting the code. Of course you can layout the UI the way you want but I see you have done a hard refresh by calling refreshDataAndView inside the $.ajax success callback. Ideally refreshDataAndView should clear the filtered data too, it has been taken note of and would be addressed in the next versions.

As a fix you can clear the filtered data yourself before calling refreshDataAndView by grid.filter({oper:'replace', data:[]});

Code: [Select]
success: function (changes) {
                    //debugger;
                    grid.commit();
                    grid.filter({oper:'replace', data:[]});
    $(".filterValue").val("");
                    grid.refreshDataAndView();
                    grid.history({ method: 'reset' });
                },

Please let me know whether it fixes the issue experienced by you.
« Last Edit: August 26, 2015, 08:16:40 am by paramquery »