import ngModule from '../../module'; import Component from '../../lib/component'; import './style.scss'; import {buildFilter} from 'vn-loopback/util/filter'; /** * 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 {Function} onSearch Function to call when search is submited * @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 */ export default class Controller extends Component { constructor($element, $scope, $compile, $state, $transitions) { super($element, $scope); this.$element = $element; this.$compile = $compile; this.$state = $state; this.$scope = $scope; let criteria = {to: this.$state.current.name}; this.deregisterCallback = $transitions.onSuccess(criteria, () => this.onStateChange()); this._filter = null; this.autoLoad = false; } $postLink() { if (this.filter === null) this.onStateChange(); } set filter(value) { this._filter = value; this.$state.go('.', {q: JSON.stringify(value)}, {location: 'replace'}); } get filter() { return this._filter; } onStateChange() { this._filter = null; if (this.$state.params.q) { try { this._filter = JSON.parse(this.$state.params.q); } catch (e) {} } this.doSearch(); } get shownFilter() { return this._filter != null ? this._filter : this.suggestedFilter; } openPanel(event) { if (event.defaultPrevented) return; event.preventDefault(); this.$panelScope = this.$.$new(); this.$panel = this.$compile(`<${this.panel}/>`)(this.$panelScope); let panel = this.$panel.isolateScope().$ctrl; if (this.shownFilter) panel.filter = JSON.parse(JSON.stringify(this.shownFilter)); panel.onSubmit = filter => this.onPanelSubmit(filter); this.$.popover.parent = this.element; this.$.popover.child = this.$panel[0]; this.$.popover.show(); } onPopoverClose() { this.$panelScope.$destroy(); this.$panel.remove(); this.$panel = null; } onPanelSubmit(filter) { this.$.popover.hide(); filter = compact(filter); this.filter = filter != null ? filter : {}; let element = this.element.querySelector('vn-textfield input'); element.select(); element.focus(); } onSubmit() { this.filter = this.getObjectFromString(this.searchString); } doSearch() { this.searchString = this.getStringFromObject(this.shownFilter); 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 ); } else this.model.clear(); } } exprBuilder(param, value) { return {[param]: value}; } /** * Finds pattern key:value or key:(extra value) and passes it to object. * * @param {String} searchString The search string * @return {Object} The parsed object */ getObjectFromString(searchString) { let result = {}; if (searchString) { let regex = /((([\w_]+):([\w_]+))|([\w_]+):\(([\w_ ]+)\))/gi; let findPattern = searchString.match(regex); let remnantString = searchString.replace(regex, '').trim(); if (findPattern) { for (let i = 0; i < findPattern.length; i++) { let aux = findPattern[i].split(':'); let property = aux[0]; let value = aux[1].replace(/\(|\)/g, ''); result[property] = value.trim(); } } if (remnantString) result.search = remnantString; } return result; } /** * Passes an object to pattern key:value or key:(extra value). * * @param {Object} searchObject The search object * @return {String} The passed string */ getStringFromObject(searchObject) { let search = []; if (searchObject) { let keys = Object.keys(searchObject); keys.forEach(key => { if (key == 'search') return; let value = searchObject[key]; let valueString; if (typeof value === 'string' && value.indexOf(' ') !== -1) valueString = `(${value})`; else if (value instanceof Date) valueString = value.toJSON(); else { switch (typeof value) { case 'number': case 'string': case 'boolean': valueString = `${value}`; } } if (valueString) search.push(`${key}:${valueString}`); }); if (searchObject.search) search.unshift(searchObject.search); } return search.length ? search.join(' ') : ''; } $onDestroy() { this.deregisterCallback(); } } Controller.$inject = ['$element', '$scope', '$compile', '$state', '$transitions']; ngModule.component('vnSearchbar', { template: require('./searchbar.html'), bindings: { 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.substr(0, 2) == '$$' || compact(obj[key]) === undefined) delete obj[key]; } if (Object.keys(obj).length == 0) return undefined; } return obj; }