CMultiSelect.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. /*##############################################################################
  2. # Copyright (C) 2011 HPCC Systems.
  3. #
  4. # All rights reserved. This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as
  6. # published by the Free Software Foundation, either version 3 of the
  7. # License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. ############################################################################## */
  17. /* This file defines CMultiSelect class, which is used to manage multiple selection
  18. across a column of a given html table. A web page can instantiate more than one such
  19. objects to support multiple selection over multiple columns (individually). This
  20. class should not be instantiated directly but rather via the factory method
  21. "createMultiSelect" which is defined at the bottom of the file. By default,
  22. checkboxes are managed but conceptually different html objects (for instance,
  23. drop down lists, edit boxes, etc.) can be managed instead in future.
  24. If the callback methods onColumnSelChanged and onItemSelChanged are specified then
  25. they must be implemented by the html page instantiating this CMultiSelect object.
  26. function onColumnSelChanged(msid) {...}
  27. This callback gets invoked when selection status across all rows changes (i.e. at
  28. least one row is selected or unselected).
  29. function onItemSelChanged(o, msid) {...}
  30. This callback is invoked for every single html object o (usually a checkbox)
  31. that is being managed by the multiselect object when its selection changes.
  32. */
  33. function CMultiSelect( id,
  34. tableId,
  35. onColumnSelChanged/*=null*/,
  36. onItemSelChanged/*=null*/,
  37. columnToSort/*=0*/,
  38. inputTag/*='INPUT'*/,
  39. inputType/*='checkbox'*/,
  40. bRowsAreTables/*=false*/)
  41. {
  42. //save parameters as instance data
  43. //
  44. this.b_initialized = false;
  45. this.id = id;
  46. this.tableId = tableId;
  47. this.o_table = null; //to be initialized
  48. this.o_htable= null; //to be initialized
  49. this.n_column = typeof columnToSort == 'undefined' ? 0 : columnToSort;
  50. this.onColumnSelChanged = onColumnSelChanged ? onColumnSelChanged : null;
  51. this.onItemSelChanged = onItemSelChanged ? onItemSelChanged : null;
  52. this.inputTag = inputTag ? inputTag : 'INPUT';
  53. this.inputType = inputType ? inputType : 'checkbox';
  54. this.b_rowsAreTables = typeof bRowsAreTables == 'undefined' ? false : bRowsAreTables;
  55. this.b_singleSelect = false;
  56. // define ids used by multiselect controls (usually checkboxes) as tableId_[x]_[y]
  57. // where x is id of this multiselect and y is index of multiselect cell
  58. // (for instance, checkboxes at top and bottom of this table
  59. //
  60. this.selectAllIdPrefixForTable = tableId + '_ms';
  61. // define ids used by multiselect controls (usually checkboxes) as tableId_[x]_[y]
  62. // where x is id of this multiselect and y is index of multiselect cell
  63. // (for instance, checkboxes at top and bottom of this table
  64. //
  65. this.selectAllIdPrefix = this.selectAllIdPrefixForTable + this.n_column + '_';
  66. //bind class methods to this object
  67. //
  68. this.initialize = initialize;
  69. this.getManagedCell = getManagedCell;
  70. this._determineNewSetAllValue = _determineNewSetAllValue;
  71. this.setAll = setAll;
  72. this.getSelectionCount = getSelectionCount;
  73. this.makeSetAllId = makeSetAllId;
  74. this._setRange = _setRange;
  75. this._setSelectAllControls = _setSelectAllControls;
  76. this._onChange = _onChange;
  77. this._getValue = _getValue;
  78. this._setValue = _setValue;
  79. this._updateSelectionCount = _updateSelectionCount;
  80. }
  81. /*------------------------------------------------------------------------------*/
  82. /* PUBLIC METHODS */
  83. /*------------------------------------------------------------------------------*/
  84. //public:
  85. /* define the "constructor"
  86. function initialize()
  87. */
  88. function initialize()
  89. {
  90. this.b_checkbox = this.inputTag == 'INPUT' && this.inputType == 'checkbox';
  91. if (this.b_singleSelect && !this.b_checkbox)
  92. {
  93. alert("Single select is only supported for checkboxes!");
  94. debugger;
  95. }
  96. this.o_table = document.getElementById(this.tableId);
  97. if (!this.o_table)//it is possible that the table was nested within another table's row and got deleted
  98. return;
  99. //this table may be split with its header (separate table w/ just header)
  100. //in one div and body (separate table w/ just body) in another div
  101. //in order to keep its header fixed while the body is scolled up/down
  102. //
  103. this.o_htable = document.getElementById('H.' + this.tableId);
  104. this.lastValueSet = undefined;
  105. this.n_lastRowClicked= -1;
  106. this.inputTag = this.inputTag.toUpperCase();
  107. //updates this.n_selected, this.n_totalItems and this.setAllValue
  108. //
  109. this.n_selected = 0;
  110. this.n_totalItems= 0;
  111. this.setAllValue = undefined;
  112. this.setAllSet = false;
  113. var numRows = this.o_table.rows.length;
  114. var firstRowIndex = this.o_htable ? 0 : 1;
  115. for (var r=firstRowIndex; r<numRows; r++)
  116. {
  117. var row = this.o_table.rows[r];
  118. var numCells = row.cells.length;
  119. var cell = this.getManagedCell(row);
  120. if (cell && cell.childNodes.length > 0)
  121. {
  122. var o = cell.childNodes[0];
  123. if (o.tagName==this.inputTag && o.type==this.inputType && !o.disabled &&
  124. (!o.id || o.id.indexOf(this.selectAllIdPrefixForTable)== -1))
  125. {
  126. this.n_totalItems++;
  127. var val = this._getValue(o);
  128. if (val != undefined)
  129. {
  130. this.n_lastRowClicked = r;
  131. this.lastValueSet = val;
  132. if (this.b_checkbox)
  133. {
  134. if (val)
  135. this.n_selected++;
  136. }
  137. else
  138. {
  139. if (r == 1) {
  140. this.setAllValue = val;
  141. this.setAllSet = true;
  142. }
  143. else
  144. if (this.setAllValue != val) {
  145. this.setAllSet = false;
  146. this.setAllValue = undefined;
  147. }
  148. }
  149. }
  150. }
  151. }
  152. }//for
  153. if (this.b_checkbox) {
  154. this.setAllValue = this.n_totalItems && (this.n_selected == this.n_totalItems);
  155. this.setAllSet = true;
  156. }
  157. this._setSelectAllControls(this.setAllValue);
  158. this.b_initialized = true;
  159. if (this.onColumnSelChanged)
  160. this.onColumnSelChanged( this.id );
  161. }
  162. function getManagedCell(row)
  163. {
  164. if (this.b_rowsAreTables)
  165. {
  166. var cells = row.cells;
  167. if (cells.length > 0)
  168. {
  169. var childNodes = cells[0].childNodes;
  170. var table = childNodes.length > 0 && childNodes[0].tagName == 'TABLE' ? childNodes[0] : null;
  171. if (table)
  172. {
  173. var rows = table.rows;
  174. if (rows.length > 0)
  175. managedRow = rows[0];
  176. }
  177. }
  178. }
  179. else
  180. managedRow = row;
  181. var found = null;
  182. if (managedRow)
  183. {
  184. var cells = managedRow.cells;
  185. if (this.n_column < cells.length)
  186. found = cells[ this.n_column ];
  187. }
  188. return found;
  189. }
  190. /*
  191. function _getNewValueForSetAll()
  192. */
  193. function _determineNewSetAllValue(newValue)
  194. {
  195. if (this.b_checkbox)
  196. newValue = this.n_selected == this.n_totalItems;
  197. else
  198. if (this.n_totalItems > 0)
  199. {
  200. //return value of controls in the column if all cells in that column
  201. //have same value, else return empty string
  202. var rows = this.o_table.rows;
  203. var nRows = rows.length;
  204. var firstRowIndex = this.o_htable ? 0 : 1;
  205. for (var i=firstRowIndex; i<nRows; i++)
  206. {
  207. var cell = this.getManagedCell(rows[i]);
  208. if (cell)
  209. {
  210. var input = cell.firstChild;
  211. if (input && !input.disabled)
  212. {
  213. var val = this._getValue(input);
  214. if (i == 1 && newValue == undefined)
  215. newValue = val;
  216. else
  217. if (newValue != val)
  218. {
  219. newValue = undefined;
  220. break;
  221. }
  222. }
  223. }
  224. }
  225. }
  226. return newValue;
  227. }
  228. /*
  229. function setAll(o)
  230. */
  231. function setAll(o, notify)
  232. {
  233. newValue = this._getValue(o);
  234. var firstRowIndex = this.o_htable ? 0 : 1;
  235. this._setRange(newValue, firstRowIndex, this.o_table.rows.length-1);//row 0 is heading
  236. if (this.b_checkbox)
  237. this.n_selected = newValue ? this.n_totalItems : 0;
  238. if (newValue != this.setAllValue)
  239. this._setSelectAllControls(newValue);
  240. this.setAllSet = true;
  241. if (this.onColumnSelChanged && (typeof notify == 'undefined' || notify)) {
  242. this.onColumnSelChanged( this.id );
  243. }
  244. this.setAllSet = false;
  245. }
  246. /*
  247. function getSelectionCount()
  248. */
  249. function getSelectionCount()
  250. {
  251. return this.b_checkbox ? this.n_selected : undefined;
  252. }
  253. /*
  254. function makeSetAllId(topOfTable)
  255. */
  256. function makeSetAllId(topOfTable)
  257. {
  258. return this.selectAllIdPrefix + (topOfTable ? 'T':'B');
  259. }
  260. /*------------------------------------------------------------------------------*/
  261. /* PRIVATE METHODS */
  262. /*------------------------------------------------------------------------------*/
  263. //private:
  264. //associate the methods with this class
  265. //
  266. /*
  267. function _setRange(newValue, from, to)
  268. */
  269. function _setRange(newValue, from, to)
  270. {
  271. var selectAllIdPrefixForAnyColumn = this.o_table.id + '_ms';
  272. for (var r=from; r<=to; r++)
  273. {
  274. var row = this.o_table.rows[r];
  275. var numCells = row.cells.length;
  276. var cell = this.getManagedCell(row);
  277. if (cell.childNodes.length > 0)
  278. {
  279. //is the first child of this cell a checkbox (not the select all checkbox)
  280. //which has different check state than intended?
  281. //
  282. var o = cell.childNodes[0];
  283. if (o.tagName==this.inputTag && o.type==this.inputType && !o.disabled &&
  284. (!o.id || o.id.indexOf(selectAllIdPrefixForAnyColumn) == -1) && this._getValue(o) != newValue)
  285. {
  286. this._setValue(o, newValue);
  287. this._updateSelectionCount(newValue);
  288. if (this.onItemSelChanged)
  289. this.onItemSelChanged(o, this.id);
  290. }
  291. }
  292. }
  293. }
  294. /*
  295. function _setSelectAllControls(val)
  296. */
  297. function _setSelectAllControls(val)
  298. {
  299. for (var i=0; i<2; i++)//top and bottom
  300. {
  301. selectAllCell = document.getElementById( this.makeSetAllId(i==0) );
  302. if (selectAllCell)
  303. {
  304. var inputs = selectAllCell.getElementsByTagName(this.inputTag);
  305. if (inputs.length > 0)
  306. {
  307. var input = inputs[0];
  308. this._setValue(input, val);
  309. if (input.disabled)
  310. {
  311. if (this.n_totalItems > 0)
  312. input.disabled = false;
  313. }
  314. else
  315. if (this.n_totalItems == 0)
  316. input.disabled = true;
  317. }
  318. }
  319. }
  320. this.setAllValue = val;
  321. this.setAllSet = false;
  322. }
  323. /*
  324. function _onChange(o)
  325. */
  326. function _onChange(o)
  327. {
  328. if (this.b_singleSelect && this._getValue(o)) //note that this.b_checkbox is implied
  329. {
  330. this._setValue(o, false);
  331. this.setAll(o, false);
  332. this._setValue(o, true);
  333. }
  334. var cell = o.parentNode;
  335. var row = cell.parentNode;
  336. var rowNum = row.rowIndex;
  337. if (!this.b_singleSelect && window.event && window.event.shiftKey &&
  338. this.n_lastRowClicked != -1 && this.n_lastRowClicked != rowNum)
  339. {
  340. rc = this.lastValueSet == this._getValue(o);
  341. if (this.n_lastRowClicked < rowNum)
  342. this._setRange(this.lastValueSet, this.n_lastRowClicked, rowNum);
  343. else
  344. this._setRange(this.lastValueSet, rowNum, this.n_lastRowClicked);
  345. }
  346. else
  347. {
  348. this.n_lastRowClicked = rowNum;
  349. this.lastValueSet = this._getValue(o);
  350. rc = true;
  351. }
  352. val = this._getValue(o);
  353. if (rc)
  354. {
  355. this._updateSelectionCount( val );
  356. var newSetAllValue = this._determineNewSetAllValue(val);
  357. if (newSetAllValue != this.setAllValue)
  358. this._setSelectAllControls(newSetAllValue);
  359. }
  360. else
  361. {
  362. //the window has already changed selection for the checkbox which
  363. //should not have been done since shift key was pressed
  364. if (this.b_checkbox)
  365. this._updateSelectionCount(! val ); //compensation
  366. }
  367. if (this.onColumnSelChanged)
  368. this.onColumnSelChanged(this.id);
  369. return rc;
  370. }
  371. /*
  372. function _getValue(o)
  373. */
  374. function _getValue(o)
  375. {
  376. if (this.b_checkbox)
  377. return o.checked;
  378. else
  379. if (this.inputTag == 'SELECT')
  380. return o.selectedIndex;
  381. else
  382. return o.value;
  383. }
  384. /*
  385. function _setValue(o, val)
  386. */
  387. function _setValue(o, val)
  388. {
  389. if (!o.disabled)
  390. {
  391. if (this.b_checkbox)
  392. o.checked = val == undefined ? false : val;
  393. else if (this.inputTag == 'SELECT')
  394. o.selectedIndex = val == undefined ? -1 : val;
  395. else
  396. o.value = val == undefined ? '' : val;
  397. }
  398. }
  399. /*
  400. function _updateSelectionCount(select)
  401. */
  402. function _updateSelectionCount(select)
  403. {
  404. if (this.b_checkbox)
  405. {
  406. if (select)
  407. this.n_selected++;
  408. else
  409. this.n_selected--;
  410. }
  411. }
  412. /*------------------------------------------------------------------------------*/
  413. /* GLOBAL METHODS */
  414. /*------------------------------------------------------------------------------*/
  415. var a_multiselect = [];
  416. /* instantiates a new CMultiSelect object. The caller must initialize it using its
  417. initialize() method appropriately (for instance, after populating table rows).
  418. */
  419. function ms_create( tableId,
  420. onColumnSelChanged/*=null*/,
  421. onItemSelChanged/*=null*/,
  422. columnToSort/*=0*/,
  423. inputTag/*='INPUT'*/,
  424. inputType/*='checkbox'*/,
  425. bRowsAreTables/*=false*/)
  426. {
  427. //add this class instance to the array of multiselects that is used to
  428. //work with multiple such objects in the same page
  429. //
  430. var id = a_multiselect.length;
  431. var ms = new CMultiSelect( id, tableId, onColumnSelChanged,
  432. onItemSelChanged, columnToSort, inputTag, inputType,
  433. bRowsAreTables);
  434. a_multiselect[ id ] = ms;
  435. return ms;
  436. }
  437. function ms_lookup(msid/*=0*/)
  438. {
  439. if (msid == undefined)
  440. msid = 0;
  441. return (msid >= 0 && msid < a_multiselect.length) ? a_multiselect[ msid ] : null;
  442. }
  443. function ms_lookupByTableAndColumn(tableId, columnNum/*=0*/)
  444. {
  445. if (columnNum == undefined)
  446. columnNum = 0;
  447. var count = a_multiselect.length;
  448. for (var i=0; i<count; i++)
  449. {
  450. ms = a_multiselect[i];
  451. if (ms.tableId == tableId && ms.n_column == columnNum)
  452. return ms;
  453. }
  454. return null;
  455. }
  456. /*public*/
  457. function ms_onChange(o, tableId, columnNum/*=0*/)
  458. {
  459. o_ms = ms_lookupByTableAndColumn(tableId, columnNum);
  460. o_ms._onChange(o);
  461. }
  462. /*public*/
  463. function ms_setAll(o, tableId, columnNum/*=0*/)
  464. {
  465. o_ms = ms_lookupByTableAndColumn(tableId, columnNum);
  466. o_ms.setAll(o);
  467. }
  468. /*public*/
  469. function ms_getSelectionCount(msid/*=0*/)
  470. {
  471. o_ms = ms_lookup(msid);
  472. return o_ms.getSelectionCount();
  473. }
  474. /*public*/
  475. function ms_initialize(startColumn/*=0*/, endColumn/*=a_multiselect.length*/)
  476. {
  477. var len = a_multiselect.length;
  478. if (typeof startColumn == 'undefined' || startColumn < 0)
  479. startColumn = 0;
  480. if (typeof endColumn == 'undefined' || endColumn >= len)
  481. endColumn = a_multiselect.length-1;
  482. for (var i=startColumn; i<=endColumn; i++)
  483. a_multiselect[i].initialize();
  484. }
  485. /*public*/
  486. function ms_initializeAll()
  487. {
  488. var len = a_multiselect.length;
  489. for (var i=0; i<len; i++)
  490. a_multiselect[i].initialize();
  491. }
  492. /*public*/
  493. function ms_reinitializeAll()
  494. {
  495. var len = a_multiselect.length;
  496. for (var i=0; i<len; i++)
  497. {
  498. ms = a_multiselect[i];
  499. if (ms.b_initialized)
  500. ms.initialize();
  501. }
  502. }