<template> <div :id="'search-field-' + _uid" class="searh-field tags-input__wrapper"> <div class="tags-group" v-for="(filter, index) in filtered" :index="index"> <span v-if="filter.option" class="el-tag el-tag--primary el-tag--small el-tag--light el-tag-option"> {{ filter.option }} </span> <span v-if="filter.operator" class="el-tag el-tag--primary el-tag--small el-tag--light el-tag-operator"> {{ (filter.operator == '=') ? operatorIsText : operatorIsNotText }} </span> <span v-if="filter.value" class="el-tag el-tag--primary el-tag--small el-tag--light el-tag-value"> {{ filter.value }} <i class="el-tag__close el-icon-close" @click="onFilterDelete(index)"></i> </span> </div> <div class="dropdown input-full"> <input v-if="!show_date" type="text" class="form-control" :placeholder="placeholder" :ref="'input-search-field-' + _uid" v-model="search" @focus="onInputFocus" @input="onInput" @keyup.enter="onInputConfirm" /> <flat-picker v-else @on-open="onInputFocus" :config="dateConfig" class="form-control datepicker" :placeholder="placeholder" :ref="'input-search-date-field-' + _uid" v-model="search" @focus="onInputFocus" @input="onInputDateSelected" @keyup.enter="onInputConfirm" > </flat-picker> <button type="button" class="btn btn-link clear" @click="onSearchAndFilterClear"> <i class="el-tag__close el-icon-close"></i> </button> <div :id="'search-field-option-' + _uid" class="dropdown-menu" :class="[{'show': visible.options}]"> <li ref="" class="dropdown-item" v-for="option in filteredOptions" :data-value="option.key"> <button type="button" class="btn btn-link" @click="onOptionSelected(option.key)">{{ option.value }}</button> </li> <li ref="" v-if="search" class="dropdown-item"> <button type="button" class="btn btn-link" @click="onInputConfirm">{{ searchText }}</button> </li> </div> <div :id="'search-field-operator-' + _uid" class="dropdown-menu" :class="[{'show': visible.operator}]"> <li ref="" class="dropdown-item"> <button type="button" class="btn btn-link" @click="onOperatorSelected('=')">{{ operatorIsText }}<span class="btn-helptext d-none">is</span></button> </li> <li ref="" class="dropdown-item"> <button type="button" class="btn btn-link" @click="onOperatorSelected('!=')">{{ operatorIsNotText }}<span class="btn-helptext d-none">is not</span></button> </li> </div> <div :id="'search-field-value-' + _uid" class="dropdown-menu" :class="[{'show': visible.values}]"> <li ref="" class="dropdown-item" v-for="(value) in filteredValues" :data-value="value.key"> <button type="button" class="btn btn-link" @click="onValueSelected(value.key)">{{ value.value }}</button> </li> <li ref="" class="dropdown-item" v-if="!filteredValues.length"> <button type="button" class="btn btn-link">{{ noMatchingDataText }}</button> </li> </div> </div> </div> </template> <script> import flatPicker from "vue-flatpickr-component"; import "flatpickr/dist/flatpickr.css"; export default { name: 'akaunting-search', components: { flatPicker }, props: { placeholder: { type: String, default: 'Search or filter results...', description: 'Input placeholder' }, searchText: { type: String, default: 'Search for this text', description: 'Input placeholder' }, operatorIsText: { type: String, default: 'is', description: 'Operator is "="' }, operatorIsNotText: { type: String, default: 'is not', description: 'Operator is not "!="' }, noDataText: { type: String, default: 'No Data', description: "Selectbox empty options message" }, noMatchingDataText: { type: String, default: 'No Matchign Data', description: "Selectbox search option not found item message" }, value: { type: String, default: null, description: 'Search attribute value' }, filters: { type: Array, default: () => [], description: 'List of filters' }, dateConfig: null, }, model: { prop: 'value', event: 'change' }, data() { return { filter_list: this.filters, // set filters prop assing it. search: '', // search cloumn model filtered:[], // Show selected filters filter_index: 0, // added filter count filter_last_step: 'options', // last fitler step visible: { // Which visible dropdown options: false, operator: false, values: false, }, option_values: [], selected_options: [], selected_values: [], values: [], current_value: null, show_date: false, }; }, methods: { onInputFocus() { if (!this.filter_list.length) { return; } if (this.filter_last_step != 'values' || (this.filter_last_step == 'values' && this.selected_options[this.filter_index].type != 'date')) { this.visible[this.filter_last_step] = true; this.$nextTick(() => { this.$refs['input-search-field-' + this._uid].focus(); }); } else { this.show_date = true; this.$nextTick(() => { this.$refs['input-search-date-field-' + this._uid].focus(); }); } console.log('Focus :' + this.filter_last_step); }, onInputDateSelected(event) { this.filtered[this.filter_index].value = event; this.selected_values.push({ key: event, value: event, }); this.$emit('change', this.filtered); this.show_date = false; this.search = ''; this.$nextTick(() => { this.$refs['input-search-field-' + this._uid].focus(); }); this.filter_index++; if (this.filter_list.length) { this.visible = { options: true, operator: false, values: false, }; } else { this.visible = { options: false, operator: false, values: false, }; } this.show_date = false; this.filter_last_step = 'options'; }, onInput(evt) { this.search = evt.target.value; let option_url = this.selected_options[this.filter_index].url; if (this.search) { option_url += '?search="' + this.search + '" limit:10'; } if (option_url) { window.axios.get(option_url) .then(response => { this.values = []; let data = response.data.data; data.forEach(function (item) { this.values.push({ key: item.id, value: item.name }); }, this); this.option_values[value] = this.values; }) .catch(error => { }); } this.$emit('input', evt.target.value); }, onInputConfirm() { let path = window.location.href.replace(window.location.search, ''); let args = ''; if (this.search) { args += '?search="' + this.search + '" '; } let now = new Date(); now.setTime(now.getTime() + 1 * 3600 * 1000); let expires = now.toUTCString(); let serach_string = {}; serach_string[path] = {}; this.filtered.forEach(function (filter, index) { if (!args) { args += '?search='; } args += this.selected_options[index].key + ':' + this.selected_values[index].key + ' '; serach_string[path][this.selected_options[index].key] = { 'key': this.selected_values[index].key, 'value': this.selected_values[index].value }; }, this); Cookies.set('search-string', serach_string, expires); window.location = path + args; }, onOptionSelected(value) { this.current_value = value; let option = false; let option_url = false; for (let i = 0; i < this.filter_list.length; i++) { if (this.filter_list[i].key == value) { option = this.filter_list[i].value; if (typeof this.filter_list[i].url !== 'undefined' && this.filter_list[i].url) { option_url = this.filter_list[i].url; } if (typeof this.filter_list[i].type !== 'undefined' && this.filter_list[i].type == 'boolean') { this.option_values[value] = this.filter_list[i].values; } this.selected_options.push(this.filter_list[i]); this.filter_list.splice(i, 1); break; } } // Set filtered select option this.filtered.push({ option: option, operator: false, value: false }); this.$emit('change', this.filtered); this.search = ''; if (!this.option_values[value] && option_url) { window.axios.get(option_url) .then(response => { let data = response.data.data; data.forEach(function (item) { this.values.push({ key: (item.code) ? item.code : item.id, value: (item.title) ? item.title : (item.display_name) ? item.display_name : item.name }); }, this); this.option_values[value] = this.values; }) .catch(error => { }); } else { this.values = (this.option_values[value]) ? this.option_values[value] : []; } this.$nextTick(() => { this.$refs['input-search-field-' + this._uid].focus(); }); this.visible = { options: false, operator: true, values: false, }; this.filter_last_step = 'operator'; }, onOperatorSelected(value) { this.filtered[this.filter_index].operator = value; this.$emit('change', this.filtered); if (this.selected_options[this.filter_index].type != 'date') { this.$nextTick(() => { this.$refs['input-search-field-' + this._uid].focus(); }); this.visible = { options: false, operator: false, values: true, }; } else { this.show_date = true; this.$nextTick(() => { this.$refs['input-search-date-field-' + this._uid].focus(); }); this.visible = { options: false, operator: false, values: false, }; } this.filter_last_step = 'values'; }, onValueSelected(value) { let select_value = false; for (let i = 0; i < this.values.length; i++) { if (this.values[i].key == value) { select_value = this.values[i].value; this.selected_values.push(this.values[i]); this.option_values[this.current_value].splice(i, 1); break; } } this.filtered[this.filter_index].value = select_value; this.$emit('change', this.filtered); this.$nextTick(() => { this.$refs['input-search-field-' + this._uid].focus(); }); this.filter_index++; if (this.filter_list.length) { this.visible = { options: true, operator: false, values: false, }; } else { this.visible = { options: false, operator: false, values: false, }; } this.show_date = false; this.filter_last_step = 'options'; }, onFilterDelete(index) { this.filter_list.push(this.selected_options[index]); this.filter_index--; this.filtered.splice(index, 1); this.selected_options.splice(index, 1); }, onSearchAndFilterClear() { this.filtered = []; this.search = ''; Cookies.remove('search-string'); this.onInputConfirm(); }, closeIfClickedOutside(event) { if (!document.getElementById('search-field-' + this._uid).contains(event.target) && event.target.className != 'btn btn-link') { this.visible.options = false; this.visible.operator = false; this.visible.values = false; document.removeEventListener('click', this.closeIfClickedOutside); } }, }, created() { let path = window.location.href.replace(window.location.search, ''); let cookie = Cookies.get('search-string'); cookie = JSON.parse(cookie)[path]; if (this.value) { let serach_string = this.value.split(' '); serach_string.forEach(function (string) { if (string.search(':') === -1) { this.search = string; } else { let filter = string.split(':'); let option = ''; let value = ''; this.filter_list.forEach(function (_filter, i) { if (_filter.key == filter[0]) { option = _filter.value; _filter.values.forEach(function (_value) { if (_value.key == filter[1]) { value = _value.value; } }, this); if (!value && cookie[_filter.key]) { value = cookie[_filter.key].value; } this.selected_options.push(this.filter_list[i]); this.filter_list.splice(i, 1); this.option_values[_filter.key] = _filter.values; _filter.values.forEach(function (value, j) { if (value.key == filter[1]) { this.selected_values.push(value); this.option_values[_filter.key].splice(j, 1); } }, this); if (cookie[_filter.key]) { this.selected_values.push(cookie[_filter.key]); } } }, this); this.filtered.push({ option: option, operator: '=', value: value }); this.filter_index++; } }, this); } }, computed: { filteredOptions() { this.filter_list.sort(function (a, b) { var nameA = a.value.toUpperCase(); // ignore upper and lowercase var nameB = b.value.toUpperCase(); // ignore upper and lowercase if (nameA < nameB) { return -1; } if (nameA > nameB) { return 1; } // names must be equal return 0; }); return this.filter_list.filter(option => { return option.value.toLowerCase().includes(this.search.toLowerCase()) }); }, filteredValues() { this.values.sort(function (a, b) { var nameA = a.value.toUpperCase(); // ignore upper and lowercase var nameB = b.value.toUpperCase(); // ignore upper and lowercase if (nameA < nameB) { return -1; } if (nameA > nameB) { return 1; } // names must be equal return 0; }); return this.values.filter(value => { return value.value.toLowerCase().includes(this.search.toLowerCase()) }) } }, watch: { visible: { handler: function(newValue) { if (newValue) { document.addEventListener('click', this.closeIfClickedOutside); } }, deep: true } }, }; </script> <style> .searh-field { border: 1px solid #dee2e6; border-radius: 0.25rem; } .searh-field .tags-group { display: contents; } .searh-field .el-tag.el-tag--primary { background: #f6f9fc; background-color: #f6f9fc; border-color: #f6f9fc; color: #8898aa; margin-top: 7px; } .searh-field .tags-group:hover > span { background:#cbd4de; background-color: #cbd4de; border-color: #cbd4de; } .searh-field .el-tag.el-tag--primary .el-tag__close.el-icon-close { color: #8898aa; } .searh-field .el-tag-option { border-radius: 0.25rem 0 0 0.25rem; margin-left: 10px; } .searh-field .el-tag-operator { border-radius: 0; margin-left: -1px; margin-right: -1px; } .searh-field .el-tag-value { border-radius: 0 0.25rem 0.25rem 0; margin-right: 10px; } .searh-field .el-select.input-new-tag { width: 100%; } .searh-field .input-full { width: 100%; } .searh-field .btn.btn-link { width: inherit; text-align: left; display: flex; margin: 0; text-overflow: inherit; text-align: left; text-overflow: ellipsis; padding: unset; color: #212529; } .searh-field .btn.btn-link.clear { position: absolute; top: 0; right: 0; width: 45px; height: 45px; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; -webkit-box-align: center; -ms-flex-align: center; align-items: center; color: #adb5bd; opacity: 1; } .searh-field .btn.btn-link.clear:hover { color: #8898aa; } .searh-field .btn-helptext { margin-left: auto; color: var(--gray); } .searh-field .btn:not(:disabled):not(.disabled):active:focus, .searh-field .btn:not(:disabled):not(.disabled).active:focus { -webkit-box-shadow: none !important; box-shadow: none !important; } .searh-field .form-control.datepicker.flatpickr-input { padding: inherit !important; } </style>