bootstrap-table-filter-control.js 25 KB

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