bootstrap-table-filter-control.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. /**
  2. * @author: Dennis Hernández
  3. * @webSite: http://djhvscf.github.io/Blog
  4. * @version: v2.1.0
  5. */
  6. (function ($) {
  7. 'use strict';
  8. var sprintf = $.fn.bootstrapTable.utils.sprintf,
  9. objectKeys = $.fn.bootstrapTable.utils.objectKeys;
  10. var addOptionToSelectControl = function (selectControl, value, text) {
  11. value = $.trim(value);
  12. selectControl = $(selectControl.get(selectControl.length - 1));
  13. if (existOptionInSelectControl(selectControl, value)) {
  14. selectControl.append($("<option></option>")
  15. .attr("value", value)
  16. .text($('<div />').html(text).text()));
  17. // Sort it. Not overly efficient to do this here
  18. var $opts = selectControl.find('option:gt(0)');
  19. $opts.sort(function (a, b) {
  20. a = $(a).text().toLowerCase();
  21. b = $(b).text().toLowerCase();
  22. if ($.isNumeric(a) && $.isNumeric(b)) {
  23. // Convert numerical values from string to float.
  24. a = parseFloat(a);
  25. b = parseFloat(b);
  26. }
  27. return a > b ? 1 : a < b ? -1 : 0;
  28. });
  29. selectControl.find('option:gt(0)').remove();
  30. selectControl.append($opts);
  31. }
  32. };
  33. var existOptionInSelectControl = function (selectControl, value) {
  34. var options = selectControl.get(selectControl.length - 1).options;
  35. for (var i = 0; i < options.length; i++) {
  36. if (!value || options[i].value === value.toString()) {
  37. //The value is not valid to add
  38. return false;
  39. }
  40. }
  41. //If we get here, the value is valid to add
  42. return true;
  43. };
  44. var fixHeaderCSS = function (that) {
  45. that.$tableHeader.css('height', '77px');
  46. };
  47. var getCurrentHeader = function (that) {
  48. var header = that.$header;
  49. if (that.options.height) {
  50. header = that.$tableHeader;
  51. }
  52. return header;
  53. };
  54. var getCurrentSearchControls = function (that) {
  55. var searchControls = 'select, input';
  56. if (that.options.height) {
  57. searchControls = 'table select, table input';
  58. }
  59. return searchControls;
  60. };
  61. var getCursorPosition = function(el) {
  62. if ($.fn.bootstrapTable.utils.isIEBrowser()) {
  63. if ($(el).is('input')) {
  64. var pos = 0;
  65. if ('selectionStart' in el) {
  66. pos = el.selectionStart;
  67. } else if ('selection' in document) {
  68. el.focus();
  69. var Sel = document.selection.createRange();
  70. var SelLength = document.selection.createRange().text.length;
  71. Sel.moveStart('character', -el.value.length);
  72. pos = Sel.text.length - SelLength;
  73. }
  74. return pos;
  75. } else {
  76. return -1;
  77. }
  78. } else {
  79. return -1;
  80. }
  81. };
  82. var setCursorPosition = function (el, index) {
  83. if ($.fn.bootstrapTable.utils.isIEBrowser()) {
  84. if(el.setSelectionRange !== undefined) {
  85. el.setSelectionRange(index, index);
  86. } else {
  87. $(el).val(el.value);
  88. }
  89. }
  90. };
  91. var copyValues = function (that) {
  92. var header = getCurrentHeader(that),
  93. searchControls = getCurrentSearchControls(that);
  94. that.options.valuesFilterControl = [];
  95. header.find(searchControls).each(function () {
  96. that.options.valuesFilterControl.push(
  97. {
  98. field: $(this).closest('[data-field]').data('field'),
  99. value: $(this).val(),
  100. position: getCursorPosition($(this).get(0))
  101. });
  102. });
  103. };
  104. var setValues = function(that) {
  105. var field = null,
  106. result = [],
  107. header = getCurrentHeader(that),
  108. searchControls = getCurrentSearchControls(that);
  109. if (that.options.valuesFilterControl.length > 0) {
  110. header.find(searchControls).each(function (index, ele) {
  111. field = $(this).closest('[data-field]').data('field');
  112. result = $.grep(that.options.valuesFilterControl, function (valueObj) {
  113. return valueObj.field === field;
  114. });
  115. if (result.length > 0) {
  116. $(this).val(result[0].value);
  117. setCursorPosition($(this).get(0), result[0].position);
  118. }
  119. });
  120. }
  121. };
  122. var collectBootstrapCookies = function cookiesRegex() {
  123. var cookies = [],
  124. foundCookies = document.cookie.match(/(?:bs.table.)(\w*)/g);
  125. if (foundCookies) {
  126. $.each(foundCookies, function (i, cookie) {
  127. if (/./.test(cookie)) {
  128. cookie = cookie.split(".").pop();
  129. }
  130. if ($.inArray(cookie, cookies) === -1) {
  131. cookies.push(cookie);
  132. }
  133. });
  134. return cookies;
  135. }
  136. };
  137. var initFilterSelectControls = function (that) {
  138. var data = that.options.data,
  139. itemsPerPage = that.pageTo < that.options.data.length ? that.options.data.length : that.pageTo,
  140. isColumnSearchableViaSelect = function (column) {
  141. return column.filterControl && column.filterControl.toLowerCase() === 'select' && column.searchable;
  142. },
  143. isFilterDataNotGiven = function (column) {
  144. return column.filterData === undefined || column.filterData.toLowerCase() === 'column';
  145. },
  146. hasSelectControlElement = function (selectControl) {
  147. return selectControl && selectControl.length > 0;
  148. };
  149. var z = that.options.pagination ?
  150. (that.options.sidePagination === 'server' ? that.pageTo : that.options.totalRows) :
  151. that.pageTo;
  152. for (var i = 0; i < z; i++) {
  153. $.each(that.header.fields, function (j, field) {
  154. var column = that.columns[$.fn.bootstrapTable.utils.getFieldIndex(that.columns, field)],
  155. selectControl = $('.' + escapeID(column.field));
  156. if (isColumnSearchableViaSelect(column) && isFilterDataNotGiven(column) && hasSelectControlElement(selectControl)) {
  157. if (selectControl.get(selectControl.length - 1).options.length === 0) {
  158. //Added the default option
  159. addOptionToSelectControl(selectControl, '', '');
  160. }
  161. //Added a new value
  162. var fieldValue = data[i][field],
  163. formattedValue = $.fn.bootstrapTable.utils.calculateObjectValue(that.header, that.header.formatters[j], [fieldValue, data[i], i], fieldValue);
  164. addOptionToSelectControl(selectControl, fieldValue, formattedValue);
  165. }
  166. });
  167. }
  168. };
  169. var escapeID = function( id ) {
  170. return String(id).replace( /(:|\.|\[|\]|,)/g, "\\$1" );
  171. };
  172. var createControls = function (that, header) {
  173. var addedFilterControl = false,
  174. isVisible,
  175. html,
  176. timeoutId = 0;
  177. $.each(that.columns, function (i, column) {
  178. isVisible = 'hidden';
  179. html = [];
  180. if (!column.visible) {
  181. return;
  182. }
  183. if (!column.filterControl) {
  184. html.push('<div style="height: 34px;"></div>');
  185. } else {
  186. html.push('<div style="margin: 0 2px 2px 2px;" class="filterControl">');
  187. var nameControl = column.filterControl.toLowerCase();
  188. if (column.searchable && that.options.filterTemplate[nameControl]) {
  189. addedFilterControl = true;
  190. isVisible = 'visible';
  191. html.push(that.options.filterTemplate[nameControl](that, column.field, isVisible));
  192. }
  193. }
  194. $.each(header.children().children(), function (i, tr) {
  195. tr = $(tr);
  196. if (tr.data('field') === column.field) {
  197. tr.find('.fht-cell').append(html.join(''));
  198. return false;
  199. }
  200. });
  201. if (column.filterData !== undefined && column.filterData.toLowerCase() !== 'column') {
  202. var filterDataType = getFilterDataMethod(filterDataMethods, column.filterData.substring(0, column.filterData.indexOf(':')));
  203. var filterDataSource, selectControl;
  204. if (filterDataType !== null) {
  205. filterDataSource = column.filterData.substring(column.filterData.indexOf(':') + 1, column.filterData.length);
  206. selectControl = $('.' + escapeID(column.field));
  207. addOptionToSelectControl(selectControl, '', '');
  208. filterDataType(filterDataSource, selectControl);
  209. } else {
  210. throw new SyntaxError('Error. You should use any of these allowed filter data methods: var, json, url.' + ' Use like this: var: {key: "value"}');
  211. }
  212. var variableValues, key;
  213. switch (filterDataType) {
  214. case 'url':
  215. $.ajax({
  216. url: filterDataSource,
  217. dataType: 'json',
  218. success: function (data) {
  219. $.each(data, function (key, value) {
  220. addOptionToSelectControl(selectControl, key, value);
  221. });
  222. }
  223. });
  224. break;
  225. case 'var':
  226. variableValues = window[filterDataSource];
  227. for (key in variableValues) {
  228. addOptionToSelectControl(selectControl, key, variableValues[key]);
  229. }
  230. break;
  231. case 'jso':
  232. variableValues = JSON.parse(filterDataSource);
  233. for (key in variableValues) {
  234. addOptionToSelectControl(selectControl, key, variableValues[key]);
  235. }
  236. break;
  237. }
  238. }
  239. });
  240. if (addedFilterControl) {
  241. header.off('keyup', 'input').on('keyup', 'input', function (event) {
  242. clearTimeout(timeoutId);
  243. timeoutId = setTimeout(function () {
  244. that.onColumnSearch(event);
  245. }, that.options.searchTimeOut);
  246. });
  247. header.off('change', 'select').on('change', 'select', function (event) {
  248. clearTimeout(timeoutId);
  249. timeoutId = setTimeout(function () {
  250. that.onColumnSearch(event);
  251. }, that.options.searchTimeOut);
  252. });
  253. header.off('mouseup', 'input').on('mouseup', 'input', function (event) {
  254. var $input = $(this),
  255. oldValue = $input.val();
  256. if (oldValue === "") {
  257. return;
  258. }
  259. setTimeout(function(){
  260. var newValue = $input.val();
  261. if (newValue === "") {
  262. clearTimeout(timeoutId);
  263. timeoutId = setTimeout(function () {
  264. that.onColumnSearch(event);
  265. }, that.options.searchTimeOut);
  266. }
  267. }, 1);
  268. });
  269. if (header.find('.date-filter-control').length > 0) {
  270. $.each(that.columns, function (i, column) {
  271. if (column.filterControl !== undefined && column.filterControl.toLowerCase() === 'datepicker') {
  272. header.find('.date-filter-control.' + column.field).datepicker(column.filterDatepickerOptions)
  273. .on('changeDate', function (e) {
  274. //Fired the keyup event
  275. $(e.currentTarget).keyup();
  276. });
  277. }
  278. });
  279. }
  280. } else {
  281. header.find('.filterControl').hide();
  282. }
  283. };
  284. var getDirectionOfSelectOptions = function (alignment) {
  285. alignment = alignment === undefined ? 'left' : alignment.toLowerCase();
  286. switch (alignment) {
  287. case 'left':
  288. return 'ltr';
  289. case 'right':
  290. return 'rtl';
  291. case 'auto':
  292. return 'auto';
  293. default:
  294. return 'ltr';
  295. }
  296. };
  297. var filterDataMethods =
  298. {
  299. 'var': function (filterDataSource, selectControl) {
  300. var variableValues = window[filterDataSource];
  301. for (var key in variableValues) {
  302. addOptionToSelectControl(selectControl, key, variableValues[key]);
  303. }
  304. },
  305. 'url': function (filterDataSource, selectControl) {
  306. $.ajax({
  307. url: filterDataSource,
  308. dataType: 'json',
  309. success: function (data) {
  310. $.each(data, function (key, value) {
  311. addOptionToSelectControl(selectControl, key, value);
  312. });
  313. }
  314. });
  315. },
  316. 'json':function (filterDataSource, selectControl) {
  317. var variableValues = JSON.parse(filterDataSource);
  318. for (var key in variableValues) {
  319. addOptionToSelectControl(selectControl, key, variableValues[key]);
  320. }
  321. }
  322. };
  323. var getFilterDataMethod = function (objFilterDataMethod, searchTerm) {
  324. var keys = Object.keys(objFilterDataMethod);
  325. for (var i = 0; i < keys.length; i++) {
  326. if (keys[i] === searchTerm) {
  327. return objFilterDataMethod[searchTerm];
  328. }
  329. }
  330. return null;
  331. };
  332. $.extend($.fn.bootstrapTable.defaults, {
  333. filterControl: false,
  334. onColumnSearch: function (field, text) {
  335. return false;
  336. },
  337. filterShowClear: false,
  338. alignmentSelectControlOptions: undefined,
  339. filterTemplate: {
  340. input: function (that, field, isVisible) {
  341. return sprintf('<input type="text" class="form-control %s" style="width: 100%; visibility: %s">', field, isVisible);
  342. },
  343. select: function (that, field, isVisible) {
  344. return sprintf('<select class="%s form-control" style="width: 100%; visibility: %s" dir="%s"></select>',
  345. field, isVisible, getDirectionOfSelectOptions(that.options.alignmentSelectControlOptions));
  346. },
  347. datepicker: function (that, field, isVisible) {
  348. return sprintf('<input type="text" class="date-filter-control %s form-control" style="width: 100%; visibility: %s">', field, isVisible);
  349. }
  350. },
  351. //internal variables
  352. valuesFilterControl: []
  353. });
  354. $.extend($.fn.bootstrapTable.COLUMN_DEFAULTS, {
  355. filterControl: undefined,
  356. filterData: undefined,
  357. filterDatepickerOptions: undefined,
  358. filterStrictSearch: false,
  359. filterStartsWithSearch: false
  360. });
  361. $.extend($.fn.bootstrapTable.Constructor.EVENTS, {
  362. 'column-search.bs.table': 'onColumnSearch'
  363. });
  364. $.extend($.fn.bootstrapTable.defaults.icons, {
  365. clear: 'glyphicon-trash icon-clear'
  366. });
  367. var BootstrapTable = $.fn.bootstrapTable.Constructor,
  368. _init = BootstrapTable.prototype.init,
  369. _initToolbar = BootstrapTable.prototype.initToolbar,
  370. _initHeader = BootstrapTable.prototype.initHeader,
  371. _initBody = BootstrapTable.prototype.initBody,
  372. _initSearch = BootstrapTable.prototype.initSearch;
  373. BootstrapTable.prototype.init = function () {
  374. //Make sure that the filterControl option is set
  375. if (this.options.filterControl) {
  376. var that = this;
  377. // Compatibility: IE < 9 and old browsers
  378. if (!Object.keys) {
  379. objectKeys();
  380. }
  381. //Make sure that the internal variables are set correctly
  382. this.options.valuesFilterControl = [];
  383. this.$el.on('reset-view.bs.table', function () {
  384. //Create controls on $tableHeader if the height is set
  385. if (!that.options.height) {
  386. return;
  387. }
  388. //Avoid recreate the controls
  389. if (that.$tableHeader.find('select').length > 0 || that.$tableHeader.find('input').length > 0) {
  390. return;
  391. }
  392. createControls(that, that.$tableHeader);
  393. }).on('post-header.bs.table', function () {
  394. setValues(that);
  395. }).on('post-body.bs.table', function () {
  396. if (that.options.height) {
  397. fixHeaderCSS(that);
  398. }
  399. }).on('column-switch.bs.table', function() {
  400. setValues(that);
  401. });
  402. }
  403. _init.apply(this, Array.prototype.slice.apply(arguments));
  404. };
  405. $.extend($.fn.bootstrapTable.locales, {
  406. formatClearFilters: function () {
  407. return 'Clear Filters';
  408. }
  409. });
  410. $.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales);
  411. BootstrapTable.prototype.initToolbar = function () {
  412. if ((!this.showToolbar) && (this.options.filterControl)) {
  413. this.showToolbar = this.options.filterControl;
  414. }
  415. _initToolbar.apply(this, Array.prototype.slice.apply(arguments));
  416. if (this.options.filterControl && this.options.filterShowClear) {
  417. var $btnGroup = this.$toolbar.find('>.btn-group'),
  418. $btnClear = $btnGroup.find('div.export');
  419. if (!$btnClear.length) {
  420. $btnClear = $([
  421. '<button class="btn btn-default" ',
  422. sprintf('type="button" title="%s">', this.options.formatClearFilters()),
  423. sprintf('<i class="%s %s"></i> ', this.options.iconsPrefix, this.options.icons.clear),
  424. '</button>',
  425. '</ul>'].join('')).appendTo($btnGroup);
  426. $btnClear.off('click').on('click', $.proxy(this.clearFilterControl, this));
  427. }
  428. }
  429. };
  430. BootstrapTable.prototype.initHeader = function () {
  431. _initHeader.apply(this, Array.prototype.slice.apply(arguments));
  432. if (!this.options.filterControl) {
  433. return;
  434. }
  435. createControls(this, this.$header);
  436. };
  437. BootstrapTable.prototype.initBody = function () {
  438. _initBody.apply(this, Array.prototype.slice.apply(arguments));
  439. initFilterSelectControls(this);
  440. };
  441. BootstrapTable.prototype.initSearch = function () {
  442. _initSearch.apply(this, Array.prototype.slice.apply(arguments));
  443. if (this.options.sidePagination === 'server') {
  444. return;
  445. }
  446. var that = this;
  447. var fp = $.isEmptyObject(this.filterColumnsPartial) ? null : this.filterColumnsPartial;
  448. //Check partial column filter
  449. this.data = fp ? $.grep(this.data, function (item, i) {
  450. for (var key in fp) {
  451. var thisColumn = that.columns[$.fn.bootstrapTable.utils.getFieldIndex(that.columns, key)];
  452. var fval = fp[key].toLowerCase();
  453. var value = item[key];
  454. // Fix #142: search use formated data
  455. if (thisColumn && thisColumn.searchFormatter) {
  456. value = $.fn.bootstrapTable.utils.calculateObjectValue(that.header,
  457. that.header.formatters[$.inArray(key, that.header.fields)],
  458. [value, item, i], value);
  459. }
  460. if (thisColumn.filterStrictSearch) {
  461. if (!($.inArray(key, that.header.fields) !== -1 &&
  462. (typeof value === 'string' || typeof value === 'number') &&
  463. value.toString().toLowerCase() === fval.toString().toLowerCase())) {
  464. return false;
  465. }
  466. } else if (thisColumn.filterStartsWithSearch) {
  467. if (!($.inArray(key, that.header.fields) !== -1 &&
  468. (typeof value === 'string' || typeof value === 'number') &&
  469. (value + '').toLowerCase().indexOf(fval) === 0)) {
  470. return false;
  471. }
  472. } else {
  473. if (!($.inArray(key, that.header.fields) !== -1 &&
  474. (typeof value === 'string' || typeof value === 'number') &&
  475. (value + '').toLowerCase().indexOf(fval) !== -1)) {
  476. return false;
  477. }
  478. }
  479. }
  480. return true;
  481. }) : this.data;
  482. };
  483. BootstrapTable.prototype.onColumnSearch = function (event) {
  484. if ($.inArray(event.keyCode, [37, 38, 39, 40]) > -1) {
  485. return;
  486. }
  487. copyValues(this);
  488. var text = $.trim($(event.currentTarget).val());
  489. var $field = $(event.currentTarget).closest('[data-field]').data('field');
  490. if ($.isEmptyObject(this.filterColumnsPartial)) {
  491. this.filterColumnsPartial = {};
  492. }
  493. if (text) {
  494. this.filterColumnsPartial[$field] = text;
  495. } else {
  496. delete this.filterColumnsPartial[$field];
  497. }
  498. this.options.pageNumber = 1;
  499. this.onSearch(event);
  500. this.updatePagination();
  501. this.trigger('column-search', $field, text);
  502. };
  503. BootstrapTable.prototype.clearFilterControl = function () {
  504. if (this.options.filterControl && this.options.filterShowClear) {
  505. var that = this,
  506. cookies = collectBootstrapCookies(),
  507. header = getCurrentHeader(that),
  508. table = header.closest('table'),
  509. controls = header.find(getCurrentSearchControls(that)),
  510. search = that.$toolbar.find('.search input'),
  511. timeoutId = 0;
  512. $.each(that.options.valuesFilterControl, function (i, item) {
  513. item.value = '';
  514. });
  515. setValues(that);
  516. // Clear each type of filter if it exists.
  517. // Requires the body to reload each time a type of filter is found because we never know
  518. // which ones are going to be present.
  519. if (controls.length > 0) {
  520. this.filterColumnsPartial = {};
  521. $(controls[0]).trigger(controls[0].tagName === 'INPUT' ? 'keyup' : 'change');
  522. }
  523. if (search.length > 0) {
  524. that.resetSearch();
  525. }
  526. // use the default sort order if it exists. do nothing if it does not
  527. if (that.options.sortName !== table.data('sortName') || that.options.sortOrder !== table.data('sortOrder')) {
  528. var sorter = sprintf(header.find('[data-field="%s"]', $(controls[0]).closest('table').data('sortName')));
  529. that.onSort(table.data('sortName'), table.data('sortName'));
  530. $(sorter).find('.sortable').trigger('click');
  531. }
  532. // clear cookies once the filters are clean
  533. clearTimeout(timeoutId);
  534. timeoutId = setTimeout(function () {
  535. if (cookies && cookies.length > 0) {
  536. $.each(cookies, function (i, item) {
  537. that.deleteCookie(item);
  538. });
  539. }
  540. }, that.options.searchTimeOut);
  541. }
  542. };
  543. })(jQuery);