UNPKG

21.8 kBJavaScriptView Raw
1/* eslint-disable no-use-before-define */
2const Utils = $.fn.bootstrapTable.utils
3const searchControls = 'select, input:not([type="checkbox"]):not([type="radio"])'
4
5export function getInputClass (that, isSelect = false) {
6 const formControlClass = isSelect ? that.constants.classes.select : that.constants.classes.input
7
8 return that.options.iconSize ? Utils.sprintf('%s %s-%s', formControlClass, formControlClass, that.options.iconSize) : formControlClass
9}
10
11export function getOptionsFromSelectControl (selectControl) {
12 return selectControl[0].options
13}
14
15export function getControlContainer (that) {
16 if (that.options.filterControlContainer) {
17 return $(`${that.options.filterControlContainer}`)
18 }
19
20 if (that.options.height && that._initialized) {
21 return $('.fixed-table-header table thead')
22 }
23
24 return that.$header
25}
26
27export function isKeyAllowed (keyCode) {
28 return $.inArray(keyCode, [37, 38, 39, 40]) > -1
29}
30
31export function getSearchControls (that) {
32 return getControlContainer(that).find(searchControls)
33}
34
35export function hideUnusedSelectOptions (selectControl, uniqueValues) {
36 const options = getOptionsFromSelectControl(selectControl)
37
38 for (let i = 0; i < options.length; i++) {
39 if (options[i].value !== '') {
40 if (!uniqueValues.hasOwnProperty(options[i].value)) {
41 selectControl.find(Utils.sprintf('option[value=\'%s\']', options[i].value)).hide()
42 } else {
43 selectControl.find(Utils.sprintf('option[value=\'%s\']', options[i].value)).show()
44 }
45 }
46 }
47}
48
49export function existOptionInSelectControl (selectControl, value) {
50 const options = getOptionsFromSelectControl(selectControl)
51
52 for (let i = 0; i < options.length; i++) {
53 if (options[i].value === Utils.unescapeHTML(value)) {
54 // The value is not valid to add
55 return true
56 }
57 }
58
59 // If we get here, the value is valid to add
60 return false
61}
62
63export function addOptionToSelectControl (selectControl, _value, text, selected, shouldCompareText) {
64 let value = (_value === undefined || _value === null) ? '' : _value.toString().trim()
65
66 value = Utils.removeHTML(Utils.unescapeHTML(value))
67 text = Utils.removeHTML(Utils.unescapeHTML(text))
68
69 if (existOptionInSelectControl(selectControl, value)) {
70 return
71 }
72
73 const isSelected = shouldCompareText ? (value === selected || text === selected) : value === selected
74
75 const option = new Option(text, value, false, isSelected)
76
77 selectControl.get(0).add(option)
78}
79
80export function sortSelectControl (selectControl, orderBy, options) {
81 const $selectControl = selectControl.get(0)
82
83 if (orderBy === 'server') {
84 return
85 }
86
87 const tmpAry = new Array()
88
89 for (let i = 0; i < $selectControl.options.length; i++) {
90 tmpAry[i] = new Array()
91 tmpAry[i][0] = $selectControl.options[i].text
92 tmpAry[i][1] = $selectControl.options[i].value
93 tmpAry[i][2] = $selectControl.options[i].selected
94 }
95
96 tmpAry.sort((a, b) => {
97 return Utils.sort(a[0], b[0], orderBy === 'desc' ? -1 : 1, options)
98 })
99 while ($selectControl.options.length > 0) {
100 $selectControl.options[0] = null
101 }
102
103 for (let i = 0; i < tmpAry.length; i++) {
104 const op = new Option(tmpAry[i][0], tmpAry[i][1], false, tmpAry[i][2])
105
106 $selectControl.add(op)
107 }
108}
109
110export function fixHeaderCSS ({ $tableHeader }) {
111 $tableHeader.css('height', $tableHeader.find('table').outerHeight(true))
112}
113
114export function getElementClass ($element) {
115 return $element.attr('class')
116 .replace('form-control', '')
117 .replace('form-select', '')
118 .replace('focus-temp', '')
119 .replace('search-input', '')
120 .trim()
121}
122
123export function getCursorPosition (el) {
124 if ($(el).is('input[type=search]')) {
125 let pos = 0
126
127 if ('selectionStart' in el) {
128 pos = el.selectionStart
129 } else if ('selection' in document) {
130 el.focus()
131 const Sel = document.selection.createRange()
132 const SelLength = document.selection.createRange().text.length
133
134 Sel.moveStart('character', -el.value.length)
135 pos = Sel.text.length - SelLength
136 }
137 return pos
138 }
139 return -1
140}
141
142export function cacheValues (that) {
143 const searchControls = getSearchControls(that)
144
145 that._valuesFilterControl = []
146
147 searchControls.each(function () {
148 let $field = $(this)
149 const fieldClass = getElementClass($field)
150
151 if (that.options.height && !that.options.filterControlContainer) {
152 $field = that.$el.find(`.fixed-table-header .${fieldClass}`)
153 } else if (that.options.filterControlContainer) {
154 $field = $(`${that.options.filterControlContainer} .${fieldClass}`)
155 } else {
156 $field = that.$el.find(`.${fieldClass}`)
157 }
158
159 that._valuesFilterControl.push({
160 field: $field.closest('[data-field]').data('field'),
161 value: $field.val(),
162 position: getCursorPosition($field.get(0)),
163 hasFocus: $field.is(':focus')
164 })
165 })
166}
167
168export function setCaretPosition (elem, caretPos) {
169 try {
170 if (elem) {
171 if (elem.createTextRange) {
172 const range = elem.createTextRange()
173
174 range.move('character', caretPos)
175 range.select()
176 } else {
177 elem.setSelectionRange(caretPos, caretPos)
178 }
179 }
180 }
181 catch (ex) {
182 // ignored
183 }
184}
185
186export function setValues (that) {
187 let field = null
188 let result = []
189 const searchControls = getSearchControls(that)
190
191 if (that._valuesFilterControl.length > 0) {
192 // Callback to apply after settings fields values
193 const callbacks = []
194
195 searchControls.each((i, el) => {
196 const $this = $(el)
197
198 field = $this.closest('[data-field]').data('field')
199 result = that._valuesFilterControl.filter(valueObj => valueObj.field === field)
200
201 if (result.length > 0) {
202 if (result[0].hasFocus || result[0].value) {
203 const fieldToFocusCallback = ((element, cacheElementInfo) => {
204 // Closure here to capture the field information
205 const closedCallback = () => {
206 if (cacheElementInfo.hasFocus) {
207 element.focus()
208 }
209
210 if (Array.isArray(cacheElementInfo.value)) {
211 const $element = $(element)
212
213 $.each(cacheElementInfo.value, function (i, e) {
214 $element.find(Utils.sprintf('option[value=\'%s\']', e)).prop('selected', true)
215 })
216 } else {
217 element.value = cacheElementInfo.value
218 }
219 setCaretPosition(element, cacheElementInfo.position)
220 }
221
222 return closedCallback
223 })($this.get(0), result[0])
224
225 callbacks.push(fieldToFocusCallback)
226 }
227 }
228 })
229
230 // Callback call.
231 if (callbacks.length > 0) {
232 callbacks.forEach(callback => callback())
233 }
234 }
235}
236
237export function collectBootstrapTableFilterCookies () {
238 const cookies = []
239 const foundCookies = document.cookie.match(/bs\.table\.(filterControl|searchText)/g)
240 const foundLocalStorage = localStorage
241
242 if (foundCookies) {
243 $.each(foundCookies, (i, _cookie) => {
244 let cookie = _cookie
245
246 if (/./.test(cookie)) {
247 cookie = cookie.split('.').pop()
248 }
249
250 if ($.inArray(cookie, cookies) === -1) {
251 cookies.push(cookie)
252 }
253 })
254 }
255 if (foundLocalStorage) {
256 for (let i = 0; i < foundLocalStorage.length; i++) {
257 let cookie = foundLocalStorage.key(i)
258
259 if (/./.test(cookie)) {
260 cookie = cookie.split('.').pop()
261 }
262
263 if (!cookies.includes(cookie)) {
264 cookies.push(cookie)
265 }
266 }
267 }
268 return cookies
269}
270
271export function escapeID (id) {
272 // eslint-disable-next-line no-useless-escape
273 return String(id).replace(/([:.\[\],])/g, '\\$1')
274}
275
276export function isColumnSearchableViaSelect ({ filterControl, searchable }) {
277 return filterControl && filterControl.toLowerCase() === 'select' && searchable
278}
279
280export function isFilterDataNotGiven ({ filterData }) {
281 return filterData === undefined ||
282 filterData.toLowerCase() === 'column'
283}
284
285export function hasSelectControlElement (selectControl) {
286 return selectControl && selectControl.length > 0
287}
288
289export function initFilterSelectControls (that) {
290 const data = that.options.data
291
292 $.each(that.header.fields, (j, field) => {
293 const column = that.columns[that.fieldsColumnsIndex[field]]
294 const selectControl = getControlContainer(that).find(`select.bootstrap-table-filter-control-${escapeID(column.field)}`)
295
296 if (isColumnSearchableViaSelect(column) && isFilterDataNotGiven(column) && hasSelectControlElement(selectControl)) {
297 if (!selectControl[0].multiple && selectControl.get(selectControl.length - 1).options.length === 0) {
298 // Added the default option, must use a non-breaking space(&nbsp;) to pass the W3C validator
299 addOptionToSelectControl(selectControl, '', column.filterControlPlaceholder || ' ', column.filterDefault)
300 }
301
302 const uniqueValues = {}
303
304 for (let i = 0; i < data.length; i++) {
305 // Added a new value
306 let fieldValue = Utils.getItemField(data[i], field, false)
307 const formatter = that.options.editable && column.editable ? column._formatter : that.header.formatters[j]
308 let formattedValue = Utils.calculateObjectValue(that.header, formatter, [fieldValue, data[i], i], fieldValue)
309
310 if (fieldValue === undefined || fieldValue === null) {
311 fieldValue = formattedValue
312 column._forceFormatter = true
313 }
314
315 if (column.filterDataCollector) {
316 formattedValue = Utils.calculateObjectValue(that.header, column.filterDataCollector, [fieldValue, data[i], formattedValue], formattedValue)
317 }
318
319 if (column.searchFormatter) {
320 fieldValue = formattedValue
321 }
322 uniqueValues[formattedValue] = fieldValue
323
324 if (typeof formattedValue === 'object' && formattedValue !== null) {
325 formattedValue.forEach(value => {
326 addOptionToSelectControl(selectControl, value, value, column.filterDefault)
327 })
328 continue
329 }
330 }
331
332 // eslint-disable-next-line guard-for-in
333 for (const key in uniqueValues) {
334 addOptionToSelectControl(selectControl, uniqueValues[key], key, column.filterDefault)
335 }
336
337 if (that.options.sortSelectOptions) {
338 sortSelectControl(selectControl, 'asc', that.options)
339 }
340 }
341 })
342}
343
344export function getFilterDataMethod (objFilterDataMethod, searchTerm) {
345 const keys = Object.keys(objFilterDataMethod)
346
347 for (let i = 0; i < keys.length; i++) {
348 if (keys[i] === searchTerm) {
349 return objFilterDataMethod[searchTerm]
350 }
351 }
352 return null
353}
354
355export function createControls (that, header) {
356 let addedFilterControl = false
357 let html
358
359 $.each(that.columns, (_, column) => {
360 html = []
361
362 if (
363 !column.visible &&
364 !(that.options.filterControlContainer && $(`.bootstrap-table-filter-control-${column.field}`).length >= 1)
365 ) {
366 return
367 }
368
369 if (!column.filterControl && !that.options.filterControlContainer) {
370 html.push('<div class="no-filter-control"></div>')
371 } else if (that.options.filterControlContainer) {
372 // Use a filter control container instead of th
373 const $filterControls = $(`.bootstrap-table-filter-control-${column.field}`)
374
375 $.each($filterControls, (_, filterControl) => {
376 const $filterControl = $(filterControl)
377
378 if (!$filterControl.is('[type=radio]')) {
379 const placeholder = column.filterControlPlaceholder || ''
380
381 $filterControl.attr('placeholder', placeholder).val(column.filterDefault)
382 }
383
384 $filterControl.attr('data-field', column.field)
385 })
386
387 addedFilterControl = true
388 } else {
389 // Create the control based on the html defined in the filterTemplate array.
390 const nameControl = column.filterControl.toLowerCase()
391
392 html.push('<div class="filter-control">')
393 addedFilterControl = true
394 if (column.searchable && that.options.filterTemplate[nameControl]) {
395 html.push(
396 that.options.filterTemplate[nameControl](
397 that,
398 column,
399 column.filterControlPlaceholder ?
400 column.filterControlPlaceholder :
401 '',
402 column.filterDefault
403 )
404 )
405 }
406 }
407
408 // Filtering by default when it is set.
409 if (column.filterControl && '' !== column.filterDefault && 'undefined' !== typeof column.filterDefault) {
410 if ($.isEmptyObject(that.filterColumnsPartial)) {
411 that.filterColumnsPartial = {}
412 }
413
414 if (!(column.field in that.filterColumnsPartial)) {
415 that.filterColumnsPartial[column.field] = column.filterDefault
416 }
417 }
418
419 $.each(header.find('th'), (_, th) => {
420 const $th = $(th)
421
422 if ($th.data('field') === column.field) {
423 $th.find('.filter-control').remove()
424 $th.find('.fht-cell').html(html.join(''))
425 return false
426 }
427 })
428
429 if (column.filterData && column.filterData.toLowerCase() !== 'column') {
430 const filterDataType = getFilterDataMethod(filterDataMethods, column.filterData.substring(0, column.filterData.indexOf(':')))
431 let filterDataSource
432 let selectControl
433
434 if (filterDataType) {
435 filterDataSource = column.filterData.substring(column.filterData.indexOf(':') + 1, column.filterData.length)
436 selectControl = header.find(`.bootstrap-table-filter-control-${escapeID(column.field)}`)
437
438 addOptionToSelectControl(selectControl, '', column.filterControlPlaceholder, column.filterDefault, true)
439 filterDataType(that, filterDataSource, selectControl, that.options.filterOrderBy, column.filterDefault)
440 } else {
441 throw new SyntaxError(
442 'Error. You should use any of these allowed filter data methods: var, obj, json, url, func.' +
443 ' Use like this: var: {key: "value"}'
444 )
445 }
446 }
447 })
448
449 if (addedFilterControl) {
450 header.off('keyup', 'input').on('keyup', 'input', ({ currentTarget, keyCode }, obj) => {
451 keyCode = obj ? obj.keyCode : keyCode
452
453 if (that.options.searchOnEnterKey && keyCode !== 13) {
454 return
455 }
456
457 if (isKeyAllowed(keyCode)) {
458 return
459 }
460
461 const $currentTarget = $(currentTarget)
462
463 if ($currentTarget.is(':checkbox') || $currentTarget.is(':radio')) {
464 return
465 }
466
467 clearTimeout(currentTarget.timeoutId || 0)
468 currentTarget.timeoutId = setTimeout(() => {
469 that.onColumnSearch({ currentTarget, keyCode })
470 }, that.options.searchTimeOut)
471 })
472
473 header.off('change', 'select', '.fc-multipleselect').on('change', 'select', '.fc-multipleselect', ({ currentTarget, keyCode }) => {
474 const $selectControl = $(currentTarget)
475 const value = $selectControl.val()
476
477 if (Array.isArray(value)) {
478 for (let i = 0; i < value.length; i++) {
479 if (value[i] && value[i].length > 0 && value[i].trim()) {
480 $selectControl.find(`option[value="${ value[i] }"]`).attr('selected', true)
481 }
482 }
483 }
484 else if (value && value.length > 0 && value.trim()) {
485 $selectControl.find('option[selected]').removeAttr('selected')
486 $selectControl.find(`option[value="${ value }"]`).attr('selected', true)
487 } else {
488 $selectControl.find('option[selected]').removeAttr('selected')
489 }
490
491 clearTimeout(currentTarget.timeoutId || 0)
492 currentTarget.timeoutId = setTimeout(() => {
493 that.onColumnSearch({ currentTarget, keyCode })
494 }, that.options.searchTimeOut)
495 })
496
497 header.off('mouseup', 'input:not([type=radio])').on('mouseup', 'input:not([type=radio])', ({ currentTarget, keyCode }) => {
498 const $input = $(currentTarget)
499 const oldValue = $input.val()
500
501 if (oldValue === '') {
502 return
503 }
504
505 setTimeout(() => {
506 const newValue = $input.val()
507
508 if (newValue === '') {
509 clearTimeout(currentTarget.timeoutId || 0)
510 currentTarget.timeoutId = setTimeout(() => {
511 that.onColumnSearch({ currentTarget, keyCode })
512 }, that.options.searchTimeOut)
513 }
514 }, 1)
515 })
516
517 header.off('change', 'input[type=radio]').on('change', 'input[type=radio]', ({ currentTarget, keyCode }) => {
518 clearTimeout(currentTarget.timeoutId || 0)
519 currentTarget.timeoutId = setTimeout(() => {
520 that.onColumnSearch({ currentTarget, keyCode })
521 }, that.options.searchTimeOut)
522 })
523
524 // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date
525 if (header.find('.date-filter-control').length > 0) {
526 $.each(that.columns, (i, { filterDefault, filterControl, field, filterDatepickerOptions }) => {
527 if (filterControl !== undefined && filterControl.toLowerCase() === 'datepicker') {
528 const $datepicker = header.find(`.date-filter-control.bootstrap-table-filter-control-${field}`)
529
530 if (filterDefault) {
531 $datepicker.value(filterDefault)
532 }
533
534 if (filterDatepickerOptions.min) {
535 $datepicker.attr('min', filterDatepickerOptions.min)
536 }
537
538 if (filterDatepickerOptions.max) {
539 $datepicker.attr('max', filterDatepickerOptions.max)
540 }
541
542 if (filterDatepickerOptions.step) {
543 $datepicker.attr('step', filterDatepickerOptions.step)
544 }
545
546 if (filterDatepickerOptions.pattern) {
547 $datepicker.attr('pattern', filterDatepickerOptions.pattern)
548 }
549
550 $datepicker.on('change', ({ currentTarget }) => {
551 clearTimeout(currentTarget.timeoutId || 0)
552 currentTarget.timeoutId = setTimeout(() => {
553 that.onColumnSearch({ currentTarget })
554 }, that.options.searchTimeOut)
555 })
556 }
557 })
558 }
559
560 if (that.options.sidePagination !== 'server') {
561 that.triggerSearch()
562 }
563
564 if (!that.options.filterControlVisible) {
565 header.find('.filter-control, .no-filter-control').hide()
566 }
567 } else {
568 header.find('.filter-control, .no-filter-control').hide()
569 }
570
571 that.trigger('created-controls')
572}
573
574export function getDirectionOfSelectOptions (_alignment) {
575 const alignment = _alignment === undefined ? 'left' : _alignment.toLowerCase()
576
577 switch (alignment) {
578 case 'left':
579 return 'ltr'
580 case 'right':
581 return 'rtl'
582 case 'auto':
583 return 'auto'
584 default:
585 return 'ltr'
586 }
587}
588
589export function syncHeaders (that) {
590 if (!that.options.height) {
591 return
592 }
593 const fixedHeader = $('.fixed-table-header table thead')
594
595 if (fixedHeader.length === 0) {
596 return
597 }
598
599 that.$header.children().find('th[data-field]').each((_, element) => {
600 if (element.classList[0] !== 'bs-checkbox') {
601 const $element = $(element)
602 const $field = $element.data('field')
603 const $fixedField = $(`th[data-field='${$field}']`).not($element)
604
605 const input = $element.find('input')
606 const fixedInput = $fixedField.find('input')
607
608 if (input.length > 0 && fixedInput.length > 0) {
609 if (input.val() !== fixedInput.val()) {
610 input.val(fixedInput.val())
611 }
612 }
613 }
614 })
615}
616
617const filterDataMethods = {
618 func (that, filterDataSource, selectControl, filterOrderBy, selected) {
619 const variableValues = window[filterDataSource].apply()
620
621 // eslint-disable-next-line guard-for-in
622 for (const key in variableValues) {
623 addOptionToSelectControl(selectControl, key, variableValues[key], selected)
624 }
625
626 if (that.options.sortSelectOptions) {
627 sortSelectControl(selectControl, filterOrderBy, that.options)
628 }
629
630 setValues(that)
631 },
632 obj (that, filterDataSource, selectControl, filterOrderBy, selected) {
633 const objectKeys = filterDataSource.split('.')
634 const variableName = objectKeys.shift()
635 let variableValues = window[variableName]
636
637 if (objectKeys.length > 0) {
638 objectKeys.forEach(key => {
639 variableValues = variableValues[key]
640 })
641 }
642
643 // eslint-disable-next-line guard-for-in
644 for (const key in variableValues) {
645 addOptionToSelectControl(selectControl, key, variableValues[key], selected)
646 }
647
648 if (that.options.sortSelectOptions) {
649 sortSelectControl(selectControl, filterOrderBy, that.options)
650 }
651
652 setValues(that)
653 },
654 var (that, filterDataSource, selectControl, filterOrderBy, selected) {
655 const variableValues = window[filterDataSource]
656 const isArray = Array.isArray(variableValues)
657
658 for (const key in variableValues) {
659 if (isArray) {
660 addOptionToSelectControl(selectControl, variableValues[key], variableValues[key], selected, true)
661 } else {
662 addOptionToSelectControl(selectControl, key, variableValues[key], selected, true)
663 }
664 }
665
666 if (that.options.sortSelectOptions) {
667 sortSelectControl(selectControl, filterOrderBy, that.options)
668 }
669
670 setValues(that)
671 },
672 url (that, filterDataSource, selectControl, filterOrderBy, selected) {
673 $.ajax({
674 url: filterDataSource,
675 dataType: 'json',
676 success (data) {
677 // eslint-disable-next-line guard-for-in
678 for (const key in data) {
679 addOptionToSelectControl(selectControl, key, data[key], selected)
680 }
681
682 if (that.options.sortSelectOptions) {
683 sortSelectControl(selectControl, filterOrderBy, that.options)
684 }
685
686 setValues(that)
687 }
688 })
689 },
690 json (that, filterDataSource, selectControl, filterOrderBy, selected) {
691 const variableValues = JSON.parse(filterDataSource)
692
693 // eslint-disable-next-line guard-for-in
694 for (const key in variableValues) {
695 addOptionToSelectControl(selectControl, key, variableValues[key], selected)
696 }
697
698 if (that.options.sortSelectOptions) {
699 sortSelectControl(selectControl, filterOrderBy, that.options)
700 }
701
702 setValues(that)
703 }
704}