javascript - aoData is null when using multiple instances of jquery datatable -
scenario:
on webpage have 3 divs contain table tags.
there 3 buttons, clicking on each button creates instance of datatable on particular div table tag.
the datatable gets data serverside
all data returned , displayed, pagination, filtering works fine.
so when 3 instances created, using fnsettings() on last instance created returns proper object, , other 2 instances return null
so using fndata() etc api methods throw error saying : "typeerror: cannot read property 'aodata' of null" because settings object of datatable instance somehow null
code description
i have made class called datagrid, , create multiple instances of class:
/** * datagrid class contains methods , properties in controllling , manipulating multiple instances of datagrid class * * function constructor datagrid class * * @param {string} domcontainerselector dom selector of element containing datagrid * @param {array} columns definitions of columns of datagrid * @param {string} ajaxsource url jqgrid use request data * @param {object} configurationparameters configuration parameters used jqgrid , datagrid instance. suppoted configuration parameters are: initialcachesize, idisplaylength, sscrolly, bpaginate, bfilter, sdom, bsort * @param {object} uicallback contains callback functions used when server request in progress , after completion of request. used 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={}; } }; /** * method returns row selected user * * @return {object} row object containing columns properties */ this.getselectedrow = function() { var allrows = this.datatable.fngetnodes(); (i = 0; < allrows.length; i++) if ($(allrows[i]).hasclass('row_selected')) return this.datatable.fngetdata(allrows[i]); }; this.getpostdatavalue=function(postdata, key){ (var i=0;i<postdata.length;i++) { if (postdata[i].name == key) { return postdata[i].value; } } return null; }; this.setpostdatavalue=function(postdata, key, value){ (var i=0; i<postdata.length;i++) { if (postdata[i].name == key) { postdata[i].value = value; } } }; this.setpostdatafiltervalues=function(postdata){ (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 cache because datagrid in dirty state this.columns[index].ssearch=value; if (evt.keycode == 13) this.datatable.fnfilter(); }; /** * method acts general button handler when operation in progress */ this.busystatebuttonhandler=function() { ui.showmessage("another operation in progress. please wait operation complete"); }; /** * method sets event handlers 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()}); }; /** * method sets appropriate event handlers 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); }; /** * method enables column specific filtering * * methods adds filtering capability columns definitions indicate 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 = ""; (i = 0; < 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); }); } }; /** * method enables single selection on rows of 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 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; } }; /** * method interfaces backend end controller * * method called when grid initiates operation requires server side processing * * @param {string} ssource source url used xhr request * @param {array} aodata contains parameters sent datatable forwarded backend controller * @param {function} fncallback callback function of datatable gets executed render grid 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); } }; /** * method destroys datatable instance * * remove contents parent div , reinserts simple table tag on fresh datatable reinitialized */ this.destroy = function() { $(this.domrowselector).die("click"); $(this.domgridwrapperselector).remove();//remove datatable generated dynamic code $(this.domcontainerselector).prepend("<table id='grid'></table>"); }; /** * datatable property holds instance of 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 0 of 0" }, "aocolumns": this.columns }); this.init=function(){ this.enableselection(); this.enablecolumnfilter(); this.seteventhandlers(); }; this.init(); };
now in web page create 3 instances :
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 3 instances created, last 1 supposedly has fnsettings() object defined rest instances return null fnsettings , calling other api methods use aodata (which member of fnsettings() returned object) show error can't read property aodata of null
console preview:
the 3 instances stored in customergroupdatagrid, salesmandatagrid, distributordatagrid variables
when customergroupdatagrid instance created
customergroupdatagrid.datatable.fnsettings(); // returns object
when salesmandatagrid instance created
salesmandatagrid.datatable.fnsettings(); // returns object
customergroupdatagrid.datatable.fnsettings(); // returns null
when distributordatagrid instance created
distributordatagrid.datatable.fnsettings(); // returns object
salesmandatagrid.datatable.fnsettings(); // returns null
customergroupdatagrid.datatable.fnsettings(); // returns null
i believe problem tables have same id. please note proper html requires unique ids: http://www.w3.org/tr/html401/struct/global.html#h-7.5.2
id = name [cs] attribute assigns name element. name must unique in document.
here 2 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());
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 1 duplicates code, using same id 2 tables. displays alert after first table created; fnsettings not null. displays alert after next table created, , fnsettings of table 1 null. second jsfiddle uses unique ids, , problem disappears.
perhaps table id combination of div id , "grid", e.g., div1grid, div2grid etc. use domcontainerselector + 'grid' instead of ' #grid'.
bqb
Comments
Post a Comment