import ngModule from '../../module'; import Component from '../../lib/component'; import './style.scss'; import {buildFilter} from 'vn-loopback/common/filter.js'; /** * 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.$compile = $compile; this.$state = $state; this.deregisterCallback = $transitions.onStart({}, transition => this.changeState(transition)); this.filter = {}; this.searchString = ''; } $onInit() { if (this.$state.params.q) this.filter = JSON.parse(decodeURIComponent(this.$state.params.q)); this.refreshString(); this.doSearch(); } changeState(transition) { return transition._targetState._identifier.name !== this.$state.current.name; } openPanel(event) { if (event.defaultPrevented) return; event.preventDefault(); this.$panel = this.$compile(`<${this.panel}/>`)(this.$.$new()); let panel = this.$panel.isolateScope().$ctrl; panel.filter = this.filter; panel.onSubmit = filter => this.onPanelSubmit(filter); this.$.popover.parent = this.element; this.$.popover.child = this.$panel[0]; this.$.popover.show(); } onPopoverClose() { this.$panel.scope().$destroy(); this.$panel.remove(); this.$panel = null; } onPanelSubmit(filter) { this.$.popover.hide(); for (let param in filter) if (filter[param] == null) delete filter[param]; this.filter = filter; this.refreshString(); this.doSearch(); } refreshString() { this.searchString = this.getStringFromObject(this.filter); } onSubmit() { this.filter = this.getObjectFromString(this.searchString); this.doSearch(); } doSearch() { this.pushFilterToState(this.filter); if (this.onSearch) this.onSearch({$params: this.filter}); if (this.model) { let where = buildFilter(this.filter, (param, value) => this.exprBuilder({param, value})); let userParams = {}; let hasParams = false; if (this.paramBuilder) for (let param in this.filter) { let value = this.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 ); } } exprBuilder(param, value) { return {[param]: value}; } pushFilterToState(filter) { let state = window.location.hash.split('?')[0]; let keys = Object.keys(filter); if (keys.length) { let hashFilter = {}; keys.forEach(key => { let value = filter[key]; if (value instanceof Date) hashFilter[key] = value; else switch (typeof value) { case 'number': case 'string': case 'boolean': hashFilter[key] = value; } }); let search = encodeURIComponent(JSON.stringify(hashFilter)); state += `?q=${search}`; } if (!window.history) throw new Error('Browser incompatibility: window.history not found'); window.history.pushState({}, null, state); } /** * 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: '