Ag-grid 视口:无法读取未定义的 属性 'bind'

Ag-grid viewport: cannot read property 'bind' of undefined

我正在复制 ag-grid 视口示例,将其放入表示 table 而不是匿名函数的 object 中。我不明白为什么它在尝试连接到模拟服务器时给我标题中的错误,因为它是完全相同的代码。

这是似乎不起作用的代码:

   function ViewportDatasource(mockServer) {
        this.mockServer = mockServer;
        this.connectionId = this.mockServer.connect(this.eventListener.bind(this));
    }

    ViewportDatasource.prototype.eventListener = function (event) {
        switch (event.eventType) {
            case 'rowCountChanged':
                this.onRowCountChanged(event);
                break;
            case 'rowData':
                this.onRowData(event);
                break;
            case 'dataUpdated':
                this.onDataUpdated(event);
                break;
        }
    };

这是全部object:

function WatcherTable($rootScope,$scope, $http) {

    this.FIELD_KEY_ID = "id";
    this.FIELD_KEY_ISSUER_SHORT_DESC= "isd";
    this.FIELD_KEY_ISSUER = "iss";
    this.FIELD_KEY_CUSIP = "cus";
    this.FIELD_KEY_ISIN = "isin";
    this.FIELD_KEY_BOARD_LABEL = "lbl";
    this.FIELD_KEY_IND_SECT_LABEL = "isl";
    this.FIELD_KEY_CURR="cur"

    this.FIELD_KEY_BEST_BID_SIZE = "bBidSz";
    this.FIELD_KEY_BEST_BID_PRICE = "bBidPrc";
    this.FIELD_KEY_BEST_ASK_PRICE = "bAskPrc";
    this.FIELD_KEY_BEST_ASK_SIZE = "bAskSz";
    this.FIELD_KEY_BEST_BID_SPREAD = "bBidSpd";
    this.FIELD_KEY_BEST_ASK_SPREAD = "bAskSpd";

    this.FIELD_KEY_ORDER_BID_SIZE = "oBidSz";
    this.FIELD_KEY_ORDER_BID_PRICE = "oBidPrc";
    this.FIELD_KEY_ORDER_ASK_PRICE = "oAskPrc";
    this.FIELD_KEY_ORDER_ASK_SIZE = "oAskSz";
    this.FIELD_KEY_ORDER_BID_SPREAD = "oBidSpd";
    this.FIELD_KEY_ORDER_ASK_SPREAD = "oAskSpd";

    this.headerMyOrder="ORDER";
    this.headerMyBidOrder="BID ORDER";
    this.headerMyAskOrder="ASK ORDER";


    this.cols = [{
        headerName : "Security Info",
        marryChildren : true,
        children : [{
            headerName : "Issuer Short Desc.",
            field : this.FIELD_KEY_ISSUER_SHORT_DESC,

            //width : 0,
            //hide : true
        },
        {
            headerName : "Industry Sector Label",
            field : this.FIELD_KEY_IND_SECT_LABEL,
            //width : 80,
            filter: 'set',
            filterParams: { values: ['Advertising', 'Aerospace/Defense', 'Agriculture', 'Airlines', 'Apparel', 'Auto Manufacturers', 'Auto Parts&Equipment', 'Banks', 'Basic Materials', 'Beverages', 'Biotechnology', 'Building Materials', 'Chemicals', 'Coal', 'Commercial Services', 'Communications', 'Computers', 'Consumer, Cyclical', 'Consumer, Non-cyclical', 'Cosmetics/Personal Care', 'Distribution/Wholesale', 'Diversified', 'Electrical Compo&Equip', 'Electronics', 'Energy', 'Energy-Alternate Sources', 'Engineering&Construction', 'Entertainment', 'Machinery-Constr&Mining', 'Household Products/WaresIndustrial', 'Insurance', 'Internet', 'Investment Companies', 'Iron/Steel', 'Kangaroos', 'Leisure Time', 'Lodging', 'Machinery-Constr&Mining', 'Machinery-Diversified', 'Media', 'Metal Fabricate/Hardware', 'Mining', 'Miscellaneous Manufactur', 'Multi-National', 'Office Furnishings', 'Office/Business ', 'Oil&Gas', 'Oil&Gas Services', 'Packaging&Containers', 'Pharmaceuticals', 'Pipelines', 'Real Estate', 'Regional(state/provnc)', 'REITS', 'Retail', 'Savings&Loans'],
                            newRowsAction: 'keep'},
            },
        {
            headerName : "Board Label",
            field : this.FIELD_KEY_BOARD_LABEL,
            //width : 80,
            filter: 'text',
            filterParams: { apply: true }

        }, {
            headerName : "CUSIP",
            field : this.FIELD_KEY_CUSIP,
            //width : 150,
            //suppressFilter: true
        },
        {
            headerName : "ISIN",
            field : this.FIELD_KEY_ISIN,
            //width : 150,
            //suppressFilter: true
        },
        {
            headerName : "Currency",
            field : this.FIELD_KEY_CURR,
            //width : 150,
            //suppressFilter: true
        }
        ]},
        {
        headerName : "Best",
        marryChildren : true,

        children : [ {
            headerName : "Bid Size",
            pinned: 'right',
            field : this.FIELD_KEY_BEST_BID_SIZE,
            cellStyle: {'border-left': '1px solid #E82043', 'color':'#E82043', 'font-weight':'bold', 'text-align':'right'},
            cellRenderer: BidRenderer,
            filter:'number',


            //width : 125,
            //suppressFilter: true
        }, {
            headerName : "Bid Price",
            pinned: 'right',
            field : this.FIELD_KEY_BEST_BID_PRICE,
            cellStyle: {'border-right': '1px solid #E82043', 'color':'#E82043', 'font-weight':'bold', 'text-align':'right'},
            //cellRenderer: 'animateShowChange',
            cellFormatter: numberFormatter,
            cellRenderer: BidRenderer,
            width : 125,
            filter:'number',


            //suppressFilter: true
        },
        {
            headerName : "Ask Price",
            pinned: 'right',
            field : this.FIELD_KEY_BEST_ASK_PRICE,
            //cellRenderer: 'animateShowChange',
            cellFormatter: numberFormatter,
            cellRenderer: AskRenderer,
            cellStyle: {'color':'#19B092', 'font-weight':'bold', 'text-align':'right'},
            filter:'number',
            //width : 125,
            //suppressFilter: true
        },
        {
            headerName : "Ask Size",
            pinned: 'right',
            field : this.FIELD_KEY_BEST_ASK_SIZE,
            cellStyle: {'border-right': '1px solid #19B092', 'color':'#19B092', 'font-weight':'bold', 'text-align':'right'},
            cellRenderer: AskRenderer,
            filter:'number',
            //width : 125,
            //suppressFilter: true
        } ]
    } ];


    //definizione della tabella//// rowModelType: 'virtual', rowModelType: 'pagination',



    this.table = {
            showPrice: true,
            showSpread: true,
            orderSize: 0,
            orderFilter: '',
            enableServerSideFilter: true,
            enableServerSideSorting: true,
            sortingOrder: ['desc','asc',null],
            enableColResize : true,
            debug : true,
            rowSelection : 'multiple',
            rowDeselection : true,
            columnDefs : this.cols,
            rowModelType : 'viewport',
            headerHeight: 20,
            rowHeight: 20,
            viewportRowModelPageSize:30,
            viewportRowModelBufferSize: 15,         
            suppressColumnVirtualisation: true, 
            suppressMenuColumnPanel: true,
            onCellDoubleClicked: WatcherTable.prototype.onCellDoubleClicked.bind(this,$rootScope),          
            getContextMenuItems: WatcherTable.prototype.getContextMenuItems.bind(this,$rootScope),
            getMainMenuItems: WatcherTable.prototype.getMainMenuItems.bind(this, $rootScope), 
            getRowNodeId: function (data) {
                // the code is unique, so perfect for the id
                return data.isin;
            },
            onGridReady:setRowData($http)
    };

    function numberFormatter(params) {
        if (typeof params.value === 'number') {
            return params.value.toFixed(4);
        } else {
            return params.value;
        }
    }



    function BidRenderer () {}

    BidRenderer.prototype.init = function(params) {
        // create the cell
        this.eGui = document.createElement('div');
        this.eGui.innerHTML = '<div></div>';

        // set value into cell
        this.eValue = this.eGui.querySelectorAll('div')[0];
        this.eValue.innerHTML = params.valueFormatted ? params.valueFormatted : params.value;

    };

    // gets called once when grid ready to insert the element
    BidRenderer.prototype.getGui = function() {
        return this.eGui;
    };

    // gets called whenever the user gets the cell to refresh
    BidRenderer.prototype.refresh = function(params) {
        // set value into cell again
        this.eGui.innerHTML = '<div class="bid"></div>';
        this.eValue = this.eGui.querySelectorAll('div')[0];
        this.eValue.innerHTML = params.valueFormatted ? params.valueFormatted : params.value;

    };

    function AskRenderer () {}

    AskRenderer.prototype.init = function(params) {
        // create the cell
        this.eGui = document.createElement('div');
        this.eGui.innerHTML = '<div></div>';

        // set value into cell
        this.eValue = this.eGui.querySelectorAll('div')[0];
        this.eValue.innerHTML = params.valueFormatted ? params.valueFormatted : params.value;

    };

    // gets called once when grid ready to insert the element
    AskRenderer.prototype.getGui = function() {
        return this.eGui;
    };

    // gets called whenever the user gets the cell to refresh
    AskRenderer.prototype.refresh = function(params) {
        // set value into cell again
        this.eGui.innerHTML = '<div class="ask"></div>';
        this.eValue = this.eGui.querySelectorAll('div')[0];
        this.eValue.innerHTML = params.valueFormatted ? params.valueFormatted : params.value;
    };

    // client code (ie your code) will call this constructor, pass in whatever you need for the
    // viewport to do it's job
    function ViewportDatasource(mockServer) {
        this.mockServer = mockServer;
        this.connectionId = this.mockServer.connect(this.eventListener.bind(this));
    }

    // gets called by the grid, tells us what rows the grid is displaying, so time for
    // us to tell the server to give us the rows for that displayed range
    ViewportDatasource.prototype.setViewportRange = function (firstRow, lastRow) {
        this.mockServer.setViewportRange(this.connectionId, firstRow, lastRow);
    };

    // gets called by the grid, provides us with the callbacks we need
    ViewportDatasource.prototype.init = function (params) {
        this.params = params;
    };

    // gets called by grid, when grid is destroyed or this datasource is swapped out for another one
    ViewportDatasource.prototype.destroy = function () {
        this.mockServer.disconnect(this.connectionId);
    };

    // manages events back from the server
    ViewportDatasource.prototype.eventListener = function (event) {
        switch (event.eventType) {
            case 'rowCountChanged':
                this.onRowCountChanged(event);
                break;
            case 'rowData':
                this.onRowData(event);
                break;
            case 'dataUpdated':
                this.onDataUpdated(event);
                break;
        }
    };

    // process rowData event
    ViewportDatasource.prototype.onRowData = function (event) {
        var rowDataFromServer = event.rowDataMap;
        this.params.setRowData(rowDataFromServer);
    };

    // process dataUpdated event
    ViewportDatasource.prototype.onDataUpdated = function (event) {
        var that = this;
        event.changes.forEach(function (change) {
            var rowNode = that.params.getRow(change.rowIndex);
            // if the rowNode is missing, it means the grid is not displaying that row.
            // if the data is missing, it means the rowNode is there, but that data has not
            // loaded into it yet, so to early to set delta changes.
            if (!rowNode || !rowNode.data) {
                return;
            }
            // rowNode.data[change.columnId] = change.newValue;
            // this is a trick, it gets the row to refresh
            rowNode.setDataValue(change.columnId, change.newValue);
        });
    };

    // process rowCount event
    ViewportDatasource.prototype.onRowCountChanged = function (event) {
        var rowCountFromServer = event.rowCount;
        // this will get the grid to make set the height of the row container, so we can scroll vertically properly
        this.params.setRowCount(rowCountFromServer);
    };

    function setRowData($http) {
        // set up a mock server - real code will not do this, it will contact your
        // real server to get what it needs
        var mockServer = new MockServer();
        $http.get('data.json').then(function(response){
            mockServer.init(response.data);
        });

        var viewportDatasource = new ViewportDatasource(mockServer);
        table.api.setViewportDatasource(viewportDatasource);
        // put the 'size cols to fit' into a timeout, so that the scroll is taken into consideration
        setTimeout(function () {
            table.api.sizeColumnsToFit();
        }, 100);
    }

    // setup the grid after the page has finished loading
   /* document.addEventListener('DOMContentLoaded', function () {
        var gridDiv = document.querySelector('#liveStreamExample');
        new agGrid.Grid(gridDiv, table);

        // do http request to get our sample data - not using any framework to keep the example self contained.
        // you will probably use a framework like JQuery, Angular or something else to do your HTTP calls.
        var httpRequest = new XMLHttpRequest();
        httpRequest.open('GET', 'data.json');
        httpRequest.send();
        httpRequest.onreadystatechange = function () {
            if (httpRequest.readyState == 4 && httpRequest.status == 200) {
                var httpResponse = JSON.parse(httpRequest.responseText);
                setRowData(httpResponse);
            }
        };
    });*/

};

WatcherTable.prototype.getContextMenuItems = function ($rootScope,params){
    var result= [{name:"Show Book", action:WatcherTable.prototype.changeBookSubscription.bind(this,$rootScope,params.node.data)}];

    return result;
}

WatcherTable.prototype.onCellDoubleClicked = function ($rootScope,params){
    $rootScope.$broadcast("changeBookSubscription",{instrkey:params.data.cus+"."+params.data.isin,boardLabel:params.data.isd+" "+params.data.lbl});
    if(params.colDef.field.indexOf("bBid")>-1){
        $rootScope.$broadcast("showHitDialog",params);
        log("Show hit dialog");
        console.log(params);
    }
    else if(params.colDef.field.indexOf("bAsk")>-1){
        $rootScope.$broadcast("showLiftDialog",params);
        log("Show lift dialog");
        console.log(params);
    }
}

WatcherTable.prototype.changeBookSubscription = function ($rootScope,data) {
    $rootScope.$broadcast("changeBookSubscription",{instrkey:data.cus+"."+data.isin,boardLabel:data.isd+" "+data.lbl});
}

WatcherTable.prototype.getMainMenuItems = function($rootScope, params){
    var prcCols=["bBidPrc","bAskPrc","oBidPrc","oAskPrc"];
    var spdCols=["bBidSpd","bAskSpd","oBidSpd","oAskSpd"];
    var menuItems= [
        {name:'Show Price', 
            action:function(){
                        params.columnApi.setColumnsVisible(prcCols, !params.api.gridCore.gridOptions.showPrice);
                        params.api.gridCore.gridOptions.showPrice= !params.api.gridCore.gridOptions.showPrice;      
                    }, 
             checked: params.api.gridCore.gridOptions.showPrice
             },
        {name:'Show Spread', 
        action:function(){
                    params.columnApi.setColumnsVisible(spdCols, !params.api.gridCore.gridOptions.showSpread);
                    params.api.gridCore.gridOptions.showSpread= !params.api.gridCore.gridOptions.showSpread;        
                }, 
         checked: params.api.gridCore.gridOptions.showSpread
         },
         {
             name:'Orders',
                subMenu:[
                    {name:'Live Orders Only', 
                        action:function(){
                        if (params.api.gridCore.gridOptions.orderFilter==='live'){
                            params.api.gridCore.gridOptions.orderFilter='';
                            //TODO filter
                        }
                        else {params.api.gridCore.gridOptions.orderFilter='live';
                            //TODO filter
                        }

                    }, checked:(params.api.gridCore.gridOptions.orderFilter==='live')},
                    {name:'My Orders Only',     action:function(){
                        if (params.api.gridCore.gridOptions.orderFilter==='mine'){
                            params.api.gridCore.gridOptions.orderFilter='';
                            //TODO filter
                        }
                        else {params.api.gridCore.gridOptions.orderFilter='mine';
                            //TODO filter
                        }

                    }, checked:(params.api.gridCore.gridOptions.orderFilter==='mine')},
                    {name:'My Firm\'s Orders Only', action:function(){
                        if (params.api.gridCore.gridOptions.orderFilter==='firm'){
                            params.api.gridCore.gridOptions.orderFilter='';
                            //TODO filter
                        }
                        else {params.api.gridCore.gridOptions.orderFilter='firm';
                            //TODO filter
                        }

                    }, checked:(params.api.gridCore.gridOptions.orderFilter==='firm')},
                ]
         },
         {name: 'Size ...', action: function() {

             console.log(params.api.gridCore.gridOptions.orderSize);
             $rootScope.$broadcast("orderSizeDialog",{size:parseInt(params.api.gridCore.gridOptions.orderSize)});
            },
            }
    ];
    return menuItems;
}

你能帮帮我吗?

编辑

我注意到在创建 ViewportDatasource 时没有调用 eventListener 函数 object。怎么强制呢?

编辑 2

我将 ViewportDatasource 声明移出 WatcherTable 范围。现在我已经解决了开始的问题,但是我无法将数据加载到网格中。

我创建了一个 plunker:https://plnkr.co/edit/EEEJULRE72nbPF6G0PCK

(从你今天的问题中摘取的,我认为这里有类似的问题)

您的 plunker 中存在计时问题 - 您的 MockServer 正在尝试在数据可用之前对其进行处理。

您需要做两件事来解决此问题 - 首先是仅在数据在 MockServer 中可用时才尝试设置数据源:

WatcherTable.prototype.setRowData = function ($http) {
    // set up a mock server - real code will not do this, it will contact your
    // real server to get what it needs
    var mockServer = new MockServer();
    var that = this;
    $http.get('data.json').then(function (response) {
        mockServer.init(response.data);
        var viewportDatasource = new ViewportDatasource(mockServer);
        that.table.api.setViewportDatasource(viewportDatasource);
        // put the 'size cols to fit' into a timeout, so that the scroll is taken into consideration
        setTimeout(function () {
            that.table.api.sizeColumnsToFit();
        }, 100);
    });
}

其次,沿着相同的主题,您需要防止定期更新在数据准备就绪之前尝试处理数据。在这里,您可以在数据可用后开始定期更新,或者更简单地在尝试使用它之前添加一个检查:

MockServer.prototype.periodicallyUpdateData = function() {
    if(!this.allData) return;

我在这里 fork 你的 plunker(进行了上述更改):https://plnkr.co/edit/cY30aHIPydVOjcihX8Zh?p=preview