From 82aa7a8e6e5a8157c8ff96e027be3a75a9bfd9d2 Mon Sep 17 00:00:00 2001 From: Matthew Fedak <135224620+mfedak-notebooksbillger@users.noreply.github.com> Date: Tue, 19 Sep 2023 12:25:20 +0200 Subject: [PATCH] Feature/add support for an in filter operator to grid helper (#115) * Add support for an in filter operator * Added js overrides for in filter and compatability with id property. * Allow for filtering of floating numbers using in filter * Sanitize number filter values better when usin IN filter * Remove unnecessary comment --- public/js/pimcore/overrides.js | 298 +++++++++++++++++++++++++++++++ src/Helper/GridHelperService.php | 20 ++- 2 files changed, 317 insertions(+), 1 deletion(-) diff --git a/public/js/pimcore/overrides.js b/public/js/pimcore/overrides.js index ae50d4be2e..52b80b9ea2 100644 --- a/public/js/pimcore/overrides.js +++ b/public/js/pimcore/overrides.js @@ -1066,3 +1066,301 @@ Ext.define('Ext.overrides.grid.RowEditor', { return scrollDelta; }, }); + +Ext.define('Ext.local.grid.filters.filter.TriFilter', { + extend: 'Ext.grid.filters.filter.TriFilter', + menuItems: [ + 'lt', + 'gt', + '-', + 'eq', + 'in' + ], + constructor: function(config) { + var me = this, + stateful = false, + filter = {}, + filterGt, filterLt, filterEq, filterIn, value, operator; + me.callParent([ + config + ]); + value = me.value; + filterLt = me.getStoreFilter('lt'); + filterGt = me.getStoreFilter('gt'); + filterEq = me.getStoreFilter('eq'); + filterIn = me.getStoreFilter('in'); + + if (filterLt || filterGt || filterEq || filterIn) { + stateful = me.active = true; + if (filterLt) { + me.onStateRestore(filterLt); + } + if (filterGt) { + me.onStateRestore(filterGt); + } + if (filterEq) { + me.onStateRestore(filterEq); + } + if (filterIn) { + me.onStateRestore(filterIn); + } + } else { + if (me.grid.stateful && me.getGridStore().saveStatefulFilters) { + value = undefined; + } + me.active = me.getActiveState(config, value); + } + filter.lt = filterLt || me.createFilter({ + operator: 'lt', + value: (!stateful && value && value.lt) || null + }, 'lt'); + filter.gt = filterGt || me.createFilter({ + operator: 'gt', + value: (!stateful && value && value.gt) || null + }, 'gt'); + filter.eq = filterEq || me.createFilter({ + operator: 'eq', + value: (!stateful && value && value.eq) || null + }, 'eq'); + filter.in = filterIn || me.createFilter({ + operator: 'in', + type: 'numeric', + value: (!stateful && value && value.in) || null + }, 'in'); + me.filter = filter; + if (me.active) { + me.setColumnActive(true); + if (!stateful) { + for (operator in value) { + me.addStoreFilter(me.filter[operator]); + } + } + } + }, + setValue: function(value) { + var me = this, + filters = me.filter, + add = [], + remove = [], + active = false, + filterCollection = me.getGridStore().getFilters(), + field, filter, v, i, len, rLen, aLen; + if (me.preventFilterRemoval) { + return; + } + me.preventFilterRemoval = true; + if ('eq' in value) { + v = filters.lt.getValue(); + if (v || v === 0) { + remove.push(filters.lt); + } + v = filters.gt.getValue(); + if (v || v === 0) { + remove.push(filters.gt); + } + v = filters.in.getValue(); + if (v || v === 0) { + remove.push(filters.in); + } + v = value.eq; + if (v || v === 0) { + add.push(filters.eq); + filters.eq.setValue(v); + } else { + remove.push(filters.eq); + } + } else { + v = filters.eq.getValue(); + if (v || v === 0) { + remove.push(filters.eq); + } + if ('lt' in value) { + v = value.lt; + if (v || v === 0) { + add.push(filters.lt); + filters.lt.setValue(v); + } else { + remove.push(filters.lt); + } + } + if ('gt' in value) { + v = value.gt; + if (v || v === 0) { + add.push(filters.gt); + filters.gt.setValue(v); + } else { + remove.push(filters.gt); + } + } + if ('in' in value) { + v = value.in; + if (typeof v === "object" && v[0][0] == '') { + remove.push(filters.in); + } else if (v || v === 0) { + add.push(filters.in); + filters.in.setValue(v); + } else { + remove.push(filters.in); + } + } + } + rLen = remove.length; + aLen = add.length; + active = !!(me.countActiveFilters() + aLen - rLen); + if (rLen || aLen || active !== me.active) { + filterCollection.beginUpdate(); + if (rLen) { + for (i = 0; i < rLen; i++) { + filter = remove[i]; + me.fields[filter.getOperator()].setValue(null); + filter.setValue(null); + me.removeStoreFilter(filter); + } + } + if (aLen) { + for (i = 0; i < aLen; i++) { + me.addStoreFilter(add[i]); + } + } + me.setActive(active); + filterCollection.endUpdate(); + } + me.preventFilterRemoval = false; + } +}); + +Ext.define('Ext.grid.filters.filter.Number', { + extend: 'Ext.local.grid.filters.filter.TriFilter', + alias: ['grid.filter.number', 'grid.filter.numeric'], + + uses: ['Ext.form.field.Number'], + + type: 'number', + + config: { + fields: { + gt: { + iconCls: Ext.baseCSSPrefix + 'grid-filters-gt', + margin: '0 0 3px 0' + }, + lt: { + iconCls: Ext.baseCSSPrefix + 'grid-filters-lt', + margin: '0 0 3px 0' + }, + eq: { + iconCls: Ext.baseCSSPrefix + 'grid-filters-eq', + margin: '0 0 3px 0' + }, + in: { + iconCls: Ext.baseCSSPrefix + 'grid-filters-find', + margin: 0 + } + } + }, + + itemDefaults: { + enableKeyEvents: true, + hideEmptyLabel: false, + labelSeparator: '', + labelWidth: 29, + selectOnFocus: false + }, + + menuDefaults: { + bodyPadding: 3, + showSeparator: false + }, + + createMenu: function() { + var me = this, + listeners = { + scope: me, + keyup: me.onValueChange, + spin: { + fn: me.onInputSpin, + buffer: 200 + }, + el: { + click: me.stopFn + } + }, + itemDefaults = me.getItemDefaults(), + menuItems = me.menuItems, + fields = me.getFields(), + field, i, len, key, item, cfg; + + me.callParent(); + + me.fields = {}; + + for (i = 0, len = menuItems.length; i < len; i++) { + key = menuItems[i]; + + if (key !== '-' && key !== 'in') { + itemDefaults.xtype = 'numberfield'; + field = fields[key]; + + cfg = { + labelClsExtra: Ext.baseCSSPrefix + 'grid-filters-icon ' + field.iconCls, + emptyText: 'Enter Number...' + }; + + if (itemDefaults) { + Ext.merge(cfg, itemDefaults); + } + + Ext.merge(cfg, field); + cfg.emptyText = cfg.emptyText || me.emptyText; + delete cfg.iconCls; + + me.fields[key] = item = me.menu.add(cfg); + + item.filter = me.filter[key]; + item.filterKey = key; + item.on(listeners); + } else if (key === 'in') { + itemDefaults.xtype = 'textfield'; + field = fields.in; + + cfg = { + labelClsExtra: Ext.baseCSSPrefix + 'grid-filters-icon ' + field.iconCls, + emptyText: 'Enter Numbers...' + }; + + if (itemDefaults) { + Ext.merge(cfg, itemDefaults); + } + + Ext.merge(cfg, field); + cfg.emptyText = cfg.emptyText || me.emptyText; + delete cfg.iconCls; + + me.fields[key] = item = me.menu.add(cfg); + + item.filter = me.filter[key]; + item.filterKey = key; + item.on(listeners); + } + else { + me.menu.add(key); + } + } + }, + getValue: function(field) { + var value = {}; + + value[field.filterKey] = field.getValue(); + + return value; + }, + onInputSpin: function(field, direction) { + var value = {}; + + value[field.filterKey] = field.getValue(); + + this.setValue(value); + }, + stopFn: function(e) { + e.stopPropagation(); + } +}); diff --git a/src/Helper/GridHelperService.php b/src/Helper/GridHelperService.php index 1a61cece51..019709e16b 100644 --- a/src/Helper/GridHelperService.php +++ b/src/Helper/GridHelperService.php @@ -203,6 +203,22 @@ public function getFilterCondition(string $filterJson, ClassDefinition $class, ? } elseif ($filter['type'] == 'boolean') { $operator = '='; $filter['value'] = (int)$filter['value']; + } elseif ($filterOperator == 'in' && is_array($filter['value'])) { + $operator = 'in'; + $matches = preg_split('/[^0-9\.]+/', $filter['value'][0][0] ?? [], -1, PREG_SPLIT_NO_EMPTY); + if (is_array($matches) && count($matches) > 0) { + $filter['value'][0][0] = implode(',', array_unique(array_map(floatval(...), $matches))); + } else { + continue; + } + } elseif ($filterOperator == 'in' && !is_array($filter['value'])) { + $operator = 'in'; + $matches = preg_split('/[^0-9\.]+/', $filter['value'], -1, PREG_SPLIT_NO_EMPTY); + if (is_array($matches) && count($matches) > 0) { + $filter['value'] = implode(',', array_unique(array_map(floatval(...), $matches))); + } else { + continue; + } } else { if ($filterOperator == 'lt') { $operator = '<'; @@ -323,8 +339,10 @@ public function getFilterCondition(string $filterJson, ClassDefinition $class, ? $conditionPartsFilters[] = 'concat(`path`, `key`) ' . $operator . ' ' . $db->quote('%' . $filter['value'] . '%'); } elseif ($filterField == 'key') { $conditionPartsFilters[] = '`key` ' . $operator . ' ' . $db->quote('%' . $filter['value'] . '%'); - } elseif ($filterField == 'id') { + } elseif ($filterField == 'id' && $operator !== 'in') { $conditionPartsFilters[] = 'oo_id ' . $operator . ' ' . $db->quote($filter['value']); + } elseif ($filterField == 'id' && $operator === 'in') { + $conditionPartsFilters[] = 'oo_id ' . $operator . ' (' . $filter['value'] . ')'; } else { $filterField = $db->quoteIdentifier($filterField); if ($filter['type'] == 'date' && $operator == '=') {