salix/front/core/components/searchbar/searchbar.js

255 lines
7.5 KiB
JavaScript

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.$compile = $compile;
this.$state = $state;
let criteria = {to: this.$state.current.name};
this.deregisterCallback = $transitions.onSuccess(criteria,
() => this.onStateChange());
this._filter = null;
this.searchString = '';
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();
}
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;
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.$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() {
let filter = this._filter;
if (filter == null && this.autoload)
filter = {};
this.searchString = this.getStringFromObject(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: '<?',
onSearch: '&?',
panel: '@',
model: '<?',
exprBuilder: '&?',
paramBuilder: '&?',
autoLoad: '<?'
},
controller: Controller
});
/**
* Removes null, undefined, empty objects and empty arrays from an object.
* It also applies to nested objects/arrays.
*
* @param {*} obj The value to format
* @return {*} The formatted value
*/
function compact(obj) {
if (obj == null)
return undefined;
else if (Array.isArray(obj)) {
for (let i = obj.length - 1; i >= 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;
}