javascript 使用 jquery 数据表的多个实例时,aoData 为 null

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16463646/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 04:46:24  来源:igfitidea点击:

aoData is null when using multiple instances of jquery datatable

javascriptjqueryjquery-datatables

提问by Robin Rizvi

Scenario:

设想:

On a webpage I have three divs that contain table tags.

在网页上,我有三个包含表格标签的 div。

There are 3 buttons, clicking on each button creates an instance of the datatable on a particular div with the table tag.

有 3 个按钮,单击每个按钮会在具有 table 标记的特定 div 上创建数据表的实例。

The datatable gets the data from serverside

数据表从服务器端获取数据

All the data returned and displayed, pagination, filtering works fine.

返回和显示的所有数据、分页、过滤工作正常。

So when all three instances are created, using fnSettings() on only the last instance created returns the proper object, and the other two instances return null

所以当所有三个实例都被创建时,只在最后一个创建的实例上使用 fnSettings() 返回正确的对象,其他两个实例返回 null

So using fnData() etc api methods throw an error saying : "TypeError: Cannot read property 'aoData' of null" because settings object of that datatable instance is somehow null

所以使用 fnData() 等 api 方法会抛出一个错误说:“TypeError:无法读取 null 的属性 'aoData'”,因为该数据表实例的设置对象以某种方式为空

Code Description

代码说明

I have made a class called datagrid, and I create multiple instances of this class:

我创建了一个名为 datagrid 的类,并创建了该类的多个实例:

/**
 * datagrid class contains methods and properties that will help in controllling and manipulating the multiple instances of the datagrid class
 * 
 * This function is the constructor for the datagrid class
 * 
 * @param {string} domContainerSelector DOM selector of the element containing the datagrid
 * @param {Array} columns Definitions of the columns of the datagrid
 * @param {string} ajaxSource The url that the jqgrid will use to request for data
 * @param {Object} configurationParameters The configuration parameters that will be used by the jqGrid and this datagrid instance. Currently suppoted configuration parameters are: initialCacheSize, iDisplayLength, sScrollY, bPaginate, bFilter, sDom, bSort
 * @param {Object} uiCallback Contains callback functions that are used when a server request is in progress and after the completion of the request. Mainly used for showing progress indicators.
 * @returns {datagrid}
 */
function datagrid(domContainerSelector, columns, ajaxSource, configurationParameters, uiCallback)
{
    this.domContainerSelector = domContainerSelector;
    this.domTableSelector = this.domContainerSelector + " #grid";
    this.domRowSelector = this.domTableSelector + " tbody tr";
    this.domGridWrapperSelector = this.domContainerSelector + " .dataTables_wrapper";
    this.columns = columns;
    this.ajaxSource = ajaxSource;
    this.configParams = configurationParameters;
    this.uiCallback = uiCallback;
    this.cache= {
            start: 0,
            end: 0,
            initialSize:this.configParams.initialCacheSize == undefined ? 2 : this.configParams.initialCacheSize,
            pageSize:this.configParams.iDisplayLength == undefined ? 10 : this.configParams.iDisplayLength,
            loading:false,
            jsondata: {},
            reset: function(){
                this.start=0;
                this.end=0;
                this.loading=false;
                this.jsondata={};
            }
    };
    /**
     * This method returns the row selected by the user
     * 
     * @return {Object} Row object containing columns as its properties
     */
    this.getSelectedRow = function()
    {
        var allrows = this.dataTable.fnGetNodes();
        for (i = 0; i < allrows.length; i++)
            if ($(allrows[i]).hasClass('row_selected'))
                return this.dataTable.fnGetData(allrows[i]);
    };
    this.getPostDataValue=function(postData, key){
        for (var i=0;i<postData.length;i++)
        {
            if (postData[i].name == key)
            {
                return postData[i].value;
            }
        }
        return null;
    };
    this.setPostDataValue=function(postData, key, value){
        for (var i=0; i<postData.length;i++)
        {
            if (postData[i].name == key)
            {
                postData[i].value = value;
            }
        }
    };
    this.setPostDataFilterValues=function(postData){
        for (i=0;i<this.columns.length;i++)
        {
            var key="sSearch_"+i;
            this.setPostDataValue(postData,key,this.columns[i].sSearch===undefined?'':this.columns[i].sSearch);
        }
    };
    this.filterColumnKeyupHandler = function(evt) {
        var id=evt.target.id;
        var index=id.charAt(id.length-1);
        var oldvalue=this.columns[index].sSearch;
        var value = evt.target.value == '' ? undefined : evt.target.value;
        if (oldvalue!=value) this.cache.reset();//resetting the cache because the datagrid is in dirty state
        this.columns[index].sSearch=value;
        if (evt.keyCode == 13) this.dataTable.fnFilter();
    };
    /**
     * This method acts as the general button handler when an operation is in progress
     */
    this.busyStateButtonHandler=function()
    {
        ui.showmessage("Another operation is in progress. Please wait for the operation to complete");
    };
    /**
     * This method sets the event handlers for the datagrid
    */
    this.setEventHandlers = function() {
        var self=this;
        $(this.domGridWrapperSelector + " input[class='columnfilterinput']").off("keyup").on("keyup", function(evt) {self.filterColumnKeyupHandler(evt,self)});
        $(this.domGridWrapperSelector + " .filterbar .searchbtn").off("click").on("click", function() {self.dataTable.fnFilter()});
    };
    /**
     * This method sets the appropriate event handlers to indicate busy status
    */
    this.setBusyStatusEventHandlers=function()
    {
        $(this.domGridWrapperSelector + " input[class='columnfilterinput']").off("keyup").on("keyup", this.busyStateButtonHandler);
        $(this.domGridWrapperSelector + " .filterbar .searchbtn").off("click").on("click", this.busyStateButtonHandler);
    };
    /**
     * This method enables column specific filtering
     * 
     * This methods adds filtering capability to columns whose definitions indicate that they are searchable (bSearchable:true)
     */
    this.enablecolumnfilter = function() {
        var self = this;
        var oTable = self.dataTable;
        var oSettings = oTable.fnSettings();
        var aoColumns = oSettings.aoColumns;
        var nTHead = oSettings.nTHead;
        var htmlTrTemplate = "<tr class='filterbar'>{content}</tr>";
        var htmlTdTemplate = "<td>{content}</td>";
        var htmlInputTemplate = "<input type='text' name='{name}' id='{id}' class='{class}' /><div class='searchbtn' id='{searchbtnid}'><div class='icon-filter'></div></div>";
        var isAnyColumnFilterable = false;
        var htmlTr = htmlTrTemplate;
        var allHtmlTds = "";
        for (i = 0; i < aoColumns.length; i++)
        {
            var column = aoColumns[i];
            var htmlTd = htmlTdTemplate;
            if (column.bSearchable == true)
            {
                isAnyColumnFilterable = true;
                var htmlInput = htmlInputTemplate;
                htmlInput = htmlInput.replace('{name}', column.mData);
                htmlInput = htmlInput.replace('{id}', "sSearch_" + i);
                htmlInput = htmlInput.replace('{class}', 'columnfilterinput');
                htmlTd = htmlTd.replace('{content}', htmlInput);
            }
            else
                htmlTd = htmlTd.replace('{content}', '');
            allHtmlTds += htmlTd;
        }
        if (isAnyColumnFilterable)
        {
            htmlTr = htmlTr.replace('{content}', allHtmlTds);
            nTHead.innerHTML += htmlTr;
            $(this.domGridWrapperSelector + " .filterbar input[class='columnfilterinput']").each(function(){
                $(this).width($(this).parent().width()-26);
            });
        }
    };
    /**
     * This method enables single selection on the rows of the grid
     */
    this.enableSelection = function()
    {
        $(this.domRowSelector).die("click").live("click", function() {
            if ($(this).hasClass('row_selected')) {
                $(this).removeClass('row_selected');
            }
            else {
                $(this).siblings().removeClass('row_selected');
                $(this).addClass('row_selected');
            }
        });
    };
    this.loadDataIntoCache=function(postData, sourceUrl, start, length){
        if (!this.cache.loading)
        {
            var postData=$.extend(true, [], postData);
            var start = start==undefined?this.cache.end:start;
            var length = length==undefined?this.cache.pageSize:length;
            var end = start + length;
            this.setPostDataValue(postData, "iDisplayStart", start);
            this.setPostDataValue(postData, "iDisplayLength", length);

            var self=this;
            this.cache.loading=true;
            $.ajax({
                type: "POST",
                url: sourceUrl,
                data: postData,
                success:
                        function(json, textStatus, jqXHR)
                        {
                            json = JSON.parse(json);
                            var olddata=self.cache.jsondata.aaData;
                            if (olddata===undefined) self.cache.jsondata = $.extend(true, {}, json);
                            else olddata.push.apply(olddata,json.aaData);
                            self.cache.end=end;
                        },
                error:
                        function(jqXHR, textStatus, errorThrown)
                        {
                            ui.showmessage(jqXHR.responseText);//remove this from here
                        },
                complete:
                        function()
                        {
                            self.cache.loading=false;
                        }
            });
        }
    };
    this.loadDataFromCache=function(postData,sourceUrl){
        var start=this.getPostDataValue(postData, "iDisplayStart");
        var length=this.cache.pageSize;
        var end=start+length;
        var sEcho = this.getPostDataValue(postData,"sEcho");
        if (this.cache.end>=end)
        {
            var jsondata=$.extend(true, {},this.cache.jsondata);
            var data=jsondata.aaData;
            jsondata.aaData=data.splice(start,length);
            jsondata.sEcho = sEcho;
            var totalRecords=jsondata.iTotalRecords;
            if ((this.cache.end-end)<((this.cache.initialSize*this.cache.pageSize)/2) && (totalRecords==0 || this.cache.end<totalRecords) ) this.loadDataIntoCache(postData, sourceUrl);//prefetch data if needed
            return jsondata;
        }
        else
        {
            this.loadDataIntoCache(postData,sourceUrl);
            return null;
        }
    };
    /**
     * This method interfaces with the backend end controller
     * 
     * This method is called when the grid initiates any operation that requires server side processing
     * 
     * @param {String} sSource The source url that will be used for the xhr request
     * @param {Array} aoData Contains the parameters sent by the dataTable that will be forwarded to the backend controller
     * @param {Function} fnCallback The callback function of the dataTable that gets executed to finally render the grid with the data
     */
    this.interfaceWithServer = function(sSource, aoData, fnCallback)
    {
        this.setPostDataFilterValues(aoData);
        var self=this;
        if (this.cache.end==0)
        {
            this.setPostDataValue(aoData, "iDisplayStart", this.cache.start);
            if (this.dataTable!=undefined) this.dataTable.fnSettings()._iDisplayStart=0;
            this.loadDataIntoCache(aoData, sSource, 0, (this.cache.initialSize*this.cache.pageSize));
        }
        var data=this.loadDataFromCache(aoData,sSource);
        if (data!=null) fnCallback(data);
        else
        {
            this.setBusyStatusEventHandlers();
            this.uiCallback.inprogress();
            self.cacheLoadingTimerId=setInterval(function(){
                if (self.cache.loading==false)
                {
                    clearInterval(self.cacheLoadingTimerId);
                    var data=self.loadDataFromCache(aoData,sSource);
                    fnCallback(data);
                    self.uiCallback.completed();
                    self.setEventHandlers();
                }
            },500);
        }
    };
    /**
     * This method destroys the datatable instance
     * 
     * Remove all the contents from the parent div and reinserts a simple table tag on which a fresh datatable will be reinitialized
     */
    this.destroy = function()
    {
        $(this.domRowSelector).die("click");
        $(this.domGridWrapperSelector).remove();//remove only the datatable generated dynamic code
        $(this.domContainerSelector).prepend("<table id='grid'></table>");
    };
    /**
     * The dataTable property holds the instance of the jquery Datatable
     */
    this.dataTable = $(this.domTableSelector).dataTable({
        "bJQueryUI": true,
        "sScrollY": this.configParams.sScrollY == undefined ? "320px" : this.configParams.sScrollY,
        "bAutoWidth": true,
        "bPaginate": this.configParams.bPaginate == undefined ? true : this.configParams.bPaginate,
        "sPaginationType": "two_button",
        "bLengthChange": false,
        "bFilter": this.configParams.bFilter == undefined ? true : this.configParams.bFilter,
        "sDom": this.configParams.sDom == undefined ? '<"H"lfr>t<"F"ip>' : this.configParams.sDom,
        "bSort": this.configParams.bSort == undefined ? true : this.configParams.bSort,
        "iDisplayLength": this.configParams.iDisplayLength == undefined ? 10 : this.configParams.iDisplayLength,
        "bServerSide": true,
        "sAjaxSource": this.ajaxSource,
        "fnServerData": this.interfaceWithServer.bind(this),
        "oLanguage": {
            "sZeroRecords": "No Records Found",
            "sInfo": "_START_ - _END_ of _TOTAL_",
            "sInfoEmpty": "0 to 0 of 0"
        },
        "aoColumns": this.columns
    });

    this.init=function(){
        this.enableSelection();
        this.enablecolumnfilter();
        this.setEventHandlers();
    };
    this.init();
};

Now in my web page this is where I create the 3 instances :

现在在我的网页中,这是我创建 3 个实例的地方:

switch (dialog)
        {
            case "cusgrp_dialog":
                var columndefs = [
                    {
                        "sTitle": "XWBNCD",
                        "mData": "xwbncd",
                        "sWidth": "40%"
                    },
                    {
                        "sTitle": "XWKHTX",
                        "mData": "xwkhtx",
                        "sWidth": "60%"
                    }
                ];
                var ajaxSource = "./entities/Cusgrp";
                var configurationParameters = {
                    bFilter: null,
                    sDom: 't<"dataTable_controlbar"ip>'
                };
                this.customergroupDatagrid = new datagrid(domContainerSelector, columndefs, ajaxSource, configurationParameters, uiCallback);
                break;
            case "slmen_dialog":
                var columndefs = [
                    {
                        "sTitle": "PERSON",
                        "mData": "person",
                        "sWidth": "40%"
                    },
                    {
                        "sTitle": "PNAME",
                        "mData": "pname",
                        "sWidth": "60%"
                    }
                ];
                var ajaxSource = "./entities/Slmen";
                var configurationParameters = {
                    bFilter: null,
                    sDom: 't<"dataTable_controlbar"ip>'
                };
                this.salesmanDatagrid = new datagrid(domContainerSelector, columndefs, ajaxSource, configurationParameters, uiCallback);
                break;
            case "dists_dialog":
                var columndefs = [
                    {
                        "sTitle": "DSDCDE",
                        "mData": "dsdcde",
                        "sWidth": "40%"
                    },
                    {
                        "sTitle": "DNAME",
                        "mData": "dname",
                        "sWidth": "60%"
                    }
                ];
                var ajaxSource = "./entities/Dists";
                var configurationParameters = {
                    bFilter: null,
                    sDom: 't<"dataTable_controlbar"ip>'
                };
                this.distributorDatagrid = new datagrid(domContainerSelector, columndefs, ajaxSource, configurationParameters, uiCallback);
                break;
        }

After all three instances are created, only the last one supposedly has fnSettings() object defined rest instances return null for fnSettings and thus calling other api methods that use aoData (which is a member of the fnSettings() returned object) show the error that can't read property aoData of null

创建所有三个实例后,只有最后一个应该有 fnSettings() 对象定义,其余实例为 fnSettings 返回 null,因此调用其他使用 aoData(它是 fnSettings() 返回对象的成员)的 api 方法显示错误无法读取 null 的属性 aoData

Console Preview:

控制台预览:

The 3 instances are stored in customergroupDatagrid, salesmanDatagrid, distributorDatagridvariables

这 3 个实例存储在customergroupDatagridsalesmanDatagriddistributorDatagrid变量中

When the customergroupDatagrid instance is created

当创建 customergroupDatagrid 实例时

customergroupDatagrid.dataTable.fnSettings(); // returns object

customergroupDatagrid.dataTable.fnSettings(); // 返回对象

When the salesmanDatagrid instance is created

当 salesmanDatagrid 实例被创建时

salesmanDatagrid.dataTable.fnSettings(); // returns object
customergroupDatagrid.dataTable.fnSettings(); // returns null

salesmanDatagrid.dataTable.fnSettings(); // 返回对象
customergroupDatagrid.dataTable.fnSettings(); // 返回空值

When the distributorDatagrid instance is created

当创建distributorDatagrid 实例时

distributorDatagrid.dataTable.fnSettings(); // returns object
salesmanDatagrid.dataTable.fnSettings(); // returns null
customergroupDatagrid.dataTable.fnSettings(); // returns null

经销商Datagrid.dataTable.fnSettings(); // 返回对象
salesmanDatagrid.dataTable.fnSettings(); // 返回空
customergroupDatagrid.dataTable.fnSettings(); // 返回空值

回答by Bumptious Q Bangwhistle

I believe the problem is that your tables all have the same ID. Please note proper HTML requires unique IDs: http://www.w3.org/TR/html401/struct/global.html#h-7.5.2

我相信问题在于您的表都具有相同的 ID。请注意正确的 HTML 需要唯一的 ID:http: //www.w3.org/TR/html401/struct/global.html#h-7.5.2

id = name [CS]
This attribute assigns a name to an element. This name 
must be unique in a document.
id = name [CS]
This attribute assigns a name to an element. This name 
must be unique in a document.

Here are two jsfiddles.
http://jsfiddle.net/QFrz9/

这是两个jsfiddle。
http://jsfiddle.net/QFrz9/

var dt1 = $('#div1 #grid').dataTable();
alert('dt1 settings: ' + dt1.fnSettings());
var dt2 = $('#div2 #grid').dataTable();
alert('dt1 settings: ' + dt1.fnSettings());
alert('dt2 settings: ' + dt2.fnSettings());

http://jsfiddle.net/mRFaP/1/

http://jsfiddle.net/mRFaP/1/

var dt1 = $('#div1 #grid1').dataTable();
alert('dt1 settings: ' + dt1.fnSettings());
var dt2 = $('#div2 #grid2').dataTable();
alert('dt1 settings: ' + dt1.fnSettings());
alert('dt2 settings: ' + dt2.fnSettings());

The first one duplicates your code, using the same id for the two tables. It displays an alert after the first table is created; fnSettings is not null. Then it displays an alert after the next table is created, and suddenly the fnSettings of table 1 is null. The second jsfiddle uses unique ids, and the problem disappears.

第一个复制您的代码,对两个表使用相同的 id。创建第一个表后显示警报;fnSettings 不为空。然后在创建下一个表后显示警报,突然表1的fnSettings为空。第二个 jsfiddle 使用唯一的 id,问题就消失了。

Perhaps your table id could be a combination of the div ID and "grid", e.g., div1grid, div2grid etc. Then you would use domContainerSelector + 'grid' instead of ' #grid'.

也许您的表 ID 可以是 div ID 和“网格”的组合,例如 div1grid、div2grid 等。然后您将使用 domContainerSelector + 'grid' 而不是 '#grid'。