如果启用无限滚动,如何防止 Kendo 网格加载数据两次?

How to prevent the Kendo grid from loading the data twice if endless scrolling is enabled?

我有一个 Kendo 网格,服务器端 paging/sorting/filtering 并且启用了无限滚动。在这种情况下,我遇到了一个问题,即在过滤网格时,数据会加载两次。第一次加载所有数据,第二次加载筛选后的数据。

要重现该问题,您必须执行以下步骤。

代码示例:https://dojo.telerik.com/@Ruben/OnODErav

  1. 在网格中向下滚动,直到加载新数据
  2. 现在控制台中应该有事件"Grid data bound"两次
  3. 在任何列上设置任何过滤器
  4. 现在您在控制台中有四次 "Grid data bound" 事件,而不是三次!

错误仅在您向下滚动后出现。如果您重新启动并只执行第三步,您将看到该事件仅被触发两次(第一次和过滤后),这是正确的。

有人知道如何防止它加载数据两次吗?

   function onDataBound(arg) {
                    kendoConsole.log("Grid data bound");
                }
              
                $(document).ready(function() {
                    $("#grid").kendoGrid({
                        dataSource: {
                            type: "odata",
                            transport: {
                                read: "https://demos.telerik.com/kendo-ui/service/Northwind.svc/Orders"
                            },
                            schema: {
                                model: {
                                    fields: {
                                        OrderID: { type: "number" },
                                        Freight: { type: "number" },
                                        ShipName: { type: "string" },
                                        OrderDate: { type: "date" },
                                        ShipCity: { type: "string" }
                                    }
                                }
                            },
                            pageSize: 20,
                            serverPaging: true,
                            serverFiltering: true,
                            serverSorting: true
                        },
                        height: 550,
                        dataBound: onDataBound,
                        filterable: true,
                        sortable: true,
                        scrollable: {
                            endless: true
                        },
                        pageable: {
                            numeric: false,
                            previousNext: false
                        },
                        columns: [{
                                field:"OrderID",
                                filterable: false
                            },
                            "Freight",
                            {
                                field: "OrderDate",
                                title: "Order Date",
                                format: "{0:MM/dd/yyyy}"
                            }, {
                                field: "ShipName",
                                title: "Ship Name"
                            }, {
                                field: "ShipCity",
                                title: "Ship City"
                            }
                        ]
                    });
                });
                
                 (function($, undefined){
    window.kendoConsole = {
        log: function(message, isError, container) {
            var lastContainer = $(".console div:first", container),
                counter = lastContainer.find(".count").detach(),
                lastMessage = lastContainer.text(),
                count = 1 * (counter.text() || 1);

            lastContainer.append(counter);

            if (!lastContainer.length || message !== lastMessage) {
                $("<div" + (isError ? " class='error'" : "") + "/>")
                    .css({
                        marginTop: -24,
                        backgroundColor: isError ? "#ffbbbb" : "#b2ebf2"
                    })
                    .html(message)
                    .prependTo($(".console", container))
                    .animate({ marginTop: 0 }, 300)
                    .animate({ backgroundColor: isError ? "#ffdddd" : "#ffffff" }, 800);
            } else {
                count++;

                if (counter.length) {
                    counter.html(count);
                } else {
                    lastContainer.html(lastMessage)
                        .append("<span class='count'>" + count + "</span>");
                }
            }
        },

        error: function(message) {
            this.log(message, true);
        }
    };
})(jQuery);

/*
 * jQuery Color Animations
 * Copyright 2007 John Resig
 * Released under the MIT and GPL licenses.
 */

(function(jQuery) {

    // We override the animation for all of these color styles
    jQuery.each(["backgroundColor", "borderBottomColor", "borderLeftColor", "borderRightColor", "borderTopColor", "color", "outlineColor"], function(i, attr) {
        jQuery.fx.step[attr] = function(fx) {
            if (!fx.state || typeof fx.end == typeof "") {
                fx.start = getColor(fx.elem, attr);
                fx.end = getRGB(fx.end);
            }

            fx.elem.style[attr] = ["rgb(", [
                Math.max(Math.min(parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0], 10), 255), 0),
                Math.max(Math.min(parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1], 10), 255), 0),
                Math.max(Math.min(parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2], 10), 255), 0)
            ].join(","), ")"].join("");
        };
    });

    // Color Conversion functions from highlightFade
    // By Blair Mitchelmore
    // http://jquery.offput.ca/highlightFade/

    // Parse strings looking for color tuples [255,255,255]
    function getRGB(color) {
        var result;

        // Check if we're already dealing with an array of colors
        if (color && color.constructor == Array && color.length == 3) {
            return color;
        }

        // Look for rgb(num,num,num)
        result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color);
        if (result) {
            return [parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10)];
        }

        // Look for #a0b1c2
        result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color);
        if (result) {
            return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)];
        }

        // Otherwise, we're most likely dealing with a named color
        return jQuery.trim(color).toLowerCase();
    }

    function getColor(elem, attr) {
        var color;

        do {
            color = jQuery.css(elem, attr);

            // Keep going until we find an element that has color, or we hit the body
            if (color && color != "transparent" || jQuery.nodeName(elem, "body")) {
                break;
            }

            attr = "backgroundColor";

            elem = elem.parentNode;
        } while (elem);

        return getRGB(color);
    }

    var href = window.location.href;
    if (href.indexOf("culture") > -1) {
        $("#culture").val(href.replace(/(.*)culture=([^&]*)/, ""));
    }

    function onlocalizationchange() {
        var value = $(this).val();
        var href = window.location.href;
        if (href.indexOf("culture") > -1) {
            href = href.replace(/culture=([^&]*)/, "culture=" + value);
        } else {
            href += href.indexOf("?") > -1 ? "&culture=" + value : "?culture=" + value;
        }
        window.location.href = href;
    }

    $("#culture").change(onlocalizationchange);
})(jQuery);
/*global*/

.floatWrap:after,#example:after{content:"";display:block;clear:both}
.floatWrap,#example{display:inline-block}
.floatWrap,#example{display:block}
.clear{clear:both}

body,h1,h2,h3,h4,p,ul,li,a,button
{
    margin:0;
    padding:0;
    list-style:none;
}

html
{
    top: 0;
    left: 0;
    overflow-y:scroll;
    font:75% Arial, Helvetica, sans-serif;
    background: #f5f7f8;
}
body
{
    margin: 0;
    padding: 0;
}

a,
li>a,
h2>a,
h3>a,
a
{
    text-decoration:none;
    -webkit-tap-highlight-color: rgba(0,0,0,0);
}

a
{
    color: #0487c4;
}

a:hover
{
    text-decoration: underline;
}

.page
{
    max-width:72%;
    margin: 2% auto;
    padding: 3% 5% 0;
    background: #fff;
    border: 1px solid #e2e4e7;
}

.offline-button {
    display: inline-block;
    margin: 0 0 30px;
    padding: 9px 23px;
    background-color: #015991;
    border-radius: 2px;
    color: #fff;
    text-decoration: none;
    font-size: 13px;
    font-weight: 700;
    line-height: 1.2;
    transition-duration: 0.2s;
    transition-property: background-color;
    transition-timing-function: ease;
}

.offline-button:hover {
    background-color: #013a5e;
    color: #fff;
    text-decoration: none;
}

#example
{
    margin: 2em 0 5em;
    padding: 0;
    border: 0;
    background: transparent;
    font-size: 14px;
}

/*console*/

.console
{
    background-color: transparent;
    color: #333;
    font: 11px Consolas, Monaco, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
    margin: 0;
    overflow-x: hidden;
    text-align: left;
    height: 200px;
    border: 1px solid rgba(20,53,80,0.1);
    background-color: #ffffff;
    text-indent: 0;
}

.demo-section .box-col .console
{
    min-width: 200px;
}

.console .count
{
    background-color: #26c6da;
    -moz-border-radius: 15px;
    -webkit-border-radius: 15px;
    border-radius: 15px;
    color: #ffffff;
    font-size: 10px;
    margin-left: 5px;
    padding: 2px 6px 2px 5px;
}

.console div
{
    background-position: 6px -95px;
    border-bottom: 1px solid #DDD;
    padding: 5px 10px;
    height: 2em;
    line-height: 2em;
    vertical-align: middle;
}

.console .error
{
    background-position: 6px -135px;
}

/*configurator*/

.centerWrap .configuration,
.configuration,
.configuration-horizontal
{
    margin: 4.5em auto;
    padding: 3em;
    background-color: rgba(20,53,80,0.038);
    border: 1px solid rgba(20,53,80,0.05);
}

.absConf .configuration
{
    position: absolute;
    top: -1px;
    right: -1px;
    height: auto;
    margin: 0;
    z-index: 2;
}

.configuration-horizontal
{
    position: static;
    height: auto;
    min-height: 0;
    margin: 0 auto;
    zoom: 1;
}

.configuration-horizontal-bottom
{
    margin: 20px -21px -21px;
    position: static;
    height: auto;
    min-height: 0;
    width: auto;
    float:none;
}

.configuration .configHead,
.configuration .infoHead,
.configuration-horizontal .configHead,
.configuration-horizontal .infoHead
{
    display: block;
    margin-bottom: 1em;
    font-size: 12px;
    line-height: 1em;
    font-weight: bold;
    text-transform: uppercase;
}


.configuration .configTitle,
.configuration-horizontal .configTitle
{
    font-size: 12px;
    display: block;
    line-height: 22px;
}

.configuration .options,
.configuration-horizontal .options
{
    list-style:none;
    margin: 0;
    padding: 0;
}

.configuration button,
.configuration-horizontal button
{
    margin: 0;
    vertical-align: middle;
}

.configuration .k-textbox,
.configuration-horizontal .k-textbox
{
    margin-left: 7px;
    width: 30px;
}

.configuration .options li { display: block; margin: 0; padding: 0.2em 0; zoom: 1; }

.configuration .options li:after,
.configuration-horizontal:after
{
    content: "";
    display: block;
    clear: both;
    height: 0;
}

.configuration-horizontal .config-section
{
    display: block;
    float: left;
    min-width: 200px;
    margin: 0;
    padding: 10px 20px 10px 10px;
}

.configuration label,
.configuration input
{
    vertical-align: middle;
    line-height: 20px;
    margin-top: 0;
}

.configuration label
{
    float: left;
}

.configuration input
{
    width: 40px;
}

.configuration input,
.configuration select,
.configuration .k-numerictextbox
{
    float: right;
}

.configuration input.k-input
{
    float: none;
}

.configuration .k-button,
.configuration .k-widget
{
    margin-bottom: 3px;
}

/* Code Viewer */
.source {
    background-color: #f5f7f8;
    margin: 0 0 5em;
    border: 1px solid rgba(20,53,80,0.05);
}
.source .code {
    background-color: #fff;
    border-top: 1px solid rgba(20,53,80,0.08);
    padding: 20px 0 0;
}
.source .code pre {
    margin: 0;
    padding: 0 20px 20px;
}
.source .offline-button {
    background: none;
    text-decoration: none;
    color: #0487c4;
    margin: 10px 0 10px 14px;
    padding: 10px;
}

.source .offline-button.selected {
    color: #000;
}

.source .code .controller {
    display: none;
}

/* Pretty Print */
.prettyprint
{
    font-size: 12px;
    overflow: auto;
}

pre .nocode { background-color: transparent; color: #000; }
pre .str,                    /* string */
pre .atv { color: #2db245; } /* attribute value */
pre .kwd { color: #ff3399; } /* keyword */
pre .com { color: #9933cc; } /* comment */
pre .typ { color: #000; } /* type */
pre .lit { color: #0099ff; } /* literal */
pre .pun { color: #333; }    /* punctuation */
pre .pln { color: #3e526b; }    /* plaintext */
pre .tag { color: #3e526b; } /* html/xml tag */
pre .atn { color: #3e526b; } /* attribute name */
pre .dec { color: #3e526b; } /* decimal */

/* Specify class=linenums on a pre to get line numbering */
ol.linenums { margin-top: 0; margin-bottom: 0; color: #333; }
li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
li.L1,li.L3,li.L5,li.L7,li.L9 { background: #eee; }

/*keyboard navigation legend */
.key-button {
    display: inline-block;
    text-decoration: none;
    color: #555;
    min-width: 20px;
    margin: 0;
    padding: 3px 5px;
    font-size: 12px;
    text-align: center;
    border-radius: 2px;
    -webkit-border-radius: 2px;
    -moz-border-radius: 2px;
    background: #eee;
    box-shadow: 0 1px 0 1px rgba(0,0,0,0.1), 0 2px 0 rgba(0,0,0,0.1);
}
.widest {}
.wider {}
.wide {}
.leftAlign, .rightAlign, .centerAlign {text-align: left;}

.letter {
    padding-top: 14px;
    padding-bottom: 11px;
    font-weight: bold;
    font-size: 17px;
}

ul.keyboard-legend {
    list-style-type: none;
    margin: 0 auto;
    padding: 0;
    text-align: left;
}

#example ul.keyboard-legend li,
.demo-section .box-col ul.keyboard-legend li {
    display: block;
    margin: 0;
    padding: 4px 0;
    line-height: 1.5em;
}

ul.keyboard-legend li a {
    color: #0487C4;
}


.button-preview {
    display: inline-block;
    vertical-align: top;
    padding: 0 5px 0 0;
}
.button-descr {
    display: inline-block;
    vertical-align: top;
    text-align: left;
    padding: 3px 0;
}

.demo-section p a.hyperlink,
.config-section a {
    color: #e15613;
    text-decoration: none;
}

.chart-wrapper,
.map-wrapper,
.diagram-wrapper {
    position: relative;
    height: 430px;
    margin: 0 auto 15px auto;
    padding: 10px;
}

#example.absConf .chart-wrapper,
#example.absConf .map-wrapper,
#example.absConf .diagram-wrapper
{
    margin-left: 0;
}

.chart-wrapper .k-chart,
.map-wrapper .k-map,
.diagram-wrapper .k-diagram {
    height: 430px;
}

.config-section.console-section
{
    width: 400px;
    float: right;
}

#page > h2 {
    float: none;
    text-align: center;
    width: auto;
    padding: 5em 0 1em;
    font-size: 3em;
}

#suites .imgPlate,
#suites .box {
    border-width: 0;
    -webkit-box-shadow: none;
    -moz-box-shadow: none;
    box-shadow: none;
}

#suites {
    text-align: center;
}

#suites .box {
    float: none;
    clear: none;
    display: inline-block;
    width: auto;
    min-width: auto;
}

#suites .box h2 {
    margin-top: 1em;
}

#draggable
{
    cursor: pointer;
    position: absolute;
    top: 210px;
    left: 30px;
    border: 1px solid #ff8000;
    width: 78px;
    height: 78px;
    border-radius: 37px;
    box-shadow: 2px 0 10px #9d9d9d;
    background: #ffcc00 url(../../web/dragdrop/draggable.png) 50% 50% no-repeat;
    background: url(../../web/dragdrop/draggable.png) 50% 50% no-repeat, -moz-linear-gradient(top, #ffcc00 0%, #ff8000 100%);
    background: url(../../web/dragdrop/draggable.png) 50% 50% no-repeat, -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffcc00), color-stop(100%,#ff8000));
    background: url(../../web/dragdrop/draggable.png) 50% 50% no-repeat, -webkit-linear-gradient(top, #ffcc00 0%,#ff8000 100%);
    background: url(../../web/dragdrop/draggable.png) 50% 50% no-repeat, -o-linear-gradient(top, #ffcc00 0%,#ff8000 100%);
    background: url(../../web/dragdrop/draggable.png) 50% 50% no-repeat, -ms-linear-gradient(top, #ffcc00 0%,#ff8000 100%);
    background: url(../../web/dragdrop/draggable.png) 50% 50% no-repeat, linear-gradient(top, #ffcc00 0%,#ff8000 100%);
}

#draggable.hollow
{
    cursor: default;
    background: #ececec;
    border-color: #cbcbcb;
}

/* Box Styles */

.box {
    margin: 4.5em 7.5em;
    padding: 3em;
    background-color: rgba(20,53,80,0.038);
    border: 1px solid rgba(20,53,80,0.05);
}

.demo-section {
    margin: 0 auto 4.5em;
    padding: 3em;
    border: 1px solid rgba(20,53,80,0.14);
}

.demo-section:not(.wide),
#example .box:not(.wide) {
    max-width: 400px;
}

.box:after,
.demo-section:after {
    content: "";
    display: block;
    clear: both;
    height: 0;
}

#example .box {
    margin: 4.5em auto;
}

#example .box:first-child {
    margin-top: 0;
}

.demo-section.k-content {
    box-shadow: 0 1px 2px 1px rgba(0,0,0,.08), 0 3px 6px rgba(0,0,0,.08);
}

.box h4,
.demo-section h4 {
    margin-bottom: 1em;
    font-size: 12px;
    line-height: 1em;
    font-weight: bold;
    text-transform: uppercase;
}
.box-col {
    display: block;
    float: left;
    padding: 0 3em 1.667em 0;
}

.box ul:not(.km-widget) li,
.demo-section .box-col ul:not(.km-widget) li {
    line-height: 3em;
}

.box li:last-child {
    margin-bottom: 0;
}

.box li a {
    font-size: 13px;
}

.box .k-widget {
    background-color: #ebeef0;
    border-color: #ccc;
    color: #7c7c7c;
}
.box .k-widget.k-slider {
    background-color: transparent;
}

.box .k-button {
    cursor: pointer;
    border-radius: 2px;
    font-size: inherit;
    color: #333;
    background: #e2e4e7;
    border-color: #e2e4e7;
    min-width: 90px;
    box-shadow: none;
}

.box .k-upload-status .k-button-bare {
    min-width: 0;
}

.box .k-button:hover,
.box .k-button:focus:active:not(.k-state-disabled):not([disabled]),
.box .k-button:focus:not(.k-state-disabled):not([disabled]) {
    background: #cad0d6;
    border-color: #cad0d6;
    color: #000;
    box-shadow: none;
}

.box .k-primary {
    color: #fff;
    background: #015991;
    border-color: #015991;
}

.box .k-primary:hover,
.box .k-primary:focus:active:not(.k-state-disabled):not([disabled]),
.box .k-primary:focus:not(.k-state-disabled):not([disabled]) {
    background: #013A5E;
    border-color: #013A5E;
    color: #fff;
}

.box .k-textbox,
.box textarea {
    background: #fff;
    border-color: #e2e4e7;
    color: #555;
    border-radius: 2px;
}

.box .k-textbox:hover,
.box .k-textbox:active,
.box .k-textbox:focus,
.box textarea:hover,
.box textarea:active,
.box textarea:focus {
    border-color: #cad0d6;
    background: #fff;
    color: #333;
    box-shadow: none;
}

.box.demo-description p {
    line-height: 1.5em;
    max-width: 1000px;
    padding-bottom: 1em;
}

.box.demo-description p:last-child {
    padding-bottom: 0;
}

.box.demo-description ul,
.box.demo-description ul li {
    list-style: disc inside;
    line-height: 1.5em;
    max-width: 1000px;
}

.box.demo-description ol,
.box.demo-description ol li {
    list-style: decimal inside;
    line-height: 1.5em;
    max-width: 1000px;
}

.box.demo-description ul,
.box.demo-description ol {
    margin: 1em;
    padding: 0;
}

.demo-hint {
    line-height: 22px;
    color: #aaa;
    font-style: italic;
    font-size: .9em;
    padding-top: 1em;
}

.responsive-message {
    font-size: 17px;
    display: none;
    margin: 4em auto;
    padding: 2.5em;
    text-align: center;
    background-color: #ffda3f;
    color: #000;
}

.responsive-message:before {
    content: "This demo requires browser or device screen width to be equal or greater than 1024px.";
}

@media screen and (max-width: 1023px) {
  .page {
    max-width:100%;
    margin: 0;
    padding: 10px;
    background: #fff;
    border: 0;
  }
  
  .hidden-on-narrow {
    display: none !important;
  }
  .responsive-message {
    display: block;
  }
}
            div.console div {
                height: auto;
            }
<!DOCTYPE html>
<html>
<head>
    <base href="https://demos.telerik.com/kendo-ui/grid/remote-data-binding">
    <style>html { font-size: 14px; font-family: Arial, Helvetica, sans-serif; }</style>
    <title></title>
    <link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.3.1023/styles/kendo.default-v2.min.css" />

    <script src="https://kendo.cdn.telerik.com/2019.3.1023/js/jquery.min.js"></script>
    <script src="https://kendo.cdn.telerik.com/2019.3.1023/js/kendo.all.min.js"></script>
    

</head>
<body>

        <div id="example">
            <div id="grid"></div>

            <div class="box wide">
                <h4>Console log</h4>
                <div class="console"></div>
            </div>
        </div>



</body>
</html>

不确定如何预防。

在 kendo.all.js 的深处,在 FilterMenu 小部件过滤方法中,似乎发生了加倍:

            filter: function (expression) {
                var filters = this._stripFilters(expression.filters);
                if (filters.length && this.trigger('change', {
                        filter: {
                            logic: expression.logic,
                            filters: filters
                        },
                        field: this.field
                    })) {
                    return;
                }
                expression = this._merge(expression);
                if (expression.filters.length) {
                    this.dataSource.filter(expression);
                }
            },

第一次在导致加载数据页的滚动之后,UI 过滤将导致:

  • this.trigger('change', 发出导致第一个 dataBound
  • 的请求
  • this.dataSource.filter(expression); 发出导致第二个 dataBound
  • 的请求

查看调试器网络选项卡,您将看到框架发出两个 GET 请求。 (我将页面大小更改为 8,因此 URL 参数具有 top=8):

  • https://demos.telerik.com/kendo-ui/service/Northwind.svc/Orders?$callback=jQuery1123011816937403352101_1583588644905&%24inlinecount=allpages&%24format=json&%24top=8
  • https://demos.telerik.com/kendo-ui/service/Northwind.svc/Orders?$callback=jQuery1123011816937403352101_1583588644905&%24inlinecount=allpages&%24format=json&%24top=8&%24filter=startswith(ShipName%2C%27h%27)

最好的回答可能来自 Progress forums。您可能需要注册或获得 post 新问题的许可。

问题似乎是设计使然,近期不会更改。

We are considering improvements in dataSource requests. The current state is with two calls because we use the first one to reset the state of the datasource and the second one to apply the sorting.

来源:https://github.com/telerik/kendo-ui-core/issues/5462#issuecomment-563259073