editablegrid.js 76 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244
  1. if (typeof _$ == 'undefined') {
  2. function _$(elementId) { return document.getElementById(elementId); }
  3. }
  4. /**
  5. * Creates a new column
  6. * @constructor
  7. * @class Represents a column in the editable grid
  8. * @param {Object} config
  9. */
  10. function Column(config)
  11. {
  12. // default properties
  13. var props = {
  14. name: "",
  15. label: "",
  16. editable: true,
  17. renderable: true,
  18. datatype: "string",
  19. unit: null,
  20. precision: -1, // means that all decimals are displayed
  21. nansymbol: '',
  22. decimal_point: ',',
  23. thousands_separator: '.',
  24. unit_before_number: false,
  25. bar: true, // is the column to be displayed in a bar chart ? relevant only for numerical columns
  26. hidden: false, // should the column be hidden by default
  27. headerRenderer: null,
  28. headerEditor: null,
  29. cellRenderer: null,
  30. cellEditor: null,
  31. cellValidators: [],
  32. enumProvider: null,
  33. optionValues: null,
  34. optionValuesForRender: null,
  35. columnIndex: -1
  36. };
  37. // override default properties with the ones given
  38. for (var p in props) this[p] = (typeof config == 'undefined' || typeof config[p] == 'undefined') ? props[p] : config[p];
  39. }
  40. Column.prototype.getOptionValuesForRender = function(rowIndex) {
  41. if (!this.enumProvider) {
  42. console.log('getOptionValuesForRender called on column ' + this.name + ' but there is no EnumProvider');
  43. return null;
  44. }
  45. var values = this.enumProvider.getOptionValuesForRender(this.editablegrid, this, rowIndex);
  46. return values ? values : this.optionValuesForRender;
  47. };
  48. Column.prototype.getOptionValuesForEdit = function(rowIndex) {
  49. if (!this.enumProvider) {
  50. console.log('getOptionValuesForEdit called on column ' + this.name + ' but there is no EnumProvider');
  51. return null;
  52. }
  53. var values = this.enumProvider.getOptionValuesForEdit(this.editablegrid, this, rowIndex);
  54. return values ? this.editablegrid._convertOptions(values) : this.optionValues;
  55. };
  56. Column.prototype.isValid = function(value) {
  57. for (var i = 0; i < this.cellValidators.length; i++) if (!this.cellValidators[i].isValid(value)) return false;
  58. return true;
  59. };
  60. Column.prototype.isNumerical = function() {
  61. return this.datatype =='double' || this.datatype =='integer';
  62. };
  63. /**
  64. * Creates a new enumeration provider
  65. * @constructor
  66. * @class Base class for all enumeration providers
  67. * @param {Object} config
  68. */
  69. function EnumProvider(config)
  70. {
  71. // default properties
  72. this.getOptionValuesForRender = function(grid, column, rowIndex) { return null; };
  73. this.getOptionValuesForEdit = function(grid, column, rowIndex) { return null; };
  74. // override default properties with the ones given
  75. for (var p in config) this[p] = config[p];
  76. }
  77. /**
  78. * Creates a new EditableGrid.
  79. * <p>You can specify here some configuration options (optional).
  80. * <br/>You can also set these same configuration options afterwards.
  81. * <p>These options are:
  82. * <ul>
  83. * <li>enableSort: enable sorting when clicking on column headers (default=true)</li>
  84. * <li>doubleclick: use double click to edit cells (default=false)</li>
  85. * <li>editmode: can be one of
  86. * <ul>
  87. * <li>absolute: cell editor comes over the cell (default)</li>
  88. * <li>static: cell editor comes inside the cell</li>
  89. * <li>fixed: cell editor comes in an external div</li>
  90. * </ul>
  91. * </li>
  92. * <li>editorzoneid: used only when editmode is set to fixed, it is the id of the div to use for cell editors</li>
  93. * <li>allowSimultaneousEdition: tells if several cells can be edited at the same time (default=false)<br/>
  94. * Warning: on some Linux browsers (eg. Epiphany), a blur event is sent when the user clicks on a 'select' input to expand it.
  95. * So practically, in these browsers you should set allowSimultaneousEdition to true if you want to use columns with option values and/or enum providers.
  96. * This also used to happen in older versions of Google Chrome Linux but it has been fixed, so upgrade if needed.</li>
  97. * <li>saveOnBlur: should be cells saved when clicking elsewhere ? (default=true)</li>
  98. * <li>invalidClassName: CSS class to apply to text fields when the entered value is invalid (default="invalid")</li>
  99. * <li>ignoreLastRow: ignore last row when sorting and charting the data (typically for a 'total' row)</li>
  100. * <li>caption: text to use as the grid's caption</li>
  101. * <li>dateFormat: EU or US (default="EU")</li>
  102. * <li>shortMonthNames: list of month names (default=["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"])</li>
  103. * <li>smartColorsBar: colors used for rendering (stacked) bar charts</li>
  104. * <li>smartColorsPie: colors used for rendering pie charts</li>
  105. * <li>pageSize: maximum number of rows displayed (0 means we don't want any pagination, which is the default)</li>
  106. * <li>sortIconDown: icon used to show desc order</li>
  107. * <li>sortIconUp: icon used to show asc order</li>
  108. * </ul>
  109. * @constructor
  110. * @class EditableGrid
  111. */
  112. function EditableGrid(name, config) { if (name) this.init(name, config); }
  113. /**
  114. * Default properties
  115. */
  116. EditableGrid.prototype.enableSort = true;
  117. EditableGrid.prototype.enableStore = true;
  118. EditableGrid.prototype.doubleclick = false;
  119. EditableGrid.prototype.editmode = "absolute";
  120. EditableGrid.prototype.editorzoneid = "";
  121. EditableGrid.prototype.allowSimultaneousEdition = false;
  122. EditableGrid.prototype.saveOnBlur = true;
  123. EditableGrid.prototype.invalidClassName = "invalid";
  124. EditableGrid.prototype.ignoreLastRow = false;
  125. EditableGrid.prototype.caption = null;
  126. EditableGrid.prototype.dateFormat = "EU";
  127. EditableGrid.prototype.shortMonthNames = null;
  128. EditableGrid.prototype.smartColorsBar = ["#dc243c","#4040f6","#00f629","#efe100","#f93fb1","#6f8183","#111111"];
  129. EditableGrid.prototype.smartColorsPie = ["#FF0000","#00FF00","#0000FF","#FFD700","#FF00FF","#00FFFF","#800080"];
  130. EditableGrid.prototype.pageSize = 0; // client-side pagination, don't set this for server-side pagination!
  131. //server-side pagination, sorting and filtering
  132. EditableGrid.prototype.serverSide = false;
  133. EditableGrid.prototype.pageCount = 0;
  134. EditableGrid.prototype.totalRowCount = 0;
  135. EditableGrid.prototype.unfilteredRowCount = 0;
  136. EditableGrid.prototype.paginatorAttributes = null;
  137. EditableGrid.prototype.lastURL = null;
  138. EditableGrid.prototype.init = function (name, config)
  139. {
  140. if (typeof name != "string" || (typeof config != "object" && typeof config != "undefined")) {
  141. alert("The EditableGrid constructor takes two arguments:\n- name (string)\n- config (object)\n\nGot instead " + (typeof name) + " and " + (typeof config) + ".");
  142. };
  143. // override default properties with the ones given
  144. if (typeof config != 'undefined') for (var p in config) this[p] = config[p];
  145. this.Browser = {
  146. IE: !!(window.attachEvent && navigator.userAgent.indexOf('Opera') === -1),
  147. Opera: navigator.userAgent.indexOf('Opera') > -1,
  148. WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
  149. Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') === -1,
  150. MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
  151. };
  152. if (typeof this.detectDir != 'function') {
  153. var error = new Error();
  154. alert("Who is calling me now ? " + error.stack);
  155. }
  156. // private data
  157. this.name = name;
  158. this.columns = [];
  159. this.data = [];
  160. this.dataUnfiltered = null; // non null means that data is filtered
  161. this.xmlDoc = null;
  162. this.sortedColumnName = -1;
  163. this.sortDescending = false;
  164. this.baseUrl = this.detectDir();
  165. this.nbHeaderRows = 1;
  166. this.lastSelectedRowIndex = -1;
  167. this.currentPageIndex = 0;
  168. this.currentFilter = null;
  169. this.currentContainerid = null;
  170. this.currentClassName = null;
  171. this.currentTableid = null;
  172. if (this.enableSort) {
  173. this.sortUpImage = new Image();
  174. if ( typeof config != "undefined" && typeof config['sortIconUp'] != "undefined" )
  175. this.sortUpImage.src = config['sortIconUp'];
  176. else
  177. this.sortUpImage.src ="/static/img/bullet_arrow_up.png";
  178. this.sortDownImage = new Image();
  179. if ( typeof config != "undefined" && typeof config['sortIconDown'] != "undefined" )
  180. this.sortDownImage.src = config['sortIconDown'];
  181. else
  182. this.sortDownImage.src = "/static/img/bullet_arrow_down.png";
  183. }
  184. // restore stored parameters, or use default values if nothing stored
  185. this.currentPageIndex = this.localisset('pageIndex') ? parseInt(this.localget('pageIndex')) : 0;
  186. this.sortedColumnName = this.localisset('sortColumnIndexOrName') ? this.localget('sortColumnIndexOrName') : -1;
  187. this.sortDescending = this.localisset('sortColumnIndexOrName') && this.localisset('sortDescending') ? this.localget('sortDescending') == 'true' : false;
  188. this.currentFilter = this.localisset('filter') ? this.localget('filter') : null;
  189. };
  190. /**
  191. * Callback functions
  192. */
  193. EditableGrid.prototype.tableLoaded = function() {};
  194. EditableGrid.prototype.chartRendered = function() {};
  195. EditableGrid.prototype.tableRendered = function(containerid, className, tableid) {};
  196. EditableGrid.prototype.tableSorted = function(columnIndex, descending) {};
  197. EditableGrid.prototype.tableFiltered = function() {};
  198. EditableGrid.prototype.modelChanged = function(rowIndex, columnIndex, oldValue, newValue, row) {};
  199. EditableGrid.prototype.rowSelected = function(oldRowIndex, newRowIndex) {};
  200. EditableGrid.prototype.isHeaderEditable = function(rowIndex, columnIndex) { return false; };
  201. EditableGrid.prototype.isEditable =function(rowIndex, columnIndex) { return true; };
  202. EditableGrid.prototype.readonlyWarning = function() {};
  203. /** Notifies that a row has been deleted */
  204. EditableGrid.prototype.rowRemoved = function(oldRowIndex, rowId) {};
  205. /**
  206. * Load metadata and/or data from an XML url
  207. * The callback "tableLoaded" is called when loading is complete.
  208. */
  209. EditableGrid.prototype.loadXML = function(url, callback, dataOnly)
  210. {
  211. this.lastURL = url;
  212. var self = this;
  213. // IE
  214. if (window.ActiveXObject)
  215. {
  216. this.xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
  217. this.xmlDoc.onreadystatechange = function() {
  218. if (self.xmlDoc.readyState == 4) {
  219. self.processXML();
  220. self._callback('xml', callback);
  221. }
  222. };
  223. this.xmlDoc.load(this._addUrlParameters(url, dataOnly));
  224. }
  225. // generic Ajax
  226. else if (window.XMLHttpRequest)
  227. {
  228. this.xmlDoc = new XMLHttpRequest();
  229. this.xmlDoc.onreadystatechange = function () {
  230. if (this.readyState == 4) {
  231. self.xmlDoc = this.responseXML;
  232. if (!self.xmlDoc) { console.error("Could not load XML from url '" + url + "'"); return false; }
  233. self.processXML();
  234. self._callback('xml', callback);
  235. }
  236. };
  237. this.xmlDoc.open("GET", this._addUrlParameters(url, dataOnly), true);
  238. this.xmlDoc.send("");
  239. }
  240. // Firefox (and some other browsers)
  241. else if (document.implementation && document.implementation.createDocument)
  242. {
  243. this.xmlDoc = document.implementation.createDocument("", "", null);
  244. this.xmlDoc.onload = function() {
  245. self.processXML();
  246. self._callback('xml', callback);
  247. };
  248. this.xmlDoc.load(this._addUrlParameters(url, dataOnly));
  249. }
  250. // should never happen
  251. else {
  252. alert("Cannot load a XML url with this browser!");
  253. return false;
  254. }
  255. return true;
  256. };
  257. /**
  258. * Load metadata and/or data from an XML string
  259. * No callback "tableLoaded" is called since this is a synchronous operation.
  260. *
  261. * Contributed by Tim Consolazio of Tcoz Tech Services, tcoz@tcoz.com
  262. * http://tcoztechwire.blogspot.com/2012/04/setxmlfromstring-extension-for.html
  263. */
  264. EditableGrid.prototype.loadXMLFromString = function(xml)
  265. {
  266. if (window.DOMParser) {
  267. var parser = new DOMParser();
  268. this.xmlDoc = parser.parseFromString(xml, "application/xml");
  269. }
  270. else {
  271. this.xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); // IE
  272. this.xmlDoc.async = "false";
  273. this.xmlDoc.loadXML(xml);
  274. }
  275. this.processXML();
  276. };
  277. /**
  278. * Process the XML content
  279. * @private
  280. */
  281. EditableGrid.prototype.processXML = function()
  282. {
  283. with (this) {
  284. // clear model and pointer to current table
  285. this.data = [];
  286. this.dataUnfiltered = null;
  287. this.table = null;
  288. // load metadata (only one tag <metadata> --> metadata[0])
  289. var metadata = xmlDoc.getElementsByTagName("metadata");
  290. if (metadata && metadata.length >= 1) {
  291. this.columns = [];
  292. var columnDeclarations = metadata[0].getElementsByTagName("column");
  293. for (var i = 0; i < columnDeclarations.length; i++) {
  294. // get column type
  295. var col = columnDeclarations[i];
  296. var datatype = col.getAttribute("datatype");
  297. // get enumerated values if any
  298. var optionValuesForRender = null;
  299. var optionValues = null;
  300. var enumValues = col.getElementsByTagName("values");
  301. if (enumValues.length > 0) {
  302. optionValues = [];
  303. optionValuesForRender = {};
  304. var enumGroups = enumValues[0].getElementsByTagName("group");
  305. if (enumGroups.length > 0) {
  306. for (var g = 0; g < enumGroups.length; g++) {
  307. var groupOptionValues = [];
  308. enumValues = enumGroups[g].getElementsByTagName("value");
  309. for (var v = 0; v < enumValues.length; v++) {
  310. var _value = enumValues[v].getAttribute("value");
  311. var _label = enumValues[v].firstChild ? enumValues[v].firstChild.nodeValue : "";
  312. optionValuesForRender[_value] = _label;
  313. groupOptionValues.push({ value: _value, label: _label });
  314. }
  315. optionValues.push({ label: enumGroups[g].getAttribute("label"), values: groupOptionValues});
  316. }
  317. }
  318. else {
  319. enumValues = enumValues[0].getElementsByTagName("value");
  320. for (var v = 0; v < enumValues.length; v++) {
  321. var _value = enumValues[v].getAttribute("value");
  322. var _label = enumValues[v].firstChild ? enumValues[v].firstChild.nodeValue : "";
  323. optionValuesForRender[_value] = _label;
  324. optionValues.push({ value: _value, label: _label });
  325. }
  326. }
  327. }
  328. // create new column
  329. columns.push(new Column({
  330. name: col.getAttribute("name"),
  331. label: (typeof col.getAttribute("label") == 'string' ? col.getAttribute("label") : col.getAttribute("name")),
  332. datatype: (col.getAttribute("datatype") ? col.getAttribute("datatype") : "string"),
  333. editable: col.getAttribute("editable") == "true",
  334. bar: (col.getAttribute("bar") ? col.getAttribute("bar") == "true" : true),
  335. hidden: (col.getAttribute("hidden") ? col.getAttribute("hidden") == "true" : false),
  336. optionValuesForRender: optionValuesForRender,
  337. optionValues: optionValues
  338. }));
  339. }
  340. // process columns
  341. processColumns();
  342. }
  343. // load server-side pagination data
  344. var paginator = xmlDoc.getElementsByTagName("paginator");
  345. if (paginator && paginator.length >= 1) {
  346. this.paginatorAttributes = null; // TODO: paginator[0].getAllAttributesAsPOJO;
  347. this.pageCount = paginator[0].getAttribute('pagecount');
  348. this.totalRowCount = paginator[0].getAttribute('totalrowcount');
  349. this.unfilteredRowCount = paginator[0].getAttribute('unfilteredrowcount');
  350. }
  351. // if no row id is provided, we create one since we need one
  352. var defaultRowId = 1;
  353. // load content
  354. var rows = xmlDoc.getElementsByTagName("row");
  355. for (var i = 0; i < rows.length; i++)
  356. {
  357. // get all defined cell values
  358. var cellValues = {};
  359. var cols = rows[i].getElementsByTagName("column");
  360. for (var j = 0; j < cols.length; j++) {
  361. var colname = cols[j].getAttribute("name");
  362. if (!colname) {
  363. if (j >= columns.length) console.error("You defined too many columns for row " + (i+1));
  364. else colname = columns[j].name;
  365. }
  366. cellValues[colname] = cols[j].firstChild ? cols[j].firstChild.nodeValue : "";
  367. }
  368. // for each row we keep the orginal index, the id and all other attributes that may have been set in the XML
  369. var rowData = { visible: true, originalIndex: i, id: rows[i].getAttribute("id") ? rows[i].getAttribute("id") : defaultRowId++ };
  370. for (var attrIndex = 0; attrIndex < rows[i].attributes.length; attrIndex++) {
  371. var node = rows[i].attributes.item(attrIndex);
  372. if (node.nodeName != "id") rowData[node.nodeName] = node.nodeValue;
  373. }
  374. // get column values for this rows
  375. rowData.columns = [];
  376. for (var c = 0; c < columns.length; c++) {
  377. var cellValue = columns[c].name in cellValues ? cellValues[columns[c].name] : "";
  378. rowData.columns.push(getTypedValue(c, cellValue));
  379. }
  380. // add row data in our model
  381. data.push(rowData);
  382. }
  383. }
  384. return true;
  385. };
  386. /**
  387. * Load metadata and/or data from a JSON url
  388. * The callback "tableLoaded" is called when loading is complete.
  389. */
  390. EditableGrid.prototype.loadJSON = function(url, callback, dataOnly)
  391. {
  392. this.lastURL = url;
  393. var self = this;
  394. // should never happen
  395. if (!window.XMLHttpRequest) {
  396. alert("Cannot load a JSON url with this browser!");
  397. return false;
  398. }
  399. var ajaxRequest = new XMLHttpRequest();
  400. ajaxRequest.onreadystatechange = function () {
  401. if (this.readyState == 4) {
  402. if (!this.responseText) { console.error("Could not load JSON from url '" + url + "'"); return false; }
  403. if (!self.processJSON(this.responseText)) { console.error("Invalid JSON data obtained from url '" + url + "'"); return false; }
  404. self._callback('json', callback);
  405. }
  406. };
  407. ajaxRequest.open("GET", this._addUrlParameters(url, dataOnly), true);
  408. ajaxRequest.send("");
  409. return true;
  410. };
  411. EditableGrid.prototype._addUrlParameters = function(baseUrl, dataOnly)
  412. {
  413. // we add a dummy timestamp parameter to avoid getting an old version from the browser's cache
  414. var sep = baseUrl.indexOf('?') >= 0 ? '&' : '?';
  415. baseUrl += sep + (new Date().getTime());
  416. if (!this.serverSide) return baseUrl;
  417. // add pagination, filtering and sorting parameters to the base url
  418. return baseUrl
  419. + "&page=" + (this.currentPageIndex + 1)
  420. + "&filter=" + (this.currentFilter ? encodeURIComponent(this.currentFilter) : "")
  421. + "&sort=" + (this.sortedColumnName && this.sortedColumnName != -1 ? encodeURIComponent(this.sortedColumnName) : "")
  422. + "&asc=" + (this.sortDescending ? 0 : 1)
  423. + (dataOnly ? '&data_only=1' : '');
  424. };
  425. EditableGrid.prototype._callback = function(type, callback)
  426. {
  427. if (callback) callback.call(this);
  428. else {
  429. if (this.serverSide) {
  430. // deferred refreshGrid: first load the updated data from the server then call the original refreshGrid
  431. this.refreshGrid = function(baseUrl) {
  432. var callback = function() { EditableGrid.prototype.refreshGrid.call(this); };
  433. var load = type == 'xml' ? this.loadXML : this.loadJSON;
  434. load.call(this, baseUrl || this.lastURL, callback, true);
  435. };
  436. }
  437. this.tableLoaded();
  438. }
  439. };
  440. /**
  441. * Load metadata and/or data from a JSON string
  442. * No callback "tableLoaded" is called since this is a synchronous operation.
  443. */
  444. EditableGrid.prototype.loadJSONFromString = function(json)
  445. {
  446. return this.processJSON(json);
  447. };
  448. /**
  449. * Load metadata and/or data from a Javascript object
  450. * No callback "tableLoaded" is called since this is a synchronous operation.
  451. */
  452. EditableGrid.prototype.load = function(object)
  453. {
  454. return this.processJSON(object);
  455. };
  456. /**
  457. * Update and render data for given rows from a Javascript object
  458. */
  459. EditableGrid.prototype.update = function(object)
  460. {
  461. if (object.data) for (var i = 0; i < object.data.length; i++)
  462. {
  463. var row = object.data[i];
  464. if (!row.id || !row.values) continue;
  465. // get row to update in our model
  466. var rowIndex = this.getRowIndex(row.id);
  467. var rowData = this.data[rowIndex];
  468. // row values can be given as an array (same order as columns) or as an object (associative array)
  469. if (Object.prototype.toString.call(row.values) !== '[object Array]' ) cellValues = row.values;
  470. else {
  471. cellValues = {};
  472. for (var j = 0; j < row.values.length && j < this.columns.length; j++) cellValues[this.columns[j].name] = row.values[j];
  473. }
  474. // set all attributes that may have been set in the JSON
  475. for (var attributeName in row) if (attributeName != "id" && attributeName != "values") rowData[attributeName] = row[attributeName];
  476. // get column values for this rows
  477. rowData.columns = [];
  478. for (var c = 0; c < this.columns.length; c++) {
  479. var cellValue = this.columns[c].name in cellValues ? cellValues[this.columns[c].name] : "";
  480. rowData.columns.push(this.getTypedValue(c, cellValue));
  481. }
  482. // render row
  483. var tr = this.getRow(rowIndex);
  484. for (var j = 0; j < tr.cells.length && j < this.columns.length; j++) if (this.columns[j].renderable) this.columns[j].cellRenderer._render(rowIndex, j, tr.cells[j], this.getValueAt(rowIndex,j));
  485. this.tableRendered(this.currentContainerid, this.currentClassName, this.currentTableid);
  486. }
  487. };
  488. /**
  489. * Process the JSON content
  490. * @private
  491. */
  492. EditableGrid.prototype.processJSON = function(jsonData)
  493. {
  494. if (typeof jsonData == "string") jsonData = eval("(" + jsonData + ")");
  495. if (!jsonData) return false;
  496. // clear model and pointer to current table
  497. this.data = [];
  498. this.dataUnfiltered = null;
  499. this.table = null;
  500. // load metadata
  501. if (jsonData.metadata) {
  502. // create columns
  503. this.columns = [];
  504. for (var c = 0; c < jsonData.metadata.length; c++) {
  505. var columndata = jsonData.metadata[c];
  506. var optionValues = columndata.values ? this._convertOptions(columndata.values) : null;
  507. var optionValuesForRender = null;
  508. if (optionValues) {
  509. // build a fast lookup structure for rendering
  510. var optionValuesForRender = {};
  511. for (var optionIndex = 0; optionIndex < optionValues.length; optionIndex++) {
  512. var optionValue = optionValues[optionIndex];
  513. if (typeof optionValue.values == 'object') {
  514. for (var groupOptionIndex = 0; groupOptionIndex < optionValue.values.length; groupOptionIndex++) {
  515. var groupOptionValue = optionValue.values[groupOptionIndex];
  516. optionValuesForRender[groupOptionValue.value] = groupOptionValue.label;
  517. }
  518. }
  519. else optionValuesForRender[optionValue.value] = optionValue.label;
  520. }
  521. }
  522. this.columns.push(new Column({
  523. name: columndata.name,
  524. label: (columndata.label ? columndata.label : columndata.name),
  525. datatype: (columndata.datatype ? columndata.datatype : "string"),
  526. editable: (columndata.editable ? true : false),
  527. bar: (typeof columndata.bar == 'undefined' ? true : (columndata.bar || false)),
  528. hidden: (typeof columndata.hidden == 'undefined' ? false : (columndata.hidden ? true : false)),
  529. optionValuesForRender: optionValuesForRender,
  530. optionValues: optionValues
  531. }));
  532. }
  533. // process columns
  534. this.processColumns();
  535. }
  536. // load server-side pagination data
  537. if (jsonData.paginator) {
  538. this.paginatorAttributes = jsonData.paginator;
  539. this.pageCount = jsonData.paginator.pagecount;
  540. this.totalRowCount = jsonData.paginator.totalrowcount;
  541. this.unfilteredRowCount = jsonData.paginator.unfilteredrowcount;
  542. }
  543. // if no row id is provided, we create one since we need one
  544. var defaultRowId = 1;
  545. // load content
  546. if (jsonData.data) for (var i = 0; i < jsonData.data.length; i++)
  547. {
  548. var row = jsonData.data[i];
  549. if (!row.values) continue;
  550. // row values can be given as an array (same order as columns) or as an object (associative array)
  551. if (Object.prototype.toString.call(row.values) !== '[object Array]' ) cellValues = row.values;
  552. else {
  553. cellValues = {};
  554. for (var j = 0; j < row.values.length && j < this.columns.length; j++) cellValues[this.columns[j].name] = row.values[j];
  555. }
  556. // for each row we keep the orginal index, the id and all other attributes that may have been set in the JSON
  557. var rowData = { visible: true, originalIndex: i, id: row.id ? row.id : defaultRowId++ };
  558. for (var attributeName in row) if (attributeName != "id" && attributeName != "values") rowData[attributeName] = row[attributeName];
  559. // get column values for this rows
  560. rowData.columns = [];
  561. for (var c = 0; c < this.columns.length; c++) {
  562. var cellValue = this.columns[c].name in cellValues ? cellValues[this.columns[c].name] : "";
  563. rowData.columns.push(this.getTypedValue(c, cellValue));
  564. }
  565. // add row data in our model
  566. this.data.push(rowData);
  567. }
  568. return true;
  569. };
  570. /**
  571. * Process columns
  572. * @private
  573. */
  574. EditableGrid.prototype.processColumns = function()
  575. {
  576. for (var columnIndex = 0; columnIndex < this.columns.length; columnIndex++) {
  577. var column = this.columns[columnIndex];
  578. // set column index and back pointer
  579. column.columnIndex = columnIndex;
  580. column.editablegrid = this;
  581. // parse column type
  582. this.parseColumnType(column);
  583. // create suited enum provider if none given
  584. if (!column.enumProvider) column.enumProvider = column.optionValues ? new EnumProvider() : null;
  585. // create suited cell renderer if none given
  586. if (!column.cellRenderer) this._createCellRenderer(column);
  587. if (!column.headerRenderer) this._createHeaderRenderer(column);
  588. // create suited cell editor if none given
  589. if (!column.cellEditor) this._createCellEditor(column);
  590. if (!column.headerEditor) this._createHeaderEditor(column);
  591. // add default cell validators based on the column type
  592. this._addDefaultCellValidators(column);
  593. }
  594. };
  595. /**
  596. * Parse column type
  597. * @private
  598. */
  599. EditableGrid.prototype.parseColumnType = function(column)
  600. {
  601. // reset
  602. column.unit = null;
  603. column.precision = -1;
  604. column.decimal_point = ',';
  605. column.thousands_separator = '.';
  606. column.unit_before_number = false;
  607. column.nansymbol = '';
  608. // extract precision, unit and number format from type if 6 given
  609. if (column.datatype.match(/(.*)\((.*),(.*),(.*),(.*),(.*),(.*)\)$/)) {
  610. column.datatype = RegExp.$1;
  611. column.unit = RegExp.$2;
  612. column.precision = parseInt(RegExp.$3);
  613. column.decimal_point = RegExp.$4;
  614. column.thousands_separator = RegExp.$5;
  615. column.unit_before_number = RegExp.$6;
  616. column.nansymbol = RegExp.$7;
  617. // trim should be done after fetching RegExp matches beacuse it itself uses a RegExp and causes interferences!
  618. column.unit = column.unit.trim();
  619. column.decimal_point = column.decimal_point.trim();
  620. column.thousands_separator = column.thousands_separator.trim();
  621. column.unit_before_number = column.unit_before_number.trim() == '1';
  622. column.nansymbol = column.nansymbol.trim();
  623. }
  624. // extract precision, unit and number format from type if 5 given
  625. else if (column.datatype.match(/(.*)\((.*),(.*),(.*),(.*),(.*)\)$/)) {
  626. column.datatype = RegExp.$1;
  627. column.unit = RegExp.$2;
  628. column.precision = parseInt(RegExp.$3);
  629. column.decimal_point = RegExp.$4;
  630. column.thousands_separator = RegExp.$5;
  631. column.unit_before_number = RegExp.$6;
  632. // trim should be done after fetching RegExp matches beacuse it itself uses a RegExp and causes interferences!
  633. column.unit = column.unit.trim();
  634. column.decimal_point = column.decimal_point.trim();
  635. column.thousands_separator = column.thousands_separator.trim();
  636. column.unit_before_number = column.unit_before_number.trim() == '1';
  637. }
  638. // extract precision, unit and nansymbol from type if 3 given
  639. else if (column.datatype.match(/(.*)\((.*),(.*),(.*)\)$/)) {
  640. column.datatype = RegExp.$1;
  641. column.unit = RegExp.$2.trim();
  642. column.precision = parseInt(RegExp.$3);
  643. column.nansymbol = RegExp.$4.trim();
  644. }
  645. // extract precision and unit from type if two given
  646. else if (column.datatype.match(/(.*)\((.*),(.*)\)$/)) {
  647. column.datatype = RegExp.$1.trim();
  648. column.unit = RegExp.$2.trim();
  649. column.precision = parseInt(RegExp.$3);
  650. }
  651. // extract precision or unit from type if any given
  652. else if (column.datatype.match(/(.*)\((.*)\)$/)) {
  653. column.datatype = RegExp.$1.trim();
  654. var unit_or_precision = RegExp.$2.trim();
  655. if (unit_or_precision.match(/^[0-9]*$/)) column.precision = parseInt(unit_or_precision);
  656. else column.unit = unit_or_precision;
  657. }
  658. if (column.decimal_point == 'comma') column.decimal_point = ',';
  659. if (column.decimal_point == 'dot') column.decimal_point = '.';
  660. if (column.thousands_separator == 'comma') column.thousands_separator = ',';
  661. if (column.thousands_separator == 'dot') column.thousands_separator = '.';
  662. if (isNaN(column.precision)) column.precision = -1;
  663. if (column.unit == '') column.unit = null;
  664. if (column.nansymbol == '') column.nansymbol = null;
  665. };
  666. /**
  667. * Get typed value
  668. * @private
  669. */
  670. EditableGrid.prototype.getTypedValue = function(columnIndex, cellValue)
  671. {
  672. if (cellValue === null) return cellValue;
  673. var colType = this.getColumnType(columnIndex);
  674. if (colType == 'boolean') cellValue = (cellValue && cellValue != 0 && cellValue != "false") ? true : false;
  675. if (colType == 'integer') { cellValue = parseInt(cellValue, 10); }
  676. if (colType == 'double') { cellValue = parseFloat(cellValue); }
  677. if (colType == 'string') { cellValue = "" + cellValue; }
  678. return cellValue;
  679. };
  680. /**
  681. * Attach to an existing HTML table.
  682. * The second parameter can be used to give the column definitions.
  683. * This parameter is left for compatibility, but is deprecated: you should now use "load" to setup the metadata.
  684. */
  685. EditableGrid.prototype.attachToHTMLTable = function(_table, _columns)
  686. {
  687. // clear model and pointer to current table
  688. this.data = [];
  689. this.dataUnfiltered = null;
  690. this.table = null;
  691. // process columns if given
  692. if (_columns) {
  693. this.columns = _columns;
  694. for (var columnIndex = 0; columnIndex < this.columns.length; columnIndex++) this.columns[columnIndex].optionValues = this._convertOptions(this.columns[columnIndex].optionValues); // convert options from old format if needed
  695. this.processColumns();
  696. }
  697. // get pointers to table components
  698. this.table = typeof _table == 'string' ? _$(_table) : _table ;
  699. if (!this.table) console.error("Invalid table given: " + _table);
  700. this.tHead = this.table.tHead;
  701. this.tBody = this.table.tBodies[0];
  702. // create table body if needed
  703. if (!this.tBody) {
  704. this.tBody = document.createElement("TBODY");
  705. this.table.insertBefore(this.tBody, this.table.firstChild);
  706. }
  707. // create table header if needed
  708. if (!this.tHead) {
  709. this.tHead = document.createElement("THEAD");
  710. this.table.insertBefore(this.tHead, this.tBody);
  711. }
  712. // if header is empty use first body row as header
  713. if (this.tHead.rows.length == 0 && this.tBody.rows.length > 0)
  714. this.tHead.appendChild(this.tBody.rows[0]);
  715. // get number of rows in header
  716. this.nbHeaderRows = this.tHead.rows.length;
  717. // load header labels
  718. var rows = this.tHead.rows;
  719. for (var i = 0; i < rows.length; i++) {
  720. var cols = rows[i].cells;
  721. var columnIndexInModel = 0;
  722. for (var j = 0; j < cols.length && columnIndexInModel < this.columns.length; j++) {
  723. if (!this.columns[columnIndexInModel].label || this.columns[columnIndexInModel].label == this.columns[columnIndexInModel].name) this.columns[columnIndexInModel].label = cols[j].innerHTML;
  724. var colspan = parseInt(cols[j].getAttribute("colspan"));
  725. columnIndexInModel += colspan > 1 ? colspan : 1;
  726. }
  727. }
  728. // load content
  729. var rows = this.tBody.rows;
  730. for (var i = 0; i < rows.length; i++) {
  731. var rowData = [];
  732. var cols = rows[i].cells;
  733. for (var j = 0; j < cols.length && j < this.columns.length; j++) rowData.push(this.getTypedValue(j, cols[j].innerHTML));
  734. this.data.push({ visible: true, originalIndex: i, id: rows[i].id, columns: rowData });
  735. rows[i].rowId = rows[i].id;
  736. rows[i].id = this._getRowDOMId(rows[i].id);
  737. }
  738. };
  739. /**
  740. * Creates a suitable cell renderer for the column
  741. * @private
  742. */
  743. EditableGrid.prototype._createCellRenderer = function(column)
  744. {
  745. column.cellRenderer =
  746. column.enumProvider && column.datatype == "list" && typeof MultiselectCellRenderer != 'undefined' ? new MultiselectCellRenderer() :
  747. column.enumProvider ? new EnumCellRenderer() :
  748. column.datatype == "integer" || column.datatype == "double" ? new NumberCellRenderer() :
  749. column.datatype == "boolean" ? new CheckboxCellRenderer() :
  750. column.datatype == "email" ? new EmailCellRenderer() :
  751. column.datatype == "website" || column.datatype == "url" ? new WebsiteCellRenderer() :
  752. column.datatype == "date" ? new DateCellRenderer() :
  753. new CellRenderer();
  754. // give access to the column from the cell renderer
  755. if (column.cellRenderer) {
  756. column.cellRenderer.editablegrid = this;
  757. column.cellRenderer.column = column;
  758. }
  759. };
  760. /**
  761. * Creates a suitable header cell renderer for the column
  762. * @private
  763. */
  764. EditableGrid.prototype._createHeaderRenderer = function(column)
  765. {
  766. column.headerRenderer = (this.enableSort && column.datatype != "html") ? new SortHeaderRenderer(column.name) : new CellRenderer();
  767. // give access to the column from the header cell renderer
  768. if (column.headerRenderer) {
  769. column.headerRenderer.editablegrid = this;
  770. column.headerRenderer.column = column;
  771. }
  772. };
  773. /**
  774. * Creates a suitable cell editor for the column
  775. * @private
  776. */
  777. EditableGrid.prototype._createCellEditor = function(column)
  778. {
  779. column.cellEditor =
  780. column.enumProvider && column.datatype == "list" && typeof MultiselectCellEditor != 'undefined' ? new MultiselectCellEditor() :
  781. column.enumProvider ? new SelectCellEditor() :
  782. column.datatype == "integer" || column.datatype == "double" ? new NumberCellEditor(column.datatype) :
  783. column.datatype == "boolean" ? null :
  784. column.datatype == "email" ? new TextCellEditor(column.precision) :
  785. column.datatype == "website" || column.datatype == "url" ? new TextCellEditor(column.precision) :
  786. column.datatype == "date" ? (typeof jQuery == 'undefined' || typeof jQuery.datepicker == 'undefined' ? new TextCellEditor(column.precision, 10) : new DateCellEditor({ fieldSize: column.precision, maxLength: 10 })) :
  787. new TextCellEditor(column.precision);
  788. // give access to the column from the cell editor
  789. if (column.cellEditor) {
  790. column.cellEditor.editablegrid = this;
  791. column.cellEditor.column = column;
  792. }
  793. };
  794. /**
  795. * Creates a suitable header cell editor for the column
  796. * @private
  797. */
  798. EditableGrid.prototype._createHeaderEditor = function(column)
  799. {
  800. column.headerEditor = new TextCellEditor();
  801. // give access to the column from the cell editor
  802. if (column.headerEditor) {
  803. column.headerEditor.editablegrid = this;
  804. column.headerEditor.column = column;
  805. }
  806. };
  807. /**
  808. * Returns the number of rows
  809. */
  810. EditableGrid.prototype.getRowCount = function()
  811. {
  812. return this.data.length;
  813. };
  814. /**
  815. * Returns the number of rows, not taking the filter into account if any
  816. */
  817. EditableGrid.prototype.getUnfilteredRowCount = function()
  818. {
  819. // given if server-side filtering is involved
  820. if (this.unfilteredRowCount > 0) return this.unfilteredRowCount;
  821. var _data = this.dataUnfiltered == null ? this.data : this.dataUnfiltered;
  822. return _data.length;
  823. };
  824. /**
  825. * Returns the number of rows in all pages
  826. */
  827. EditableGrid.prototype.getTotalRowCount = function()
  828. {
  829. // different from getRowCount only is server-side pagination is involved
  830. if (this.totalRowCount > 0) return this.totalRowCount;
  831. return this.getRowCount();
  832. };
  833. /**
  834. * Returns the number of columns
  835. */
  836. EditableGrid.prototype.getColumnCount = function()
  837. {
  838. return this.columns.length;
  839. };
  840. /**
  841. * Returns true if the column exists
  842. * @param {Object} columnIndexOrName index or name of the column
  843. */
  844. EditableGrid.prototype.hasColumn = function(columnIndexOrName)
  845. {
  846. return this.getColumnIndex(columnIndexOrName) >= 0;
  847. };
  848. /**
  849. * Returns the column
  850. * @param {Object} columnIndexOrName index or name of the column
  851. */
  852. EditableGrid.prototype.getColumn = function(columnIndexOrName)
  853. {
  854. var colIndex = this.getColumnIndex(columnIndexOrName);
  855. if (colIndex < 0) { console.error("[getColumn] Column not found with index or name " + columnIndexOrName); return null; }
  856. return this.columns[colIndex];
  857. };
  858. /**
  859. * Returns the name of a column
  860. * @param {Object} columnIndexOrName index or name of the column
  861. */
  862. EditableGrid.prototype.getColumnName = function(columnIndexOrName)
  863. {
  864. return this.getColumn(columnIndexOrName).name;
  865. };
  866. /**
  867. * Returns the label of a column
  868. * @param {Object} columnIndexOrName index or name of the column
  869. */
  870. EditableGrid.prototype.getColumnLabel = function(columnIndexOrName)
  871. {
  872. return this.getColumn(columnIndexOrName).label;
  873. };
  874. /**
  875. * Returns the type of a column
  876. * @param {Object} columnIndexOrName index or name of the column
  877. */
  878. EditableGrid.prototype.getColumnType = function(columnIndexOrName)
  879. {
  880. return this.getColumn(columnIndexOrName).datatype;
  881. };
  882. /**
  883. * Returns the unit of a column
  884. * @param {Object} columnIndexOrName index or name of the column
  885. */
  886. EditableGrid.prototype.getColumnUnit = function(columnIndexOrName)
  887. {
  888. return this.getColumn(columnIndexOrName).unit;
  889. };
  890. /**
  891. * Returns the precision of a column
  892. * @param {Object} columnIndexOrName index or name of the column
  893. */
  894. EditableGrid.prototype.getColumnPrecision = function(columnIndexOrName)
  895. {
  896. return this.getColumn(columnIndexOrName).precision;
  897. };
  898. /**
  899. * Returns true if the column is to be displayed in a bar chart
  900. * @param {Object} columnIndexOrName index or name of the column
  901. */
  902. EditableGrid.prototype.isColumnBar = function(columnIndexOrName)
  903. {
  904. var column = this.getColumn(columnIndexOrName);
  905. return (column.bar && column.isNumerical());
  906. };
  907. /**
  908. * Returns the stack of a column (for stacked bar charts)
  909. * @param {Object} columnIndexOrName index or name of the column
  910. */
  911. EditableGrid.prototype.getColumnStack = function(columnIndexOrName)
  912. {
  913. var column = this.getColumn(columnIndexOrName);
  914. return column.isNumerical() ? column.bar : '';
  915. };
  916. /**
  917. * Returns true if the column is numerical (double or integer)
  918. * @param {Object} columnIndexOrName index or name of the column
  919. */
  920. EditableGrid.prototype.isColumnNumerical = function(columnIndexOrName)
  921. {
  922. var column = this.getColumn(columnIndexOrName);
  923. return column.isNumerical();;
  924. };
  925. /**
  926. * Returns the value at the specified index
  927. * @param {Integer} rowIndex
  928. * @param {Integer} columnIndex
  929. */
  930. EditableGrid.prototype.getValueAt = function(rowIndex, columnIndex)
  931. {
  932. // check and get column
  933. if (columnIndex < 0 || columnIndex >= this.columns.length) { console.error("[getValueAt] Invalid column index " + columnIndex); return null; }
  934. var column = this.columns[columnIndex];
  935. // get value in model
  936. if (rowIndex < 0) return column.label;
  937. if (typeof this.data[rowIndex] == 'undefined') { console.error("[getValueAt] Invalid row index " + rowIndex); return null; }
  938. var rowData = this.data[rowIndex]['columns'];
  939. return rowData ? rowData[columnIndex] : null;
  940. };
  941. /**
  942. * Returns the display value (used for sorting and filtering) at the specified index
  943. * @param {Integer} rowIndex
  944. * @param {Integer} columnIndex
  945. */
  946. EditableGrid.prototype.getDisplayValueAt = function(rowIndex, columnIndex)
  947. {
  948. // use renderer to get the value that must be used for sorting and filtering
  949. var value = this.getValueAt(rowIndex, columnIndex);
  950. var renderer = rowIndex < 0 ? this.columns[columnIndex].headerRenderer : this.columns[columnIndex].cellRenderer;
  951. return renderer.getDisplayValue(rowIndex, value);
  952. };
  953. /**
  954. * Sets the value at the specified index
  955. * @param {Integer} rowIndex
  956. * @param {Integer} columnIndex
  957. * @param {Object} value
  958. * @param {Boolean} render
  959. */
  960. EditableGrid.prototype.setValueAt = function(rowIndex, columnIndex, value, render)
  961. {
  962. if (typeof render == "undefined") render = true;
  963. var previousValue = null;;
  964. // check and get column
  965. if (columnIndex < 0 || columnIndex >= this.columns.length) { console.error("[setValueAt] Invalid column index " + columnIndex); return null; }
  966. var column = this.columns[columnIndex];
  967. // set new value in model
  968. if (rowIndex < 0) {
  969. previousValue = column.label;
  970. column.label = value;
  971. }
  972. else {
  973. if (typeof this.data[rowIndex] == 'undefined') {
  974. console.error('Invalid rowindex ' + rowIndex);
  975. return null;
  976. }
  977. var rowData = this.data[rowIndex]['columns'];
  978. previousValue = rowData[columnIndex];
  979. if (rowData) rowData[columnIndex] = this.getTypedValue(columnIndex, value);
  980. }
  981. // render new value
  982. if (render) {
  983. var renderer = rowIndex < 0 ? column.headerRenderer : column.cellRenderer;
  984. var cell = this.getCell(rowIndex, columnIndex);
  985. if (cell) renderer._render(rowIndex, columnIndex, cell, value);
  986. }
  987. return previousValue;
  988. };
  989. /**
  990. * Find column index from its name
  991. * @param {Object} columnIndexOrName index or name of the column
  992. */
  993. EditableGrid.prototype.getColumnIndex = function(columnIndexOrName)
  994. {
  995. if (typeof columnIndexOrName == "undefined" || columnIndexOrName === "") return -1;
  996. // TODO: problem because the name of a column could be a valid index, and we cannot make the distinction here!
  997. // if columnIndexOrName is a number which is a valid index return it
  998. if (!isNaN(columnIndexOrName) && columnIndexOrName >= 0 && columnIndexOrName < this.columns.length) return columnIndexOrName;
  999. // otherwise search for the name
  1000. for (var c = 0; c < this.columns.length; c++) if (this.columns[c].name == columnIndexOrName) return c;
  1001. return -1;
  1002. };
  1003. /**
  1004. * Get HTML row object at given index
  1005. * @param {Integer} index of the row
  1006. */
  1007. EditableGrid.prototype.getRow = function(rowIndex)
  1008. {
  1009. if (rowIndex < 0) return this.tHead.rows[rowIndex + this.nbHeaderRows];
  1010. if (typeof this.data[rowIndex] == 'undefined') { console.error("[getRow] Invalid row index " + rowIndex); return null; }
  1011. return _$(this._getRowDOMId(this.data[rowIndex].id));
  1012. };
  1013. /**
  1014. * Get row id for given row index
  1015. * @param {Integer} index of the row
  1016. */
  1017. EditableGrid.prototype.getRowId = function(rowIndex)
  1018. {
  1019. return (rowIndex < 0 || rowIndex >= this.data.length) ? null : this.data[rowIndex]['id'];
  1020. };
  1021. /**
  1022. * Get index of row (in filtered data) with given id
  1023. * @param {Integer} rowId or HTML row object
  1024. */
  1025. EditableGrid.prototype.getRowIndex = function(rowId)
  1026. {
  1027. rowId = typeof rowId == 'object' ? rowId.rowId : rowId;
  1028. for (var rowIndex = 0; rowIndex < this.data.length; rowIndex++) if (this.data[rowIndex].id == rowId) return rowIndex;
  1029. return -1;
  1030. };
  1031. /**
  1032. * Get custom row attribute specified in XML
  1033. * @param {Integer} index of the row
  1034. * @param {String} name of the attribute
  1035. */
  1036. EditableGrid.prototype.getRowAttribute = function(rowIndex, attributeName)
  1037. {
  1038. if (typeof this.data[rowIndex] == 'undefined') {
  1039. console.error('Invalid rowindex ' + rowIndex);
  1040. return null;
  1041. }
  1042. return this.data[rowIndex][attributeName];
  1043. };
  1044. /**
  1045. * Set custom row attribute
  1046. * @param {Integer} index of the row
  1047. * @param {String} name of the attribute
  1048. * @param value of the attribute
  1049. */
  1050. EditableGrid.prototype.setRowAttribute = function(rowIndex, attributeName, attributeValue)
  1051. {
  1052. this.data[rowIndex][attributeName] = attributeValue;
  1053. };
  1054. /**
  1055. * Get Id of row in HTML DOM
  1056. * @private
  1057. */
  1058. EditableGrid.prototype._getRowDOMId = function(rowId)
  1059. {
  1060. return this.currentContainerid != null ? this.name + "_" + rowId : rowId;
  1061. };
  1062. /**
  1063. * Remove row with given id
  1064. * Deprecated: use remove(rowIndex) instead
  1065. * @param {Integer} rowId
  1066. */
  1067. EditableGrid.prototype.removeRow = function(rowId)
  1068. {
  1069. return this.remove(this.getRowIndex(rowId));
  1070. };
  1071. /**
  1072. * Remove row at given index
  1073. * @param {Integer} rowIndex
  1074. */
  1075. EditableGrid.prototype.remove = function(rowIndex)
  1076. {
  1077. var rowId = this.data[rowIndex].id;
  1078. var originalIndex = this.data[rowIndex].originalIndex;
  1079. var _data = this.dataUnfiltered == null ? this.data : this.dataUnfiltered;
  1080. // delete row from DOM (needed for attach mode)
  1081. var tr = _$(this._getRowDOMId(rowId));
  1082. if (tr != null) this.tBody.removeChild(tr);
  1083. // update originalRowIndex
  1084. for (var r = 0; r < _data.length; r++) if (_data[r].originalIndex >= originalIndex) _data[r].originalIndex--;
  1085. // delete row from data
  1086. this.data.splice(rowIndex, 1);
  1087. if (this.dataUnfiltered != null) for (var r = 0; r < this.dataUnfiltered.length; r++) if (this.dataUnfiltered[r].id == rowId) { this.dataUnfiltered.splice(r, 1); break; }
  1088. // callback
  1089. this.rowRemoved(rowIndex,rowId);
  1090. // refresh grid
  1091. this.refreshGrid();
  1092. };
  1093. /**
  1094. * Return an associative array (column name => value) of values in row with given index
  1095. * @param {Integer} rowIndex
  1096. */
  1097. EditableGrid.prototype.getRowValues = function(rowIndex)
  1098. {
  1099. var rowValues = {};
  1100. for (var columnIndex = 0; columnIndex < this.getColumnCount(); columnIndex++) {
  1101. rowValues[this.getColumnName(columnIndex)] = this.getValueAt(rowIndex, columnIndex);
  1102. }
  1103. return rowValues;
  1104. };
  1105. /**
  1106. * Append row with given id and data
  1107. * @param {Integer} rowId id of new row
  1108. * @param {Integer} columns
  1109. * @param {Boolean} dontSort
  1110. */
  1111. EditableGrid.prototype.append = function(rowId, cellValues, rowAttributes, dontSort)
  1112. {
  1113. return this.insertAfter(this.data.length - 1, rowId, cellValues, rowAttributes, dontSort);
  1114. };
  1115. /**
  1116. * Append row with given id and data
  1117. * Deprecated: use append instead
  1118. * @param {Integer} rowId id of new row
  1119. * @param {Integer} columns
  1120. * @param {Boolean} dontSort
  1121. */
  1122. EditableGrid.prototype.addRow = function(rowId, cellValues, rowAttributes, dontSort)
  1123. {
  1124. return this.append(rowId, cellValues, rowAttributes, dontSort);
  1125. };
  1126. /**
  1127. * Insert row with given id and data at given location
  1128. * We know rowIndex is valid, unless the table is empty
  1129. * @private
  1130. */
  1131. EditableGrid.prototype._insert = function(rowIndex, offset, rowId, cellValues, rowAttributes, dontSort)
  1132. {
  1133. var originalRowId = null;
  1134. var originalIndex = 0;
  1135. var _data = this.dataUnfiltered == null ? this.data : this.dataUnfiltered;
  1136. if (typeof this.data[rowIndex] != "undefined") {
  1137. originalRowId = this.data[rowIndex].id;
  1138. originalIndex = this.data[rowIndex].originalIndex + offset;
  1139. }
  1140. // append row in DOM (needed for attach mode)
  1141. if (this.currentContainerid == null) {
  1142. var tr = this.tBody.insertRow(rowIndex + offset);
  1143. tr.rowId = rowId;
  1144. tr.id = this._getRowDOMId(rowId);
  1145. for (var c = 0; c < this.columns.length; c++) tr.insertCell(c);
  1146. }
  1147. // build data for new row
  1148. var rowData = { visible: true, originalIndex: originalIndex, id: rowId };
  1149. if (rowAttributes) for (var attributeName in rowAttributes) rowData[attributeName] = rowAttributes[attributeName];
  1150. rowData.columns = [];
  1151. for (var c = 0; c < this.columns.length; c++) {
  1152. var cellValue = this.columns[c].name in cellValues ? cellValues[this.columns[c].name] : "";
  1153. rowData.columns.push(this.getTypedValue(c, cellValue));
  1154. }
  1155. // update originalRowIndex
  1156. for (var r = 0; r < _data.length; r++) if (_data[r].originalIndex >= originalIndex) _data[r].originalIndex++;
  1157. // append row in data
  1158. this.data.splice(rowIndex + offset, 0, rowData);
  1159. if (this.dataUnfiltered != null) {
  1160. if (originalRowId === null) this.dataUnfiltered.splice(rowIndex + offset, 0, rowData);
  1161. else for (var r = 0; r < this.dataUnfiltered.length; r++) if (this.dataUnfiltered[r].id == originalRowId) { this.dataUnfiltered.splice(r + offset, 0, rowData); break; }
  1162. }
  1163. // refresh grid
  1164. this.refreshGrid();
  1165. // sort and filter table
  1166. if (!dontSort) this.sort();
  1167. this.filter();
  1168. };
  1169. /**
  1170. * Insert row with given id and data before given row index
  1171. * @param {Integer} rowIndex index of row before which to insert new row
  1172. * @param {Integer} rowId id of new row
  1173. * @param {Integer} columns
  1174. * @param {Boolean} dontSort
  1175. */
  1176. EditableGrid.prototype.insert = function(rowIndex, rowId, cellValues, rowAttributes, dontSort)
  1177. {
  1178. if (rowIndex < 0) rowIndex = 0;
  1179. if (rowIndex >= this.data.length && this.data.length > 0) return this.insertAfter(this.data.length - 1, rowId, cellValues, rowAttributes, dontSort);
  1180. return this._insert(rowIndex, 0, rowId, cellValues, rowAttributes, dontSort);
  1181. };
  1182. /**
  1183. * Insert row with given id and data after given row index
  1184. * @param {Integer} rowIndex index of row after which to insert new row
  1185. * @param {Integer} rowId id of new row
  1186. * @param {Integer} columns
  1187. * @param {Boolean} dontSort
  1188. */
  1189. EditableGrid.prototype.insertAfter = function(rowIndex, rowId, cellValues, rowAttributes, dontSort)
  1190. {
  1191. if (rowIndex < 0) return this.insert(0, rowId, cellValues, rowAttributes, dontSort);
  1192. if (rowIndex >= this.data.length) rowIndex = this.data.length - 1;
  1193. return this._insert(rowIndex, 1, rowId, cellValues, rowAttributes, dontSort);
  1194. };
  1195. /**
  1196. * Sets the column header cell renderer for the specified column index
  1197. * @param {Object} columnIndexOrName index or name of the column
  1198. * @param {CellRenderer} cellRenderer
  1199. */
  1200. EditableGrid.prototype.setHeaderRenderer = function(columnIndexOrName, cellRenderer)
  1201. {
  1202. var columnIndex = this.getColumnIndex(columnIndexOrName);
  1203. if (columnIndex < 0) console.error("[setHeaderRenderer] Invalid column: " + columnIndexOrName);
  1204. else {
  1205. var column = this.columns[columnIndex];
  1206. column.headerRenderer = (this.enableSort && column.datatype != "html") ? new SortHeaderRenderer(column.name, cellRenderer) : cellRenderer;
  1207. // give access to the column from the cell renderer
  1208. if (cellRenderer) {
  1209. if (this.enableSort && column.datatype != "html") {
  1210. column.headerRenderer.editablegrid = this;
  1211. column.headerRenderer.column = column;
  1212. }
  1213. cellRenderer.editablegrid = this;
  1214. cellRenderer.column = column;
  1215. }
  1216. }
  1217. };
  1218. /**
  1219. * Sets the cell renderer for the specified column index
  1220. * @param {Object} columnIndexOrName index or name of the column
  1221. * @param {CellRenderer} cellRenderer
  1222. */
  1223. EditableGrid.prototype.setCellRenderer = function(columnIndexOrName, cellRenderer)
  1224. {
  1225. var columnIndex = this.getColumnIndex(columnIndexOrName);
  1226. if (columnIndex < 0) console.error("[setCellRenderer] Invalid column: " + columnIndexOrName);
  1227. else {
  1228. var column = this.columns[columnIndex];
  1229. column.cellRenderer = cellRenderer;
  1230. // give access to the column from the cell renderer
  1231. if (cellRenderer) {
  1232. cellRenderer.editablegrid = this;
  1233. cellRenderer.column = column;
  1234. }
  1235. }
  1236. };
  1237. /**
  1238. * Sets the cell editor for the specified column index
  1239. * @param {Object} columnIndexOrName index or name of the column
  1240. * @param {CellEditor} cellEditor
  1241. */
  1242. EditableGrid.prototype.setCellEditor = function(columnIndexOrName, cellEditor)
  1243. {
  1244. var columnIndex = this.getColumnIndex(columnIndexOrName);
  1245. if (columnIndex < 0) console.error("[setCellEditor] Invalid column: " + columnIndexOrName);
  1246. else {
  1247. var column = this.columns[columnIndex];
  1248. column.cellEditor = cellEditor;
  1249. // give access to the column from the cell editor
  1250. if (cellEditor) {
  1251. cellEditor.editablegrid = this;
  1252. cellEditor.column = column;
  1253. }
  1254. }
  1255. };
  1256. /**
  1257. * Sets the header cell editor for the specified column index
  1258. * @param {Object} columnIndexOrName index or name of the column
  1259. * @param {CellEditor} cellEditor
  1260. */
  1261. EditableGrid.prototype.setHeaderEditor = function(columnIndexOrName, cellEditor)
  1262. {
  1263. var columnIndex = this.getColumnIndex(columnIndexOrName);
  1264. if (columnIndex < 0) console.error("[setHeaderEditor] Invalid column: " + columnIndexOrName);
  1265. else {
  1266. var column = this.columns[columnIndex];
  1267. column.headerEditor = cellEditor;
  1268. // give access to the column from the cell editor
  1269. if (cellEditor) {
  1270. cellEditor.editablegrid = this;
  1271. cellEditor.column = column;
  1272. }
  1273. }
  1274. };
  1275. /**
  1276. * Sets the enum provider for the specified column index
  1277. * @param {Object} columnIndexOrName index or name of the column
  1278. * @param {EnumProvider} enumProvider
  1279. */
  1280. EditableGrid.prototype.setEnumProvider = function(columnIndexOrName, enumProvider)
  1281. {
  1282. var columnIndex = this.getColumnIndex(columnIndexOrName);
  1283. if (columnIndex < 0) console.error("[setEnumProvider] Invalid column: " + columnIndexOrName);
  1284. else {
  1285. var hadProviderAlready = this.columns[columnIndex].enumProvider != null;
  1286. this.columns[columnIndex].enumProvider = enumProvider;
  1287. // if needed, we recreate the cell renderer and editor for this column
  1288. // if the column had an enum provider already, the render/editor previously created by default is ok already
  1289. // ... and we don't want to erase a custom renderer/editor that may have been set before calling setEnumProvider
  1290. if (!hadProviderAlready) {
  1291. this._createCellRenderer(this.columns[columnIndex]);
  1292. this._createCellEditor(this.columns[columnIndex]);
  1293. }
  1294. }
  1295. };
  1296. /**
  1297. * Clear all cell validators for the specified column index
  1298. * @param {Object} columnIndexOrName index or name of the column
  1299. */
  1300. EditableGrid.prototype.clearCellValidators = function(columnIndexOrName)
  1301. {
  1302. var columnIndex = this.getColumnIndex(columnIndexOrName);
  1303. if (columnIndex < 0) console.error("[clearCellValidators] Invalid column: " + columnIndexOrName);
  1304. else this.columns[columnIndex].cellValidators = [];
  1305. };
  1306. /**
  1307. * Adds default cell validators for the specified column index (according to the column type)
  1308. * @param {Object} columnIndexOrName index or name of the column
  1309. */
  1310. EditableGrid.prototype.addDefaultCellValidators = function(columnIndexOrName)
  1311. {
  1312. var columnIndex = this.getColumnIndex(columnIndexOrName);
  1313. if (columnIndex < 0) console.error("[addDefaultCellValidators] Invalid column: " + columnIndexOrName);
  1314. return this._addDefaultCellValidators(this.columns[columnIndex]);
  1315. };
  1316. /**
  1317. * Adds default cell validators for the specified column
  1318. * @private
  1319. */
  1320. EditableGrid.prototype._addDefaultCellValidators = function(column)
  1321. {
  1322. if (column.datatype == "integer" || column.datatype == "double") column.cellValidators.push(new NumberCellValidator(column.datatype));
  1323. else if (column.datatype == "email") column.cellValidators.push(new EmailCellValidator());
  1324. else if (column.datatype == "website" || column.datatype == "url") column.cellValidators.push(new WebsiteCellValidator());
  1325. else if (column.datatype == "date") column.cellValidators.push(new DateCellValidator(this));
  1326. };
  1327. /**
  1328. * Adds a cell validator for the specified column index
  1329. * @param {Object} columnIndexOrName index or name of the column
  1330. * @param {CellValidator} cellValidator
  1331. */
  1332. EditableGrid.prototype.addCellValidator = function(columnIndexOrName, cellValidator)
  1333. {
  1334. var columnIndex = this.getColumnIndex(columnIndexOrName);
  1335. if (columnIndex < 0) console.error("[addCellValidator] Invalid column: " + columnIndexOrName);
  1336. else this.columns[columnIndex].cellValidators.push(cellValidator);
  1337. };
  1338. /**
  1339. * Sets the table caption: set as null to remove
  1340. * @param columnIndexOrName
  1341. * @param caption
  1342. * @return
  1343. */
  1344. EditableGrid.prototype.setCaption = function(caption)
  1345. {
  1346. this.caption = caption;
  1347. };
  1348. /**
  1349. * Get cell element at given row and column
  1350. */
  1351. EditableGrid.prototype.getCell = function(rowIndex, columnIndex)
  1352. {
  1353. var row = this.getRow(rowIndex);
  1354. if (row == null) { console.error("[getCell] Invalid row index " + rowIndex); return null; }
  1355. return row.cells[columnIndex];
  1356. };
  1357. /**
  1358. * Get cell X position relative to the first non static offset parent
  1359. * @private
  1360. */
  1361. EditableGrid.prototype.getCellX = function(oElement)
  1362. {
  1363. var iReturnValue = 0;
  1364. while (oElement != null && this.isStatic(oElement)) try {
  1365. iReturnValue += oElement.offsetLeft;
  1366. oElement = oElement.offsetParent;
  1367. } catch(err) { oElement = null; }
  1368. return iReturnValue;
  1369. };
  1370. /**
  1371. * Get cell Y position relative to the first non static offset parent
  1372. * @private
  1373. */
  1374. EditableGrid.prototype.getCellY = function(oElement)
  1375. {
  1376. var iReturnValue = 0;
  1377. while (oElement != null && this.isStatic(oElement)) try {
  1378. iReturnValue += oElement.offsetTop;
  1379. oElement = oElement.offsetParent;
  1380. } catch(err) { oElement = null; }
  1381. return iReturnValue;
  1382. };
  1383. /**
  1384. * Get X scroll offset relative to the first non static offset parent
  1385. * @private
  1386. */
  1387. EditableGrid.prototype.getScrollXOffset = function(oElement)
  1388. {
  1389. var iReturnValue = 0;
  1390. while (oElement != null && typeof oElement.scrollLeft != 'undefined' && this.isStatic(oElement) && oElement != document.body) try {
  1391. iReturnValue += parseInt(oElement.scrollLeft);
  1392. oElement = oElement.parentNode;
  1393. } catch(err) { oElement = null; }
  1394. return iReturnValue;
  1395. };
  1396. /**
  1397. * Get Y scroll offset relative to the first non static offset parent
  1398. * @private
  1399. */
  1400. EditableGrid.prototype.getScrollYOffset = function(oElement)
  1401. {
  1402. var iReturnValue = 0;
  1403. while (oElement != null && typeof oElement.scrollTop != 'undefined' && this.isStatic(oElement) && oElement != document.body) try {
  1404. iReturnValue += parseInt(oElement.scrollTop);
  1405. oElement = oElement.parentNode;
  1406. } catch(err) { oElement = null; }
  1407. return iReturnValue;
  1408. };
  1409. /**
  1410. * Private
  1411. * @param containerid
  1412. * @param className
  1413. * @param tableid
  1414. * @return
  1415. */
  1416. EditableGrid.prototype._rendergrid = function(containerid, className, tableid)
  1417. {
  1418. with (this) {
  1419. lastSelectedRowIndex = -1;
  1420. _currentPageIndex = getCurrentPageIndex();
  1421. // if we are already attached to an existing table, just update the cell contents
  1422. if (typeof table != "undefined" && table != null) {
  1423. var _data = dataUnfiltered == null ? data : dataUnfiltered;
  1424. // render headers
  1425. _renderHeaders();
  1426. // render content
  1427. var rows = tBody.rows;
  1428. var skipped = 0;
  1429. var displayed = 0;
  1430. var rowIndex = 0;
  1431. for (var i = 0; i < rows.length; i++) {
  1432. // filtering and pagination in attach mode means hiding rows
  1433. if (!_data[i].visible || (pageSize > 0 && displayed >= pageSize)) {
  1434. if (rows[i].style.display != 'none') {
  1435. rows[i].style.display = 'none';
  1436. rows[i].hidden_by_editablegrid = true;
  1437. }
  1438. }
  1439. else {
  1440. if (skipped < pageSize * _currentPageIndex) {
  1441. skipped++;
  1442. if (rows[i].style.display != 'none') {
  1443. rows[i].style.display = 'none';
  1444. rows[i].hidden_by_editablegrid = true;
  1445. }
  1446. }
  1447. else {
  1448. displayed++;
  1449. var cols = rows[i].cells;
  1450. if (typeof rows[i].hidden_by_editablegrid != 'undefined' && rows[i].hidden_by_editablegrid) {
  1451. rows[i].style.display = '';
  1452. rows[i].hidden_by_editablegrid = false;
  1453. }
  1454. rows[i].rowId = getRowId(rowIndex);
  1455. rows[i].id = _getRowDOMId(rows[i].rowId);
  1456. for (var j = 0; j < cols.length && j < columns.length; j++)
  1457. if (columns[j].renderable) columns[j].cellRenderer._render(rowIndex, j, cols[j], getValueAt(rowIndex,j));
  1458. }
  1459. rowIndex++;
  1460. }
  1461. }
  1462. // attach handler on click or double click
  1463. table.editablegrid = this;
  1464. if (doubleclick) table.ondblclick = function(e) { this.editablegrid.mouseClicked(e); };
  1465. else table.onclick = function(e) { this.editablegrid.mouseClicked(e); };
  1466. }
  1467. // we must render a whole new table
  1468. else {
  1469. if (!containerid) return console.warn("The container ID not specified (renderGrid not called yet ?)");
  1470. if (!_$(containerid)) return console.error("Unable to get element [" + containerid + "]");
  1471. currentContainerid = containerid;
  1472. currentClassName = className;
  1473. currentTableid = tableid;
  1474. var startRowIndex = 0;
  1475. var endRowIndex = getRowCount();
  1476. // paginate if required
  1477. if (pageSize > 0) {
  1478. startRowIndex = _currentPageIndex * pageSize;
  1479. endRowIndex = Math.min(getRowCount(), startRowIndex + pageSize);
  1480. }
  1481. // create editablegrid table and add it to our container
  1482. this.table = document.createElement("table");
  1483. table.className = className || "editablegrid";
  1484. if (typeof tableid != "undefined") table.id = tableid;
  1485. while (_$(containerid).hasChildNodes()) _$(containerid).removeChild(_$(containerid).firstChild);
  1486. _$(containerid).appendChild(table);
  1487. // create header
  1488. if (caption) {
  1489. var captionElement = document.createElement("CAPTION");
  1490. captionElement.innerHTML = this.caption;
  1491. table.appendChild(captionElement);
  1492. }
  1493. this.tHead = document.createElement("THEAD");
  1494. table.appendChild(tHead);
  1495. var trHeader = tHead.insertRow(0);
  1496. var columnCount = getColumnCount();
  1497. for (var c = 0; c < columnCount; c++) {
  1498. var headerCell = document.createElement("TH");
  1499. var td = trHeader.appendChild(headerCell);
  1500. columns[c].headerRenderer._render(-1, c, td, columns[c].label);
  1501. }
  1502. // create body and rows
  1503. this.tBody = document.createElement("TBODY");
  1504. table.appendChild(tBody);
  1505. var insertRowIndex = 0;
  1506. for (var i = startRowIndex; i < endRowIndex; i++) {
  1507. var tr = tBody.insertRow(insertRowIndex++);
  1508. tr.rowId = data[i]['id'];
  1509. tr.id = this._getRowDOMId(data[i]['id']);
  1510. for (j = 0; j < columnCount; j++) {
  1511. // create cell and render its content
  1512. var td = tr.insertCell(j);
  1513. columns[j].cellRenderer._render(i, j, td, getValueAt(i,j));
  1514. }
  1515. }
  1516. // attach handler on click or double click
  1517. _$(containerid).editablegrid = this;
  1518. if (doubleclick) _$(containerid).ondblclick = function(e) { this.editablegrid.mouseClicked(e); };
  1519. else _$(containerid).onclick = function(e) { this.editablegrid.mouseClicked(e); };
  1520. }
  1521. // callback
  1522. tableRendered(containerid, className, tableid);
  1523. }
  1524. };
  1525. /**
  1526. * Renders the grid as an HTML table in the document
  1527. * @param {String} containerid
  1528. * id of the div in which you wish to render the HTML table (this parameter is ignored if you used attachToHTMLTable)
  1529. * @param {String} className
  1530. * CSS class name to be applied to the table (this parameter is ignored if you used attachToHTMLTable)
  1531. * @param {String} tableid
  1532. * ID to give to the table (this parameter is ignored if you used attachToHTMLTable)
  1533. * @see EditableGrid#attachToHTMLTable
  1534. * @see EditableGrid#loadXML
  1535. */
  1536. EditableGrid.prototype.renderGrid = function(containerid, className, tableid)
  1537. {
  1538. // actually render grid
  1539. this._rendergrid(containerid, className, tableid);
  1540. // if client side: sort and filter
  1541. if (!this.serverSide) {
  1542. this.sort() ;
  1543. this.filter();
  1544. }
  1545. };
  1546. /**
  1547. * Refreshes the grid
  1548. * @return
  1549. */
  1550. EditableGrid.prototype.refreshGrid = function()
  1551. {
  1552. if (this.currentContainerid != null) this.table = null; // if we are not in "attach mode", clear table to force a full re-render
  1553. this._rendergrid(this.currentContainerid, this.currentClassName, this.currentTableid);
  1554. };
  1555. /**
  1556. * Render all column headers
  1557. * @private
  1558. */
  1559. EditableGrid.prototype._renderHeaders = function()
  1560. {
  1561. with (this) {
  1562. var rows = tHead.rows;
  1563. for (var i = 0; i < 1 /*rows.length*/; i++) {
  1564. var rowData = [];
  1565. var cols = rows[i].cells;
  1566. var columnIndexInModel = 0;
  1567. for (var j = 0; j < cols.length && columnIndexInModel < columns.length; j++) {
  1568. columns[columnIndexInModel].headerRenderer._render(-1, columnIndexInModel, cols[j], columns[columnIndexInModel].label);
  1569. var colspan = parseInt(cols[j].getAttribute("colspan"));
  1570. columnIndexInModel += colspan > 1 ? colspan : 1;
  1571. }
  1572. }
  1573. }
  1574. };
  1575. /**
  1576. * Mouse click handler
  1577. * @param {Object} e
  1578. * @private
  1579. */
  1580. EditableGrid.prototype.mouseClicked = function(e)
  1581. {
  1582. e = e || window.event;
  1583. with (this) {
  1584. // get row and column index from the clicked cell
  1585. var target = e.target || e.srcElement;
  1586. // go up parents to find a cell or a link under the clicked position
  1587. while (target) if (target.tagName == "A" || target.tagName == "TD" || target.tagName == "TH") break; else target = target.parentNode;
  1588. if (!target || !target.parentNode || !target.parentNode.parentNode || (target.parentNode.parentNode.tagName != "TBODY" && target.parentNode.parentNode.tagName != "THEAD") || target.isEditing) return;
  1589. // don't handle clicks on links
  1590. if (target.tagName == "A") return;
  1591. // get cell position in table
  1592. var rowIndex = getRowIndex(target.parentNode);
  1593. var columnIndex = target.cellIndex;
  1594. var column = columns[columnIndex];
  1595. if (column) {
  1596. // if another row has been selected: callback
  1597. if (rowIndex > -1 && rowIndex != lastSelectedRowIndex) {
  1598. rowSelected(lastSelectedRowIndex, rowIndex);
  1599. lastSelectedRowIndex = rowIndex;
  1600. }
  1601. // edit current cell value
  1602. if (!column.editable) { readonlyWarning(column); }
  1603. else {
  1604. if (rowIndex < 0) {
  1605. if (column.headerEditor && isHeaderEditable(rowIndex, columnIndex))
  1606. column.headerEditor.edit(rowIndex, columnIndex, target, column.label);
  1607. }
  1608. else if (column.cellEditor && isEditable(rowIndex, columnIndex))
  1609. column.cellEditor.edit(rowIndex, columnIndex, target, getValueAt(rowIndex, columnIndex));
  1610. }
  1611. }
  1612. }
  1613. };
  1614. /**
  1615. * Moves columns around (added by JRE)
  1616. * @param {array[strings]} an array of class names of the headers
  1617. * returns boolean based on success
  1618. */
  1619. EditableGrid.prototype.sortColumns = function(headerArray){
  1620. with (this){
  1621. newColumns = [];
  1622. newColumnIndeces = [];
  1623. for (var i = 0; i < headerArray.length; i++) {
  1624. columnIndex = this.getColumnIndex(headerArray[i]);
  1625. if(columnIndex == -1){//a column could not be found. can't reorder anything or data may be lost
  1626. console.error("[sortColumns] Invalid column: " + columnIndex);
  1627. return false;
  1628. }
  1629. newColumns[i] = this.columns[columnIndex];
  1630. newColumnIndeces[i] = columnIndex;
  1631. }
  1632. //rearrance headers
  1633. this.columns = newColumns;
  1634. //need to rearrange all of the data elements as well
  1635. for (var i = 0; i < this.data.length; i++) {
  1636. var myData = this.data[i];
  1637. var myDataColumns = myData.columns;
  1638. var newDataColumns = [];
  1639. for (var j = 0; j < myDataColumns.length; j++) {
  1640. newIndex = newColumnIndeces[j];
  1641. newDataColumns[j] = myDataColumns[newIndex];
  1642. }
  1643. this.data[i].columns = newDataColumns;
  1644. }
  1645. return true;
  1646. }
  1647. }
  1648. /**
  1649. * Sort on a column
  1650. * @param {Object} columnIndexOrName index or name of the column
  1651. * @param {Boolean} descending
  1652. */
  1653. EditableGrid.prototype.sort = function(columnIndexOrName, descending, backOnFirstPage)
  1654. {
  1655. with (this) {
  1656. if (typeof columnIndexOrName == 'undefined' && sortedColumnName === -1) {
  1657. // avoid a double render, but still send the expected callback
  1658. tableSorted(-1, sortDescending);
  1659. return true;
  1660. }
  1661. if (typeof columnIndexOrName == 'undefined') columnIndexOrName = sortedColumnName;
  1662. if (typeof descending == 'undefined') descending = sortDescending;
  1663. localset('sortColumnIndexOrName', columnIndexOrName);
  1664. localset('sortDescending', descending);
  1665. // if sorting is done on server-side, we are done here
  1666. if (serverSide) return backOnFirstPage ? setPageIndex(0) : refreshGrid();
  1667. var columnIndex = columnIndexOrName;
  1668. if (parseInt(columnIndex, 10) !== -1) {
  1669. columnIndex = this.getColumnIndex(columnIndexOrName);
  1670. if (columnIndex < 0) {
  1671. console.error("[sort] Invalid column: " + columnIndexOrName);
  1672. return false;
  1673. }
  1674. }
  1675. if (!enableSort) {
  1676. tableSorted(columnIndex, descending);
  1677. return;
  1678. }
  1679. // work on unfiltered data
  1680. var filterActive = dataUnfiltered != null;
  1681. if (filterActive) data = dataUnfiltered;
  1682. var type = columnIndex < 0 ? "" : getColumnType(columnIndex);
  1683. var row_array = [];
  1684. var rowCount = getRowCount();
  1685. for (var i = 0; i < rowCount - (ignoreLastRow ? 1 : 0); i++) row_array.push([columnIndex < 0 ? null : getDisplayValueAt(i, columnIndex), i, data[i].originalIndex]);
  1686. var sort_function = type == "integer" || type == "double" ? sort_numeric : type == "boolean" ? sort_boolean : type == "date" ? sort_date : sort_alpha;
  1687. row_array.sort(columnIndex < 0 ? unsort : sort_stable(sort_function, descending));
  1688. if (ignoreLastRow) row_array.push([columnIndex < 0 ? null : getDisplayValueAt(rowCount - 1, columnIndex), rowCount - 1, data[rowCount - 1].originalIndex]);
  1689. // rebuild data using the new order
  1690. var _data = data;
  1691. data = [];
  1692. for (var i = 0; i < row_array.length; i++) data.push(_data[row_array[i][1]]);
  1693. delete row_array;
  1694. if (filterActive) {
  1695. // keep only visible rows in data
  1696. dataUnfiltered = data;
  1697. data = [];
  1698. for (var r = 0; r < rowCount; r++) if (dataUnfiltered[r].visible) data.push(dataUnfiltered[r]);
  1699. }
  1700. // refresh grid (back on first page if sort column has changed) and callback
  1701. if (backOnFirstPage) setPageIndex(0); else refreshGrid();
  1702. tableSorted(columnIndex, descending);
  1703. return true;
  1704. }
  1705. };
  1706. /**
  1707. * Filter the content of the table
  1708. * @param {String} filterString String string used to filter: all words must be found in the row
  1709. * @param {Array} cols Columns to sort. If cols is not specified, the filter will be done on all columns
  1710. */
  1711. EditableGrid.prototype.filter = function(filterString, cols)
  1712. {
  1713. with (this) {
  1714. if (typeof filterString != 'undefined') {
  1715. this.currentFilter = filterString;
  1716. this.localset('filter', filterString);
  1717. }
  1718. // if filtering is done on server-side, we are done here
  1719. if (serverSide) return setPageIndex(0);
  1720. // un-filter if no or empty filter set
  1721. if (currentFilter == null || currentFilter == "") {
  1722. if (dataUnfiltered != null) {
  1723. data = dataUnfiltered;
  1724. dataUnfiltered = null;
  1725. for (var r = 0; r < getRowCount(); r++) data[r].visible = true;
  1726. setPageIndex(0);
  1727. tableFiltered();
  1728. }
  1729. return;
  1730. }
  1731. var words = currentFilter.toLowerCase().split(" ");
  1732. // work on unfiltered data
  1733. if (dataUnfiltered != null) data = dataUnfiltered;
  1734. var rowCount = getRowCount();
  1735. var columnCount = typeof cols != 'undefined' ? cols.length : getColumnCount();
  1736. for (var r = 0; r < rowCount; r++) {
  1737. var row = data[r];
  1738. row.visible = true;
  1739. var rowContent = "";
  1740. // add column values
  1741. for (var c = 0; c < columnCount; c++) {
  1742. if (getColumnType(c) == 'boolean') continue;
  1743. var displayValue = getDisplayValueAt(r, typeof cols != 'undefined' ? cols[c] : c);
  1744. var value = getValueAt(r, typeof cols != 'undefined' ? cols[c] : c);
  1745. rowContent += displayValue + " " + (displayValue == value ? "" : value + " ");
  1746. }
  1747. // add attribute values
  1748. for (var attributeName in row) {
  1749. if (attributeName != "visible" && attributeName != "originalIndex" && attributeName != "columns") rowContent += row[attributeName];
  1750. }
  1751. // if row contents do not match one word in the filter, hide the row
  1752. for (var i = 0; i < words.length; i++) {
  1753. var word = words[i];
  1754. var match = false;
  1755. // a word starting with "!" means that we want a NON match
  1756. var invertMatch = word.startsWith("!");
  1757. if (invertMatch) word = word.substr(1);
  1758. // if word is of the form "colname/attributename=value" or "colname/attributename!=value", only this column/attribute is used
  1759. var colindex = -1;
  1760. var attributeName = null;
  1761. if (word.contains("!=")) {
  1762. var parts = word.split("!=");
  1763. colindex = getColumnIndex(parts[0]);
  1764. if (colindex >= 0) {
  1765. word = parts[1];
  1766. invertMatch = !invertMatch;
  1767. }
  1768. else if (typeof row[parts[0]] != 'undefined') {
  1769. attributeName = parts[0];
  1770. word = parts[1];
  1771. invertMatch = !invertMatch;
  1772. }
  1773. }
  1774. else if (word.contains("=")) {
  1775. var parts = word.split("=");
  1776. colindex = getColumnIndex(parts[0]);
  1777. if (colindex >= 0) word = parts[1];
  1778. else if (typeof row[parts[0]] != 'undefined') {
  1779. attributeName = parts[0];
  1780. word = parts[1];
  1781. }
  1782. }
  1783. // a word ending with "!" means that a column must match this word exactly
  1784. if (!word.endsWith("!")) {
  1785. if (colindex >= 0) match = (getValueAt(r, colindex) + ' ' + getDisplayValueAt(r, colindex)).trim().toLowerCase().indexOf(word) >= 0;
  1786. else if (attributeName !== null) match = (''+getRowAttribute(r, attributeName)).trim().toLowerCase().indexOf(word) >= 0;
  1787. else match = rowContent.toLowerCase().indexOf(word) >= 0;
  1788. }
  1789. else {
  1790. word = word.substr(0, word.length - 1);
  1791. if (colindex >= 0) match = (''+getDisplayValueAt(r, colindex)).trim().toLowerCase() == word || (''+getValueAt(r, colindex)).trim().toLowerCase() == word;
  1792. else if (attributeName !== null) match = (''+getRowAttribute(r, attributeName)).trim().toLowerCase() == word;
  1793. else for (var c = 0; c < columnCount; c++) {
  1794. if (getColumnType(typeof cols != 'undefined' ? cols[c] : c) == 'boolean') continue;
  1795. if ((''+getDisplayValueAt(r, typeof cols != 'undefined' ? cols[c] : c)).trim().toLowerCase() == word || (''+getValueAt(r, typeof cols != 'undefined' ? cols[c] : c)).trim().toLowerCase() == word) match = true;
  1796. }
  1797. }
  1798. if (invertMatch ? match : !match) {
  1799. data[r].visible = false;
  1800. break;
  1801. }
  1802. }
  1803. }
  1804. // keep only visible rows in data
  1805. dataUnfiltered = data;
  1806. data = [];
  1807. for (var r = 0; r < rowCount; r++) if (dataUnfiltered[r].visible) data.push(dataUnfiltered[r]);
  1808. // refresh grid (back on first page) and callback
  1809. setPageIndex(0);
  1810. tableFiltered();
  1811. }
  1812. };
  1813. /**
  1814. * Sets the page size(pageSize of 0 means no pagination)
  1815. * @param {Integer} pageSize Integer page size
  1816. */
  1817. EditableGrid.prototype.setPageSize = function(pageSize)
  1818. {
  1819. this.pageSize = parseInt(pageSize);
  1820. if (isNaN(this.pageSize)) this.pageSize = 0;
  1821. this.currentPageIndex = 0;
  1822. this.refreshGrid();
  1823. };
  1824. /**
  1825. * Returns the number of pages according to the current page size
  1826. */
  1827. EditableGrid.prototype.getPageCount = function()
  1828. {
  1829. if (this.getRowCount() == 0) return 0;
  1830. if (this.pageCount > 0) return this.pageCount; // server side pagination
  1831. else if (this.pageSize <= 0) { console.error("getPageCount: no or invalid page size defined (" + this.pageSize + ")"); return -1; }
  1832. return Math.ceil(this.getRowCount() / this.pageSize);
  1833. };
  1834. /**
  1835. * Returns the number of pages according to the current page size
  1836. */
  1837. EditableGrid.prototype.getCurrentPageIndex = function()
  1838. {
  1839. // if pagination is handled on the server side, pageSize will (must) be 0
  1840. if (this.pageSize <= 0 && !this.serverSide) return 0;
  1841. // if page index does not exist anymore, go to last page (without losing the information of the current page)
  1842. return Math.max(0, this.currentPageIndex >= this.getPageCount() ? this.getPageCount() - 1 : this.currentPageIndex);
  1843. };
  1844. /**
  1845. * Sets the current page (no effect if pageSize is 0)
  1846. * @param {Integer} pageIndex Integer page index
  1847. */
  1848. EditableGrid.prototype.setPageIndex = function(pageIndex)
  1849. {
  1850. this.currentPageIndex = pageIndex;
  1851. this.localset('pageIndex', pageIndex);
  1852. this.refreshGrid();
  1853. };
  1854. /**
  1855. * Go the previous page if we are not already on the first page
  1856. * @return
  1857. */
  1858. EditableGrid.prototype.prevPage = function()
  1859. {
  1860. if (this.canGoBack()) this.setPageIndex(this.getCurrentPageIndex() - 1);
  1861. };
  1862. /**
  1863. * Go the first page if we are not already on the first page
  1864. * @return
  1865. */
  1866. EditableGrid.prototype.firstPage = function()
  1867. {
  1868. if (this.canGoBack()) this.setPageIndex(0);
  1869. };
  1870. /**
  1871. * Go the next page if we are not already on the last page
  1872. * @return
  1873. */
  1874. EditableGrid.prototype.nextPage = function()
  1875. {
  1876. if (this.canGoForward()) this.setPageIndex(this.getCurrentPageIndex() + 1);
  1877. };
  1878. /**
  1879. * Go the last page if we are not already on the last page
  1880. * @return
  1881. */
  1882. EditableGrid.prototype.lastPage = function()
  1883. {
  1884. if (this.canGoForward()) this.setPageIndex(this.getPageCount() - 1);
  1885. };
  1886. /**
  1887. * Returns true if we are not already on the first page
  1888. * @return
  1889. */
  1890. EditableGrid.prototype.canGoBack = function()
  1891. {
  1892. return this.getCurrentPageIndex() > 0;
  1893. };
  1894. /**
  1895. * Returns true if we are not already on the last page
  1896. * @return
  1897. */
  1898. EditableGrid.prototype.canGoForward = function()
  1899. {
  1900. return this.getCurrentPageIndex() < this.getPageCount() - 1;
  1901. };
  1902. /**
  1903. * Returns an interval { startPageIndex: ..., endPageIndex: ... } so that a window of the given size is visible around the current page (hence the 'sliding').
  1904. * If pagination is not enabled this method displays an error and returns null.
  1905. * If pagination is enabled but there is only one page this function returns null (wihtout error).
  1906. * @param slidingWindowSize size of the visible window
  1907. * @return
  1908. */
  1909. EditableGrid.prototype.getSlidingPageInterval = function(slidingWindowSize)
  1910. {
  1911. var nbPages = this.getPageCount();
  1912. if (nbPages <= 1) return null;
  1913. var curPageIndex = this.getCurrentPageIndex();
  1914. var startPageIndex = Math.max(0, curPageIndex - Math.floor(slidingWindowSize/2));
  1915. var endPageIndex = Math.min(nbPages - 1, curPageIndex + Math.floor(slidingWindowSize/2));
  1916. if (endPageIndex - startPageIndex < slidingWindowSize) {
  1917. var diff = slidingWindowSize - (endPageIndex - startPageIndex + 1);
  1918. startPageIndex = Math.max(0, startPageIndex - diff);
  1919. endPageIndex = Math.min(nbPages - 1, endPageIndex + diff);
  1920. }
  1921. return { startPageIndex: startPageIndex, endPageIndex: endPageIndex };
  1922. };
  1923. /**
  1924. * Returns an array of page indices in the given interval.
  1925. *
  1926. * @param interval
  1927. * The given interval must be an object with properties 'startPageIndex' and 'endPageIndex'.
  1928. * This interval may for example have been obtained with getCurrentPageInterval.
  1929. *
  1930. * @param callback
  1931. * The given callback is applied to each page index before adding it to the result array.
  1932. * This callback is optional: if none given, the page index will be added as is to the array.
  1933. * If given , the callback will be called with two parameters: pageIndex (integer) and isCurrent (boolean).
  1934. *
  1935. * @return
  1936. */
  1937. EditableGrid.prototype.getPagesInInterval = function(interval, callback)
  1938. {
  1939. var pages = [];
  1940. for (var p = interval.startPageIndex; p <= interval.endPageIndex; p++) {
  1941. pages.push(typeof callback == 'function' ? callback(p, p == this.getCurrentPageIndex()) : p);
  1942. }
  1943. return pages;
  1944. };