bootstrap-table-filter.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. !function($) {
  2. 'use strict';
  3. // TOOLS DEFINITION
  4. // ======================
  5. var rowLabel = function(el) {
  6. return typeof el === 'object' ? el.label : el;
  7. };
  8. var rowId = function(el) {
  9. return typeof el === 'object' ? el.id : el;
  10. };
  11. var getOptionData = function($option) {
  12. var val = false;
  13. var name;
  14. var data = {}, cnt = 0;
  15. var $chck = $option.find('.filter-enabled');
  16. $(':input', $option).each(function() {
  17. var $this = $(this);
  18. if ($this.is($chck)) {
  19. return;
  20. }
  21. name = $this.attr('data-name');
  22. if (name) {
  23. data[name] = $this.val();
  24. }
  25. val = $this.val();
  26. cnt++;
  27. });
  28. return $.isEmptyObject(data) ? val : data;
  29. };
  30. // FILTER CLASS DEFINITION
  31. // ======================
  32. var BootstrapTableFilter = function(el, options) {
  33. this.options = options;
  34. this.$el = $(el);
  35. this.$el_ = this.$el.clone();
  36. this.timeoutId_ = 0;
  37. this.filters = {};
  38. this.init();
  39. };
  40. BootstrapTableFilter.DEFAULTS = {
  41. filters: [],
  42. connectTo: false,
  43. onAll: function(name, args) {
  44. return false;
  45. },
  46. onFilterChanged: function(data) {
  47. return false;
  48. },
  49. onResetView: function() {
  50. return false;
  51. },
  52. onAddFilter: function(filter) {
  53. return false;
  54. },
  55. onRemoveFilter: function(field) {
  56. return false;
  57. },
  58. onEnableFilter: function(field) {
  59. return false;
  60. },
  61. onDisableFilter: function(field) {
  62. return false;
  63. },
  64. onSelectFilterOption: function(field, option, data) {
  65. return false;
  66. },
  67. onUnselectFilterOption: function(field, option) {
  68. return false;
  69. },
  70. onDataChanged: function(data) {
  71. return false;
  72. },
  73. onSubmit: function(data) {
  74. return false;
  75. },
  76. };
  77. BootstrapTableFilter.EVENTS = {
  78. 'all.bs.table.filter': 'onAll',
  79. 'reset.bs.table.filter': 'onResetView',
  80. 'add-filter.bs.table.filter': 'onAddFilter',
  81. 'remove-filter.bs.table.filter': 'onRemoveFilter',
  82. 'enable-filter.bs.table.filter': 'onEnableFilter',
  83. 'disable-filter.bs.table.filter': 'onDisableFilter',
  84. 'select-filter-option.bs.table.filter': 'onSelectFilterOption',
  85. 'unselect-filter-option.bs.table.filter': 'onUnselectFilterOption',
  86. 'data-changed.bs.table.filter': 'onDataChanged',
  87. 'submit.bs.table.filter': 'onSubmit'
  88. };
  89. BootstrapTableFilter.FILTER_SOURCES = {
  90. range: {
  91. search: false,
  92. rows: [
  93. {id: 'lte', label: 'Less than <input class="form-control" type="text">'},
  94. {id: 'gte', label: 'More than <input class="form-control" type="text">'},
  95. {id: 'eq', label: 'Equals <input class="form-control" type="text">'}
  96. ],
  97. check: function(filterData, value) {
  98. if (typeof filterData.lte !== 'undefined' && parseInt(value) > parseInt(filterData.lte)) {
  99. return false;
  100. }
  101. if (typeof filterData.gte !== 'undefined' && parseInt(value) < parseInt(filterData.gte)) {
  102. return false;
  103. }
  104. if (typeof filterData.eq !== 'undefined' && parseInt(value) != parseInt(filterData.eq)) {
  105. return false;
  106. }
  107. return true;
  108. }
  109. },
  110. ajaxSelect: {
  111. search: true,
  112. rows: [],
  113. rowsCallback: function(filter, searchPhrase) {
  114. var that = this;
  115. $.ajax(filter.source, {dataType: 'json', data: {q: searchPhrase}})
  116. .done(function(data) {
  117. that.clearFilterOptions(filter.field);
  118. that.fillFilterOptions(filter.field, data);
  119. });
  120. }
  121. },
  122. select: {
  123. search: true,
  124. rows: [],
  125. rowsCallback: function(filter, searchPhrase) {
  126. var vals = filter.values;
  127. var label;
  128. if (searchPhrase.length) {
  129. vals = vals.filter(function(el) {
  130. return rowLabel(el).indexOf(searchPhrase) > -1
  131. });
  132. }
  133. this.clearFilterOptions(filter.field);
  134. this.fillFilterOptions(filter.field, vals.slice(0, 20));
  135. }
  136. }
  137. };
  138. BootstrapTableFilter.EXTERNALS = [];
  139. BootstrapTableFilter.prototype.init = function() {
  140. this.initContainer();
  141. this.initMainButton();
  142. this.initFilters();
  143. this.initRefreshButton();
  144. this.initFilterSelector();
  145. this.initExternals();
  146. };
  147. BootstrapTableFilter.prototype.initContainer = function() {
  148. var that = this;
  149. this.$toolbar = $([
  150. '<div class="btn-toolbar">',
  151. '<div class="btn-group btn-group-filter-main">',
  152. '<button type="button" class="btn btn-default dropdown-toggle btn-filter" data-toggle="dropdown">',
  153. '<span class="glyphicon glyphicon-filter"></span>',
  154. '</button>',
  155. '<ul class="dropdown-menu" role="menu">',
  156. '</ul>',
  157. '</div>',
  158. '<div class="btn-group btn-group-filters">',
  159. '</div>',
  160. '<div class="btn-group btn-group-filter-refresh">',
  161. '<button type="button" class="btn btn-default btn-primary btn-refresh" data-toggle="dropdown">',
  162. '<span class="glyphicon glyphicon-repeat"></span>',
  163. '</button>',
  164. '</div>',
  165. '</div>'
  166. ].join(''));
  167. this.$toolbar.appendTo(this.$el);
  168. this.$filters = this.$toolbar.find('.btn-group-filters');
  169. this.$toolbar.delegate('.btn-group-filters li', 'click', function (e) {
  170. e.stopImmediatePropagation();
  171. });
  172. this.$toolbar.delegate('.btn-group-filters li .filter-enabled', 'click', function(e) {
  173. var $chck = $(this);
  174. var field = $chck.closest('[data-filter-field]').attr('data-filter-field');
  175. var $option = $chck.closest('[data-val]');
  176. var option = $option.attr('data-val');
  177. if ($chck.prop('checked')) {
  178. var data = getOptionData($option);
  179. that.selectFilterOption(field, option, data);
  180. }
  181. else {
  182. that.unselectFilterOption(field, option);
  183. }
  184. e.stopImmediatePropagation();
  185. });
  186. this.$toolbar.delegate('.btn-group-filters li :input:not(.filter-enabled)', 'click change', function(e) {
  187. var $inp = $(this);
  188. var field = $inp.closest('[data-filter-field]').attr('data-filter-field');
  189. var $option = $inp.closest('[data-val]');
  190. var option = $option.attr('data-val');
  191. var $chck = $option.find('.filter-enabled');
  192. if ($inp.val()) {
  193. var data = getOptionData($option);
  194. that.selectFilterOption(field, option, data);
  195. $chck.prop('checked', true);
  196. }
  197. else {
  198. that.unselectFilterOption(field, option);
  199. $chck.prop('checked', false);
  200. }
  201. e.stopImmediatePropagation();
  202. });
  203. this.$toolbar.delegate('.search-values', 'keyup', function(e) {
  204. var $this = $(this);
  205. var phrase = $this.val();
  206. var field = $this.closest('[data-filter-field]').attr('data-filter-field');
  207. var filter = that.getFilter(field);
  208. var fType = that.getFilterType(filter);
  209. if (fType.rowsCallback) {
  210. fType.rowsCallback.call(that, filter, phrase);
  211. }
  212. });
  213. };
  214. BootstrapTableFilter.prototype.initMainButton = function() {
  215. this.$button = this.$toolbar.find('.btn-filter');
  216. this.$buttonList = this.$button.parent().find('.dropdown-menu');
  217. this.$button.dropdown();
  218. };
  219. BootstrapTableFilter.prototype.initRefreshButton = function() {
  220. var that = this;
  221. this.$refreshButton = this.$toolbar.find('.btn-refresh');
  222. this.$refreshButton.click(function(e) {
  223. that.trigger('submit', that.getData());
  224. });
  225. };
  226. BootstrapTableFilter.prototype.initFilters = function() {
  227. var that = this;
  228. this.$buttonList.append('<li class="remove-filters"><a href="javascript:void(0)"><span class="glyphicon glyphicon-remove"></span> Remove all filters</a></li>');
  229. this.$buttonList.append('<li class="divider"></li>');
  230. $.each(this.options.filters, function(i, filter) {
  231. that.addFilter(filter);
  232. });
  233. this.$toolbar.delegate('.remove-filters *', 'click', function() {
  234. $.each(that.filters, function(i, filter) {
  235. that.disableFilter(filter.field)
  236. });
  237. });
  238. };
  239. BootstrapTableFilter.prototype.initFilterSelector = function() {
  240. var that = this;
  241. var applyFilter = function($chck) {
  242. var filterField = $chck.closest('[data-filter-field]').attr('data-filter-field');
  243. if ($chck.prop('checked')) {
  244. that.enableFilter(filterField);
  245. }
  246. else {
  247. that.disableFilter(filterField);
  248. }
  249. };
  250. this.$buttonList.delegate('li :input[type=checkbox]', 'click', function(e) {
  251. applyFilter($(this));
  252. e.stopImmediatePropagation();
  253. });
  254. this.$buttonList.delegate('li, li a', 'click', function(e) {
  255. var $chck = $(':input[type=checkbox]', this);
  256. if ($chck.length) {
  257. $chck.prop('checked', !$chck.is(':checked'));
  258. applyFilter($chck);
  259. e.stopImmediatePropagation();
  260. }
  261. var $inp = $(':input[type=text]', this);
  262. if ($inp.length) {
  263. $inp.focus();
  264. }
  265. });
  266. };
  267. BootstrapTableFilter.prototype.initExternals = function() {
  268. var that = this;
  269. $.each(BootstrapTableFilter.EXTERNALS, function(i, ext) {
  270. ext.call(that);
  271. });
  272. }
  273. BootstrapTableFilter.prototype.getFilter = function(field) {
  274. if (typeof this.filters[field] === 'undefined') {
  275. throw 'Invalid filter ' + field;
  276. }
  277. return this.filters[field];
  278. };
  279. BootstrapTableFilter.prototype.getFilterType = function(field, type) {
  280. if (field) {
  281. var filter = typeof field === 'object' ? field : this.getFilter(field);
  282. type = filter.type;
  283. }
  284. if (typeof BootstrapTableFilter.FILTER_SOURCES[type] === 'undefined') {
  285. throw 'Invalid filter type ' + type;
  286. }
  287. var ret = BootstrapTableFilter.FILTER_SOURCES[type];
  288. if (typeof ret.extend !== 'undefined') {
  289. ret = $.extend({}, ret, this.getFilterType(null, ret.extend));
  290. }
  291. return ret;
  292. };
  293. BootstrapTableFilter.prototype.checkFilterTypeValue = function(filterType, filterData, value) {
  294. if (typeof filterType.check === 'function') {
  295. return filterType.check(filterData, value);
  296. }
  297. else {
  298. if (typeof filterData._values !== 'undefined') {
  299. return $.inArray(value, filterData._values) >= 0;
  300. }
  301. }
  302. };
  303. BootstrapTableFilter.prototype.clearFilterOptions = function(field) {
  304. var filter = this.getFilter(field);
  305. filter.$dropdownList.find('li:not(.static)').remove();
  306. };
  307. BootstrapTableFilter.prototype.fillFilterOptions = function(field, data, cls) {
  308. var that = this;
  309. var filter = this.getFilter(field);
  310. cls = cls || '';
  311. var option, checked;
  312. $.each(data, function(i, row) {
  313. option = rowId(row);
  314. checked = that.isSelected(field, option);
  315. filter.$dropdownList.append($('<li data-val="' + option + '" class="' + cls + '"><a href="javascript:void(0)"><input type="checkbox" class="filter-enabled"' + (checked ? ' checked' : '') + '> ' + rowLabel(row) + '</a></li>'));
  316. });
  317. };
  318. BootstrapTableFilter.prototype.trigger = function(name) {
  319. var args = Array.prototype.slice.call(arguments, 1);
  320. name += '.bs.table.filter';
  321. if (typeof BootstrapTableFilter.EVENTS[name] === 'undefined') {
  322. throw 'Unknown event ' + name;
  323. }
  324. this.options[BootstrapTableFilter.EVENTS[name]].apply(this.options, args);
  325. this.$el.trigger($.Event(name), args);
  326. this.options.onAll(name, args);
  327. this.$el.trigger($.Event('all.bs.table.filter'), [name, args]);
  328. };
  329. // PUBLIC FUNCTION DEFINITION
  330. // =======================
  331. BootstrapTableFilter.prototype.resetView = function() {
  332. this.$el.html();
  333. this.init();
  334. this.trigger('reset');
  335. };
  336. BootstrapTableFilter.prototype.addFilter = function(filter) {
  337. this.filters[filter.field] = filter;
  338. this.$buttonList.append('<li data-filter-field="' + filter.field + '"><a href="javascript:void(0)"><input type="checkbox"> ' + filter.label + '</a></li>');
  339. this.trigger('add-filter', filter);
  340. if (typeof filter.enabled !== 'undefined' && filter.enabled) {
  341. this.enableFilter(filter.field);
  342. }
  343. };
  344. BootstrapTableFilter.prototype.removeFilter = function(field) {
  345. this.disableFilter(field);
  346. this.$buttonList.find('[data-filter-field=' + field + ']').remove();
  347. this.trigger('remove-filter', field);
  348. };
  349. BootstrapTableFilter.prototype.enableFilter = function(field) {
  350. var filter = this.getFilter(field);
  351. var $filterDropdown = $([
  352. '<div class="btn-group" data-filter-field="' + field + '">',
  353. '<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">',
  354. filter.label,
  355. ' <span class="caret"></span>',
  356. '</button>',
  357. '<ul class="dropdown-menu" role="menu">',
  358. '</ul>',
  359. '</div>'
  360. ].join(''));
  361. $filterDropdown.appendTo(this.$filters);
  362. filter.$dropdown = $filterDropdown;
  363. filter.$dropdownList = $filterDropdown.find('.dropdown-menu');
  364. filter.enabled = true;
  365. this.$buttonList.find('[data-filter-field=' + field + '] input[type=checkbox]').prop('checked', true);
  366. var fType = this.getFilterType(filter);
  367. if (fType.search) {
  368. filter.$dropdownList.append($('<li class="static"><span><input type="text" class="form-control search-values" placeholder="Search"></span></li>'));
  369. filter.$dropdownList.append($('<li class="static divider"></li>'));
  370. }
  371. if (fType.rows) {
  372. this.fillFilterOptions(field, fType.rows, 'static');
  373. }
  374. if (fType.rowsCallback) {
  375. fType.rowsCallback.call(this, filter, '');
  376. }
  377. this.trigger('enable-filter', filter);
  378. };
  379. BootstrapTableFilter.prototype.disableFilter = function(field) {
  380. var filter = this.getFilter(field);
  381. this.$buttonList.find('[data-filter-field=' + field + '] input[type=checkbox]').prop('checked', false);
  382. filter.enabled = false;
  383. if (filter.$dropdown) {
  384. filter.$dropdown.remove();
  385. delete filter.$dropdown;
  386. this.trigger('disable-filter', filter);
  387. }
  388. };
  389. BootstrapTableFilter.prototype.selectFilterOption = function(field, option, data) {
  390. var filter = this.getFilter(field);
  391. if (typeof filter.selectedOptions === 'undefined')
  392. filter.selectedOptions = {};
  393. if (data) {
  394. filter.selectedOptions[option] = data;
  395. }
  396. else {
  397. if (typeof filter.selectedOptions._values === 'undefined') {
  398. filter.selectedOptions._values = [];
  399. }
  400. filter.selectedOptions._values.push(option);
  401. }
  402. this.trigger('select-filter-option', field, option, data);
  403. };
  404. BootstrapTableFilter.prototype.unselectFilterOption = function(field, option) {
  405. var filter = this.getFilter(field);
  406. if (typeof filter.selectedOptions !== 'undefined' && typeof filter.selectedOptions[option] !== 'undefined') {
  407. delete filter.selectedOptions[option];
  408. }
  409. if (typeof filter.selectedOptions !== 'undefined' && typeof filter.selectedOptions._values !== 'undefined') {
  410. filter.selectedOptions._values = filter.selectedOptions._values.filter(function(item) {
  411. return item != option
  412. });
  413. if (filter.selectedOptions._values.length == 0) {
  414. delete filter.selectedOptions._values;
  415. }
  416. if ($.isEmptyObject(filter.selectedOptions)) {
  417. delete filter.selectedOptions;
  418. }
  419. }
  420. this.trigger('unselect-filter-option', field, option);
  421. };
  422. BootstrapTableFilter.prototype.isSelected = function(field, option, value) {
  423. var filter = this.getFilter(field);
  424. if (typeof filter.selectedOptions !== 'undefined') {
  425. if (typeof filter.selectedOptions[option] !== 'undefined') {
  426. if (value ? (filter.selectedOptions[option] == value) : filter.selectedOptions[option]) {
  427. return true
  428. }
  429. }
  430. if (typeof filter.selectedOptions._values !== 'undefined') {
  431. if (filter.selectedOptions._values.indexOf(option.toString()) > -1) {
  432. return true;
  433. }
  434. }
  435. }
  436. return false;
  437. };
  438. BootstrapTableFilter.prototype.getData = function() {
  439. var that = this;
  440. var ret = {};
  441. $.each(that.filters, function(field, filter) {
  442. if (filter.enabled) {
  443. if (typeof filter.selectedOptions !== 'undefined') {
  444. ret[field] = filter.selectedOptions;
  445. }
  446. }
  447. });
  448. return ret;
  449. };
  450. // BOOTSTRAP FILTER TABLE PLUGIN DEFINITION
  451. // =======================
  452. $.fn.bootstrapTableFilter = function(option, _relatedTarget) {
  453. BootstrapTableFilter.externals = this.externals;
  454. var allowedMethods = [
  455. 'addFilter', 'removeFilter',
  456. 'enableFilter', 'disableFilter',
  457. 'selectFilterOption', 'unselectFilterOption',
  458. 'getData', 'isSelected',
  459. 'resetView'
  460. ],
  461. value;
  462. this.each(function() {
  463. var $this = $(this),
  464. data = $this.data('bootstrap.tableFilter'),
  465. options = $.extend(
  466. {}, BootstrapTableFilter.DEFAULTS, $this.data(),
  467. typeof option === 'object' && option
  468. );
  469. if (typeof option === 'string') {
  470. if ($.inArray(option, allowedMethods) < 0) {
  471. throw "Unknown method: " + option;
  472. }
  473. if (!data) {
  474. return;
  475. }
  476. value = data[option](_relatedTarget);
  477. if (option === 'destroy') {
  478. $this.removeData('bootstrap.tableFilter');
  479. }
  480. }
  481. if (!data) {
  482. $this.data('bootstrap.tableFilter', (data = new BootstrapTableFilter(this, options)));
  483. }
  484. });
  485. return typeof value === 'undefined' ? this : value;
  486. };
  487. $.fn.bootstrapTableFilter.Constructor = BootstrapTableFilter;
  488. $.fn.bootstrapTableFilter.defaults = BootstrapTableFilter.DEFAULTS;
  489. $.fn.bootstrapTableFilter.columnDefaults = BootstrapTableFilter.COLUMN_DEFAULTS;
  490. $.fn.bootstrapTableFilter.externals = BootstrapTableFilter.EXTERNALS;
  491. // BOOTSTRAP TABLE FILTER INIT
  492. // =======================
  493. $(function() {
  494. $('[data-toggle="tableFilter"]').bootstrapTableFilter();
  495. });
  496. }(jQuery);