2018-06-07 21:47:19 +00:00
|
|
|
import ngModule from '../../module';
|
|
|
|
import Component from '../../lib/component';
|
|
|
|
import './style.scss';
|
2018-10-30 12:58:02 +00:00
|
|
|
import {buildFilter} from 'vn-loopback/common/filter.js';
|
2018-06-07 21:47:19 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
2018-10-30 12:58:02 +00:00
|
|
|
this.onSearch({$params: this.filter});
|
2018-06-07 21:47:19 +00:00
|
|
|
|
|
|
|
if (this.model) {
|
2018-10-30 12:58:02 +00:00
|
|
|
let where = buildFilter(this.filter,
|
|
|
|
(param, value) => this.exprBuilder({param, value}));
|
|
|
|
|
2018-09-05 11:01:21 +00:00
|
|
|
let userParams = {};
|
|
|
|
let hasParams = false;
|
2018-06-07 21:47:19 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
if (this.paramBuilder)
|
|
|
|
for (let param in this.filter) {
|
|
|
|
let value = this.filter[param];
|
|
|
|
if (value == null) continue;
|
2018-09-05 11:01:21 +00:00
|
|
|
let expr = this.paramBuilder({param, value});
|
|
|
|
if (expr) {
|
|
|
|
Object.assign(userParams, expr);
|
|
|
|
hasParams = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.model.applyFilter(
|
2018-10-30 12:58:02 +00:00
|
|
|
where ? {where} : null,
|
2018-09-05 11:01:21 +00:00
|
|
|
hasParams ? userParams : null
|
|
|
|
);
|
2018-06-07 21:47:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-05 11:01:21 +00:00
|
|
|
exprBuilder(param, value) {
|
|
|
|
return {[param]: value};
|
|
|
|
}
|
|
|
|
|
2018-06-07 21:47:19 +00:00
|
|
|
pushFilterToState(filter) {
|
2018-07-06 14:32:23 +00:00
|
|
|
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);
|
2018-06-07 21:47:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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();
|
2018-11-05 09:14:31 +00:00
|
|
|
if (findPattern)
|
2018-06-07 21:47:19 +00:00
|
|
|
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: '<?',
|
2018-10-30 12:58:02 +00:00
|
|
|
onSearch: '&?',
|
2018-06-07 21:47:19 +00:00
|
|
|
panel: '@',
|
|
|
|
model: '<?',
|
2018-09-05 11:01:21 +00:00
|
|
|
exprBuilder: '&?',
|
|
|
|
paramBuilder: '&?'
|
2018-06-07 21:47:19 +00:00
|
|
|
},
|
|
|
|
controller: Controller
|
|
|
|
});
|