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

时间:2013-05-09 14:02:43

标签: javascript jquery jquery-datatables

情境:

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

有3个按钮,单击每个按钮可以使用表格标记在特定div上创建数据表的实例。

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

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

因此,当创建所有三个实例时,仅在最后创建的实例上使用fnSettings()返回正确的对象,其他两个实例返回null

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

代码说明

我创建了一个名为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();
};

现在在我的网页中,这是我创建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;
        }

在创建了所有三个实例后,只有最后一个实际上有fnSettings()对象定义的rest实例为fnSettings返回null,从而调用其他使用aoData的api方法(它是fnSettings()返回对象的成员)show无法读取属性的错误aoData为null

控制台预览:

3个实例存储在 customergroupDatagrid salesmanDatagrid distributorDatagrid 变量

创建customergroupDatagrid实例时

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

创建salesmanDatagrid实例时

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

创建distributorDatagrid实例时

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

1 个答案:

答案 0 :(得分:4)

我认为问题是您的表都具有相同的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.

这是两个jsfiddles 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/

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());

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

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