import ngModule from '../../module'; import Component from '../../lib/component'; import {buildFilter} from 'vn-loopback/util/filter'; import focus from '../../lib/focus'; import './style.scss'; /** * An input specialized to perform searches, it allows to use a panel * for advanced searches when the panel property is defined. * When model and exprBuilder properties are used, the model is updated * automatically with an and-filter exprexion in which each operand is built * by calling the exprBuilder function for each non-null parameter. * * @property {Object} filter A key-value object with filter parameters * @property {SearchPanel} panel The panel used for advanced searches * @property {CrudModel} model The model used for searching * @property {Function} exprBuilder If defined, is used to build each non-null param expresion * @property {Function} onSearch Function to call when search is submited */ export default class Searchbar extends Component { constructor($element, $) { super($element, $); this.searchState = '.'; let criteria = {}; this.deregisterCallback = this.$transitions.onSuccess( criteria, () => this.onStateChange()); } $postLink() { this.onStateChange(); if (this.$state.is(this.searchState) && this.filter !== null) this.doSearch(); } $onDestroy() { this.deregisterCallback(); } get filter() { return this._filter; } set filter(value) { this._filter = value; this.toBar(value); } get shownFilter() { return this.filter != null ? this.filter : this.suggestedFilter; } get searchString() { return this._searchString; } set searchString(value) { this._searchString = value; if (value == null) this.params = []; } onStateChange() { let filter = null; let isIndex = this.$state.is(this.searchState); if (isIndex) { if (this.$params.q) { try { filter = JSON.parse(this.$params.q); } catch (e) { console.error(e); } } focus(this.element.querySelector('vn-textfield input')); } this.filter = filter; if (isIndex) this.doSearch(); else if (this.model) this.model.clear(); } openPanel(event) { if (event.defaultPrevented) return; this.$.popover.show(this.element); this.$panelScope = this.$.$new(); this.panelEl = this.$compile(`<${this.panel}/>`)(this.$panelScope)[0]; let panel = this.panelEl.$ctrl; if (this.shownFilter) panel.filter = JSON.parse(JSON.stringify(this.shownFilter)); panel.onSubmit = filter => this.onPanelSubmit(filter.$filter); this.$.popover.content.appendChild(this.panelEl); } onPopoverClose() { this.$panelScope.$destroy(); this.$panelScope = null; this.panelEl.remove(); this.panelEl = null; } onPanelSubmit(filter) { this.$.popover.hide(); filter = compact(filter); filter = filter != null ? filter : {}; this.goSearch(filter); } onSubmit() { this.goSearch(this.fromBar()); } removeParam(index) { this.params.splice(index, 1); this.goSearch(this.fromBar()); } goSearch(filter) { this.filter = filter; this.doSearch(); } fromBar() { let filter = {}; if (this.searchString) filter.search = this.searchString; if (this.params) { for (let param of this.params) filter[param.key] = param.value; } return filter; } toBar(filter) { this.params = []; this.searchString = filter && filter.search; if (!filter) return; let keys = Object.keys(filter); keys.forEach(key => { if (key == 'search') return; let value = filter[key]; let chip; if (typeof value == 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) value = new Date(value); switch (typeof value) { case 'boolean': chip = `${value ? '' : 'not '}${key}`; break; case 'number': case 'string': chip = `${key}: ${value}`; break; default: if (value instanceof Date) { let format = 'yyyy-MM-dd'; if (value.getHours() || value.getMinutes()) format += ' HH:mm'; chip = `${key}: ${this.$filter('date')(value, format)}`; } else chip = key; } this.params.push({chip, key, value}); }); } doSearch() { let filter = this.filter; if (filter == null && this.autoload) filter = {}; if (this.onSearch) this.onSearch({$params: filter}); if (this.model) { if (filter !== null) { let where = buildFilter(filter, (param, value) => this.exprBuilder({param, value})); let userParams = {}; let hasParams = false; if (this.paramBuilder) { for (let param in filter) { let value = filter[param]; if (value == null) continue; let expr = this.paramBuilder({param, value}); if (expr) { Object.assign(userParams, expr); hasParams = true; } } } this.model.applyFilter( where ? {where} : null, hasParams ? userParams : null ).then(() => this.onModelFilter()); } else this.model.clear(); } } onModelFilter() { let params; let opts; let data = this.model.data; let currentState = this.$state.current.name; let stateParts = currentState.split('.'); let baseState = stateParts[0]; let subState = stateParts[1]; if (this.goState && data && data.length == 1) { switch (subState) { case 'index': subState = 'card.summary'; break; case 'card': subState += `.${stateParts[2]}`; break; } if (this.goStateParams) params = this.goStateParams({$row: data[0]}); } else { if (subState == 'index') opts = {location: 'replace'}; subState = `index`; params = {q: JSON.stringify(this.filter)}; } this.$state.go(`${baseState}.${subState}`, params, opts); } exprBuilder(param, value) { return {[param]: value}; } } ngModule.vnComponent('vnSearchbar', { controller: Searchbar, template: require('./searchbar.html'), bindings: { searchState: '@?', filter: '= 0; i--) { if (compact(obj[i]) === undefined) obj.splice(i, 1); } if (obj.length == 0) return undefined; } else if (typeof obj == 'object' && obj.constructor == Object) { let keys = Object.keys(obj); for (let key of keys) { if (key.charAt(0) == '$' || compact(obj[key]) === undefined) delete obj[key]; } if (Object.keys(obj).length == 0) return undefined; } return obj; }