aoData имеет значение null при использовании нескольких экземпляров jquery datatable

Сценарий:

На веб-странице у меня есть три div, которые содержат теги таблицы.

Есть 3 кнопки, нажатие на каждую кнопку создает экземпляр datatable в конкретном div с тегом table.

Datatable получает данные с сервера

Все данные возвращаются и отображаются, нумерация страниц, фильтрация работает нормально.

Поэтому, когда все три экземпляра созданы, использование fnSettings() только для последнего созданного экземпляра возвращает правильный объект, а два других экземпляра возвращают null

Поэтому использование методов API fnData() и т. д. выдает сообщение об ошибке: "TypeError: Cannot read property 'aoData' of null", поскольку объект настроек этого экземпляра с данными каким-то образом имеет значение null

Описание кода

Я создал класс с именем 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() определяет остальные экземпляры, которые возвращают значение null для fnSettings, и, таким образом, вызов других методов API, использующих aoData (который является членом возвращаемого объекта fnSettings()), показывает ошибку, которая не могу прочитать свойство aoData из null

Предварительная версия для консоли:

3 экземпляра хранятся в переменных customergroupDatagrid, salesmanDatagrid, distributorDatagrid.

При создании экземпляра customergroupDatagrid

группа клиентовDatagrid.dataTable.fnSettings(); // возвращает объект

При создании экземпляра salesmanDatagrid

продавец Datagrid.dataTable.fnSettings(); // возвращает объект
customergroupDatagrid.dataTable.fnSettings(); // возвращает ноль

При создании экземпляра DistributionDatagrid

дистрибьюторDatagrid.dataTable.fnSettings(); // возвращает объект
salesmanDatagrid.dataTable.fnSettings(); // возвращает null
customergroupDatagrid.dataTable.fnSettings(); // возвращает ноль


person Robin Rizvi    schedule 09.05.2013    source источник
comment
Можете ли вы смоделировать то же самое на jsfiddle.net?   -  person dreamweiver    schedule 09.05.2013


Ответы (1)


Я считаю, что проблема в том, что все ваши таблицы имеют одинаковый идентификатор. Обратите внимание, что для правильного HTML требуются уникальные идентификаторы: 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());

Первый дублирует ваш код, используя один и тот же идентификатор для двух таблиц. Он отображает предупреждение после создания первой таблицы; fnSettings не равно нулю. Затем он отображает предупреждение после создания следующей таблицы, и внезапно fnSettings таблицы 1 становится нулевым. Второй jsfiddle использует уникальные идентификаторы, и проблема исчезает.

Возможно, идентификатор вашей таблицы может быть комбинацией идентификатора div и «сетки», например, div1grid, div2grid и т. д. Тогда вы должны использовать domContainerSelector + 'grid' вместо ' #grid'.

person Bumptious Q Bangwhistle    schedule 09.05.2013
comment
Хотя я согласен с вашим ответом, я уверен, что w3schools не одобряется как ресурс. - person Jay Rizzi; 10.05.2013
comment
Да, я, похоже, упустил из виду этот аспект, инициализировал каждую таблицу данных с помощью селектора unqiue, но все же возникла та же проблема с идентификатором. - person Robin Rizvi; 10.05.2013
comment
@JayRizzi - точка зрения принята в отношении w3schools. Я отредактировал ответ, указав ссылку на w3.org. - person Bumptious Q Bangwhistle; 10.05.2013
comment
Мне помогло, как ни странно. Я вообще не использовал идентификатор нигде в своем коде. Я думаю, что DataTables использовали идентификатор в качестве селектора, если он был доступен. Я просто удалил идентификатор вообще. - person Ray; 05.06.2013