1574 lines
47 KiB
JavaScript
1574 lines
47 KiB
JavaScript
/*
|
|
* Axelor Business Solutions
|
|
*
|
|
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
(function() {
|
|
|
|
'use strict';
|
|
|
|
var ui = angular.module('axelor.ui');
|
|
|
|
var OPERATORS = {
|
|
"=" : _t("equals"),
|
|
"!=" : _t("not equal"),
|
|
">=" : _t("greater or equal"),
|
|
"<=" : _t("less or equal"),
|
|
">" : _t("greater than"),
|
|
"<" : _t("less than"),
|
|
|
|
"like" : _t("contains"),
|
|
"notLike" : _t("doesn't contain"),
|
|
|
|
"in" : _t("in"),
|
|
"notIn" : _t("not in"),
|
|
|
|
"between" : _t("in range"),
|
|
"notBetween" : _t("not in range"),
|
|
|
|
"isNull" : _t("is null"),
|
|
"notNull" : _t("is not null"),
|
|
|
|
"true" : _t("is true"),
|
|
"false" : _t("is false")
|
|
};
|
|
|
|
var OPERATORS_BY_TYPE = {
|
|
"enum" : ["=", "!=", "isNull", "notNull"],
|
|
"text" : ["like", "notLike", "isNull", "notNull"],
|
|
"string" : ["=", "!=", "like", "notLike", "isNull", "notNull"],
|
|
"integer" : ["=", "!=", ">=", "<=", ">", "<", "between", "notBetween", "isNull", "notNull"],
|
|
"boolean" : ["true", "false"]
|
|
};
|
|
|
|
_.each(["long", "decimal", "date", "time", "datetime"], function(type) {
|
|
OPERATORS_BY_TYPE[type] = OPERATORS_BY_TYPE.integer;
|
|
});
|
|
|
|
_.each(["one-to-many"], function(type) {
|
|
OPERATORS_BY_TYPE[type] = ["isNull", "notNull"];
|
|
});
|
|
|
|
_.each(["one-to-one", "many-to-one", "many-to-many"], function(type) {
|
|
OPERATORS_BY_TYPE[type] = ["like", "notLike", "in", "notIn", "isNull", "notNull"];
|
|
});
|
|
|
|
|
|
function sharedProperty(scope, handler, property, initialValue) {
|
|
var ds = handler._dataSource;
|
|
if (ds) {
|
|
var adv = ds._advSearch || (ds._advSearch = {});
|
|
adv[property] = adv[property] || initialValue;
|
|
Object.defineProperty(scope, property, {
|
|
get: function () { return adv[property]; },
|
|
set: function (value) { adv[property] = value; }
|
|
});
|
|
}
|
|
}
|
|
|
|
ui.directive('uiFilterItem', function() {
|
|
|
|
return {
|
|
replace: true,
|
|
require: '^uiFilterForm',
|
|
scope: {
|
|
fields: "=",
|
|
filter: "=",
|
|
model: "="
|
|
},
|
|
link: function(scope, element, attrs, form) {
|
|
|
|
function getOperators() {
|
|
|
|
if (element.is(':hidden')) {
|
|
return;
|
|
}
|
|
|
|
var filter = scope.filter || {};
|
|
if (filter.type === undefined || !filter.field) {
|
|
return [];
|
|
}
|
|
|
|
var field = scope.fields[filter.field] || {};
|
|
var operators = filter.selectionList
|
|
? OPERATORS_BY_TYPE["enum"] || []
|
|
: OPERATORS_BY_TYPE[filter.type] || [];
|
|
|
|
if (field.target && !field.targetName) {
|
|
operators = ["isNull", "notNull"];
|
|
}
|
|
|
|
return _.map(operators, function(name) {
|
|
return {
|
|
name: name,
|
|
title: OPERATORS[name]
|
|
};
|
|
});
|
|
}
|
|
|
|
scope.remove = function(filter) {
|
|
form.removeFilter(filter);
|
|
};
|
|
|
|
scope.canShowSelect = function () {
|
|
return scope.filter && scope.filter.selectionList &&
|
|
scope.filter.operator && !(
|
|
scope.filter.operator == 'isNull' ||
|
|
scope.filter.operator == 'notNull');
|
|
};
|
|
|
|
scope.canShowTags = function() {
|
|
return scope.filter &&
|
|
['many-to-one', 'one-to-one', 'many-to-many'].indexOf(scope.filter.type) > -1
|
|
&& ( scope.filter.operator == 'in' ||
|
|
scope.filter.operator == 'notNot');
|
|
};
|
|
|
|
scope.canShowInput = function() {
|
|
return scope.filter && !scope.canShowSelect() && !scope.canShowTags() &&
|
|
scope.filter.operator && !(
|
|
scope.filter.type == 'boolean' ||
|
|
scope.filter.operator == 'isNull' ||
|
|
scope.filter.operator == 'notNull');
|
|
};
|
|
|
|
scope.canShowRange = function() {
|
|
return scope.filter && (
|
|
scope.filter.operator === 'between' ||
|
|
scope.filter.operator === 'notBetween');
|
|
};
|
|
|
|
scope.getSelection = function () {
|
|
if (!scope.canShowSelect()) return [];
|
|
var field = (scope.fields||{})[scope.filter.field] || {};
|
|
return field.selectionList || [];
|
|
};
|
|
|
|
scope.onFieldChange = function() {
|
|
var filter = scope.filter,
|
|
field = scope.fields[filter.field] || {};
|
|
|
|
filter.type = field.type || 'string';
|
|
filter.selectionList = field.selectionList;
|
|
filter.value = undefined;
|
|
filter.value2 = undefined;
|
|
|
|
if (field.type === 'many-to-one' || field.type === 'one-to-one') {
|
|
filter.targetName = field.targetName;
|
|
} else {
|
|
filter.targetName = null;
|
|
}
|
|
};
|
|
|
|
scope.onOperatorChange = function() {
|
|
setTimeout(function() {
|
|
scope.$parent.$parent.$parent.doAdjust();
|
|
});
|
|
};
|
|
|
|
scope.$watch('filter.field', function searchFilterFieldWatch(value, old) {
|
|
scope.operators = getOperators();
|
|
});
|
|
|
|
scope.$on('on:show-menu', function () {
|
|
scope.operators = getOperators();
|
|
});
|
|
|
|
scope.getOptions = function () {
|
|
var all = [];
|
|
var data = scope.$parent.contextData || {};
|
|
var field = data.field || {};
|
|
_.each(scope.options, function (item) {
|
|
var name = field.name;
|
|
if (name && item.name === name && data.value) {
|
|
return;
|
|
}
|
|
if (item.contextField && !(item.contextField === name && item.contextFieldValue === data.value)) {
|
|
return;
|
|
}
|
|
all.push(item);
|
|
});
|
|
return all;
|
|
};
|
|
|
|
var unwatch = scope.$watch('fields', function searchFiledsWatch(fields, old) {
|
|
if (_.isEmpty(fields)) return;
|
|
unwatch();
|
|
var options = _.values(fields);
|
|
scope.options = _.sortBy(options, function (x) { return (x.title||'').toLowerCase(); });
|
|
}, true);
|
|
},
|
|
template:
|
|
"<div class='flex-layout'>" +
|
|
"<div class='flex-row'>" +
|
|
"<div class='flex-item filter-remove'>" +
|
|
"<a href='' ng-click='remove(filter)'><i class='fa fa-times'></i></a>" +
|
|
"</div>" +
|
|
"<div class='flex-item filter-inputs'>" +
|
|
"<span>" +
|
|
"<select ng-model='filter.field' ng-options='v.name as v.title for v in getOptions()' ng-change='onFieldChange()' class='input-medium'></select> " +
|
|
"</span>" +
|
|
"<span>" +
|
|
"<select ng-model='filter.operator' ng-options='o.name as o.title for o in operators' ng-change='onOperatorChange()' class='input-medium'></select> "+
|
|
"</span>" +
|
|
"<span ng-show='canShowSelect()'>" +
|
|
"<select ng-model='filter.value' class='input=medium' ng-options='o.value as o.title for o in getSelection()'></select>" +
|
|
"</span>" +
|
|
"<span ng-if='canShowTags()'>" +
|
|
"<div ui-filter-tags x-filter='filter' x-model='model' x-fields='fields'></div>" +
|
|
"</span>" +
|
|
"<span ng-show='canShowInput()'>" +
|
|
"<input type='text' ui-filter-input ng-model='filter.value' class='input-medium'> " +
|
|
"</span>" +
|
|
"<span ng-show='canShowRange()'>" +
|
|
"<input type='text' ui-filter-input ng-model='filter.value2' class='input-medium'> " +
|
|
"</span>" +
|
|
"</div>" +
|
|
"</div>" +
|
|
"</div>"
|
|
};
|
|
});
|
|
|
|
ui.directive('uiFilterTags', function() {
|
|
return {
|
|
scope: {
|
|
filter: '=',
|
|
fields: '=',
|
|
model: '='
|
|
},
|
|
controller: ['$scope', '$element', 'DataSource', 'ViewService',
|
|
function ($scope, $element, DataSource, ViewService) {
|
|
|
|
var filter = $scope.filter || {};
|
|
var fields = $scope.fields || {};
|
|
|
|
var field = _.extend({}, fields[filter.field], {
|
|
required: false,
|
|
readonly: false,
|
|
widget: 'tag-select',
|
|
showTitle: false,
|
|
canNew: false,
|
|
canEdit: false,
|
|
colSpan: 12
|
|
});
|
|
|
|
var schema = {
|
|
cols: 1,
|
|
type: 'form',
|
|
items: [{
|
|
type: 'panel',
|
|
items: [field]
|
|
}]
|
|
};
|
|
|
|
$scope._viewParams = {
|
|
model: $scope.model,
|
|
views: [schema],
|
|
fields: fields
|
|
};
|
|
|
|
ui.ViewCtrl($scope, DataSource, ViewService);
|
|
ui.FormViewCtrl.call(this, $scope, $element);
|
|
|
|
$scope.schema = schema;
|
|
$scope.schema.loaded = true;
|
|
|
|
$scope.$watch('record.' + filter.field, function searchFilterFieldWatch(value) {
|
|
filter.value = _.pluck(value, 'id');
|
|
});
|
|
|
|
function fetchValues(value) {
|
|
$scope._dataSource._new(field.target).search({
|
|
fields: _.compact([field.targetName]),
|
|
domain: 'self.id in (:ids)',
|
|
context: { ids: value }
|
|
}).success(function (records) {
|
|
var record = {};
|
|
var names = filter.field.split('.');
|
|
var rec = record;
|
|
while (names.length > 1) {
|
|
rec = rec[names.shift()] = {};
|
|
}
|
|
rec[names[0]] = records;
|
|
$scope.edit(record);
|
|
});
|
|
}
|
|
|
|
$scope.defaultValues = {};
|
|
$scope.editRecord = function (record) {
|
|
if (record && record !== $scope.defaultValues) {
|
|
$scope.record = record;
|
|
}
|
|
};
|
|
|
|
$scope.setEditable();
|
|
$scope.show();
|
|
|
|
if (_.isArray(filter.value) && filter.value.length) {
|
|
fetchValues(filter.value);
|
|
}
|
|
}],
|
|
template:
|
|
"<div ui-view-form x-handler='true'></div>"
|
|
};
|
|
});
|
|
|
|
ui.directive('uiFilterInput', function() {
|
|
|
|
return {
|
|
require: '^ngModel',
|
|
|
|
link: function(scope, element, attrs, model) {
|
|
|
|
var picker = null;
|
|
var pattern = /^(\d{2}\/\d{2}\/\d{4})$/;
|
|
var isopattern = /^(\d{4}-\d{2}-\d{2}T.*)$/;
|
|
|
|
var options = {
|
|
dateFormat: 'dd/mm/yy',
|
|
showButtonsPanel: false,
|
|
showTime: false,
|
|
showOn: null,
|
|
onSelect: function(dateText, inst) {
|
|
var value = picker.datepicker('getDate');
|
|
var isValue2 = _.str.endsWith(attrs.ngModel, 'value2');
|
|
|
|
value = isValue2 ? moment(value).endOf('day').toDate() :
|
|
moment(value).startOf('day').toDate();
|
|
|
|
model.$setViewValue(value.toISOString());
|
|
},
|
|
onClose: function (dateText, inst) {
|
|
picker.datepicker('destroy');
|
|
picker = null;
|
|
}
|
|
};
|
|
|
|
model.$formatters.push(function(value) {
|
|
if (_.isDate(value)) {
|
|
value = moment(value).format('DD/MM/YYYY');
|
|
}
|
|
return value;
|
|
});
|
|
|
|
model.$parsers.push(function(value) {
|
|
if (/^date/.test(scope.filter.type)) {
|
|
if (isopattern.test(value)) {
|
|
return value;
|
|
} else if (pattern.test(value)) {
|
|
var isValue2 = _.str.endsWith(attrs.ngModel, 'value2');
|
|
return isValue2 ? moment(value, 'DD/MM/YYYY').endOf('day').toDate() :
|
|
moment(value, 'DD/MM/YYYY').startOf('day').toDate();
|
|
}
|
|
return null;
|
|
}
|
|
return value;
|
|
});
|
|
|
|
model.$parsers.push(function(value) {
|
|
var type = scope.filter.type;
|
|
if (!(type == 'date' || type == 'datetime') || isDate(value)) {
|
|
return value;
|
|
}
|
|
return toMoment(value).toDate();
|
|
});
|
|
|
|
function isDate(value) {
|
|
if (value === null || value === undefined) return true;
|
|
if (_.isDate(value)) return true;
|
|
if (/\d+-\d+-\d+T/.test(value)) return true;
|
|
}
|
|
|
|
function toMoment(value) {
|
|
var format = null;
|
|
if (/\d+\/\d+\/\d+/.test(value)) format = 'DD/MM/YYYY';
|
|
if (/\d+\/\d+\/\d+\s+\d+:\d+/.test(value)) format = 'DD/MM/YYYY HH:mm';
|
|
if (format === null) {
|
|
return moment();
|
|
}
|
|
return moment(value, format);
|
|
}
|
|
|
|
element.focus(function(e) {
|
|
var type = scope.filter.type;
|
|
if (!(type == 'date' || type == 'datetime')) {
|
|
return;
|
|
}
|
|
picker = picker || element.datepicker(options);
|
|
picker.datepicker('show');
|
|
});
|
|
|
|
element.on('$destroy', function() {
|
|
if (picker) {
|
|
picker.datepicker('destroy');
|
|
picker = null;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
});
|
|
|
|
ui.directive('uiFilterContext', function () {
|
|
|
|
return {
|
|
scope: {
|
|
fields: '=',
|
|
context: '='
|
|
},
|
|
controller: ['$scope', function ($scope) {
|
|
$scope.field = {
|
|
name: 'contextValue',
|
|
evalTarget: 'context.field.target',
|
|
evalTargetName: 'context.field.targetName',
|
|
evalValue: 'context.value',
|
|
evalTitle: 'context.title'
|
|
};
|
|
|
|
$scope.getViewDef = function () {
|
|
return $scope.field;
|
|
};
|
|
|
|
$scope.remove = function () {
|
|
var context = {};
|
|
var fields = $scope.contextFields || [];
|
|
if (fields.length === 1) {
|
|
context.field = fields[0];
|
|
}
|
|
$scope.context = context;
|
|
};
|
|
|
|
$scope.$watch('context.field.name', function searchContextFieldWatch(name) {
|
|
if (!name) {
|
|
$scope.remove();
|
|
}
|
|
});
|
|
|
|
$scope.onFields = function (fields) {
|
|
var contextFields = {};
|
|
for (var item in fields) {
|
|
var field = fields[item];
|
|
var name = field.contextField;
|
|
if (name && fields[name] && !contextFields[name]) {
|
|
contextFields[name] = fields[name];
|
|
}
|
|
}
|
|
$scope.contextFields = _.sortBy(_.values(contextFields), function (x) { return (x.title||'').toLowerCase(); });
|
|
$scope.remove();
|
|
};
|
|
}],
|
|
link: function (scope, element, attrs) {
|
|
var unwatch = scope.$watch('fields', function searchContextFieldsWatch(fields) {
|
|
if (_.isEmpty(fields)) return;
|
|
unwatch();
|
|
scope.onFields(fields);
|
|
}, true);
|
|
},
|
|
template:
|
|
"<div class='flex-layout filter-context' ng-show='contextFields.length'>" +
|
|
"<div class='flex-row'>" +
|
|
"<div class='flex-item filter-remove'>" +
|
|
"<a href='' ng-click='remove()'><i class='fa fa-times'></i></a>" +
|
|
"</div>" +
|
|
"<div class='flex-item filter-inputs'>" +
|
|
"<span>" +
|
|
"<select ng-model='context.field' ng-options='v as v.title for v in contextFields'></select>" +
|
|
"</span>" +
|
|
"<span>" +
|
|
"<span ui-eval-ref-select ng-model='context.value' x-field='contextValue'></span>" +
|
|
"</span>" +
|
|
"</div>" +
|
|
"</div>" +
|
|
"</div>"
|
|
};
|
|
});
|
|
|
|
FilterFormCtrl.$inject = ['$scope', '$element', 'ViewService'];
|
|
function FilterFormCtrl($scope, $element, ViewService) {
|
|
|
|
this.doInit = function(model, viewItems) {
|
|
var context = $scope.$parent.$parent._context || {};
|
|
return ViewService
|
|
.getFields(model, context.jsonModel)
|
|
.success(function(fields, jsonFields) {
|
|
|
|
var items = {};
|
|
var nameField = null;
|
|
|
|
_.each(fields, function(field, name) {
|
|
if (field.name === 'id' || field.name === 'version' ||
|
|
field.name === 'archived' || field.name === 'selected') return;
|
|
if (field.type === 'binary' || field.large || field.encrypted) return;
|
|
if (field.nameColumn) {
|
|
nameField = name;
|
|
}
|
|
items[name] = field;
|
|
});
|
|
|
|
// include json fields
|
|
_.each(jsonFields, function (fields, prefix) {
|
|
_.each(fields, function (field, name) {
|
|
if (['button', 'panel', 'separator', 'many-to-many'].indexOf(field.type) > -1) return;
|
|
var key = prefix + '.' + name;
|
|
if (field.type !== 'many-to-one') {
|
|
key += '::' + (field.jsonType || 'text');
|
|
}
|
|
items[key] = _.extend({}, field, {
|
|
name: key,
|
|
title: (field.title || field.autoTitle) + " (" + items[prefix].title + ")"
|
|
});
|
|
});
|
|
// don't search parent
|
|
delete items[prefix];
|
|
});
|
|
|
|
if (!nameField) {
|
|
nameField = (fields.name || {}).name;
|
|
}
|
|
|
|
_.each(viewItems, function (item) {
|
|
if (item.hidden) {
|
|
delete items[item.name];
|
|
} else {
|
|
items[item.name] = item;
|
|
}
|
|
});
|
|
|
|
var contextFieldNames = [];
|
|
for (var item in items) {
|
|
var field = items[item];
|
|
var name = field.contextField;
|
|
if (name && items[name] && contextFieldNames.indexOf(name) === -1) {
|
|
contextFieldNames.push(name);
|
|
}
|
|
}
|
|
$scope.fields = items;
|
|
$scope.contextFieldNames = contextFieldNames;
|
|
|
|
$scope.$parent.fields = $scope.fields;
|
|
$scope.$parent.contextFieldNames = $scope.contextFieldNames;
|
|
$scope.$parent.nameField = nameField || ($scope.fields.name ? 'name' : null);
|
|
});
|
|
};
|
|
|
|
$scope.fields = {};
|
|
|
|
sharedProperty($scope, $scope.$parent.handler, 'filters', [{ $new: true }]);
|
|
sharedProperty($scope, $scope.$parent.handler, 'operator', 'and');
|
|
sharedProperty($scope, $scope.$parent.handler, 'showArchived', false);
|
|
|
|
var handler = $scope.$parent.handler;
|
|
if (handler && handler._dataSource) {
|
|
$scope.showArchived = handler._dataSource._showArchived;
|
|
}
|
|
|
|
$scope.addFilter = function(filter) {
|
|
var last = _.last($scope.filters);
|
|
if (last && !(last.field && last.operator)) return;
|
|
$scope.filters.push(filter || { $new: true });
|
|
};
|
|
|
|
this.removeFilter = function(filter) {
|
|
var index = $scope.filters.indexOf(filter);
|
|
if (index > -1) {
|
|
$scope.filters.splice(index, 1);
|
|
}
|
|
if ($scope.filters.length === 0) {
|
|
$scope.addFilter();
|
|
}
|
|
};
|
|
|
|
$scope.$on('on:select-custom', function(e, custom) {
|
|
|
|
$scope.filters.length = 0;
|
|
$scope.contextData = {};
|
|
|
|
if (custom.$selected) {
|
|
select(custom);
|
|
} else {
|
|
$scope.addFilter();
|
|
}
|
|
|
|
return $scope.applyFilter();
|
|
});
|
|
|
|
$scope.$on('on:select-domain', function(e, filter) {
|
|
$scope.filters.length = 0;
|
|
$scope.addFilter();
|
|
return $scope.applyFilter();
|
|
});
|
|
|
|
$scope.$on('on:before-save', function(e, data) {
|
|
var criteria = $scope.prepareFilter();
|
|
if (data) {
|
|
data.criteria = criteria;
|
|
}
|
|
});
|
|
|
|
$scope.$on('on:clear-filter', function (e, options) {
|
|
$scope.clearFilter(options);
|
|
});
|
|
|
|
function select(custom) {
|
|
|
|
var criteria = custom.criteria;
|
|
var filters = criteria.criteria;
|
|
var contextFieldNames = $scope.contextFieldNames || [];
|
|
|
|
if (filters && filters.length && filters.length < 3) {
|
|
var first = _.first(filters);
|
|
var last = _.last(filters);
|
|
var name = first.fieldName.replace('.id', '');
|
|
if (contextFieldNames.indexOf(name) > -1) {
|
|
$scope.contextData = {
|
|
field: $scope.fields[name],
|
|
value: first.value,
|
|
title: first.title,
|
|
saved: true
|
|
};
|
|
filters = (last||{}).criteria || [{}];
|
|
}
|
|
}
|
|
|
|
$scope.operator = criteria.operator || 'and';
|
|
|
|
_.each(filters, function(item) {
|
|
|
|
var fieldName = item.fieldName || '';
|
|
if (fieldName && $scope.fields[fieldName] === undefined && fieldName.indexOf('.') > -1 && fieldName.indexOf('::') === -1) {
|
|
fieldName = fieldName.substring(0, fieldName.lastIndexOf('.'));
|
|
}
|
|
|
|
var field = $scope.fields[fieldName] || {};
|
|
var filter = {
|
|
field: fieldName,
|
|
value: item.value,
|
|
value2: item.value2
|
|
};
|
|
|
|
filter.type = field.type || 'string';
|
|
filter.operator = item.operator;
|
|
|
|
if (field.selectionList) {
|
|
filter.selectionList = field.selectionList;
|
|
}
|
|
|
|
if (item.operator === '=' && filter.value === true) {
|
|
filter.operator = 'true';
|
|
}
|
|
if (filter.operator === '=' && filter.value === false) {
|
|
filter.operator = 'false';
|
|
}
|
|
|
|
if (field.type === 'date' || field.type === 'datetime') {
|
|
if (filter.value) {
|
|
filter.value = moment(filter.value).toDate();
|
|
}
|
|
if (filter.value2) {
|
|
filter.value2 = moment(filter.value2).toDate();
|
|
}
|
|
}
|
|
|
|
if (filter.type == 'many-to-one' || field.type === 'one-to-one') {
|
|
filter.targetName = field.targetName;
|
|
}
|
|
|
|
$scope.addFilter(filter);
|
|
});
|
|
}
|
|
|
|
$scope.clearFilter = function(options) {
|
|
$scope.filters.length = 0;
|
|
$scope.showArchived = false;
|
|
$scope.addFilter();
|
|
$scope.contextData = {};
|
|
|
|
if ($scope.$parent.onClear) {
|
|
$scope.$parent.onClear();
|
|
}
|
|
|
|
var hide = options === true;
|
|
var silent = !hide && options && options.silent;
|
|
|
|
if (!silent) {
|
|
$scope.applyFilter();
|
|
}
|
|
|
|
if ($scope.$parent && hide) {
|
|
$scope.$parent.$broadcast('on:hide-menu');
|
|
}
|
|
};
|
|
|
|
$scope.prepareFilter = function() {
|
|
|
|
var criteria = {
|
|
archived: $scope.showArchived,
|
|
operator: $scope.operator,
|
|
criteria: []
|
|
};
|
|
|
|
_.each($scope.filters, function(filter) {
|
|
|
|
if (!filter.field || !filter.operator) {
|
|
return;
|
|
}
|
|
|
|
var criterion = {
|
|
fieldName: filter.field,
|
|
operator: filter.operator,
|
|
value: filter.value
|
|
};
|
|
|
|
if (filter.operator == 'like' || filter.operator == 'notLike') {
|
|
criterion.value = criterion.value || '';
|
|
}
|
|
|
|
if (filter.operator == 'in' ||
|
|
filter.operator == 'notIn') {
|
|
if (_.isEmpty(filter.value)) {
|
|
return;
|
|
}
|
|
criterion.fieldName += '.id';
|
|
} else if (filter.targetName && criterion.fieldName.indexOf(':') == -1 && (
|
|
filter.operator !== 'isNull' ||
|
|
filter.operator !== 'notNull')) {
|
|
criterion.fieldName += '.' + filter.targetName;
|
|
} else if (/-many/.test(filter.type) && (
|
|
filter.operator !== 'isNull' ||
|
|
filter.operator !== 'notNull')) {
|
|
criterion.fieldName += '.id';
|
|
}
|
|
|
|
if (criterion.operator == "true") {
|
|
criterion.operator = "=";
|
|
criterion.value = true;
|
|
}
|
|
if (criterion.operator == "false") {
|
|
criterion = {
|
|
operator: "or",
|
|
criteria: [
|
|
{
|
|
fieldName: filter.field,
|
|
operator: "=",
|
|
value: false
|
|
},
|
|
{
|
|
fieldName: filter.field,
|
|
operator: "isNull"
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
if (criterion.operator == "between" || criterion.operator == "notBetween") {
|
|
criterion.value2 = filter.value2;
|
|
}
|
|
|
|
if (filter.$new) {
|
|
criterion.$new = true;
|
|
}
|
|
|
|
criteria.criteria.push(criterion);
|
|
});
|
|
|
|
var contextData = $scope.contextData || {};
|
|
|
|
if (contextData.value && contextData.field && contextData.field.name) {
|
|
var previous = criteria.criteria;
|
|
var operator = criteria.operator;
|
|
|
|
criteria.operator = "and";
|
|
criteria.criteria = [{
|
|
fieldName: contextData.field.name + ".id",
|
|
operator: "=",
|
|
value: contextData.value,
|
|
title: contextData.title,
|
|
$new: !contextData.saved
|
|
}];
|
|
|
|
if (previous && previous.length) {
|
|
criteria.criteria.push({
|
|
operator: operator,
|
|
criteria: previous
|
|
});
|
|
}
|
|
}
|
|
|
|
return criteria;
|
|
};
|
|
|
|
var appliedFilters = false;
|
|
var appliedContext = false;
|
|
|
|
$scope.$watch('filters', function (fitlers, old) {
|
|
appliedFilters = fitlers === old;
|
|
}, true);
|
|
|
|
$scope.$watch('contextData', function (data, old) {
|
|
appliedContext = data === old;
|
|
}, true);
|
|
|
|
$scope.applyFilter = function(hide) {
|
|
var criteria = $scope.prepareFilter();
|
|
var promise;
|
|
if ($scope.$parent.onFilter) {
|
|
promise = $scope.$parent.onFilter(criteria);
|
|
}
|
|
if ($scope.$parent && hide) {
|
|
$scope.$parent.$broadcast('on:hide-menu');
|
|
}
|
|
handler.$broadcast('on:advance-filter', criteria);
|
|
handler.$broadcast('on:context-field-change', $scope.contextData);
|
|
appliedFilters = true;
|
|
appliedContext = true;
|
|
return promise;
|
|
};
|
|
|
|
$scope.canExport = function(full) {
|
|
var allowFull = axelor.config["view.adv-search.export.full"] !== false;
|
|
var handler = $scope.$parent.handler;
|
|
if (handler && handler.hasPermission) {
|
|
return full ? allowFull && handler.hasPermission('export') : handler.hasPermission('export');
|
|
}
|
|
return true;
|
|
};
|
|
|
|
$scope.onExport = function(full) {
|
|
var handler = $scope.$parent.handler;
|
|
if (handler && handler.onExport) {
|
|
var promise = appliedFilters && appliedContext ? null : $scope.applyFilter(true);
|
|
if (promise && promise.then) {
|
|
promise.then(function () {
|
|
handler.onExport(full);
|
|
});
|
|
} else {
|
|
handler.onExport(full);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
ui.directive('uiFilterForm', function() {
|
|
|
|
return {
|
|
replace: true,
|
|
|
|
scope: {
|
|
model: '=',
|
|
onSearch: '&'
|
|
},
|
|
|
|
controller: FilterFormCtrl,
|
|
|
|
link: function(scope, element, attrs, ctrl) {
|
|
var unwatch = scope.$watch("$parent.viewItems", function searchViewItemsWatch(items) {
|
|
if (items === undefined) return;
|
|
unwatch();
|
|
ctrl.doInit(scope.model, items);
|
|
});
|
|
},
|
|
template:
|
|
"<div class='filter-form'>" +
|
|
"<div ui-filter-context fields='fields' context='contextData'></div>" +
|
|
"<form class='filter-operator form-inline'>" +
|
|
"<label class='radio inline'>" +
|
|
"<input type='radio' name='operator' ng-model='operator' value='and' x-translate><span x-translate>and</span>" +
|
|
"</label>" +
|
|
"<label class='radio inline'>" +
|
|
"<input type='radio' name='operator' ng-model='operator' value='or' x-translate><span x-translate>or</span>" +
|
|
"</label>" +
|
|
"<label class='checkbox inline show-archived'>" +
|
|
"<input type='checkbox' ng-model='showArchived'><span x-translate>Show archived</span>" +
|
|
"</label>" +
|
|
"</form>" +
|
|
"<div ng-repeat='filter in filters' ui-filter-item x-model='model' x-fields='fields' x-filter='filter'></div>" +
|
|
"<div class='links'>"+
|
|
"<a href='' ng-click='addFilter()' x-translate>Add filter</a>"+
|
|
"<span class='divider'>|</span>"+
|
|
"<a href='' ng-click='clearFilter(true)' x-translate>Clear</a></li>"+
|
|
"<span class='divider' ng-if='canExport()'>|</span>"+
|
|
"<a href='' ng-if='canExport()' ng-click='onExport()' x-translate>Export</a></li>"+
|
|
"<span class='divider' ng-if='canExport()'>|</span>"+
|
|
"<a href='' ng-if='canExport(true)' ng-click='onExport(true)' x-translate>Export full</a></li>"+
|
|
"<span class='divider' ng-if='canExport(true)'>|</span>"+
|
|
"<a href='' ng-click='applyFilter(true)' x-translate>Apply</a></li>"+
|
|
"<div>"+
|
|
"</div>"
|
|
};
|
|
});
|
|
|
|
ui.directive('uiFilterBox', function() {
|
|
|
|
return {
|
|
scope: {
|
|
handler: '='
|
|
},
|
|
controller: ['$scope', 'ViewService', 'DataSource', function($scope, ViewService, DataSource) {
|
|
|
|
var handler = $scope.handler,
|
|
params = (handler._viewParams || {}).params;
|
|
|
|
var filterView = params ? params['search-filters'] : null;
|
|
var filterDS = DataSource.create('com.axelor.meta.db.MetaFilter');
|
|
|
|
this.$scope = $scope;
|
|
|
|
$scope.model = handler._model;
|
|
$scope.view = {};
|
|
|
|
sharedProperty($scope, $scope.handler, 'viewFilters', []);
|
|
sharedProperty($scope, $scope.handler, 'custFilters', []);
|
|
sharedProperty($scope, $scope.handler, 'tagItems', []);
|
|
|
|
$scope.canShare = axelor.config["view.adv-search.share"] !== false;
|
|
|
|
if (filterView) {
|
|
ViewService.getMetaDef($scope.model, {name: filterView, type: 'search-filters'})
|
|
.success(function(fields, view) {
|
|
var viewItems = _.map(view.items, function (item) {
|
|
var field = fields[item.name] || {};
|
|
return _.extend({}, field, item, { type: field.type });
|
|
});
|
|
$scope.view = view;
|
|
$scope.viewItems = viewItems;
|
|
$scope.viewFilters = angular.copy(view.filters);
|
|
});
|
|
} else {
|
|
$scope.viewItems = [];
|
|
filterView = 'act:' + (handler._viewParams || {}).action;
|
|
}
|
|
|
|
if (filterView) {
|
|
filterDS.rpc('com.axelor.meta.web.MetaFilterController:findFilters', {
|
|
model: 'com.axelor.meta.db.MetaFilter',
|
|
context: {
|
|
filterView: filterView
|
|
}
|
|
}).success(function(res) {
|
|
_.each(res.data, function(item) {
|
|
acceptCustom(item);
|
|
});
|
|
});
|
|
}
|
|
|
|
var current = {
|
|
criteria: {},
|
|
domains: [],
|
|
customs: []
|
|
};
|
|
|
|
function acceptCustom(filter) {
|
|
|
|
var custom = {
|
|
title: filter.title,
|
|
name: filter.name,
|
|
shared: filter.shared,
|
|
criteria: angular.fromJson(filter.filterCustom)
|
|
};
|
|
custom.selected = filter.filters ? filter.filters.split(/\s*,\s*/) : [];
|
|
custom.selected = _.map(custom.selected, function(x) {
|
|
return parseInt(x);
|
|
});
|
|
|
|
var found = _.findWhere($scope.custFilters, {name: custom.name});
|
|
if (found) {
|
|
_.extend(found, custom);
|
|
} else {
|
|
$scope.custFilters.push(custom);
|
|
}
|
|
|
|
return found ? found : custom;
|
|
}
|
|
|
|
$scope.selectFilter = function(filter, isCustom, live) {
|
|
|
|
var selected = live ? !filter.$selected : filter.$selected;
|
|
var selection = isCustom ? current.customs : current.domains;
|
|
var applyAll = (handler.schema||{}).customSearch === false;
|
|
|
|
if (live) {
|
|
$scope.onClear();
|
|
}
|
|
|
|
filter.$selected = selected;
|
|
|
|
var index = selection.indexOf(filter);
|
|
if (selected) {
|
|
selection.push(filter);
|
|
}
|
|
if (!selected && index > -1) {
|
|
selection.splice(index, 1);
|
|
}
|
|
|
|
if (isCustom && (live || applyAll)) {
|
|
$scope.hasCustSelected = filter.$selected && !applyAll;
|
|
$scope.custName = filter.$selected ? filter.name : null;
|
|
$scope.oldCustTitle = filter.$selected ? filter.title : '';
|
|
$scope.custTitle = filter.$selected ? filter.title : '';
|
|
$scope.custShared = filter.$selected ? filter.shared : false;
|
|
return $scope.$broadcast('on:select-custom', filter, selection);
|
|
}
|
|
|
|
if (live || applyAll) {
|
|
$scope.$broadcast('on:select-domain', filter);
|
|
}
|
|
};
|
|
|
|
$scope.isSelected = function(filter) {
|
|
return filter.$selected;
|
|
};
|
|
|
|
$scope.onRefresh = function() {
|
|
if (this.custTerm) {
|
|
return this.onFreeSearch();
|
|
}
|
|
handler.onRefresh();
|
|
};
|
|
|
|
$scope.hasFilters = function(which) {
|
|
if (which === 1) {
|
|
return this.viewFilters && this.viewFilters.length;
|
|
}
|
|
if (which === 2) {
|
|
return this.custFilters && this.custFilters.length;
|
|
}
|
|
return (this.viewFilters && this.viewFilters.length) ||
|
|
(this.custFilters && this.custFilters.length);
|
|
};
|
|
|
|
$scope.canSaveNew = function() {
|
|
if ($scope.hasCustSelected && $scope.oldCustTitle && $scope.custTitle) {
|
|
return !angular.equals(_.underscored($scope.oldCustTitle), _.underscored($scope.custTitle));
|
|
}
|
|
return false;
|
|
};
|
|
|
|
$scope.onSave = function(saveAs) {
|
|
|
|
var data = { criteria: null };
|
|
$scope.$broadcast('on:before-save', data);
|
|
|
|
var title = _.trim($scope.custTitle),
|
|
name = $scope.custName || _.underscored(title);
|
|
|
|
if (saveAs) {
|
|
name = _.underscored(title);
|
|
}
|
|
|
|
var selected = [];
|
|
|
|
_.each($scope.viewFilters, function(item, i) {
|
|
if (item.$selected) selected.push(i);
|
|
});
|
|
|
|
var custom = data.criteria || {};
|
|
|
|
custom = _.extend({
|
|
operator: custom.operator,
|
|
criteria: custom.criteria
|
|
});
|
|
|
|
var value = {
|
|
name: name,
|
|
title: title,
|
|
shared: $scope.custShared,
|
|
filters: selected.join(', '),
|
|
filterView: filterView,
|
|
filterCustom: angular.toJson(custom)
|
|
};
|
|
|
|
filterDS.rpc('com.axelor.meta.web.MetaFilterController:saveFilter', {
|
|
model: 'com.axelor.meta.db.MetaFilter',
|
|
context: value
|
|
}).success(function(res) {
|
|
var custom = acceptCustom(res.data);
|
|
custom.$selected = false;
|
|
$scope.selectFilter(custom, true, true);
|
|
});
|
|
};
|
|
|
|
$scope.onDelete = function() {
|
|
|
|
var name = $scope.custName;
|
|
if (!$scope.hasCustSelected || !name) {
|
|
return;
|
|
}
|
|
|
|
function doDelete() {
|
|
filterDS.rpc('com.axelor.meta.web.MetaFilterController:removeFilter', {
|
|
model: 'com.axelor.meta.db.MetaFilter',
|
|
context: {
|
|
name: name,
|
|
filterView: filterView
|
|
}
|
|
}).success(function(res) {
|
|
var found = _.findWhere($scope.custFilters, {name: name});
|
|
if (found) {
|
|
$scope.custFilters.splice($scope.custFilters.indexOf(found), 1);
|
|
$scope.hasCustSelected = false;
|
|
$scope.custName = null;
|
|
$scope.custTitle = null;
|
|
$scope.custShared = false;
|
|
}
|
|
$scope.onFilter();
|
|
});
|
|
}
|
|
|
|
axelor.dialogs.confirm(_t("Would you like to remove the filter?"), function(confirmed){
|
|
if (confirmed) {
|
|
doDelete();
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.onClear = function() {
|
|
|
|
_.each($scope.viewFilters, function(d) { d.$selected = false; });
|
|
_.each($scope.custFilters, function(d) { d.$selected = false; });
|
|
|
|
current.domains.length = 0;
|
|
current.customs.length = 0;
|
|
|
|
$scope.hasCustSelected = false;
|
|
$scope.custName = null;
|
|
$scope.oldCustTitle = null;
|
|
$scope.custTitle = null;
|
|
$scope.custShared = false;
|
|
$scope.custTerm = null;
|
|
$scope.tagItems = [];
|
|
|
|
if ($scope.handler && $scope.handler.clearFilters) {
|
|
$scope.handler.clearFilters();
|
|
}
|
|
};
|
|
|
|
$scope.onFilter = function(criteria) {
|
|
|
|
if (criteria) {
|
|
current.criteria = criteria;
|
|
} else {
|
|
criteria = current.criteria;
|
|
}
|
|
|
|
var search = _.extend({}, criteria);
|
|
if (!search.criteria) {
|
|
search.operator = 'and';
|
|
search.criteria = [];
|
|
} else {
|
|
search.criteria = _.clone(search.criteria);
|
|
}
|
|
|
|
if (arguments.length > 1 && _.isString(arguments[1])) {
|
|
search._searchText = arguments[1];
|
|
}
|
|
|
|
var domains = [],
|
|
customs = [];
|
|
|
|
_.each(current.domains, function(domain) {
|
|
domains.push(domain);
|
|
});
|
|
|
|
_.each(current.customs, function(custom) {
|
|
if($scope.hasCustSelected) { return; }
|
|
if (custom.criteria && custom.criteria.criteria) {
|
|
customs.push({
|
|
operator: custom.criteria.operator || 'and',
|
|
criteria: custom.criteria.criteria
|
|
});
|
|
}
|
|
_.each(custom.selected, function(i) {
|
|
var domain = $scope.viewFilters[i];
|
|
if (domains.indexOf(domain) == -1) {
|
|
domains.push(domain);
|
|
}
|
|
});
|
|
});
|
|
|
|
if (customs.length > 0) {
|
|
search.criteria.push({
|
|
operator: criteria.operator || 'and',
|
|
criteria: customs
|
|
});
|
|
}
|
|
|
|
search._domains = domains;
|
|
search.criteria = process(search.criteria);
|
|
|
|
// process criteria for datetime fields, always use between operator
|
|
function process(filter) {
|
|
if (_.isArray(filter)) return _.map(filter, process);
|
|
if (_.isArray(filter.criteria)) {
|
|
filter.criteria = process(filter.criteria);
|
|
return filter;
|
|
}
|
|
var name = filter.fieldName;
|
|
var type = (($scope.fields||$scope.$parent.fields||{})[filter.fieldName]||{}).type;
|
|
|
|
var v1 = filter.value;
|
|
var v2 = filter.value2;
|
|
|
|
// if json date/datetime field
|
|
if (name.indexOf('::') > -1 && (type === 'date' || type === 'datetime')) {
|
|
filter = _.extend({}, filter);
|
|
switch (filter.operator) {
|
|
case '>':
|
|
filter.value = moment(v1).endOf('day').toDate();
|
|
filter.value2 = undefined;
|
|
break;
|
|
case '<':
|
|
filter.value = moment(v1).startOf('day').toDate();
|
|
filter.value2 = undefined;
|
|
break;
|
|
case '=':
|
|
filter.operator = 'between';
|
|
filter.value = moment(v1).startOf('day').toDate();
|
|
filter.value2 = moment(v1).endOf('day').toDate();
|
|
break;
|
|
case '!=':
|
|
filter.operator = 'notBetween';
|
|
filter.value = moment(v1).startOf('day').toDate();
|
|
filter.value2 = moment(v1).endOf('day').toDate();
|
|
break;
|
|
case 'between':
|
|
case 'notBetween':
|
|
filter.value = moment(v1).startOf('day').toDate();
|
|
filter.value2 = moment(v2).endOf('day').toDate();
|
|
break;
|
|
}
|
|
return filter;
|
|
}
|
|
|
|
if (filter.operator !== '=') return filter;
|
|
if (type != 'datetime') return filter;
|
|
|
|
if (!(/\d+-\d+\d+T/.test(filter.value) || _.isDate(filter.value))) {
|
|
return filter;
|
|
}
|
|
|
|
v1 = moment(v1).startOf('day').toDate();
|
|
v2 = moment(v1).endOf('day').toDate();
|
|
return _.extend({}, filter, {
|
|
operator: 'between',
|
|
value: v1,
|
|
value2: v2
|
|
});
|
|
}
|
|
|
|
function countCustom(criteria) {
|
|
var n = _.filter(criteria, function (item) { return item.$new; }).length;
|
|
if (criteria.length === 2 && criteria[1].criteria) {
|
|
n += countCustom(criteria[1].criteria);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
var tag = {};
|
|
var all = _.chain([$scope.viewFilters, $scope.custFilters])
|
|
.flatten()
|
|
.filter(function (item) {
|
|
return item && item.$selected;
|
|
})
|
|
.pluck('title')
|
|
.value();
|
|
|
|
var nCustom = countCustom((criteria||{}).criteria);
|
|
if (nCustom > 0) {
|
|
all.push(_t('Custom ({0})', nCustom));
|
|
}
|
|
|
|
if (all.length === 1) {
|
|
tag.title = all[0];
|
|
}
|
|
if (all.length > 1) {
|
|
tag.title = _t('Filters ({0})', all.length);
|
|
tag.help = all.join(', ');
|
|
}
|
|
|
|
if (all.length === 0) {
|
|
$scope.tagItems = [];
|
|
} else {
|
|
$scope.tagItems = [tag];
|
|
}
|
|
|
|
return handler._simpleFilters
|
|
? handler.filter(handler._simpleFilters, search)
|
|
: handler.filter(search);
|
|
};
|
|
|
|
$scope.onFreeSearch = function() {
|
|
|
|
var filters = [],
|
|
fields = {},
|
|
text = this.custTerm,
|
|
number = +(text);
|
|
|
|
var freeSearch = handler.schema && handler.schema.freeSearch;
|
|
var freeCols = freeSearch && freeSearch !== 'all' ? freeSearch.split(/\s*,\s*/) : [];
|
|
var cols = freeCols.length === 0 ? $scope.findCols() : freeCols;
|
|
|
|
text = text ? text.trim() : null;
|
|
fields = _.pick(this.$parent.fields, cols);
|
|
|
|
if (text) {
|
|
for(var name in fields) {
|
|
|
|
var fieldName = null,
|
|
operator = "like",
|
|
value = text;
|
|
|
|
var field = fields[name];
|
|
|
|
if (field.transient) continue;
|
|
|
|
switch (field.type) {
|
|
case 'integer':
|
|
case 'decimal':
|
|
if (_.isNaN(number) || !_.isNumber(number)) continue;
|
|
if (field.type === 'integer' && (number > 2147483647 || number < -2147483648)) continue;
|
|
fieldName = name;
|
|
operator = '=';
|
|
value = number;
|
|
break;
|
|
case 'text':
|
|
case 'string':
|
|
fieldName = name;
|
|
break;
|
|
case 'one-to-one':
|
|
case 'many-to-one':
|
|
if (field.jsonField) {
|
|
fieldName = name;
|
|
} else if (field.targetName) {
|
|
fieldName = name + '.' + field.targetName;
|
|
}
|
|
break;
|
|
case 'boolean':
|
|
if (/^(t|f|y|n|true|false|yes|no)$/.test(text)) {
|
|
fieldName = name;
|
|
operator = '=';
|
|
value = /^(t|y|true|yes)$/.test(text);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!fieldName) continue;
|
|
|
|
filters.push({
|
|
fieldName: fieldName,
|
|
operator: operator,
|
|
value: value
|
|
});
|
|
}
|
|
}
|
|
|
|
var criteria = {
|
|
operator: 'or',
|
|
criteria: filters
|
|
};
|
|
|
|
this.onFilter(criteria, text);
|
|
};
|
|
}],
|
|
link: function(scope, element, attrs) {
|
|
|
|
var menu = element.children('.filter-menu'),
|
|
toggleButton = null;
|
|
|
|
scope.onSearch = function(e) {
|
|
if (menu && menu.is(':visible')) {
|
|
hideMenu();
|
|
return;
|
|
}
|
|
toggleButton = $(e.currentTarget);
|
|
// more than top navbar's zIndex 1030 to hide
|
|
menu.zIndex(element.parent().zIndex() + 1031);
|
|
menu.show();
|
|
scope.doAdjust();
|
|
|
|
$(document).on('mousedown.search-menu', onMouseDown);
|
|
|
|
scope.$applyAsync(function () {
|
|
scope.visible = true;
|
|
scope.$broadcast('on:show-menu');
|
|
});
|
|
};
|
|
|
|
scope.doAdjust = (function() {
|
|
var opts = {
|
|
my: "left top",
|
|
at: "left bottom",
|
|
of: element,
|
|
collision: "fit"
|
|
};
|
|
if (element.hasClass('pull-right')) {
|
|
opts.my = "right top";
|
|
opts.at = "right bottom";
|
|
}
|
|
return function() {
|
|
menu.position(opts);
|
|
};
|
|
}());
|
|
|
|
scope.onClearFilter = function () {
|
|
hideMenu();
|
|
scope.visible = true;
|
|
scope.$broadcast('on:clear-filter');
|
|
scope.$timeout(function () {
|
|
scope.visible = false;
|
|
});
|
|
};
|
|
|
|
// append menu to body to fix overlaping issue
|
|
scope.$timeout(function() {
|
|
menu.zIndex(element.zIndex() + 1);
|
|
menu.appendTo("body");
|
|
});
|
|
|
|
element.on('keydown.search-query', '.search-query', function(e) {
|
|
if (e.keyCode === 13) { // enter
|
|
scope.onFreeSearch();
|
|
}
|
|
});
|
|
|
|
scope.$on('on:hide-menu', function () {
|
|
hideMenu();
|
|
});
|
|
|
|
scope.$on('on:clear-filter-silent', function () {
|
|
var visible = scope.visible;
|
|
scope.visible = true;
|
|
scope.$broadcast('on:clear-filter', { silent: true });
|
|
scope.$timeout(function () {
|
|
scope.visible = visible;
|
|
});
|
|
});
|
|
|
|
function hideMenu() {
|
|
$(document).off('mousedown.search-menu', onMouseDown);
|
|
scope.$timeout(function () {
|
|
scope.visible = false;
|
|
});
|
|
menu.hide();
|
|
}
|
|
|
|
function onMouseDown(e) {
|
|
var all = $(menu).add(toggleButton);
|
|
if (all.is(e.target) || all.has(e.target).length > 0) {
|
|
return;
|
|
}
|
|
if ($(e.target).zIndex() > $(menu).zIndex() || $(e.target).parents('.ui-dialog').length) {
|
|
return;
|
|
}
|
|
if(menu) {
|
|
hideMenu();
|
|
}
|
|
}
|
|
|
|
scope.hideMenu = hideMenu;
|
|
|
|
scope.findCols = function () {
|
|
var grid = element.parents('.grid-view:first').children('[ui-slick-grid]:first').data('grid');
|
|
return grid
|
|
? _.pluck(grid.getColumns(), 'field').filter(function (n) { return n in scope.$parent.fields; })
|
|
: _.pluck(scope.$parent.fields, 'name');
|
|
};
|
|
|
|
scope.handler.$watch('schema.freeSearch', function searchFreeSearchWatch(value, old) {
|
|
if (value === 'none') {
|
|
var input = element.find('input:first')
|
|
.addClass('not-readonly')
|
|
.prop('readonly', true)
|
|
.click(scope.onSearch.bind(scope));
|
|
}
|
|
});
|
|
|
|
element.on('$destroy', function() {
|
|
$(document).off('mousedown.search-menu', onMouseDown);
|
|
if (menu) {
|
|
menu.remove();
|
|
menu = null;
|
|
}
|
|
});
|
|
},
|
|
replace: true,
|
|
template:
|
|
"<div class='filter-box'>" +
|
|
"<div class='tag-select picker-input search-query'>" +
|
|
"<ul>" +
|
|
"<li class='tag-item label label-primary' ng-repeat='item in tagItems'>" +
|
|
"<span class='tag-text' title='{{item.help}}'>{{item.title}}</span> " +
|
|
"<i class='fa fa-times fa-small' ng-click='onClearFilter()'></i>" +
|
|
"</li>" +
|
|
"<li class='tag-selector' ng-show='!tagItems.length'>" +
|
|
"<input type='text' autocomplete='off' ng-model='custTerm'>" +
|
|
"</li>" +
|
|
"</ul>" +
|
|
"<span class='picker-icons'>" +
|
|
"<i ng-click='onSearch($event)' class='fa fa-caret-down'></i>"+
|
|
"<i ng-click='onRefresh()' class='fa fa-search'></i>" +
|
|
"</span>" +
|
|
"</div>" +
|
|
"<div class='filter-menu'>" +
|
|
"<span>" +
|
|
"<strong x-translate>Advanced Search</strong>" +
|
|
"<a href='' class='pull-right' ng-click='hideMenu()'><i class='fa fa-times'></i></a>" +
|
|
"</span>" +
|
|
"<hr>"+
|
|
"<div class='filter-list'>" +
|
|
"<dl ng-show='!hasFilters() && handler.schema.customSearch == false' style='display: hidden;'>" +
|
|
"<dd><span x-translate>No filters available</span></dd>" +
|
|
"</dl>" +
|
|
"<dl ng-show='hasFilters(1)'>" +
|
|
"<dt><i class='fa fa-floppy-o'></i><span x-translate> Filters</span></dt>" +
|
|
"<dd ng-repeat='filter in viewFilters' class='checkbox'>" +
|
|
"<input type='checkbox' " +
|
|
"ng-model='filter.$selected' " +
|
|
"ng-click='selectFilter(filter, false, false)' ng-disabled='hasCustSelected'> " +
|
|
"<a href='' ng-click='selectFilter(filter, false, true)' ng-disabled='hasCustSelected'>{{filter.title}}</a>" +
|
|
"</dd>" +
|
|
"</dl>" +
|
|
"<dl ng-show='hasFilters(2)'>" +
|
|
"<dt><i class='fa fa-filter'></i><span x-translate> My Filters</span></dt>" +
|
|
"<dd ng-repeat='filter in custFilters' class='checkbox'>" +
|
|
"<input type='checkbox' " +
|
|
"ng-model='filter.$selected' " +
|
|
"ng-click='selectFilter(filter, true, false)' ng-disabled='hasCustSelected'> " +
|
|
"<a href='' ng-click='selectFilter(filter, true, true)' ng-disabled='!filter.$selected && hasCustSelected'>{{filter.title}}</a>" +
|
|
"</dd>" +
|
|
"</dl>" +
|
|
"</div>" +
|
|
"<div ng-hide='handler.schema.customSearch == false'>" +
|
|
"<hr ng-show='hasFilters()'>" +
|
|
"<div ui-filter-form x-model='model'></div>" +
|
|
"<hr>" +
|
|
"<div class='form-inline'>" +
|
|
"<div class='control-group'>" +
|
|
"<input type='text' placeholder='{{\"Save filter as\" | t}}' ng-model='custTitle'> " +
|
|
"<label class='checkbox' ng-show='canShare'>" +
|
|
"<input type='checkbox' ng-model='custShared'><span x-translate>Share</span>" +
|
|
"</label>" +
|
|
"</div>" +
|
|
"<button class='btn btn-small' ng-click='onSave()' ng-show='custTitle && !hasCustSelected'><span x-translate>Save</span></button> " +
|
|
"<button class='btn btn-small' ng-click='onSave()' ng-show='custTitle && hasCustSelected'><span x-translate>Update</span></button> " +
|
|
"<button class='btn btn-small' ng-click='onSave(true)' ng-show='canSaveNew()'><span x-translate>Save as</span></button> " +
|
|
"<button class='btn btn-small' ng-click='onDelete()' ng-show='hasCustSelected'><span x-translate>Delete</span></button>" +
|
|
"</div>" +
|
|
"</div>" +
|
|
"</div>" +
|
|
"</div>"
|
|
};
|
|
});
|
|
|
|
})();
|