/src/w2grid.js

https://github.com/delebash/w2ui · JavaScript · 5177 lines · 4531 code · 201 blank · 445 comment · 1920 complexity · 4ac01842935df7bb7d4875cf0b3ada86 MD5 · raw file

  1. /************************************************************************
  2. * Library: Web 2.0 UI for jQuery (using prototypical inheritance)
  3. * - Following objects defined
  4. * - w2grid - grid widget
  5. * - $().w2grid - jQuery wrapper
  6. * - Dependencies: jQuery, w2utils, w2toolbar, w2fields, w2alert, w2confirm
  7. *
  8. * == NICE TO HAVE ==
  9. * - frozen columns
  10. * - add colspans
  11. * - allow this.total to be unknown (-1)
  12. * - column autosize based on largest content
  13. * - easy bubbles in the grid
  14. * - More than 2 layers of header groups
  15. * - reorder columns/records
  16. * - hidden searches could not be clearned by the user
  17. * - problem with .set() and arrays, array get extended too, but should be replaced
  18. * - move events into prototype
  19. * - add grid.focus()
  20. * - add showExtra, KickIn Infinite scroll when so many records
  21. * - after edit stay on the same record option
  22. * - allow render: function to be filters
  23. * - if supplied array of ids, get should return array of records
  24. * - row drag and drop has bugs
  25. *
  26. * == 1.5 changes
  27. * - $('#grid').w2grid() - if called w/o argument then it returns grid object
  28. * - added statusRange : true,
  29. * statusBuffered : false,
  30. * statusRecordID : true,
  31. * statusSelection : true,
  32. * statusResponse : true,
  33. * statusSort : true,
  34. * statusSearch : true,
  35. * - change selectAll() and selectNone() - return time it took
  36. *
  37. ************************************************************************/
  38. (function () {
  39. var w2grid = function(options) {
  40. // public properties
  41. this.name = null;
  42. this.box = null; // HTML element that hold this element
  43. this.header = '';
  44. this.url = '';
  45. this.routeData = {}; // data for dynamic routes
  46. this.columns = []; // { field, caption, size, attr, render, hidden, gridMinWidth, editable }
  47. this.columnGroups = []; // { span: int, caption: 'string', master: true/false }
  48. this.records = []; // { recid: int(requied), field1: 'value1', ... fieldN: 'valueN', style: 'string', editable: true/false, summary: true/false, changes: object }
  49. this.summary = []; // arry of summary records, same structure as records array
  50. this.searches = []; // { type, caption, field, inTag, outTag, hidden }
  51. this.searchData = [];
  52. this.sortData = [];
  53. this.postData = {};
  54. this.toolbar = {}; // if not empty object; then it is toolbar object
  55. this.show = {
  56. header : false,
  57. toolbar : false,
  58. footer : false,
  59. columnHeaders : true,
  60. lineNumbers : false,
  61. expandColumn : false,
  62. selectColumn : false,
  63. emptyRecords : true,
  64. toolbarReload : true,
  65. toolbarColumns : true,
  66. toolbarSearch : true,
  67. toolbarAdd : false,
  68. toolbarEdit : false,
  69. toolbarDelete : false,
  70. toolbarSave : false,
  71. statusRange : true,
  72. statusBuffered : false,
  73. statusRecordID : true,
  74. statusSelection : true,
  75. statusResponse : true,
  76. statusSort : true,
  77. statusSearch : true,
  78. recordTitles : true,
  79. selectionBorder : true,
  80. skipRecords : true
  81. };
  82. this.autoLoad = true; // for infinite scroll
  83. this.fixedBody = true; // if false; then grid grows with data
  84. this.recordHeight = 24;
  85. this.keyboard = true;
  86. this.selectType = 'row'; // can be row|cell
  87. this.multiSearch = true;
  88. this.multiSelect = true;
  89. this.multiSort = true;
  90. this.reorderColumns = false;
  91. this.reorderRows = false;
  92. this.markSearch = true;
  93. this.total = 0; // server total
  94. this.limit = 100;
  95. this.offset = 0; // how many records to skip (for infinite scroll) when pulling from server
  96. this.style = '';
  97. this.ranges = [];
  98. this.menu = [];
  99. this.method = null; // if defined, then overwrited ajax method
  100. this.recid = null;
  101. this.parser = null;
  102. // events
  103. this.onAdd = null;
  104. this.onEdit = null;
  105. this.onRequest = null; // called on any server event
  106. this.onLoad = null;
  107. this.onDelete = null;
  108. this.onDeleted = null;
  109. this.onSubmit = null;
  110. this.onSave = null;
  111. this.onSelect = null;
  112. this.onUnselect = null;
  113. this.onClick = null;
  114. this.onDblClick = null;
  115. this.onContextMenu = null;
  116. this.onMenuClick = null; // when context menu item selected
  117. this.onColumnClick = null;
  118. this.onColumnResize = null;
  119. this.onSort = null;
  120. this.onSearch = null;
  121. this.onChange = null; // called when editable record is changed
  122. this.onRestore = null; // called when editable record is restored
  123. this.onExpand = null;
  124. this.onCollapse = null;
  125. this.onError = null;
  126. this.onKeydown = null;
  127. this.onToolbar = null; // all events from toolbar
  128. this.onColumnOnOff = null;
  129. this.onCopy = null;
  130. this.onPaste = null;
  131. this.onSelectionExtend = null;
  132. this.onEditField = null;
  133. this.onRender = null;
  134. this.onRefresh = null;
  135. this.onReload = null;
  136. this.onResize = null;
  137. this.onDestroy = null;
  138. this.onStateSave = null;
  139. this.onStateRestore = null;
  140. // internal
  141. this.last = {
  142. field : 'all',
  143. caption : w2utils.lang('All Fields'),
  144. logic : 'OR',
  145. search : '',
  146. searchIds : [],
  147. selection : {
  148. indexes : [],
  149. columns : {}
  150. },
  151. multi : false,
  152. scrollTop : 0,
  153. scrollLeft : 0,
  154. sortData : null,
  155. sortCount : 0,
  156. xhr : null,
  157. range_start : null,
  158. range_end : null,
  159. sel_ind : null,
  160. sel_col : null,
  161. sel_type : null,
  162. edit_col : null
  163. };
  164. $.extend(true, this, w2obj.grid, options);
  165. };
  166. // ====================================================
  167. // -- Registers as a jQuery plugin
  168. $.fn.w2grid = function(method) {
  169. if ($.isPlainObject(method)) {
  170. // check name parameter
  171. if (!w2utils.checkName(method, 'w2grid')) return;
  172. // remember items
  173. var columns = method.columns;
  174. var columnGroups = method.columnGroups;
  175. var records = method.records;
  176. var searches = method.searches;
  177. var searchData = method.searchData;
  178. var sortData = method.sortData;
  179. var postData = method.postData;
  180. var toolbar = method.toolbar;
  181. // extend items
  182. var object = new w2grid(method);
  183. $.extend(object, { postData: {}, records: [], columns: [], searches: [], toolbar: {}, sortData: [], searchData: [], handlers: [] });
  184. if (object.onExpand != null) object.show.expandColumn = true;
  185. $.extend(true, object.toolbar, toolbar);
  186. // reassign variables
  187. for (var p in columns) object.columns[p] = $.extend(true, {}, columns[p]);
  188. for (var p in columnGroups) object.columnGroups[p] = $.extend(true, {}, columnGroups[p]);
  189. for (var p in searches) object.searches[p] = $.extend(true, {}, searches[p]);
  190. for (var p in searchData) object.searchData[p] = $.extend(true, {}, searchData[p]);
  191. for (var p in sortData) object.sortData[p] = $.extend(true, {}, sortData[p]);
  192. object.postData = $.extend(true, {}, postData);
  193. // check if there are records without recid
  194. for (var r in records) {
  195. if (records[r].recid == null || typeof records[r].recid == 'undefined') {
  196. console.log('ERROR: Cannot add records without recid. (obj: '+ object.name +')');
  197. return;
  198. }
  199. object.records[r] = $.extend(true, {}, records[r]);
  200. }
  201. // add searches
  202. for (var c in object.columns) {
  203. var col = object.columns[c];
  204. if (typeof col.searchable == 'undefined' || object.getSearch(col.field) != null) continue;
  205. var stype = col.searchable;
  206. var attr = '';
  207. if (col.searchable === true) { stype = 'text'; attr = 'size="20"'; }
  208. object.addSearch({ field: col.field, caption: col.caption, type: stype, attr: attr });
  209. }
  210. // init toolbar
  211. object.initToolbar();
  212. // render if necessary
  213. if ($(this).length !== 0) {
  214. object.render($(this)[0]);
  215. }
  216. // register new object
  217. w2ui[object.name] = object;
  218. return object;
  219. } else {
  220. var obj = w2ui[$(this).attr('name')];
  221. if (!obj) return null;
  222. if (arguments.length > 0) {
  223. if (obj[method]) obj[method].apply(obj, Array.prototype.slice.call(arguments, 1));
  224. return this;
  225. } else {
  226. return obj;
  227. }
  228. }
  229. };
  230. // ====================================================
  231. // -- Implementation of core functionality
  232. w2grid.prototype = {
  233. // ----
  234. // properties that need to be in prototype
  235. msgDelete : 'Are you sure you want to delete selected records?',
  236. msgNotJSON : 'Returned data is not in valid JSON format.',
  237. msgAJAXerror : 'AJAX error. See console for more details.',
  238. msgRefresh : 'Refreshing...',
  239. // for easy button overwrite
  240. buttons: {
  241. 'reload' : { type: 'button', id: 'w2ui-reload', icon: 'w2ui-icon-reload', hint: 'Reload data in the list' },
  242. 'columns' : { type: 'drop', id: 'w2ui-column-on-off', icon: 'w2ui-icon-columns', hint: 'Show/hide columns', arrow: false, html: '' },
  243. 'search' : { type: 'html', id: 'w2ui-search',
  244. html: '<div class="w2ui-icon icon-search-down w2ui-search-down" title="'+ 'Select Search Field' +'" '+
  245. 'onclick="var obj = w2ui[$(this).parents(\'div.w2ui-grid\').attr(\'name\')]; obj.searchShowFields();"></div>'
  246. },
  247. 'search-go': { type: 'check', id: 'w2ui-search-advanced', caption: 'Search...', hint: 'Open Search Fields' },
  248. 'add' : { type: 'button', id: 'w2ui-add', caption: 'Add New', hint: 'Add new record', icon: 'w2ui-icon-plus' },
  249. 'edit' : { type: 'button', id: 'w2ui-edit', caption: 'Edit', hint: 'Edit selected record', icon: 'w2ui-icon-pencil', disabled: true },
  250. 'delete' : { type: 'button', id: 'w2ui-delete', caption: 'Delete', hint: 'Delete selected records', icon: 'w2ui-icon-cross', disabled: true },
  251. 'save' : { type: 'button', id: 'w2ui-save', caption: 'Save', hint: 'Save changed records', icon: 'w2ui-icon-check' }
  252. },
  253. add: function (record) {
  254. if (!$.isArray(record)) record = [record];
  255. var added = 0;
  256. for (var o in record) {
  257. if (!this.recid && typeof record[o].recid == 'undefined') record[o].recid = record[o][this.recid];
  258. if (record[o].recid == null || typeof record[o].recid == 'undefined') {
  259. console.log('ERROR: Cannot add record without recid. (obj: '+ this.name +')');
  260. continue;
  261. }
  262. this.records.push(record[o]);
  263. added++;
  264. }
  265. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  266. if (!url) {
  267. this.total = this.records.length;
  268. this.localSort();
  269. this.localSearch();
  270. }
  271. this.refresh(); // ?? should it be reload?
  272. return added;
  273. },
  274. find: function (obj, returnIndex) {
  275. if (typeof obj == 'undefined' || obj == null) obj = {};
  276. var recs = [];
  277. var hasDots = false;
  278. // check if property is nested - needed for speed
  279. for (var o in obj) if (String(o).indexOf('.') != -1) hasDots = true;
  280. // look for an item
  281. for (var i = 0; i < this.records.length; i++) {
  282. var match = true;
  283. for (var o in obj) {
  284. var val = this.records[i][o];
  285. if (hasDots && String(o).indexOf('.') != -1) val = this.parseField(this.records[i], o);
  286. if (obj[o] != val) match = false;
  287. }
  288. if (match && returnIndex !== true) recs.push(this.records[i].recid);
  289. if (match && returnIndex === true) recs.push(i);
  290. }
  291. return recs;
  292. },
  293. set: function (recid, record, noRefresh) { // does not delete existing, but overrides on top of it
  294. if (typeof recid == 'object') {
  295. noRefresh = record;
  296. record = recid;
  297. recid = null;
  298. }
  299. // update all records
  300. if (recid == null) {
  301. for (var r in this.records) {
  302. $.extend(true, this.records[r], record); // recid is the whole record
  303. }
  304. if (noRefresh !== true) this.refresh();
  305. } else { // find record to update
  306. var ind = this.get(recid, true);
  307. if (ind == null) return false;
  308. var isSummary = (this.records[ind] && this.records[ind].recid == recid ? false : true);
  309. if (isSummary) {
  310. $.extend(true, this.summary[ind], record);
  311. } else {
  312. $.extend(true, this.records[ind], record);
  313. }
  314. if (noRefresh !== true) this.refreshRow(recid); // refresh only that record
  315. }
  316. return true;
  317. },
  318. get: function (recid, returnIndex) {
  319. // search records
  320. for (var i = 0; i < this.records.length; i++) {
  321. if (this.records[i].recid == recid) {
  322. if (returnIndex === true) return i; else return this.records[i];
  323. }
  324. }
  325. // search summary
  326. for (var i = 0; i < this.summary.length; i++) {
  327. if (this.summary[i].recid == recid) {
  328. if (returnIndex === true) return i; else return this.summary[i];
  329. }
  330. }
  331. return null;
  332. },
  333. remove: function () {
  334. var removed = 0;
  335. for (var a = 0; a < arguments.length; a++) {
  336. for (var r = this.records.length-1; r >= 0; r--) {
  337. if (this.records[r].recid == arguments[a]) { this.records.splice(r, 1); removed++; }
  338. }
  339. }
  340. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  341. if (!url) {
  342. this.localSort();
  343. this.localSearch();
  344. }
  345. this.refresh();
  346. return removed;
  347. },
  348. addColumn: function (before, columns) {
  349. var added = 0;
  350. if (arguments.length == 1) {
  351. columns = before;
  352. before = this.columns.length;
  353. } else {
  354. if (typeof before == 'string') before = this.getColumn(before, true);
  355. if (before === null) before = this.columns.length;
  356. }
  357. if (!$.isArray(columns)) columns = [columns];
  358. for (var o in columns) {
  359. this.columns.splice(before, 0, columns[o]);
  360. before++;
  361. added++;
  362. }
  363. this.refresh();
  364. return added;
  365. },
  366. removeColumn: function () {
  367. var removed = 0;
  368. for (var a = 0; a < arguments.length; a++) {
  369. for (var r = this.columns.length-1; r >= 0; r--) {
  370. if (this.columns[r].field == arguments[a]) { this.columns.splice(r, 1); removed++; }
  371. }
  372. }
  373. this.refresh();
  374. return removed;
  375. },
  376. getColumn: function (field, returnIndex) {
  377. for (var i = 0; i < this.columns.length; i++) {
  378. if (this.columns[i].field == field) {
  379. if (returnIndex === true) return i; else return this.columns[i];
  380. }
  381. }
  382. return null;
  383. },
  384. toggleColumn: function () {
  385. var effected = 0;
  386. for (var a = 0; a < arguments.length; a++) {
  387. for (var r = this.columns.length-1; r >= 0; r--) {
  388. var col = this.columns[r];
  389. if (col.field == arguments[a]) {
  390. col.hidden = !col.hidden;
  391. effected++;
  392. }
  393. }
  394. }
  395. this.refresh();
  396. return effected;
  397. },
  398. showColumn: function () {
  399. var shown = 0;
  400. for (var a = 0; a < arguments.length; a++) {
  401. for (var r = this.columns.length-1; r >= 0; r--) {
  402. var col = this.columns[r];
  403. if (col.gridMinWidth) delete col.gridMinWidth;
  404. if (col.field == arguments[a] && col.hidden !== false) {
  405. col.hidden = false;
  406. shown++;
  407. }
  408. }
  409. }
  410. this.refresh();
  411. return shown;
  412. },
  413. hideColumn: function () {
  414. var hidden = 0;
  415. for (var a = 0; a < arguments.length; a++) {
  416. for (var r = this.columns.length-1; r >= 0; r--) {
  417. var col = this.columns[r];
  418. if (col.field == arguments[a] && col.hidden !== true) {
  419. col.hidden = true;
  420. hidden++;
  421. }
  422. }
  423. }
  424. this.refresh();
  425. return hidden;
  426. },
  427. addSearch: function (before, search) {
  428. var added = 0;
  429. if (arguments.length == 1) {
  430. search = before;
  431. before = this.searches.length;
  432. } else {
  433. if (typeof before == 'string') before = this.getSearch(before, true);
  434. if (before === null) before = this.searches.length;
  435. }
  436. if (!$.isArray(search)) search = [search];
  437. for (var o in search) {
  438. this.searches.splice(before, 0, search[o]);
  439. before++;
  440. added++;
  441. }
  442. this.searchClose();
  443. return added;
  444. },
  445. removeSearch: function () {
  446. var removed = 0;
  447. for (var a = 0; a < arguments.length; a++) {
  448. for (var r = this.searches.length-1; r >= 0; r--) {
  449. if (this.searches[r].field == arguments[a]) { this.searches.splice(r, 1); removed++; }
  450. }
  451. }
  452. this.searchClose();
  453. return removed;
  454. },
  455. getSearch: function (field, returnIndex) {
  456. for (var i = 0; i < this.searches.length; i++) {
  457. if (this.searches[i].field == field) {
  458. if (returnIndex === true) return i; else return this.searches[i];
  459. }
  460. }
  461. return null;
  462. },
  463. toggleSearch: function () {
  464. var effected = 0;
  465. for (var a = 0; a < arguments.length; a++) {
  466. for (var r = this.searches.length-1; r >= 0; r--) {
  467. if (this.searches[r].field == arguments[a]) {
  468. this.searches[r].hidden = !this.searches[r].hidden;
  469. effected++;
  470. }
  471. }
  472. }
  473. this.searchClose();
  474. return effected;
  475. },
  476. showSearch: function () {
  477. var shown = 0;
  478. for (var a = 0; a < arguments.length; a++) {
  479. for (var r = this.searches.length-1; r >= 0; r--) {
  480. if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== false) {
  481. this.searches[r].hidden = false;
  482. shown++;
  483. }
  484. }
  485. }
  486. this.searchClose();
  487. return shown;
  488. },
  489. hideSearch: function () {
  490. var hidden = 0;
  491. for (var a = 0; a < arguments.length; a++) {
  492. for (var r = this.searches.length-1; r >= 0; r--) {
  493. if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== true) {
  494. this.searches[r].hidden = true;
  495. hidden++;
  496. }
  497. }
  498. }
  499. this.searchClose();
  500. return hidden;
  501. },
  502. getSearchData: function (field) {
  503. for (var s in this.searchData) {
  504. if (this.searchData[s].field == field) return this.searchData[s];
  505. }
  506. return null;
  507. },
  508. localSort: function (silent) {
  509. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  510. if (url) {
  511. console.log('ERROR: grid.localSort can only be used on local data source, grid.url should be empty.');
  512. return;
  513. }
  514. if ($.isEmptyObject(this.sortData)) return;
  515. var time = (new Date()).getTime();
  516. var obj = this;
  517. // process date fields
  518. obj.prepareData();
  519. // process sortData
  520. for (var s in this.sortData) {
  521. var column = this.getColumn(this.sortData[s].field);
  522. if (!column) return;
  523. if (typeof column.render == 'string') {
  524. if (['date', 'age'].indexOf(column.render.split(':')[0]) != -1) {
  525. this.sortData[s]['field_'] = column.field + '_';
  526. }
  527. if (['time'].indexOf(column.render.split(':')[0]) != -1) {
  528. this.sortData[s]['field_'] = column.field + '_';
  529. }
  530. }
  531. }
  532. // process sort
  533. this.records.sort(function (a, b) {
  534. var ret = 0;
  535. for (var s in obj.sortData) {
  536. var fld = obj.sortData[s].field;
  537. if (obj.sortData[s].field_) fld = obj.sortData[s].field_;
  538. var aa = a[fld];
  539. var bb = b[fld];
  540. if (String(fld).indexOf('.') != -1) {
  541. aa = obj.parseField(a, fld);
  542. bb = obj.parseField(b, fld);
  543. }
  544. if (typeof aa == 'string') aa = $.trim(aa.toLowerCase());
  545. if (typeof bb == 'string') bb = $.trim(bb.toLowerCase());
  546. if (aa > bb) ret = (obj.sortData[s].direction == 'asc' ? 1 : -1);
  547. if (aa < bb) ret = (obj.sortData[s].direction == 'asc' ? -1 : 1);
  548. if (typeof aa != 'object' && typeof bb == 'object') ret = -1;
  549. if (typeof bb != 'object' && typeof aa == 'object') ret = 1;
  550. if (aa == null && bb != null) ret = 1; // all nuls and undefined on bottom
  551. if (aa != null && bb == null) ret = -1;
  552. if (ret != 0) break;
  553. }
  554. return ret;
  555. });
  556. time = (new Date()).getTime() - time;
  557. if (silent !== true && obj.show.statusSort) {
  558. setTimeout(function () {
  559. obj.status(w2utils.lang('Sorting took') + ' ' + time/1000 + ' ' + w2utils.lang('sec'));
  560. }, 10);
  561. }
  562. return time;
  563. },
  564. localSearch: function (silent) {
  565. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  566. if (url) {
  567. console.log('ERROR: grid.localSearch can only be used on local data source, grid.url should be empty.');
  568. return;
  569. }
  570. var time = (new Date()).getTime();
  571. var obj = this;
  572. this.total = this.records.length;
  573. // mark all records as shown
  574. this.last.searchIds = [];
  575. // prepare date/time fields
  576. this.prepareData();
  577. // hide records that did not match
  578. if (this.searchData.length > 0 && !url) {
  579. this.total = 0;
  580. for (var r in this.records) {
  581. var rec = this.records[r];
  582. var fl = 0;
  583. for (var s in this.searchData) {
  584. var sdata = this.searchData[s];
  585. var search = this.getSearch(sdata.field);
  586. if (sdata == null) continue;
  587. if (search == null) search = { field: sdata.field, type: sdata.type };
  588. var val1 = String(obj.parseField(rec, search.field)).toLowerCase();
  589. if (typeof sdata.value != 'undefined') {
  590. if (!$.isArray(sdata.value)) {
  591. var val2 = String(sdata.value).toLowerCase();
  592. } else {
  593. var val2 = sdata.value[0];
  594. var val3 = sdata.value[1];
  595. }
  596. }
  597. switch (sdata.operator) {
  598. case 'is':
  599. if (rec[search.field] == sdata.value) fl++; // do not hide record
  600. if (search.type == 'date') {
  601. var val1 = w2utils.formatDate(rec[search.field + '_'], 'yyyy-mm-dd');
  602. var val2 = w2utils.formatDate(val2, 'yyyy-mm-dd');
  603. if (val1 == val2) fl++;
  604. }
  605. if (search.type == 'time') {
  606. var val1 = w2utils.formatTime(rec[search.field + '_'], 'h24:mi');
  607. var val2 = w2utils.formatTime(val2, 'h24:mi');
  608. if (val1 == val2) fl++;
  609. }
  610. break;
  611. case 'between':
  612. if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) {
  613. if (parseFloat(rec[search.field]) >= parseFloat(val2) && parseFloat(rec[search.field]) <= parseFloat(val3)) fl++;
  614. }
  615. if (search.type == 'date') {
  616. var val1 = rec[search.field + '_'];
  617. var val2 = w2utils.isDate(val2, w2utils.settings.date_format, true);
  618. var val3 = w2utils.isDate(val3, w2utils.settings.date_format, true);
  619. if (val3 != null) val3 = new Date(val3.getTime() + 86400000); // 1 day
  620. if (val1 >= val2 && val1 < val3) fl++;
  621. }
  622. if (search.type == 'time') {
  623. var val1 = rec[search.field + '_'];
  624. var val2 = w2utils.isTime(val2, true);
  625. var val3 = w2utils.isTime(val3, true);
  626. val2 = (new Date()).setHours(val2.hours, val2.minutes, val2.seconds ? val2.seconds : 0, 0);
  627. val3 = (new Date()).setHours(val3.hours, val3.minutes, val3.seconds ? val3.seconds : 0, 0);
  628. if (val1 >= val2 && val1 < val3) fl++;
  629. }
  630. break;
  631. case 'in':
  632. var tmp = sdata.value;
  633. if (sdata.svalue) tmp = sdata.svalue;
  634. if (tmp.indexOf(val1) !== -1) fl++;
  635. break;
  636. case 'not in':
  637. var tmp = sdata.value;
  638. if (sdata.svalue) tmp = sdata.svalue;
  639. if (tmp.indexOf(val1) == -1) fl++;
  640. break;
  641. case 'begins':
  642. case 'begins with': // need for back compatib.
  643. if (val1.indexOf(val2) == 0) fl++; // do not hide record
  644. break;
  645. case 'contains':
  646. if (val1.indexOf(val2) >= 0) fl++; // do not hide record
  647. break;
  648. case 'ends':
  649. case 'ends with': // need for back compatib.
  650. if (val1.indexOf(val2) == val1.length - val2.length) fl++; // do not hide record
  651. break;
  652. }
  653. }
  654. if ((this.last.logic == 'OR' && fl != 0) || (this.last.logic == 'AND' && fl == this.searchData.length)) this.last.searchIds.push(parseInt(r));
  655. }
  656. this.total = this.last.searchIds.length;
  657. }
  658. time = (new Date()).getTime() - time;
  659. if (silent !== true && obj.show.statusSearch) {
  660. setTimeout(function () {
  661. obj.status(w2utils.lang('Search took') + ' ' + time/1000 + ' ' + w2utils.lang('sec'));
  662. }, 10);
  663. }
  664. return time;
  665. },
  666. getRangeData: function (range, extra) {
  667. var rec1 = this.get(range[0].recid, true);
  668. var rec2 = this.get(range[1].recid, true);
  669. var col1 = range[0].column;
  670. var col2 = range[1].column;
  671. var res = [];
  672. if (col1 == col2) { // one row
  673. for (var r = rec1; r <= rec2; r++) {
  674. var record = this.records[r];
  675. var dt = record[this.columns[col1].field] || null;
  676. if (extra !== true) {
  677. res.push(dt);
  678. } else {
  679. res.push({ data: dt, column: col1, index: r, record: record });
  680. }
  681. }
  682. } else if (rec1 == rec2) { // one line
  683. var record = this.records[rec1];
  684. for (var i = col1; i <= col2; i++) {
  685. var dt = record[this.columns[i].field] || null;
  686. if (extra !== true) {
  687. res.push(dt);
  688. } else {
  689. res.push({ data: dt, column: i, index: rec1, record: record });
  690. }
  691. }
  692. } else {
  693. for (var r = rec1; r <= rec2; r++) {
  694. var record = this.records[r];
  695. res.push([]);
  696. for (var i = col1; i <= col2; i++) {
  697. var dt = record[this.columns[i].field];
  698. if (extra !== true) {
  699. res[res.length-1].push(dt);
  700. } else {
  701. res[res.length-1].push({ data: dt, column: i, index: r, record: record });
  702. }
  703. }
  704. }
  705. }
  706. return res;
  707. },
  708. addRange: function (ranges) {
  709. var added = 0;
  710. if (this.selectType == 'row') return added;
  711. if (!$.isArray(ranges)) ranges = [ranges];
  712. // if it is selection
  713. for (var r in ranges) {
  714. if (typeof ranges[r] != 'object') ranges[r] = { name: 'selection' };
  715. if (ranges[r].name == 'selection') {
  716. if (this.show.selectionBorder === false) continue;
  717. var sel = this.getSelection();
  718. if (sel.length == 0) {
  719. this.removeRange(ranges[r].name);
  720. continue;
  721. } else {
  722. var first = sel[0];
  723. var last = sel[sel.length-1];
  724. var td1 = $('#grid_'+ this.name +'_rec_'+ first.recid + ' td[col='+ first.column +']');
  725. var td2 = $('#grid_'+ this.name +'_rec_'+ last.recid + ' td[col='+ last.column +']');
  726. }
  727. } else { // other range
  728. var first = ranges[r].range[0];
  729. var last = ranges[r].range[1];
  730. var td1 = $('#grid_'+ this.name +'_rec_'+ first.recid + ' td[col='+ first.column +']');
  731. var td2 = $('#grid_'+ this.name +'_rec_'+ last.recid + ' td[col='+ last.column +']');
  732. }
  733. if (first) {
  734. var rg = {
  735. name: ranges[r].name,
  736. range: [{ recid: first.recid, column: first.column }, { recid: last.recid, column: last.column }],
  737. style: ranges[r].style || ''
  738. };
  739. // add range
  740. var ind = false;
  741. for (var t in this.ranges) if (this.ranges[t].name == ranges[r].name) { ind = r; break; }
  742. if (ind !== false) {
  743. this.ranges[ind] = rg;
  744. } else {
  745. this.ranges.push(rg);
  746. }
  747. added++
  748. }
  749. }
  750. this.refreshRanges();
  751. return added;
  752. },
  753. removeRange: function () {
  754. var removed = 0;
  755. for (var a = 0; a < arguments.length; a++) {
  756. var name = arguments[a];
  757. $('#grid_'+ this.name +'_'+ name).remove();
  758. for (var r = this.ranges.length-1; r >= 0; r--) {
  759. if (this.ranges[r].name == name) {
  760. this.ranges.splice(r, 1);
  761. removed++;
  762. }
  763. }
  764. }
  765. return removed;
  766. },
  767. refreshRanges: function () {
  768. var obj = this;
  769. var time = (new Date()).getTime();
  770. var rec = $('#grid_'+ this.name +'_records');
  771. for (var r in this.ranges) {
  772. var rg = this.ranges[r];
  773. var first = rg.range[0];
  774. var last = rg.range[1];
  775. var td1 = $('#grid_'+ this.name +'_rec_'+ first.recid + ' td[col='+ first.column +']');
  776. var td2 = $('#grid_'+ this.name +'_rec_'+ last.recid + ' td[col='+ last.column +']');
  777. var sel1 = $('#grid_'+ this.name +'_rec_top').next().find('td.w2ui-selected');
  778. var sel2 = $('#grid_'+ this.name +'_rec_bottom').prev().find('td.w2ui-selected');
  779. if ($('#grid_'+ this.name +'_'+ rg.name).length == 0) {
  780. rec.append('<div id="grid_'+ this.name +'_' + rg.name +'" class="w2ui-selection" style="'+ rg.style +'">'+
  781. (rg.name == 'selection' ? '<div id="grid_'+ this.name +'_resizer" class="w2ui-selection-resizer"></div>' : '')+
  782. '</div>');
  783. } else {
  784. $('#grid_'+ this.name +'_'+ rg.name).attr('style', rg.style);
  785. }
  786. // if virtual scrolling kicked in
  787. if ((td1.length == 0 && td2.length != 0) || sel1.length > 0) {
  788. td1 = $('#grid_'+ this.name +'_rec_top').next().find('td[col='+ first.column +']');
  789. }
  790. if ((td2.length == 0 && td1.length != 0) || sel2.length > 0) {
  791. td2 = $('#grid_'+ this.name +'_rec_bottom').prev().find('td[col='+ last.column +']');
  792. }
  793. // display range
  794. if (td1.length > 0 && td2.length > 0) {
  795. $('#grid_'+ this.name +'_'+ rg.name).css({
  796. left : (td1.position().left - 1 + rec.scrollLeft()) + 'px',
  797. top : (td1.position().top - 1 + rec.scrollTop()) + 'px',
  798. width : (td2.position().left - td1.position().left + td2.width() + 3) + 'px',
  799. height : (td2.position().top - td1.position().top + td2.height() + 3) + 'px'
  800. });
  801. }
  802. }
  803. // add resizer events
  804. $(this.box).find('#grid_'+ this.name +'_resizer').off('mousedown').on('mousedown', mouseStart);
  805. //$(this.box).find('#grid_'+ this.name +'_resizer').off('selectstart').on('selectstart', function () { return false; }); // fixes chrome cursror bug
  806. var eventData = { phase: 'before', type: 'selectionExtend', target: obj.name, originalRange: null, newRange: null };
  807. function mouseStart (event) {
  808. var sel = obj.getSelection();
  809. obj.last.move = {
  810. type : 'expand',
  811. x : event.screenX,
  812. y : event.screenY,
  813. divX : 0,
  814. divY : 0,
  815. recid : sel[0].recid,
  816. column : sel[0].column,
  817. originalRange : [{ recid: sel[0].recid, column: sel[0].column }, { recid: sel[sel.length-1].recid, column: sel[sel.length-1].column }],
  818. newRange : [{ recid: sel[0].recid, column: sel[0].column }, { recid: sel[sel.length-1].recid, column: sel[sel.length-1].column }]
  819. };
  820. $(document).off('mousemove', mouseMove).on('mousemove', mouseMove);
  821. $(document).off('mouseup', mouseStop).on('mouseup', mouseStop);
  822. }
  823. function mouseMove (event) {
  824. var mv = obj.last.move;
  825. if (!mv || mv.type != 'expand') return;
  826. mv.divX = (event.screenX - mv.x);
  827. mv.divY = (event.screenY - mv.y);
  828. // find new cell
  829. var recid, column;
  830. var tmp = event.originalEvent.target;
  831. if (tmp.tagName != 'TD') tmp = $(tmp).parents('td')[0];
  832. if (typeof $(tmp).attr('col') != 'undefined') column = parseInt($(tmp).attr('col'));
  833. tmp = $(tmp).parents('tr')[0];
  834. recid = $(tmp).attr('recid');
  835. // new range
  836. if (mv.newRange[1].recid == recid && mv.newRange[1].column == column) return;
  837. var prevNewRange = $.extend({}, mv.newRange);
  838. mv.newRange = [{ recid: mv.recid, column: mv.column }, { recid: recid, column: column }];
  839. // event before
  840. eventData = obj.trigger($.extend(eventData, { originalRange: mv.originalRange, newRange : mv.newRange }));
  841. if (eventData.isCancelled === true) {
  842. mv.newRange = prevNewRange;
  843. eventData.newRange = prevNewRange;
  844. return;
  845. } else {
  846. // default behavior
  847. obj.removeRange('grid-selection-expand');
  848. obj.addRange({
  849. name : 'grid-selection-expand',
  850. range : eventData.newRange,
  851. style : 'background-color: rgba(100,100,100,0.1); border: 2px dotted rgba(100,100,100,0.5);'
  852. });
  853. }
  854. }
  855. function mouseStop (event) {
  856. // default behavior
  857. obj.removeRange('grid-selection-expand');
  858. delete obj.last.move;
  859. $(document).off('mousemove', mouseMove);
  860. $(document).off('mouseup', mouseStop);
  861. // event after
  862. obj.trigger($.extend(eventData, { phase: 'after' }));
  863. }
  864. return (new Date()).getTime() - time;
  865. },
  866. select: function () {
  867. var selected = 0;
  868. var sel = this.last.selection;
  869. if (!this.multiSelect) this.selectNone();
  870. for (var a = 0; a < arguments.length; a++) {
  871. var recid = typeof arguments[a] == 'object' ? arguments[a].recid : arguments[a];
  872. var record = this.get(recid);
  873. if (record == null) continue;
  874. var index = this.get(recid, true);
  875. var recEl = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid));
  876. if (this.selectType == 'row') {
  877. if (sel.indexes.indexOf(index) >= 0) continue;
  878. // event before
  879. var eventData = this.trigger({ phase: 'before', type: 'select', target: this.name, recid: recid, index: index });
  880. if (eventData.isCancelled === true) continue;
  881. // default action
  882. sel.indexes.push(index);
  883. sel.indexes.sort(function(a, b) { return a-b });
  884. recEl.addClass('w2ui-selected').data('selected', 'yes').find('.w2ui-col-number').addClass('w2ui-row-selected');
  885. recEl.find('.w2ui-grid-select-check').prop("checked", true);
  886. selected++;
  887. } else {
  888. var col = arguments[a].column;
  889. if (!w2utils.isInt(col)) { // select all columns
  890. var cols = [];
  891. for (var c in this.columns) { if (this.columns[c].hidden) continue; cols.push({ recid: recid, column: parseInt(c) }); }
  892. if (!this.multiSelect) cols = cols.splice(0, 1);
  893. return this.select.apply(this, cols);
  894. }
  895. var s = sel.columns[index] || [];
  896. if ($.isArray(s) && s.indexOf(col) != -1) continue;
  897. // event before
  898. var eventData = this.trigger({ phase: 'before', type: 'select', target: this.name, recid: recid, index: index, column: col });
  899. if (eventData.isCancelled === true) continue;
  900. // default action
  901. if (sel.indexes.indexOf(index) == -1) {
  902. sel.indexes.push(index);
  903. sel.indexes.sort(function(a, b) { return a-b });
  904. }
  905. s.push(col);
  906. s.sort(function(a, b) { return a-b }); // sort function must be for numerical sort
  907. recEl.find(' > td[col='+ col +']').addClass('w2ui-selected');
  908. recEl.find('.w2ui-col-number').addClass('w2ui-row-selected');
  909. $(this.box).find('.w2ui-grid-columns td[col='+ col +'] .w2ui-col-header').addClass('w2ui-col-selected');
  910. selected++;
  911. recEl.data('selected', 'yes');
  912. recEl.find('.w2ui-grid-select-check').prop("checked", true);
  913. // save back to selection object
  914. sel.columns[index] = s;
  915. }
  916. // event after
  917. this.trigger($.extend(eventData, { phase: 'after' }));
  918. }
  919. // all selected?
  920. if (sel.indexes.length == this.records.length || (this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length)) {
  921. $('#grid_'+ this.name +'_check_all').prop('checked', true);
  922. } else {
  923. $('#grid_'+ this.name +'_check_all').prop('checked', false);
  924. }
  925. this.status();
  926. this.addRange('selection');
  927. return selected;
  928. },
  929. unselect: function () {
  930. var unselected = 0;
  931. var sel = this.last.selection;
  932. for (var a = 0; a < arguments.length; a++) {
  933. var recid = typeof arguments[a] == 'object' ? arguments[a].recid : arguments[a];
  934. var record = this.get(recid);
  935. if (record == null) continue;
  936. var index = this.get(record.recid, true);
  937. var recEl = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid));
  938. if (this.selectType == 'row') {
  939. if (sel.indexes.indexOf(index) == -1) continue;
  940. // event before
  941. var eventData = this.trigger({ phase: 'before', type: 'unselect', target: this.name, recid: recid, index: index });
  942. if (eventData.isCancelled === true) continue;
  943. // default action
  944. sel.indexes.splice(sel.indexes.indexOf(index), 1);
  945. recEl.removeClass('w2ui-selected').removeData('selected').find('.w2ui-col-number').removeClass('w2ui-row-selected');
  946. if (recEl.length != 0) recEl[0].style.cssText = 'height: '+ this.recordHeight +'px; ' + recEl.attr('custom_style');
  947. recEl.find('.w2ui-grid-select-check').prop("checked", false);
  948. unselected++;
  949. } else {
  950. var col = arguments[a].column;
  951. if (!w2utils.isInt(col)) { // unselect all columns
  952. var cols = [];
  953. for (var c in this.columns) { if (this.columns[c].hidden) continue; cols.push({ recid: recid, column: parseInt(c) }); }
  954. return this.unselect.apply(this, cols);
  955. }
  956. var s = sel.columns[index];
  957. if (!$.isArray(s) || s.indexOf(col) == -1) continue;
  958. // event before
  959. var eventData = this.trigger({ phase: 'before', type: 'unselect', target: this.name, recid: recid, column: col });
  960. if (eventData.isCancelled === true) continue;
  961. // default action
  962. s.splice(s.indexOf(col), 1);
  963. $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)).find(' > td[col='+ col +']').removeClass('w2ui-selected');
  964. // check if any row/column still selected
  965. var isColSelected = false;
  966. var isRowSelected = false;
  967. var tmp = this.getSelection();
  968. for (var t in tmp) {
  969. if (tmp[t].column == col) isColSelected = true;
  970. if (tmp[t].recid == recid) isRowSelected = true;
  971. }
  972. if (!isColSelected) {
  973. $(this.box).find('.w2ui-grid-columns td[col='+ col +'] .w2ui-col-header').removeClass('w2ui-col-selected');
  974. }
  975. if (!isRowSelected) {
  976. $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)).find('.w2ui-col-number').removeClass('w2ui-row-selected');
  977. }
  978. unselected++;
  979. if (s.length == 0) {
  980. delete sel.columns[index];
  981. sel.indexes.splice(sel.indexes.indexOf(index), 1);
  982. recEl.removeData('selected');
  983. recEl.find('.w2ui-grid-select-check').prop("checked", false);
  984. }
  985. }
  986. // event after
  987. this.trigger($.extend(eventData, { phase: 'after' }));
  988. }
  989. // all selected?
  990. if (sel.indexes.length == this.records.length || (this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length)) {
  991. $('#grid_'+ this.name +'_check_all').prop('checked', true);
  992. } else {
  993. $('#grid_'+ this.name +'_check_all').prop('checked', false);
  994. }
  995. // show number of selected
  996. this.status();
  997. this.addRange('selection');
  998. return unselected;
  999. },
  1000. selectAll: function () {
  1001. var time = (new Date()).getTime();
  1002. if (this.multiSelect === false) return;
  1003. // event before
  1004. var eventData = this.trigger({ phase: 'before', type: 'select', target: this.name, all: true });
  1005. if (eventData.isCancelled === true) return;
  1006. // default action
  1007. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  1008. var sel = this.last.selection;
  1009. var cols = [];
  1010. for (var c in this.columns) cols.push(parseInt(c));
  1011. // if local data source and searched
  1012. sel.indexes = [];
  1013. if (!url && this.searchData.length !== 0) {
  1014. // local search applied
  1015. for (var i = 0; i < this.last.searchIds.length; i++) {
  1016. sel.indexes.push(this.last.searchIds[i]);
  1017. if (this.selectType != 'row') sel.columns[this.last.searchIds[i]] = cols.slice(); // .slice makes copy of the array
  1018. }
  1019. } else {
  1020. var buffered = this.records.length;
  1021. if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length;
  1022. for (var i = 0; i < buffered; i++) {
  1023. sel.indexes.push(i);
  1024. if (this.selectType != 'row') sel.columns[i] = cols.slice(); // .slice makes copy of the array
  1025. }
  1026. }
  1027. // add selected class
  1028. if (this.selectType == 'row') {
  1029. $(this.box).find('.w2ui-grid-records tr').addClass('w2ui-selected').data('selected', 'yes').find('.w2ui-col-number').addClass('w2ui-row-selected');
  1030. $(this.box).find('input.w2ui-grid-select-check').prop('checked', true);
  1031. } else {
  1032. $(this.box).find('.w2ui-grid-columns td .w2ui-col-header').addClass('w2ui-col-selected');
  1033. $(this.box).find('.w2ui-grid-records tr .w2ui-col-number').addClass('w2ui-row-selected')
  1034. $(this.box).find('.w2ui-grid-data').addClass('w2ui-selected').data('selected', 'yes');
  1035. }
  1036. // enable/disable toolbar buttons
  1037. var sel = this.getSelection();
  1038. if (sel.length == 1) this.toolbar.enable('w2ui-edit'); else this.toolbar.disable('w2ui-edit');
  1039. if (sel.length >= 1) this.toolbar.enable('w2ui-delete'); else this.toolbar.disable('w2ui-delete');
  1040. this.addRange('selection');
  1041. // event after
  1042. this.trigger($.extend(eventData, { phase: 'after' }));
  1043. return (new Date()).getTime() - time;
  1044. },
  1045. selectNone: function () {
  1046. var time = (new Date()).getTime();
  1047. // event before
  1048. var eventData = this.trigger({ phase: 'before', type: 'unselect', target: this.name, all: true });
  1049. if (eventData.isCancelled === true) return;
  1050. // default action
  1051. var sel = this.last.selection;
  1052. // remove selected class
  1053. if (this.selectType == 'row') {
  1054. $(this.box).find('.w2ui-grid-records tr.w2ui-selected').removeClass('w2ui-selected').removeData('selected')
  1055. .find('.w2ui-col-number').removeClass('w2ui-row-selected');
  1056. $(this.box).find('input.w2ui-grid-select-check').prop('checked', false);
  1057. } else {
  1058. $(this.box).find('.w2ui-grid-columns td .w2ui-col-header').removeClass('w2ui-col-selected');
  1059. $(this.box).find('.w2ui-grid-records tr .w2ui-col-number').removeClass('w2ui-row-selected');
  1060. $(this.box).find('.w2ui-grid-data.w2ui-selected').removeClass('w2ui-selected').removeData('selected');
  1061. }
  1062. sel.indexes = [];
  1063. sel.columns = {};
  1064. this.toolbar.disable('w2ui-edit', 'w2ui-delete');
  1065. this.removeRange('selection');
  1066. $('#grid_'+ this.name +'_check_all').prop('checked', false);
  1067. // event after
  1068. this.trigger($.extend(eventData, { phase: 'after' }));
  1069. return (new Date()).getTime() - time;
  1070. },
  1071. getSelection: function (returnIndex) {
  1072. var ret = [];
  1073. var sel = this.last.selection;
  1074. if (this.selectType == 'row') {
  1075. for (var s in sel.indexes) {
  1076. if (!this.records[sel.indexes[s]]) continue;
  1077. if (returnIndex === true) ret.push(sel.indexes[s]); else ret.push(this.records[sel.indexes[s]].recid);
  1078. }
  1079. return ret;
  1080. } else {
  1081. for (var s in sel.indexes) {
  1082. var cols = sel.columns[sel.indexes[s]];
  1083. if (!this.records[sel.indexes[s]]) continue;
  1084. for (var c in cols) {
  1085. ret.push({ recid: this.records[sel.indexes[s]].recid, index: parseInt(sel.indexes[s]), column: cols[c] });
  1086. }
  1087. }
  1088. return ret;
  1089. }
  1090. },
  1091. search: function (field, value) {
  1092. var obj = this;
  1093. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  1094. var searchData = [];
  1095. var last_multi = this.last.multi;
  1096. var last_logic = this.last.logic;
  1097. var last_field = this.last.field;
  1098. var last_search = this.last.search;
  1099. // 1: search() - advanced search (reads from popup)
  1100. if (arguments.length == 0) {
  1101. last_search = '';
  1102. // advanced search
  1103. for (var s in this.searches) {
  1104. var search = this.searches[s];
  1105. var operator = $('#grid_'+ this.name + '_operator_'+s).val();
  1106. var field1 = $('#grid_'+ this.name + '_field_'+s);
  1107. var field2 = $('#grid_'+ this.name + '_field2_'+s);
  1108. var value1 = field1.val();
  1109. var value2 = field2.val();
  1110. var svalue = null;
  1111. if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) {
  1112. var fld1 = field1.data('w2field');
  1113. var fld2 = field2.data('w2field');
  1114. if (fld1) value1 = fld1.clean(value1);
  1115. if (fld2) value2 = fld2.clean(value2);
  1116. }
  1117. if (['list', 'enum'].indexOf(search.type) != -1) {
  1118. value1 = field1.data('selected') || {};
  1119. if ($.isArray(value1)) {
  1120. svalue = [];
  1121. for (var v in value1) {
  1122. svalue.push(w2utils.isFloat(value1[v].id) ? parseFloat(value1[v].id) : String(value1[v].id).toLowerCase());
  1123. delete value1[v].hidden;
  1124. }
  1125. } else {
  1126. value1 = value1.id || '';
  1127. }
  1128. }
  1129. if ((value1 != '' && value1 != null) || (typeof value2 != 'undefined' && value2 != '')) {
  1130. var tmp = {
  1131. field : search.field,
  1132. type : search.type,
  1133. operator : operator
  1134. }
  1135. if (operator == 'between') {
  1136. $.extend(tmp, { value: [value1, value2] });
  1137. } else if (operator == 'in' && typeof value1 == 'string') {
  1138. $.extend(tmp, { value: value1.split(',') });
  1139. } else if (operator == 'not in' && typeof value1 == 'string') {
  1140. $.extend(tmp, { value: value1.split(',') });
  1141. } else {
  1142. $.extend(tmp, { value: value1 });
  1143. }
  1144. if (svalue) $.extend(tmp, { svalue: svalue });
  1145. // conver date to unix time
  1146. try {
  1147. if (search.type == 'date' && operator == 'between') {
  1148. tmp.value[0] = value1; // w2utils.isDate(value1, w2utils.settings.date_format, true).getTime();
  1149. tmp.value[1] = value2; // w2utils.isDate(value2, w2utils.settings.date_format, true).getTime();
  1150. }
  1151. if (search.type == 'date' && operator == 'is') {
  1152. tmp.value = value1; // w2utils.isDate(value1, w2utils.settings.date_format, true).getTime();
  1153. }
  1154. } catch (e) {
  1155. }
  1156. searchData.push(tmp);
  1157. }
  1158. }
  1159. if (searchData.length > 0 && !url) {
  1160. last_multi = true;
  1161. last_logic = 'AND';
  1162. } else {
  1163. last_multi = true;
  1164. last_logic = 'AND';
  1165. }
  1166. }
  1167. // 2: search(field, value) - regular search
  1168. if (typeof field == 'string') {
  1169. last_field = field;
  1170. last_search = value;
  1171. last_multi = false;
  1172. last_logic = 'OR';
  1173. // loop through all searches and see if it applies
  1174. if (typeof value != 'undefined') {
  1175. if (field.toLowerCase() == 'all') {
  1176. // if there are search fields loop thru them
  1177. if (this.searches.length > 0) {
  1178. for (var s in this.searches) {
  1179. var search = this.searches[s];
  1180. if (search.type == 'text' || (search.type == 'alphanumeric' && w2utils.isAlphaNumeric(value))
  1181. || (search.type == 'int' && w2utils.isInt(value)) || (search.type == 'float' && w2utils.isFloat(value))
  1182. || (search.type == 'percent' && w2utils.isFloat(value)) || (search.type == 'hex' && w2utils.isHex(value))
  1183. || (search.type == 'currency' && w2utils.isMoney(value)) || (search.type == 'money' && w2utils.isMoney(value))
  1184. || (search.type == 'date' && w2utils.isDate(value)) ) {
  1185. var tmp = {
  1186. field : search.field,
  1187. type : search.type,
  1188. operator : (search.type == 'text' ? 'contains' : 'is'),
  1189. value : value
  1190. };
  1191. searchData.push(tmp);
  1192. }
  1193. // range in global search box
  1194. if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1 && String(value).indexOf('-') != -1) {
  1195. var t = String(value).split('-');
  1196. var tmp = {
  1197. field : search.field,
  1198. type : search.type,
  1199. operator : 'between',
  1200. value : [t[0], t[1]]
  1201. };
  1202. searchData.push(tmp);
  1203. }
  1204. }
  1205. } else {
  1206. // no search fields, loop thru columns
  1207. for (var c in this.columns) {
  1208. var tmp = {
  1209. field : this.columns[c].field,
  1210. type : 'text',
  1211. operator : 'contains',
  1212. value : value
  1213. };
  1214. searchData.push(tmp);
  1215. }
  1216. }
  1217. } else {
  1218. var el = $('#grid_'+ this.name +'_search_all');
  1219. var search = this.getSearch(field);
  1220. if (search == null) search = { field: field, type: 'text' };
  1221. if (search.field == field) this.last.caption = search.caption;
  1222. if (search.type == 'list') {
  1223. var tmp = el.data('selected');
  1224. if (tmp && !$.isEmptyObject(tmp)) value = tmp.id;
  1225. }
  1226. if (value != '') {
  1227. var op = 'contains';
  1228. var val = value;
  1229. if (['date', 'time', 'list'].indexOf(search.type) != -1) op = 'is';
  1230. if (search.type == 'int' && value != '') {
  1231. op = 'is';
  1232. if (String(value).indexOf('-') != -1) {
  1233. var tmp = value.split('-');
  1234. if (tmp.length == 2) {
  1235. op = 'between';
  1236. val = [parseInt(tmp[0]), parseInt(tmp[1])];
  1237. }
  1238. }
  1239. if (String(value).indexOf(',') != -1) {
  1240. var tmp = value.split(',');
  1241. op = 'in';
  1242. val = [];
  1243. for (var t in tmp) val.push(tmp[t]);
  1244. }
  1245. }
  1246. var tmp = {
  1247. field : search.field,
  1248. type : search.type,
  1249. operator : op,
  1250. value : val
  1251. };
  1252. searchData.push(tmp);
  1253. }
  1254. }
  1255. }
  1256. }
  1257. // 3: search([ { field, value, [operator,] [type] }, { field, value, [operator,] [type] } ], logic) - submit whole structure
  1258. if ($.isArray(field)) {
  1259. var logic = 'AND';
  1260. if (typeof value == 'string') {
  1261. logic = value.toUpperCase();
  1262. if (logic != 'OR' && logic != 'AND') logic = 'AND';
  1263. }
  1264. last_search = '';
  1265. last_multi = true;
  1266. last_logic = logic;
  1267. for (var f in field) {
  1268. var data = field[f];
  1269. var search = this.getSearch(data.field);
  1270. if (search == null) search = { type: 'text', operator: 'contains' };
  1271. if ($.isArray(data.value)) {
  1272. for (var v in data.value) {
  1273. if (typeof data.value[v] == 'string') data.value[v] = data.value[v].toLowerCase();
  1274. }
  1275. }
  1276. // merge current field and search if any
  1277. searchData.push($.extend(true, {}, search, data));
  1278. }
  1279. }
  1280. // event before
  1281. var eventData = this.trigger({ phase: 'before', type: 'search', target: this.name, searchData: searchData,
  1282. searchField: (field ? field : 'multi'), searchValue: (value ? value : 'multi') });
  1283. if (eventData.isCancelled === true) return;
  1284. // default action
  1285. this.searchData = eventData.searchData;
  1286. this.last.field = last_field;
  1287. this.last.search = last_search;
  1288. this.last.multi = last_multi;
  1289. this.last.logic = last_logic;
  1290. this.last.scrollTop = 0;
  1291. this.last.scrollLeft = 0;
  1292. this.last.selection.indexes = [];
  1293. this.last.selection.columns = {};
  1294. // -- clear all search field
  1295. this.searchClose();
  1296. this.set({ expanded: false }, true);
  1297. // apply search
  1298. if (url) {
  1299. this.last.xhr_offset = 0;
  1300. this.reload();
  1301. } else {
  1302. // local search
  1303. this.localSearch();
  1304. this.refresh();
  1305. }
  1306. // event after
  1307. this.trigger($.extend(eventData, { phase: 'after' }));
  1308. },
  1309. searchOpen: function () {
  1310. if (!this.box) return;
  1311. if (this.searches.length == 0) return;
  1312. var obj = this;
  1313. // show search
  1314. $('#tb_'+ this.name +'_toolbar_item_w2ui-search-advanced').w2overlay(
  1315. this.getSearchesHTML(), {
  1316. name : 'searches-'+ this.name,
  1317. left : -10,
  1318. 'class' : 'w2ui-grid-searches',
  1319. onShow : function () {
  1320. if (obj.last.logic == 'OR') obj.searchData = [];
  1321. obj.initSearches();
  1322. $('#w2ui-overlay-searches-'+ this.name +' .w2ui-grid-searches').data('grid-name', obj.name);
  1323. var sfields = $('#w2ui-overlay-searches-'+ this.name +' .w2ui-grid-searches *[rel=search]');
  1324. if (sfields.length > 0) sfields[0].focus();
  1325. }
  1326. }
  1327. );
  1328. },
  1329. searchClose: function () {
  1330. if (!this.box) return;
  1331. if (this.searches.length == 0) return;
  1332. if (this.toolbar) this.toolbar.uncheck('w2ui-search-advanced');
  1333. // hide search
  1334. if ($('#w2ui-overlay-searches-'+ this.name +' .w2ui-grid-searches').length > 0) {
  1335. $().w2overlay('', { name: 'searches-'+ this.name });
  1336. }
  1337. },
  1338. searchShowFields: function () {
  1339. var el = $('#grid_'+ this.name +'_search_all');
  1340. var html = '<div class="w2ui-select-field"><table>';
  1341. for (var s = -1; s < this.searches.length; s++) {
  1342. var search = this.searches[s];
  1343. if (s == -1) {
  1344. if (!this.multiSearch) continue;
  1345. search = { field: 'all', caption: w2utils.lang('All Fields') };
  1346. } else {
  1347. if (this.searches[s].hidden === true) continue;
  1348. }
  1349. html += '<tr '+ (w2utils.isIOS ? 'onTouchStart' : 'onClick') +'="w2ui[\''+ this.name +'\'].initAllField(\''+ search.field +'\')">'+
  1350. ' <td><input type="radio" tabIndex="-1" '+ (search.field == this.last.field ? 'checked' : '') +'></td>'+
  1351. ' <td>'+ search.caption +'</td>'+
  1352. '</tr>';
  1353. }
  1354. html += "</table></div>";
  1355. // need timer otherwise does nto show with list type
  1356. setTimeout(function () {
  1357. $(el).w2overlay(html, { left: -10 });
  1358. }, 1);
  1359. },
  1360. initAllField: function (field, value) {
  1361. var el = $('#grid_'+ this.name +'_search_all');
  1362. var search = this.getSearch(field);
  1363. if (field == 'all') {
  1364. search = { field: 'all', caption: w2utils.lang('All Fields') };
  1365. el.w2field('clear');
  1366. el.change().focus();
  1367. } else {
  1368. var st = search.type;
  1369. if (['enum', 'select'].indexOf(st) != -1) st = 'list';
  1370. el.w2field(st, $.extend({}, search.options, { suffix: '', autoFormat: false, selected: value }));
  1371. if (['list', 'enum'].indexOf(search.type) != -1) {
  1372. this.last.search = '';
  1373. this.last.item = '';
  1374. el.val('');
  1375. }
  1376. // set focus
  1377. setTimeout(function () {
  1378. el.focus(); /* do not do el.change() as it will refresh grid and pull from server */
  1379. }, 1);
  1380. }
  1381. // update field
  1382. if (this.last.search != '') {
  1383. this.search(search.field, this.last.search);
  1384. } else {
  1385. this.last.field = search.field;
  1386. this.last.caption = search.caption;
  1387. }
  1388. el.attr('placeholder', search.caption);
  1389. $().w2overlay();
  1390. },
  1391. searchReset: function (noRefresh) {
  1392. // event before
  1393. var eventData = this.trigger({ phase: 'before', type: 'search', target: this.name, searchData: [] });
  1394. if (eventData.isCancelled === true) return;
  1395. // default action
  1396. this.searchData = [];
  1397. this.last.search = '';
  1398. this.last.logic = 'OR';
  1399. // --- do not reset to All Fields (I think)
  1400. // if (this.last.multi) {
  1401. // if (!this.multiSearch) {
  1402. // this.last.field = this.searches[0].field;
  1403. // this.last.caption = this.searches[0].caption;
  1404. // } else {
  1405. // this.last.field = 'all';
  1406. // this.last.caption = w2utils.lang('All Fields');
  1407. // }
  1408. // }
  1409. this.last.multi = false;
  1410. this.last.xhr_offset = 0;
  1411. // reset scrolling position
  1412. this.last.scrollTop = 0;
  1413. this.last.scrollLeft = 0;
  1414. this.last.selection.indexes = [];
  1415. this.last.selection.columns = {};
  1416. // -- clear all search field
  1417. this.searchClose();
  1418. $('#grid_'+ this.name +'_search_all').val('');
  1419. // apply search
  1420. if (!noRefresh) this.reload();
  1421. // event after
  1422. this.trigger($.extend(eventData, { phase: 'after' }));
  1423. },
  1424. clear: function (noRefresh) {
  1425. // this.offset = 0; // clear should not reset offset
  1426. // this.total = 0; // clear should not reset total
  1427. this.records = [];
  1428. this.summary = [];
  1429. this.last.scrollTop = 0;
  1430. this.last.scrollLeft = 0;
  1431. this.last.range_start = null;
  1432. this.last.range_end = null;
  1433. // this.last.xhr_offset = 0; // clear should not reset offset
  1434. if (!noRefresh) this.refresh();
  1435. },
  1436. reset: function (noRefresh) {
  1437. // reset last remembered state
  1438. this.offset = 0;
  1439. this.total = 0;
  1440. this.last.scrollTop = 0;
  1441. this.last.scrollLeft = 0;
  1442. this.last.selection.indexes = [];
  1443. this.last.selection.columns = {};
  1444. this.last.range_start = null;
  1445. this.last.range_end = null;
  1446. this.last.xhr_offset = 0;
  1447. this.searchReset(noRefresh);
  1448. // initial sort
  1449. if (this.last.sortData != null ) this.sortData = this.last.sortData;
  1450. // select none without refresh
  1451. this.set({ expanded: false }, true);
  1452. // refresh
  1453. if (!noRefresh) this.refresh();
  1454. },
  1455. skip: function (offset) {
  1456. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  1457. if (url) {
  1458. this.offset = parseInt(offset);
  1459. if (this.offset > this.total) this.offset = this.total - this.limit;
  1460. if (this.offset < 0 || !w2utils.isInt(this.offset)) this.offset = 0;
  1461. this.records = [];
  1462. this.last.xhr_offset = 0;
  1463. this.last.pull_more = true;
  1464. this.last.scrollTop = 0;
  1465. this.last.scrollLeft = 0;
  1466. $('#grid_'+ this.name +'_records').prop('scrollTop', 0);
  1467. this.reload();
  1468. } else {
  1469. console.log('ERROR: grid.skip() can only be called when you have remote data source.');
  1470. }
  1471. },
  1472. load: function (url, callBack) {
  1473. if (typeof url == 'undefined') {
  1474. console.log('ERROR: You need to provide url argument when calling .load() method of "'+ this.name +'" object.');
  1475. return;
  1476. }
  1477. // default action
  1478. this.request('get-records', {}, url, callBack);
  1479. },
  1480. reload: function (callBack) {
  1481. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  1482. if (url) {
  1483. this.clear(true);
  1484. this.request('get-records', {}, null, callBack);
  1485. } else {
  1486. this.last.scrollTop = 0;
  1487. this.last.scrollLeft = 0;
  1488. this.last.range_start = null;
  1489. this.last.range_end = null;
  1490. this.localSearch();
  1491. this.refresh();
  1492. if (typeof callBack == 'function') callBack({ status: 'success' });
  1493. }
  1494. },
  1495. request: function (cmd, add_params, url, callBack) {
  1496. if (typeof add_params == 'undefined') add_params = {};
  1497. if (typeof url == 'undefined' || url == '' || url == null) url = this.url;
  1498. if (url == '' || url == null) return;
  1499. // build parameters list
  1500. var params = {};
  1501. if (!w2utils.isInt(this.offset)) this.offset = 0;
  1502. if (!w2utils.isInt(this.last.xhr_offset)) this.last.xhr_offset = 0;
  1503. // add list params
  1504. params['cmd'] = cmd;
  1505. params['selected'] = this.getSelection();
  1506. params['limit'] = this.limit;
  1507. params['offset'] = parseInt(this.offset) + this.last.xhr_offset;
  1508. params['search'] = this.searchData;
  1509. params['searchLogic'] = this.last.logic;
  1510. params['sort'] = this.sortData;
  1511. if (this.searchData.length == 0) {
  1512. delete params['search'];
  1513. delete params['searchLogic'];
  1514. }
  1515. if (this.sortData.length == 0) {
  1516. delete params['sort'];
  1517. }
  1518. // append other params
  1519. $.extend(params, this.postData);
  1520. $.extend(params, add_params);
  1521. // event before
  1522. if (cmd == 'get-records') {
  1523. var eventData = this.trigger({ phase: 'before', type: 'request', target: this.name, url: url, postData: params });
  1524. if (eventData.isCancelled === true) { if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); return; }
  1525. } else {
  1526. var eventData = { url: url, postData: params };
  1527. }
  1528. // call server to get data
  1529. var obj = this;
  1530. if (this.last.xhr_offset == 0) {
  1531. this.lock(this.msgRefresh, true);
  1532. } else {
  1533. var more = $('#grid_'+ this.name +'_rec_more');
  1534. if (this.autoLoad === true) {
  1535. more.show().find('td').html('<div><div style="width: 20px; height: 20px;" class="w2ui-spinner"></div></div>');
  1536. } else {
  1537. more.find('td').html('<div>'+ w2utils.lang('Load') + ' ' + obj.limit + ' ' + w2utils.lang('More') + '...</div>');
  1538. }
  1539. }
  1540. if (this.last.xhr) try { this.last.xhr.abort(); } catch (e) {}
  1541. // URL
  1542. var url = (typeof eventData.url != 'object' ? eventData.url : eventData.url.get);
  1543. if (params.cmd == 'save-records' && typeof eventData.url == 'object') url = eventData.url.save;
  1544. if (params.cmd == 'delete-records' && typeof eventData.url == 'object') url = eventData.url.remove;
  1545. // process url with routeData
  1546. if (!$.isEmptyObject(obj.routeData)) {
  1547. var info = w2utils.parseRoute(url);
  1548. if (info.keys.length > 0) {
  1549. for (var k = 0; k < info.keys.length; k++) {
  1550. if (obj.routeData[info.keys[k].name] == null) continue;
  1551. url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), obj.routeData[info.keys[k].name]);
  1552. }
  1553. }
  1554. }
  1555. // ajax ptions
  1556. var ajaxOptions = {
  1557. type : 'POST',
  1558. url : url,
  1559. data : eventData.postData,
  1560. dataType : 'text' // expected data type from server
  1561. };
  1562. if (w2utils.settings.dataType == 'HTTP') {
  1563. ajaxOptions.data = (typeof ajaxOptions.data == 'object' ? String($.param(ajaxOptions.data, false)).replace(/%5B/g, '[').replace(/%5D/g, ']') : ajaxOptions.data);
  1564. }
  1565. if (w2utils.settings.dataType == 'RESTFULL') {
  1566. ajaxOptions.type = 'GET';
  1567. if (params.cmd == 'save-records') ajaxOptions.type = 'PUT'; // so far it is always update
  1568. if (params.cmd == 'delete-records') ajaxOptions.type = 'DELETE';
  1569. ajaxOptions.data = (typeof ajaxOptions.data == 'object' ? String($.param(ajaxOptions.data, false)).replace(/%5B/g, '[').replace(/%5D/g, ']') : ajaxOptions.data);
  1570. }
  1571. if (w2utils.settings.dataType == 'JSON') {
  1572. ajaxOptions.type = 'POST';
  1573. ajaxOptions.data = JSON.stringify(ajaxOptions.data);
  1574. ajaxOptions.contentType = 'application/json';
  1575. }
  1576. if (this.method) ajaxOptions.type = this.method;
  1577. this.last.xhr_cmd = params.cmd;
  1578. this.last.xhr_start = (new Date()).getTime();
  1579. this.last.xhr = $.ajax(ajaxOptions)
  1580. .done(function (data, status, xhr) {
  1581. obj.requestComplete(status, cmd, callBack);
  1582. })
  1583. .fail(function (xhr, status, error) {
  1584. // trigger event
  1585. var errorObj = { status: status, error: error, rawResponseText: xhr.responseText };
  1586. var eventData2 = obj.trigger({ phase: 'before', type: 'error', error: errorObj, xhr: xhr });
  1587. if (eventData2.isCancelled === true) return;
  1588. // default behavior
  1589. if (status != 'abort') {
  1590. var data;
  1591. try { data = $.parseJSON(xhr.responseText) } catch (e) {}
  1592. console.log('ERROR: Server communication failed.',
  1593. '\n EXPECTED:', { status: 'success', total: 5, records: [{ recid: 1, field: 'value' }] },
  1594. '\n OR:', { status: 'error', message: 'error message' },
  1595. '\n RECEIVED:', typeof data == 'object' ? data : xhr.responseText);
  1596. }
  1597. obj.requestComplete('error', cmd, callBack);
  1598. // event after
  1599. obj.trigger($.extend(eventData2, { phase: 'after' }));
  1600. });
  1601. if (cmd == 'get-records') {
  1602. // event after
  1603. this.trigger($.extend(eventData, { phase: 'after' }));
  1604. }
  1605. },
  1606. requestComplete: function(status, cmd, callBack) {
  1607. var obj = this;
  1608. this.unlock();
  1609. setTimeout(function () {
  1610. if (obj.show.statusResponse) obj.status(w2utils.lang('Server Response') + ' ' + ((new Date()).getTime() - obj.last.xhr_start)/1000 +' ' + w2utils.lang('sec'));
  1611. }, 10);
  1612. this.last.pull_more = false;
  1613. this.last.pull_refresh = true;
  1614. // event before
  1615. var event_name = 'load';
  1616. if (this.last.xhr_cmd == 'save-records') event_name = 'save';
  1617. if (this.last.xhr_cmd == 'delete-records') event_name = 'deleted';
  1618. var eventData = this.trigger({ phase: 'before', target: this.name, type: event_name, xhr: this.last.xhr, status: status });
  1619. if (eventData.isCancelled === true) {
  1620. if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' });
  1621. return;
  1622. }
  1623. // parse server response
  1624. var data;
  1625. var responseText = this.last.xhr.responseText;
  1626. if (status != 'error') {
  1627. // default action
  1628. if (typeof responseText != 'undefined' && responseText != '') {
  1629. // check if the onLoad handler has not already parsed the data
  1630. if (typeof responseText == "object") {
  1631. data = responseText;
  1632. } else {
  1633. if (typeof obj.parser == 'function') {
  1634. data = obj.parser(responseText);
  1635. if (typeof data != 'object') {
  1636. console.log('ERROR: Your parser did not return proper object');
  1637. }
  1638. } else {
  1639. // $.parseJSON or $.getJSON did not work because those expect perfect JSON data - where everything is in double quotes
  1640. //
  1641. // TODO: avoid (potentially malicious) code injection from the response.
  1642. try { eval('data = '+ responseText); } catch (e) { }
  1643. }
  1644. }
  1645. // convert recids
  1646. if (obj.recid) {
  1647. for (var r in data.records) {
  1648. data.records[r]['recid'] = data.records[r][obj.recid];
  1649. }
  1650. }
  1651. if (typeof data == 'undefined') {
  1652. data = {
  1653. status : 'error',
  1654. message : this.msgNotJSON,
  1655. responseText : responseText
  1656. };
  1657. }
  1658. if (data['status'] == 'error') {
  1659. obj.error(data['message']);
  1660. } else {
  1661. if (cmd == 'get-records') {
  1662. if (this.last.xhr_offset == 0) {
  1663. this.records = [];
  1664. this.summary = [];
  1665. //data.xhr_status=data.status;
  1666. delete data.status;
  1667. $.extend(true, this, data);
  1668. } else {
  1669. var records = data.records;
  1670. delete data.records;
  1671. //data.xhr_status=data.status;
  1672. delete data.status;
  1673. $.extend(true, this, data);
  1674. for (var r in records) {
  1675. this.records.push(records[r]);
  1676. }
  1677. }
  1678. }
  1679. if (cmd == 'delete-records') {
  1680. // reset() also triggers reload
  1681. this.reset(); // unselect old selections
  1682. return;
  1683. }
  1684. }
  1685. }
  1686. } else {
  1687. data = {
  1688. status : 'error',
  1689. message : this.msgAJAXerror,
  1690. responseText : responseText
  1691. };
  1692. obj.error(this.msgAJAXerror);
  1693. }
  1694. // event after
  1695. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  1696. if (!url) {
  1697. this.localSort();
  1698. this.localSearch();
  1699. }
  1700. this.total = parseInt(this.total);
  1701. this.trigger($.extend(eventData, { phase: 'after' }));
  1702. // do not refresh if loading on infinite scroll
  1703. if (this.last.xhr_offset == 0) this.refresh(); else this.scroll();
  1704. // call back
  1705. if (typeof callBack == 'function') callBack(data);
  1706. },
  1707. error: function (msg) {
  1708. var obj = this;
  1709. // let the management of the error outside of the grid
  1710. var eventData = this.trigger({ target: this.name, type: 'error', message: msg , xhr: this.last.xhr });
  1711. if (eventData.isCancelled === true) {
  1712. if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' });
  1713. return;
  1714. }
  1715. w2alert(msg, 'Error');
  1716. // event after
  1717. this.trigger($.extend(eventData, { phase: 'after' }));
  1718. },
  1719. getChanges: function () {
  1720. var changes = [];
  1721. for (var r in this.records) {
  1722. var rec = this.records[r];
  1723. if (typeof rec['changes'] != 'undefined') {
  1724. changes.push($.extend(true, { recid: rec.recid }, rec.changes));
  1725. }
  1726. }
  1727. return changes;
  1728. },
  1729. mergeChanges: function () {
  1730. var changes = this.getChanges();
  1731. for (var c in changes) {
  1732. var record = this.get(changes[c].recid);
  1733. for (var s in changes[c]) {
  1734. if (s == 'recid') continue; // do not allow to change recid
  1735. try { eval('record.' + s + ' = changes[c][s]'); } catch (e) {}
  1736. delete record.changes;
  1737. }
  1738. }
  1739. this.refresh();
  1740. },
  1741. // ===================================================
  1742. // -- Action Handlers
  1743. save: function () {
  1744. var obj = this;
  1745. var changes = this.getChanges();
  1746. // event before
  1747. var eventData = this.trigger({ phase: 'before', target: this.name, type: 'submit', changes: changes });
  1748. if (eventData.isCancelled === true) return;
  1749. var url = (typeof this.url != 'object' ? this.url : this.url.save);
  1750. if (url) {
  1751. this.request('save-records', { 'changes' : eventData.changes }, null,
  1752. function (data) {
  1753. if (data.status !== 'error') {
  1754. // only merge changes, if save was successful
  1755. obj.mergeChanges();
  1756. }
  1757. // event after
  1758. obj.trigger($.extend(eventData, { phase: 'after' }));
  1759. }
  1760. );
  1761. } else {
  1762. this.mergeChanges();
  1763. // event after
  1764. this.trigger($.extend(eventData, { phase: 'after' }));
  1765. }
  1766. },
  1767. editField: function (recid, column, value, event) {
  1768. var obj = this;
  1769. var index = obj.get(recid, true);
  1770. var rec = obj.records[index];
  1771. var col = obj.columns[column];
  1772. var edit = col ? col.editable : null;
  1773. if (!rec || !col || !edit || rec.editable === false) return;
  1774. if (['enum', 'file'].indexOf(edit.type) != -1) {
  1775. console.log('ERROR: input types "enum" and "file" are not supported in inline editing.');
  1776. return;
  1777. }
  1778. // event before
  1779. var eventData = obj.trigger({ phase: 'before', type: 'editField', target: obj.name, recid: recid, column: column, value: value,
  1780. index: index, originalEvent: event });
  1781. if (eventData.isCancelled === true) return;
  1782. value = eventData.value;
  1783. // default behaviour
  1784. this.selectNone();
  1785. this.select({ recid: recid, column: column });
  1786. this.last.edit_col = column;
  1787. if (['checkbox', 'check'].indexOf(edit.type) != -1) return;
  1788. // create input element
  1789. var tr = $('#grid_'+ obj.name +'_rec_'+ w2utils.escapeId(recid));
  1790. var el = tr.find('[col='+ column +'] > div');
  1791. if (typeof edit.inTag == 'undefined') edit.inTag = '';
  1792. if (typeof edit.outTag == 'undefined') edit.outTag = '';
  1793. if (typeof edit.style == 'undefined') edit.style = '';
  1794. if (typeof edit.items == 'undefined') edit.items = [];
  1795. var val = (rec.changes && typeof rec.changes[col.field] != 'undefined' ? w2utils.stripTags(rec.changes[col.field]) : w2utils.stripTags(rec[col.field]));
  1796. if (val == null || typeof val == 'undefined') val = '';
  1797. if (typeof value != 'undefined' && value != null) val = value;
  1798. var addStyle = (typeof col.style != 'undefined' ? col.style + ';' : '');
  1799. if (typeof col.render == 'string' && ['number', 'int', 'float', 'money', 'percent'].indexOf(col.render.split(':')[0]) != -1) {
  1800. addStyle += 'text-align: right;';
  1801. }
  1802. // mormalize items
  1803. if (edit.items.length > 0 && !$.isPlainObject(edit.items[0])) {
  1804. edit.items = w2obj.field.prototype.normMenu(edit.items);
  1805. }
  1806. if (edit.type == 'select') {
  1807. var html = '';
  1808. for (var i in edit.items) {
  1809. html += '<option value="'+ edit.items[i].id +'" '+ (edit.items[i].id == val ? 'selected' : '') +'>'+ edit.items[i].text +'</option>';
  1810. }
  1811. el.addClass('w2ui-editable')
  1812. .html('<select id="grid_'+ obj.name +'_edit_'+ recid +'_'+ column +'" column="'+ column +'" '+
  1813. ' style="width: 100%; '+ addStyle + edit.style +'" field="'+ col.field +'" recid="'+ recid +'" '+
  1814. ' '+ edit.inTag +
  1815. '>'+ html +'</select>' + edit.outTag);
  1816. el.find('select').focus()
  1817. .on('change', function (event) {
  1818. delete obj.last.move;
  1819. })
  1820. .on('blur', function (event) {
  1821. obj.editChange.call(obj, this, index, column, event);
  1822. });
  1823. } else {
  1824. el.addClass('w2ui-editable')
  1825. .html('<input id="grid_'+ obj.name +'_edit_'+ recid +'_'+ column +'" '+
  1826. ' type="text" style="outline: none; '+ addStyle + edit.style +'" field="'+ col.field +'" recid="'+ recid +'" '+
  1827. ' column="'+ column +'" '+ edit.inTag +
  1828. '>' + edit.outTag);
  1829. if (value == null) el.find('input').val(val != 'object' ? val : '');
  1830. // init w2field
  1831. var input = el.find('input').get(0);
  1832. $(input).w2field(edit.type, $.extend(edit, { selected: val }));
  1833. // add blur listener
  1834. setTimeout(function () {
  1835. var tmp = input;
  1836. if (edit.type == 'list') {
  1837. tmp = $($(input).data('w2field').helpers.focus).find('input');
  1838. if (val != 'object' && val != '') tmp.val(val).css({ opacity: 1 }).prev().css({ opacity: 1 });
  1839. }
  1840. $(tmp).on('blur', function (event) {
  1841. obj.editChange.call(obj, input, index, column, event);
  1842. });
  1843. }, 10);
  1844. if (value != null) $(input).val(val != 'object' ? val : '');
  1845. }
  1846. setTimeout(function () {
  1847. el.find('input, select')
  1848. .on('click', function (event) {
  1849. event.stopPropagation();
  1850. })
  1851. .on('keydown', function (event) {
  1852. var cancel = false;
  1853. switch (event.keyCode) {
  1854. case 9: // tab
  1855. cancel = true;
  1856. var next_rec = recid;
  1857. var next_col = event.shiftKey ? obj.prevCell(column, true) : obj.nextCell(column, true);
  1858. // next or prev row
  1859. if (next_col == null) {
  1860. var tmp = event.shiftKey ? obj.prevRow(index) : obj.nextRow(index);
  1861. if (tmp != null && tmp != index) {
  1862. next_rec = obj.records[tmp].recid;
  1863. // find first editable row
  1864. for (var c in obj.columns) {
  1865. var tmp = obj.columns[c].editable;
  1866. if (typeof tmp != 'undefined' && ['checkbox', 'check'].indexOf(tmp.type) == -1) {
  1867. next_col = parseInt(c);
  1868. if (!event.shiftKey) break;
  1869. }
  1870. }
  1871. }
  1872. }
  1873. if (next_rec === false) next_rec = recid;
  1874. if (next_col == null) next_col = column;
  1875. // init new or same record
  1876. this.blur();
  1877. setTimeout(function () {
  1878. if (obj.selectType != 'row') {
  1879. obj.selectNone();
  1880. obj.select({ recid: next_rec, column: next_col });
  1881. } else {
  1882. obj.editField(next_rec, next_col, null, event);
  1883. }
  1884. }, 1);
  1885. break;
  1886. case 13: // enter
  1887. this.blur();
  1888. var next = event.shiftKey ? obj.prevRow(index) : obj.nextRow(index);
  1889. if (next != null && next != index) {
  1890. setTimeout(function () {
  1891. if (obj.selectType != 'row') {
  1892. obj.selectNone();
  1893. obj.select({ recid: obj.records[next].recid, column: column });
  1894. } else {
  1895. obj.editField(obj.records[next].recid, column, null, event);
  1896. }
  1897. }, 100);
  1898. }
  1899. break;
  1900. case 38: // up arrow
  1901. if (!event.shiftKey) break;
  1902. cancel = true;
  1903. var next = obj.prevRow(index);
  1904. if (next != index) {
  1905. this.blur();
  1906. setTimeout(function () {
  1907. if (obj.selectType != 'row') {
  1908. obj.selectNone();
  1909. obj.select({ recid: obj.records[next].recid, column: column });
  1910. } else {
  1911. obj.editField(obj.records[next].recid, column, null, event);
  1912. }
  1913. }, 1);
  1914. }
  1915. break;
  1916. case 40: // down arrow
  1917. if (!event.shiftKey) break;
  1918. cancel = true;
  1919. var next = obj.nextRow(index);
  1920. if (next != null && next != index) {
  1921. this.blur();
  1922. setTimeout(function () {
  1923. if (obj.selectType != 'row') {
  1924. obj.selectNone();
  1925. obj.select({ recid: obj.records[next].recid, column: column });
  1926. } else {
  1927. obj.editField(obj.records[next].recid, column, null, event);
  1928. }
  1929. }, 1);
  1930. }
  1931. break;
  1932. case 27: // escape
  1933. var old = obj.parseField(rec, col.field);
  1934. if (rec.changes && typeof rec.changes[col.field] != 'undefined') old = rec.changes[col.field];
  1935. this.value = typeof old != 'undefined' ? old : '';
  1936. this.blur();
  1937. setTimeout(function () { obj.select({ recid: recid, column: column }) }, 1);
  1938. break;
  1939. }
  1940. if (cancel) if (event.preventDefault) event.preventDefault();
  1941. });
  1942. // focus and select
  1943. var tmp = el.find('input').focus();
  1944. if (value != null) {
  1945. // set cursor to the end
  1946. tmp[0].setSelectionRange(tmp.val().length, tmp.val().length);
  1947. } else {
  1948. tmp.select();
  1949. }
  1950. }, 1);
  1951. // event after
  1952. obj.trigger($.extend(eventData, { phase: 'after' }));
  1953. },
  1954. editChange: function (el, index, column, event) {
  1955. // all other fields
  1956. var summary = index < 0;
  1957. index = index < 0 ? -index - 1 : index;
  1958. var records = summary ? this.summary : this.records;
  1959. var rec = records[index];
  1960. var tr = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(rec.recid));
  1961. var col = this.columns[column];
  1962. var new_val = el.value;
  1963. var old_val = this.parseField(rec, col.field);
  1964. var tmp = $(el).data('w2field');
  1965. if (tmp) {
  1966. new_val = tmp.clean(new_val);
  1967. if (tmp.type == 'list' && new_val != '') new_val = $(el).data('selected');
  1968. }
  1969. if (el.type == 'checkbox') new_val = el.checked;
  1970. // change/restore event
  1971. var eventData = {
  1972. phase: 'before', type: 'change', target: this.name, input_id: el.id, recid: rec.recid, index: index, column: column,
  1973. value_new: new_val, value_previous: (rec.changes && rec.changes.hasOwnProperty(col.field) ? rec.changes[col.field]: old_val), value_original: old_val
  1974. };
  1975. while (true) {
  1976. new_val = eventData.value_new;
  1977. if (( typeof old_val == 'undefined' || old_val === null ? '' : String(old_val)) !== String(new_val)) {
  1978. // change event
  1979. eventData = this.trigger($.extend(eventData, { type: 'change', phase: 'before' }));
  1980. if (eventData.isCancelled !== true) {
  1981. if (new_val !== eventData.value_new) {
  1982. // re-evaluate the type of change to be made
  1983. continue;
  1984. }
  1985. // default action
  1986. rec.changes = rec.changes || {};
  1987. rec.changes[col.field] = eventData.value_new;
  1988. // event after
  1989. this.trigger($.extend(eventData, { phase: 'after' }));
  1990. }
  1991. } else {
  1992. // restore event
  1993. eventData = this.trigger($.extend(eventData, { type: 'restore', phase: 'before' }));
  1994. if (eventData.isCancelled !== true) {
  1995. if (new_val !== eventData.value_new) {
  1996. // re-evaluate the type of change to be made
  1997. continue;
  1998. }
  1999. // default action
  2000. if (rec.changes) delete rec.changes[col.field];
  2001. if ($.isEmptyObject(rec.changes)) delete rec.changes;
  2002. // event after
  2003. this.trigger($.extend(eventData, { phase: 'after' }));
  2004. }
  2005. }
  2006. break;
  2007. }
  2008. // refresh cell
  2009. var cell = this.getCellHTML(index, column, summary);
  2010. if (!summary) {
  2011. if (rec.changes && typeof rec.changes[col.field] != 'undefined') {
  2012. $(tr).find('[col='+ column +']').addClass('w2ui-changed').html(cell);
  2013. } else {
  2014. $(tr).find('[col='+ column +']').removeClass('w2ui-changed').html(cell);
  2015. }
  2016. }
  2017. },
  2018. "delete": function (force) {
  2019. var obj = this;
  2020. // event before
  2021. var eventData = this.trigger({ phase: 'before', target: this.name, type: 'delete', force: force });
  2022. if (eventData.isCancelled === true) return;
  2023. force = eventData.force;
  2024. // default action
  2025. var recs = this.getSelection();
  2026. if (recs.length == 0) return;
  2027. if (this.msgDelete != '' && !force) {
  2028. w2confirm({
  2029. title : w2utils.lang('Delete Confirmation'),
  2030. msg : obj.msgDelete,
  2031. btn_yes : { "class": 'btn-red' },
  2032. callBack: function (result) {
  2033. if (result == 'Yes') w2ui[obj.name].delete(true);
  2034. }
  2035. });
  2036. return;
  2037. }
  2038. // call delete script
  2039. var url = (typeof this.url != 'object' ? this.url : this.url.remove);
  2040. if (url) {
  2041. this.request('delete-records');
  2042. } else {
  2043. this.selectNone();
  2044. if (typeof recs[0] != 'object') {
  2045. this.remove.apply(this, recs);
  2046. } else {
  2047. // clear cells
  2048. for (var r in recs) {
  2049. var fld = this.columns[recs[r].column].field;
  2050. var ind = this.get(recs[r].recid, true);
  2051. if (ind != null && fld != 'recid') {
  2052. this.records[ind][fld] = '';
  2053. if (this.records[ind].changes) delete this.records[ind].changes[fld];
  2054. }
  2055. }
  2056. this.refresh();
  2057. }
  2058. }
  2059. // event after
  2060. this.trigger($.extend(eventData, { phase: 'after' }));
  2061. },
  2062. click: function (recid, event) {
  2063. var time = (new Date()).getTime();
  2064. var column = null;
  2065. if (this.last.cancelClick == true || (event && event.altKey)) return;
  2066. if (typeof recid == 'object') {
  2067. column = recid.column;
  2068. recid = recid.recid;
  2069. }
  2070. if (typeof event == 'undefined') event = {};
  2071. // check for double click
  2072. if (time - parseInt(this.last.click_time) < 350 && event.type == 'click') {
  2073. this.dblClick(recid, event);
  2074. return;
  2075. }
  2076. this.last.click_time = time;
  2077. // column user clicked on
  2078. if (column == null && event.target) {
  2079. var tmp = event.target;
  2080. if (tmp.tagName != 'TD') tmp = $(tmp).parents('td')[0];
  2081. if (typeof $(tmp).attr('col') != 'undefined') column = parseInt($(tmp).attr('col'));
  2082. }
  2083. // event before
  2084. var eventData = this.trigger({ phase: 'before', target: this.name, type: 'click', recid: recid, column: column, originalEvent: event });
  2085. if (eventData.isCancelled === true) return;
  2086. // if it is subgrid unselect top grid
  2087. var parent = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)).parents('tr');
  2088. if (parent.length > 0 && String(parent.attr('id')).indexOf('expanded_row') != -1) {
  2089. var grid = parent.parents('.w2ui-grid').attr('name');
  2090. w2ui[grid].selectNone();
  2091. // all subgrids
  2092. parent.parents('.w2ui-grid').find('.w2ui-expanded-row .w2ui-grid').each(function (index, el) {
  2093. var grid = $(el).attr('name');
  2094. if (w2ui[grid]) w2ui[grid].selectNone();
  2095. });
  2096. }
  2097. // unselect all subgrids
  2098. $(this.box).find('.w2ui-expanded-row .w2ui-grid').each(function (index, el) {
  2099. var grid = $(el).attr('name');
  2100. if (w2ui[grid]) w2ui[grid].selectNone();
  2101. });
  2102. // default action
  2103. var obj = this;
  2104. var sel = this.getSelection();
  2105. $('#grid_'+ this.name +'_check_all').prop("checked", false);
  2106. var ind = this.get(recid, true);
  2107. var record = this.records[ind];
  2108. var selectColumns = [];
  2109. obj.last.sel_ind = ind;
  2110. obj.last.sel_col = column;
  2111. obj.last.sel_recid = recid;
  2112. obj.last.sel_type = 'click';
  2113. // multi select with shif key
  2114. if (event.shiftKey && sel.length > 0 && obj.multiSelect) {
  2115. if (sel[0].recid) {
  2116. var start = this.get(sel[0].recid, true);
  2117. var end = this.get(recid, true);
  2118. if (column > sel[0].column) {
  2119. var t1 = sel[0].column;
  2120. var t2 = column;
  2121. } else {
  2122. var t1 = column;
  2123. var t2 = sel[0].column;
  2124. }
  2125. for (var c = t1; c <= t2; c++) selectColumns.push(c);
  2126. } else {
  2127. var start = this.get(sel[0], true);
  2128. var end = this.get(recid, true);
  2129. }
  2130. var sel_add = []
  2131. if (start > end) { var tmp = start; start = end; end = tmp; }
  2132. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  2133. for (var i = start; i <= end; i++) {
  2134. if (this.searchData.length > 0 && !url && $.inArray(i, this.last.searchIds) == -1) continue;
  2135. if (this.selectType == 'row') {
  2136. sel_add.push(this.records[i].recid);
  2137. } else {
  2138. for (var sc in selectColumns) sel_add.push({ recid: this.records[i].recid, column: selectColumns[sc] });
  2139. }
  2140. //sel.push(this.records[i].recid);
  2141. }
  2142. this.select.apply(this, sel_add);
  2143. } else {
  2144. var last = this.last.selection;
  2145. var flag = (last.indexes.indexOf(ind) != -1 ? true : false);
  2146. // clear other if necessary
  2147. if (((!event.ctrlKey && !event.shiftKey && !event.metaKey) || !this.multiSelect) && !this.showSelectColumn) {
  2148. if (this.selectType != 'row' && $.inArray(column, last.columns[ind]) == -1) flag = false;
  2149. if (sel.length > 300) this.selectNone(); else this.unselect.apply(this, sel);
  2150. if (flag === true) {
  2151. this.unselect({ recid: recid, column: column });
  2152. } else {
  2153. this.select({ recid: recid, column: column });
  2154. }
  2155. } else {
  2156. if (this.selectType != 'row' && $.inArray(column, last.columns[ind]) == -1) flag = false;
  2157. if (flag === true) {
  2158. this.unselect({ recid: recid, column: column });
  2159. } else {
  2160. this.select({ recid: recid, column: column });
  2161. }
  2162. }
  2163. }
  2164. this.status();
  2165. obj.initResize();
  2166. // event after
  2167. this.trigger($.extend(eventData, { phase: 'after' }));
  2168. },
  2169. columnClick: function (field, event) {
  2170. // event before
  2171. var eventData = this.trigger({ phase: 'before', type: 'columnClick', target: this.name, field: field, originalEvent: event });
  2172. if (eventData.isCancelled === true) return;
  2173. // default behaviour
  2174. var column = this.getColumn(field);
  2175. if (column.sortable) this.sort(field, null, (event && (event.ctrlKey || event.metaKey) ? true : false) );
  2176. // event after
  2177. this.trigger($.extend(eventData, { phase: 'after' }));
  2178. },
  2179. keydown: function (event) {
  2180. // this method is called from w2utils
  2181. var obj = this;
  2182. if (obj.keyboard !== true) return;
  2183. // trigger event
  2184. var eventData = obj.trigger({ phase: 'before', type: 'keydown', target: obj.name, originalEvent: event });
  2185. if (eventData.isCancelled === true) return;
  2186. // default behavior
  2187. var empty = false;
  2188. var records = $('#grid_'+ obj.name +'_records');
  2189. var sel = obj.getSelection();
  2190. if (sel.length == 0) empty = true;
  2191. var recid = sel[0] || null;
  2192. var columns = [];
  2193. var recid2 = sel[sel.length-1];
  2194. if (typeof recid == 'object' && recid != null) {
  2195. recid = sel[0].recid;
  2196. columns = [];
  2197. var ii = 0;
  2198. while (true) {
  2199. if (!sel[ii] || sel[ii].recid != recid) break;
  2200. columns.push(sel[ii].column);
  2201. ii++;
  2202. }
  2203. recid2 = sel[sel.length-1].recid;
  2204. }
  2205. var ind = obj.get(recid, true);
  2206. var ind2 = obj.get(recid2, true);
  2207. var rec = obj.get(recid);
  2208. var recEL = $('#grid_'+ obj.name +'_rec_'+ (ind !== null ? w2utils.escapeId(obj.records[ind].recid) : 'none'));
  2209. var cancel = false;
  2210. var key = event.keyCode;
  2211. var shiftKey= event.shiftKey;
  2212. if (key == 9) { // tab key
  2213. if (event.shiftKey) key = 37; else key = 39; // replace with arrows
  2214. shiftKey = false;
  2215. cancel = true;
  2216. }
  2217. switch (key) {
  2218. case 8: // backspace
  2219. case 46: // delete
  2220. if (this.show.toolbarDelete) obj["delete"]();
  2221. cancel = true;
  2222. event.stopPropagation();
  2223. break;
  2224. case 27: // escape
  2225. obj.selectNone();
  2226. if (sel.length > 0 && typeof sel[0] == 'object') {
  2227. obj.select({ recid: sel[0].recid, column: sel[0].column });
  2228. }
  2229. cancel = true;
  2230. break;
  2231. case 65: // cmd + A
  2232. if (!event.metaKey && !event.ctrlKey) break;
  2233. obj.selectAll();
  2234. cancel = true;
  2235. break;
  2236. case 70: // cmd + F
  2237. if (!event.metaKey && !event.ctrlKey) break;
  2238. $('#grid_'+ obj.name + '_search_all').focus();
  2239. cancel = true;
  2240. break;
  2241. case 13: // enter
  2242. // if expandable columns - expand it
  2243. if (this.selectType == 'row' && obj.show.expandColumn === true) {
  2244. if (recEL.length <= 0) break;
  2245. obj.toggle(recid, event);
  2246. cancel = true;
  2247. } else { // or enter edit
  2248. for (var c in this.columns) {
  2249. if (this.columns[c].editable) {
  2250. columns.push(parseInt(c));
  2251. break;
  2252. }
  2253. }
  2254. // edit last column that was edited
  2255. if (this.selectType == 'row' && this.last.edit_col) columns = [this.last.edit_col];
  2256. if (columns.length > 0) {
  2257. obj.editField(recid, columns[0], null, event);
  2258. cancel = true;
  2259. }
  2260. }
  2261. break;
  2262. case 37: // left
  2263. if (empty) break;
  2264. // check if this is subgrid
  2265. var parent = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(obj.records[ind].recid)).parents('tr');
  2266. if (parent.length > 0 && String(parent.attr('id')).indexOf('expanded_row') != -1) {
  2267. var recid = parent.prev().attr('recid');
  2268. var grid = parent.parents('.w2ui-grid').attr('name');
  2269. obj.selectNone();
  2270. w2utils.keyboard.active(grid);
  2271. w2ui[grid].set(recid, { expanded: false });
  2272. w2ui[grid].collapse(recid);
  2273. w2ui[grid].click(recid);
  2274. cancel = true;
  2275. break;
  2276. }
  2277. if (this.selectType == 'row') {
  2278. if (recEL.length <= 0 || rec.expanded !== true ) break;
  2279. obj.set(recid, { expanded: false }, true);
  2280. obj.collapse(recid, event);
  2281. } else {
  2282. var prev = obj.prevCell(columns[0]);
  2283. if (prev != null) {
  2284. if (shiftKey && obj.multiSelect) {
  2285. if (tmpUnselect()) return;
  2286. var tmp = [];
  2287. var newSel = [];
  2288. var unSel = [];
  2289. if (columns.indexOf(this.last.sel_col) == 0 && columns.length > 1) {
  2290. for (var i in sel) {
  2291. if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid);
  2292. unSel.push({ recid: sel[i].recid, column: columns[columns.length-1] });
  2293. }
  2294. } else {
  2295. for (var i in sel) {
  2296. if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid);
  2297. newSel.push({ recid: sel[i].recid, column: prev });
  2298. }
  2299. }
  2300. obj.unselect.apply(obj, unSel);
  2301. obj.select.apply(obj, newSel);
  2302. } else {
  2303. event.shiftKey = false;
  2304. obj.click({ recid: recid, column: prev }, event);
  2305. }
  2306. } else {
  2307. // if selected more then one, then select first
  2308. if (!shiftKey) {
  2309. for (var s=1; s<sel.length; s++) obj.unselect(sel[s]);
  2310. }
  2311. }
  2312. }
  2313. cancel = true;
  2314. break;
  2315. case 39: // right
  2316. if (empty) break;
  2317. if (this.selectType == 'row') {
  2318. if (recEL.length <= 0 || rec.expanded === true || obj.show.expandColumn !== true) break;
  2319. obj.expand(recid, event);
  2320. } else {
  2321. var next = obj.nextCell(columns[columns.length-1]);
  2322. if (next !== null) {
  2323. if (shiftKey && key == 39 && obj.multiSelect) {
  2324. if (tmpUnselect()) return;
  2325. var tmp = [];
  2326. var newSel = [];
  2327. var unSel = [];
  2328. if (columns.indexOf(this.last.sel_col) == columns.length-1 && columns.length > 1) {
  2329. for (var i in sel) {
  2330. if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid);
  2331. unSel.push({ recid: sel[i].recid, column: columns[0] });
  2332. }
  2333. } else {
  2334. for (var i in sel) {
  2335. if (tmp.indexOf(sel[i].recid) == -1) tmp.push(sel[i].recid);
  2336. newSel.push({ recid: sel[i].recid, column: next });
  2337. }
  2338. }
  2339. obj.unselect.apply(obj, unSel);
  2340. obj.select.apply(obj, newSel);
  2341. } else {
  2342. obj.click({ recid: recid, column: next }, event);
  2343. }
  2344. } else {
  2345. // if selected more then one, then select first
  2346. if (!shiftKey) {
  2347. for (var s=0; s<sel.length-1; s++) obj.unselect(sel[s]);
  2348. }
  2349. }
  2350. }
  2351. cancel = true;
  2352. break;
  2353. case 38: // up
  2354. if (empty) selectTopRecord();
  2355. if (recEL.length <= 0) break;
  2356. // move to the previous record
  2357. var prev = obj.prevRow(ind);
  2358. if (prev != null) {
  2359. // jump into subgrid
  2360. if (obj.records[prev].expanded) {
  2361. var subgrid = $('#grid_'+ obj.name +'_rec_'+ w2utils.escapeId(obj.records[prev].recid) +'_expanded_row').find('.w2ui-grid');
  2362. if (subgrid.length > 0 && w2ui[subgrid.attr('name')]) {
  2363. obj.selectNone();
  2364. var grid = subgrid.attr('name');
  2365. var recs = w2ui[grid].records;
  2366. w2utils.keyboard.active(grid);
  2367. w2ui[grid].click(recs[recs.length-1].recid);
  2368. cancel = true;
  2369. break;
  2370. }
  2371. }
  2372. if (shiftKey && obj.multiSelect) { // expand selection
  2373. if (tmpUnselect()) return;
  2374. if (obj.selectType == 'row') {
  2375. if (obj.last.sel_ind > prev && obj.last.sel_ind != ind2) {
  2376. obj.unselect(obj.records[ind2].recid);
  2377. } else {
  2378. obj.select(obj.records[prev].recid);
  2379. }
  2380. } else {
  2381. if (obj.last.sel_ind > prev && obj.last.sel_ind != ind2) {
  2382. prev = ind2;
  2383. var tmp = [];
  2384. for (var c in columns) tmp.push({ recid: obj.records[prev].recid, column: columns[c] });
  2385. obj.unselect.apply(obj, tmp);
  2386. } else {
  2387. var tmp = [];
  2388. for (var c in columns) tmp.push({ recid: obj.records[prev].recid, column: columns[c] });
  2389. obj.select.apply(obj, tmp);
  2390. }
  2391. }
  2392. } else { // move selected record
  2393. obj.selectNone();
  2394. obj.click({ recid: obj.records[prev].recid, column: columns[0] }, event);
  2395. }
  2396. obj.scrollIntoView(prev);
  2397. if (event.preventDefault) event.preventDefault();
  2398. } else {
  2399. // if selected more then one, then select first
  2400. if (!shiftKey) {
  2401. for (var s=1; s<sel.length; s++) obj.unselect(sel[s]);
  2402. }
  2403. // jump out of subgird (if first record)
  2404. var parent = $('#grid_'+ obj.name +'_rec_'+ w2utils.escapeId(obj.records[ind].recid)).parents('tr');
  2405. if (parent.length > 0 && String(parent.attr('id')).indexOf('expanded_row') != -1) {
  2406. var recid = parent.prev().attr('recid');
  2407. var grid = parent.parents('.w2ui-grid').attr('name');
  2408. obj.selectNone();
  2409. w2utils.keyboard.active(grid);
  2410. w2ui[grid].click(recid);
  2411. cancel = true;
  2412. break;
  2413. }
  2414. }
  2415. break;
  2416. case 40: // down
  2417. if (empty) selectTopRecord();
  2418. if (recEL.length <= 0) break;
  2419. // jump into subgrid
  2420. if (obj.records[ind2].expanded) {
  2421. var subgrid = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(obj.records[ind2].recid) +'_expanded_row').find('.w2ui-grid');
  2422. if (subgrid.length > 0 && w2ui[subgrid.attr('name')]) {
  2423. obj.selectNone();
  2424. var grid = subgrid.attr('name');
  2425. var recs = w2ui[grid].records;
  2426. w2utils.keyboard.active(grid);
  2427. w2ui[grid].click(recs[0].recid);
  2428. cancel = true;
  2429. break;
  2430. }
  2431. }
  2432. // move to the next record
  2433. var next = obj.nextRow(ind2);
  2434. if (next != null) {
  2435. if (shiftKey && obj.multiSelect) { // expand selection
  2436. if (tmpUnselect()) return;
  2437. if (obj.selectType == 'row') {
  2438. if (this.last.sel_ind < next && this.last.sel_ind != ind) {
  2439. obj.unselect(obj.records[ind].recid);
  2440. } else {
  2441. obj.select(obj.records[next].recid);
  2442. }
  2443. } else {
  2444. if (this.last.sel_ind < next && this.last.sel_ind != ind) {
  2445. next = ind;
  2446. var tmp = [];
  2447. for (var c in columns) tmp.push({ recid: obj.records[next].recid, column: columns[c] });
  2448. obj.unselect.apply(obj, tmp);
  2449. } else {
  2450. var tmp = [];
  2451. for (var c in columns) tmp.push({ recid: obj.records[next].recid, column: columns[c] });
  2452. obj.select.apply(obj, tmp);
  2453. }
  2454. }
  2455. } else { // move selected record
  2456. obj.selectNone();
  2457. obj.click({ recid: obj.records[next].recid, column: columns[0] }, event);
  2458. }
  2459. obj.scrollIntoView(next);
  2460. cancel = true;
  2461. } else {
  2462. // if selected more then one, then select first
  2463. if (!shiftKey) {
  2464. for (var s=0; s<sel.length-1; s++) obj.unselect(sel[s]);
  2465. }
  2466. // jump out of subgrid (if last record in subgrid)
  2467. var parent = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(obj.records[ind2].recid)).parents('tr');
  2468. if (parent.length > 0 && String(parent.attr('id')).indexOf('expanded_row') != -1) {
  2469. var recid = parent.next().attr('recid');
  2470. var grid = parent.parents('.w2ui-grid').attr('name');
  2471. obj.selectNone();
  2472. w2utils.keyboard.active(grid);
  2473. w2ui[grid].click(recid);
  2474. cancel = true;
  2475. break;
  2476. }
  2477. }
  2478. break;
  2479. // copy & paste
  2480. case 17: // ctrl key
  2481. case 91: // cmd key
  2482. if (empty) break;
  2483. var text = obj.copy();
  2484. $('body').append('<textarea id="_tmp_copy_data" '+
  2485. ' onpaste="var obj = this; setTimeout(function () { w2ui[\''+ obj.name + '\'].paste(obj.value); }, 1);" '+
  2486. ' onkeydown="w2ui[\''+ obj.name +'\'].keydown(event)"'+
  2487. ' style="position: absolute; top: -100px; height: 1px; width: 1px">'+ text +'</textarea>');
  2488. $('#_tmp_copy_data').focus().select();
  2489. // remove _tmp_copy_data textarea
  2490. $(document).on('keyup', tmp_key_down);
  2491. function tmp_key_down() {
  2492. $('#_tmp_copy_data').remove();
  2493. $(document).off('keyup', tmp_key_down);
  2494. }
  2495. break;
  2496. case 88: // x - cut
  2497. if (empty) break;
  2498. if (event.ctrlKey || event.metaKey) {
  2499. setTimeout(function () { obj["delete"](true); }, 100);
  2500. }
  2501. break;
  2502. }
  2503. var tmp = [187, 189, 32]; // =-spacebar
  2504. for (var i=48; i<=90; i++) tmp.push(i); // 0-9,a-z,A-Z
  2505. if (tmp.indexOf(key) != -1 && !event.ctrlKey && !event.metaKey && !cancel) {
  2506. if (columns.length == 0) columns.push(0);
  2507. var tmp = String.fromCharCode(key);
  2508. if (key == 187) tmp = '=';
  2509. if (key == 189) tmp = '-';
  2510. if (!shiftKey) tmp = tmp.toLowerCase();
  2511. obj.editField(recid, columns[0], tmp, event);
  2512. cancel = true;
  2513. }
  2514. if (cancel) { // cancel default behaviour
  2515. if (event.preventDefault) event.preventDefault();
  2516. }
  2517. // event after
  2518. obj.trigger($.extend(eventData, { phase: 'after' }));
  2519. function selectTopRecord() {
  2520. var ind = Math.floor((records[0].scrollTop + (records.height() / 2.1)) / obj.recordHeight);
  2521. if (!obj.records[ind]) ind = 0;
  2522. obj.select({ recid: obj.records[ind].recid, column: 0});
  2523. }
  2524. function tmpUnselect () {
  2525. if (obj.last.sel_type != 'click') return false;
  2526. if (obj.selectType != 'row') {
  2527. obj.last.sel_type = 'key';
  2528. if (sel.length > 1) {
  2529. for (var s in sel) {
  2530. if (sel[s].recid == obj.last.sel_recid && sel[s].column == obj.last.sel_col) {
  2531. sel.splice(s, 1);
  2532. break;
  2533. }
  2534. }
  2535. obj.unselect.apply(obj, sel);
  2536. return true;
  2537. }
  2538. return false;
  2539. } else {
  2540. obj.last.sel_type = 'key';
  2541. if (sel.length > 1) {
  2542. sel.splice(sel.indexOf(obj.records[obj.last.sel_ind].recid), 1);
  2543. obj.unselect.apply(obj, sel);
  2544. return true;
  2545. }
  2546. return false;
  2547. }
  2548. }
  2549. },
  2550. scrollIntoView: function (ind) {
  2551. var buffered = this.records.length;
  2552. if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length;
  2553. if (typeof ind == 'undefined') {
  2554. var sel = this.getSelection();
  2555. if (sel.length == 0) return;
  2556. ind = this.get(sel[0], true);
  2557. }
  2558. var records = $('#grid_'+ this.name +'_records');
  2559. if (buffered == 0) return;
  2560. // if all records in view
  2561. var len = this.last.searchIds.length;
  2562. if (records.height() > this.recordHeight * (len > 0 ? len : buffered)) return;
  2563. if (len > 0) ind = this.last.searchIds.indexOf(ind); // if seach is applied
  2564. // scroll to correct one
  2565. var t1 = Math.floor(records[0].scrollTop / this.recordHeight);
  2566. var t2 = t1 + Math.floor(records.height() / this.recordHeight);
  2567. if (ind == t1) records.animate({ 'scrollTop': records.scrollTop() - records.height() / 1.3 }, 250, 'linear');
  2568. if (ind == t2) records.animate({ 'scrollTop': records.scrollTop() + records.height() / 1.3 }, 250, 'linear');
  2569. if (ind < t1 || ind > t2) records.animate({ 'scrollTop': (ind - 1) * this.recordHeight });
  2570. },
  2571. dblClick: function (recid, event) {
  2572. //if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection
  2573. // find columns
  2574. var column = null;
  2575. if (typeof recid == 'object') {
  2576. column = recid.column;
  2577. recid = recid.recid;
  2578. }
  2579. if (typeof event == 'undefined') event = {};
  2580. // column user clicked on
  2581. if (column == null && event.target) {
  2582. var tmp = event.target;
  2583. if (tmp.tagName != 'TD') tmp = $(tmp).parents('td')[0];
  2584. column = parseInt($(tmp).attr('col'));
  2585. }
  2586. // event before
  2587. var eventData = this.trigger({ phase: 'before', target: this.name, type: 'dblClick', recid: recid, column: column, originalEvent: event });
  2588. if (eventData.isCancelled === true) return;
  2589. // default action
  2590. this.selectNone();
  2591. var col = this.columns[column];
  2592. if (col && $.isPlainObject(col.editable)) {
  2593. this.editField(recid, column, null, event);
  2594. } else {
  2595. this.select({ recid: recid, column: column });
  2596. }
  2597. // event after
  2598. this.trigger($.extend(eventData, { phase: 'after' }));
  2599. },
  2600. contextMenu: function (recid, event) {
  2601. var obj = this;
  2602. if (obj.last.userSelect == 'text') return;
  2603. if (typeof event == 'undefined') event = { offsetX: 0, offsetY: 0, target: $('#grid_'+ obj.name +'_rec_'+ recid)[0] };
  2604. if (typeof event.offsetX === 'undefined') {
  2605. event.offsetX = event.layerX - event.target.offsetLeft;
  2606. event.offsetY = event.layerY - event.target.offsetTop;
  2607. }
  2608. if (w2utils.isFloat(recid)) recid = parseFloat(recid);
  2609. if (this.getSelection().indexOf(recid) == -1) obj.click(recid);
  2610. // need timeout to allow click to finish first
  2611. setTimeout(function () {
  2612. // event before
  2613. var eventData = obj.trigger({ phase: 'before', type: 'contextMenu', target: obj.name, originalEvent: event, recid: recid });
  2614. if (eventData.isCancelled === true) return;
  2615. // default action
  2616. if (obj.menu.length > 0) {
  2617. $(obj.box).find(event.target)
  2618. .w2menu(obj.menu, {
  2619. left : event.offsetX,
  2620. onSelect: function (event) {
  2621. obj.menuClick(recid, parseInt(event.index), event.originalEvent);
  2622. }
  2623. }
  2624. );
  2625. }
  2626. // event after
  2627. obj.trigger($.extend(eventData, { phase: 'after' }));
  2628. }, 150); // need timer 150 for FF
  2629. // cancel event
  2630. if (event.preventDefault) event.preventDefault();
  2631. },
  2632. menuClick: function (recid, index, event) {
  2633. var obj = this;
  2634. // event before
  2635. var eventData = obj.trigger({ phase: 'before', type: 'menuClick', target: obj.name, originalEvent: event,
  2636. recid: recid, menuIndex: index, menuItem: obj.menu[index] });
  2637. if (eventData.isCancelled === true) return;
  2638. // default action
  2639. // -- empty
  2640. // event after
  2641. obj.trigger($.extend(eventData, { phase: 'after' }));
  2642. },
  2643. toggle: function (recid) {
  2644. var rec = this.get(recid);
  2645. if (rec.expanded === true) return this.collapse(recid); else return this.expand(recid);
  2646. },
  2647. expand: function (recid) {
  2648. var rec = this.get(recid);
  2649. var obj = this;
  2650. var id = w2utils.escapeId(recid);
  2651. if ($('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').length > 0) return false;
  2652. if (rec.expanded == 'none') return false;
  2653. // insert expand row
  2654. var tmp = 1 + (this.show.selectColumn ? 1 : 0);
  2655. var addClass = ''; // ($('#grid_'+this.name +'_rec_'+ w2utils.escapeId(recid)).hasClass('w2ui-odd') ? 'w2ui-odd' : 'w2ui-even');
  2656. $('#grid_'+ this.name +'_rec_'+ id).after(
  2657. '<tr id="grid_'+ this.name +'_rec_'+ id +'_expanded_row" class="w2ui-expanded-row '+ addClass +'">'+
  2658. (this.show.lineNumbers ? '<td class="w2ui-col-number"></td>' : '') +
  2659. ' <td class="w2ui-grid-data w2ui-expanded1" colspan="'+ tmp +'"><div style="display: none"></div></td>'+
  2660. ' <td colspan="100" class="w2ui-expanded2">'+
  2661. ' <div id="grid_'+ this.name +'_rec_'+ id +'_expanded" style="opacity: 0"></div>'+
  2662. ' </td>'+
  2663. '</tr>');
  2664. // event before
  2665. var eventData = this.trigger({ phase: 'before', type: 'expand', target: this.name, recid: recid,
  2666. box_id: 'grid_'+ this.name +'_rec_'+ id +'_expanded', ready: ready });
  2667. if (eventData.isCancelled === true) {
  2668. $('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').remove();
  2669. return;
  2670. }
  2671. // default action
  2672. $('#grid_'+ this.name +'_rec_'+ id).attr('expanded', 'yes').addClass('w2ui-expanded');
  2673. $('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').show();
  2674. $('#grid_'+ this.name +'_cell_'+ this.get(recid, true) +'_expand div').html('<div class="w2ui-spinner" style="width: 16px; height: 16px; margin: -2px 2px;"></div>');
  2675. rec.expanded = true;
  2676. // check if height of expanded row > 5 then remove spinner
  2677. setTimeout(ready, 300);
  2678. function ready() {
  2679. var div1 = $('#grid_'+ obj.name +'_rec_'+ id +'_expanded');
  2680. var div2 = $('#grid_'+ obj.name +'_rec_'+ id +'_expanded_row .w2ui-expanded1 > div');
  2681. if (div1.height() < 5) return;
  2682. div1.css('opacity', 1);
  2683. div2.show().css('opacity', 1);
  2684. $('#grid_'+ obj.name +'_cell_'+ obj.get(recid, true) +'_expand div').html('-');
  2685. }
  2686. // event after
  2687. this.trigger($.extend(eventData, { phase: 'after' }));
  2688. this.resizeRecords();
  2689. return true;
  2690. },
  2691. collapse: function (recid) {
  2692. var rec = this.get(recid);
  2693. var obj = this;
  2694. var id = w2utils.escapeId(recid);
  2695. if ($('#grid_'+ this.name +'_rec_'+ id +'_expanded_row').length == 0) return false;
  2696. // event before
  2697. var eventData = this.trigger({ phase: 'before', type: 'collapse', target: this.name, recid: recid,
  2698. box_id: 'grid_'+ this.name +'_rec_'+ id +'_expanded' });
  2699. if (eventData.isCancelled === true) return;
  2700. // default action
  2701. $('#grid_'+ this.name +'_rec_'+ id).removeAttr('expanded').removeClass('w2ui-expanded');
  2702. $('#grid_'+ this.name +'_rec_'+ id +'_expanded').css('opacity', 0);
  2703. $('#grid_'+ this.name +'_cell_'+ this.get(recid, true) +'_expand div').html('+');
  2704. setTimeout(function () {
  2705. $('#grid_'+ obj.name +'_rec_'+ id +'_expanded').height('0px');
  2706. setTimeout(function () {
  2707. $('#grid_'+ obj.name +'_rec_'+ id +'_expanded_row').remove();
  2708. delete rec.expanded;
  2709. // event after
  2710. obj.trigger($.extend(eventData, { phase: 'after' }));
  2711. obj.resizeRecords();
  2712. }, 300);
  2713. }, 200);
  2714. return true;
  2715. },
  2716. sort: function (field, direction, multiField) { // if no params - clears sort
  2717. // event before
  2718. var eventData = this.trigger({ phase: 'before', type: 'sort', target: this.name, field: field, direction: direction, multiField: multiField });
  2719. if (eventData.isCancelled === true) return;
  2720. // check if needed to quit
  2721. if (typeof field != 'undefined') {
  2722. // default action
  2723. var sortIndex = this.sortData.length;
  2724. for (var s in this.sortData) {
  2725. if (this.sortData[s].field == field) { sortIndex = s; break; }
  2726. }
  2727. if (typeof direction == 'undefined' || direction == null) {
  2728. if (typeof this.sortData[sortIndex] == 'undefined') {
  2729. direction = 'asc';
  2730. } else {
  2731. switch (String(this.sortData[sortIndex].direction)) {
  2732. case 'asc' : direction = 'desc'; break;
  2733. case 'desc' : direction = 'asc'; break;
  2734. default : direction = 'asc'; break;
  2735. }
  2736. }
  2737. }
  2738. if (this.multiSort === false) { this.sortData = []; sortIndex = 0; }
  2739. if (multiField != true) { this.sortData = []; sortIndex = 0; }
  2740. // set new sort
  2741. if (typeof this.sortData[sortIndex] == 'undefined') this.sortData[sortIndex] = {};
  2742. this.sortData[sortIndex].field = field;
  2743. this.sortData[sortIndex].direction = direction;
  2744. } else {
  2745. this.sortData = [];
  2746. }
  2747. this.selectNone();
  2748. // if local
  2749. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  2750. if (!url) {
  2751. this.localSort();
  2752. if (this.searchData.length > 0) this.localSearch(true);
  2753. // event after
  2754. this.trigger($.extend(eventData, { phase: 'after' }));
  2755. this.refresh();
  2756. } else {
  2757. // event after
  2758. this.trigger($.extend(eventData, { phase: 'after' }));
  2759. this.last.xhr_offset = 0;
  2760. this.reload();
  2761. }
  2762. },
  2763. copy: function () {
  2764. var sel = this.getSelection();
  2765. if (sel.length == 0) return '';
  2766. var text = '';
  2767. if (typeof sel[0] == 'object') { // cell copy
  2768. // find min/max column
  2769. var minCol = sel[0].column;
  2770. var maxCol = sel[0].column;
  2771. var recs = [];
  2772. for (var s in sel) {
  2773. if (sel[s].column < minCol) minCol = sel[s].column;
  2774. if (sel[s].column > maxCol) maxCol = sel[s].column;
  2775. if (recs.indexOf(sel[s].index) == -1) recs.push(sel[s].index);
  2776. }
  2777. recs.sort();
  2778. for (var r in recs) {
  2779. var ind = recs[r];
  2780. for (var c = minCol; c <= maxCol; c++) {
  2781. var col = this.columns[c];
  2782. if (col.hidden === true) continue;
  2783. text += w2utils.stripTags(this.getCellHTML(ind, c)) + '\t';
  2784. }
  2785. text = text.substr(0, text.length-1); // remove last \t
  2786. text += '\n';
  2787. }
  2788. } else { // row copy
  2789. // copy headers
  2790. for (var c in this.columns) {
  2791. var col = this.columns[c];
  2792. if (col.hidden === true) continue;
  2793. text += '"' + w2utils.stripTags(col.caption ? col.caption : col.field) + '"\t';
  2794. }
  2795. text = text.substr(0, text.length-1); // remove last \t
  2796. text += '\n';
  2797. // copy selected text
  2798. for (var s in sel) {
  2799. var ind = this.get(sel[s], true);
  2800. for (var c in this.columns) {
  2801. var col = this.columns[c];
  2802. if (col.hidden === true) continue;
  2803. text += '"' + w2utils.stripTags(this.getCellHTML(ind, c)) + '"\t';
  2804. }
  2805. text = text.substr(0, text.length-1); // remove last \t
  2806. text += '\n';
  2807. }
  2808. }
  2809. text = text.substr(0, text.length - 1);
  2810. // before event
  2811. var eventData = this.trigger({ phase: 'before', type: 'copy', target: this.name, text: text });
  2812. if (eventData.isCancelled === true) return '';
  2813. text = eventData.text;
  2814. // event after
  2815. this.trigger($.extend(eventData, { phase: 'after' }));
  2816. return text;
  2817. },
  2818. paste: function (text) {
  2819. var sel = this.getSelection();
  2820. var ind = this.get(sel[0].recid, true);
  2821. var col = sel[0].column;
  2822. // before event
  2823. var eventData = this.trigger({ phase: 'before', type: 'paste', target: this.name, text: text, index: ind, column: col });
  2824. if (eventData.isCancelled === true) return;
  2825. text = eventData.text;
  2826. // default action
  2827. if (this.selectType == 'row' || sel.length == 0) {
  2828. console.log('ERROR: You can paste only if grid.selectType = \'cell\' and when at least one cell selected.');
  2829. // event after
  2830. this.trigger($.extend(eventData, { phase: 'after' }));
  2831. return;
  2832. }
  2833. var newSel = [];
  2834. var text = text.split('\n');
  2835. for (var t in text) {
  2836. var tmp = text[t].split('\t');
  2837. var cnt = 0;
  2838. var rec = this.records[ind];
  2839. var cols = [];
  2840. for (var dt in tmp) {
  2841. if (!this.columns[col + cnt]) continue;
  2842. var field = this.columns[col + cnt].field;
  2843. rec.changes = rec.changes || {};
  2844. rec.changes[field] = tmp[dt];
  2845. cols.push(col + cnt);
  2846. cnt++;
  2847. }
  2848. for (var c in cols) newSel.push({ recid: rec.recid, column: cols[c] });
  2849. ind++;
  2850. }
  2851. this.selectNone();
  2852. this.select.apply(this, newSel);
  2853. this.refresh();
  2854. // event after
  2855. this.trigger($.extend(eventData, { phase: 'after' }));
  2856. },
  2857. // ==================================================
  2858. // --- Common functions
  2859. resize: function () {
  2860. var obj = this;
  2861. var time = (new Date()).getTime();
  2862. //if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection
  2863. // make sure the box is right
  2864. if (!this.box || $(this.box).attr('name') != this.name) return;
  2865. // determine new width and height
  2866. $(this.box).find('> div')
  2867. .css('width', $(this.box).width())
  2868. .css('height', $(this.box).height());
  2869. // event before
  2870. var eventData = this.trigger({ phase: 'before', type: 'resize', target: this.name });
  2871. if (eventData.isCancelled === true) return;
  2872. // resize
  2873. obj.resizeBoxes();
  2874. obj.resizeRecords();
  2875. // event after
  2876. this.trigger($.extend(eventData, { phase: 'after' }));
  2877. return (new Date()).getTime() - time;
  2878. },
  2879. refreshCell: function (recid, field) {
  2880. var index = this.get(recid, true);
  2881. var isSummary = (this.records[index] && this.records[index].recid == recid ? false : true);
  2882. var col_ind = this.getColumn(field, true);
  2883. var rec = (isSummary ? this.summary[index] : this.records[index]);
  2884. var col = this.columns[col_ind];
  2885. var cell = $('#grid_'+ this.name + '_rec_'+ recid +' [col='+ col_ind +']');
  2886. // set cell html and changed flag
  2887. cell.html(this.getCellHTML(index, col_ind, isSummary));
  2888. if (rec.changes && typeof rec.changes[col.field] != 'undefined') {
  2889. cell.addClass('w2ui-changed');
  2890. } else {
  2891. cell.removeClass('w2ui-changed');
  2892. }
  2893. },
  2894. refreshRow: function (recid) {
  2895. var tr = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid));
  2896. if (tr.length != 0) {
  2897. var ind = this.get(recid, true);
  2898. var line = tr.attr('line');
  2899. var isSummary = (this.records[ind] && this.records[ind].recid == recid ? false : true);
  2900. // if it is searched, find index in search array
  2901. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  2902. if (this.searchData.length > 0 && !url) for (var s in this.last.searchIds) if (this.last.searchIds[s] == ind) ind = s;
  2903. $(tr).replaceWith(this.getRecordHTML(ind, line, isSummary));
  2904. if (isSummary) this.resize();
  2905. }
  2906. },
  2907. refresh: function () {
  2908. var obj = this;
  2909. var time = (new Date()).getTime();
  2910. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  2911. if (this.total <= 0 && !url && this.searchData.length == 0) {
  2912. this.total = this.records.length;
  2913. }
  2914. //if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection
  2915. this.toolbar.disable('w2ui-edit', 'w2ui-delete');
  2916. if (!this.box) return;
  2917. // event before
  2918. var eventData = this.trigger({ phase: 'before', target: this.name, type: 'refresh' });
  2919. if (eventData.isCancelled === true) return;
  2920. // -- header
  2921. if (this.show.header) {
  2922. $('#grid_'+ this.name +'_header').html(this.header +'&nbsp;').show();
  2923. } else {
  2924. $('#grid_'+ this.name +'_header').hide();
  2925. }
  2926. // -- toolbar
  2927. if (this.show.toolbar) {
  2928. // if select-collumn is checked - no toolbar refresh
  2929. if (this.toolbar && this.toolbar.get('w2ui-column-on-off') && this.toolbar.get('w2ui-column-on-off').checked) {
  2930. // no action
  2931. } else {
  2932. $('#grid_'+ this.name +'_toolbar').show();
  2933. // refresh toolbar all but search field
  2934. if (typeof this.toolbar == 'object') {
  2935. var tmp = this.toolbar.items;
  2936. for (var t in tmp) {
  2937. if (tmp[t].id == 'w2ui-search' || tmp[t].type == 'break') continue;
  2938. this.toolbar.refresh(tmp[t].id);
  2939. }
  2940. }
  2941. }
  2942. } else {
  2943. $('#grid_'+ this.name +'_toolbar').hide();
  2944. }
  2945. // -- make sure search is closed
  2946. this.searchClose();
  2947. // search placeholder
  2948. var el = $('#grid_'+ obj.name +'_search_all');
  2949. if (!this.multiSearch && this.last.field == 'all' && this.searches.length > 0) {
  2950. this.last.field = this.searches[0].field;
  2951. this.last.caption = this.searches[0].caption;
  2952. }
  2953. for (var s in this.searches) {
  2954. if (this.searches[s].field == this.last.field) this.last.caption = this.searches[s].caption;
  2955. }
  2956. if (this.last.multi) {
  2957. el.attr('placeholder', '[' + w2utils.lang('Multiple Fields') + ']');
  2958. } else {
  2959. el.attr('placeholder', this.last.caption);
  2960. }
  2961. if (el.val() != this.last.search) {
  2962. var val = this.last.search;
  2963. var tmp = el.data('w2field');
  2964. if (tmp) val = tmp.format(val);
  2965. el.val(val);
  2966. }
  2967. // -- separate summary
  2968. var tmp = this.find({ summary: true }, true);
  2969. if (tmp.length > 0) {
  2970. for (var t in tmp) this.summary.push(this.records[tmp[t]]);
  2971. for (var t=tmp.length-1; t>=0; t--) this.records.splice(tmp[t], 1);
  2972. this.total = this.total - tmp.length;
  2973. }
  2974. // -- body
  2975. var bodyHTML = '';
  2976. bodyHTML += '<div id="grid_'+ this.name +'_records" class="w2ui-grid-records"'+
  2977. ' onscroll="var obj = w2ui[\''+ this.name + '\']; '+
  2978. ' obj.last.scrollTop = this.scrollTop; '+
  2979. ' obj.last.scrollLeft = this.scrollLeft; '+
  2980. ' $(\'#grid_'+ this.name +'_columns\')[0].scrollLeft = this.scrollLeft;'+
  2981. ' $(\'#grid_'+ this.name +'_summary\')[0].scrollLeft = this.scrollLeft;'+
  2982. ' obj.scroll(event);">'+
  2983. this.getRecordsHTML() +
  2984. '</div>'+
  2985. '<div id="grid_'+ this.name +'_columns" class="w2ui-grid-columns">'+
  2986. ' <table>'+ this.getColumnsHTML() +'</table>'+
  2987. '</div>'; // Columns need to be after to be able to overlap
  2988. $('#grid_'+ this.name +'_body').html(bodyHTML);
  2989. // show summary records
  2990. if (this.summary.length > 0) {
  2991. $('#grid_'+ this.name +'_summary').html(this.getSummaryHTML()).show();
  2992. } else {
  2993. $('#grid_'+ this.name +'_summary').hide();
  2994. }
  2995. // -- footer
  2996. if (this.show.footer) {
  2997. $('#grid_'+ this.name +'_footer').html(this.getFooterHTML()).show();
  2998. } else {
  2999. $('#grid_'+ this.name +'_footer').hide();
  3000. }
  3001. // show/hide clear search link
  3002. if (this.searchData.length > 0) {
  3003. $('#grid_'+ this.name +'_searchClear').show();
  3004. } else {
  3005. $('#grid_'+ this.name +'_searchClear').hide();
  3006. }
  3007. // all selected?
  3008. var sel = this.last.selection;
  3009. if (sel.indexes.length == this.records.length || (this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length)) {
  3010. $('#grid_'+ this.name +'_check_all').prop('checked', true);
  3011. } else {
  3012. $('#grid_'+ this.name +'_check_all').prop('checked', false);
  3013. }
  3014. // show number of selected
  3015. this.status();
  3016. // collapse all records
  3017. var rows = obj.find({ expanded: true }, true);
  3018. for (var r in rows) obj.records[rows[r]].expanded = false;
  3019. // mark selection
  3020. setTimeout(function () {
  3021. var str = $.trim($('#grid_'+ obj.name +'_search_all').val());
  3022. if (str != '') $(obj.box).find('.w2ui-grid-data > div').w2marker(str);
  3023. }, 50);
  3024. // event after
  3025. this.trigger($.extend(eventData, { phase: 'after' }));
  3026. obj.resize();
  3027. obj.addRange('selection');
  3028. setTimeout(function () { obj.resize(); obj.scroll(); }, 1); // allow to render first
  3029. if ( obj.reorderColumns && !obj.last.columnDrag ) {
  3030. obj.last.columnDrag = obj.initColumnDrag();
  3031. } else if ( !obj.reorderColumns && obj.last.columnDrag ) {
  3032. obj.last.columnDrag.remove();
  3033. }
  3034. return (new Date()).getTime() - time;
  3035. },
  3036. render: function (box) {
  3037. var obj = this;
  3038. var time = (new Date()).getTime();
  3039. //if (window.getSelection) window.getSelection().removeAllRanges(); // clear selection
  3040. if (typeof box != 'undefined' && box != null) {
  3041. if ($(this.box).find('#grid_'+ this.name +'_body').length > 0) {
  3042. $(this.box)
  3043. .removeAttr('name')
  3044. .removeClass('w2ui-reset w2ui-grid')
  3045. .html('');
  3046. }
  3047. this.box = box;
  3048. }
  3049. if (!this.box) return;
  3050. if (this.last.sortData == null) this.last.sortData = this.sortData;
  3051. // event before
  3052. var eventData = this.trigger({ phase: 'before', target: this.name, type: 'render', box: box });
  3053. if (eventData.isCancelled === true) return;
  3054. // insert Elements
  3055. $(this.box)
  3056. .attr('name', this.name)
  3057. .addClass('w2ui-reset w2ui-grid')
  3058. .html('<div>'+
  3059. ' <div id="grid_'+ this.name +'_header" class="w2ui-grid-header"></div>'+
  3060. ' <div id="grid_'+ this.name +'_toolbar" class="w2ui-grid-toolbar"></div>'+
  3061. ' <div id="grid_'+ this.name +'_body" class="w2ui-grid-body"></div>'+
  3062. ' <div id="grid_'+ this.name +'_summary" class="w2ui-grid-body w2ui-grid-summary"></div>'+
  3063. ' <div id="grid_'+ this.name +'_footer" class="w2ui-grid-footer"></div>'+
  3064. '</div>');
  3065. if (this.selectType != 'row') $(this.box).addClass('w2ui-ss');
  3066. if ($(this.box).length > 0) $(this.box)[0].style.cssText += this.style;
  3067. // init toolbar
  3068. this.initToolbar();
  3069. if (this.toolbar != null) this.toolbar.render($('#grid_'+ this.name +'_toolbar')[0]);
  3070. // reinit search_all
  3071. if (this.last.field && this.last.field != 'all') {
  3072. var sd = this.searchData;
  3073. this.initAllField(this.last.field, (sd.length == 1 ? sd[0].value : null));
  3074. }
  3075. // init footer
  3076. $('#grid_'+ this.name +'_footer').html(this.getFooterHTML());
  3077. // refresh
  3078. if (!this.last.state) this.last.state = this.stateSave(true); // initial default state
  3079. this.stateRestore();
  3080. if (this.url) this.refresh(); // show empty grid (need it) - should it be only for remote data source
  3081. this.reload();
  3082. // init mouse events for mouse selection
  3083. $(this.box).on('mousedown', mouseStart);
  3084. $(this.box).on('selectstart', function () { return false; }); // fixes chrome cursor bug
  3085. // event after
  3086. this.trigger($.extend(eventData, { phase: 'after' }));
  3087. // attach to resize event
  3088. if ($('.w2ui-layout').length == 0) { // if there is layout, it will send a resize event
  3089. this.tmp_resize = function (event) { w2ui[obj.name].resize(); }
  3090. $(window).off('resize', this.tmp_resize).on('resize', this.tmp_resize);
  3091. }
  3092. return (new Date()).getTime() - time;
  3093. function mouseStart (event) {
  3094. if (event.which != 1) return; // if not left mouse button
  3095. // restore css user-select
  3096. if (obj.last.userSelect == 'text') {
  3097. delete obj.last.userSelect;
  3098. $(obj.box).find('.w2ui-grid-body')
  3099. .css('user-select', 'none')
  3100. .css('-webkit-user-select', 'none')
  3101. .css('-moz-user-select', 'none')
  3102. .css('-ms-user-select', 'none');
  3103. $(this.box).on('selectstart', function () { return false; });
  3104. }
  3105. // regular record select
  3106. if ($(event.target).parents().hasClass('w2ui-head') || $(event.target).hasClass('w2ui-head')) return;
  3107. if (obj.last.move && obj.last.move.type == 'expand') return;
  3108. // if altKey - alow text selection
  3109. if (event.altKey) {
  3110. $(obj.box).off('selectstart');
  3111. $(obj.box).find('.w2ui-grid-body')
  3112. .css('user-select', 'text')
  3113. .css('-webkit-user-select', 'text')
  3114. .css('-moz-user-select', 'text')
  3115. .css('-ms-user-select', 'text');
  3116. obj.selectNone();
  3117. obj.last.move = { type: 'text-select' };
  3118. obj.last.userSelect = 'text';
  3119. } else {
  3120. if (!obj.multiSelect) return;
  3121. obj.last.move = {
  3122. x : event.screenX,
  3123. y : event.screenY,
  3124. divX : 0,
  3125. divY : 0,
  3126. recid : $(event.target).parents('tr').attr('recid'),
  3127. column : (event.target.tagName == 'TD' ? $(event.target).attr('col') : $(event.target).parents('td').attr('col')),
  3128. type : 'select',
  3129. ghost : false,
  3130. start : true
  3131. };
  3132. }
  3133. $(document).on('mousemove', mouseMove);
  3134. $(document).on('mouseup', mouseStop);
  3135. }
  3136. function mouseMove (event) {
  3137. var mv = obj.last.move;
  3138. if (!mv || mv.type != 'select') return;
  3139. mv.divX = (event.screenX - mv.x);
  3140. mv.divY = (event.screenY - mv.y);
  3141. if (Math.abs(mv.divX) <= 1 && Math.abs(mv.divY) <= 1) return; // only if moved more then 1px
  3142. obj.last.cancelClick = true;
  3143. if (obj.reorderRows == true) {
  3144. if (!mv.ghost) {
  3145. var row = $('#grid_'+ obj.name + '_rec_'+ mv.recid);
  3146. var tmp = row.parents('table').find('tr:first-child').clone();
  3147. mv.offsetY = event.offsetY;
  3148. mv.from = mv.recid;
  3149. mv.pos = row.position();
  3150. mv.ghost = $(row).clone(true);
  3151. mv.ghost.removeAttr('id');
  3152. row.find('td:first-child').replaceWith('<td colspan="1000" style="height: '+ obj.recordHeight +'px; background-color: #ddd"></td>');
  3153. var recs = $(obj.box).find('.w2ui-grid-records');
  3154. recs.append('<table id="grid_'+ obj.name + '_ghost" style="position: absolute; z-index: 999999; opacity: 0.8; border-bottom: 2px dashed #aaa; border-top: 2px dashed #aaa; pointer-events: none;"></table>');
  3155. $('#grid_'+ obj.name + '_ghost').append(tmp).append(mv.ghost);
  3156. }
  3157. var recid = $(event.target).parents('tr').attr('recid');
  3158. if (recid != mv.from) {
  3159. var row1 = $('#grid_'+ obj.name + '_rec_'+ mv.recid);
  3160. var row2 = $('#grid_'+ obj.name + '_rec_'+ recid);
  3161. if (event.screenY - mv.lastY < 0) row1.after(row2); else row2.after(row1);
  3162. mv.lastY = event.screenY;
  3163. mv.to = recid;
  3164. }
  3165. var ghost = $('#grid_'+ obj.name + '_ghost');
  3166. var recs = $(obj.box).find('.w2ui-grid-records');
  3167. ghost.css({
  3168. top : mv.pos.top + mv.divY + recs.scrollTop(), // + mv.offsetY - obj.recordHeight / 2,
  3169. left : mv.pos.left
  3170. });
  3171. return;
  3172. }
  3173. if (mv.start && mv.recid) {
  3174. obj.selectNone();
  3175. mv.start = false;
  3176. }
  3177. var newSel= [];
  3178. var recid = (event.target.tagName == 'TR' ? $(event.target).attr('recid') : $(event.target).parents('tr').attr('recid'));
  3179. if (typeof recid == 'undefined') return;
  3180. var ind1 = obj.get(mv.recid, true);
  3181. // |:wolfmanx:| this happens when selection is started on summary row
  3182. if (ind1 === null) return;
  3183. var ind2 = obj.get(recid, true);
  3184. // this happens when selection is extended into summary row (a good place to implement scrolling)
  3185. if (ind2 === null) return;
  3186. var col1 = parseInt(mv.column);
  3187. var col2 = parseInt(event.target.tagName == 'TD' ? $(event.target).attr('col') : $(event.target).parents('td').attr('col'));
  3188. if (ind1 > ind2) { var tmp = ind1; ind1 = ind2; ind2 = tmp; }
  3189. // check if need to refresh
  3190. var tmp = 'ind1:'+ ind1 +',ind2;'+ ind2 +',col1:'+ col1 +',col2:'+ col2;
  3191. if (mv.range == tmp) return;
  3192. mv.range = tmp;
  3193. for (var i = ind1; i <= ind2; i++) {
  3194. if (obj.last.searchIds.length > 0 && obj.last.searchIds.indexOf(i) == -1) continue;
  3195. if (obj.selectType != 'row') {
  3196. if (col1 > col2) { var tmp = col1; col1 = col2; col2 = tmp; }
  3197. var tmp = [];
  3198. for (var c = col1; c <= col2; c++) {
  3199. if (obj.columns[c].hidden) continue;
  3200. newSel.push({ recid: obj.records[i].recid, column: parseInt(c) });
  3201. }
  3202. } else {
  3203. newSel.push(obj.records[i].recid);
  3204. }
  3205. }
  3206. if (obj.selectType != 'row') {
  3207. var sel = obj.getSelection();
  3208. // add more items
  3209. var tmp = [];
  3210. for (var ns in newSel) {
  3211. var flag = false;
  3212. for (var s in sel) if (newSel[ns].recid == sel[s].recid && newSel[ns].column == sel[s].column) flag = true;
  3213. if (!flag) tmp.push({ recid: newSel[ns].recid, column: newSel[ns].column });
  3214. }
  3215. obj.select.apply(obj, tmp);
  3216. // remove items
  3217. var tmp = [];
  3218. for (var s in sel) {
  3219. var flag = false;
  3220. for (var ns in newSel) if (newSel[ns].recid == sel[s].recid && newSel[ns].column == sel[s].column) flag = true;
  3221. if (!flag) tmp.push({ recid: sel[s].recid, column: sel[s].column });
  3222. }
  3223. obj.unselect.apply(obj, tmp);
  3224. } else {
  3225. if (obj.multiSelect) {
  3226. var sel = obj.getSelection();
  3227. for (var ns in newSel) if (sel.indexOf(newSel[ns]) == -1) obj.select(newSel[ns]); // add more items
  3228. for (var s in sel) if (newSel.indexOf(sel[s]) == -1) obj.unselect(sel[s]); // remove items
  3229. }
  3230. }
  3231. }
  3232. function mouseStop (event) {
  3233. var mv = obj.last.move;
  3234. setTimeout(function () { delete obj.last.cancelClick; }, 1);
  3235. if ($(event.target).parents().hasClass('.w2ui-head') || $(event.target).hasClass('.w2ui-head')) return;
  3236. if (mv && mv.type == 'select') {
  3237. if (obj.reorderRows == true) {
  3238. var ind1 = obj.get(mv.from, true);
  3239. var tmp = obj.records[ind1];
  3240. obj.records.splice(ind1, 1);
  3241. var ind2 = obj.get(mv.to, true);
  3242. if (ind1 > ind2) obj.records.splice(ind2, 0, tmp); else obj.records.splice(ind2+1, 0, tmp);
  3243. $('#grid_'+ obj.name + '_ghost').remove();
  3244. obj.refresh();
  3245. }
  3246. }
  3247. delete obj.last.move;
  3248. $(document).off('mousemove', mouseMove);
  3249. $(document).off('mouseup', mouseStop);
  3250. }
  3251. },
  3252. destroy: function () {
  3253. // event before
  3254. var eventData = this.trigger({ phase: 'before', target: this.name, type: 'destroy' });
  3255. if (eventData.isCancelled === true) return;
  3256. // remove events
  3257. $(window).off('resize', this.tmp_resize);
  3258. // clean up
  3259. if (typeof this.toolbar == 'object' && this.toolbar.destroy) this.toolbar.destroy();
  3260. if ($(this.box).find('#grid_'+ this.name +'_body').length > 0) {
  3261. $(this.box)
  3262. .removeAttr('name')
  3263. .removeClass('w2ui-reset w2ui-grid')
  3264. .html('');
  3265. }
  3266. delete w2ui[this.name];
  3267. // event after
  3268. this.trigger($.extend(eventData, { phase: 'after' }));
  3269. },
  3270. // ===========================================
  3271. // --- Internal Functions
  3272. initColumnOnOff: function () {
  3273. if (!this.show.toolbarColumns) return;
  3274. var obj = this;
  3275. var col_html = '<div class="w2ui-col-on-off">'+
  3276. '<table><tr>'+
  3277. '<td style="width: 30px">'+
  3278. ' <input id="grid_'+ this.name +'_column_ln_check" type="checkbox" tabIndex="-1" '+ (obj.show.lineNumbers ? 'checked' : '') +
  3279. ' onclick="w2ui[\''+ obj.name +'\'].columnOnOff(this, event, \'line-numbers\');">'+
  3280. '</td>'+
  3281. '<td onclick="w2ui[\''+ obj.name +'\'].columnOnOff(this, event, \'line-numbers\'); $(\'#w2ui-overlay\')[0].hide();">'+
  3282. ' <label for="grid_'+ this.name +'_column_ln_check">'+ w2utils.lang('Line #') +'</label>'+
  3283. '</td></tr>';
  3284. for (var c in this.columns) {
  3285. var col = this.columns[c];
  3286. var tmp = this.columns[c].caption;
  3287. if (col.hideable === false) continue;
  3288. if (!tmp && this.columns[c].hint) tmp = this.columns[c].hint;
  3289. if (!tmp) tmp = '- column '+ (parseInt(c) + 1) +' -';
  3290. col_html += '<tr>'+
  3291. '<td style="width: 30px">'+
  3292. ' <input id="grid_'+ this.name +'_column_'+ c +'_check" type="checkbox" tabIndex="-1" '+ (col.hidden ? '' : 'checked') +
  3293. ' onclick="w2ui[\''+ obj.name +'\'].columnOnOff(this, event, \''+ col.field +'\');">'+
  3294. '</td>'+
  3295. '<td>'+
  3296. ' <label for="grid_'+ this.name +'_column_'+ c +'_check">'+ tmp + '</label>'+
  3297. '</td>'+
  3298. '</tr>';
  3299. }
  3300. col_html += '<tr><td colspan="2"><div style="border-top: 1px solid #ddd;"></div></td></tr>';
  3301. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  3302. if (url && obj.show.skipRecords) {
  3303. col_html +=
  3304. '<tr><td colspan="2" style="padding: 0px">'+
  3305. ' <div style="cursor: pointer; padding: 2px 8px; cursor: default">'+ w2utils.lang('Skip') +
  3306. ' <input type="text" style="width: 45px" value="'+ this.offset +'" '+
  3307. ' onkeypress="if (event.keyCode == 13) { '+
  3308. ' w2ui[\''+ obj.name +'\'].skip(this.value); '+
  3309. ' $(\'#w2ui-overlay\')[0].hide(); '+
  3310. ' }"> '+ w2utils.lang('Records')+
  3311. ' </div>'+
  3312. '</td></tr>';
  3313. }
  3314. col_html += '<tr><td colspan="2" onclick="w2ui[\''+ obj.name +'\'].stateSave(); $(\'#w2ui-overlay\')[0].hide();">'+
  3315. ' <div style="cursor: pointer; padding: 4px 8px; cursor: default">'+ w2utils.lang('Save Grid State') + '</div>'+
  3316. '</td></tr>'+
  3317. '<tr><td colspan="2" onclick="w2ui[\''+ obj.name +'\'].stateReset(); $(\'#w2ui-overlay\')[0].hide();">'+
  3318. ' <div style="cursor: pointer; padding: 4px 8px; cursor: default">'+ w2utils.lang('Restore Default State') + '</div>'+
  3319. '</td></tr>';
  3320. col_html += "</table></div>";
  3321. this.toolbar.get('w2ui-column-on-off').html = col_html;
  3322. },
  3323. /**
  3324. *
  3325. * @param box, grid object
  3326. * @returns {{remove: Function}} contains a closure around all events to ensure they are removed from the dom
  3327. */
  3328. initColumnDrag: function ( box ) {
  3329. //throw error if using column groups
  3330. if ( this.columnGroups && this.columnGroups.length ) throw 'Draggable columns are not currently supported with column groups.';
  3331. var obj = this,
  3332. _dragData = {};
  3333. _dragData.lastInt = null;
  3334. _dragData.pressed = false;
  3335. _dragData.timeout = null;_dragData.columnHead = null;
  3336. //attach orginal event listener
  3337. $(obj.box).on('mousedown', dragColStart);
  3338. $(obj.box).on('mouseup', catchMouseup);
  3339. function catchMouseup(){
  3340. _dragData.pressed = false;
  3341. clearTimeout( _dragData.timeout );
  3342. }
  3343. /**
  3344. *
  3345. * @param event, mousedown
  3346. * @returns {boolean} false, preventsDefault
  3347. */
  3348. function dragColStart ( event ) {
  3349. if ( _dragData.timeout ) clearTimeout( _dragData.timeout );
  3350. var self = this;
  3351. _dragData.pressed = true;
  3352. _dragData.timeout = setTimeout(function(){
  3353. if ( !_dragData.pressed ) return;
  3354. var eventData,
  3355. columns,
  3356. selectedCol,
  3357. origColumn,
  3358. origColumnNumber,
  3359. invalidPreColumns = [ 'w2ui-col-number', 'w2ui-col-expand', 'w2ui-col-select' ],
  3360. invalidPostColumns = [ 'w2ui-head-last' ],
  3361. invalidColumns = invalidPreColumns.concat( invalidPostColumns ),
  3362. preColumnsSelector = '.w2ui-col-number, .w2ui-col-expand, .w2ui-col-select',
  3363. preColHeadersSelector = '.w2ui-head.w2ui-col-number, .w2ui-head.w2ui-col-expand, .w2ui-head.w2ui-col-select';
  3364. // do nothing if it is not a header
  3365. if ( !$( event.originalEvent.target ).parents().hasClass( 'w2ui-head' ) ) return;
  3366. // do nothing if it is an invalid column
  3367. for ( var i = 0, l = invalidColumns.length; i < l; i++ ){
  3368. if ( $( event.originalEvent.target ).parents().hasClass( invalidColumns[ i ] ) ) return;
  3369. }
  3370. _dragData.numberPreColumnsPresent = $( obj.box ).find( preColHeadersSelector ).length;
  3371. //start event for drag start
  3372. _dragData.columnHead = origColumn = $( event.originalEvent.target ).parents( '.w2ui-head' );
  3373. origColumnNumber = parseInt( origColumn.attr( 'col' ), 10);
  3374. eventData = obj.trigger({ type: 'columnDragStart', phase: 'before', originalEvent: event, origColumnNumber: origColumnNumber, target: origColumn[0] });
  3375. if ( eventData.isCancelled === true ) return false;
  3376. columns = _dragData.columns = $( obj.box ).find( '.w2ui-head:not(.w2ui-head-last)' );
  3377. //add events
  3378. $( document ).on( 'mouseup', dragColEnd );
  3379. $( document ).on( 'mousemove', dragColOver );
  3380. _dragData.originalPos = parseInt( $( event.originalEvent.target ).parent( '.w2ui-head' ).attr( 'col' ), 10 );
  3381. //_dragData.columns.css({ overflow: 'visible' }).children( 'div' ).css({ overflow: 'visible' });
  3382. //configure and style ghost image
  3383. _dragData.ghost = $( self ).clone( true );
  3384. //hide other elements on ghost except the grid body
  3385. $( _dragData.ghost ).find( '[col]:not([col="' + _dragData.originalPos + '"]), .w2ui-toolbar, .w2ui-grid-header' ).remove();
  3386. $( _dragData.ghost ).find( preColumnsSelector ).remove();
  3387. $( _dragData.ghost ).find( '.w2ui-grid-body' ).css({ top: 0 });
  3388. selectedCol = $( _dragData.ghost ).find( '[col="' + _dragData.originalPos + '"]' );
  3389. $( document.body ).append( _dragData.ghost );
  3390. $( _dragData.ghost ).css({
  3391. width: 0,
  3392. height: 0,
  3393. margin: 0,
  3394. position: 'fixed',
  3395. zIndex: 999999,
  3396. opacity: 0
  3397. }).addClass( '.w2ui-grid-ghost' ).animate({
  3398. width: selectedCol.width(),
  3399. height: $(obj.box).find('.w2ui-grid-body:first').height(),
  3400. left : event.pageX,
  3401. top : event.pageY,
  3402. opacity: .8
  3403. }, 0 );
  3404. //establish current offsets
  3405. _dragData.offsets = [];
  3406. for ( var i = 0, l = columns.length; i < l; i++ ) {
  3407. _dragData.offsets.push( $( columns[ i ] ).offset().left );
  3408. }
  3409. //conclude event
  3410. obj.trigger( $.extend( eventData, { phase: 'after' } ) );
  3411. }, 150 );//end timeout wrapper
  3412. }
  3413. function dragColOver ( event ) {
  3414. if ( !_dragData.pressed ) return;
  3415. var cursorX = event.originalEvent.pageX,
  3416. cursorY = event.originalEvent.pageY,
  3417. offsets = _dragData.offsets,
  3418. lastWidth = $( '.w2ui-head:not(.w2ui-head-last)' ).width();
  3419. _dragData.targetInt = Math.max(_dragData.numberPreColumnsPresent,targetIntersection( cursorX, offsets, lastWidth ));
  3420. markIntersection( _dragData.targetInt );
  3421. trackGhost( cursorX, cursorY );
  3422. }
  3423. function dragColEnd ( event ) {
  3424. _dragData.pressed = false;
  3425. var eventData,
  3426. target,
  3427. selected,
  3428. columnConfig,
  3429. targetColumn,
  3430. ghosts = $( '.w2ui-grid-ghost' );
  3431. //start event for drag start
  3432. eventData = obj.trigger({ type: 'columnDragEnd', phase: 'before', originalEvent: event, target: _dragData.columnHead[0] });
  3433. if ( eventData.isCancelled === true ) return false;
  3434. selected = obj.columns[ _dragData.originalPos ];
  3435. columnConfig = obj.columns;
  3436. targetColumn = $( _dragData.columns[ Math.min(_dragData.lastInt, _dragData.columns.length - 1) ] );
  3437. target = (_dragData.lastInt < _dragData.columns.length) ? parseInt(targetColumn.attr('col')) : columnConfig.length;
  3438. if ( target !== _dragData.originalPos + 1 && target !== _dragData.originalPos && targetColumn && targetColumn.length ) {
  3439. $( _dragData.ghost ).animate({
  3440. top: $( obj.box ).offset().top,
  3441. left: targetColumn.offset().left,
  3442. width: 0,
  3443. height: 0,
  3444. opacity:.2
  3445. }, 300, function(){
  3446. $( this ).remove();
  3447. ghosts.remove();
  3448. });
  3449. columnConfig.splice( target, 0, $.extend( {}, selected ) );
  3450. columnConfig.splice( columnConfig.indexOf( selected ), 1);
  3451. } else {
  3452. $( _dragData.ghost ).remove();
  3453. ghosts.remove();
  3454. }
  3455. //_dragData.columns.css({ overflow: '' }).children( 'div' ).css({ overflow: '' });
  3456. $( document ).off( 'mouseup', dragColEnd );
  3457. $( document ).off( 'mousemove', dragColOver );
  3458. if ( _dragData.marker ) _dragData.marker.remove();
  3459. _dragData = {};
  3460. obj.refresh();
  3461. //conclude event
  3462. obj.trigger( $.extend( eventData, { phase: 'after', targetColumnNumber: target - 1 } ) );
  3463. }
  3464. function markIntersection( intersection ){
  3465. if ( !_dragData.marker && !_dragData.markerLeft ) {
  3466. _dragData.marker = $('<div class="col-intersection-marker">' +
  3467. '<div class="top-marker"></div>' +
  3468. '<div class="bottom-marker"></div>' +
  3469. '</div>');
  3470. _dragData.markerLeft = $('<div class="col-intersection-marker">' +
  3471. '<div class="top-marker"></div>' +
  3472. '<div class="bottom-marker"></div>' +
  3473. '</div>');
  3474. }
  3475. if ( !_dragData.lastInt || _dragData.lastInt !== intersection ){
  3476. _dragData.lastInt = intersection;
  3477. _dragData.marker.remove();
  3478. _dragData.markerLeft.remove();
  3479. $('.w2ui-head').removeClass('w2ui-col-intersection');
  3480. //if the current intersection is greater than the number of columns add the marker to the end of the last column only
  3481. if ( intersection >= _dragData.columns.length ) {
  3482. $( _dragData.columns[ _dragData.columns.length - 1 ] ).children( 'div:last' ).append( _dragData.marker.addClass( 'right' ).removeClass( 'left' ) );
  3483. $( _dragData.columns[ _dragData.columns.length - 1 ] ).addClass('w2ui-col-intersection');
  3484. } else if ( intersection <= _dragData.numberPreColumnsPresent ) {
  3485. //if the current intersection is on the column numbers place marker on first available column only
  3486. $( _dragData.columns[ _dragData.numberPreColumnsPresent ] ).prepend( _dragData.marker.addClass( 'left' ).removeClass( 'right' ) ).css({ position: 'relative' });
  3487. $( _dragData.columns[ _dragData.numberPreColumnsPresent ] ).prev().addClass('w2ui-col-intersection');
  3488. } else {
  3489. //otherwise prepend the marker to the targeted column and append it to the previous column
  3490. $( _dragData.columns[intersection] ).children( 'div:last' ).prepend( _dragData.marker.addClass( 'left' ).removeClass( 'right' ) );
  3491. $( _dragData.columns[intersection] ).prev().children( 'div:last' ).append( _dragData.markerLeft.addClass( 'right' ).removeClass( 'left' ) ).css({ position: 'relative' });
  3492. $( _dragData.columns[intersection - 1] ).addClass('w2ui-col-intersection');
  3493. }
  3494. }
  3495. }
  3496. function targetIntersection( cursorX, offsets, lastWidth ){
  3497. if ( cursorX <= offsets[0] ) {
  3498. return 0;
  3499. } else if ( cursorX >= offsets[offsets.length - 1] + lastWidth ) {
  3500. return offsets.length;
  3501. } else {
  3502. for ( var i = 0, l = offsets.length; i < l; i++ ) {
  3503. var thisOffset = offsets[ i ];
  3504. var nextOffset = offsets[ i + 1 ] || offsets[ i ] + lastWidth;
  3505. var midpoint = ( nextOffset - offsets[ i ]) / 2 + offsets[ i ];
  3506. if ( cursorX > thisOffset && cursorX <= midpoint ) {
  3507. return i;
  3508. } else if ( cursorX > midpoint && cursorX <= nextOffset ) {
  3509. return i + 1;
  3510. }
  3511. }
  3512. return intersection;
  3513. }
  3514. }
  3515. function trackGhost( cursorX, cursorY ){
  3516. $( _dragData.ghost ).css({
  3517. left: cursorX - 10,
  3518. top: cursorY - 10
  3519. });
  3520. }
  3521. //return an object to remove drag if it has ever been enabled
  3522. return {
  3523. remove: function(){
  3524. $( obj.box ).off( 'mousedown', dragColStart );
  3525. $( obj.box ).off( 'mouseup', catchMouseup );
  3526. $( obj.box ).find( '.w2ui-head' ).removeAttr( 'draggable' );
  3527. obj.last.columnDrag = false;
  3528. }
  3529. }
  3530. },
  3531. columnOnOff: function (el, event, field) {
  3532. // event before
  3533. var eventData = this.trigger({ phase: 'before', target: this.name, type: 'columnOnOff', checkbox: el, field: field, originalEvent: event });
  3534. if (eventData.isCancelled === true) return;
  3535. // regular processing
  3536. var obj = this;
  3537. // collapse expanded rows
  3538. for (var r in this.records) {
  3539. if (this.records[r].expanded === true) this.records[r].expanded = false
  3540. }
  3541. // show/hide
  3542. var hide = true;
  3543. if (field == 'line-numbers') {
  3544. this.show.lineNumbers = !this.show.lineNumbers;
  3545. this.refresh();
  3546. } else {
  3547. var col = this.getColumn(field);
  3548. if (col.hidden) {
  3549. $(el).prop('checked', true);
  3550. this.showColumn(col.field);
  3551. } else {
  3552. $(el).prop('checked', false);
  3553. this.hideColumn(col.field);
  3554. }
  3555. hide = false;
  3556. }
  3557. if (hide) {
  3558. setTimeout(function () {
  3559. $().w2overlay('', { name: 'searches-'+ this.name });
  3560. obj.toolbar.uncheck('column-on-off');
  3561. }, 100);
  3562. }
  3563. // event after
  3564. this.trigger($.extend(eventData, { phase: 'after' }));
  3565. },
  3566. initToolbar: function () {
  3567. // -- if toolbar is true
  3568. if (typeof this.toolbar['render'] == 'undefined') {
  3569. var tmp_items = this.toolbar.items;
  3570. this.toolbar.items = [];
  3571. this.toolbar = $().w2toolbar($.extend(true, {}, this.toolbar, { name: this.name +'_toolbar', owner: this }));
  3572. // =============================================
  3573. // ------ Toolbar Generic buttons
  3574. if (this.show.toolbarReload) {
  3575. this.toolbar.items.push($.extend(true, {}, this.buttons['reload']));
  3576. }
  3577. if (this.show.toolbarColumns) {
  3578. this.toolbar.items.push($.extend(true, {}, this.buttons['columns']));
  3579. }
  3580. if (this.show.toolbarReload || this.show.toolbarColumn) {
  3581. this.toolbar.items.push({ type: 'break', id: 'w2ui-break0' });
  3582. }
  3583. if (this.show.toolbarSearch) {
  3584. var html =
  3585. '<div class="w2ui-toolbar-search">'+
  3586. '<table cellpadding="0" cellspacing="0"><tr>'+
  3587. ' <td>'+ this.buttons['search'].html +'</td>'+
  3588. ' <td>'+
  3589. ' <input id="grid_'+ this.name +'_search_all" class="w2ui-search-all" '+
  3590. ' placeholder="'+ this.last.caption +'" value="'+ this.last.search +'"'+
  3591. ' onkeydown="if (event.keyCode == 13 && w2utils.isIE) this.onchange();"'+
  3592. ' onchange="'+
  3593. ' var val = this.value; '+
  3594. ' var fld = $(this).data(\'w2field\'); '+
  3595. ' if (fld) val = fld.clean(val);'+
  3596. ' w2ui[\''+ this.name +'\'].search(w2ui[\''+ this.name +'\'].last.field, val); '+
  3597. ' ">'+
  3598. ' </td>'+
  3599. ' <td>'+
  3600. ' <div title="'+ w2utils.lang('Clear Search') +'" class="w2ui-search-clear" id="grid_'+ this.name +'_searchClear" '+
  3601. ' onclick="var obj = w2ui[\''+ this.name +'\']; obj.searchReset();" '+
  3602. ' >&nbsp;&nbsp;</div>'+
  3603. ' </td>'+
  3604. '</tr></table>'+
  3605. '</div>';
  3606. this.toolbar.items.push({ type: 'html', id: 'w2ui-search', html: html });
  3607. if (this.multiSearch && this.searches.length > 0) {
  3608. this.toolbar.items.push($.extend(true, {}, this.buttons['search-go']));
  3609. }
  3610. }
  3611. if (this.show.toolbarSearch && (this.show.toolbarAdd || this.show.toolbarEdit || this.show.toolbarDelete || this.show.toolbarSave)) {
  3612. this.toolbar.items.push({ type: 'break', id: 'w2ui-break1' });
  3613. }
  3614. if (this.show.toolbarAdd) {
  3615. this.toolbar.items.push($.extend(true, {}, this.buttons['add']));
  3616. }
  3617. if (this.show.toolbarEdit) {
  3618. this.toolbar.items.push($.extend(true, {}, this.buttons['edit']));
  3619. }
  3620. if (this.show.toolbarDelete) {
  3621. this.toolbar.items.push($.extend(true, {}, this.buttons['delete']));
  3622. }
  3623. if (this.show.toolbarSave) {
  3624. if (this.show.toolbarAdd || this.show.toolbarDelete || this.show.toolbarEdit) {
  3625. this.toolbar.items.push({ type: 'break', id: 'w2ui-break2' });
  3626. }
  3627. this.toolbar.items.push($.extend(true, {}, this.buttons['save']));
  3628. }
  3629. // add original buttons
  3630. for (var i in tmp_items) this.toolbar.items.push(tmp_items[i]);
  3631. // =============================================
  3632. // ------ Toolbar onClick processing
  3633. var obj = this;
  3634. this.toolbar.on('click', function (event) {
  3635. var eventData = obj.trigger({ phase: 'before', type: 'toolbar', target: event.target, originalEvent: event });
  3636. if (eventData.isCancelled === true) return;
  3637. var id = event.target;
  3638. switch (id) {
  3639. case 'w2ui-reload':
  3640. var eventData2 = obj.trigger({ phase: 'before', type: 'reload', target: obj.name });
  3641. if (eventData2.isCancelled === true) return false;
  3642. obj.reload();
  3643. obj.trigger($.extend(eventData2, { phase: 'after' }));
  3644. break;
  3645. case 'w2ui-column-on-off':
  3646. obj.initColumnOnOff();
  3647. obj.initResize();
  3648. obj.resize();
  3649. break;
  3650. case 'w2ui-search-advanced':
  3651. var tb = this;
  3652. var it = this.get(id);
  3653. if (it.checked) {
  3654. obj.searchClose();
  3655. setTimeout(function () { tb.uncheck(id); }, 1);
  3656. } else {
  3657. obj.searchOpen();
  3658. event.originalEvent.stopPropagation();
  3659. function tmp_close() {
  3660. if ($('#w2ui-overlay-searches-'+ obj.name).data('keepOpen') === true) return;
  3661. tb.uncheck(id);
  3662. $(document).off('click', 'body', tmp_close);
  3663. }
  3664. $(document).on('click', 'body', tmp_close);
  3665. }
  3666. break;
  3667. case 'w2ui-add':
  3668. // events
  3669. var eventData = obj.trigger({ phase: 'before', target: obj.name, type: 'add', recid: null });
  3670. obj.trigger($.extend(eventData, { phase: 'after' }));
  3671. break;
  3672. case 'w2ui-edit':
  3673. var sel = obj.getSelection();
  3674. var recid = null;
  3675. if (sel.length == 1) recid = sel[0];
  3676. // events
  3677. var eventData = obj.trigger({ phase: 'before', target: obj.name, type: 'edit', recid: recid });
  3678. obj.trigger($.extend(eventData, { phase: 'after' }));
  3679. break;
  3680. case 'w2ui-delete':
  3681. obj["delete"]();
  3682. break;
  3683. case 'w2ui-save':
  3684. obj.save();
  3685. break;
  3686. }
  3687. // no default action
  3688. obj.trigger($.extend(eventData, { phase: 'after' }));
  3689. });
  3690. }
  3691. return;
  3692. },
  3693. initResize: function () {
  3694. var obj = this;
  3695. //if (obj.resizing === true) return;
  3696. $(this.box).find('.w2ui-resizer')
  3697. .off('click')
  3698. .on('click', function (event) {
  3699. if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;
  3700. if (event.preventDefault) event.preventDefault();
  3701. })
  3702. .off('mousedown')
  3703. .on('mousedown', function (event) {
  3704. if (!event) event = window.event;
  3705. if (!window.addEventListener) { window.document.attachEvent('onselectstart', function() { return false; } ); }
  3706. obj.resizing = true;
  3707. obj.last.tmp = {
  3708. x : event.screenX,
  3709. y : event.screenY,
  3710. gx : event.screenX,
  3711. gy : event.screenY,
  3712. col : parseInt($(this).attr('name'))
  3713. };
  3714. if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;
  3715. if (event.preventDefault) event.preventDefault();
  3716. // fix sizes
  3717. for (var c in obj.columns) {
  3718. if (typeof obj.columns[c].sizeOriginal == 'undefined') obj.columns[c].sizeOriginal = obj.columns[c].size;
  3719. obj.columns[c].size = obj.columns[c].sizeCalculated;
  3720. }
  3721. var eventData = { phase: 'before', type: 'columnResize', target: obj.name, column: obj.last.tmp.col, field: obj.columns[obj.last.tmp.col].field };
  3722. eventData = obj.trigger($.extend(eventData, { resizeBy: 0, originalEvent: event }));
  3723. // set move event
  3724. var mouseMove = function (event) {
  3725. if (obj.resizing != true) return;
  3726. if (!event) event = window.event;
  3727. // event before
  3728. eventData = obj.trigger($.extend(eventData, { resizeBy: (event.screenX - obj.last.tmp.gx), originalEvent: event }));
  3729. if (eventData.isCancelled === true) { eventData.isCancelled = false; return; }
  3730. // default action
  3731. obj.last.tmp.x = (event.screenX - obj.last.tmp.x);
  3732. obj.last.tmp.y = (event.screenY - obj.last.tmp.y);
  3733. obj.columns[obj.last.tmp.col].size = (parseInt(obj.columns[obj.last.tmp.col].size) + obj.last.tmp.x) + 'px';
  3734. obj.resizeRecords();
  3735. // reset
  3736. obj.last.tmp.x = event.screenX;
  3737. obj.last.tmp.y = event.screenY;
  3738. };
  3739. var mouseUp = function (event) {
  3740. delete obj.resizing;
  3741. $(document).off('mousemove', 'body');
  3742. $(document).off('mouseup', 'body');
  3743. obj.resizeRecords();
  3744. // event before
  3745. obj.trigger($.extend(eventData, { phase: 'after', originalEvent: event }));
  3746. };
  3747. $(document).on('mousemove', 'body', mouseMove);
  3748. $(document).on('mouseup', 'body', mouseUp);
  3749. })
  3750. .each(function (index, el) {
  3751. var td = $(el).parent();
  3752. $(el).css({
  3753. "height" : '25px',
  3754. "margin-left" : (td.width() - 3) + 'px'
  3755. })
  3756. });
  3757. },
  3758. resizeBoxes: function () {
  3759. // elements
  3760. var main = $(this.box).find('> div');
  3761. var header = $('#grid_'+ this.name +'_header');
  3762. var toolbar = $('#grid_'+ this.name +'_toolbar');
  3763. var summary = $('#grid_'+ this.name +'_summary');
  3764. var footer = $('#grid_'+ this.name +'_footer');
  3765. var body = $('#grid_'+ this.name +'_body');
  3766. var columns = $('#grid_'+ this.name +'_columns');
  3767. var records = $('#grid_'+ this.name +'_records');
  3768. if (this.show.header) {
  3769. header.css({
  3770. top: '0px',
  3771. left: '0px',
  3772. right: '0px'
  3773. });
  3774. }
  3775. if (this.show.toolbar) {
  3776. toolbar.css({
  3777. top: ( 0 + (this.show.header ? w2utils.getSize(header, 'height') : 0) ) + 'px',
  3778. left: '0px',
  3779. right: '0px'
  3780. });
  3781. }
  3782. if (this.show.footer) {
  3783. footer.css({
  3784. bottom: '0px',
  3785. left: '0px',
  3786. right: '0px'
  3787. });
  3788. }
  3789. if (this.summary.length > 0) {
  3790. summary.css({
  3791. bottom: ( 0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0) ) + 'px',
  3792. left: '0px',
  3793. right: '0px'
  3794. });
  3795. }
  3796. body.css({
  3797. top: ( 0 + (this.show.header ? w2utils.getSize(header, 'height') : 0) + (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0) ) + 'px',
  3798. bottom: ( 0 + (this.show.footer ? w2utils.getSize(footer, 'height') : 0) + (this.summary.length > 0 ? w2utils.getSize(summary, 'height') : 0) ) + 'px',
  3799. left: '0px',
  3800. right: '0px'
  3801. });
  3802. },
  3803. resizeRecords: function () {
  3804. var obj = this;
  3805. // remove empty records
  3806. $(this.box).find('.w2ui-empty-record').remove();
  3807. // -- Calculate Column size in PX
  3808. var box = $(this.box);
  3809. var grid = $(this.box).find('> div');
  3810. var header = $('#grid_'+ this.name +'_header');
  3811. var toolbar = $('#grid_'+ this.name +'_toolbar');
  3812. var summary = $('#grid_'+ this.name +'_summary');
  3813. var footer = $('#grid_'+ this.name +'_footer');
  3814. var body = $('#grid_'+ this.name +'_body');
  3815. var columns = $('#grid_'+ this.name +'_columns');
  3816. var records = $('#grid_'+ this.name +'_records');
  3817. // body might be expanded by data
  3818. if (!this.fixedBody) {
  3819. // allow it to render records, then resize
  3820. var calculatedHeight = w2utils.getSize(columns, 'height')
  3821. + w2utils.getSize($('#grid_'+ obj.name +'_records table'), 'height');
  3822. obj.height = calculatedHeight
  3823. + w2utils.getSize(grid, '+height')
  3824. + (obj.show.header ? w2utils.getSize(header, 'height') : 0)
  3825. + (obj.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0)
  3826. + (summary.css('display') != 'none' ? w2utils.getSize(summary, 'height') : 0)
  3827. + (obj.show.footer ? w2utils.getSize(footer, 'height') : 0);
  3828. grid.css('height', obj.height);
  3829. body.css('height', calculatedHeight);
  3830. box.css('height', w2utils.getSize(grid, 'height') + w2utils.getSize(box, '+height'));
  3831. } else {
  3832. // fixed body height
  3833. var calculatedHeight = grid.height()
  3834. - (this.show.header ? w2utils.getSize(header, 'height') : 0)
  3835. - (this.show.toolbar ? w2utils.getSize(toolbar, 'height') : 0)
  3836. - (summary.css('display') != 'none' ? w2utils.getSize(summary, 'height') : 0)
  3837. - (this.show.footer ? w2utils.getSize(footer, 'height') : 0);
  3838. body.css('height', calculatedHeight);
  3839. }
  3840. var buffered = this.records.length;
  3841. if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length;
  3842. // check overflow
  3843. var bodyOverflowX = false;
  3844. var bodyOverflowY = false;
  3845. if (body.width() < $(records).find('>table').width()) bodyOverflowX = true;
  3846. if (body.height() - columns.height() < $(records).find('>table').height() + (bodyOverflowX ? w2utils.scrollBarSize() : 0)) bodyOverflowY = true;
  3847. if (!this.fixedBody) { bodyOverflowY = false; bodyOverflowX = false; }
  3848. if (bodyOverflowX || bodyOverflowY) {
  3849. columns.find('> table > tbody > tr:nth-child(1) td.w2ui-head-last').css('width', w2utils.scrollBarSize()).show();
  3850. records.css({
  3851. top: ((this.columnGroups.length > 0 && this.show.columns ? 1 : 0) + w2utils.getSize(columns, 'height')) +'px',
  3852. "-webkit-overflow-scrolling": "touch",
  3853. "overflow-x": (bodyOverflowX ? 'auto' : 'hidden'),
  3854. "overflow-y": (bodyOverflowY ? 'auto' : 'hidden') });
  3855. } else {
  3856. columns.find('> table > tbody > tr:nth-child(1) td.w2ui-head-last').hide();
  3857. records.css({
  3858. top: ((this.columnGroups.length > 0 && this.show.columns ? 1 : 0) + w2utils.getSize(columns, 'height')) +'px',
  3859. overflow: 'hidden'
  3860. });
  3861. if (records.length > 0) { this.last.scrollTop = 0; this.last.scrollLeft = 0; } // if no scrollbars, always show top
  3862. }
  3863. if (this.show.emptyRecords && !bodyOverflowY) {
  3864. var max = Math.floor(records.height() / this.recordHeight) + 1;
  3865. if (this.fixedBody) {
  3866. for (var di = buffered; di <= max; di++) {
  3867. var html = '';
  3868. html += '<tr class="'+ (di % 2 ? 'w2ui-even' : 'w2ui-odd') + ' w2ui-empty-record" style="height: '+ this.recordHeight +'px">';
  3869. if (this.show.lineNumbers) html += '<td class="w2ui-col-number"></td>';
  3870. if (this.show.selectColumn) html += '<td class="w2ui-grid-data w2ui-col-select"></td>';
  3871. if (this.show.expandColumn) html += '<td class="w2ui-grid-data w2ui-col-expand"></td>';
  3872. var j = 0;
  3873. while (this.columns.length > 0) {
  3874. var col = this.columns[j];
  3875. if (col.hidden) { j++; if (typeof this.columns[j] == 'undefined') break; else continue; }
  3876. html += '<td class="w2ui-grid-data" '+ (typeof col.attr != 'undefined' ? col.attr : '') +' col="'+ j +'"></td>';
  3877. j++;
  3878. if (typeof this.columns[j] == 'undefined') break;
  3879. }
  3880. html += '<td class="w2ui-grid-data-last"></td>';
  3881. html += '</tr>';
  3882. $('#grid_'+ this.name +'_records > table').append(html);
  3883. }
  3884. }
  3885. }
  3886. if (body.length > 0) {
  3887. var width_max = parseInt(body.width())
  3888. - (bodyOverflowY ? w2utils.scrollBarSize() : 0)
  3889. - (this.show.lineNumbers ? 34 : 0)
  3890. - (this.show.selectColumn ? 26 : 0)
  3891. - (this.show.expandColumn ? 26 : 0);
  3892. var width_box = width_max;
  3893. var percent = 0;
  3894. // gridMinWidth processiong
  3895. var restart = false;
  3896. for (var i = 0; i < this.columns.length; i++) {
  3897. var col = this.columns[i];
  3898. if (col.gridMinWidth > 0) {
  3899. if (col.gridMinWidth > width_box && col.hidden !== true) {
  3900. col.hidden = true;
  3901. restart = true;
  3902. }
  3903. if (col.gridMinWidth < width_box && col.hidden === true) {
  3904. col.hidden = false;
  3905. restart = true;
  3906. }
  3907. }
  3908. }
  3909. if (restart === true) {
  3910. this.refresh();
  3911. return;
  3912. }
  3913. // assign PX column s
  3914. for (var i = 0; i < this.columns.length; i++) {
  3915. var col = this.columns[i];
  3916. if (col.hidden) continue;
  3917. if (String(col.size).substr(String(col.size).length-2).toLowerCase() == 'px') {
  3918. width_max -= parseFloat(col.size);
  3919. this.columns[i].sizeCalculated = col.size;
  3920. this.columns[i].sizeType = 'px';
  3921. } else {
  3922. percent += parseFloat(col.size);
  3923. this.columns[i].sizeType = '%';
  3924. delete col.sizeCorrected;
  3925. }
  3926. }
  3927. // if sum != 100% -- reassign proportionally
  3928. if (percent != 100 && percent > 0) {
  3929. for (var i = 0; i < this.columns.length; i++) {
  3930. var col = this.columns[i];
  3931. if (col.hidden) continue;
  3932. if (col.sizeType == '%') {
  3933. col.sizeCorrected = Math.round(parseFloat(col.size) * 100 * 100 / percent) / 100 + '%';
  3934. }
  3935. }
  3936. }
  3937. // calculate % columns
  3938. for (var i = 0; i < this.columns.length; i++) {
  3939. var col = this.columns[i];
  3940. if (col.hidden) continue;
  3941. if (col.sizeType == '%') {
  3942. if (typeof this.columns[i].sizeCorrected != 'undefined') {
  3943. // make it 1px smaller, so margin of error can be calculated correctly
  3944. this.columns[i].sizeCalculated = Math.floor(width_max * parseFloat(col.sizeCorrected) / 100) - 1 + 'px';
  3945. } else {
  3946. // make it 1px smaller, so margin of error can be calculated correctly
  3947. this.columns[i].sizeCalculated = Math.floor(width_max * parseFloat(col.size) / 100) - 1 + 'px';
  3948. }
  3949. }
  3950. }
  3951. }
  3952. // fix margin of error that is due percentage calculations
  3953. var width_cols = 0;
  3954. for (var i = 0; i < this.columns.length; i++) {
  3955. var col = this.columns[i];
  3956. if (col.hidden) continue;
  3957. if (typeof col.min == 'undefined') col.min = 20;
  3958. if (parseInt(col.sizeCalculated) < parseInt(col.min)) col.sizeCalculated = col.min + 'px';
  3959. if (parseInt(col.sizeCalculated) > parseInt(col.max)) col.sizeCalculated = col.max + 'px';
  3960. width_cols += parseInt(col.sizeCalculated);
  3961. }
  3962. var width_diff = parseInt(width_box) - parseInt(width_cols);
  3963. if (width_diff > 0 && percent > 0) {
  3964. var i = 0;
  3965. while (true) {
  3966. var col = this.columns[i];
  3967. if (typeof col == 'undefined') { i = 0; continue; }
  3968. if (col.hidden || col.sizeType == 'px') { i++; continue; }
  3969. col.sizeCalculated = (parseInt(col.sizeCalculated) + 1) + 'px';
  3970. width_diff--;
  3971. if (width_diff == 0) break;
  3972. i++;
  3973. }
  3974. } else if (width_diff > 0) {
  3975. columns.find('> table > tbody > tr:nth-child(1) td.w2ui-head-last').css('width', w2utils.scrollBarSize()).show();
  3976. }
  3977. // resize columns
  3978. columns.find('> table > tbody > tr:nth-child(1) td').each(function (index, el) {
  3979. var ind = $(el).attr('col');
  3980. if (typeof ind != 'undefined' && obj.columns[ind]) $(el).css('width', obj.columns[ind].sizeCalculated);
  3981. // last column
  3982. if ($(el).hasClass('w2ui-head-last')) {
  3983. $(el).css('width', w2utils.scrollBarSize() + (width_diff > 0 && percent == 0 ? width_diff : 0) + 'px');
  3984. }
  3985. });
  3986. // if there are column groups - hide first row (needed for sizing)
  3987. if (columns.find('> table > tbody > tr').length == 3) {
  3988. columns.find('> table > tbody > tr:nth-child(1) td').html('').css({
  3989. 'height' : '0px',
  3990. 'border' : '0px',
  3991. 'padding': '0px',
  3992. 'margin' : '0px'
  3993. });
  3994. }
  3995. // resize records
  3996. records.find('> table > tbody > tr:nth-child(1) td').each(function (index, el) {
  3997. var ind = $(el).attr('col');
  3998. if (typeof ind != 'undefined' && obj.columns[ind]) $(el).css('width', obj.columns[ind].sizeCalculated);
  3999. // last column
  4000. if ($(el).hasClass('w2ui-grid-data-last')) {
  4001. $(el).css('width', (width_diff > 0 && percent == 0 ? width_diff : 0) + 'px');
  4002. }
  4003. });
  4004. // resize summary
  4005. summary.find('> table > tbody > tr:nth-child(1) td').each(function (index, el) {
  4006. var ind = $(el).attr('col');
  4007. if (typeof ind != 'undefined' && obj.columns[ind]) $(el).css('width', obj.columns[ind].sizeCalculated);
  4008. // last column
  4009. if ($(el).hasClass('w2ui-grid-data-last')) {
  4010. $(el).css('width', w2utils.scrollBarSize() + (width_diff > 0 && percent == 0 ? width_diff : 0) + 'px');
  4011. }
  4012. });
  4013. this.initResize();
  4014. this.refreshRanges();
  4015. // apply last scroll if any
  4016. if (this.last.scrollTop != '' && records.length > 0) {
  4017. columns.prop('scrollLeft', this.last.scrollLeft);
  4018. records.prop('scrollTop', this.last.scrollTop);
  4019. records.prop('scrollLeft', this.last.scrollLeft);
  4020. }
  4021. },
  4022. getSearchesHTML: function () {
  4023. var html = '<table cellspacing="0">';
  4024. var showBtn = false;
  4025. for (var i = 0; i < this.searches.length; i++) {
  4026. var s = this.searches[i];
  4027. s.type = String(s.type).toLowerCase();
  4028. if (s.hidden) continue;
  4029. var btn = '';
  4030. if (showBtn == false) {
  4031. btn = '<button class="btn close-btn" onclick="obj = w2ui[\''+ this.name +'\']; if (obj) { obj.searchClose(); }">X</button';
  4032. showBtn = true;
  4033. }
  4034. if (typeof s.inTag == 'undefined') s.inTag = '';
  4035. if (typeof s.outTag == 'undefined') s.outTag = '';
  4036. if (typeof s.type == 'undefined') s.type = 'text';
  4037. if (['text', 'alphanumeric', 'combo'].indexOf(s.type) != -1) {
  4038. var operator = '<select id="grid_'+ this.name +'_operator_'+ i +'" onclick="event.stopPropagation();">'+
  4039. ' <option value="is">'+ w2utils.lang('is') +'</option>'+
  4040. ' <option value="begins">'+ w2utils.lang('begins') +'</option>'+
  4041. ' <option value="contains">'+ w2utils.lang('contains') +'</option>'+
  4042. ' <option value="ends">'+ w2utils.lang('ends') +'</option>'+
  4043. '</select>';
  4044. }
  4045. if (['int', 'float', 'money', 'currency', 'percent', 'date', 'time'].indexOf(s.type) != -1) {
  4046. var operator = '<select id="grid_'+ this.name +'_operator_'+ i +'" '+
  4047. ' onchange="w2ui[\''+ this.name + '\'].initOperator(this, '+ i +');" onclick="event.stopPropagation();">'+
  4048. ' <option value="is">'+ w2utils.lang('is') +'</option>'+
  4049. (['int'].indexOf(s.type) != -1 ? '<option value="in">'+ w2utils.lang('in') +'</option>' : '') +
  4050. (['int'].indexOf(s.type) != -1 ? '<option value="not in">'+ w2utils.lang('not in') +'</option>' : '') +
  4051. '<option value="between">'+ w2utils.lang('between') +'</option>'+
  4052. '</select>';
  4053. }
  4054. if (['select', 'list', 'hex'].indexOf(s.type) != -1) {
  4055. var operator = '<select id="grid_'+ this.name +'_operator_'+ i +'" onclick="event.stopPropagation();">'+
  4056. ' <option value="is">'+ w2utils.lang('is') +'</option>'+
  4057. '</select>';
  4058. }
  4059. if (['enum'].indexOf(s.type) != -1) {
  4060. var operator = '<select id="grid_'+ this.name +'_operator_'+ i +'" onclick="event.stopPropagation();">'+
  4061. ' <option value="in">'+ w2utils.lang('in') +'</option>'+
  4062. ' <option value="in">'+ w2utils.lang('not in') +'</option>'+
  4063. '</select>';
  4064. }
  4065. html += '<tr>'+
  4066. ' <td class="close-btn">'+ btn +'</td>' +
  4067. ' <td class="caption">'+ s.caption +'</td>' +
  4068. ' <td class="operator">'+ operator +'</td>'+
  4069. ' <td class="value">';
  4070. switch (s.type) {
  4071. case 'text':
  4072. case 'alphanumeric':
  4073. case 'hex':
  4074. case 'list':
  4075. case 'combo':
  4076. case 'enum':
  4077. html += '<input rel="search" type="text" style="width: 300px;" id="grid_'+ this.name +'_field_'+ i +'" name="'+ s.field +'" '+ s.inTag +'>';
  4078. break;
  4079. case 'int':
  4080. case 'float':
  4081. case 'money':
  4082. case 'currency':
  4083. case 'percent':
  4084. case 'date':
  4085. case 'time':
  4086. html += '<input rel="search" type="text" size="12" id="grid_'+ this.name +'_field_'+ i +'" name="'+ s.field +'" '+ s.inTag +'>'+
  4087. '<span id="grid_'+ this.name +'_range_'+ i +'" style="display: none">'+
  4088. '&nbsp;-&nbsp;&nbsp;<input rel="search" type="text" style="width: 90px" id="grid_'+ this.name +'_field2_'+i+'" name="'+ s.field +'" '+ s.inTag +'>'+
  4089. '</span>';
  4090. break;
  4091. case 'select':
  4092. html += '<select rel="search" id="grid_'+ this.name +'_field_'+ i +'" name="'+ s.field +'" '+ s.inTag +' onclick="event.stopPropagation();"></select>';
  4093. break;
  4094. }
  4095. html += s.outTag +
  4096. ' </td>' +
  4097. '</tr>';
  4098. }
  4099. html += '<tr>'+
  4100. ' <td colspan="4" class="actions">'+
  4101. ' <div>'+
  4102. ' <button class="btn" onclick="obj = w2ui[\''+ this.name +'\']; if (obj) { obj.searchReset(); }">'+ w2utils.lang('Reset') + '</button>'+
  4103. ' <button class="btn btn-blue" onclick="obj = w2ui[\''+ this.name +'\']; if (obj) { obj.search(); }">'+ w2utils.lang('Search') + '</button>'+
  4104. ' </div>'+
  4105. ' </td>'+
  4106. '</tr></table>';
  4107. return html;
  4108. },
  4109. initOperator: function (el, search_ind) {
  4110. var obj = this;
  4111. var search = obj.searches[search_ind];
  4112. var range = $('#grid_'+ obj.name + '_range_'+ search_ind);
  4113. var fld1 = $('#grid_'+ obj.name +'_field_'+ search_ind);
  4114. var fld2 = fld1.parent().find('span input');
  4115. if ($(el).val() == 'in' || $(el).val() == 'not in') { fld1.w2field('clear'); } else { fld1.w2field(search.type); }
  4116. if ($(el).val() == 'between') { range.show(); fld2.w2field(search.type); } else { range.hide(); }
  4117. },
  4118. initSearches: function () {
  4119. var obj = this;
  4120. // init searches
  4121. for (var s in this.searches) {
  4122. var search = this.searches[s];
  4123. var sdata = this.getSearchData(search.field);
  4124. search.type = String(search.type).toLowerCase();
  4125. if (typeof search.options != 'object') search.options = {};
  4126. // init types
  4127. switch (search.type) {
  4128. case 'text':
  4129. case 'alphanumeric':
  4130. $('#grid_'+ this.name +'_operator_'+s).val('begins');
  4131. if (['alphanumeric', 'hex'].indexOf(search.type) != -1) {
  4132. $('#grid_'+ this.name +'_field_' + s).w2field(search.type, search.options);
  4133. }
  4134. break;
  4135. case 'int':
  4136. case 'float':
  4137. case 'money':
  4138. case 'currency':
  4139. case 'percent':
  4140. case 'date':
  4141. case 'time':
  4142. if (sdata && sdata.type == 'int' && ['in', 'not in'].indexOf(sdata.operator) != -1) break;
  4143. $('#grid_'+ this.name +'_field_'+s).w2field(search.type, search.options);
  4144. $('#grid_'+ this.name +'_field2_'+s).w2field(search.type, search.options);
  4145. setTimeout(function () { // convert to date if it is number
  4146. $('#grid_'+ obj.name +'_field_'+s).keydown();
  4147. $('#grid_'+ obj.name +'_field2_'+s).keydown();
  4148. }, 1);
  4149. break;
  4150. case 'hex':
  4151. break;
  4152. case 'list':
  4153. case 'combo':
  4154. case 'enum':
  4155. var options = search.options;
  4156. if (search.type == 'list') options.selected = {};
  4157. if (search.type == 'enum') options.selected = [];
  4158. if (sdata) options.selected = sdata.value;
  4159. $('#grid_'+ this.name +'_field_'+s).w2field(search.type, $.extend({ openOnFocus: true }, options));
  4160. if (search.type == 'combo') {
  4161. $('#grid_'+ this.name +'_operator_'+s).val('begins');
  4162. }
  4163. break;
  4164. case 'select':
  4165. // build options
  4166. var options = '<option value="">--</option>';
  4167. for (var i in search.options.items) {
  4168. var si = search.options.items[i];
  4169. if ($.isPlainObject(search.options.items[i])) {
  4170. var val = si.id;
  4171. var txt = si.text;
  4172. if (typeof val == 'undefined' && typeof si.value != 'undefined') val = si.value;
  4173. if (typeof txt == 'undefined' && typeof si.caption != 'undefined') txt = si.caption;
  4174. if (val == null) val = '';
  4175. options += '<option value="'+ val +'">'+ txt +'</option>';
  4176. } else {
  4177. options += '<option value="'+ si +'">'+ si +'</option>';
  4178. }
  4179. }
  4180. $('#grid_'+ this.name +'_field_'+s).html(options);
  4181. break;
  4182. }
  4183. if (sdata != null) {
  4184. if (sdata.type == 'int' && ['in', 'not in'].indexOf(sdata.operator) != -1) {
  4185. $('#grid_'+ this.name +'_field_'+ s).w2field('clear').val(sdata.value);
  4186. }
  4187. $('#grid_'+ this.name +'_operator_'+ s).val(sdata.operator).trigger('change');
  4188. if (!$.isArray(sdata.value)) {
  4189. if (typeof sdata.value != 'udefined') $('#grid_'+ this.name +'_field_'+ s).val(sdata.value).trigger('change');
  4190. } else {
  4191. if (['in', 'not in'].indexOf(sdata.operator) != -1) {
  4192. $('#grid_'+ this.name +'_field_'+ s).val(sdata.value).trigger('change');
  4193. } else {
  4194. $('#grid_'+ this.name +'_field_'+ s).val(sdata.value[0]).trigger('change');
  4195. $('#grid_'+ this.name +'_field2_'+ s).val(sdata.value[1]).trigger('change');
  4196. }
  4197. }
  4198. }
  4199. }
  4200. // add on change event
  4201. $('#w2ui-overlay-searches-'+ this.name +' .w2ui-grid-searches *[rel=search]').on('keypress', function (evnt) {
  4202. if (evnt.keyCode == 13) {
  4203. obj.search();
  4204. $().w2overlay();
  4205. }
  4206. });
  4207. },
  4208. getColumnsHTML: function () {
  4209. var obj = this;
  4210. var html = '';
  4211. if (this.show.columnHeaders) {
  4212. if (this.columnGroups.length > 0) {
  4213. html = getColumns(true) + getGroups() + getColumns(false);
  4214. } else {
  4215. html = getColumns(true);
  4216. }
  4217. }
  4218. return html;
  4219. function getGroups () {
  4220. var html = '<tr>';
  4221. // add empty group at the end
  4222. if (obj.columnGroups[obj.columnGroups.length-1].caption != '') obj.columnGroups.push({ caption: '' });
  4223. if (obj.show.lineNumbers) {
  4224. html += '<td class="w2ui-head w2ui-col-number">'+
  4225. ' <div>&nbsp;</div>'+
  4226. '</td>';
  4227. }
  4228. if (obj.show.selectColumn) {
  4229. html += '<td class="w2ui-head w2ui-col-select">'+
  4230. ' <div>&nbsp;</div>'+
  4231. '</td>';
  4232. }
  4233. if (obj.show.expandColumn) {
  4234. html += '<td class="w2ui-head w2ui-col-expand">'+
  4235. ' <div>&nbsp;</div>'+
  4236. '</td>';
  4237. }
  4238. var ii = 0;
  4239. for (var i=0; i<obj.columnGroups.length; i++) {
  4240. var colg = obj.columnGroups[i];
  4241. var col = obj.columns[ii];
  4242. if (typeof colg.span == 'undefined' || colg.span != parseInt(colg.span)) colg.span = 1;
  4243. if (typeof colg.colspan != 'undefined') colg.span = colg.colspan;
  4244. if (colg.master === true) {
  4245. var sortStyle = '';
  4246. for (var si in obj.sortData) {
  4247. if (obj.sortData[si].field == col.field) {
  4248. if (new RegExp('asc', 'i').test(obj.sortData[si].direction)) sortStyle = 'w2ui-sort-up';
  4249. if (new RegExp('desc', 'i').test(obj.sortData[si].direction)) sortStyle = 'w2ui-sort-down';
  4250. }
  4251. }
  4252. var resizer = "";
  4253. if (col.resizable !== false) {
  4254. resizer = '<div class="w2ui-resizer" name="'+ ii +'"></div>';
  4255. }
  4256. html += '<td class="w2ui-head '+ sortStyle +'" col="'+ ii + '" rowspan="2" colspan="'+ (colg.span + (i == obj.columnGroups.length-1 ? 1 : 0) ) +'" '+
  4257. ' onclick="w2ui[\''+ obj.name +'\'].columnClick(\''+ col.field +'\', event);">'+
  4258. resizer +
  4259. ' <div class="w2ui-col-group w2ui-col-header '+ (sortStyle ? 'w2ui-col-sorted' : '') +'">'+
  4260. ' <div class="'+ sortStyle +'"></div>'+
  4261. (!col.caption ? '&nbsp;' : col.caption) +
  4262. ' </div>'+
  4263. '</td>';
  4264. } else {
  4265. html += '<td class="w2ui-head" col="'+ ii + '" '+
  4266. ' colspan="'+ (colg.span + (i == obj.columnGroups.length-1 ? 1 : 0) ) +'">'+
  4267. ' <div class="w2ui-col-group">'+
  4268. (!colg.caption ? '&nbsp;' : colg.caption) +
  4269. ' </div>'+
  4270. '</td>';
  4271. }
  4272. ii += colg.span;
  4273. }
  4274. html += '</tr>';
  4275. return html;
  4276. }
  4277. function getColumns (master) {
  4278. var html = '<tr>',
  4279. reorderCols = (obj.reorderColumns && (!obj.columnGroups || !obj.columnGroups.length)) ? ' w2ui-reorder-cols-head ' : '';
  4280. if (obj.show.lineNumbers) {
  4281. html += '<td class="w2ui-head w2ui-col-number" onclick="w2ui[\''+ obj.name +'\'].columnClick(\'line-number\', event);">'+
  4282. ' <div>#</div>'+
  4283. '</td>';
  4284. }
  4285. if (obj.show.selectColumn) {
  4286. html += '<td class="w2ui-head w2ui-col-select" '+
  4287. ' onclick="if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;">'+
  4288. ' <div>'+
  4289. ' <input type="checkbox" id="grid_'+ obj.name +'_check_all" tabIndex="-1"'+
  4290. ' style="' + (obj.multiSelect == false ? 'display: none;' : '') + '"'+
  4291. ' onclick="if (this.checked) w2ui[\''+ obj.name +'\'].selectAll(); '+
  4292. ' else w2ui[\''+ obj.name +'\'].selectNone(); '+
  4293. ' if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;">'+
  4294. ' </div>'+
  4295. '</td>';
  4296. }
  4297. if (obj.show.expandColumn) {
  4298. html += '<td class="w2ui-head w2ui-col-expand">'+
  4299. ' <div>&nbsp;</div>'+
  4300. '</td>';
  4301. }
  4302. var ii = 0;
  4303. var id = 0;
  4304. for (var i=0; i<obj.columns.length; i++) {
  4305. var col = obj.columns[i];
  4306. var colg = {};
  4307. if (i == id) {
  4308. id = id + (typeof obj.columnGroups[ii] != 'undefined' ? parseInt(obj.columnGroups[ii].span) : 0);
  4309. ii++;
  4310. }
  4311. if (typeof obj.columnGroups[ii-1] != 'undefined') var colg = obj.columnGroups[ii-1];
  4312. if (col.hidden) continue;
  4313. var sortStyle = '';
  4314. for (var si in obj.sortData) {
  4315. if (obj.sortData[si].field == col.field) {
  4316. if (new RegExp('asc', 'i').test(obj.sortData[si].direction)) sortStyle = 'w2ui-sort-up';
  4317. if (new RegExp('desc', 'i').test(obj.sortData[si].direction)) sortStyle = 'w2ui-sort-down';
  4318. }
  4319. }
  4320. if (colg['master'] !== true || master) { // grouping of columns
  4321. var resizer = "";
  4322. if (col.resizable !== false) {
  4323. resizer = '<div class="w2ui-resizer" name="'+ i +'"></div>';
  4324. }
  4325. html += '<td col="'+ i +'" class="w2ui-head '+ sortStyle + reorderCols + '" ' +
  4326. ' onclick="w2ui[\''+ obj.name +'\'].columnClick(\''+ col.field +'\', event);">'+
  4327. resizer +
  4328. ' <div class="w2ui-col-header '+ (sortStyle ? 'w2ui-col-sorted' : '') +'">'+
  4329. ' <div class="'+ sortStyle +'"></div>'+
  4330. (!col.caption ? '&nbsp;' : col.caption) +
  4331. ' </div>'+
  4332. '</td>';
  4333. }
  4334. }
  4335. html += '<td class="w2ui-head w2ui-head-last"><div>&nbsp;</div></td>';
  4336. html += '</tr>';
  4337. return html;
  4338. }
  4339. },
  4340. getRecordsHTML: function () {
  4341. var buffered = this.records.length;
  4342. if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length;
  4343. // larget number works better with chrome, smaller with FF.
  4344. if (buffered > 300) this.show_extra = 30; else this.show_extra = 300;
  4345. var records = $('#grid_'+ this.name +'_records');
  4346. var limit = Math.floor(records.height() / this.recordHeight) + this.show_extra + 1;
  4347. if (!this.fixedBody || limit > buffered) limit = buffered;
  4348. // always need first record for resizing purposes
  4349. var html = '<table>' + this.getRecordHTML(-1, 0);
  4350. // first empty row with height
  4351. html += '<tr id="grid_'+ this.name + '_rec_top" line="top" style="height: '+ 0 +'px">'+
  4352. ' <td colspan="200"></td>'+
  4353. '</tr>';
  4354. for (var i = 0; i < limit; i++) {
  4355. html += this.getRecordHTML(i, i+1);
  4356. }
  4357. html += '<tr id="grid_'+ this.name + '_rec_bottom" line="bottom" style="height: '+ ((buffered - limit) * this.recordHeight) +'px">'+
  4358. ' <td colspan="200"></td>'+
  4359. '</tr>'+
  4360. '<tr id="grid_'+ this.name +'_rec_more" style="display: none">'+
  4361. ' <td colspan="200" class="w2ui-load-more"></td>'+
  4362. '</tr>'+
  4363. '</table>';
  4364. this.last.range_start = 0;
  4365. this.last.range_end = limit;
  4366. return html;
  4367. },
  4368. getSummaryHTML: function () {
  4369. if (this.summary.length == 0) return;
  4370. var html = '<table>';
  4371. for (var i = 0; i < this.summary.length; i++) {
  4372. html += this.getRecordHTML(i, i+1, true);
  4373. }
  4374. html += '</table>';
  4375. return html;
  4376. },
  4377. scroll: function (event) {
  4378. var time = (new Date()).getTime();
  4379. var obj = this;
  4380. var records = $('#grid_'+ this.name +'_records');
  4381. var buffered = this.records.length;
  4382. if (this.searchData.length != 0 && !this.url) buffered = this.last.searchIds.length;
  4383. if (buffered == 0 || records.length == 0 || records.height() == 0) return;
  4384. if (buffered > 300) this.show_extra = 30; else this.show_extra = 300;
  4385. // need this to enable scrolling when this.limit < then a screen can fit
  4386. if (records.height() < buffered * this.recordHeight && records.css('overflow-y') == 'hidden') {
  4387. if (this.total > 0) this.refresh();
  4388. return;
  4389. }
  4390. // update footer
  4391. var t1 = Math.round(records[0].scrollTop / this.recordHeight + 1);
  4392. var t2 = t1 + (Math.round(records.height() / this.recordHeight) - 1);
  4393. if (t1 > buffered) t1 = buffered;
  4394. if (t2 > buffered) t2 = buffered;
  4395. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  4396. $('#grid_'+ this.name + '_footer .w2ui-footer-right').html(
  4397. (obj.show.statusRange ? w2utils.formatNumber(this.offset + t1) + '-' + w2utils.formatNumber(this.offset + t2) + ' ' + w2utils.lang('of') + ' ' + w2utils.formatNumber(this.total) : '') +
  4398. (url && obj.show.statusBuffered ? ' ('+ w2utils.lang('buffered') + ' '+ w2utils.formatNumber(buffered) + (this.offset > 0 ? ', skip ' + w2utils.formatNumber(this.offset) : '') + ')' : '')
  4399. );
  4400. // only for local data source, else no extra records loaded
  4401. if (!url && (!this.fixedBody || this.total <= 300)) return;
  4402. // regular processing
  4403. var start = Math.floor(records[0].scrollTop / this.recordHeight) - this.show_extra;
  4404. var end = start + Math.floor(records.height() / this.recordHeight) + this.show_extra * 2 + 1;
  4405. // var div = start - this.last.range_start;
  4406. if (start < 1) start = 1;
  4407. if (end > this.total) end = this.total;
  4408. var tr1 = records.find('#grid_'+ this.name +'_rec_top');
  4409. var tr2 = records.find('#grid_'+ this.name +'_rec_bottom');
  4410. // if row is expanded
  4411. if (String(tr1.next().prop('id')).indexOf('_expanded_row') != -1) tr1.next().remove();
  4412. if (this.total > end && String(tr2.prev().prop('id')).indexOf('_expanded_row') != -1) tr2.prev().remove();
  4413. var first = parseInt(tr1.next().attr('line'));
  4414. var last = parseInt(tr2.prev().attr('line'));
  4415. //$('#log').html('buffer: '+ this.buffered +' start-end: ' + start + '-'+ end + ' ===> first-last: ' + first + '-' + last);
  4416. if (first < start || first == 1 || this.last.pull_refresh) { // scroll down
  4417. // console.log('end', end, 'last', last, 'show_extre', this.show_extra, this.last.pull_refresh);
  4418. if (end <= last + this.show_extra - 2 && end != this.total) return;
  4419. this.last.pull_refresh = false;
  4420. // remove from top
  4421. while (true) {
  4422. var tmp = records.find('#grid_'+ this.name +'_rec_top').next();
  4423. if (tmp.attr('line') == 'bottom') break;
  4424. if (parseInt(tmp.attr('line')) < start) tmp.remove(); else break;
  4425. }
  4426. // add at bottom
  4427. var tmp = records.find('#grid_'+ this.name +'_rec_bottom').prev();
  4428. var rec_start = tmp.attr('line');
  4429. if (rec_start == 'top') rec_start = start;
  4430. for (var i = parseInt(rec_start) + 1; i <= end; i++) {
  4431. if (!this.records[i-1]) continue;
  4432. if (this.records[i-1].expanded === true) this.records[i-1].expanded = false;
  4433. tr2.before(this.getRecordHTML(i-1, i));
  4434. }
  4435. markSearch();
  4436. setTimeout(function() { obj.refreshRanges(); }, 0);
  4437. } else { // scroll up
  4438. if (start >= first - this.show_extra + 2 && start > 1) return;
  4439. // remove from bottom
  4440. while (true) {
  4441. var tmp = records.find('#grid_'+ this.name +'_rec_bottom').prev();
  4442. if (tmp.attr('line') == 'top') break;
  4443. if (parseInt(tmp.attr('line')) > end) tmp.remove(); else break;
  4444. }
  4445. // add at top
  4446. var tmp = records.find('#grid_'+ this.name +'_rec_top').next();
  4447. var rec_start = tmp.attr('line');
  4448. if (rec_start == 'bottom') rec_start = end;
  4449. for (var i = parseInt(rec_start) - 1; i >= start; i--) {
  4450. if (!this.records[i-1]) continue;
  4451. if (this.records[i-1].expanded === true) this.records[i-1].expanded = false;
  4452. tr1.after(this.getRecordHTML(i-1, i));
  4453. }
  4454. markSearch();
  4455. setTimeout(function() { obj.refreshRanges(); }, 0);
  4456. }
  4457. // first/last row size
  4458. var h1 = (start - 1) * obj.recordHeight;
  4459. var h2 = (buffered - end) * obj.recordHeight;
  4460. if (h2 < 0) h2 = 0;
  4461. tr1.css('height', h1 + 'px');
  4462. tr2.css('height', h2 + 'px');
  4463. obj.last.range_start = start;
  4464. obj.last.range_end = end;
  4465. // load more if needed
  4466. var s = Math.floor(records[0].scrollTop / this.recordHeight);
  4467. var e = s + Math.floor(records.height() / this.recordHeight);
  4468. // --- see https://github.com/vitmalina/w2ui/issues/596
  4469. // if (e + 10 > buffered && this.last.pull_more !== true && buffered < this.total - this.offset) {
  4470. if (e + 10 > buffered && this.last.pull_more !== true && buffered < this.total - this.last.xhr_offset) {
  4471. if (this.autoLoad === true) {
  4472. this.last.pull_more = true;
  4473. this.last.xhr_offset += this.limit;
  4474. this.request('get-records');
  4475. } else {
  4476. var more = $('#grid_'+ this.name +'_rec_more');
  4477. if (more.css('display') == 'none') {
  4478. more.show()
  4479. .on('click', function () {
  4480. obj.last.pull_more = true;
  4481. obj.last.xhr_offset += obj.limit;
  4482. obj.request('get-records');
  4483. // show spinner the last
  4484. $(this).find('td').html('<div><div style="width: 20px; height: 20px;" class="w2ui-spinner"></div></div>');
  4485. });
  4486. }
  4487. if (more.find('td').text().indexOf('Load') == -1) {
  4488. more.find('td').html('<div>'+ w2utils.lang('Load') + ' ' + obj.limit + ' ' + w2utils.lang('More') + '...</div>');
  4489. }
  4490. }
  4491. }
  4492. // check for grid end
  4493. if (buffered >= this.total - this.offset) $('#grid_'+ this.name +'_rec_more').hide();
  4494. return;
  4495. function markSearch() {
  4496. // mark search
  4497. if(obj.markSearch === false) return;
  4498. clearTimeout(obj.last.marker_timer);
  4499. obj.last.marker_timer = setTimeout(function () {
  4500. // mark all search strings
  4501. var str = [];
  4502. for (var s in obj.searchData) {
  4503. var tmp = obj.searchData[s];
  4504. if ($.inArray(tmp.value, str) == -1) str.push(tmp.value);
  4505. }
  4506. if (str.length > 0) $(obj.box).find('.w2ui-grid-data > div').w2marker(str);
  4507. }, 50);
  4508. }
  4509. },
  4510. getRecordHTML: function (ind, lineNum, summary) {
  4511. var rec_html = '';
  4512. var sel = this.last.selection;
  4513. var record;
  4514. // first record needs for resize purposes
  4515. if (ind == -1) {
  4516. rec_html += '<tr line="0">';
  4517. if (this.show.lineNumbers) rec_html += '<td class="w2ui-col-number" style="height: 0px;"></td>';
  4518. if (this.show.selectColumn) rec_html += '<td class="w2ui-col-select" style="height: 0px;"></td>';
  4519. if (this.show.expandColumn) rec_html += '<td class="w2ui-col-expand" style="height: 0px;"></td>';
  4520. for (var i in this.columns) {
  4521. if (this.columns[i].hidden) continue;
  4522. rec_html += '<td class="w2ui-grid-data" col="'+ i +'" style="height: 0px;"></td>';
  4523. }
  4524. rec_html += '<td class="w2ui-grid-data-last" style="height: 0px;"></td>';
  4525. rec_html += '</tr>';
  4526. return rec_html;
  4527. }
  4528. // regular record
  4529. var url = (typeof this.url != 'object' ? this.url : this.url.get);
  4530. if (summary !== true) {
  4531. if (this.searchData.length > 0 && !url) {
  4532. if (ind >= this.last.searchIds.length) return '';
  4533. ind = this.last.searchIds[ind];
  4534. record = this.records[ind];
  4535. } else {
  4536. if (ind >= this.records.length) return '';
  4537. record = this.records[ind];
  4538. }
  4539. } else {
  4540. if (ind >= this.summary.length) return '';
  4541. record = this.summary[ind];
  4542. }
  4543. if (!record) return '';
  4544. var id = w2utils.escapeId(record.recid);
  4545. var isRowSelected = false;
  4546. if (sel.indexes.indexOf(ind) != -1) isRowSelected = true;
  4547. // render TR
  4548. rec_html += '<tr id="grid_'+ this.name +'_rec_'+ record.recid +'" recid="'+ record.recid +'" line="'+ lineNum +'" '+
  4549. ' class="'+ (lineNum % 2 == 0 ? 'w2ui-even' : 'w2ui-odd') + (isRowSelected && this.selectType == 'row' ? ' w2ui-selected' : '') + (record.expanded === true ? ' w2ui-expanded' : '') + '" ' +
  4550. (summary !== true ?
  4551. (w2utils.isIOS ?
  4552. ' onclick = "w2ui[\''+ this.name +'\'].dblClick(\''+ record.recid +'\', event);"'
  4553. :
  4554. ' onclick = "w2ui[\''+ this.name +'\'].click(\''+ record.recid +'\', event);"'+
  4555. ' oncontextmenu = "w2ui[\''+ this.name +'\'].contextMenu(\''+ record.recid +'\', event);"'
  4556. )
  4557. : ''
  4558. ) +
  4559. ' style="height: '+ this.recordHeight +'px; '+ (!isRowSelected && typeof record['style'] == 'string' ? record['style'] : '') +'" '+
  4560. ( typeof record['style'] == 'string' ? 'custom_style="'+ record['style'] +'"' : '') +
  4561. '>';
  4562. if (this.show.lineNumbers) {
  4563. rec_html += '<td id="grid_'+ this.name +'_cell_'+ ind +'_number' + (summary ? '_s' : '') + '" '+
  4564. ' class="w2ui-col-number '+ (isRowSelected ? ' w2ui-row-selected' : '') +'">'+
  4565. (summary !== true ? '<div>'+ lineNum +'</div>' : '') +
  4566. '</td>';
  4567. }
  4568. if (this.show.selectColumn) {
  4569. rec_html +=
  4570. '<td id="grid_'+ this.name +'_cell_'+ ind +'_select' + (summary ? '_s' : '') + '" class="w2ui-grid-data w2ui-col-select" '+
  4571. ' onclick="if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;">'+
  4572. (summary !== true ?
  4573. ' <div>'+
  4574. ' <input class="w2ui-grid-select-check" type="checkbox" tabIndex="-1"'+
  4575. ' '+ (isRowSelected ? 'checked="checked"' : '') +
  4576. ' onclick="var obj = w2ui[\''+ this.name +'\']; '+
  4577. ' if (!obj.multiSelect) { obj.selectNone(); }'+
  4578. ' if (this.checked) obj.select(\''+ record.recid + '\'); else obj.unselect(\''+ record.recid + '\'); '+
  4579. ' if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;">'+
  4580. ' </div>'
  4581. :
  4582. '' ) +
  4583. '</td>';
  4584. }
  4585. if (this.show.expandColumn) {
  4586. var tmp_img = '';
  4587. if (record.expanded === true) tmp_img = '-'; else tmp_img = '+';
  4588. if (record.expanded == 'none') tmp_img = '';
  4589. if (record.expanded == 'spinner') tmp_img = '<div class="w2ui-spinner" style="width: 16px; margin: -2px 2px;"></div>';
  4590. rec_html +=
  4591. '<td id="grid_'+ this.name +'_cell_'+ ind +'_expand' + (summary ? '_s' : '') + '" class="w2ui-grid-data w2ui-col-expand">'+
  4592. (summary !== true ?
  4593. ' <div ondblclick="if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;" '+
  4594. ' onclick="w2ui[\''+ this.name +'\'].toggle(\''+ record.recid +'\', event); '+
  4595. ' if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true;">'+
  4596. ' '+ tmp_img +' </div>'
  4597. :
  4598. '' ) +
  4599. '</td>';
  4600. }
  4601. var col_ind = 0;
  4602. while (true) {
  4603. var col = this.columns[col_ind];
  4604. if (col.hidden) { col_ind++; if (typeof this.columns[col_ind] == 'undefined') break; else continue; }
  4605. var isChanged = !summary && record.changes && typeof record.changes[col.field] != 'undefined';
  4606. var rec_cell = this.getCellHTML(ind, col_ind, summary);
  4607. var addStyle = '';
  4608. if (typeof col.render == 'string') {
  4609. var tmp = col.render.toLowerCase().split(':');
  4610. if (['number', 'int', 'float', 'money', 'currency', 'percent'].indexOf(tmp[0]) != -1) addStyle += 'text-align: right;';
  4611. }
  4612. if (typeof record.style == 'object' && typeof record.style[col_ind] == 'string') {
  4613. addStyle += record.style[col_ind] + ';';
  4614. }
  4615. var isCellSelected = false;
  4616. if (isRowSelected && $.inArray(col_ind, sel.columns[ind]) != -1) isCellSelected = true;
  4617. rec_html += '<td class="w2ui-grid-data'+ (isCellSelected ? ' w2ui-selected' : '') + (isChanged ? ' w2ui-changed' : '') +'" col="'+ col_ind +'" '+
  4618. ' style="'+ addStyle + (typeof col.style != 'undefined' ? col.style : '') +'" '+
  4619. (typeof col.attr != 'undefined' ? col.attr : '') +'>'+
  4620. rec_cell +
  4621. '</td>';
  4622. col_ind++;
  4623. if (typeof this.columns[col_ind] == 'undefined') break;
  4624. }
  4625. rec_html += '<td class="w2ui-grid-data-last"></td>';
  4626. rec_html += '</tr>';
  4627. return rec_html;
  4628. },
  4629. getCellHTML: function (ind, col_ind, summary) {
  4630. var col = this.columns[col_ind];
  4631. var record = (summary !== true ? this.records[ind] : this.summary[ind]);
  4632. var data = this.getCellValue(ind, col_ind, summary);
  4633. var edit = col.editable;
  4634. // various renderers
  4635. if (typeof col.render != 'undefined') {
  4636. if (typeof col.render == 'function') {
  4637. data = $.trim(col.render.call(this, record, ind, col_ind));
  4638. if (data.length < 4 || data.substr(0, 4).toLowerCase() != '<div') data = '<div>' + data + '</div>';
  4639. }
  4640. if (typeof col.render == 'object') data = '<div>' + (col.render[data] || '') + '</div>';
  4641. if (typeof col.render == 'string') {
  4642. var tmp = col.render.toLowerCase().split(':');
  4643. var prefix = '';
  4644. var suffix = '';
  4645. if (['number', 'int', 'float', 'money', 'currency', 'percent'].indexOf(tmp[0]) != -1) {
  4646. if (typeof tmp[1] == 'undefined' || !w2utils.isInt(tmp[1])) tmp[1] = 0;
  4647. if (tmp[1] > 20) tmp[1] = 20;
  4648. if (tmp[1] < 0) tmp[1] = 0;
  4649. if (['money', 'currency'].indexOf(tmp[0]) != -1) { tmp[1] = w2utils.settings.currencyPrecision; prefix = w2utils.settings.currencyPrefix; suffix = w2utils.settings.currencySuffix }
  4650. if (tmp[0] == 'percent') { suffix = '%'; if (tmp[1] !== '0') tmp[1] = 1; }
  4651. if (tmp[0] == 'int') { tmp[1] = 0; }
  4652. // format
  4653. data = '<div>' + (data !== '' ? prefix + w2utils.formatNumber(Number(data).toFixed(tmp[1])) + suffix : '') + '</div>';
  4654. }
  4655. if (tmp[0] == 'time') {
  4656. if (typeof tmp[1] == 'undefined' || tmp[1] == '') tmp[1] = w2utils.settings.time_format;
  4657. data = '<div>' + prefix + w2utils.formatTime(data, tmp[1] == 'h12' ? 'hh:mi pm': 'h24:min') + suffix + '</div>';
  4658. }
  4659. if (tmp[0] == 'date') {
  4660. if (typeof tmp[1] == 'undefined' || tmp[1] == '') tmp[1] = w2utils.settings.date_display;
  4661. data = '<div>' + prefix + w2utils.formatDate(data, tmp[1]) + suffix + '</div>';
  4662. }
  4663. if (tmp[0] == 'age') {
  4664. data = '<div>' + prefix + w2utils.age(data) + suffix + '</div>';
  4665. }
  4666. if (tmp[0] == 'toggle') {
  4667. data = '<div>' + prefix + (data ? 'Yes' : '') + suffix + '</div>';
  4668. }
  4669. }
  4670. } else {
  4671. // if editable checkbox
  4672. var addStyle = '';
  4673. if (edit && ['checkbox', 'check'].indexOf(edit.type) != -1) {
  4674. var changeInd = summary ? -(ind + 1) : ind;
  4675. addStyle = 'text-align: center';
  4676. data = '<input type="checkbox" '+ (data ? 'checked' : '') +' onclick="' +
  4677. ' var obj = w2ui[\''+ this.name + '\']; '+
  4678. ' obj.editChange.call(obj, this, '+ changeInd +', '+ col_ind +', event); ' +
  4679. '">';
  4680. }
  4681. if (!this.show.recordTitles) {
  4682. var data = '<div style="'+ addStyle +'">'+ data +'</div>';
  4683. } else {
  4684. // title overwrite
  4685. var title = String(data).replace(/"/g, "''");
  4686. if (typeof col.title != 'undefined') {
  4687. if (typeof col.title == 'function') title = col.title.call(this, record, ind, col_ind);
  4688. if (typeof col.title == 'string') title = col.title;
  4689. }
  4690. var data = '<div title="'+ w2utils.stripTags(title) +'" style="'+ addStyle +'">'+ data +'</div>';
  4691. }
  4692. }
  4693. if (data == null || typeof data == 'undefined') data = '';
  4694. return data;
  4695. },
  4696. getCellValue: function (ind, col_ind, summary) {
  4697. var col = this.columns[col_ind];
  4698. var record = (summary !== true ? this.records[ind] : this.summary[ind]);
  4699. var data = this.parseField(record, col.field);
  4700. if (record.changes && typeof record.changes[col.field] != 'undefined') data = record.changes[col.field];
  4701. if (data == null || typeof data == 'undefined') data = '';
  4702. return data;
  4703. },
  4704. getFooterHTML: function () {
  4705. return '<div>'+
  4706. ' <div class="w2ui-footer-left"></div>'+
  4707. ' <div class="w2ui-footer-right"></div>'+
  4708. ' <div class="w2ui-footer-center"></div>'+
  4709. '</div>';
  4710. },
  4711. status: function (msg) {
  4712. if (typeof msg != 'undefined') {
  4713. $('#grid_'+ this.name +'_footer').find('.w2ui-footer-left').html(msg);
  4714. } else {
  4715. // show number of selected
  4716. var msgLeft = '';
  4717. var sel = this.getSelection();
  4718. if (sel.length > 0) {
  4719. if (this.show.statusSelection && sel.length > 1) {
  4720. msgLeft = String(sel.length).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,") + ' ' + w2utils.lang('selected');
  4721. }
  4722. if (this.show.statusRecordID && sel.length == 1) {
  4723. var tmp = sel[0];
  4724. if (typeof tmp == 'object') tmp = tmp.recid + ', '+ w2utils.lang('Column') +': '+ tmp.column;
  4725. msgLeft = w2utils.lang('Record ID') + ': '+ tmp + ' ';
  4726. }
  4727. }
  4728. $('#grid_'+ this.name +'_footer .w2ui-footer-left').html(msgLeft);
  4729. // toolbar
  4730. if (sel.length == 1) this.toolbar.enable('w2ui-edit'); else this.toolbar.disable('w2ui-edit');
  4731. if (sel.length >= 1) this.toolbar.enable('w2ui-delete'); else this.toolbar.disable('w2ui-delete');
  4732. }
  4733. },
  4734. lock: function (msg, showSpinner) {
  4735. var box = $(this.box).find('> div:first-child');
  4736. var args = Array.prototype.slice.call(arguments, 0);
  4737. args.unshift(box);
  4738. setTimeout(function () { w2utils.lock.apply(window, args); }, 10);
  4739. },
  4740. unlock: function () {
  4741. var box = this.box;
  4742. setTimeout(function () { w2utils.unlock(box); }, 25); // needed timer so if server fast, it will not flash
  4743. },
  4744. stateSave: function (returnOnly) {
  4745. if (!localStorage) return null;
  4746. var state = {
  4747. columns : [],
  4748. show : $.extend({}, this.show),
  4749. last : {
  4750. search : this.last.search,
  4751. multi : this.last.multi,
  4752. logic : this.last.logic,
  4753. caption : this.last.caption,
  4754. field : this.last.field,
  4755. scrollTop : this.last.scrollTop,
  4756. scrollLeft : this.last.scrollLeft
  4757. },
  4758. sortData : [],
  4759. searchData : []
  4760. };
  4761. for (var i in this.columns) {
  4762. var col = this.columns[i];
  4763. state.columns.push({
  4764. field : col.field,
  4765. hidden : col.hidden,
  4766. size : col.size,
  4767. sizeCalculated : col.sizeCalculated,
  4768. sizeOriginal : col.sizeOriginal,
  4769. sizeType : col.sizeType
  4770. });
  4771. }
  4772. for (var i in this.sortData) state.sortData.push($.extend({}, this.sortData[i]));
  4773. for (var i in this.searchData) state.searchData.push($.extend({}, this.searchData[i]));
  4774. // save into local storage
  4775. if (returnOnly !== true) {
  4776. // event before
  4777. var eventData = this.trigger({ phase: 'before', type: 'stateSave', target: this.name, state: state });
  4778. if (eventData.isCancelled === true) { if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); return; }
  4779. try {
  4780. var savedState = $.parseJSON(localStorage.w2ui || '{}');
  4781. if (!savedState) savedState = {};
  4782. if (!savedState.states) savedState.states = {};
  4783. savedState.states[this.name] = state;
  4784. localStorage.w2ui = JSON.stringify(savedState);
  4785. } catch (e) {
  4786. delete localStorage.w2ui;
  4787. return null;
  4788. }
  4789. // event after
  4790. this.trigger($.extend(eventData, { phase: 'after' }));
  4791. }
  4792. return state;
  4793. },
  4794. stateRestore: function (newState) {
  4795. var obj = this;
  4796. if (!newState) {
  4797. // read it from local storage
  4798. try {
  4799. if (!localStorage) return false;
  4800. var tmp = $.parseJSON(localStorage.w2ui || '{}');
  4801. if (!tmp) tmp = {};
  4802. if (!tmp.states) tmp.states = {};
  4803. newState = tmp.states[this.name];
  4804. } catch (e) {
  4805. delete localStorage.w2ui;
  4806. return null;
  4807. }
  4808. }
  4809. // event before
  4810. var eventData = this.trigger({ phase: 'before', type: 'stateRestore', target: this.name, state: newState });
  4811. if (eventData.isCancelled === true) { if (typeof callBack == 'function') callBack({ status: 'error', message: 'Request aborted.' }); return; }
  4812. // default behavior
  4813. if ($.isPlainObject(newState)) {
  4814. $.extend(this.show, newState.show);
  4815. $.extend(this.last, newState.last);
  4816. var sTop = this.last.scrollTop;
  4817. var sLeft = this.last.scrollLeft;
  4818. for (var c in newState.columns) {
  4819. var tmp = newState.columns[c];
  4820. var col = this.getColumn(tmp.field);
  4821. if (col) $.extend(col, tmp);
  4822. }
  4823. this.sortData.splice(0, this.sortData.length);
  4824. for (var c in newState.sortData) this.sortData.push(newState.sortData[c]);
  4825. this.searchData.splice(0, this.searchData.length);
  4826. for (var c in newState.searchData) this.searchData.push(newState.searchData[c]);
  4827. // apply sort and search
  4828. setTimeout(function () {
  4829. // needs timeout as records need to be populated
  4830. if (obj.sortData.length > 0) obj.localSort();
  4831. if (obj.searchData.length > 0) obj.localSearch();
  4832. obj.last.scrollTop = sTop;
  4833. obj.last.scrollLeft = sLeft;
  4834. obj.refresh();
  4835. }, 1);
  4836. }
  4837. // event after
  4838. this.trigger($.extend(eventData, { phase: 'after' }));
  4839. return true;
  4840. },
  4841. stateReset: function () {
  4842. this.stateRestore(this.last.state);
  4843. // remove from local storage
  4844. if (localStorage) {
  4845. try {
  4846. var tmp = $.parseJSON(localStorage.w2ui || '{}');
  4847. if (tmp.states && tmp.states[this.name]) {
  4848. delete tmp.states[this.name];
  4849. }
  4850. localStorage.w2ui = JSON.stringify(tmp);
  4851. } catch (e) {
  4852. delete localStorage.w2ui;
  4853. return null;
  4854. }
  4855. }
  4856. },
  4857. parseField: function (obj, field) {
  4858. var val = '';
  4859. try { // need this to make sure no error in fields
  4860. val = obj;
  4861. var tmp = String(field).split('.');
  4862. for (var i in tmp) {
  4863. val = val[tmp[i]];
  4864. }
  4865. } catch (event) {
  4866. val = '';
  4867. }
  4868. return val;
  4869. },
  4870. prepareData: function () {
  4871. // loops thru records and prepares date and time objects
  4872. for (var r in this.records) {
  4873. var rec = this.records[r];
  4874. for (var c in this.columns) {
  4875. var column = this.columns[c];
  4876. if (rec[column.field] == null || typeof column.render != 'string') continue;
  4877. // number
  4878. if (['number', 'int', 'float', 'money', 'currency', 'percent'].indexOf(column.render.split(':')[0]) != -1) {
  4879. if (typeof rec[column.field] != 'number') rec[column.field] = parseFloat(rec[column.field]);
  4880. }
  4881. // date
  4882. if (['date', 'age'].indexOf(column.render.split(':')[0]) != -1) {
  4883. if (!rec[column.field + '_']) {
  4884. var dt = rec[column.field];
  4885. if (w2utils.isInt(dt)) dt = parseInt(dt);
  4886. rec[column.field + '_'] = new Date(dt);
  4887. }
  4888. }
  4889. // time
  4890. if (['time'].indexOf(column.render) != -1) {
  4891. if (w2utils.isTime(rec[column.field])) { // if string
  4892. var tmp = w2utils.isTime(rec[column.field], true);
  4893. var dt = new Date();
  4894. dt.setHours(tmp.hours, tmp.minutes, (tmp.seconds ? tmp.seconds : 0), 0); // sets hours, min, sec, mills
  4895. if (!rec[column.field + '_']) rec[column.field + '_'] = dt;
  4896. } else { // if date object
  4897. var tmp = rec[column.field];
  4898. if (w2utils.isInt(tmp)) tmp = parseInt(tmp);
  4899. var tmp = (tmp != null ? new Date(tmp) : new Date());
  4900. var dt = new Date();
  4901. dt.setHours(tmp.getHours(), tmp.getMinutes(), tmp.getSeconds(), 0); // sets hours, min, sec, mills
  4902. if (!rec[column.field + '_']) rec[column.field + '_'] = dt;
  4903. }
  4904. }
  4905. }
  4906. }
  4907. },
  4908. nextCell: function (col_ind, editable) {
  4909. var check = col_ind + 1;
  4910. if (this.columns.length == check) return null;
  4911. if (editable === true) {
  4912. var edit = this.columns[check].editable;
  4913. if (this.columns[check].hidden || typeof edit == 'undefined'
  4914. || (edit && ['checkbox', 'check'].indexOf(edit.type) != -1)) return this.nextCell(check, editable);
  4915. }
  4916. return check;
  4917. },
  4918. prevCell: function (col_ind, editable) {
  4919. var check = col_ind - 1;
  4920. if (check < 0) return null;
  4921. if (editable === true) {
  4922. var edit = this.columns[check].editable;
  4923. if (this.columns[check].hidden || typeof edit == 'undefined'
  4924. || (edit && ['checkbox', 'check'].indexOf(edit.type) != -1)) return this.prevCell(check, editable);
  4925. }
  4926. return check;
  4927. },
  4928. nextRow: function (ind) {
  4929. if ((ind + 1 < this.records.length && this.last.searchIds.length == 0) // if there are more records
  4930. || (this.last.searchIds.length > 0 && ind < this.last.searchIds[this.last.searchIds.length-1])) {
  4931. ind++;
  4932. if (this.last.searchIds.length > 0) {
  4933. while (true) {
  4934. if ($.inArray(ind, this.last.searchIds) != -1 || ind > this.records.length) break;
  4935. ind++;
  4936. }
  4937. }
  4938. return ind;
  4939. } else {
  4940. return null;
  4941. }
  4942. },
  4943. prevRow: function (ind) {
  4944. if ((ind > 0 && this.last.searchIds.length == 0) // if there are more records
  4945. || (this.last.searchIds.length > 0 && ind > this.last.searchIds[0])) {
  4946. ind--;
  4947. if (this.last.searchIds.length > 0) {
  4948. while (true) {
  4949. if ($.inArray(ind, this.last.searchIds) != -1 || ind < 0) break;
  4950. ind--;
  4951. }
  4952. }
  4953. return ind;
  4954. } else {
  4955. return null;
  4956. }
  4957. }
  4958. };
  4959. $.extend(w2grid.prototype, w2utils.event);
  4960. w2obj.grid = w2grid;
  4961. })();