From a476ba49cb48d86db1f3647e75f847103bc785fe Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 27 Jan 2023 23:54:50 -0500 Subject: [PATCH 01/33] Progress round 2, manual conflict resolution on base grid and tabs classes --- .eslintrc.js | 57 ++-- .../assets/modext/widgets/core/modx.grid.js | 322 ++++++++++++++++-- .../assets/modext/widgets/core/modx.tabs.js | 110 +++--- 3 files changed, 390 insertions(+), 99 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 36ad477a907..d0cfd62f6cf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,41 +1,58 @@ module.exports = { env: { browser: true, - es2021: true, + es2021: true }, extends: [ 'eslint:recommended', - 'airbnb-base', + 'airbnb-base' ], globals: { MODx: 'readonly', Ext: 'readonly', - _: 'readonly', + _: 'readonly' }, ignorePatterns: [ - 'manager/assets/modext/workspace/workspace.panel.js', 'manager/assets/ext3/**/*.js', 'manager/assets/fileapi/**/*.js', 'manager/assets/lib/**/*.js', 'manager/assets/modext/modx.jsgrps-min.js', - 'setup/assets/js/ext-core.js', - 'setup/assets/js/ext-core-debug.js', - ], - overrides: [ + 'setup/assets/js/ext-core-debug.js' ], + overrides: [], parserOptions: { - ecmaVersion: 'latest', + ecmaVersion: 'latest' }, rules: { - // TODO Enable rules gradually - indent: 0, - quotes: ['error', 'single'], - semi: 0, - 'space-before-function-paren': 0, - 'comma-dangle': 0, - 'prefer-arrow-callback': 0, - 'space-before-blocks': 0, - 'object-shorthand': 0, - }, -} + 'arrow-parens': ['error', 'as-needed'], + 'comma-dangle': ['error', 'never'], + 'consistent-return': 0, + curly: ['error', 'all'], + eqeqeq: ['error', 'smart'], + 'func-names': ['warn', 'as-needed'], + indent: ['error', 4, { + VariableDeclarator: 'first', + SwitchCase: 1 + }], + 'max-len': ['warn', { + code: 140, + ignoreComments: true + }], + 'no-continue': 'warn', + 'no-new': 'warn', + 'no-param-reassign': 'warn', + 'no-plusplus': ['error', { + allowForLoopAfterthoughts: true + }], + 'no-underscore-dangle': 'warn', + 'no-unused-vars': ['error', { args: 'none' }], + 'no-use-before-define': ['error', 'nofunc'], + 'object-shorthand': ['error', 'consistent'], + 'one-var': ['error', 'consecutive'], + 'prefer-arrow-callback': 'warn', + 'prefer-rest-params': 'warn', + 'semi-style': ['warn', 'last'], + 'space-before-function-paren': ['error', 'never'] + } +}; \ No newline at end of file diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js index e754c5e1d8f..a204f267679 100644 --- a/manager/assets/modext/widgets/core/modx.grid.js +++ b/manager/assets/modext/widgets/core/modx.grid.js @@ -143,8 +143,21 @@ MODx.grid.Grid = function(config) { this.on('afterAutoSave', this.onAfterAutoSave, this); } if (!config.preventRender) { this.render(); } - - this.on('rowcontextmenu',this._showMenu,this); + this.on({ + render: { + fn: function() { + const topToolbar = this.getTopToolbar(); + if (topToolbar && topToolbar.initialConfig.cls && topToolbar.initialConfig.cls == 'has-nested-filters') { + this.hasNestedFilters = true; + } + }, + scope: this + }, + rowcontextmenu: { + fn: this._showMenu, + scope: this + } + }); if (config.autosave) { this.on('afteredit',this.saveRecord,this); } @@ -787,9 +800,9 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ this.menu.record = record.data; this[actionHandler](record, recordIndex, e); - }, + } - actionContextMenu: function(record, recordIndex, e) { + ,actionContextMenu: function(record, recordIndex, e) { this._showMenu(this, recordIndex, e); } @@ -826,6 +839,35 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } + /** + * @property {Function} findTabPanel - Recursively search ownerCts for this component's enclosing TabPanel + * + * @param {Object} referenceCmp - A child component of the TabPanel we're looking for + * @return Ext.TabPanel + */ + ,findTabPanel: function(referenceCmp) { + if (!referenceCmp.hasOwnProperty('ownerCt')) { + console.error('MODx.grid.Grid::findTabPanel: This component must have an ownerCt to find its tab panel.'); + return false; + } + const container = referenceCmp.ownerCt, + isTabPanel = container.hasOwnProperty('xtype') && container.xtype.includes('tabs') + ; + if (isTabPanel) { + return container; + } + return this.findTabPanel(container); + } + + /** + * @property {Boolean} hasNestedFilters - Indicates whether the top toolbar filter(s) are nested + * within a secondary container; they will be nested when they have labels and those labels are + * positioned above the filter's input. + */ + ,hasNestedFilters: false + + ,currentLanguage: MODx.config.cultureKey || 'en' // removed MODx.request.language + /** * @property {Function} applyGridFilter - Filters the grid data by the passed filter component (field) * @@ -837,42 +879,32 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ const filterValue = cmp.getValue(), store = this.getStore(), urlParams = {}, + tabPanel = this.findTabPanel(this), bottomToolbar = this.getBottomToolbar() ; - let tabPanel = this.ownerCt.ownerCt, - hasParentTabPanel = false, + let hasParentTabPanel = false, parentTabItems, activeParentTabIdx ; if (!Ext.isEmpty(filterValue)) { urlParams[param] = filterValue; + } else { + MODx.util.url.clearParam(cmp); } if (param == 'ns') { store.baseParams['namespace'] = filterValue; } else { store.baseParams[param] = filterValue; } - /* - If there is an additional container in the structure, - we need to search further for the tabs object... - NOTE: This may be able to be removed once all grid panels have been - updated and their structures have been made consistent with one another - */ - if (!tabPanel.hasOwnProperty('xtype') || !tabPanel.xtype.includes('tabs')) { - if (tabPanel.ownerCt && tabPanel.ownerCt.xtype && tabPanel.ownerCt.xtype.includes('tabs')) { - tabPanel = tabPanel.ownerCt; - } - } - // Make sure we've retrieved a tab panel before working on/with it - if (tabPanel && tabPanel.xtype.includes('tabs')) { + if (tabPanel) { /* Determine if this is a vertical tab panel; if so there will also be a horizontal parent tab panel that needs to be accounted for */ if (tabPanel.xtype == 'modx-vtabs') { - const parentTabPanel = tabPanel.ownerCt.ownerCt; - if (parentTabPanel && parentTabPanel.xtype.includes('tabs')) { + const parentTabPanel = this.findTabPanel(tabPanel); + if (parentTabPanel) { const activeParentTab = parentTabPanel.getActiveTab(); hasParentTabPanel = true; parentTabItems = parentTabPanel.items; @@ -897,6 +929,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } } + // console.log('store baseParams: ', store.baseParams); store.load(); MODx.util.url.setParams(urlParams) if (bottomToolbar) { @@ -907,32 +940,57 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ /** * @property {Function} clearGridFilters - Clears all grid filters and sets them to their default value * - * @param {String} itemIds - A comma-separated list of the Ext component ids to be cleared + * @param {String|Array} items - A comma-separated list (or array) of items to be cleared. An optional default value + * may also be specified. The expected format for each item in the list is: + * 'filter-category', where 'filter-category' matches the Ext component's itemId and 'category' is the record index to filter on OR + * 'filter-category:3', where '3' is the filter's default value to be applied (instead of setting to an empty value) + * */ - ,clearGridFilters: function(itemIds) { + ,clearGridFilters: function(items) { const store = this.getStore(), - bottomToolbar = this.getBottomToolbar() + bottomToolbar = this.getBottomToolbar(), + data = Array.isArray(items) ? items : items.split(',') ; - itemIds = Array.isArray(itemIds) ? itemIds : itemIds.split(','); - /* - Note that param below relies on the following naming convention being followed for each filter's config: - itemId: 'filter-category', where 'category' is the record index to filter on - */ - itemIds.forEach(itemId => { - const id = itemId.trim(), - cmp = this.getFilterComponent(id) + data.forEach(item => { + itemData = item.replace(/\s+/g, '').split(':'); + // console.log('nested ct: ', topToolbar.find('itemId', `${item[0]}-container`)); + const itemId = itemData[0], + itemDefaultVal = itemData.length == 2 ? itemData[1] : null , + cmp = this.getFilterComponent(itemId), + param = MODx.util.url.getParamNameFromCmp(cmp) ; - let param = id.split('-')[1]; - param = param == 'ns' ? 'namespace' : param ; + // console.log('filter cmp: ', cmp); if (cmp.xtype.includes('combo')) { - cmp.setValue(null); + cmp.setValue(itemDefaultVal); } else { cmp.setValue(''); } - store.baseParams[param] = ''; + if (!Ext.isEmpty(itemDefaultVal)) { + // console.log('clear filters, cmp', cmp); + const paramsList = Object.keys(cmp.baseParams); + // console.log('filter param list: ', paramsList); + paramsList.forEach(param => { + switch(param) { + case 'namespace': + console.log(`namespace -- reset ${param} baseParam for ${itemId}, filterDefault: ${itemDefaultVal}`); + cmp.baseParams[param] = 'core'; + break; + case 'topic': + console.log(`topic -- reset ${param} baseParam for ${itemId}, filterDefault: ${itemDefaultVal}`); + cmp.baseParams[param] = 'default'; + break; + } + }); + cmp.getStore().load(); + // store.baseParams[param] = itemDefaultVal; + } else { + // store.baseParams[param] = ''; + } + store.baseParams[param] = itemDefaultVal; + // store.baseParams[param] = !Ext.isEmpty(itemDefaultVal) ? itemDefaultVal : '' ; }); store.load(); - MODx.util.url.clearParams(); + MODx.util.url.clearAllParams(); if (bottomToolbar) { bottomToolbar.changePage(1); } @@ -1013,6 +1071,196 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ filterStore.load(); } } + + /** + * @property {Function} resetDependentFilters - Desc + */ + ,resetDependentFilters: function(gridId, cmp, targets) { + const store = this.getStore(), + toolbar = cmp.ownerCt, + callerId = cmp.itemId, + callerDataIndex = callerId.replace('filter-', ''), + callerValue = cmp.getValue(), + targetCmps = {} + ; + targets = targets.replace(/\s+/g, '').split(','); + + // get all target filter component objects + targets.forEach(target => { + target = target.replace(/\s+/g, '').split(':')[0]; + const targetItemId = target.includes('filter-') ? target : `filter-${target}`; + targetCmps[target] = this.getFilterComponent(targetItemId); + + }); + // console.log('resetDependentFilters, gridId: ', gridId); + // console.log('resetDependentFilters, targetCmps', targetCmps); + + switch(gridId) { + case 'modx-grid-lexicon': + targets.forEach(target => { + target = target.replace(/\s+/g, '').split(':'); + const filterId = target[0], + filterDefault = target.length > 1 ? target[1] : null , + targetCurrentValue = targetCmps[filterId].getValue() + ; + + if (filterId == 'query') { + // probably do nothing ??? + } else { + // console.log('resetDependentFilters, callerDataIndex', callerDataIndex); + // console.log('resetDependentFilters, callerValue', callerValue); + /* + callerDataIndex is the data index of the filter whose value has changed, prompting + the update of this iteration's target (dependent filter) + */ + const targetStore = targetCmps[filterId].getStore(), + targetStoreLastOpts = targetStore.lastOptions + ; + switch(callerDataIndex) { + case 'ns': + // targetCmps[filterId].store.baseParams['namespace'] = callerValue; + // targetCmps[filterId].store.load(); + Ext.apply(targetStoreLastOpts.params, { + namespace: callerValue + }); + targetStore.reload(targetStoreLastOpts); + + if (filterId == 'language') { + const filterStore = targetCmps[filterId].getStore(), + currentValueFound = filterStore.findExact('name', targetCurrentValue) + ; + console.log(`${filterId} cmp: `, targetCmps[filterId]); + console.log(`${filterId} current val: `, targetCurrentValue); + console.log(`${filterId} default val: `, filterDefault); + console.log(`${filterId} current val found at: `, currentValueFound); + } + // targetCmps[filterId].setValue(filterDefault); + // targetCmps[filterId].store.load(); + break; + + case 'topic': + + break; + + case 'language': + // const targetCurrentValue = targetCmps[filterId].getValue(); + + targetCmps[filterId].store.baseParams['language'] = callerValue; + // console.log('resetDependentFilters, language > targetCmps:', targetCmps); + // console.log('resetDependentFilters, language > topic:', targetCmps[filterId]); + // console.log('resetDependentFilters, language > ns:', targetCmps['ns']); + + targetCmps[filterId].store.load(); + + const filterStore = targetCmps[filterId].getStore(), + currentValueFound = filterStore.findExact('name', targetCurrentValue) + ; + // console.log(`${targetCmps[filterId]} store: `, filterStore); + console.log(`${filterId} current val: `, targetCurrentValue); + console.log(`${filterId} default val: `, filterDefault); + console.log(`${filterId} current val found at: `, currentValueFound); + // Only reset to the default if the currently-selected topic is not found for this language + if (currentValueFound === -1) { + targetCmps[filterId].setValue(filterDefault); + targetCmps[filterId].store.load(); + } + break; + } + } + }); + + break; + } + store.load(); + } + + /** + * @property {Function} getQueryFilterField - Creates the query field component configuration + * + * @param {String} implementation - An optional identifier used to assign grid-specific behavior + * @param {Number} index - Optional explicitly-set tab index for this field + * @return {Object} + */ + ,getQueryFilterField: function(implementation = 'default', index = 1) { + // console.log('query fld implementation: ', implementation); + return { + xtype: 'textfield', + itemId: 'filter-query', + emptyText: _('search'), + value: MODx.request.query ? MODx.util.url.decodeParamValue(MODx.request.query) : '', + // tabIndex: index, + cls: 'filter-query', + listeners: { + change: { + fn: function(cmp, newValue, oldValue) { + this.applyGridFilter(cmp); + if (implementation == 'user-group-users') { + // console.log('doing extra stuff for ug users grid...'); + const usergroupTree = Ext.getCmp('modx-tree-usergroup'), + selectedNode = usergroupTree.getSelectionModel().getSelectedNode(), + groupId = MODx.util.tree.getGroupIdFromNode(selectedNode) + ; + MODx.util.url.setParams({group: groupId}); + // console.log('ug tree: ', usergroupTree); + // console.log('ug tree selected: ', selectedNode); + // console.log('ugu query change, this: ', this); + } + }, + scope: this + }, + afterrender: { + fn: function(cmp) { + if (MODx.request.query) { + this.applyGridFilter(cmp); + } + }, + scope: this + }, + render: { + fn: function(cmp) { + new Ext.KeyMap(cmp.getEl(), { + key: Ext.EventObject.ENTER, + fn: this.blur, + scope: cmp + }); + } + ,scope: this + } + } + } + } + + /** + * @property {Function} getClearFiltersButton - Creates the clear filter button component configuration + * + * @param {String} filters - A comma-separated list of filter component ids (itemId) specifying those that should be cleared + * @param {Number} index - Optional explicitly-set tab index for this field + * @return {Object} + */ + ,getClearFiltersButton: function(filters = 'filter-query', index = 2) { + if (Ext.isEmpty(filters)) { + console.error('MODx.grid.Grid::getClearFiltersButton: There was a problem creating the Clear Filter button because the supplied filters list is invalid.'); + return {}; + } + return { + text: _('filter_clear'), + itemId: 'filter-clear', + // tabIndex: index, + listeners: { + click: { + fn: function(cmp) { + this.clearGridFilters(filters); + }, + scope: this + }, + mouseout: { + fn: function(evt) { + this.removeClass('x-btn-focus'); + } + } + } + } + } }); /* local grid */ diff --git a/manager/assets/modext/widgets/core/modx.tabs.js b/manager/assets/modext/widgets/core/modx.tabs.js index 2bff4706b7f..8309469db7c 100644 --- a/manager/assets/modext/widgets/core/modx.tabs.js +++ b/manager/assets/modext/widgets/core/modx.tabs.js @@ -1,70 +1,96 @@ MODx.Tabs = function(config) { config = config || {}; - Ext.applyIf(config,{ - enableTabScroll: true - ,layoutOnTabChange: true - ,plain: true - ,deferredRender: true - ,hideMode: 'offsets' - ,defaults: { - autoHeight: true - ,hideMode: 'offsets' - ,border: true - ,autoWidth: true - ,bodyCssClass: 'tab-panel-wrapper' - } - ,activeTab: 0 - ,border: false - ,autoScroll: true - ,autoHeight: true - ,cls: 'modx-tabs' + Ext.applyIf(config, { + enableTabScroll: true, + layoutOnTabChange: true, + plain: true, + deferredRender: true, + hideMode: 'offsets', + defaults: { + autoHeight: true, + hideMode: 'offsets', + border: true, + autoWidth: true, + bodyCssClass: 'tab-panel-wrapper' + }, + activeTab: 0, + border: false, + autoScroll: true, + autoHeight: true, + cls: 'modx-tabs' }); - MODx.Tabs.superclass.constructor.call(this,config); + MODx.Tabs.superclass.constructor.call(this, config); this.config = config; this.on({ afterrender: function(tabPanel) { - if (MODx.request && MODx.request.hasOwnProperty('tab')){ - const tabId = parseInt(MODx.request.tab); - this.setActiveTab(tabId); + if (this.id !== 'modx-leftbar-tabpanel') { + console.log('tabs afterrender, activeTab: ', this.activeTab?.id); + } + if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'tab')) { + const tabId = parseInt(MODx.request.tab, 10); + // console.log('tabs, afterrender, this', this); + // Ensure tab panels other than the main one are unaffected + if (this.id !== 'modx-leftbar-tabpanel') { + console.log(`trying to set active tab to: ${tabId} for this:`, this); + this.setActiveTab(tabId); + } } /* Placing listener here because we only want to listen after the initial panel has loaded */ tabPanel.on({ - beforetabchange: function(tabPanel, newTab, currentTab) { + beforetabchange: function(tabPanelCmp, newTab, currentTab) { /* Only proceed with the clearing process if the tab has changed. This is needed to prevent clearing when a URL has been typed in. NOTE: The currentTab is the previous one being navigated away from */ + // console.log('request: ', MODx.request); + console.log('tabs, beforetabchange, currentTab (prev): ', currentTab.id); + console.log('tabs, beforetabchange, newTab: ', newTab?.id); + if (newTab && currentTab && newTab.id !== currentTab.id) { const resetVerticalTabPanelFilters = (currentTab.items?.items[0]?.xtype === 'modx-vtabs') || currentTab.ownerCt?.xtype === 'modx-vtabs', changedBetweenVtabs = newTab.ownerCt?.xtype === 'modx-vtabs' && currentTab.ownerCt?.xtype === 'modx-vtabs' ; + // console.log('tab changed...'); let itemsSource, gridObj = null ; if (resetVerticalTabPanelFilters) { - itemsSource = changedBetweenVtabs ? currentTab.items : currentTab.items.items[0].activeTab.items ; + itemsSource = changedBetweenVtabs + ? currentTab.items + : currentTab.items.items[0].activeTab.items; } else { itemsSource = currentTab.items; + // console.log('current tab:', currentTab); + // console.log('current tab items: ', itemsSource); } if (itemsSource.length > 0) { gridObj = itemsSource.find(obj => { - return Object.entries(obj).find(([key, value]) => key == 'xtype' && value.includes('modx-grid')); + // console.log('obj entries: ', Object.entries(obj)); + return Object.entries(obj).find(([key, value]) => key === 'xtype' && value.includes('modx-grid')); }); + // test = itemsSource.filter('id', 'modx-grid'); + // console.log('gridObj: ', gridObj); + // console.log('test filtered: ', test); + if (!gridObj) { + // keep? + } } if (gridObj) { const toolbar = gridObj.getTopToolbar(), filterIds = [] ; + // console.log('tabs, toolbar:', toolbar); if (toolbar && toolbar.items.items.length > 0) { toolbar.items.items.forEach(cmp => { - if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype == 'textfield') && cmp.itemId) { + if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype === 'textfield') && cmp.itemId) { filterIds.push(cmp.itemId); } }); } if (filterIds.length > 0) { + // console.log('clearing filters: ', filterIds); gridObj.clearGridFilters(filterIds); } } @@ -74,31 +100,31 @@ MODx.Tabs = function(config) { } }); }; -Ext.extend(MODx.Tabs,Ext.TabPanel); -Ext.reg('modx-tabs',MODx.Tabs); +Ext.extend(MODx.Tabs, Ext.TabPanel); +Ext.reg('modx-tabs', MODx.Tabs); MODx.VerticalTabs = function(config) { config = config || {}; - Ext.applyIf(config,{ - cls: 'vertical-tabs-panel' - ,headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' } - ,bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' } - ,defaults: { - bodyCssClass: 'vertical-tabs-body' - ,autoScroll: true - ,autoHeight: true - ,autoWidth: true - ,layout: 'form' + Ext.applyIf(config, { + cls: 'vertical-tabs-panel', + headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' }, + bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' }, + defaults: { + bodyCssClass: 'vertical-tabs-body', + autoScroll: true, + autoHeight: true, + autoWidth: true, + layout: 'form' } }); - MODx.VerticalTabs.superclass.constructor.call(this,config); + MODx.VerticalTabs.superclass.constructor.call(this, config); this.config = config; this.on('afterrender', function() { - if (MODx.request && MODx.request.hasOwnProperty('vtab')){ - const tabId = parseInt(MODx.request.vtab); + if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'vtab')) { + const tabId = parseInt(MODx.request.vtab, 10); this.setActiveTab(tabId); } }); }; Ext.extend(MODx.VerticalTabs, MODx.Tabs); -Ext.reg('modx-vtabs',MODx.VerticalTabs); +Ext.reg('modx-vtabs', MODx.VerticalTabs); From 9ec1bc8d0f530c0dbfff018c2e5ddcb0dd4da0b7 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 27 Jan 2023 23:57:34 -0500 Subject: [PATCH 02/33] Progress round 2, manual conflict resolution for usergroup acls panels --- .../modext/widgets/security/modx.grid.user.group.category.js | 2 ++ .../modext/widgets/security/modx.grid.user.group.context.js | 2 ++ .../modext/widgets/security/modx.grid.user.group.namespace.js | 2 ++ .../modext/widgets/security/modx.grid.user.group.resource.js | 2 ++ .../modext/widgets/security/modx.grid.user.group.source.js | 2 ++ 5 files changed, 10 insertions(+) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js index 5db825fbaa9..cb4a86edb95 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js @@ -147,6 +147,8 @@ MODx.grid.UserGroupCategory = function(config) { } ,scope: this } + // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account + // this.getClearFiltersButton('filter-category, filter-policy-category'); ] }); MODx.grid.UserGroupCategory.superclass.constructor.call(this,config); diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.context.js b/manager/assets/modext/widgets/security/modx.grid.user.group.context.js index ab7f4896455..62cc7e60328 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.context.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.context.js @@ -141,6 +141,8 @@ MODx.grid.UserGroupContext = function(config) { } ,scope: this } + // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account + // this.getClearFiltersButton('filter-context, filter-policy-context'); ] }); MODx.grid.UserGroupContext.superclass.constructor.call(this,config); diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js index 6569ff5f7fd..a349c91bcb9 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js @@ -146,6 +146,8 @@ MODx.grid.UserGroupNamespace = function(config) { } ,scope: this } + // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account + // this.getClearFiltersButton('filter-namespace, filter-policy-namespace'); ] }); MODx.grid.UserGroupNamespace.superclass.constructor.call(this,config); diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js b/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js index c552e928a4a..e6fb3a989fd 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js @@ -149,6 +149,8 @@ MODx.grid.UserGroupResourceGroup = function(config) { } ,scope: this } + // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account + // this.getClearFiltersButton('filter-resourceGroup, filter-policy-resourceGroup'); ] }); MODx.grid.UserGroupResourceGroup.superclass.constructor.call(this,config); diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.source.js b/manager/assets/modext/widgets/security/modx.grid.user.group.source.js index 6ad924fb7d7..4dd74a2d90a 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.source.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.source.js @@ -142,6 +142,8 @@ MODx.grid.UserGroupSource = function(config) { } ,scope: this } + // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account + // this.getClearFiltersButton('filter-source, filter-policy-source'); ] }); MODx.grid.UserGroupSource.superclass.constructor.call(this,config); From 4e89fc74be8263dfd769ace9600ec5023f2dae20 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sat, 28 Jan 2023 00:08:55 -0500 Subject: [PATCH 03/33] Revert "Progress round 2, manual conflict resolution on base grid and tabs classes" This reverts commit d70885cc3ecfcf97f80d39300aad2df84fcf1fdd. --- .eslintrc.js | 57 ++-- .../assets/modext/widgets/core/modx.grid.js | 322 ++---------------- .../assets/modext/widgets/core/modx.tabs.js | 110 +++--- 3 files changed, 99 insertions(+), 390 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index d0cfd62f6cf..36ad477a907 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,58 +1,41 @@ module.exports = { env: { browser: true, - es2021: true + es2021: true, }, extends: [ 'eslint:recommended', - 'airbnb-base' + 'airbnb-base', ], globals: { MODx: 'readonly', Ext: 'readonly', - _: 'readonly' + _: 'readonly', }, ignorePatterns: [ + 'manager/assets/modext/workspace/workspace.panel.js', 'manager/assets/ext3/**/*.js', 'manager/assets/fileapi/**/*.js', 'manager/assets/lib/**/*.js', 'manager/assets/modext/modx.jsgrps-min.js', + 'setup/assets/js/ext-core.js', - 'setup/assets/js/ext-core-debug.js' + 'setup/assets/js/ext-core-debug.js', + ], + overrides: [ ], - overrides: [], parserOptions: { - ecmaVersion: 'latest' + ecmaVersion: 'latest', }, rules: { - 'arrow-parens': ['error', 'as-needed'], - 'comma-dangle': ['error', 'never'], - 'consistent-return': 0, - curly: ['error', 'all'], - eqeqeq: ['error', 'smart'], - 'func-names': ['warn', 'as-needed'], - indent: ['error', 4, { - VariableDeclarator: 'first', - SwitchCase: 1 - }], - 'max-len': ['warn', { - code: 140, - ignoreComments: true - }], - 'no-continue': 'warn', - 'no-new': 'warn', - 'no-param-reassign': 'warn', - 'no-plusplus': ['error', { - allowForLoopAfterthoughts: true - }], - 'no-underscore-dangle': 'warn', - 'no-unused-vars': ['error', { args: 'none' }], - 'no-use-before-define': ['error', 'nofunc'], - 'object-shorthand': ['error', 'consistent'], - 'one-var': ['error', 'consecutive'], - 'prefer-arrow-callback': 'warn', - 'prefer-rest-params': 'warn', - 'semi-style': ['warn', 'last'], - 'space-before-function-paren': ['error', 'never'] - } -}; \ No newline at end of file + // TODO Enable rules gradually + indent: 0, + quotes: ['error', 'single'], + semi: 0, + 'space-before-function-paren': 0, + 'comma-dangle': 0, + 'prefer-arrow-callback': 0, + 'space-before-blocks': 0, + 'object-shorthand': 0, + }, +} diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js index a204f267679..e754c5e1d8f 100644 --- a/manager/assets/modext/widgets/core/modx.grid.js +++ b/manager/assets/modext/widgets/core/modx.grid.js @@ -143,21 +143,8 @@ MODx.grid.Grid = function(config) { this.on('afterAutoSave', this.onAfterAutoSave, this); } if (!config.preventRender) { this.render(); } - this.on({ - render: { - fn: function() { - const topToolbar = this.getTopToolbar(); - if (topToolbar && topToolbar.initialConfig.cls && topToolbar.initialConfig.cls == 'has-nested-filters') { - this.hasNestedFilters = true; - } - }, - scope: this - }, - rowcontextmenu: { - fn: this._showMenu, - scope: this - } - }); + + this.on('rowcontextmenu',this._showMenu,this); if (config.autosave) { this.on('afteredit',this.saveRecord,this); } @@ -800,9 +787,9 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ this.menu.record = record.data; this[actionHandler](record, recordIndex, e); - } + }, - ,actionContextMenu: function(record, recordIndex, e) { + actionContextMenu: function(record, recordIndex, e) { this._showMenu(this, recordIndex, e); } @@ -839,35 +826,6 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } - /** - * @property {Function} findTabPanel - Recursively search ownerCts for this component's enclosing TabPanel - * - * @param {Object} referenceCmp - A child component of the TabPanel we're looking for - * @return Ext.TabPanel - */ - ,findTabPanel: function(referenceCmp) { - if (!referenceCmp.hasOwnProperty('ownerCt')) { - console.error('MODx.grid.Grid::findTabPanel: This component must have an ownerCt to find its tab panel.'); - return false; - } - const container = referenceCmp.ownerCt, - isTabPanel = container.hasOwnProperty('xtype') && container.xtype.includes('tabs') - ; - if (isTabPanel) { - return container; - } - return this.findTabPanel(container); - } - - /** - * @property {Boolean} hasNestedFilters - Indicates whether the top toolbar filter(s) are nested - * within a secondary container; they will be nested when they have labels and those labels are - * positioned above the filter's input. - */ - ,hasNestedFilters: false - - ,currentLanguage: MODx.config.cultureKey || 'en' // removed MODx.request.language - /** * @property {Function} applyGridFilter - Filters the grid data by the passed filter component (field) * @@ -879,32 +837,42 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ const filterValue = cmp.getValue(), store = this.getStore(), urlParams = {}, - tabPanel = this.findTabPanel(this), bottomToolbar = this.getBottomToolbar() ; - let hasParentTabPanel = false, + let tabPanel = this.ownerCt.ownerCt, + hasParentTabPanel = false, parentTabItems, activeParentTabIdx ; if (!Ext.isEmpty(filterValue)) { urlParams[param] = filterValue; - } else { - MODx.util.url.clearParam(cmp); } if (param == 'ns') { store.baseParams['namespace'] = filterValue; } else { store.baseParams[param] = filterValue; } + /* + If there is an additional container in the structure, + we need to search further for the tabs object... - if (tabPanel) { + NOTE: This may be able to be removed once all grid panels have been + updated and their structures have been made consistent with one another + */ + if (!tabPanel.hasOwnProperty('xtype') || !tabPanel.xtype.includes('tabs')) { + if (tabPanel.ownerCt && tabPanel.ownerCt.xtype && tabPanel.ownerCt.xtype.includes('tabs')) { + tabPanel = tabPanel.ownerCt; + } + } + // Make sure we've retrieved a tab panel before working on/with it + if (tabPanel && tabPanel.xtype.includes('tabs')) { /* Determine if this is a vertical tab panel; if so there will also be a horizontal parent tab panel that needs to be accounted for */ if (tabPanel.xtype == 'modx-vtabs') { - const parentTabPanel = this.findTabPanel(tabPanel); - if (parentTabPanel) { + const parentTabPanel = tabPanel.ownerCt.ownerCt; + if (parentTabPanel && parentTabPanel.xtype.includes('tabs')) { const activeParentTab = parentTabPanel.getActiveTab(); hasParentTabPanel = true; parentTabItems = parentTabPanel.items; @@ -929,7 +897,6 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } } - // console.log('store baseParams: ', store.baseParams); store.load(); MODx.util.url.setParams(urlParams) if (bottomToolbar) { @@ -940,57 +907,32 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ /** * @property {Function} clearGridFilters - Clears all grid filters and sets them to their default value * - * @param {String|Array} items - A comma-separated list (or array) of items to be cleared. An optional default value - * may also be specified. The expected format for each item in the list is: - * 'filter-category', where 'filter-category' matches the Ext component's itemId and 'category' is the record index to filter on OR - * 'filter-category:3', where '3' is the filter's default value to be applied (instead of setting to an empty value) - * + * @param {String} itemIds - A comma-separated list of the Ext component ids to be cleared */ - ,clearGridFilters: function(items) { + ,clearGridFilters: function(itemIds) { const store = this.getStore(), - bottomToolbar = this.getBottomToolbar(), - data = Array.isArray(items) ? items : items.split(',') + bottomToolbar = this.getBottomToolbar() ; - data.forEach(item => { - itemData = item.replace(/\s+/g, '').split(':'); - // console.log('nested ct: ', topToolbar.find('itemId', `${item[0]}-container`)); - const itemId = itemData[0], - itemDefaultVal = itemData.length == 2 ? itemData[1] : null , - cmp = this.getFilterComponent(itemId), - param = MODx.util.url.getParamNameFromCmp(cmp) + itemIds = Array.isArray(itemIds) ? itemIds : itemIds.split(','); + /* + Note that param below relies on the following naming convention being followed for each filter's config: + itemId: 'filter-category', where 'category' is the record index to filter on + */ + itemIds.forEach(itemId => { + const id = itemId.trim(), + cmp = this.getFilterComponent(id) ; - // console.log('filter cmp: ', cmp); + let param = id.split('-')[1]; + param = param == 'ns' ? 'namespace' : param ; if (cmp.xtype.includes('combo')) { - cmp.setValue(itemDefaultVal); + cmp.setValue(null); } else { cmp.setValue(''); } - if (!Ext.isEmpty(itemDefaultVal)) { - // console.log('clear filters, cmp', cmp); - const paramsList = Object.keys(cmp.baseParams); - // console.log('filter param list: ', paramsList); - paramsList.forEach(param => { - switch(param) { - case 'namespace': - console.log(`namespace -- reset ${param} baseParam for ${itemId}, filterDefault: ${itemDefaultVal}`); - cmp.baseParams[param] = 'core'; - break; - case 'topic': - console.log(`topic -- reset ${param} baseParam for ${itemId}, filterDefault: ${itemDefaultVal}`); - cmp.baseParams[param] = 'default'; - break; - } - }); - cmp.getStore().load(); - // store.baseParams[param] = itemDefaultVal; - } else { - // store.baseParams[param] = ''; - } - store.baseParams[param] = itemDefaultVal; - // store.baseParams[param] = !Ext.isEmpty(itemDefaultVal) ? itemDefaultVal : '' ; + store.baseParams[param] = ''; }); store.load(); - MODx.util.url.clearAllParams(); + MODx.util.url.clearParams(); if (bottomToolbar) { bottomToolbar.changePage(1); } @@ -1071,196 +1013,6 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ filterStore.load(); } } - - /** - * @property {Function} resetDependentFilters - Desc - */ - ,resetDependentFilters: function(gridId, cmp, targets) { - const store = this.getStore(), - toolbar = cmp.ownerCt, - callerId = cmp.itemId, - callerDataIndex = callerId.replace('filter-', ''), - callerValue = cmp.getValue(), - targetCmps = {} - ; - targets = targets.replace(/\s+/g, '').split(','); - - // get all target filter component objects - targets.forEach(target => { - target = target.replace(/\s+/g, '').split(':')[0]; - const targetItemId = target.includes('filter-') ? target : `filter-${target}`; - targetCmps[target] = this.getFilterComponent(targetItemId); - - }); - // console.log('resetDependentFilters, gridId: ', gridId); - // console.log('resetDependentFilters, targetCmps', targetCmps); - - switch(gridId) { - case 'modx-grid-lexicon': - targets.forEach(target => { - target = target.replace(/\s+/g, '').split(':'); - const filterId = target[0], - filterDefault = target.length > 1 ? target[1] : null , - targetCurrentValue = targetCmps[filterId].getValue() - ; - - if (filterId == 'query') { - // probably do nothing ??? - } else { - // console.log('resetDependentFilters, callerDataIndex', callerDataIndex); - // console.log('resetDependentFilters, callerValue', callerValue); - /* - callerDataIndex is the data index of the filter whose value has changed, prompting - the update of this iteration's target (dependent filter) - */ - const targetStore = targetCmps[filterId].getStore(), - targetStoreLastOpts = targetStore.lastOptions - ; - switch(callerDataIndex) { - case 'ns': - // targetCmps[filterId].store.baseParams['namespace'] = callerValue; - // targetCmps[filterId].store.load(); - Ext.apply(targetStoreLastOpts.params, { - namespace: callerValue - }); - targetStore.reload(targetStoreLastOpts); - - if (filterId == 'language') { - const filterStore = targetCmps[filterId].getStore(), - currentValueFound = filterStore.findExact('name', targetCurrentValue) - ; - console.log(`${filterId} cmp: `, targetCmps[filterId]); - console.log(`${filterId} current val: `, targetCurrentValue); - console.log(`${filterId} default val: `, filterDefault); - console.log(`${filterId} current val found at: `, currentValueFound); - } - // targetCmps[filterId].setValue(filterDefault); - // targetCmps[filterId].store.load(); - break; - - case 'topic': - - break; - - case 'language': - // const targetCurrentValue = targetCmps[filterId].getValue(); - - targetCmps[filterId].store.baseParams['language'] = callerValue; - // console.log('resetDependentFilters, language > targetCmps:', targetCmps); - // console.log('resetDependentFilters, language > topic:', targetCmps[filterId]); - // console.log('resetDependentFilters, language > ns:', targetCmps['ns']); - - targetCmps[filterId].store.load(); - - const filterStore = targetCmps[filterId].getStore(), - currentValueFound = filterStore.findExact('name', targetCurrentValue) - ; - // console.log(`${targetCmps[filterId]} store: `, filterStore); - console.log(`${filterId} current val: `, targetCurrentValue); - console.log(`${filterId} default val: `, filterDefault); - console.log(`${filterId} current val found at: `, currentValueFound); - // Only reset to the default if the currently-selected topic is not found for this language - if (currentValueFound === -1) { - targetCmps[filterId].setValue(filterDefault); - targetCmps[filterId].store.load(); - } - break; - } - } - }); - - break; - } - store.load(); - } - - /** - * @property {Function} getQueryFilterField - Creates the query field component configuration - * - * @param {String} implementation - An optional identifier used to assign grid-specific behavior - * @param {Number} index - Optional explicitly-set tab index for this field - * @return {Object} - */ - ,getQueryFilterField: function(implementation = 'default', index = 1) { - // console.log('query fld implementation: ', implementation); - return { - xtype: 'textfield', - itemId: 'filter-query', - emptyText: _('search'), - value: MODx.request.query ? MODx.util.url.decodeParamValue(MODx.request.query) : '', - // tabIndex: index, - cls: 'filter-query', - listeners: { - change: { - fn: function(cmp, newValue, oldValue) { - this.applyGridFilter(cmp); - if (implementation == 'user-group-users') { - // console.log('doing extra stuff for ug users grid...'); - const usergroupTree = Ext.getCmp('modx-tree-usergroup'), - selectedNode = usergroupTree.getSelectionModel().getSelectedNode(), - groupId = MODx.util.tree.getGroupIdFromNode(selectedNode) - ; - MODx.util.url.setParams({group: groupId}); - // console.log('ug tree: ', usergroupTree); - // console.log('ug tree selected: ', selectedNode); - // console.log('ugu query change, this: ', this); - } - }, - scope: this - }, - afterrender: { - fn: function(cmp) { - if (MODx.request.query) { - this.applyGridFilter(cmp); - } - }, - scope: this - }, - render: { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER, - fn: this.blur, - scope: cmp - }); - } - ,scope: this - } - } - } - } - - /** - * @property {Function} getClearFiltersButton - Creates the clear filter button component configuration - * - * @param {String} filters - A comma-separated list of filter component ids (itemId) specifying those that should be cleared - * @param {Number} index - Optional explicitly-set tab index for this field - * @return {Object} - */ - ,getClearFiltersButton: function(filters = 'filter-query', index = 2) { - if (Ext.isEmpty(filters)) { - console.error('MODx.grid.Grid::getClearFiltersButton: There was a problem creating the Clear Filter button because the supplied filters list is invalid.'); - return {}; - } - return { - text: _('filter_clear'), - itemId: 'filter-clear', - // tabIndex: index, - listeners: { - click: { - fn: function(cmp) { - this.clearGridFilters(filters); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - } - } }); /* local grid */ diff --git a/manager/assets/modext/widgets/core/modx.tabs.js b/manager/assets/modext/widgets/core/modx.tabs.js index 8309469db7c..2bff4706b7f 100644 --- a/manager/assets/modext/widgets/core/modx.tabs.js +++ b/manager/assets/modext/widgets/core/modx.tabs.js @@ -1,96 +1,70 @@ MODx.Tabs = function(config) { config = config || {}; - Ext.applyIf(config, { - enableTabScroll: true, - layoutOnTabChange: true, - plain: true, - deferredRender: true, - hideMode: 'offsets', - defaults: { - autoHeight: true, - hideMode: 'offsets', - border: true, - autoWidth: true, - bodyCssClass: 'tab-panel-wrapper' - }, - activeTab: 0, - border: false, - autoScroll: true, - autoHeight: true, - cls: 'modx-tabs' + Ext.applyIf(config,{ + enableTabScroll: true + ,layoutOnTabChange: true + ,plain: true + ,deferredRender: true + ,hideMode: 'offsets' + ,defaults: { + autoHeight: true + ,hideMode: 'offsets' + ,border: true + ,autoWidth: true + ,bodyCssClass: 'tab-panel-wrapper' + } + ,activeTab: 0 + ,border: false + ,autoScroll: true + ,autoHeight: true + ,cls: 'modx-tabs' }); - MODx.Tabs.superclass.constructor.call(this, config); + MODx.Tabs.superclass.constructor.call(this,config); this.config = config; this.on({ afterrender: function(tabPanel) { - if (this.id !== 'modx-leftbar-tabpanel') { - console.log('tabs afterrender, activeTab: ', this.activeTab?.id); - } - if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'tab')) { - const tabId = parseInt(MODx.request.tab, 10); - // console.log('tabs, afterrender, this', this); - // Ensure tab panels other than the main one are unaffected - if (this.id !== 'modx-leftbar-tabpanel') { - console.log(`trying to set active tab to: ${tabId} for this:`, this); - this.setActiveTab(tabId); - } + if (MODx.request && MODx.request.hasOwnProperty('tab')){ + const tabId = parseInt(MODx.request.tab); + this.setActiveTab(tabId); } /* Placing listener here because we only want to listen after the initial panel has loaded */ tabPanel.on({ - beforetabchange: function(tabPanelCmp, newTab, currentTab) { + beforetabchange: function(tabPanel, newTab, currentTab) { /* Only proceed with the clearing process if the tab has changed. This is needed to prevent clearing when a URL has been typed in. NOTE: The currentTab is the previous one being navigated away from */ - // console.log('request: ', MODx.request); - console.log('tabs, beforetabchange, currentTab (prev): ', currentTab.id); - console.log('tabs, beforetabchange, newTab: ', newTab?.id); - if (newTab && currentTab && newTab.id !== currentTab.id) { const resetVerticalTabPanelFilters = (currentTab.items?.items[0]?.xtype === 'modx-vtabs') || currentTab.ownerCt?.xtype === 'modx-vtabs', changedBetweenVtabs = newTab.ownerCt?.xtype === 'modx-vtabs' && currentTab.ownerCt?.xtype === 'modx-vtabs' ; - // console.log('tab changed...'); let itemsSource, gridObj = null ; if (resetVerticalTabPanelFilters) { - itemsSource = changedBetweenVtabs - ? currentTab.items - : currentTab.items.items[0].activeTab.items; + itemsSource = changedBetweenVtabs ? currentTab.items : currentTab.items.items[0].activeTab.items ; } else { itemsSource = currentTab.items; - // console.log('current tab:', currentTab); - // console.log('current tab items: ', itemsSource); } if (itemsSource.length > 0) { gridObj = itemsSource.find(obj => { - // console.log('obj entries: ', Object.entries(obj)); - return Object.entries(obj).find(([key, value]) => key === 'xtype' && value.includes('modx-grid')); + return Object.entries(obj).find(([key, value]) => key == 'xtype' && value.includes('modx-grid')); }); - // test = itemsSource.filter('id', 'modx-grid'); - // console.log('gridObj: ', gridObj); - // console.log('test filtered: ', test); - if (!gridObj) { - // keep? - } } if (gridObj) { const toolbar = gridObj.getTopToolbar(), filterIds = [] ; - // console.log('tabs, toolbar:', toolbar); if (toolbar && toolbar.items.items.length > 0) { toolbar.items.items.forEach(cmp => { - if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype === 'textfield') && cmp.itemId) { + if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype == 'textfield') && cmp.itemId) { filterIds.push(cmp.itemId); } }); } if (filterIds.length > 0) { - // console.log('clearing filters: ', filterIds); gridObj.clearGridFilters(filterIds); } } @@ -100,31 +74,31 @@ MODx.Tabs = function(config) { } }); }; -Ext.extend(MODx.Tabs, Ext.TabPanel); -Ext.reg('modx-tabs', MODx.Tabs); +Ext.extend(MODx.Tabs,Ext.TabPanel); +Ext.reg('modx-tabs',MODx.Tabs); MODx.VerticalTabs = function(config) { config = config || {}; - Ext.applyIf(config, { - cls: 'vertical-tabs-panel', - headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' }, - bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' }, - defaults: { - bodyCssClass: 'vertical-tabs-body', - autoScroll: true, - autoHeight: true, - autoWidth: true, - layout: 'form' + Ext.applyIf(config,{ + cls: 'vertical-tabs-panel' + ,headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' } + ,bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' } + ,defaults: { + bodyCssClass: 'vertical-tabs-body' + ,autoScroll: true + ,autoHeight: true + ,autoWidth: true + ,layout: 'form' } }); - MODx.VerticalTabs.superclass.constructor.call(this, config); + MODx.VerticalTabs.superclass.constructor.call(this,config); this.config = config; this.on('afterrender', function() { - if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'vtab')) { - const tabId = parseInt(MODx.request.vtab, 10); + if (MODx.request && MODx.request.hasOwnProperty('vtab')){ + const tabId = parseInt(MODx.request.vtab); this.setActiveTab(tabId); } }); }; Ext.extend(MODx.VerticalTabs, MODx.Tabs); -Ext.reg('modx-vtabs', MODx.VerticalTabs); +Ext.reg('modx-vtabs',MODx.VerticalTabs); From a6e935a44ffd57e1a5512f2c90e6f97b1cf2b7a1 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 00:19:36 -0500 Subject: [PATCH 04/33] Base class updates required for grid enhancements Contains new required methods and tweaks for grid updates to follow --- manager/assets/modext/core/modx.js | 1 - manager/assets/modext/util/utilities.js | 130 ++++++++--- .../assets/modext/widgets/core/modx.grid.js | 220 +++++++++++++++--- .../assets/modext/widgets/core/modx.tabs.js | 121 ++++++---- .../modext/widgets/core/tree/modx.tree.js | 3 +- 5 files changed, 354 insertions(+), 121 deletions(-) diff --git a/manager/assets/modext/core/modx.js b/manager/assets/modext/core/modx.js index 17204dcbe73..b8c36182b6a 100644 --- a/manager/assets/modext/core/modx.js +++ b/manager/assets/modext/core/modx.js @@ -551,7 +551,6 @@ Ext.extend(MODx,Ext.Component,{ }); tabPanel.add(newTabConfig); tabPanel.doLayout(); - tabPanel.setActiveTab(0); } } ,hiddenTabs: [] diff --git a/manager/assets/modext/util/utilities.js b/manager/assets/modext/util/utilities.js index f4c456331cd..6d85a1d8289 100644 --- a/manager/assets/modext/util/utilities.js +++ b/manager/assets/modext/util/utilities.js @@ -69,12 +69,12 @@ Ext.override(Ext.form.NumberField, { fixPrecision : function(value){ var nan = isNaN(value); if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){ - return nan ? '' : value; + return nan ? '' : value; } return this.allowDecimals && this.strictDecimalPrecision ? parseFloat(value).toFixed(this.decimalPrecision) : parseFloat(parseFloat(value).toFixed(this.decimalPrecision)) - ; + ; } }); @@ -234,23 +234,23 @@ Ext.form.getCheckboxMask = function(cbgroup) { Ext.form.BasicForm.prototype.append = function() { - var layout = new Ext.form.Layout(); - var fields = []; - layout.stack.push.apply(layout.stack, arguments); - for(var i = 0; i < arguments.length; i=i+1) { - if(arguments[i].isFormField) { - fields.push(arguments[i]); - } - } - layout.render(this.el); - - if(fields.length > 0) { - this.items.addAll(fields); - for(var f=0;f 0) { + this.items.addAll(fields); + for(var f=0;f', buf = ['
  • ', - '',this.indentMarkup,"", - elbowMarkup, - iconMarkup, - cb ? ('' : '/>')) : '', - '',renderer(a),"
    ", - '', - "
  • "].join(''); + '',this.indentMarkup,"", + elbowMarkup, + iconMarkup, + cb ? ('' : '/>')) : '', + '',renderer(a),"", + '', + ""].join(''); if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){ this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf); @@ -535,7 +535,7 @@ MODx.util.url = { * @param {Object} filterData - A set of filter parameter name/value pairs to be added to (or changed in) the URL * @param {Object} stateData - Optional data to be used in subsequent url processing */ - setParams: function(filterData, stateData = {}){ + setParams: function(filterData, stateData = {}) { if (typeof window.history.replaceState !== 'undefined') { const url = new URL(window.location.href), params = url.searchParams @@ -548,12 +548,12 @@ MODx.util.url = { } }, /** - * @property {Function} clearParams - Clears all dynamically set url parameters, + * @property {Function} clearAllParams - Clears all dynamically set url parameters, * while preserving those in a pre-defined list. * * @param {Object} stateData - Optional data to be used in subsequent url processing */ - clearParams: function(stateData = {}) { + clearAllParams: function(stateData = {}) { if (typeof window.history.replaceState !== 'undefined') { const preserve = ['a', 'id', 'key'], preserved = [], @@ -570,8 +570,62 @@ MODx.util.url = { newUrl = newUrl.toString().replace(/%2F/g, '/'); window.history.replaceState(stateData, document.title, newUrl); } + }, + /** + * @property {Function} clearParam - Clears a single url parameter; + * the param name is derived from the calling filter's component itemId. + * + * @param {Object|String} reference - The filter's Ext component or the parameter name + * @param {Boolean} referenceIsComponent - Set to true if deriving parameter from a filter's Ext component, false + * @param {Object} stateData - Optional data to be used in subsequent url processing + */ + clearParam: function(reference, referenceIsComponent = true, stateData = {}) { + if (typeof window.history.replaceState !== 'undefined') { + const urlParts = window.location.href.split('?'), + removeParamName = referenceIsComponent ? this.getParamNameFromCmp(reference) : reference.trim(), + regex = new RegExp(`${removeParamName}=[^&]+`, 'i') + ; + let params = urlParts[1].replace(regex, '').replace('&&', '&'); + if (params.endsWith('&')) { + params = params.substr(0, params.length - 1); + } + let newUrl = new URL(`${urlParts[0]}?${params}`); + newUrl = newUrl.toString().replace(/%2F/g, '/'); + window.history.replaceState(stateData, document.title, newUrl); + } + }, + /** + * @property {Function} getParamNameFromCmp - Parses a filter component's + * itemId to get the url parameter name, based on the following naming convention: + * itemId: 'filter-[filterName]-[optionalAdditionalIdentifiers]' + * + * @param {Object} cmp - The filter's Ext component + * @return {String} + */ + getParamNameFromCmp: function(cmp) { + const param = cmp.itemId.split('-')[1]; + return param === 'ns' ? 'namespace' : param ; + }, + /** + * @property {Function} decodeParamValue - Decodes a given parameter's value + * + * @param {String} value + * @return {String} + */ + decodeParamValue: function(value) { + value = value.replace(/\+/g, ' '); + return decodeURIComponent(value); } -} +}; + +/** + * Utility methods for tree objects + */ +MODx.util.tree = { + getGroupIdFromNode: function(node) { + return node.id ? node.id.split('_').pop() : 0 ; + } +}; Ext.util.Format.trimCommas = function(s) { s = s.replace(',,',','); @@ -699,12 +753,12 @@ Ext.namespace('Ext.ux.dd');Ext.ux.dd.GridDragDropRowOrder=Ext.extend(Ext.util.Ob /** selectability in Ext grids */ if (!Ext.grid.GridView.prototype.templates) { - Ext.grid.GridView.prototype.templates = {}; + Ext.grid.GridView.prototype.templates = {}; } Ext.grid.GridView.prototype.templates.cell = new Ext.Template( - '', - '
    {value}
    ', - '' + '', + '
    {value}
    ', + '' ); /* combocolumn */ @@ -762,8 +816,8 @@ Ext.Button.buttonTemplate = new Ext.Template( Ext.Button.buttonTemplate.compile(); Ext.TabPanel.prototype.itemTpl = new Ext.Template( - '
  • ', - '{text}
  • ' + '
  • ', + '{text}
  • ' ); Ext.TabPanel.prototype.itemTpl.disableFormats = true; Ext.TabPanel.prototype.itemTpl.compile(); @@ -782,7 +836,7 @@ Ext.ux.form.CheckboxGroup = Ext.extend(Ext.form.CheckboxGroup, { initComponent: function() { const me = this, ct = this.ownerCt - ; + ; if (typeof this.name === 'string' && this.name.length > 0) { this.aggregateSubmitField = new Ext.form.Hidden({ name: this.name diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js index e754c5e1d8f..1e8cd0fa91a 100644 --- a/manager/assets/modext/widgets/core/modx.grid.js +++ b/manager/assets/modext/widgets/core/modx.grid.js @@ -143,8 +143,21 @@ MODx.grid.Grid = function(config) { this.on('afterAutoSave', this.onAfterAutoSave, this); } if (!config.preventRender) { this.render(); } - - this.on('rowcontextmenu',this._showMenu,this); + this.on({ + render: { + fn: function() { + const topToolbar = this.getTopToolbar(); + if (topToolbar && topToolbar.initialConfig.cls && topToolbar.initialConfig.cls == 'has-nested-filters') { + this.hasNestedFilters = true; + } + }, + scope: this + }, + rowcontextmenu: { + fn: this._showMenu, + scope: this + } + }); if (config.autosave) { this.on('afteredit',this.saveRecord,this); } @@ -787,9 +800,9 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ this.menu.record = record.data; this[actionHandler](record, recordIndex, e); - }, + } - actionContextMenu: function(record, recordIndex, e) { + ,actionContextMenu: function(record, recordIndex, e) { this._showMenu(this, recordIndex, e); } @@ -826,6 +839,35 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } + /** + * @property {Function} findTabPanel - Recursively search ownerCts for this component's enclosing TabPanel + * + * @param {Object} referenceCmp - A child component of the TabPanel we're looking for + * @return Ext.TabPanel + */ + ,findTabPanel: function(referenceCmp) { + if (!referenceCmp.hasOwnProperty('ownerCt')) { + console.error('MODx.grid.Grid::findTabPanel: This component must have an ownerCt to find its tab panel.'); + return false; + } + const container = referenceCmp.ownerCt, + isTabPanel = container.hasOwnProperty('xtype') && container.xtype.includes('tabs') + ; + if (isTabPanel) { + return container; + } + return this.findTabPanel(container); + } + + /** + * @property {Boolean} hasNestedFilters - Indicates whether the top toolbar filter(s) are nested + * within a secondary container; they will be nested when they have labels and those labels are + * positioned above the filter's input. + */ + ,hasNestedFilters: false + + ,currentLanguage: MODx.config.cultureKey || 'en' // removed MODx.request.language + /** * @property {Function} applyGridFilter - Filters the grid data by the passed filter component (field) * @@ -837,42 +879,32 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ const filterValue = cmp.getValue(), store = this.getStore(), urlParams = {}, + tabPanel = this.findTabPanel(this), bottomToolbar = this.getBottomToolbar() ; - let tabPanel = this.ownerCt.ownerCt, - hasParentTabPanel = false, + let hasParentTabPanel = false, parentTabItems, activeParentTabIdx ; if (!Ext.isEmpty(filterValue)) { urlParams[param] = filterValue; + } else { + MODx.util.url.clearParam(cmp); } if (param == 'ns') { store.baseParams['namespace'] = filterValue; } else { store.baseParams[param] = filterValue; } - /* - If there is an additional container in the structure, - we need to search further for the tabs object... - NOTE: This may be able to be removed once all grid panels have been - updated and their structures have been made consistent with one another - */ - if (!tabPanel.hasOwnProperty('xtype') || !tabPanel.xtype.includes('tabs')) { - if (tabPanel.ownerCt && tabPanel.ownerCt.xtype && tabPanel.ownerCt.xtype.includes('tabs')) { - tabPanel = tabPanel.ownerCt; - } - } - // Make sure we've retrieved a tab panel before working on/with it - if (tabPanel && tabPanel.xtype.includes('tabs')) { + if (tabPanel) { /* Determine if this is a vertical tab panel; if so there will also be a horizontal parent tab panel that needs to be accounted for */ if (tabPanel.xtype == 'modx-vtabs') { - const parentTabPanel = tabPanel.ownerCt.ownerCt; - if (parentTabPanel && parentTabPanel.xtype.includes('tabs')) { + const parentTabPanel = this.findTabPanel(tabPanel); + if (parentTabPanel) { const activeParentTab = parentTabPanel.getActiveTab(); hasParentTabPanel = true; parentTabItems = parentTabPanel.items; @@ -907,32 +939,47 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ /** * @property {Function} clearGridFilters - Clears all grid filters and sets them to their default value * - * @param {String} itemIds - A comma-separated list of the Ext component ids to be cleared + * @param {String|Array} items - A comma-separated list (or array) of items to be cleared. An optional default value + * may also be specified. The expected format for each item in the list is: + * 'filter-category', where 'filter-category' matches the Ext component's itemId and 'category' is the record index to filter on OR + * 'filter-category:3', where '3' is the filter's default value to be applied (instead of setting to an empty value) + * */ - ,clearGridFilters: function(itemIds) { + ,clearGridFilters: function(items) { const store = this.getStore(), - bottomToolbar = this.getBottomToolbar() + bottomToolbar = this.getBottomToolbar(), + data = Array.isArray(items) ? items : items.split(',') ; - itemIds = Array.isArray(itemIds) ? itemIds : itemIds.split(','); - /* - Note that param below relies on the following naming convention being followed for each filter's config: - itemId: 'filter-category', where 'category' is the record index to filter on - */ - itemIds.forEach(itemId => { - const id = itemId.trim(), - cmp = this.getFilterComponent(id) + data.forEach(item => { + itemData = item.replace(/\s+/g, '').split(':'); + const itemId = itemData[0], + itemDefaultVal = itemData.length == 2 ? itemData[1] : null , + cmp = this.getFilterComponent(itemId), + param = MODx.util.url.getParamNameFromCmp(cmp) ; - let param = id.split('-')[1]; - param = param == 'ns' ? 'namespace' : param ; if (cmp.xtype.includes('combo')) { - cmp.setValue(null); + cmp.setValue(itemDefaultVal); } else { cmp.setValue(''); } - store.baseParams[param] = ''; + if (!Ext.isEmpty(itemDefaultVal)) { + const paramsList = Object.keys(cmp.baseParams); + paramsList.forEach(param => { + switch(param) { + case 'namespace': + cmp.baseParams[param] = 'core'; + break; + case 'topic': + cmp.baseParams[param] = 'default'; + break; + } + }); + cmp.getStore().load(); + } + store.baseParams[param] = itemDefaultVal; }); store.load(); - MODx.util.url.clearParams(); + MODx.util.url.clearAllParams(); if (bottomToolbar) { bottomToolbar.changePage(1); } @@ -1013,6 +1060,105 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ filterStore.load(); } } + + /** + * @property {Function} getQueryFilterField - Creates the query field component configuration + * + * @param {String} filterId - Optional, itemId for the query filter; specify a unique id to avoid conflicts + * when multiple query fields may be present (e.g., when multiple tabs have a grid and query filter) + * @param {String} implementation - Optional, an identifier used to assign grid-specific behavior + * @return {Object} + */ + ,getQueryFilterField: function(filterId = 'filter-query', implementation = 'default') { + return { + xtype: 'textfield', + itemId: filterId.trim(), + emptyText: _('search'), + value: MODx.request.query ? MODx.util.url.decodeParamValue(MODx.request.query) : '', + cls: 'filter-query', + listeners: { + change: { + fn: function(cmp, newValue, oldValue) { + this.applyGridFilter(cmp); + const usergroupTree = Ext.getCmp('modx-tree-usergroup') + if (implementation == 'user-group-users' && usergroupTree) { + /* + When the user group users grid is shown in the primary ACLs panel, + having the user groups tree along with a corresponding grid, the + group id must be fetched from the tree + */ + const selectedNode = usergroupTree.getSelectionModel().getSelectedNode(), + groupId = MODx.util.tree.getGroupIdFromNode(selectedNode) + ; + MODx.util.url.setParams({group: groupId}); + } + }, + scope: this + }, + afterrender: { + fn: function(cmp) { + if (MODx.request.query) { + this.applyGridFilter(cmp); + } + }, + scope: this + }, + render: { + fn: function(cmp) { + new Ext.KeyMap(cmp.getEl(), { + key: Ext.EventObject.ENTER, + fn: this.blur, + scope: cmp + }); + } + ,scope: this + } + } + } + } + + /** + * @property {Function} getClearFiltersButton - Creates the clear filter button component configuration + * + * @param {String} filters - A comma-separated list of filter component ids (itemId) specifying those that should be cleared + * @param {String} dependentFilterResets - Optional, specification for reset of dependent filter stores to their pre-filtered state + * in the following format: 'filterItemId:relatedBaseParam, [filterItemId:relatedBaseParam,] ...' + * @return {Object} + */ + ,getClearFiltersButton: function(filters = 'filter-query', dependentFilterResets = null) { + if (Ext.isEmpty(filters)) { + console.error('MODx.grid.Grid::getClearFiltersButton: There was a problem creating the Clear Filter button because the supplied filters list is invalid.'); + return {}; + } + const config = { + text: _('filter_clear'), + itemId: 'filter-clear', + listeners: { + click: { + fn: function(cmp) { + if (cmp.dependentResets) { + const resets = cmp.dependentResets.split(','); + resets.forEach(reset => { + const [filterId, filterDataIndex] = reset.split(':').map(item => item.trim()); + this.updateDependentFilter(filterId, filterDataIndex, '', true); + }); + } + this.clearGridFilters(filters); + }, + scope: this + }, + mouseout: { + fn: function(evt) { + this.removeClass('x-btn-focus'); + } + } + } + } + if (dependentFilterResets) { + config.dependentResets = dependentFilterResets; + } + return config; + } }); /* local grid */ diff --git a/manager/assets/modext/widgets/core/modx.tabs.js b/manager/assets/modext/widgets/core/modx.tabs.js index 2bff4706b7f..b9eb28b9dee 100644 --- a/manager/assets/modext/widgets/core/modx.tabs.js +++ b/manager/assets/modext/widgets/core/modx.tabs.js @@ -1,41 +1,56 @@ MODx.Tabs = function(config) { config = config || {}; - Ext.applyIf(config,{ - enableTabScroll: true - ,layoutOnTabChange: true - ,plain: true - ,deferredRender: true - ,hideMode: 'offsets' - ,defaults: { - autoHeight: true - ,hideMode: 'offsets' - ,border: true - ,autoWidth: true - ,bodyCssClass: 'tab-panel-wrapper' - } - ,activeTab: 0 - ,border: false - ,autoScroll: true - ,autoHeight: true - ,cls: 'modx-tabs' + Ext.applyIf(config, { + enableTabScroll: true, + layoutOnTabChange: true, + plain: true, + deferredRender: true, + hideMode: 'offsets', + defaults: { + autoHeight: true, + hideMode: 'offsets', + border: true, + autoWidth: true, + bodyCssClass: 'tab-panel-wrapper' + }, + activeTab: 0, + border: false, + autoScroll: true, + autoHeight: true, + cls: 'modx-tabs' }); - MODx.Tabs.superclass.constructor.call(this,config); + MODx.Tabs.superclass.constructor.call(this, config); this.config = config; this.on({ + tabchange: function(tabPanel, tab) { + /* + In certain scenarios, such as when form customization and/or a plugin adds a tab, + the state of the Resource tab panel can become uncertain and no tab will be initially + selected. This workaround ensures the first tab is selected. + */ + if (this.id === 'modx-resource-tabs' && MODx.request.tab === undefined && !this.getActiveTab()) { + this.setActiveTab(0); + } + }, afterrender: function(tabPanel) { - if (MODx.request && MODx.request.hasOwnProperty('tab')){ - const tabId = parseInt(MODx.request.tab); - this.setActiveTab(tabId); + if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'tab')) { + const tabId = parseInt(MODx.request.tab, 10); + // Ensure tab panels other than the main one are unaffected + if (this.id !== 'modx-leftbar-tabpanel') { + this.setActiveTab(tabId); + } } + /* Placing listener here because we only want to listen after the initial panel has loaded */ tabPanel.on({ - beforetabchange: function(tabPanel, newTab, currentTab) { + beforetabchange: function(tabPanelCmp, newTab, currentTab) { /* Only proceed with the clearing process if the tab has changed. This is needed to prevent clearing when a URL has been typed in. NOTE: The currentTab is the previous one being navigated away from */ + if (newTab && currentTab && newTab.id !== currentTab.id) { const resetVerticalTabPanelFilters = (currentTab.items?.items[0]?.xtype === 'modx-vtabs') || currentTab.ownerCt?.xtype === 'modx-vtabs', changedBetweenVtabs = newTab.ownerCt?.xtype === 'modx-vtabs' && currentTab.ownerCt?.xtype === 'modx-vtabs' @@ -44,14 +59,22 @@ MODx.Tabs = function(config) { gridObj = null ; if (resetVerticalTabPanelFilters) { - itemsSource = changedBetweenVtabs ? currentTab.items : currentTab.items.items[0].activeTab.items ; + itemsSource = changedBetweenVtabs + ? currentTab.items + : currentTab.items.items[0].activeTab.items; } else { itemsSource = currentTab.items; } if (itemsSource.length > 0) { - gridObj = itemsSource.find(obj => { - return Object.entries(obj).find(([key, value]) => key == 'xtype' && value.includes('modx-grid')); - }); + gridObj = this.findGridObject(itemsSource); + /* + Grids placed in an atypical structure, such as the ACLs User Group grid that + is activated via the User Groups tree, require further searching + */ + if (!gridObj && itemsSource?.map['modx-tree-panel-usergroup']) { + itemsSource = itemsSource.map['modx-tree-panel-usergroup'].items; + gridObj = this.findGridObject(itemsSource); + } } if (gridObj) { const toolbar = gridObj.getTopToolbar(), @@ -59,7 +82,7 @@ MODx.Tabs = function(config) { ; if (toolbar && toolbar.items.items.length > 0) { toolbar.items.items.forEach(cmp => { - if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype == 'textfield') && cmp.itemId) { + if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype === 'textfield') && cmp.itemId) { filterIds.push(cmp.itemId); } }); @@ -74,31 +97,41 @@ MODx.Tabs = function(config) { } }); }; -Ext.extend(MODx.Tabs,Ext.TabPanel); -Ext.reg('modx-tabs',MODx.Tabs); +Ext.extend(MODx.Tabs, Ext.TabPanel, { + /** + * @property {Function} findGridObject - Search for and return a grid object with a given items array + * + * @param {String} itemsSource - The config items array to search within + * @return {MODx.grid.Grid} + */ + findGridObject: function(itemsSource) { + return itemsSource.find(obj => Object.entries(obj).find(([key, value]) => key === 'xtype' && value.includes('modx-grid'))); + } +}); +Ext.reg('modx-tabs', MODx.Tabs); MODx.VerticalTabs = function(config) { config = config || {}; - Ext.applyIf(config,{ - cls: 'vertical-tabs-panel' - ,headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' } - ,bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' } - ,defaults: { - bodyCssClass: 'vertical-tabs-body' - ,autoScroll: true - ,autoHeight: true - ,autoWidth: true - ,layout: 'form' + Ext.applyIf(config, { + cls: 'vertical-tabs-panel', + headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' }, + bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' }, + defaults: { + bodyCssClass: 'vertical-tabs-body', + autoScroll: true, + autoHeight: true, + autoWidth: true, + layout: 'form' } }); - MODx.VerticalTabs.superclass.constructor.call(this,config); + MODx.VerticalTabs.superclass.constructor.call(this, config); this.config = config; this.on('afterrender', function() { - if (MODx.request && MODx.request.hasOwnProperty('vtab')){ - const tabId = parseInt(MODx.request.vtab); + if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'vtab')) { + const tabId = parseInt(MODx.request.vtab, 10); this.setActiveTab(tabId); } }); }; Ext.extend(MODx.VerticalTabs, MODx.Tabs); -Ext.reg('modx-vtabs',MODx.VerticalTabs); +Ext.reg('modx-vtabs', MODx.VerticalTabs); diff --git a/manager/assets/modext/widgets/core/tree/modx.tree.js b/manager/assets/modext/widgets/core/tree/modx.tree.js index 1a50c69cf27..6501a6a496d 100644 --- a/manager/assets/modext/widgets/core/tree/modx.tree.js +++ b/manager/assets/modext/widgets/core/tree/modx.tree.js @@ -193,7 +193,7 @@ Ext.extend(MODx.tree.Tree,Ext.tree.TreePanel,{ ,scope: this }; MODx.tree.Tree.superclass.constructor.call(this,config); - this.addEvents('afterSort','beforeSort'); + this.addEvents('afterSort','beforeSort','refresh'); this.cm = new Ext.menu.Menu(config.menuConfig); this.on('contextmenu',this._showContextMenu,this); this.on('beforenodedrop',this._handleDrop,this); @@ -378,6 +378,7 @@ Ext.extend(MODx.tree.Tree,Ext.tree.TreePanel,{ ,refresh: function (func,scope,args) { var treeState = Ext.state.Manager.get(this.treestate_id); this.root.reload(); + this.fireEvent('refresh', {}); if (treeState === undefined) { this.root.expand(); } else { From 9fb69c658b9676fb4b2e7d0cb75582233e9eb815 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 00:27:44 -0500 Subject: [PATCH 05/33] Filter Persistence: Policies and Policy Templates --- .../Security/Access/Policy/GetList.php | 3 +- .../Access/Policy/Template/GetList.php | 2 + .../security/modx.grid.access.policy.js | 118 +++++++----------- .../modx.grid.access.policy.template.js | 107 ++++++---------- 4 files changed, 84 insertions(+), 146 deletions(-) diff --git a/core/src/Revolution/Processors/Security/Access/Policy/GetList.php b/core/src/Revolution/Processors/Security/Access/Policy/GetList.php index 8fc4c9a1d6b..94c420f6b5b 100644 --- a/core/src/Revolution/Processors/Security/Access/Policy/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/Policy/GetList.php @@ -84,7 +84,8 @@ public function prepareQueryBeforeCount(xPDOQuery $c) if (!empty($query)) { $c->where([ 'modAccessPolicy.name:LIKE' => '%' . $query . '%', - 'OR:modAccessPolicy.description:LIKE' => '%' . $query . '%' + 'OR:modAccessPolicy.description:LIKE' => '%' . $query . '%', + 'OR:Template.name:LIKE' => '%' . $query . '%' ]); } diff --git a/core/src/Revolution/Processors/Security/Access/Policy/Template/GetList.php b/core/src/Revolution/Processors/Security/Access/Policy/Template/GetList.php index c00c0c75174..877a7b5f32f 100644 --- a/core/src/Revolution/Processors/Security/Access/Policy/Template/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/Policy/Template/GetList.php @@ -1,4 +1,5 @@ where([ 'modAccessPolicyTemplate.name:LIKE' => '%' . $query . '%', 'OR:modAccessPolicyTemplate.description:LIKE' => '%' . $query . '%', + 'OR:TemplateGroup.name:LIKE' => '%' . $query . '%' ]); } diff --git a/manager/assets/modext/widgets/security/modx.grid.access.policy.js b/manager/assets/modext/widgets/security/modx.grid.access.policy.js index 1eb7583f58f..16fe5c2899e 100644 --- a/manager/assets/modext/widgets/security/modx.grid.access.policy.js +++ b/manager/assets/modext/widgets/security/modx.grid.access.policy.js @@ -6,12 +6,11 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-access-policies */ -MODx.panel.AccessPolicies = function(config) { - config = config || {}; +MODx.panel.AccessPolicies = function(config = {}) { Ext.applyIf(config,{ id: 'modx-panel-access-policies' ,bodyStyle: '' - ,defaults: { collapsible: false ,autoHeight: true } + ,defaults: { collapsible: false, autoHeight: true } ,items: [{ html: _('policies') ,id: 'modx-policies-header' @@ -41,8 +40,7 @@ Ext.reg('modx-panel-access-policies',MODx.panel.AccessPolicies); * @param {Object} config An object of options. * @xtype modx-grid-access-policy */ -MODx.grid.AccessPolicy = function(config) { - config = config || {}; +MODx.grid.AccessPolicy = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); Ext.applyIf(config,{ id: 'modx-grid-access-policy' @@ -50,7 +48,21 @@ MODx.grid.AccessPolicy = function(config) { ,baseParams: { action: 'Security/Access/Policy/GetList' } - ,fields: ['id','name','description', 'description_trans', 'class','data','parent','template','template_name','active_permissions','total_permissions','active_of','cls'] + ,fields: [ + 'id', + 'name', + 'description', + 'description_trans', + 'class', + 'data', + 'parent', + 'template', + 'template_name', + 'active_permissions', + 'total_permissions', + 'active_of', + 'cls' + ] ,paging: true ,autosave: true ,save_action: 'Security/Access/Policy/UpdateFromGrid' @@ -91,73 +103,33 @@ MODx.grid.AccessPolicy = function(config) { ,width: 100 ,editable: false }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,scope: this - ,handler: this.createPolicy - },{ - text: _('import') - ,scope: this - ,handler: this.importPolicy - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected + ,tbar: [ + { + text: _('create') + ,cls:'primary-button' ,scope: this - }] - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-policy-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: function() { - this.fireEvent('change',this.getValue()); - this.blur(); - return true;} - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-sacpol-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,handler: this.createPolicy + },{ + text: _('import') + ,scope: this + ,handler: this.importPolicy + },{ + text: _('bulk_actions') + ,menu: [{ + text: _('selected_remove') + ,handler: this.removeSelected + ,scope: this + }] + }, + '->', + this.getQueryFilterField('filter-query-policy'), + this.getClearFiltersButton() + ] }); MODx.grid.AccessPolicy.superclass.constructor.call(this,config); }; Ext.extend(MODx.grid.AccessPolicy,MODx.grid.Grid,{ - search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'Security/Access/Policy/GetList' - }; - Ext.getCmp('modx-policy-search').reset(); - this.getBottomToolbar().changePage(1); - } - - ,editPolicy: function(itm,e) { + editPolicy: function(itm,e) { MODx.loadPage('security/access/policy/update', 'id='+this.menu.record.id); } @@ -178,6 +150,7 @@ Ext.extend(MODx.grid.AccessPolicy,MODx.grid.Grid,{ this.windows.apc.reset(); this.windows.apc.show(e.target); } + ,exportPolicy: function(btn,e) { var id = this.menu.record.id; MODx.Ajax.request({ @@ -284,8 +257,7 @@ Ext.reg('modx-grid-access-policy',MODx.grid.AccessPolicy); * @param {Object} config An object of options. * @xtype modx-window-access-policy-create */ -MODx.window.CreateAccessPolicy = function(config) { - config = config || {}; +MODx.window.CreateAccessPolicy = function(config = {}) { this.ident = config.ident || 'cacp'+Ext.id(); Ext.applyIf(config,{ title: _('create') @@ -352,8 +324,7 @@ Ext.reg('modx-window-access-policy-create',MODx.window.CreateAccessPolicy); * @param {Object} config An object of options. * @xtype modx-combo-access-policy-template */ -MODx.combo.AccessPolicyTemplate = function(config) { - config = config || {}; +MODx.combo.AccessPolicyTemplate = function(config = {}) { Ext.applyIf(config,{ name: 'template' ,hiddenName: 'template' @@ -381,8 +352,7 @@ Ext.reg('modx-combo-access-policy-template',MODx.combo.AccessPolicyTemplate); * @param {Object} config An object of options. * @xtype modx-window-policy-import */ -MODx.window.ImportPolicy = function(config) { - config = config || {}; +MODx.window.ImportPolicy = function(config = {}) { this.ident = config.ident || 'imppol-'+Ext.id(); Ext.applyIf(config,{ title: _('import') diff --git a/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js b/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js index f80b7be2e94..190034c52a0 100644 --- a/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js +++ b/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js @@ -6,8 +6,7 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-access-policy-templates */ -MODx.panel.AccessPolicyTemplates = function(config) { - config = config || {}; +MODx.panel.AccessPolicyTemplates = function(config = {}) { Ext.applyIf(config,{ id: 'modx-panel-access-policy-templates' ,bodyStyle: '' @@ -41,8 +40,7 @@ Ext.reg('modx-panel-access-policy-templates',MODx.panel.AccessPolicyTemplates); * @param {Object} config An object of options. * @xtype modx-grid-access-policy */ -MODx.grid.AccessPolicyTemplate = function(config) { - config = config || {}; +MODx.grid.AccessPolicyTemplate = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); Ext.applyIf(config,{ id: 'modx-grid-access-policy-template' @@ -50,7 +48,17 @@ MODx.grid.AccessPolicyTemplate = function(config) { ,baseParams: { action: 'Security/Access/Policy/Template/GetList' } - ,fields: ['id','name','description','description_trans','template_group','template_group_name','total_permissions','policy_count','cls'] + ,fields: [ + 'id', + 'name', + 'description', + 'description_trans', + 'template_group', + 'template_group_name', + 'total_permissions', + 'policy_count', + 'cls' + ] ,paging: true ,autosave: true ,save_action: 'Security/Access/Policy/Template/UpdateFromGrid' @@ -94,54 +102,28 @@ MODx.grid.AccessPolicyTemplate = function(config) { ,editable: false ,sortable: true }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,scope: this - ,handler: this.createPolicyTemplate - },{ - text: _('import') - ,scope: this - ,handler: this.importPolicyTemplate - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected + ,tbar: [ + { + text: _('create') + ,cls:'primary-button' ,scope: this - }] - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-policy-template-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: function() { - this.fireEvent('change',this.getValue()); - this.blur(); - return true;} - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-sacpoltemp-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,handler: this.createPolicyTemplate + },{ + text: _('import') + ,scope: this + ,handler: this.importPolicyTemplate + },{ + text: _('bulk_actions') + ,menu: [{ + text: _('selected_remove') + ,handler: this.removeSelected + ,scope: this + }] + }, + '->', + this.getQueryFilterField('filter-query-policy-template'), + this.getClearFiltersButton() + ] }); MODx.grid.AccessPolicyTemplate.superclass.constructor.call(this,config); }; @@ -296,21 +278,6 @@ Ext.extend(MODx.grid.AccessPolicyTemplate,MODx.grid.Grid,{ } }); } - - ,search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'Security/Access/Policy/Template/GetList' - }; - Ext.getCmp('modx-policy-template-search').reset(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-access-policy-templates',MODx.grid.AccessPolicyTemplate); @@ -322,8 +289,7 @@ Ext.reg('modx-grid-access-policy-templates',MODx.grid.AccessPolicyTemplate); * @param {Object} config An object of options. * @xtype modx-window-access-policy-create */ -MODx.window.CreateAccessPolicyTemplate = function(config) { - config = config || {}; +MODx.window.CreateAccessPolicyTemplate = function(config = {}) { this.ident = config.ident || 'cacpt'+Ext.id(); Ext.applyIf(config,{ title: _('create') @@ -378,8 +344,7 @@ Ext.reg('modx-window-access-policy-template-create',MODx.window.CreateAccessPoli * @param {Object} config An object of options. * @xtype modx-window-policy-template-import */ -MODx.window.ImportPolicyTemplate = function(config) { - config = config || {}; +MODx.window.ImportPolicyTemplate = function(config = {}) { this.ident = config.ident || 'imppt-'+Ext.id(); Ext.applyIf(config,{ title: _('import') From a205493506751d33285e4b3d89ad9edc5f113f11 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 15:52:37 -0500 Subject: [PATCH 06/33] Filter Persistence: Deleted Resources Manager (Trash) Also applied refinement to combo filter (showing only relevant Contexts) and fixed query so only deleted Resources are returned (fix from Ruslan's closed PR). --- core/lexicon/en/default.inc.php | 3 +- .../Revolution/Processors/Context/GetList.php | 59 ++++--- .../Processors/Resource/Trash/GetList.php | 17 +- .../widgets/resource/modx.grid.trash.js | 148 +++++++----------- 4 files changed, 109 insertions(+), 118 deletions(-) diff --git a/core/lexicon/en/default.inc.php b/core/lexicon/en/default.inc.php index 0f4ea099721..fce1dd78c23 100644 --- a/core/lexicon/en/default.inc.php +++ b/core/lexicon/en/default.inc.php @@ -208,6 +208,7 @@ $_lang['filter_clear'] = 'Clear Filter'; $_lang['filter_by_key'] = 'Filter by Key...'; $_lang['filter_by_name'] = 'Filter by name...'; +$_lang['filter_by_event_group'] = 'Filter by event group...'; $_lang['filter_by_username'] = 'Filter by user name...'; $_lang['finish'] = 'Finish'; $_lang['folder'] = 'Folder'; @@ -314,7 +315,7 @@ $_lang['orm_container_rename'] = 'Rename Container'; $_lang['orm_container_remove'] = 'Delete Container'; $_lang['orm_container_remove_confirm'] = 'Are you sure you want to delete this container and all attributes below it? This is irreversible.'; -$_lang['pagetitle'] = 'Resource\'s title'; +$_lang['pagetitle'] = 'Resource‘s Title'; $_lang['page_title'] = 'Resource Title'; $_lang['parameter'] = 'Parameter'; $_lang['parameters'] = 'Parameters'; diff --git a/core/src/Revolution/Processors/Context/GetList.php b/core/src/Revolution/Processors/Context/GetList.php index 84d96221df0..1fbc0c97249 100644 --- a/core/src/Revolution/Processors/Context/GetList.php +++ b/core/src/Revolution/Processors/Context/GetList.php @@ -13,6 +13,7 @@ use MODX\Revolution\modContext; use MODX\Revolution\modAccessContext; +use MODX\Revolution\modResource; use MODX\Revolution\modUserGroup; use MODX\Revolution\Processors\Model\GetListProcessor; use xPDO\Om\xPDOObject; @@ -53,7 +54,7 @@ public function initialize() { $initialized = parent::initialize(); $this->setDefaultProperties([ - 'search' => '', + 'query' => '', 'exclude' => '', ]); $this->canCreate = $this->modx->hasPermission('new_context'); @@ -71,11 +72,11 @@ public function initialize() */ public function prepareQueryBeforeCount(xPDOQuery $c) { - $search = $this->getProperty('search'); - if (!empty($search)) { + $query = $this->getProperty('query'); + if (!empty($query)) { $c->where([ - 'key:LIKE' => '%' . $search . '%', - 'OR:description:LIKE' => '%' . $search . '%', + 'key:LIKE' => '%' . $query . '%', + 'OR:description:LIKE' => '%' . $query . '%', ]); } $exclude = $this->getProperty('exclude'); @@ -89,21 +90,39 @@ public function prepareQueryBeforeCount(xPDOQuery $c) limit results to only those contexts present in the current grid. */ if ($this->isGridFilter) { - if ($userGroup = $this->getProperty('usergroup', false)) { - $c->innerJoin( - modAccessContext::class, - 'modAccessContext', - [ - '`modAccessContext`.`target` = `modContext`.`key`', - '`modAccessContext`.`principal` = ' . (int)$userGroup, - '`modAccessContext`.`principal_class` = ' . $this->modx->quote(modUserGroup::class) - ] - ); - if ($policy = $this->getProperty('policy', false)) { - $c->where([ - '`modAccessContext`.`policy`' => (int)$policy - ]); - } + $targetGrid = $this->getProperty('targetGrid', ''); + switch ($targetGrid) { + case 'MODx.grid.UserGroupContext': + if ($userGroup = $this->getProperty('usergroup', false)) { + $c->innerJoin( + modAccessContext::class, + 'modAccessContext', + [ + '`modAccessContext`.`target` = `modContext`.`key`', + '`modAccessContext`.`principal` = ' . (int)$userGroup, + '`modAccessContext`.`principal_class` = ' . $this->modx->quote(modUserGroup::class) + ] + ); + if ($policy = $this->getProperty('policy', false)) { + $c->where([ + '`modAccessContext`.`policy`' => (int)$policy + ]); + } + } + break; + + case 'MODx.grid.Trash': + $c->innerJoin( + modResource::class, + 'modResource', + [ + '`modResource`.`context_key` = `modContext`.`key`', + '`modResource`.`deleted` = 1' + ] + ); + break; + + // no default case } } return $c; diff --git a/core/src/Revolution/Processors/Resource/Trash/GetList.php b/core/src/Revolution/Processors/Resource/Trash/GetList.php index 6b63c8f0424..e23399c66db 100644 --- a/core/src/Revolution/Processors/Resource/Trash/GetList.php +++ b/core/src/Revolution/Processors/Resource/Trash/GetList.php @@ -61,19 +61,20 @@ public function prepareQueryBeforeCount(xPDOQuery $c) // undelete_document - to restore the document // delete_document - thats perhaps not necessary, because all documents are already deleted // but we need the purge_deleted permission - for every single file - - if (!empty($query)) { - $c->where(['modResource.pagetitle:LIKE' => '%' . $query . '%']); - $c->orCondition(['modResource.longtitle:LIKE' => '%' . $query . '%']); - } - if (!empty($context)) { - $c->where(['modResource.context_key' => $context]); - } if ($deleted = $this->getDeleted()) { $c->where(['modResource.id:IN' => $deleted]); } else { $c->where(['modResource.id:IN' => 0]); } + if (!empty($query)) { + $c->where([ + 'modResource.pagetitle:LIKE' => '%' . $query . '%', + 'OR:modResource.longtitle:LIKE' => '%' . $query . '%' + ]); + } + if (!empty($context)) { + $c->where(['modResource.context_key' => $context]); + } return $c; } diff --git a/manager/assets/modext/widgets/resource/modx.grid.trash.js b/manager/assets/modext/widgets/resource/modx.grid.trash.js index 38cef48fd59..ad50e5e8085 100644 --- a/manager/assets/modext/widgets/resource/modx.grid.trash.js +++ b/manager/assets/modext/widgets/resource/modx.grid.trash.js @@ -1,12 +1,10 @@ -MODx.grid.Trash = function (config) { - config = config || {}; - +MODx.grid.Trash = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); - Ext.applyIf(config, { url: MODx.config.connector_url, baseParams: { - action: 'Resource/Trash/GetList' + action: 'Resource/Trash/GetList', + context: MODx.request.context || null }, fields: [ 'id', @@ -59,72 +57,68 @@ MODx.grid.Trash = function (config) { dataIndex: 'deletedby', width: 75, sortable: true, - renderer: function (value, metaData, record) { + renderer: function(value, metaData, record) { return record.data.deletedby_name; } }], - tbar: [{ - text: _('bulk_actions'), - menu: [{ - text: _('trash.selected_purge'), - handler: this.purgeSelected, - scope: this + tbar: [ + { + text: _('bulk_actions'), + menu: [{ + text: _('trash.selected_purge'), + handler: this.purgeSelected, + scope: this + }, { + text: _('trash.selected_restore'), + handler: this.restoreSelected, + scope: this + }] }, { - text: _('trash.selected_restore'), - handler: this.restoreSelected, - scope: this - }] - }, { - xtype: 'button', - text: _('trash.purge_all'), - id: 'modx-purge-all', - cls: 'x-btn-purge-all', - listeners: { - 'click': {fn: this.purgeAll, scope: this} - } - }, { - xtype: 'button', - text: _('trash.restore_all'), - id: 'modx-restore-all', - cls: 'x-btn-restore-all', - listeners: { - 'click': {fn: this.restoreAll, scope: this} - } - }, '->', { - xtype: 'modx-combo-context', - id: 'modx-trash-context', - emptyText: _('context'), - exclude: 'mgr', - listeners: { - 'select': {fn: this.searchContext, scope: this} - } - },{ - xtype: 'textfield', - id: 'modx-trash-search', - cls: 'x-form-filter', - emptyText: _('search'), - listeners: { - 'change': {fn: this.search, scope: this}, - 'render': { - fn: function (cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER, - fn: this.blur, - scope: cmp - }); - }, scope: this + text: _('trash.purge_all'), + id: 'modx-purge-all', + cls: 'x-btn-purge-all', + listeners: { + click: { + fn: this.purgeAll, + scope: this + } } - } - }, { - xtype: 'button', - text: _('filter_clear'), - id: 'modx-filter-clear', - cls: 'x-form-filter-clear', - listeners: { - 'click': {fn: this.clearFilter, scope: this} - } - }] + }, { + text: _('trash.restore_all'), + id: 'modx-restore-all', + cls: 'x-btn-restore-all', + listeners: { + click: { + fn: this.restoreAll, + scope: this + } + } + }, + '->', + { + xtype: 'modx-combo-context', + itemId: 'filter-context', + emptyText: _('context'), + value: MODx.request.context || null, + baseParams: { + action: 'Context/GetList', + exclude: 'mgr', + isGridFilter: true, + targetGrid: 'MODx.grid.Trash' + }, + listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'context'); + }, + scope: this + } + } + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-context, filter-query') + ] }); MODx.grid.Trash.superclass.constructor.call(this, config); @@ -167,30 +161,6 @@ Ext.extend(MODx.grid.Trash, MODx.grid.Grid, { } }, - search: function (tf, newValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - this.refresh(); - return true; - }, - - searchContext: function (tf) { - this.getStore().baseParams.context = !Ext.isEmpty(tf) ? tf.value : ''; - this.getBottomToolbar().changePage(1); - this.refresh(); - return true; - }, - - clearFilter: function () { - this.getStore().baseParams.query = ''; - this.getStore().baseParams.context = ''; - Ext.getCmp('modx-trash-search').reset(); - Ext.getCmp('modx-trash-context').reset(); - this.getBottomToolbar().changePage(1); - this.refresh(); - }, - purgeResource: function () { MODx.msg.confirm({ minWidth: 500, From c5a594ff32f30188d3680706b4b6d5d698d555ab Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 15:57:59 -0500 Subject: [PATCH 07/33] Update user group permissions, category grid Applies new shared method to create the clear filters button --- .../security/modx.grid.user.group.category.js | 57 ++++++------------- 1 file changed, 18 insertions(+), 39 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js index cb4a86edb95..a21d6ad2808 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js @@ -4,8 +4,7 @@ * @param {Object} config An object of configuration properties * @xtype modx-grid-user-group-categories */ -MODx.grid.UserGroupCategory = function(config) { - config = config || {}; +MODx.grid.UserGroupCategory = function(config = {}) { this.exp = new Ext.grid.RowExpander({ tpl: new Ext.Template('

    {permissions}

    '), lazyRender: false, @@ -22,16 +21,16 @@ MODx.grid.UserGroupCategory = function(config) { } ,fields: [ 'id', - 'target', - 'name', - 'principal', - 'authority', - 'authority_name', - 'policy', - 'policy_name', - 'context_key', - 'permissions', - 'cls' + 'target', + 'name', + 'principal', + 'authority', + 'authority_name', + 'policy', + 'policy_name', + 'context_key', + 'permissions', + 'cls' ] ,paging: true ,hideMode: 'offsets' @@ -127,28 +126,11 @@ MODx.grid.UserGroupCategory = function(config) { scope: this } } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.updateDependentFilter('filter-policy-category', 'category', '', true); - this.updateDependentFilter('filter-category', 'policy', '', true); - this.clearGridFilters('filter-category, filter-policy-category'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - ,scope: this - } - // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account - // this.getClearFiltersButton('filter-category, filter-policy-category'); + }, + this.getClearFiltersButton( + 'filter-category, filter-policy-category', + 'filter-policy-category:category, filter-category:policy' + ) ] }); MODx.grid.UserGroupCategory.superclass.constructor.call(this,config); @@ -257,8 +239,7 @@ Ext.reg('modx-grid-user-group-category',MODx.grid.UserGroupCategory); * @param {Object} config An object of options. * @xtype modx-window-user-group-category-create */ -MODx.window.CreateUGCat = function(config) { - config = config || {}; +MODx.window.CreateUGCat = function(config = {}) { this.ident = config.ident || 'cugcat'+Ext.id(); Ext.applyIf(config,{ title: _('category_add') @@ -388,10 +369,8 @@ Ext.reg('modx-window-user-group-category-create',MODx.window.CreateUGCat); * @param {Object} config An object of options. * @xtype modx-window-user-group-category-update */ -MODx.window.UpdateUGCat = function(config) { - config = config || {}; +MODx.window.UpdateUGCat = function(config = {}) { this.ident = config.ident || 'updugcat'+Ext.id(); - Ext.applyIf(config,{ title: _('access_category_update') ,action: 'Security/Access/UserGroup/Category/Update' From 430a09bcb2a7a3793d4c793fbb1c0a02463ee017 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 15:59:00 -0500 Subject: [PATCH 08/33] Update user group permissions, contexts grid Applies new shared method to create the clear filters button --- .../security/modx.grid.user.group.context.js | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.context.js b/manager/assets/modext/widgets/security/modx.grid.user.group.context.js index 62cc7e60328..9adffa56c0b 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.context.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.context.js @@ -87,6 +87,7 @@ MODx.grid.UserGroupContext = function(config) { ,baseParams: { action: 'Context/GetList', isGridFilter: true, + targetGrid: 'MODx.grid.UserGroupContext', usergroup: config.usergroup } ,listeners: { @@ -121,28 +122,11 @@ MODx.grid.UserGroupContext = function(config) { scope: this } } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.updateDependentFilter('filter-policy-context', 'context', '', true); - this.updateDependentFilter('filter-context', 'policy', '', true); - this.clearGridFilters('filter-context, filter-policy-context'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - ,scope: this - } - // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account - // this.getClearFiltersButton('filter-context, filter-policy-context'); + }, + this.getClearFiltersButton( + 'filter-context, filter-policy-context', + 'filter-policy-context:context, filter-context:policy' + ) ] }); MODx.grid.UserGroupContext.superclass.constructor.call(this,config); From 504015ebcb396b39bde1d2b0a2380fa13c410236 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 15:59:32 -0500 Subject: [PATCH 09/33] Update user group permissions, namespace grid Applies new shared method to create the clear filters button --- .../modx.grid.user.group.namespace.js | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js index a349c91bcb9..1d086dc2c24 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js @@ -126,28 +126,11 @@ MODx.grid.UserGroupNamespace = function(config) { scope: this } } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.updateDependentFilter('filter-policy-namespace', 'namespace', '', true); - this.updateDependentFilter('filter-namespace', 'policy', '', true); - this.clearGridFilters('filter-namespace, filter-policy-namespace'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - ,scope: this - } - // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account - // this.getClearFiltersButton('filter-namespace, filter-policy-namespace'); + }, + this.getClearFiltersButton( + 'filter-namespace, filter-policy-namespace', + 'filter-policy-namespace:namespace, filter-namespace:policy' + ) ] }); MODx.grid.UserGroupNamespace.superclass.constructor.call(this,config); From f999962e5031c8fdd4dfde858b0b0b3791aae4f6 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 16:00:33 -0500 Subject: [PATCH 10/33] Update user group permissions, resource groups grid Applies new shared method to create the clear filters button --- .../security/modx.grid.user.group.resource.js | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js b/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js index e6fb3a989fd..788048fbb23 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js @@ -129,28 +129,11 @@ MODx.grid.UserGroupResourceGroup = function(config) { scope: this } } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.updateDependentFilter('filter-policy-resourceGroup', 'resourceGroup', '', true); - this.updateDependentFilter('filter-resourceGroup', 'policy', '', true); - this.clearGridFilters('filter-resourceGroup, filter-policy-resourceGroup'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - ,scope: this - } - // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account - // this.getClearFiltersButton('filter-resourceGroup, filter-policy-resourceGroup'); + }, + this.getClearFiltersButton( + 'filter-resourceGroup, filter-policy-resourceGroup', + 'filter-policy-resourceGroup:resourceGroup, filter-resourceGroup:policy' + ) ] }); MODx.grid.UserGroupResourceGroup.superclass.constructor.call(this,config); From 96bdda97d11efd413245285dee66b851a834c05f Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 16:01:10 -0500 Subject: [PATCH 11/33] Update user group permissions, media sources grid Applies new shared method to create the clear filters button --- .../security/modx.grid.user.group.source.js | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.source.js b/manager/assets/modext/widgets/security/modx.grid.user.group.source.js index 4dd74a2d90a..46efb9bdcdc 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.source.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.source.js @@ -122,28 +122,11 @@ MODx.grid.UserGroupSource = function(config) { scope: this } } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.updateDependentFilter('filter-policy-source', 'source', '', true); - this.updateDependentFilter('filter-source', 'policy', '', true); - this.clearGridFilters('filter-source, filter-policy-source'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - ,scope: this - } - // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account - // this.getClearFiltersButton('filter-source, filter-policy-source'); + }, + this.getClearFiltersButton( + 'filter-source, filter-policy-source', + 'filter-policy-source:source, filter-source:policy' + ) ] }); MODx.grid.UserGroupSource.superclass.constructor.call(this,config); From 5082f5260dab2eb966e72e2050d26b48d3f83925 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 16:06:30 -0500 Subject: [PATCH 12/33] Filter Persistence: Plugin Events Grid --- .../Element/Plugin/Event/GetList.php | 9 +- .../widgets/element/modx.grid.plugin.event.js | 102 ++++++++---------- 2 files changed, 43 insertions(+), 68 deletions(-) diff --git a/core/src/Revolution/Processors/Element/Plugin/Event/GetList.php b/core/src/Revolution/Processors/Element/Plugin/Event/GetList.php index 872175ce02d..a78b87a8a34 100644 --- a/core/src/Revolution/Processors/Element/Plugin/Event/GetList.php +++ b/core/src/Revolution/Processors/Element/Plugin/Event/GetList.php @@ -1,4 +1,5 @@ toArray(); $eventArray['enabled'] = $event->get('enabled') ? 1 : 0; - - $eventArray['menu'] = [ - [ - 'text' => $this->modx->lexicon('edit'), - 'handler' => 'this.updateEvent', - ], - ]; $list[] = $eventArray; } diff --git a/manager/assets/modext/widgets/element/modx.grid.plugin.event.js b/manager/assets/modext/widgets/element/modx.grid.plugin.event.js index 9dcae1e9ccb..af3de76eaef 100644 --- a/manager/assets/modext/widgets/element/modx.grid.plugin.event.js +++ b/manager/assets/modext/widgets/element/modx.grid.plugin.event.js @@ -6,10 +6,9 @@ * @param {Object} config An object of options. * @xtype modx-grid-plugin-event */ -MODx.grid.PluginEvent = function(config) { - config = config || {}; +MODx.grid.PluginEvent = function(config = {}) { this.ident = config.ident || 'grid-pluge'+Ext.id(); - var ec = new Ext.ux.grid.CheckColumn({ + const ec = new Ext.ux.grid.CheckColumn({ header: _('enabled') ,dataIndex: 'enabled' ,editable: true @@ -23,6 +22,7 @@ MODx.grid.PluginEvent = function(config) { ,baseParams: { action: 'Element/Plugin/Event/GetList' ,plugin: config.plugin + ,group: MODx.request.group ? MODx.util.url.decodeParamValue(MODx.request.group) : null ,limit: 0 } ,saveParams: { @@ -31,7 +31,14 @@ MODx.grid.PluginEvent = function(config) { ,enableColumnResize: true ,enableColumnMove: true ,primaryKey: 'name' - ,fields: ['name','service','groupname','enabled','priority','propertyset','menu'] + ,fields: [ + 'name', + 'service', + 'groupname', + 'enabled', + 'priority', + 'propertyset' + ] ,paging: false ,pageSize: 0 ,remoteSort: false @@ -76,42 +83,26 @@ MODx.grid.PluginEvent = function(config) { ,editor: { xtype: 'textfield' ,allowBlank: false } ,sortable: true }] - ,tbar: ['->',{ - xtype: 'modx-combo-eventgroup' - ,name: 'group' - ,id: 'modx-plugin-event-filter-group' - ,itemId: 'group' - ,emptyText: _('group')+'...' - ,width: 200 - ,listeners: { - 'select': {fn:this.filterGroup,scope:this} - } - },{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-plugin-event-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': {fn: function () {this.removeClass('x-btn-focus')}} - } - }] + ,tbar: [ + '->', + { + xtype: 'modx-combo-eventgroup' + ,itemId: 'filter-group' + ,emptyText: _('filter_by_event_group') + ,width: 200 + ,value: MODx.request.group ? MODx.util.url.decodeParamValue(MODx.request.group) : null + ,listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'group'); + }, + scope: this + } + } + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-group, filter-query') + ] }); MODx.grid.PluginEvent.superclass.constructor.call(this,config); @@ -121,26 +112,17 @@ MODx.grid.PluginEvent = function(config) { }; this.addEvents('updateEvent'); }; -Ext.extend(MODx.grid.PluginEvent,MODx.grid.Grid,{ - search: function(tf,newValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getStore().load(); - return true; - } - ,filterGroup: function (cb,nv,ov) { - this.getStore().baseParams.group = Ext.isEmpty(nv) || Ext.isObject(nv) ? cb.getValue() : nv; - this.getStore().load(); - return true; - } - ,clearFilter: function() { - delete this.getStore().baseParams.query; - delete this.getStore().baseParams.group; - Ext.getCmp('modx-plugin-event-search').reset(); - Ext.getCmp('modx-plugin-event-filter-group').reset(); - this.getStore().load(); - } - ,updateEvent: function(btn,e) { +Ext.extend(MODx.grid.PluginEvent, MODx.grid.Grid, { + getMenu: function() { + const menu = []; + menu.push({ + text: _('edit'), + handler: this.updateEvent + }); + this.addContextMenuItem(menu); + }, + + updateEvent: function(btn,e) { var r = this.menu.record; if (!this.windows.peu) { this.windows.peu = MODx.load({ From 2092bc7c32ed371faecfd95d83f61adcbc4acc72 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 17:06:52 -0500 Subject: [PATCH 13/33] Move resource panel-specific tabchange listener Since the fix is only relevant to one panel, moved it out of the Tabs base class --- manager/assets/modext/widgets/core/modx.tabs.js | 10 ---------- .../modext/widgets/resource/modx.panel.resource.js | 12 ++++++++++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/manager/assets/modext/widgets/core/modx.tabs.js b/manager/assets/modext/widgets/core/modx.tabs.js index b9eb28b9dee..83edc7cc390 100644 --- a/manager/assets/modext/widgets/core/modx.tabs.js +++ b/manager/assets/modext/widgets/core/modx.tabs.js @@ -22,16 +22,6 @@ MODx.Tabs = function(config) { MODx.Tabs.superclass.constructor.call(this, config); this.config = config; this.on({ - tabchange: function(tabPanel, tab) { - /* - In certain scenarios, such as when form customization and/or a plugin adds a tab, - the state of the Resource tab panel can become uncertain and no tab will be initially - selected. This workaround ensures the first tab is selected. - */ - if (this.id === 'modx-resource-tabs' && MODx.request.tab === undefined && !this.getActiveTab()) { - this.setActiveTab(0); - } - }, afterrender: function(tabPanel) { if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'tab')) { const tabId = parseInt(MODx.request.tab, 10); diff --git a/manager/assets/modext/widgets/resource/modx.panel.resource.js b/manager/assets/modext/widgets/resource/modx.panel.resource.js index 81bd0e9046c..7fe99bed179 100644 --- a/manager/assets/modext/widgets/resource/modx.panel.resource.js +++ b/manager/assets/modext/widgets/resource/modx.panel.resource.js @@ -378,6 +378,18 @@ Ext.extend(MODx.panel.Resource,MODx.FormPanel,{ ,animCollapse: false ,itemId: 'tabs' ,items: it + ,listeners: { + tabchange: function(tabPanel, tab) { + /* + In certain scenarios, such as when form customization and/or a plugin adds a tab, + the state of the Resource tab panel can become uncertain and no tab will be initially + selected. This workaround ensures the first tab is selected. + */ + if (MODx.request.tab === undefined && !this.getActiveTab()) { + this.setActiveTab(0); + } + } + } }); if (MODx.config.tvs_below_content == 1) { var tvs = this.getTemplateVariablesPanel(config); From 88e4ee7e12891b3b3861b638f3a6a87358b6dfda Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 17:23:12 -0500 Subject: [PATCH 14/33] Filter Persistence: Namespaces Grid --- .../Workspace/PackageNamespace/GetList.php | 12 ++- .../namespace/modx.namespace.panel.js | 91 ++++--------------- 2 files changed, 25 insertions(+), 78 deletions(-) diff --git a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php index c6c0d9fd7df..1ae9c9216c0 100644 --- a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php +++ b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php @@ -43,7 +43,9 @@ class GetList extends GetListProcessor public function initialize() { $initialized = parent::initialize(); - $this->setDefaultProperties(['search' => false]); + $this->setDefaultProperties([ + 'query' => '' + ]); $this->isGridFilter = $this->getProperty('isGridFilter', false); return $initialized; } @@ -55,11 +57,11 @@ public function initialize() */ public function prepareQueryBeforeCount(xPDOQuery $c) { - $search = $this->getProperty('search', ''); - if (!empty($search)) { + $query = $this->getProperty('query', ''); + if (!empty($query)) { $c->where([ - 'name:LIKE' => '%' . $search . '%', - 'OR:path:LIKE' => '%' . $search . '%', + 'name:LIKE' => '%' . $query . '%', + 'OR:path:LIKE' => '%' . $query . '%', ]); } /* diff --git a/manager/assets/modext/workspace/namespace/modx.namespace.panel.js b/manager/assets/modext/workspace/namespace/modx.namespace.panel.js index 19befc2300b..90aaaba74e7 100644 --- a/manager/assets/modext/workspace/namespace/modx.namespace.panel.js +++ b/manager/assets/modext/workspace/namespace/modx.namespace.panel.js @@ -25,7 +25,6 @@ MODx.panel.Namespaces = function(config) { ,xtype: 'modx-description' },{ xtype: 'modx-grid-namespace' - ,urlFilters: ['search'] ,cls:'main-wrapper' ,preventRender: true }] @@ -52,7 +51,13 @@ MODx.grid.Namespace = function(config) { ,baseParams: { action: 'Workspace/PackageNamespace/GetList' } - ,fields: ['id','name','path','assets_path','perm'] + ,fields: [ + 'id', + 'name', + 'path', + 'assets_path', + 'perm' + ] ,anchor: '100%' ,paging: true ,autosave: true @@ -78,58 +83,17 @@ MODx.grid.Namespace = function(config) { ,sortable: false ,editor: { xtype: 'textfield' } }] - ,tbar: [{ - text: _('create') - ,handler: { xtype: 'modx-window-namespace-create' ,blankValues: true } - ,cls:'primary-button' - ,scope: this - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-namespace-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.search - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.namespaceSearch(cb, rec, ri); - } - ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.search) { - this.namespaceSearch(cb, cb.value); - MODx.request.search = ''; - } - } - ,scope: this - } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } - ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,tbar: [ + { + text: _('create') + ,handler: { xtype: 'modx-window-namespace-create' ,blankValues: true } + ,cls:'primary-button' + ,scope: this + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.Namespace.superclass.constructor.call(this,config); }; @@ -174,25 +138,6 @@ Ext.extend(MODx.grid.Namespace,MODx.grid.Grid,{ win.show(vent.target); } - ,namespaceSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.search = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var namespaceSearch = Ext.getCmp('modx-namespace-search'); - s.baseParams = { - action: 'Workspace/PackageNamespace/GetList' - }; - MODx.request.search = ''; - namespaceSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - ,removeSelected: function() { var cs = this.getSelectedAsList(); if (cs === false) return false; From 268e4918c6195d0af624e0e1b25763239484c7d4 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 17:27:01 -0500 Subject: [PATCH 15/33] Filter Persistence: Contexts Grid --- .../widgets/system/modx.grid.context.js | 114 +++--------------- 1 file changed, 18 insertions(+), 96 deletions(-) diff --git a/manager/assets/modext/widgets/system/modx.grid.context.js b/manager/assets/modext/widgets/system/modx.grid.context.js index b9a24b9417f..2532e5bfd78 100644 --- a/manager/assets/modext/widgets/system/modx.grid.context.js +++ b/manager/assets/modext/widgets/system/modx.grid.context.js @@ -25,7 +25,6 @@ MODx.panel.Contexts = function(config) { ,xtype: 'modx-description' },{ xtype: 'modx-grid-contexts' - ,urlFilters: ['search'] ,cls:'main-wrapper' ,preventRender: true }] @@ -53,7 +52,13 @@ MODx.grid.Context = function(config) { ,baseParams: { action: 'Context/GetList' } - ,fields: ['key','name','description','perm', 'rank'] + ,fields: [ + 'key', + 'name', + 'description', + 'perm', + 'rank' + ] ,paging: true ,autosave: true ,save_action: 'Context/UpdateFromGrid' @@ -88,58 +93,17 @@ MODx.grid.Context = function(config) { ,sortable: true ,editor: { xtype: 'numberfield' } }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,handler: this.create - ,scope: this - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-ctx-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.search - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.ctxSearch(cb, rec, ri); - } - ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.search) { - this.ctxSearch(cb, cb.value); - MODx.request.search = ''; - } - } - ,scope: this - } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } - ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,tbar: [ + { + text: _('create') + ,cls:'primary-button' + ,handler: this.create + ,scope: this + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.Context.superclass.constructor.call(this,config); }; @@ -238,25 +202,6 @@ Ext.extend(MODx.grid.Context,MODx.grid.Grid,{ }); } - ,ctxSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.search = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var ctxSearch = Ext.getCmp('modx-ctx-search'); - s.baseParams = { - action: 'Context/GetList' - }; - MODx.request.search = ''; - ctxSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - ,afterAction: function() { var cmp = Ext.getCmp('modx-resource-tree'); if (cmp) { @@ -265,29 +210,6 @@ Ext.extend(MODx.grid.Context,MODx.grid.Grid,{ this.getSelectionModel().clearSelections(true); this.refresh(); } - - ,getActions: function(record, rowIndex, colIndex, store) { - var permissions = record.data.perm; - var actions = []; - - if (~permissions.indexOf('pedit')) { - actions.push({ - action: 'updateContext', - icon: 'pencil-square-o', - text: _('edit') - }); - } - - if (~permissions.indexOf('premove')) { - actions.push({ - action: 'remove', - icon: 'trash-o', - text: _('delete') - }); - } - - return actions; - } }); Ext.reg('modx-grid-contexts',MODx.grid.Context); From 176ad1171c1250fda218ab7aed05c7405fbc3f37 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 18:56:46 -0500 Subject: [PATCH 16/33] Filter Persistence: Form Customization Profiles Grid --- .../Security/Forms/Profile/GetList.php | 11 +- .../modext/widgets/fc/modx.grid.fcprofile.js | 114 +++++------------- .../modext/widgets/fc/modx.panel.fcprofile.js | 1 - 3 files changed, 37 insertions(+), 89 deletions(-) diff --git a/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php b/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php index 03918c135c9..907f2106a03 100644 --- a/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php +++ b/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php @@ -1,4 +1,5 @@ setDefaultProperties(['search' => '']); + $this->setDefaultProperties(['query' => '']); $this->canEdit = $this->modx->hasPermission('save'); $this->canRemove = $this->modx->hasPermission('remove'); return parent::initialize(); @@ -47,11 +48,11 @@ public function initialize() public function getData() { $criteria = []; - $search = $this->getProperty('search', ''); - if (!empty($search)) { + $query = $this->getProperty('query', ''); + if (!empty($query)) { $criteria[] = [ - 'modFormCustomizationProfile.description:LIKE' => '%' . $search . '%', - 'OR:modFormCustomizationProfile.name:LIKE' => '%' . $search . '%', + 'modFormCustomizationProfile.description:LIKE' => '%' . $query . '%', + 'OR:modFormCustomizationProfile.name:LIKE' => '%' . $query . '%', ]; } $profileResult = $this->modx->call(modFormCustomizationProfile::class, 'listProfiles', [ diff --git a/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js b/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js index dc45c6a106e..d397e471b2d 100644 --- a/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js +++ b/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js @@ -25,7 +25,6 @@ MODx.panel.FCProfiles = function(config) { title: '' ,preventRender: true ,xtype: 'modx-grid-fc-profile' - ,urlFilters: ['search'] ,cls:'main-wrapper' }] }],{ @@ -52,7 +51,16 @@ MODx.grid.FCProfile = function(config) { ,baseParams: { action: 'Security/Forms/Profile/GetList' } - ,fields: ['id','name','description','usergroups','active','rank','sets','perm'] + ,fields: [ + 'id', + 'name', + 'description', + 'usergroups', + 'active', + 'rank', + 'sets', + 'perm' + ] ,paging: true ,autosave: true ,save_action: 'Security/Forms/Profile/UpdateFromGrid' @@ -95,73 +103,32 @@ MODx.grid.FCProfile = function(config) { return rec.data.active ? 'grid-row-active' : 'grid-row-inactive'; } } - ,tbar: [{ - text: _('create') - ,scope: this - ,handler: this.createProfile - ,cls:'primary-button' - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_activate') - ,handler: this.activateSelected - ,scope: this - },{ - text: _('selected_deactivate') - ,handler: this.deactivateSelected + ,tbar: [ + { + text: _('create') ,scope: this + ,handler: this.createProfile + ,cls:'primary-button' },{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }] - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-fcp-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.search - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.fcpSearch(cb, rec, ri); - } + text: _('bulk_actions') + ,menu: [{ + text: _('selected_activate') + ,handler: this.activateSelected ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.search) { - this.fcpSearch(cb, cb.value); - MODx.request.search = ''; - } - } + },{ + text: _('selected_deactivate') + ,handler: this.deactivateSelected ,scope: this - } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } + },{ + text: _('selected_remove') + ,handler: this.removeSelected ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + }] + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.FCProfile.superclass.constructor.call(this,config); this.on('render',function() { this.getStore().reload(); },this); @@ -340,25 +307,6 @@ Ext.extend(MODx.grid.FCProfile,MODx.grid.Grid,{ }); return true; } - - ,fcpSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.search = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var fcpSearch = Ext.getCmp('modx-fcp-search'); - s.baseParams = { - action: 'Security/Forms/Profile/GetList' - }; - MODx.request.search = ''; - fcpSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-fc-profile',MODx.grid.FCProfile); diff --git a/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js b/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js index c5ba53c17a7..002b5e6ed8d 100644 --- a/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js +++ b/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js @@ -71,7 +71,6 @@ MODx.panel.FCProfile = function(config) { }] },{ xtype: 'modx-grid-fc-set' - ,urlFilters: ['search'] ,cls:'main-wrapper' ,baseParams: { action: 'Security/Forms/Set/GetList' From 89b6b343b74a8638c6fba28d6fd303d4082ac600 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 18:59:49 -0500 Subject: [PATCH 17/33] Filter Persistence: Form Customization Sets Grid --- .../Processors/Security/Forms/Set/GetList.php | 12 +- .../modext/widgets/fc/modx.grid.fcset.js | 127 ++++++------------ 2 files changed, 46 insertions(+), 93 deletions(-) diff --git a/core/src/Revolution/Processors/Security/Forms/Set/GetList.php b/core/src/Revolution/Processors/Security/Forms/Set/GetList.php index cf25ab5d43e..1fcd77a4cf9 100644 --- a/core/src/Revolution/Processors/Security/Forms/Set/GetList.php +++ b/core/src/Revolution/Processors/Security/Forms/Set/GetList.php @@ -38,7 +38,7 @@ class GetList extends GetListProcessor */ public function initialize() { - $this->setDefaultProperties(['profile' => 0, 'search' => '']); + $this->setDefaultProperties(['profile' => 0, 'query' => '']); $this->canEdit = $this->modx->hasPermission('save'); $this->canRemove = $this->modx->hasPermission('remove'); return parent::initialize(); @@ -55,12 +55,12 @@ public function prepareQueryBeforeCount(xPDOQuery $c) if (!empty($profile)) { $c->where(['profile' => $profile]); } - $search = $this->getProperty('search'); - if (!empty($search)) { + $query = $this->getProperty('query'); + if (!empty($query)) { $c->where([ - 'modFormCustomizationSet.description:LIKE' => '%' . $search . '%', - 'OR:Template.templatename:LIKE' => '%' . $search . '%', - 'OR:modFormCustomizationSet.constraint_field:LIKE' => '%' . $search . '%', + 'modFormCustomizationSet.description:LIKE' => '%' . $query . '%', + 'OR:Template.templatename:LIKE' => '%' . $query . '%', + 'OR:modFormCustomizationSet.constraint_field:LIKE' => '%' . $query . '%', ], null, 2); } return $c; diff --git a/manager/assets/modext/widgets/fc/modx.grid.fcset.js b/manager/assets/modext/widgets/fc/modx.grid.fcset.js index 5bb0ddae51c..c07878db9c2 100644 --- a/manager/assets/modext/widgets/fc/modx.grid.fcset.js +++ b/manager/assets/modext/widgets/fc/modx.grid.fcset.js @@ -7,7 +7,21 @@ MODx.grid.FCSet = function(config) { ,baseParams: { action: 'Security/Forms/Set/GetList' } - ,fields: ['id','profile','action','description','active','template','templatename','constraint_data','constraint','constraint_field','constraint_class','rules','perm'] + ,fields: [ + 'id', + 'profile', + 'action', + 'description', + 'active', + 'template', + 'templatename', + 'constraint_data', + 'constraint', + 'constraint_field', + 'constraint_class', + 'rules', + 'perm' + ] ,paging: true ,autosave: true ,save_action: 'Security/Forms/Set/UpdateFromGrid' @@ -80,77 +94,36 @@ MODx.grid.FCSet = function(config) { return rec.data.active ? 'grid-row-active' : 'grid-row-inactive'; } } - ,tbar: [{ - text: _('create') - ,cls: 'primary-button' - ,scope: this - ,handler: this.createSet - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_activate') - ,handler: this.activateSelected + ,tbar: [ + { + text: _('create') + ,cls: 'primary-button' ,scope: this + ,handler: this.createSet },{ - text: _('selected_deactivate') - ,handler: this.deactivateSelected - ,scope: this - },{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }] - },{ - text: _('import') - ,handler: this.importSet - ,scope: this - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-fcs-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.search - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.fcsSearch(cb, rec, ri); - } + text: _('bulk_actions') + ,menu: [{ + text: _('selected_activate') + ,handler: this.activateSelected ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.search) { - this.fcsSearch(cb, cb.value); - MODx.request.search = ''; - } - } + },{ + text: _('selected_deactivate') + ,handler: this.deactivateSelected ,scope: this - } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } + },{ + text: _('selected_remove') + ,handler: this.removeSelected ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + }] + },{ + text: _('import') + ,handler: this.importSet + ,scope: this + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.FCSet.superclass.constructor.call(this,config); }; @@ -214,26 +187,6 @@ Ext.extend(MODx.grid.FCSet,MODx.grid.Grid,{ } } - ,fcsSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.search = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var fcsSearch = Ext.getCmp('modx-fcs-search'); - s.baseParams = { - action: 'Security/Forms/Set/GetList' - ,profile: MODx.request.id - }; - MODx.request.search = ''; - fcsSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - ,exportSet: function(btn,e) { var id = this.menu.record.id; MODx.Ajax.request({ From 8e2a23956389241f8bdc57d18bc40385b0cb8412 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 19:03:22 -0500 Subject: [PATCH 18/33] Filter Persistence: Dashboards Grid --- .../Processors/System/Dashboard/GetList.php | 6 +- .../widgets/system/modx.panel.dashboards.js | 128 ++++++------------ 2 files changed, 45 insertions(+), 89 deletions(-) diff --git a/core/src/Revolution/Processors/System/Dashboard/GetList.php b/core/src/Revolution/Processors/System/Dashboard/GetList.php index 6a5afb3bd19..199b1b00913 100644 --- a/core/src/Revolution/Processors/System/Dashboard/GetList.php +++ b/core/src/Revolution/Processors/System/Dashboard/GetList.php @@ -39,8 +39,10 @@ public function prepareQueryAfterCount(xPDOQuery $c) { $query = $this->getProperty('query'); if (!empty($query)) { - $c->where(['modDashboard.name:LIKE' => '%' . $query . '%']); - $c->orCondition(['modDashboard.description:LIKE' => '%' . $query . '%']); + $c->where([ + 'modDashboard.name:LIKE' => '%' . $query . '%', + 'OR:modDashboard.description:LIKE' => '%' . $query . '%', + ]); } $userGroup = $this->getProperty('usergroup', false); if (!empty($userGroup)) { diff --git a/manager/assets/modext/widgets/system/modx.panel.dashboards.js b/manager/assets/modext/widgets/system/modx.panel.dashboards.js index 604e8f7913a..940f47487d6 100644 --- a/manager/assets/modext/widgets/system/modx.panel.dashboards.js +++ b/manager/assets/modext/widgets/system/modx.panel.dashboards.js @@ -36,14 +36,7 @@ MODx.panel.Dashboards = function(config) { ,cls: 'main-wrapper' ,preventRender: true }] - }],{ - stateful: true - ,stateId: 'modx-dashboards-tabpanel' - ,stateEvents: ['tabchange'] - ,getState:function() { - return {activeTab:this.items.indexOf(this.getActiveTab())}; - } - })] + }])] }); MODx.panel.Dashboards.superclass.constructor.call(this,config); }; @@ -63,9 +56,15 @@ MODx.grid.Dashboards = function(config) { Ext.applyIf(config,{ url: MODx.config.connector_url ,baseParams: { - action: 'System/Dashboard/GetList' + action: 'System/Dashboard/GetList', + usergroup: MODx.request.usergroup || null } - ,fields: ['id','name','description','cls'] + ,fields: [ + 'id', + 'name', + 'description', + 'cls' + ] ,paging: true ,autosave: true ,save_action: 'System/Dashboard/UpdateFromGrid' @@ -94,65 +93,41 @@ MODx.grid.Dashboards = function(config) { ,sortable: false ,editor: { xtype: 'textarea' } }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,handler: this.createDashboard - ,scope: this - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected + ,tbar: [ + { + text: _('create') + ,cls:'primary-button' + ,handler: this.createDashboard ,scope: this - }] - },'->',{ - xtype: 'modx-combo-usergroup' - ,name: 'usergroup' - ,id: 'modx-user-filter-usergroup' - ,itemId: 'usergroup' - ,emptyText: _('user_group_filter')+'...' - ,baseParams: { - action: 'Security/Group/GetList' - ,addAll: true - } - ,value: '' - ,width: 200 - ,listeners: { - 'select': {fn:this.filterUsergroup,scope:this} - } - },{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-dashboard-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: function() { - this.fireEvent('change',this.getValue()); - this.blur(); - return true;} - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,text: _('filter_clear') - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); + },{ + text: _('bulk_actions') + ,menu: [{ + text: _('selected_remove') + ,handler: this.removeSelected + ,scope: this + }] + },'->',{ + xtype: 'modx-combo-usergroup' + ,itemId: 'filter-usergroup' + ,emptyText: _('user_group_filter') + ,baseParams: { + action: 'Security/Group/GetList' + ,addAll: true } + ,value: MODx.request.usergroup || null + ,width: 200 + ,listeners: { + select: { + fn: function (cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'usergroup'); + }, + scope: this + } } - } - }] + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-usergroup, filter-query') + ] }); MODx.grid.Dashboards.superclass.constructor.call(this,config); }; @@ -252,26 +227,5 @@ Ext.extend(MODx.grid.Dashboards,MODx.grid.Grid,{ return true; } - ,filterUsergroup: function(cb,nv,ov) { - this.getStore().baseParams.usergroup = Ext.isEmpty(nv) || Ext.isObject(nv) ? cb.getValue() : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'System/Dashboard/GetList' - }; - Ext.getCmp('modx-dashboard-search').reset(); - Ext.getCmp('modx-user-filter-usergroup').reset(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-dashboards',MODx.grid.Dashboards); From 3029692c7e09c625778538b012657d0bd36f2835 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 19:14:21 -0500 Subject: [PATCH 19/33] Filter Persistence: Dashboard Widgets Grid --- .../System/Dashboard/Widget/GetList.php | 6 +- .../system/modx.grid.dashboard.widgets.js | 91 +++++++------------ 2 files changed, 36 insertions(+), 61 deletions(-) diff --git a/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php b/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php index 6d9399349c9..63f300c9dbb 100644 --- a/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php +++ b/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php @@ -39,8 +39,10 @@ public function prepareQueryBeforeCount(xPDOQuery $c) { $query = $this->getProperty('query'); if (!empty($query)) { - $c->where(['modDashboardWidget.name:LIKE' => '%' . $query . '%']); - $c->orCondition(['modDashboardWidget.description:LIKE' => '%' . $query . '%']); + $c->where([ + 'modDashboardWidget.name:LIKE' => '%' . $query . '%', + 'OR:modDashboardWidget.description:LIKE' => '%' . $query . '%', + ]); } return $c; } diff --git a/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js b/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js index 21138b515cd..a21e8b3c3be 100644 --- a/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js +++ b/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js @@ -4,10 +4,9 @@ * @param {Object} config An object of configuration properties * @xtype modx-grid-dashboard-widgets */ -MODx.grid.DashboardWidgets = function(config) { - config = config || {}; +MODx.grid.DashboardWidgets = function(config = {}) { this.exp = new Ext.grid.RowExpander({ - tpl : new Ext.Template( + tpl: new Ext.Template( '

    {description_trans}

    ' ) }); @@ -18,7 +17,19 @@ MODx.grid.DashboardWidgets = function(config) { ,baseParams: { action: 'System/Dashboard/Widget/GetList' } - ,fields: ['id','name','name_trans','description','description_trans','type','content','namespace','lexicon','size','cls'] + ,fields: [ + 'id', + 'name', + 'name_trans', + 'description', + 'description_trans', + 'type', + 'content', + 'namespace', + 'lexicon', + 'size', + 'cls' + ] ,paging: true ,remoteSort: true ,sm: this.sm @@ -50,47 +61,24 @@ MODx.grid.DashboardWidgets = function(config) { ,width: 120 ,sortable: true }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,handler: this.createDashboard - ,scope: this - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected + ,tbar: [ + { + text: _('create') + ,cls:'primary-button' + ,handler: this.createDashboard ,scope: this - }] - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-dashboard-widget-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,text: _('filter_clear') - ,id: 'modx-dashboard-widgets-filter-clear' - ,cls: 'x-form-filter-clear' - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + },{ + text: _('bulk_actions') + ,menu: [{ + text: _('selected_remove') + ,handler: this.removeSelected + ,scope: this + }] + }, + '->', + this.getQueryFilterField('filter-query-dashboardWidgets'), + this.getClearFiltersButton('filter-query-dashboardWidgets') + ] }); MODx.grid.DashboardWidgets.superclass.constructor.call(this,config); }; @@ -170,20 +158,5 @@ Ext.extend(MODx.grid.DashboardWidgets,MODx.grid.Grid,{ }); return true; } - - ,search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'System/Dashboard/Widget/GetList' - }; - Ext.getCmp('modx-dashboard-widget-search').reset(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-dashboard-widgets',MODx.grid.DashboardWidgets); From 599a5642e7762f16aa44dbb6fb60b1201963df15 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 19:24:46 -0500 Subject: [PATCH 20/33] Filter Persistence: Packages Grid --- .../Processors/Workspace/Packages/GetList.php | 4 +- .../modext/workspace/package.containers.js | 1 - .../assets/modext/workspace/package.grid.js | 104 ++++++------------ 3 files changed, 34 insertions(+), 75 deletions(-) diff --git a/core/src/Revolution/Processors/Workspace/Packages/GetList.php b/core/src/Revolution/Processors/Workspace/Packages/GetList.php index 0c49c2ef075..a48d48c1af7 100644 --- a/core/src/Revolution/Processors/Workspace/Packages/GetList.php +++ b/core/src/Revolution/Processors/Workspace/Packages/GetList.php @@ -53,7 +53,7 @@ public function initialize() 'limit' => 10, 'workspace' => 1, 'dateFormat' => $this->modx->getOption('manager_date_format') . ', ' . $this->modx->getOption('manager_time_format'), - 'search' => '', + 'query' => '', ]); return true; } @@ -71,7 +71,7 @@ public function getData() $this->getProperty('workspace', 1), $limit > 0 ? $limit : 0, $start, - $this->getProperty('search', ''), + $this->getProperty('query', ''), ]); $data['results'] = $pkgList['collection']; $data['total'] = $pkgList['total']; diff --git a/manager/assets/modext/workspace/package.containers.js b/manager/assets/modext/workspace/package.containers.js index 124db463da2..07dc9fdaacb 100644 --- a/manager/assets/modext/workspace/package.containers.js +++ b/manager/assets/modext/workspace/package.containers.js @@ -21,7 +21,6 @@ MODx.panel.Packages = function(config) { ,activeItem: 0 ,items:[{ xtype:'modx-package-grid' - ,urlFilters: ['search'] ,id:'modx-package-grid' ,bodyCssClass: 'grid-with-buttons' },{ diff --git a/manager/assets/modext/workspace/package.grid.js b/manager/assets/modext/workspace/package.grid.js index d03e2258888..69abd8aa225 100644 --- a/manager/assets/modext/workspace/package.grid.js +++ b/manager/assets/modext/workspace/package.grid.js @@ -84,9 +84,28 @@ MODx.grid.Package = function(config) { ,id: 'modx-package-grid' ,url: MODx.config.connector_url ,action: 'Workspace/Packages/GetList' - ,fields: ['signature','name','version','release','created','updated','installed','state','workspace' - ,'provider','provider_name','disabled','source','attributes','readme','menu' - ,'install','textaction','iconaction','updateable'] + ,fields: [ + 'signature', + 'name', + 'version', + 'release', + 'created', + 'updated', + 'installed', + 'state', + 'workspace', + 'provider', + 'provider_name', + 'disabled', + 'source', + 'attributes', + 'readme', + 'menu', + 'install', + 'textaction', + 'iconaction', + 'updateable' + ] ,showActionsColumn: false ,plugins: [this.exp] ,pageSize: Math.min(parseInt(MODx.config.default_per_page), 25) @@ -95,56 +114,16 @@ MODx.grid.Package = function(config) { ,primaryKey: 'signature' ,paging: true ,autosave: true - ,tbar: [dlbtn, { - text: _('packages_purge') - ,handler: this.purgePackages - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-package-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.search - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.packageSearch(cb, rec, ri); - } - ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.search) { - this.packageSearch(cb, cb.value); - MODx.request.search = ''; - } - } - ,scope: this - } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } - ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-package-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,tbar: [ + dlbtn, + { + text: _('packages_purge'), + handler: this.purgePackages + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.Package.superclass.constructor.call(this,config); this.on('render',function() { @@ -206,25 +185,6 @@ Ext.extend(MODx.grid.Package,MODx.grid.Grid,{ Ext.getCmp('packages-breadcrumbs').reset(msg); } - ,packageSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.search = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var packageSearch = Ext.getCmp('modx-package-search'); - s.baseParams = { - action: 'Workspace/Packages/GetList' - }; - MODx.request.search = ''; - packageSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - /* Main column renderer */ ,mainColumnRenderer:function (value, metaData, record, rowIndex, colIndex, store){ var rec = record.data; From dc88bb79daf4f530b5daa82d70249c198c68e15c Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 19:28:32 -0500 Subject: [PATCH 21/33] Filter Persistence: Media Sources Grid --- .../widgets/source/modx.panel.sources.js | 127 +++++------------- 1 file changed, 34 insertions(+), 93 deletions(-) diff --git a/manager/assets/modext/widgets/source/modx.panel.sources.js b/manager/assets/modext/widgets/source/modx.panel.sources.js index 8912c755481..7cce9d4e9a3 100644 --- a/manager/assets/modext/widgets/source/modx.panel.sources.js +++ b/manager/assets/modext/widgets/source/modx.panel.sources.js @@ -25,7 +25,6 @@ MODx.panel.Sources = function(config) { ,xtype: 'modx-description' },{ xtype: 'modx-grid-sources' - ,urlFilters: ['query'] ,cls: 'main-wrapper' ,preventRender: true }] @@ -40,14 +39,7 @@ MODx.panel.Sources = function(config) { ,cls: 'main-wrapper' ,preventRender: true }] - }],{ - stateful: true - ,stateId: 'modx-sources-tabpanel' - ,stateEvents: ['tabchange'] - ,getState:function() { - return {activeTab:this.items.indexOf(this.getActiveTab())}; - } - })] + }])] }); MODx.panel.Sources.superclass.constructor.call(this,config); }; @@ -62,16 +54,20 @@ Ext.reg('modx-panel-sources',MODx.panel.Sources); * @param {Object} config An object of configuration properties * @xtype modx-grid-sources */ -MODx.grid.Sources = function(config) { - config = config || {}; - +MODx.grid.Sources = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); Ext.applyIf(config,{ url: MODx.config.connector_url ,baseParams: { action: 'Source/GetList' } - ,fields: ['id','name','description','class_key','cls'] + ,fields: [ + 'id', + 'name', + 'description', + 'class_key', + 'cls' + ] ,paging: true ,autosave: true ,save_action: 'Source/UpdateFromGrid' @@ -101,64 +97,26 @@ MODx.grid.Sources = function(config) { ,editor: { xtype: 'textarea' } ,renderer: Ext.util.Format.htmlEncode }] - ,tbar: [{ - text: _('create') - ,handler: { xtype: 'modx-window-source-create' ,blankValues: true } - ,cls:'primary-button' - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }] - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-source-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.query - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.sourceSearch(cb, rec, ri); - } - ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.query) { - this.sourceSearch(cb, cb.value); - MODx.request.query = ''; - } - } - ,scope: this + ,tbar: [ + { + text: _('create') + ,handler: { + xtype: 'modx-window-source-create', + blankValues: true } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } + ,cls:'primary-button' + },{ + text: _('bulk_actions') + ,menu: [{ + text: _('selected_remove') + ,handler: this.removeSelected ,scope: this - } - } - },{ - xtype: 'button' - ,text: _('filter_clear') - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + }] + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.Sources.superclass.constructor.call(this,config); }; @@ -258,24 +216,6 @@ Ext.extend(MODx.grid.Sources,MODx.grid.Grid,{ return true; } - ,sourceSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.query = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var sourceSearch = Ext.getCmp('modx-source-search'); - s.baseParams = { - action: 'Source/GetList' - }; - MODx.request.query = ''; - sourceSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-sources',MODx.grid.Sources); @@ -287,8 +227,7 @@ Ext.reg('modx-grid-sources',MODx.grid.Sources); * @param {Object} config An object of options. * @xtype modx-window-source-create */ -MODx.window.CreateSource = function(config) { - config = config || {}; +MODx.window.CreateSource = function(config = {}) { Ext.applyIf(config,{ title: _('create') ,url: MODx.config.connector_url @@ -322,15 +261,17 @@ MODx.window.CreateSource = function(config) { Ext.extend(MODx.window.CreateSource,MODx.Window); Ext.reg('modx-window-source-create',MODx.window.CreateSource); -MODx.grid.SourceTypes = function(config) { - config = config || {}; - +MODx.grid.SourceTypes = function(config = {}) { Ext.applyIf(config,{ url: MODx.config.connector_url ,baseParams: { action: 'Source/Type/GetList' } - ,fields: ['class','name','description'] + ,fields: [ + 'class', + 'name', + 'description' + ] ,showActionsColumn: false ,paging: true ,remoteSort: true From 22c6763179b1a49f7867ebfa16a0ad521b9ed9b0 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 20:16:29 -0500 Subject: [PATCH 22/33] Update related to commit 0e0778d Forgot to add this file in for Contexts panel/grid --- manager/assets/modext/widgets/system/modx.panel.context.js | 1 - 1 file changed, 1 deletion(-) diff --git a/manager/assets/modext/widgets/system/modx.panel.context.js b/manager/assets/modext/widgets/system/modx.panel.context.js index 5aa9cb02786..9a4270cab05 100644 --- a/manager/assets/modext/widgets/system/modx.panel.context.js +++ b/manager/assets/modext/widgets/system/modx.panel.context.js @@ -69,7 +69,6 @@ MODx.panel.Context = function(config) { ,xtype: 'modx-description' },{ xtype: 'modx-grid-context-settings' - ,urlFilters: ['namespace', 'area', 'query'] ,cls:'main-wrapper' ,title: '' ,preventRender: true From 55370e73d75b43a9438c86268622269dbe4297b6 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 20:17:37 -0500 Subject: [PATCH 23/33] Update related to commit c7fd92b Forgot to add this file --- core/lexicon/en/dashboards.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/lexicon/en/dashboards.inc.php b/core/lexicon/en/dashboards.inc.php index 3a88d926c89..f6cb29fd2d2 100644 --- a/core/lexicon/en/dashboards.inc.php +++ b/core/lexicon/en/dashboards.inc.php @@ -32,7 +32,7 @@ $_lang['dashboards'] = 'Dashboards'; $_lang['dashboards.intro_msg'] = 'Here you can manage all the available Dashboards for this MODX manager.'; $_lang['rank'] = 'Rank'; -$_lang['user_group_filter'] = 'By User Group'; +$_lang['user_group_filter'] = 'Filter by User Group...'; $_lang['widget'] = 'Widget'; $_lang['widget_content'] = 'Widget Content'; $_lang['widget_err_ae_name'] = 'A widget with the name "[[+name]]" already exists! Please try another name.'; @@ -99,4 +99,4 @@ $_lang['w_whosonline'] = 'Who\'s Online'; $_lang['w_whosonline_desc'] = 'Shows a list of online users.'; $_lang['w_view_all'] = 'View all'; -$_lang['w_no_data'] = 'No data to display'; \ No newline at end of file +$_lang['w_no_data'] = 'No data to display'; From bd994c855f037f0bd9f73e74a700b0e512e85bb6 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 21:48:31 -0500 Subject: [PATCH 24/33] Filter Persistence: Users Grid --- .../modext/widgets/security/modx.grid.user.js | 146 +++++++----------- 1 file changed, 57 insertions(+), 89 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.js b/manager/assets/modext/widgets/security/modx.grid.user.js index a1a75a38a8d..5c7a7157b4b 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.js @@ -6,8 +6,7 @@ * @param {Object} config An object of configuration options * @xtype modx-panel-users */ -MODx.panel.Users = function(config) { - config = config || {}; +MODx.panel.Users = function(config = {}) { Ext.applyIf(config,{ id: 'modx-panel-users' ,cls: 'container' @@ -43,17 +42,25 @@ Ext.reg('modx-panel-users',MODx.panel.Users); * @param {Object} config An object of configuration properties * @xtype modx-grid-user */ -MODx.grid.User = function(config) { - config = config || {}; - +MODx.grid.User = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); Ext.applyIf(config,{ url: MODx.config.connector_url ,baseParams: { action: 'Security/User/GetList' - ,usergroup: MODx.request['usergroup'] ? MODx.request['usergroup'] : '' + ,usergroup: MODx.request.usergroup || null } - ,fields: ['id','username','fullname','email','gender','blocked','role','active','cls'] + ,fields: [ + 'id', + 'username', + 'fullname', + 'email', + 'gender', + 'blocked', + 'role', + 'active', + 'cls' + ] ,paging: true ,autosave: true ,save_action: 'Security/User/UpdateFromGrid' @@ -111,70 +118,53 @@ MODx.grid.User = function(config) { ,sortable: true ,editor: { xtype: 'combo-boolean', renderer: 'boolean' } }] - ,tbar: [{ - text: _('create') - ,handler: this.createUser - ,scope: this - ,cls:'primary-button' - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_activate') - ,handler: this.activateSelected - ,scope: this - },{ - text: _('selected_deactivate') - ,handler: this.deactivateSelected + ,tbar: [ + { + text: _('create') + ,handler: this.createUser ,scope: this + ,cls:'primary-button' },{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }] - },'->',{ - xtype: 'modx-combo-usergroup' - ,name: 'usergroup' - ,id: 'modx-user-filter-usergroup' - ,itemId: 'usergroup' - ,emptyText: _('user_group')+'...' - ,baseParams: { - action: 'Security/Group/GetList' - ,addAll: true - } - ,value: MODx.request['usergroup'] ? MODx.request['usergroup'] : '' - ,width: 200 - ,listeners: { - 'select': {fn:this.filterUsergroup,scope:this} - } - },{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-user-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); + text: _('bulk_actions') + ,menu: [ + { + text: _('selected_activate') + ,handler: this.activateSelected + ,scope: this + },{ + text: _('selected_deactivate') + ,handler: this.deactivateSelected + ,scope: this + },{ + text: _('selected_remove') + ,handler: this.removeSelected + ,scope: this + } + ] + }, + '->', + { + xtype: 'modx-combo-usergroup' + ,itemId: 'filter-usergroup' + ,emptyText: `${_('user_group')}...` + ,baseParams: { + action: 'Security/Group/GetList' + ,addAll: true } + ,value: MODx.request.usergroup || null + ,width: 200 + ,listeners: { + select: { + fn: function (cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'usergroup'); + }, + scope: this + } } - } - }] + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-usergroup, filter-query') + ] }); MODx.grid.User.superclass.constructor.call(this,config); }; @@ -336,27 +326,5 @@ Ext.extend(MODx.grid.User,MODx.grid.Grid,{ return _('female'); } } - - ,filterUsergroup: function(cb,nv,ov) { - this.getStore().baseParams.usergroup = Ext.isEmpty(nv) || Ext.isObject(nv) ? cb.getValue() : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'Security/User/GetList' - }; - Ext.getCmp('modx-user-search').reset(); - Ext.getCmp('modx-user-filter-usergroup').reset(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-user',MODx.grid.User); From eb6503317ee38c61479116ee6eb8e02088cdef61 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 22:09:32 -0500 Subject: [PATCH 25/33] Filter Persistence: Groups Panel and Users Grid Note that this is the panel that appears upon first going to the ACLs. The affected grid appears when clicking on a User Group in the left tree. The grid also appears under the Users panel when editing a User Group. --- .../Security/Group/User/GetList.php | 10 +- .../security/modx.panel.groups.roles.js | 134 ++++++++++-------- .../widgets/security/modx.panel.user.group.js | 75 +++------- 3 files changed, 98 insertions(+), 121 deletions(-) diff --git a/core/src/Revolution/Processors/Security/Group/User/GetList.php b/core/src/Revolution/Processors/Security/Group/User/GetList.php index a0d5a3a9d40..628b8cf32d6 100644 --- a/core/src/Revolution/Processors/Security/Group/User/GetList.php +++ b/core/src/Revolution/Processors/Security/Group/User/GetList.php @@ -1,4 +1,5 @@ setDefaultProperties([ 'usergroup' => false, - 'username' => '', + 'query' => '' ]); return parent::initialize(); @@ -60,10 +61,11 @@ public function prepareQueryBeforeCount(xPDOQuery $c) $userGroup = $this->getProperty('usergroup', 0); $c->where(['UserGroupMembers.user_group' => $userGroup]); - $username = $this->getProperty('username', ''); - if (!empty($username)) { + $query = $this->getProperty('query', ''); + if (!empty($query)) { $c->where([ - $c->getAlias() . '.username:LIKE' => '%' . $username . '%', + $c->getAlias() . '.username:LIKE' => '%' . $query . '%', + 'OR:UserGroupRole.name:LIKE' => '%' . $query . '%' ]); } diff --git a/manager/assets/modext/widgets/security/modx.panel.groups.roles.js b/manager/assets/modext/widgets/security/modx.panel.groups.roles.js index efaf1623fdf..7b0a3496e7e 100644 --- a/manager/assets/modext/widgets/security/modx.panel.groups.roles.js +++ b/manager/assets/modext/widgets/security/modx.panel.groups.roles.js @@ -4,50 +4,73 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-groups-roles */ -MODx.panel.GroupsRoles = function(config) { - config = config || {}; - Ext.applyIf(config,{ +MODx.panel.GroupsRoles = function(config = {}) { + this.currentGroupId = 0; + Ext.applyIf(config, { id: 'modx-panel-groups-roles' - ,cls: 'container' - ,defaults: { collapsible: false ,autoHeight: true } + ,cls: 'container' + ,defaults: { + collapsible: false, + autoHeight: true + } ,forceLayout: true - ,items: [{ - html: _('user_group_management') - ,id: 'modx-access-permissions-header' - ,xtype: 'modx-header' - },MODx.getPageStructure(this.getPageTabs(config),{ - id: 'modx-access-permissions-tabs' - ,stateful: true - ,stateId: 'access-tabpanel' - ,stateEvents: ['tabchange'] - ,getState:function() { - return {activeTab:this.items.indexOf(this.getActiveTab())}; - } - })] + ,items: [ + { + html: _('user_group_management'), + id: 'modx-access-permissions-header', + xtype: 'modx-header' + }, MODx.getPageStructure( + this.getPageTabs(config), + { id: 'modx-access-permissions-tabs' } + ) + ] }); MODx.panel.GroupsRoles.superclass.constructor.call(this,config); - var west, usergroupTree = Ext.getCmp('modx-tree-usergroup'); + const userGrid = Ext.getCmp('modx-usergroup-users'), + usergroupTree = Ext.getCmp('modx-tree-usergroup') + ; - usergroupTree.on('expandnode', this.fixPanelHeight); - usergroupTree.on('collapsenode', this.fixPanelHeight); - - usergroupTree.addListener({ - resize : function(cmp) { - var centre = Ext.getCmp('modx-usergroup-users'); - if (centre.hidden){ - Ext.getCmp('modx-tree-panel-usergroup').layout.west.getSplitBar().el.hide(); - } + usergroupTree.on({ + resize: { + fn: function(cmp) { + if (userGrid.hidden) { + Ext.getCmp('modx-tree-panel-usergroup').layout.west.getSplitBar().el.hide(); + } + }, + scope: this + }, + refresh: { + fn: function() { + this.setActiveGroupNodeFromParam(); + }, + scope: this } }); if (MODx.perm.usergroup_user_list) { - Ext.getCmp('modx-tree-usergroup').on('click', function(node,e){ + usergroupTree.on('click', function(node, e){ + this.currentGroupId = MODx.util.tree.getGroupIdFromNode(node); + Ext.getCmp('modx-usergroup-users').clearGridFilters('filter-query'); + if (this.currentGroupId > 0) { + MODx.util.url.setParams({ + group: this.currentGroupId, + tab: 0 + }); + } this.getUsers(node); }, this); - } - Ext.getCmp('modx-usergroup-users').store.on('load', this.fixPanelHeight); + usergroupTree.getLoader().on({ + load: { + fn: function() { + this.currentGroupId = MODx.request.group || 0; + this.setActiveGroupNodeFromParam(); + }, + scope: this + } + }); + } }; Ext.extend(MODx.panel.GroupsRoles,MODx.FormPanel,{ getPageTabs: function(config) { @@ -89,8 +112,8 @@ Ext.extend(MODx.panel.GroupsRoles,MODx.FormPanel,{ region: 'center' ,id: 'modx-usergroup-users' ,xtype: 'modx-grid-user-group-users' - ,hidden: true - ,usergroup: '0' + ,hidden: MODx.perm.usergroup_user_list && this.currentGroupId > 0 ? false : true + ,usergroup: this.currentGroupId ,layout: 'fit' ,cls:'main-wrapper' } @@ -146,40 +169,35 @@ Ext.extend(MODx.panel.GroupsRoles,MODx.FormPanel,{ } return tbs; } - ,getUsers: function(node) { - var center = Ext.getCmp('modx-usergroup-users'); - center.removeAll(); - var id = node.attributes.id; - var usergroup = id.replace('n_ug_', '') - 0; // typecasting - var userGrid = Ext.getCmp('modx-usergroup-users'); - var westPanel = Ext.getCmp('modx-tree-panel-usergroup').layout.west; - - if (usergroup == 0) { + ,getUsers: function(node) { + const userGrid = Ext.getCmp('modx-usergroup-users'), + westPanel = Ext.getCmp('modx-tree-panel-usergroup').layout.west + ; + if (this.currentGroupId == 0) { userGrid.hide(); westPanel.getSplitBar().el.hide(); } else { userGrid.show(); westPanel.getSplitBar().el.show(); - userGrid.usergroup = usergroup; - userGrid.config.usergroup = usergroup; - userGrid.store.baseParams.usergroup = usergroup; - userGrid.clearGridFilters('filter-username'); + userGrid.usergroup = this.currentGroupId; + userGrid.config.usergroup = this.currentGroupId; + userGrid.store.baseParams.usergroup = this.currentGroupId; + userGrid.store.load(); } - } - ,fixPanelHeight: function() { - // fixing border layout's height regarding to tree panel's - var treeEl = Ext.getCmp('modx-tree-usergroup').getEl(); - if (!treeEl) { - return; + + ,setActiveGroupNodeFromParam: function() { + if (this.currentGroupId > 0) { + const usergroupTree = Ext.getCmp('modx-tree-usergroup'), + groupNodeId = `n_ug_${this.currentGroupId}`, + groupNode = usergroupTree.getNodeById(`n_ug_${this.currentGroupId}`) + ; + if (typeof groupNode !== 'undefined' && groupNodeId === groupNode.id) { + groupNode.select(); + this.getUsers(groupNode); + } } - var treeH = treeEl.getHeight(); - var cHeight = Ext.getCmp('modx-usergroup-users').getHeight(); // .main-wrapper - var maxH = (treeH > cHeight) ? treeH : cHeight; - maxH = maxH > 500 ? maxH : 500; - Ext.getCmp('modx-tree-panel-usergroup').setHeight(maxH); - Ext.getCmp('modx-content').doLayout(); } }); Ext.reg('modx-panel-groups-roles',MODx.panel.GroupsRoles); diff --git a/manager/assets/modext/widgets/security/modx.panel.user.group.js b/manager/assets/modext/widgets/security/modx.panel.user.group.js index afaf66ece42..23707b3b639 100644 --- a/manager/assets/modext/widgets/security/modx.panel.user.group.js +++ b/manager/assets/modext/widgets/security/modx.panel.user.group.js @@ -350,7 +350,6 @@ MODx.grid.UserGroupUsers = function(config) { ,baseParams: { action: 'Security/Group/User/GetList' ,usergroup: config.usergroup - ,username: MODx.request.username ? decodeURIComponent(MODx.request.username) : '' } ,paging: true ,grouping: true @@ -390,64 +389,22 @@ MODx.grid.UserGroupUsers = function(config) { }); }, scope: this } }] - ,tbar: [{ - text: _('user_group_update') - ,cls: 'primary-button' - ,handler: this.updateUserGroup - ,hidden: (MODx.perm.usergroup_edit == 0 || config.ownerCt.id != 'modx-tree-panel-usergroup') - },{ - text: _('user_group_user_add') - ,cls: 'primary-button' - ,handler: this.addUser - ,hidden: MODx.perm.usergroup_user_edit == 0 - },'->',{ - xtype: 'textfield' - ,itemId: 'filter-username' - ,emptyText: _('search') - ,value: MODx.request.username ? decodeURIComponent(MODx.request.username) : '' - ,listeners: { - change: { - fn: function (cmp, newValue, oldValue) { - this.applyGridFilter(cmp, 'username'); - }, - scope: this - }, - afterrender: { - fn: function(cmp) { - if (MODx.request.query) { - this.applyGridFilter(cmp, 'username'); - } - }, - scope: this - }, - render: { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER, - fn: this.blur, - scope: cmp - }); - } - ,scope: this - } - } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.clearGridFilters('filter-username'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - }] + ,tbar: [ + { + text: _('user_group_update') + ,cls: 'primary-button' + ,handler: this.updateUserGroup + ,hidden: (MODx.perm.usergroup_edit == 0 || config.ownerCt.id != 'modx-tree-panel-usergroup') + },{ + text: _('user_group_user_add') + ,cls: 'primary-button' + ,handler: this.addUser + ,hidden: MODx.perm.usergroup_user_edit == 0 + }, + '->', + this.getQueryFilterField('filter-query', 'user-group-users'), + this.getClearFiltersButton() + ] }); MODx.grid.UserGroupUsers.superclass.constructor.call(this,config); this.addEvents('updateRole','addUser'); From 8ad4f5aebe32bef957749904b90198050755b58f Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 22:41:34 -0500 Subject: [PATCH 26/33] Filter Persistence: Template TVs Grid Also fixes processor so the grid total count is always correct, making the paging correct and navigable. --- .../Element/Template/TemplateVar/GetList.php | 97 +++++++++---- .../widgets/element/modx.grid.template.tv.js | 129 +++++------------- 2 files changed, 105 insertions(+), 121 deletions(-) diff --git a/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php b/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php index 459fe34e6dc..b32d6c4d63e 100644 --- a/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php +++ b/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php @@ -1,4 +1,5 @@ true, 'view_template' => true]; public $languageTopics = ['template']; + protected $category = 0; + protected $query = ''; + protected $isFiltered = false; + + /** + * {@inheritDoc} + */ + public function initialize() + { + $this->category = (int)$this->getProperty('category', 0); + $this->query = $this->getProperty('query', ''); + $this->isFiltered = $this->category > 0 || $this->query; + return parent::initialize(); + } + /** * Prepare conditions for TV list - * - * @return array */ - public function prepareConditions() + public function prepareConditions(): array { $conditions = []; - $category = (integer)$this->getProperty('category', 0); - if ($category) { - $conditions[] = ['category' => $category]; + if (!$this->isFiltered) { + return $conditions; + } + + if ($this->category) { + $conditions[] = ['category' => $this->category]; } - $query = $this->getProperty('query', ''); - if (!empty($query)) { + if (!empty($this->query)) { $conditions[] = [ - 'name:LIKE' => '%' . $query . '%', - 'OR:caption:LIKE' => '%' . $query . '%', - 'OR:description:LIKE' => '%' . $query . '%', + 'name:LIKE' => '%' . $this->query . '%', + 'OR:caption:LIKE' => '%' . $this->query . '%', + 'OR:description:LIKE' => '%' . $this->query . '%' ]; } @@ -79,21 +96,21 @@ public function loadTemplate() } /** - * {@inheritdoc} - * @return array + * {@inheritDoc} */ - public function getData() + public function getData(): array { $sort = $this->getProperty('sort'); $dir = $this->getProperty('dir'); - $limit = intval($this->getProperty('limit')); - $start = intval($this->getProperty('start')); + $limit = (int)$this->getProperty('limit'); + $start = (int)$this->getProperty('start'); $conditions = $this->prepareConditions(); $template = $this->loadTemplate(); $tvList = $template->getTemplateVarList([$sort => $dir], $limit, $start, $conditions); + $data = [ - 'total' => $tvList['total'], + 'total' => $this->isFiltered ? $this->getFilteredCount() : $tvList['total'], 'results' => $tvList['collection'], ]; @@ -101,15 +118,49 @@ public function getData() } /** - * {@inheritdoc} - * @param xPDOObject $object - * - * @return array|mixed + * Workaround to get correct total count when list is filtered + */ + public function getFilteredCount(): int + { + $c = $this->modx->newQuery(modTemplateVar::class); + $c = $this->prepareQueryBeforeCount($c); + $filteredCount = $this->modx->getCount(modTemplateVar::class, $c); + + return $filteredCount; + } + + /** + * {@inheritDoc} + */ + public function prepareQueryBeforeCount(xPDOQuery $c) + { + if (!$this->isFiltered) { + return $c; + } + + if ($this->category) { + $c->where( + ['category' => $this->category] + ); + } + + if ($this->query) { + $c->where([ + 'name:LIKE' => '%' . $this->query . '%', + 'OR:caption:LIKE' => '%' . $this->query . '%', + 'OR:description:LIKE' => '%' . $this->query . '%', + ]); + } + return $c; + } + + /** + * {@inheritDoc} */ public function prepareRow(xPDOObject $object) { $tvArray = $object->get(['id', 'name', 'caption', 'tv_rank', 'category_name']); - $tvArray['access'] = (boolean)$object->get('access'); + $tvArray['access'] = (bool)$object->get('access'); $tvArray['perm'] = []; if ($this->modx->hasPermission('edit_tv')) { diff --git a/manager/assets/modext/widgets/element/modx.grid.template.tv.js b/manager/assets/modext/widgets/element/modx.grid.template.tv.js index e3f3129e13a..d653e2fa39b 100644 --- a/manager/assets/modext/widgets/element/modx.grid.template.tv.js +++ b/manager/assets/modext/widgets/element/modx.grid.template.tv.js @@ -6,9 +6,8 @@ * @param {Object} config An object of options. * @xtype modx-grid-template-tv */ -MODx.grid.TemplateTV = function(config) { - config = config || {}; - var tt = new Ext.ux.grid.CheckColumn({ +MODx.grid.TemplateTV = function(config = {}) { + const tt = new Ext.ux.grid.CheckColumn({ header: _('access') ,dataIndex: 'access' ,width: 70 @@ -18,13 +17,21 @@ MODx.grid.TemplateTV = function(config) { title: _('template_assignedtv_tab') ,id: 'modx-grid-template-tv' ,url: MODx.config.connector_url - ,fields: ['id','name','caption','tv_rank','access','perm','category_name','category'] + ,fields: [ + 'id', + 'name', + 'caption', + 'tv_rank', + 'access', + 'perm', + 'category_name', + 'category' + ] ,baseParams: { action: 'Element/Template/TemplateVar/GetList' ,template: config.template ,sort: 'tv_rank' - ,category: MODx.request.category || '' - ,query: MODx.request.query ? decodeURIComponent(MODx.request.query) : '' + ,category: MODx.request.category || null } ,saveParams: { template: config.template @@ -76,72 +83,26 @@ MODx.grid.TemplateTV = function(config) { ,editor: { xtype: 'textfield' ,allowBlank: false } ,sortable: true }] - ,tbar: ['->',{ - xtype: 'modx-combo-category' - ,name: 'filter_category' - ,hiddenName: 'filter_category' - ,id: 'modx-temptv-filter-category' - ,emptyText: _('filter_by_category') - ,value: MODx.request.category || '' - ,allowBlank: true - ,width: 150 - ,listeners: { - 'select': { - fn: function (cb, rec, ri) { - if (!MODx.request.query) { - this.filterByCategory(cb, rec, ri); - } - } - ,scope: this - } - } - },{ - xtype: 'textfield' - ,name: 'query' - ,id: 'modx-temptv-query' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.query ? decodeURIComponent(MODx.request.query) : '' - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.tvSearch(cb, rec, ri); - } - ,scope: this - } - ,'afterrender': { - fn: function (cb) { - if (MODx.request.query) { - this.tvSearch(cb, cb.value); - MODx.request.query = ''; - } + ,tbar: [ + '->', + { + xtype: 'modx-combo-category' + ,itemId: 'filter-category' + ,emptyText: _('filter_by_category') + ,value: MODx.request.category || null + ,width: 200 + ,listeners: { + select: { + fn: function (cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'category'); + }, + scope: this } - ,scope: this - } - ,'render': { - fn: function (cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } - ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); } - } - } - }] + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-category, filter-query') + ] }); MODx.grid.TemplateTV.superclass.constructor.call(this,config); this.on('render', this.prepareDDSort, this); @@ -191,34 +152,6 @@ Ext.extend(MODx.grid.TemplateTV,MODx.grid.Grid,{ }, this); } - ,filterByCategory: function (cb, newValue, ov) { - var nv = Ext.isEmpty(newValue) || Ext.isObject(newValue) ? cb.getValue() : newValue; - var s = this.getStore(); - s.baseParams.category = nv; - s.load(); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,tvSearch: function(tf, newValue, ov) { - var nv = newValue || tf; - var s = this.getStore(); - s.baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - s.load(); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - s.baseParams.category = s.baseParams.query = ''; - Ext.getCmp('modx-temptv-filter-category').setValue(''); - Ext.getCmp('modx-temptv-query').setValue(''); - s.load(); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - ,prepareDDSort: function(grid) { this.dropTarget = new Ext.dd.DropTarget(grid.getView().mainBody, { ddGroup: 'template-tvs-ddsort' From dad52e3bff5f75111756c645b16612596b7644fb Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 22:42:19 -0500 Subject: [PATCH 27/33] Filter Persistence: TV Templates Grid --- .../Element/TemplateVar/Template/GetList.php | 15 ++- .../widgets/element/modx.grid.tv.template.js | 94 ++++++------------- 2 files changed, 41 insertions(+), 68 deletions(-) diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php b/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php index c564e59c485..0b995785b5d 100644 --- a/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php +++ b/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php @@ -1,4 +1,5 @@ modx->newQuery(modTemplate::class); - $query = $this->getProperty('query'); + $query = $this->getProperty('query', ''); if (!empty($query)) { $c->where([ - 'templatename:LIKE' => "%$query%", + 'templatename:LIKE' => '%' . $query . '%', + 'OR:description:LIKE' => '%' . $query . '%' ]); } $c->leftJoin(modCategory::class, 'Category'); @@ -107,8 +108,12 @@ public function getData() $c->select([ 'category_name' => 'Category.category', ]); - $c->select($this->modx->getSelectColumns(modTemplateVarTemplate::class, 'TemplateVarTemplates', '', - ['tmplvarid'])); + $c->select($this->modx->getSelectColumns( + modTemplateVarTemplate::class, + 'TemplateVarTemplates', + '', + ['tmplvarid'] + )); $c->select(['access' => 'TemplateVarTemplates.tmplvarid']); $c->sortby($this->getProperty('sort'), $this->getProperty('dir')); if ($isLimit) { diff --git a/manager/assets/modext/widgets/element/modx.grid.tv.template.js b/manager/assets/modext/widgets/element/modx.grid.tv.template.js index 56c55aaf885..25194e84de9 100644 --- a/manager/assets/modext/widgets/element/modx.grid.tv.template.js +++ b/manager/assets/modext/widgets/element/modx.grid.tv.template.js @@ -11,17 +11,26 @@ MODx.grid.TemplateVarTemplate = function(config) { var tt = new Ext.ux.grid.CheckColumn({ header: _('access') ,dataIndex: 'access' - ,width: 50 + ,width: 60 ,sortable: true }); Ext.applyIf(config,{ id: 'modx-grid-tv-template' ,url: MODx.config.connector_url - ,fields: ['id','templatename','category','category_name','description','access','menu'] + ,fields: [ + 'id', + 'templatename', + 'category', + 'category_name', + 'description', + 'access', + 'menu' + ] ,showActionsColumn: false ,baseParams: { action: 'Element/TemplateVar/Template/GetList' ,tv: config.tv + ,category: MODx.request.category || null } ,saveParams: { tv: config.tv @@ -50,69 +59,28 @@ MODx.grid.TemplateVarTemplate = function(config) { ,dataIndex: 'description' ,width: 300 },tt] - ,tbar: ['->',{ - xtype: 'modx-combo-category' - ,name: 'filter_category' - ,hiddenName: 'filter_category' - ,id: 'modx-tvtemp-filter-category' - ,emptyText: _('filter_by_category') - ,value: '' - ,allowBlank: true - ,width: 150 - ,listeners: { - 'select': {fn: this.filterByCategory, scope:this} - } - },'-',{ - xtype: 'textfield' - ,name: 'query' - ,id: 'modx-tvtemp-search' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); + ,tbar: [ + '->', + { + xtype: 'modx-combo-category' + ,itemId: 'filter-category' + ,emptyText: _('filter_by_category') + ,value: MODx.request.category || null + ,width: 200 + ,listeners: { + select: { + fn: function (cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'category'); + }, + scope: this + } } - } - } - }] + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-category, filter-query') + ] }); MODx.grid.TemplateVarTemplate.superclass.constructor.call(this,config); }; -Ext.extend(MODx.grid.TemplateVarTemplate,MODx.grid.Grid,{ - filterByCategory: function(cb,rec,ri) { - this.getStore().baseParams['category'] = cb.getValue(); - this.getBottomToolbar().changePage(1); - } - - ,search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - Ext.getCmp('modx-tvtemp-filter-category').setValue(''); - this.getBottomToolbar().changePage(1); - return true; - } - - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'Element/TemplateVar/Template/GetList' - }; - Ext.getCmp('modx-tvtemp-filter-category').reset(); - Ext.getCmp('modx-tvtemp-search').setValue(''); - this.getBottomToolbar().changePage(1); - } -}); +Ext.extend(MODx.grid.TemplateVarTemplate,MODx.grid.Grid); Ext.reg('modx-grid-tv-template',MODx.grid.TemplateVarTemplate); From c1c704a5ec73b592c901964199b62fb405aefc23 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 1 Feb 2023 22:28:19 -0500 Subject: [PATCH 28/33] Filter Persistence: User Messages Grid --- .../Processors/Security/Message/GetList.php | 22 +-- .../widgets/security/modx.grid.message.js | 148 +++++++++--------- 2 files changed, 74 insertions(+), 96 deletions(-) diff --git a/core/src/Revolution/Processors/Security/Message/GetList.php b/core/src/Revolution/Processors/Security/Message/GetList.php index 6760d6d673e..4746f3e842e 100644 --- a/core/src/Revolution/Processors/Security/Message/GetList.php +++ b/core/src/Revolution/Processors/Security/Message/GetList.php @@ -32,20 +32,6 @@ class GetList extends GetListProcessor public $permission = 'messages'; public $defaultSortField = 'date_sent'; - /** - * @return bool - */ - public function initialize() - { - $initialized = parent::initialize(); - $this->setDefaultProperties([ - 'search' => '', - 'type' => 'inbox' - ]); - - return $initialized; - } - /** * @param xPDOQuery $c * @return xPDOQuery @@ -68,11 +54,11 @@ public function prepareQueryBeforeCount(xPDOQuery $c) } $c->where($where); - $search = $this->getProperty('search', ''); - if (!empty($search)) { + $query = $this->getProperty('query', ''); + if (!empty($query)) { $c->andCondition([ - 'subject:LIKE' => '%' . $search . '%', - 'OR:message:LIKE' => '%' . $search . '%', + 'subject:LIKE' => '%' . $query . '%', + 'OR:message:LIKE' => '%' . $query . '%', ], null, 2); } diff --git a/manager/assets/modext/widgets/security/modx.grid.message.js b/manager/assets/modext/widgets/security/modx.grid.message.js index 28d80e7db8d..f2f77450b92 100644 --- a/manager/assets/modext/widgets/security/modx.grid.message.js +++ b/manager/assets/modext/widgets/security/modx.grid.message.js @@ -6,8 +6,7 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-messages */ -MODx.panel.Messages = function(config) { - config = config || {}; +MODx.panel.Messages = function(config = {}) { Ext.applyIf(config,{ id: 'modx-panel-message' ,cls: 'container' @@ -57,9 +56,7 @@ Ext.reg('modx-panel-messages',MODx.panel.Messages); * @param {Object} config An object of options. * @xtype modx-grid-message */ -MODx.grid.Message = function(config) { - config = config || {}; - +MODx.grid.Message = function(config = {}) { this.exp = new Ext.grid.RowExpander({ tpl : new Ext.Template( '' @@ -81,10 +78,22 @@ MODx.grid.Message = function(config) { ,id: 'modx-grid-message' ,url: MODx.config.connector_url ,baseParams: { - action: 'Security/Message/GetList' + action: 'Security/Message/GetList', + type: MODx.request.type || null } - ,fields: ['id','type','subject','message','sender','recipient','private' - ,'date_sent','read','sender_name','recipient_name'] + ,fields: [ + 'id', + 'type', + 'subject', + 'message', + 'sender', + 'recipient', + 'private', + 'date_sent', + 'read', + 'sender_name', + 'recipient_name' + ] ,autosave: true ,paging: true ,plugins: this.exp @@ -115,57 +124,60 @@ MODx.grid.Message = function(config) { header: _('read') ,dataIndex: 'read' ,width: 100 - ,editor: { xtype: 'combo-boolean' ,renderer: 'boolean' } + ,editor: { + xtype: 'combo-boolean', + renderer: 'boolean' + } ,editable: false }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,disabled: disabled - ,scope: this - ,handler: this.newMessage - },'->',{ - xtype: 'modx-combo-message-type' - ,name: 'type' - ,id: 'modx-messages-filter' - ,emptyText: _('filter_by_type') - ,allowBlank: false - ,editable: false - ,typeAhead: false - ,forceSelection: true - ,width: 200 - ,listeners: { - 'select': {fn: this.filterByType, scope: this} - } - },{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-messages-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } + ,tbar: [ + { + text: _('create') + ,cls: 'primary-button' + ,disabled: disabled + ,scope: this + ,handler: this.newMessage + }, + '->', + { + xtype: 'modx-combo-message-type' + ,name: 'type' + ,itemId: 'filter-type' + ,emptyText: _('filter_by_type') + ,width: 200 + ,value: MODx.request.type || null + ,listeners: { + render: { + fn: function(cmp) { + // Maintain default type in URL and in this combo when loading this combo and when clearing all filters + const clearFiltersButton = cmp.ownerCt.getComponent('filter-clear'), + resetDefaults = () => { + MODx.util.url.setParams({ type: 'inbox' }); + cmp.setValue('inbox'); + } + ; + if (!MODx.request.type) { + resetDefaults(); + } + if (clearFiltersButton) { + clearFiltersButton.on('click', button => { + resetDefaults(); + }); + } + }, + scope: this + }, + select: { + fn: function(cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'type'); + }, + scope: this + } } - } - }] + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-type, filter-query') + ] }); MODx.grid.Message.superclass.constructor.call(this,config); }; @@ -264,24 +276,6 @@ Ext.extend(MODx.grid.Message,MODx.grid.Grid,{ xtype: 'modx-window-message-create' }); } - ,filterByType: function (combo) { - this.getStore().baseParams.type = combo.getValue(); - this.getBottomToolbar().changePage(1); - } - ,search: function(tf,newValue) { - var nv = newValue || tf; - this.getStore().baseParams.search = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'Security/Message/GetList' - }; - Ext.getCmp('modx-messages-search').reset(); - Ext.getCmp('modx-messages-filter').reset(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-message',MODx.grid.Message); @@ -293,8 +287,7 @@ Ext.reg('modx-grid-message',MODx.grid.Message); * @param {Object} config An object of options. * @xtype modx-window-message-create */ -MODx.window.CreateMessage = function(config) { - config = config || {}; +MODx.window.CreateMessage = function(config = {}) { Ext.applyIf(config,{ title: _('create') ,url: MODx.config.connector_url @@ -415,8 +408,7 @@ Ext.reg('modx-window-message-create',MODx.window.CreateMessage); * @param {Object} config An object of options. * @xtype modx-combo-message-type */ -MODx.combo.MessageType = function(config) { - config = config || {}; +MODx.combo.MessageType = function(config = {}) { Ext.applyIf(config,{ store: new Ext.data.SimpleStore({ fields: ['d', 'v'], From 8e9de6b52bea0da039e2171f86423b10db2a0fd7 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 16 Feb 2023 23:00:12 -0500 Subject: [PATCH 29/33] Filter Persistence: Settings Grids (4) Includes system settings, usergroup settings, context settings, and user settings. Also adds new method to utilities and makes minor updates for the base grid class. --- .../Processors/System/Settings/GetAreas.php | 85 ++++- .../Workspace/PackageNamespace/GetList.php | 86 ++++- manager/assets/modext/util/utilities.js | 34 +- .../assets/modext/widgets/core/modx.combo.js | 2 +- .../assets/modext/widgets/core/modx.grid.js | 28 +- .../modext/widgets/core/modx.grid.settings.js | 316 +++++++++--------- .../security/modx.grid.user.group.settings.js | 51 +-- .../security/modx.grid.user.settings.js | 51 +-- .../system/modx.grid.context.settings.js | 55 +-- .../system/modx.panel.system.settings.js | 20 +- 10 files changed, 442 insertions(+), 286 deletions(-) diff --git a/core/src/Revolution/Processors/System/Settings/GetAreas.php b/core/src/Revolution/Processors/System/Settings/GetAreas.php index a9268e60b97..9a27ee521f5 100644 --- a/core/src/Revolution/Processors/System/Settings/GetAreas.php +++ b/core/src/Revolution/Processors/System/Settings/GetAreas.php @@ -11,7 +11,10 @@ namespace MODX\Revolution\Processors\System\Settings; use MODX\Revolution\Processors\Processor; +use MODX\Revolution\modContextSetting; use MODX\Revolution\modSystemSetting; +use MODX\Revolution\modUserGroupSetting; +use MODX\Revolution\modUserSetting; use PDO; use xPDO\Om\xPDOQuery; @@ -26,6 +29,9 @@ class GetAreas extends Processor { public $permission = 'settings'; + /** @param boolean $isGridFilter Indicates the target of this list data is a filter field */ + protected $isGridFilter = false; + /** * @return mixed */ @@ -48,9 +54,9 @@ public function getLanguageTopics() public function initialize() { $this->setDefaultProperties([ - 'dir' => 'ASC', - 'namespace' => 'core', + 'query' => '' ]); + $this->isGridFilter = $this->getProperty('isGridFilter', false); return true; } @@ -108,31 +114,76 @@ public function process() */ public function getQuery() { - $namespace = $this->getProperty('namespace', 'core'); - $query = $this->getProperty('query'); + $alias = 'settingsArea'; + $aliasEscaped = $this->modx->escape($alias); + $settingsClass = modSystemSetting::class; + + // $foreignKey is the primary key of the child settings entity (e.g., user, usergroup, context) + $foreignKey = $this->getProperty('foreignKey', ''); + $foreignKeyWhere = null; - $c = $this->modx->newQuery(modSystemSetting::class); - $c->setClassAlias('settingsArea'); - $c->leftJoin(modSystemSetting::class, 'settingsCount', [ - 'settingsArea.' . $this->modx->escape('key') . ' = settingsCount.' . $this->modx->escape('key'), + /* + When this class is used to fetch data for a grid filter's store (combo), + limit results to only those areas present in the current grid. + */ + if ($this->isGridFilter && $this->getProperty('targetGrid', false) === 'MODx.grid.SettingsGrid') { + $settingsType = $this->getProperty('targetSettingsType', 'system'); + switch($settingsType) { + case 'context': + $settingsClass = modContextSetting::class; + $foreignKeyWhere = $foreignKey + ? [ $aliasEscaped . '.' . $this->modx->escape('context_key') => $this->modx->sanitizeString($foreignKey) ] + : null + ; + break; + case 'group': + $settingsClass = modUserGroupSetting::class; + $foreignKeyWhere = $foreignKey + ? [ $aliasEscaped . '.' . $this->modx->escape('group') => (int)$foreignKey ] + : null + ; + break; + case 'user': + $settingsClass = modUserSetting::class; + $foreignKeyWhere = $foreignKey + ? [ $aliasEscaped . '.' . $this->modx->escape('user') => (int)$foreignKey ] + : null + ; + break; + // no default + } + } + $areaColumn = $this->modx->escape('area'); + $keyColumn = $this->modx->escape('key'); + $namespaceColumn = $this->modx->escape('namespace'); + $joinAlias = 'settingsCount'; + $joinAliasEscaped = $this->modx->escape($joinAlias); + + $c = $this->modx->newQuery($settingsClass); + $c->setClassAlias($alias); + $c->leftJoin($settingsClass, $joinAlias, [ + "{$aliasEscaped}.{$keyColumn} = {$joinAliasEscaped}.{$keyColumn}" ]); - if (!empty($namespace)) { + if ($namespace = $this->getProperty('namespace', false)) { $c->where([ - 'settingsArea.namespace' => $namespace, + "{$aliasEscaped}.{$namespaceColumn}" => $namespace ]); } - if (!empty($query)) { + if ($query = $this->getProperty('query', '')) { $c->where([ - 'settingsArea.area:LIKE' => "%{$query}%", + "{$aliasEscaped}.{$areaColumn}:LIKE" => "%{$query}%" ]); } + if ($foreignKeyWhere) { + $c->where($foreignKeyWhere); + } $c->select([ - 'settingsArea.' . $this->modx->escape('area'), - 'settingsArea.' . $this->modx->escape('namespace'), - 'COUNT(settingsCount.' . $this->modx->escape('key') . ') AS num_settings', + "{$aliasEscaped}.{$areaColumn}", + "{$aliasEscaped}.{$namespaceColumn}", + "COUNT({$joinAliasEscaped}.{$keyColumn}) AS num_settings", ]); - $c->groupby('settingsArea.' . $this->modx->escape('area') . ', settingsArea.' . $this->modx->escape('namespace')); - $c->sortby($this->modx->escape('area'), $this->getProperty('dir', 'ASC')); + $c->groupby("{$aliasEscaped}.{$areaColumn}, {$aliasEscaped}.{$namespaceColumn}"); + $c->sortby($areaColumn, $this->getProperty('dir', 'ASC')); return $c; } } diff --git a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php index 1ae9c9216c0..24c60d2f07e 100644 --- a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php +++ b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php @@ -18,6 +18,11 @@ use xPDO\Om\xPDOObject; use xPDO\Om\xPDOQuery; +use MODX\Revolution\modContextSetting; +use MODX\Revolution\modSystemSetting; +use MODX\Revolution\modUserGroupSetting; +use MODX\Revolution\modUserSetting; + /** * Gets a list of namespaces * @param string $name (optional) If set, will search by name @@ -64,26 +69,77 @@ public function prepareQueryBeforeCount(xPDOQuery $c) 'OR:path:LIKE' => '%' . $query . '%', ]); } + + // $foreignKey is the primary key of the child settings entity (e.g., user, usergroup, context) + $foreignKey = $this->getProperty('foreignKey', ''); + $foreignKeyWhere = null; + /* When this class is used to fetch data for a grid filter's store (combo), limit results to only those namespaces present in the current grid. */ - if ($this->isGridFilter) { - if ($userGroup = $this->getProperty('usergroup', false)) { - $c->innerJoin( - modAccessNamespace::class, - 'modAccessNamespace', - [ - '`modAccessNamespace`.`target` = `modNamespace`.`name`', - '`modAccessNamespace`.`principal` = ' . (int)$userGroup, - '`modAccessNamespace`.`principal_class` = ' . $this->modx->quote(modUserGroup::class) - ] - ); - if ($policy = $this->getProperty('policy', false)) { - $c->where([ - '`modAccessNamespace`.`policy`' => (int)$policy + if ($this->isGridFilter && $targetGrid = $this->getProperty('targetGrid', false)) { + switch($targetGrid) { + case 'MODx.grid.SettingsGrid': + $settingsType = $this->getProperty('targetSettingsType', 'system'); + $alias = 'settingsNamespace'; + switch($settingsType) { + case 'context': + $settingsClass = modContextSetting::class; + $foreignKeyWhere = $foreignKey ? [ $alias . '.context_key' => $this->modx->sanitizeString($foreignKey) ] : null ; + break; + case 'group': + $settingsClass = modUserGroupSetting::class; + $foreignKeyWhere = $foreignKey ? [ $alias . '.group' => (int)$foreignKey ] : null ; + break; + case 'system': + $settingsClass = modSystemSetting::class; + break; + case 'user': + $settingsClass = modUserSetting::class; + $foreignKeyWhere = $foreignKey ? [ $alias . '.user' => (int)$foreignKey ] : null ; + break; + // no default + } + + $nsSubquery = $this->modx->newQuery($settingsClass); + $nsSubquery->setClassAlias($alias); + $nsSubquery->select([ + 'namespaces' => "GROUP_CONCAT(DISTINCT `{$alias}`.`namespace` SEPARATOR '\",\"')" ]); - } + if ($area = $this->getProperty('area', false)) { + $nsSubquery->where([ + "`{$alias}`.`area`" => $area + ]); + } + if ($foreignKeyWhere) { + $nsSubquery->where($foreignKeyWhere); + } + $namespaces = $this->modx->getObject($settingsClass, $nsSubquery)->get('namespaces'); + + $c->where( + "`{$c->getAlias()}`.`name` IN (\"{$namespaces}\")" + ); + break; + case 'MODx.grid.UserGroupNamespace': + if ($userGroup = $this->getProperty('usergroup', false)) { + $c->innerJoin( + modAccessNamespace::class, + 'modAccessNamespace', + [ + '`modAccessNamespace`.`target` = `modNamespace`.`name`', + '`modAccessNamespace`.`principal` = ' . (int)$userGroup, + '`modAccessNamespace`.`principal_class` = ' . $this->modx->quote(modUserGroup::class) + ] + ); + if ($policy = $this->getProperty('policy', false)) { + $c->where([ + '`modAccessNamespace`.`policy`' => (int)$policy + ]); + } + } + break; + // no default } } return $c; diff --git a/manager/assets/modext/util/utilities.js b/manager/assets/modext/util/utilities.js index 6d85a1d8289..0712d7b6eb7 100644 --- a/manager/assets/modext/util/utilities.js +++ b/manager/assets/modext/util/utilities.js @@ -581,17 +581,18 @@ MODx.util.url = { */ clearParam: function(reference, referenceIsComponent = true, stateData = {}) { if (typeof window.history.replaceState !== 'undefined') { - const urlParts = window.location.href.split('?'), - removeParamName = referenceIsComponent ? this.getParamNameFromCmp(reference) : reference.trim(), - regex = new RegExp(`${removeParamName}=[^&]+`, 'i') + let url = new URL(window.location.href), + removeParamName ; - let params = urlParts[1].replace(regex, '').replace('&&', '&'); - if (params.endsWith('&')) { - params = params.substr(0, params.length - 1); + if (referenceIsComponent) { + removeParamName = this.getParamNameFromCmp(reference); + removeParamName = removeParamName === 'namespace' ? 'ns' : removeParamName; + } else { + removeParamName = reference.trim(); } - let newUrl = new URL(`${urlParts[0]}?${params}`); - newUrl = newUrl.toString().replace(/%2F/g, '/'); - window.history.replaceState(stateData, document.title, newUrl); + url.searchParams.delete(removeParamName); + url = url.toString().replace(/%2F/g, '/'); + window.history.replaceState(stateData, document.title, url); } }, /** @@ -606,6 +607,21 @@ MODx.util.url = { const param = cmp.itemId.split('-')[1]; return param === 'ns' ? 'namespace' : param ; }, + /** + * @property {Function} getParamValue - Fetch and decode given parameter value from a request + * + * @param {String} param - The url query parameter + * @param {Boolean} setEmptyToString - For some components, like combos, setting to null is better + * when no value is present. Set this to true for components that prefer an empty string + * @return {Mixed} + */ + getParamValue: function(param, setEmptyToString = false) { + const + key = param === 'namespace' ? 'ns' : param, + emptyValue = setEmptyToString ? '' : null + ; + return MODx.request[key] ? this.decodeParamValue(MODx.request[key]) : emptyValue ; + }, /** * @property {Function} decodeParamValue - Decodes a given parameter's value * diff --git a/manager/assets/modext/widgets/core/modx.combo.js b/manager/assets/modext/widgets/core/modx.combo.js index 9acf73a08b8..fb63f9b9ffd 100644 --- a/manager/assets/modext/widgets/core/modx.combo.js +++ b/manager/assets/modext/widgets/core/modx.combo.js @@ -621,7 +621,7 @@ MODx.combo.Namespace = function(config) { ,hiddenName: 'namespace' ,typeAhead: true ,minChars: 1 - ,queryParam: 'search' + ,queryParam: 'query' ,editable: true ,allowBlank: true ,preselectValue: false diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js index 1e8cd0fa91a..21f95c376db 100644 --- a/manager/assets/modext/widgets/core/modx.grid.js +++ b/manager/assets/modext/widgets/core/modx.grid.js @@ -108,16 +108,16 @@ MODx.grid.Grid = function(config) { if (config.columns && Array.isArray(config.columns)) { if (config.actionsColumnWidth === undefined) { - if (isPercentage(config.columns)) { defaultActionsColumnWidth = 0.1; } } config.columns.push({ - width: config.actionsColumnWidth || defaultActionsColumnWidth - ,menuDisabled: true - ,renderer: this.actionsColumnRenderer.bind(this) + width: config.actionsColumnWidth || defaultActionsColumnWidth, + menuDisabled: true, + hideable: false, + renderer: this.actionsColumnRenderer.bind(this) }); } @@ -129,9 +129,9 @@ MODx.grid.Grid = function(config) { } config.cm.columns.push({ - width: config.actionsColumnWidth || defaultActionsColumnWidth - ,menuDisabled: true - ,renderer: this.actionsColumnRenderer.bind(this) + width: config.actionsColumnWidth || defaultActionsColumnWidth, + menuDisabled: true, + renderer: this.actionsColumnRenderer.bind(this) }); } } @@ -950,15 +950,19 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ bottomToolbar = this.getBottomToolbar(), data = Array.isArray(items) ? items : items.split(',') ; - data.forEach(item => { - itemData = item.replace(/\s+/g, '').split(':'); - const itemId = itemData[0], + data.forEach(item => {; + const itemData = item.replace(/\s+/g, '').split(':'), + itemId = itemData[0], itemDefaultVal = itemData.length == 2 ? itemData[1] : null , cmp = this.getFilterComponent(itemId), param = MODx.util.url.getParamNameFromCmp(cmp) ; if (cmp.xtype.includes('combo')) { - cmp.setValue(itemDefaultVal); + if (itemDefaultVal === '') { + cmp.setValue(null); + } else { + cmp.setValue(itemDefaultVal); + } } else { cmp.setValue(''); } @@ -1081,7 +1085,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ fn: function(cmp, newValue, oldValue) { this.applyGridFilter(cmp); const usergroupTree = Ext.getCmp('modx-tree-usergroup') - if (implementation == 'user-group-users' && usergroupTree) { + if (implementation === 'user-group-users' && usergroupTree) { /* When the user group users grid is shown in the primary ACLs panel, having the user groups tree along with a corresponding grid, the diff --git a/manager/assets/modext/widgets/core/modx.grid.settings.js b/manager/assets/modext/widgets/core/modx.grid.settings.js index 8bbfbcbf0a1..8a5f7c58e7e 100644 --- a/manager/assets/modext/widgets/core/modx.grid.settings.js +++ b/manager/assets/modext/widgets/core/modx.grid.settings.js @@ -6,148 +6,129 @@ * @param {Object} config An object of configuration properties * @xtype modx-grid-settings */ -MODx.grid.SettingsGrid = function(config) { - config = config || {}; +MODx.grid.SettingsGrid = function(config = {}) { this.exp = new Ext.grid.RowExpander({ - tpl : new Ext.XTemplate( + tpl: new Ext.XTemplate( '

    {[MODx.util.safeHtml(values.description_trans)]}

    ' ) }); + this.areaFilterValue = MODx.util.url.getParamValue('area'); + this.namespaceFilterValue = MODx.util.url.getParamValue('ns'); if (!config.tbar) { config.tbar = [{ text: _('create') ,scope: this - ,cls:'primary-button' + ,cls: 'primary-button' ,handler: { xtype: 'modx-window-setting-create' ,url: config.url || MODx.config.connector_url ,blankValues: true + ,listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('createSetting', response); + }, + scope: this + } + } } }]; } config.tbar.push( - '->' - ,{ - xtype: 'modx-combo-namespace' - ,itemId: 'filter-ns' - ,emptyText: _('namespace_filter') - ,allowBlank: false - ,editable: true - ,typeAhead: true - ,minChars: 2 - ,forceSelection: true - ,width: 200 - ,value: MODx.request.ns || null - ,listeners: { - select: { - fn: function (cmp, record, selectedIndex) { - this.applyGridFilter(cmp, 'ns'); - const areaCmp = cmp.ownerCt.getComponent('filter-area'); - if (areaCmp) { - this.getAreaFilterOptions(areaCmp); - } + '->', + { + xtype: 'modx-combo-namespace' + ,itemId: 'filter-namespace' + ,emptyText: _('namespace_filter') + ,typeAhead: true + ,minChars: 2 + ,forceSelection: true + ,width: 200 + ,value: MODx.request.ns || null + ,baseParams: { + action: 'Workspace/PackageNamespace/GetList', + area: this.areaFilterValue, + isGridFilter: true, + targetGrid: 'MODx.grid.SettingsGrid', + targetSettingsType: this.getSettingsType(), + foreignKey: config.fk || false + } + ,listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.updateDependentFilter('filter-area', 'namespace', record.data.name); + this.applyGridFilter(cmp, 'ns'); + }, + scope: this }, - scope: this - }, - change: { + change: { // Support typed-in value (where the select event is not triggered) - fn: function (cmp, newValue, oldValue) { - if (newValue != oldValue) { - this.applyGridFilter(cmp, 'ns'); - const areaCmp = cmp.ownerCt.getComponent('filter-area'); - if (areaCmp) { - this.getAreaFilterOptions(areaCmp); + fn: function(cmp, newValue, oldValue) { + /* + Note that clicking the combo trigger when there is no value chosen initially, + (the combo's value is null) but not choosing an item fires change event and + changes the combo's value to an empty string; this can cause issues in some + instances, so explicity reset to null + */ + if (newValue === '') { + cmp.setValue(null); } - } - }, - scope: this + this.updateDependentFilter('filter-area', 'namespace', newValue); + this.applyGridFilter(cmp, 'ns'); + }, + scope: this + } } - } - },{ - xtype: 'modx-combo-area' - ,itemId: 'filter-area' - ,emptyText: _('area_filter') - ,value: MODx.request.area || null - ,baseParams: { - action: 'System/Settings/GetAreas' - ,namespace: MODx.request.ns || null - } - ,width: 250 - ,allowBlank: true - ,editable: true - ,typeAhead: true - ,minChars: 2 - ,forceSelection: true - ,listeners: { - select: { - fn: function (cmp, record, selectedIndex) { - this.applyGridFilter(cmp, 'area'); + }, + { + xtype: 'modx-combo-area' + ,itemId: 'filter-area' + ,emptyText: _('area_filter') + ,value: this.areaFilterValue + ,baseParams: { + action: 'System/Settings/GetAreas', + namespace: MODx.request.ns || null, + isGridFilter: true, + targetGrid: 'MODx.grid.SettingsGrid', + targetSettingsType: this.getSettingsType(), + foreignKey: config.fk || false + } + ,width: 250 + ,allowBlank: true + ,editable: true + ,typeAhead: true + ,minChars: 2 + ,forceSelection: true + ,listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.updateDependentFilter('filter-namespace', 'area', record.data.v); + this.applyGridFilter(cmp, 'area'); + }, + scope: this }, - scope: this - }, - change: { + change: { // Support typed-in value (where the select event is not triggered) - fn: function (cmp, newValue, oldValue) { - if (newValue != oldValue) { + fn: function(cmp, newValue, oldValue) { + // See note in namespace combo above re setting to null + if (newValue === '') { + cmp.setValue(null); + } + this.updateDependentFilter('filter-namespace', 'area', newValue); this.applyGridFilter(cmp, 'area'); - } - }, - scope: this - } - } - },{ - xtype: 'textfield' - ,itemId: 'filter-query' - ,emptyText: _('search') - ,value: MODx.request.query ? decodeURIComponent(MODx.request.query) : '' - ,listeners: { - change: { - fn: function (cmp, newValue, oldValue) { - this.applyGridFilter(cmp); - }, - scope: this - }, - afterrender: { - fn: function(cmp) { - if (MODx.request.query) { - this.applyGridFilter(cmp); - } - }, - scope: this - }, - render: { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER, - fn: this.blur, - scope: cmp - }); - } - ,scope: this - } - } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function(cmp) { - this.clearGridFilters('filter-ns, filter-area, filter-query'); - const areaCmp = cmp.ownerCt.getComponent('filter-area'); - if (areaCmp) { - this.getAreaFilterOptions(areaCmp); - } - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); + }, + scope: this } } - } - }); + }, + this.getQueryFilterField(), + this.getClearFiltersButton( + 'filter-namespace:, filter-area:, filter-query', + 'filter-area:namespace, filter-namespace:area' + ) + ); this.cm = new Ext.grid.ColumnModel({ columns: [this.exp,{ @@ -237,9 +218,9 @@ MODx.grid.SettingsGrid = function(config) { ] ,url: MODx.config.connector_url ,baseParams: { - action: 'System/Settings/GetList' - ,namespace: MODx.request.ns || null - ,area: MODx.request.area || null + action: 'System/Settings/GetList', + namespace: this.namespaceFilterValue, + area: this.areaFilterValue } ,clicksToEdit: 2 ,grouping: true @@ -277,6 +258,28 @@ MODx.grid.SettingsGrid = function(config) { ,scrollOffset: 0 }); MODx.grid.SettingsGrid.superclass.constructor.call(this,config); + this.addEvents('createSetting', 'updateSetting'); + + const gridFilterData = [ + { filterId: 'filter-namespace', dependentParams: ['area'] }, + { filterId: 'filter-area', dependentParams: ['namespace'] } + ]; + + this.on({ + createSetting: function(...args) { + if (args[0].a.response.status === 200) { + this.refreshFilterOptions(gridFilterData); + } + }, + updateSetting: function(...args) { + if (args[0].a.response.status === 200) { + this.refreshFilterOptions(gridFilterData); + } + }, + afterRemoveRow: function() { + this.refreshFilterOptions(gridFilterData); + } + }); // prevents navigation to next cell editor field when pressing the ENTER key this.selModel.onEditorKey = this.selModel.onEditorKey.createInterceptor(function(field, e) { @@ -323,35 +326,28 @@ Ext.extend(MODx.grid.SettingsGrid,MODx.grid.Grid,{ ,removeSetting: function() { return this.remove('setting_remove_confirm', 'System/Settings/Remove'); } - - ,updateSetting: function(btn,e) { - var r = this.menu.record; - r.fk = Ext.isDefined(this.config.fk) ? this.config.fk : 0; - var uss = MODx.load({ - xtype: 'modx-window-setting-update' - ,record: r - ,grid: this - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + /* + TBD: Remove child settings grids' update methods and adjust this base one to accommodate those grids; only difference is the child window configs include and action property. Maybe use createDelegate to pass additional var. + */ + ,updateSetting: function(btn, e) { + const { record } = this.menu; + record.fk = this.config?.fk || 0; + this.windows.updateSetting = MODx.load({ + xtype: 'modx-window-setting-update', + record: record, + grid: this, + listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('updateSetting', response); + }, + scope: this + } } }); - uss.reset(); - uss.setValues(r); - uss.show(e.target); - } - - /** - * @property {Function} getAreaFilterOptions - Builds the dropdown list of Areas based on the namespace - * - * @param {Object} cmp - The Area filter's Ext component - */ - ,getAreaFilterOptions: function(cmp) { - cmp.store.baseParams.namespace = this.getStore().baseParams.namespace; - cmp.store.removeAll(); - cmp.store.load(); - cmp.setValue(''); + this.windows.updateSetting.setValues(record); + this.windows.updateSetting.show(e.target); } ,renderDynField: function(v,md,rec,ri,ci,s,g) { @@ -402,12 +398,21 @@ Ext.extend(MODx.grid.SettingsGrid,MODx.grid.Grid,{ // Return formatted date (server side) return value; } + + /** + * Gets an identifier for the type of setting being worked with; + * expected format: 'modx-grid-[type]-setting' + * + * @return {String} + */ + ,getSettingsType: function() { + const type = Object.getPrototypeOf(this).constructor.xtype; + return type.split('-')[2]; + } }); Ext.reg('modx-grid-settings',MODx.grid.SettingsGrid); - -MODx.combo.Area = function(config) { - config = config || {}; +MODx.combo.Area = function(config = {}) { Ext.applyIf(config,{ name: 'area' ,hiddenName: 'area' @@ -425,8 +430,7 @@ Ext.extend(MODx.combo.Area,MODx.combo.ComboBox); Ext.reg('modx-combo-area',MODx.combo.Area); -MODx.window.CreateSetting = function(config) { - config = config || {}; +MODx.window.CreateSetting = function(config = {}) { config.keyField = config.keyField || {}; Ext.applyIf(config,{ title: _('create') @@ -505,7 +509,7 @@ MODx.window.CreateSetting = function(config) { ,fieldLabel: _('namespace') ,name: 'namespace' ,id: 'modx-cs-namespace' - ,value: config.grid.getTopToolbar().getComponent('filter-ns').getValue() + ,value: config.grid.getTopToolbar().getComponent('filter-namespace').getValue() ,anchor: '100%' },{ xtype: 'label' @@ -541,7 +545,7 @@ MODx.window.CreateSetting = function(config) { this.on('show',function() { this.reset(); this.setValues({ - namespace: config.grid.getTopToolbar().getComponent('filter-ns').value + namespace: config.grid.getTopToolbar().getComponent('filter-namespace').value ,area: config.grid.getTopToolbar().getComponent('filter-area').value }); },this); @@ -550,8 +554,7 @@ Ext.extend(MODx.window.CreateSetting,MODx.Window); Ext.reg('modx-window-setting-create',MODx.window.CreateSetting); -MODx.combo.xType = function(config) { - config = config || {}; +MODx.combo.xType = function(config = {}) { Ext.applyIf(config,{ store: new Ext.data.SimpleStore({ fields: ['d','v'] @@ -591,8 +594,7 @@ Ext.extend(MODx.combo.xType,Ext.form.ComboBox); Ext.reg('modx-combo-xtype-spec',MODx.combo.xType); -MODx.window.UpdateSetting = function(config) { - config = config || {}; +MODx.window.UpdateSetting = function(config = {}) { this.ident = config.ident || 'modx-uss-'+Ext.id(); Ext.applyIf(config,{ title: _('edit') diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js b/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js index bc441fbb53e..07ec14a1444 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js @@ -6,15 +6,16 @@ * @param {Object} config An object of options. * @xtype modx-grid-group-settings */ -MODx.grid.GroupSettings = function(config) { - config = config || {}; +MODx.grid.GroupSettings = function(config = {}) { Ext.applyIf(config,{ title: _('user_group_settings') ,id: 'modx-grid-group-settings' ,url: MODx.config.connector_url ,baseParams: { - action: 'Security/Group/Setting/GetList' - ,group: config.group + action: 'Security/Group/Setting/GetList', + group: config.group, + namespace: MODx.util.url.getParamValue('ns'), + area: MODx.util.url.getParamValue('area') } ,saveParams: { group: config.group @@ -32,6 +33,15 @@ MODx.grid.GroupSettings = function(config) { action: 'Security/Group/Setting/Create' } ,fk: config.group + ,listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('createSetting', response); + }, + scope: this + } + } } }] }); @@ -65,23 +75,26 @@ Ext.extend(MODx.grid.GroupSettings,MODx.grid.SettingsGrid, { } } - ,updateSetting: function(btn,e) { - var r = this.menu.record; - r.fk = Ext.isDefined(this.config.fk) ? this.config.fk : 0; - var uss = MODx.load({ - xtype: 'modx-window-setting-update' - ,action: 'Security/Group/Setting/Update' - ,record: r - ,grid: this - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + ,updateSetting: function(btn, e) { + const { record } = this.menu; + record.fk = this.config?.fk || 0; + this.windows.updateSetting = MODx.load({ + xtype: 'modx-window-setting-update', + action: 'Security/Group/Setting/Update', + record: record, + grid: this, + listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('updateSetting', response); + }, + scope: this + } } }); - uss.reset(); - uss.setValues(r); - uss.show(e.target); + this.windows.updateSetting.setValues(record); + this.windows.updateSetting.show(e.target); } }); Ext.reg('modx-grid-group-settings',MODx.grid.GroupSettings); diff --git a/manager/assets/modext/widgets/security/modx.grid.user.settings.js b/manager/assets/modext/widgets/security/modx.grid.user.settings.js index cfea16bd7c0..8b75005abf0 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.settings.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.settings.js @@ -6,15 +6,16 @@ * @param {Object} config An object of options. * @xtype modx-grid-user-settings */ -MODx.grid.UserSettings = function(config) { - config = config || {}; +MODx.grid.UserSettings = function(config = {}) { Ext.applyIf(config,{ title: _('user_settings') ,id: 'modx-grid-user-settings' ,url: MODx.config.connector_url ,baseParams: { - action: 'Security/User/Setting/GetList' - ,user: config.user + action: 'Security/User/Setting/GetList', + user: config.user, + namespace: MODx.util.url.getParamValue('ns'), + area: MODx.util.url.getParamValue('area') } ,saveParams: { user: config.user @@ -53,6 +54,15 @@ MODx.grid.UserSettings = function(config) { } } ,fk: config.user + ,listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('createSetting', response); + }, + scope: this + } + } } }] }); @@ -86,23 +96,26 @@ Ext.extend(MODx.grid.UserSettings,MODx.grid.SettingsGrid, { } } - ,updateSetting: function(btn,e) { - var r = this.menu.record; - r.fk = Ext.isDefined(this.config.fk) ? this.config.fk : 0; - var uss = MODx.load({ - xtype: 'modx-window-setting-update' - ,action: 'Security/User/Setting/Update' - ,record: r - ,grid: this - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + ,updateSetting: function(btn, e) { + const { record } = this.menu; + record.fk = this.config?.fk || 0; + this.windows.updateSetting = MODx.load({ + xtype: 'modx-window-setting-update', + action: 'Security/User/Setting/Update', + record: record, + grid: this, + listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('updateSetting', response); + }, + scope: this + } } }); - uss.reset(); - uss.setValues(r); - uss.show(e.target); + this.windows.updateSetting.setValues(record); + this.windows.updateSetting.show(e.target); } }); Ext.reg('modx-grid-user-settings',MODx.grid.UserSettings); diff --git a/manager/assets/modext/widgets/system/modx.grid.context.settings.js b/manager/assets/modext/widgets/system/modx.grid.context.settings.js index 31cfa78e324..625188bcb04 100644 --- a/manager/assets/modext/widgets/system/modx.grid.context.settings.js +++ b/manager/assets/modext/widgets/system/modx.grid.context.settings.js @@ -6,15 +6,16 @@ * @param {Object} config An object of options. * @xtype modx-grid-context-settings */ -MODx.grid.ContextSettings = function(config) { - config = config || {}; +MODx.grid.ContextSettings = function(config = {}) { Ext.applyIf(config,{ title: _('context_settings') ,id: 'modx-grid-context-settings' ,url: MODx.config.connector_url ,baseParams: { - action: 'Context/Setting/GetList' - ,context_key: config.context_key + action: 'Context/Setting/GetList', + context_key: config.context_key, + namespace: MODx.util.url.getParamValue('ns'), + area: MODx.util.url.getParamValue('area') } ,saveParams: { context_key: config.context_key @@ -53,38 +54,50 @@ MODx.grid.ContextSettings = function(config) { } } ,fk: config.context_key + ,listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('createSetting', response); + }, + scope: this + } + } } }] }); MODx.grid.ContextSettings.superclass.constructor.call(this,config); }; + Ext.extend(MODx.grid.ContextSettings,MODx.grid.SettingsGrid, { removeSetting: function() { return this.remove('setting_remove_confirm', 'Context/Setting/Remove'); } - ,updateSetting: function(btn,e) { - var r = this.menu.record; - r.fk = Ext.isDefined(this.config.fk) ? this.config.fk : 0; - var uss = MODx.load({ - xtype: 'modx-window-setting-update' - ,action: 'Context/Setting/Update' - ,record: r - ,grid: this - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + + ,updateSetting: function(btn, e) { + const { record } = this.menu; + record.fk = this.config?.fk || 0; + this.windows.updateSetting = MODx.load({ + xtype: 'modx-window-setting-update', + action: 'Context/Setting/Update', + record: record, + grid: this, + listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('updateSetting', response); + }, + scope: this + } } }); - uss.reset(); - uss.setValues(r); - uss.show(e.target); + this.windows.updateSetting.setValues(record); + this.windows.updateSetting.show(e.target); } }); Ext.reg('modx-grid-context-settings',MODx.grid.ContextSettings); - - /** * Update a Context Setting * diff --git a/manager/assets/modext/widgets/system/modx.panel.system.settings.js b/manager/assets/modext/widgets/system/modx.panel.system.settings.js index dd78f4e2e53..d853be2c84f 100644 --- a/manager/assets/modext/widgets/system/modx.panel.system.settings.js +++ b/manager/assets/modext/widgets/system/modx.panel.system.settings.js @@ -12,42 +12,31 @@ MODx.panel.SystemSettings = function(config) { id: 'modx-panel-system-settings' ,cls: 'container' ,bodyStyle: '' - ,defaults: { collapsible: false ,autoHeight: true } + ,defaults: { + collapsible: false, + autoHeight: true + } ,items: [{ html: _('system_settings')+' & '+_('events') ,id: 'modx-system-settings-header' ,xtype: 'modx-header' },MODx.getPageStructure([{ title: _('system_settings') - ,autoHeight: true ,layout: 'form' - ,defaults: { border: false ,msgTarget: 'side' } ,items:[{ - layout: 'form' - ,autoHeight: true - ,defaults: { border: false } - ,items: [{ html: '

    '+_('settings_desc')+'

    ' ,xtype: 'modx-description' },{ xtype: 'modx-grid-system-settings' - ,urlFilters: ['namespace', 'area', 'query'] ,cls: 'main-wrapper' ,preventSaveRefresh: true },{ html: MODx.onSiteSettingsRender }] - }] },{ title: _('system_events') - ,autoHeight: true ,layout: 'form' - ,defaults: { border: false ,msgTarget: 'side' } ,items:[{ - layout: 'form' - ,autoHeight: true - ,defaults: { border: false } - ,items: [{ html: '

    '+_('system_events.desc')+'

    ' ,xtype: 'modx-description' },{ @@ -55,7 +44,6 @@ MODx.panel.SystemSettings = function(config) { ,cls: 'main-wrapper' ,preventSaveRefresh: true }] - }] }],{ id: 'modx-system-settings-tabs' })] From 7c404b45e84bd2a6cd80ad7bf02e85be3f2008d8 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 17 Feb 2023 00:44:56 -0500 Subject: [PATCH 30/33] Filter Persistence: System Events Grid --- .../widgets/system/modx.grid.system.event.js | 78 ++++++------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/manager/assets/modext/widgets/system/modx.grid.system.event.js b/manager/assets/modext/widgets/system/modx.grid.system.event.js index 11afce1ac4c..b53797db675 100644 --- a/manager/assets/modext/widgets/system/modx.grid.system.event.js +++ b/manager/assets/modext/widgets/system/modx.grid.system.event.js @@ -14,7 +14,13 @@ MODx.grid.SystemEvent = function(config) { ,baseParams: { action: 'System/Event/GetList' } - ,fields: ['id','name','service','groupname','plugins'] + ,fields: [ + 'id', + 'name', + 'service', + 'groupname', + 'plugins' + ] ,autosave: true ,paging: true ,clicksToEdit: 2 @@ -41,44 +47,22 @@ MODx.grid.SystemEvent = function(config) { ,width: 150 ,hidden: true }] - ,tbar: [{ - text: _('create') - ,scope: this - ,cls:'primary-button' - ,handler: { - xtype: 'modx-window-events-create-update' - ,url: config.url || MODx.config.connector_url - ,blankValues: true - ,isUpdate: false - } - },'->',{ - xtype: 'textfield' - ,name: 'filter_key' - ,id: 'modx-filter-event' - ,cls: 'x-form-filter' - ,emptyText: _('system_events.search_by_name')+'...' - ,listeners: { - 'change': {fn: this.filterByName, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,tbar: [ + { + text: _('create') + ,scope: this + ,cls:'primary-button' + ,handler: { + xtype: 'modx-window-events-create-update' + ,url: config.url || MODx.config.connector_url + ,blankValues: true + ,isUpdate: false + } + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.SystemEvent.superclass.constructor.call(this,config); }; @@ -94,22 +78,6 @@ Ext.extend(MODx.grid.SystemEvent,MODx.grid.Grid,{ return m; } - ,filterByName: function(tf,newValue,oldValue) { - this.getStore().baseParams.query = newValue; - this.getBottomToolbar().changePage(1); - this.refresh(); - return true; - } - ,clearFilter: function() { - Ext.getCmp('modx-filter-event').reset(); - - this.getStore().baseParams = this.initialConfig.baseParams; - this.getStore().baseParams.query = ''; - - this.getBottomToolbar().changePage(1); - this.refresh(); - } - ,removeEvent: function(btn, e) { MODx.msg.confirm({ title: _('delete') From a4b0438168ea4109d6b640b734a301283272382b Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sun, 19 Feb 2023 15:40:10 -0500 Subject: [PATCH 31/33] Filter Persistence: Lexicons Grid Also provides two new helper methods in the base modLexicon class to improve filtering granularity, as well as a small styling tweak for the top toolbar. --- _build/templates/default/sass/_toolbars.scss | 14 +- .../Processors/System/Language/GetList.php | 10 +- .../Processors/Workspace/Lexicon/GetList.php | 21 +- .../Workspace/Lexicon/Topic/GetList.php | 6 +- .../Workspace/PackageNamespace/GetList.php | 10 + core/src/Revolution/modLexicon.php | 138 +++++- .../modext/workspace/lexicon/lexicon.grid.js | 406 +++++++++--------- 7 files changed, 389 insertions(+), 216 deletions(-) diff --git a/_build/templates/default/sass/_toolbars.scss b/_build/templates/default/sass/_toolbars.scss index 8ba4d969b4b..7bea60a41b3 100644 --- a/_build/templates/default/sass/_toolbars.scss +++ b/_build/templates/default/sass/_toolbars.scss @@ -187,13 +187,25 @@ /* top toolbars */ .x-panel-tbar { overflow: visible; /* prevent cut off box-shadows in FF */ - padding-bottom: 2px; + padding-bottom: 4px; .x-toolbar { /*background-color: #F5F5F5;*/ border: 0; padding: 5px 0; overflow: visible; /* prevent cut off box-shadows in FF */ + + td { + vertical-align: bottom; + } + + input { + &.filter-query { + /* fix positioning issue with query field */ + position: relative; + bottom: -1px; + } + } } } diff --git a/core/src/Revolution/Processors/System/Language/GetList.php b/core/src/Revolution/Processors/System/Language/GetList.php index 85b1953807f..35e26ee0860 100644 --- a/core/src/Revolution/Processors/System/Language/GetList.php +++ b/core/src/Revolution/Processors/System/Language/GetList.php @@ -20,6 +20,9 @@ */ class GetList extends Processor { + /** @param boolean $isGridFilter Indicates the target of this list data is a filter field */ + protected $isGridFilter = false; + /** * @return mixed */ @@ -46,6 +49,7 @@ public function initialize() 'limit' => 0, 'namespace' => 'core', ]); + $this->isGridFilter = $this->getProperty('isGridFilter', false); return true; } @@ -80,7 +84,11 @@ public function getData() $limit = $this->getProperty('limit'); - $data['results'] = $this->modx->lexicon->getLanguageList($this->getProperty('namespace')); + if ($this->isGridFilter && $topic = $this->getProperty('topic', null)) { + $data['results'] = $this->modx->lexicon->getFilterLanguageList($this->getProperty('namespace'), $topic); + } else { + $data['results'] = $this->modx->lexicon->getLanguageList($this->getProperty('namespace')); + } $data['total'] = count($data['results']); // this allows for typeahead filtering in the lexicon topics combobox diff --git a/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php b/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php index 32f4298aef1..f2e874e510f 100644 --- a/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php +++ b/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php @@ -1,4 +1,5 @@ 'en', 'namespace' => 'core', 'topic' => 'default', + 'query' => '' ]); if ($this->getProperty('language') === '') { $this->setProperty('language', 'en'); @@ -77,11 +79,11 @@ public function process() 'language' => $this->getProperty('language'), ]; - $search = $this->getProperty('search'); - if (!empty($search)) { + $query = $this->getProperty('query'); + if (!empty($query)) { $where[] = [ - 'name:LIKE' => '%' . $search . '%', - 'OR:value:LIKE' => '%' . $search . '%', + 'name:LIKE' => '%' . $query . '%', + 'OR:value:LIKE' => '%' . $query . '%', ]; } @@ -97,12 +99,15 @@ public function process() } /* first get file-based lexicon */ - $entries = $this->modx->lexicon->getFileTopic($this->getProperty('language'), $this->getProperty('namespace'), - $this->getProperty('topic')); + $entries = $this->modx->lexicon->getFileTopic( + $this->getProperty('language'), + $this->getProperty('namespace'), + $this->getProperty('topic') + ); $entries = is_array($entries) ? $entries : []; /* if searching */ - if (!empty($search)) { + if (!empty($query)) { function parseArray($needle, array $haystack = []) { if (!is_array($haystack)) { @@ -117,7 +122,7 @@ function parseArray($needle, array $haystack = []) return $results; } - $entries = parseArray($search, $entries); + $entries = parseArray($query, $entries); } /* add in unique entries */ diff --git a/core/src/Revolution/Processors/Workspace/Lexicon/Topic/GetList.php b/core/src/Revolution/Processors/Workspace/Lexicon/Topic/GetList.php index f71350c3fb6..3d4a76c8861 100644 --- a/core/src/Revolution/Processors/Workspace/Lexicon/Topic/GetList.php +++ b/core/src/Revolution/Processors/Workspace/Lexicon/Topic/GetList.php @@ -72,8 +72,10 @@ public function process() public function getData() { $data = []; - $data['results'] = $this->modx->lexicon->getTopicList($this->getProperty('language'), - $this->getProperty('namespace')); + $data['results'] = $this->modx->lexicon->getTopicList( + $this->getProperty('language'), + $this->getProperty('namespace') + ); $data['total'] = count($data['results']); // this allows for typeahead filtering in the lexicon topics combobox diff --git a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php index 24c60d2f07e..78014f07a14 100644 --- a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php +++ b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php @@ -139,6 +139,16 @@ public function prepareQueryBeforeCount(xPDOQuery $c) } } break; + case 'MODx.grid.Lexicon': + $language = $this->getProperty('language', 'en'); + $topic = $this->getProperty('topic', ''); + $namespaces = $this->modx->lexicon->getNamespaceList($language, $topic); + if (!empty($namespaces)) { + $c->where([ + "`{$c->getAlias()}`.`name`:IN" => $namespaces + ]); + } + break; // no default } } diff --git a/core/src/Revolution/modLexicon.php b/core/src/Revolution/modLexicon.php index 9fde50f99d7..9811901c0ce 100644 --- a/core/src/Revolution/modLexicon.php +++ b/core/src/Revolution/modLexicon.php @@ -381,11 +381,71 @@ public function getNamespacePath($namespace = 'core') return $corePath; } + /** + * Get a list of available Namespaces when given a Language and Topic + * + * @param string $language The language to filter by + * @param string $topic The topic to filter by + * + * @return array An array of Namespace names + */ + public function getNamespaceList($language = 'en', $topic = '') : array + { + $namespaceList = []; + if ($namespaces = $this->modx->getCollection(modNamespace::class)) { + foreach($namespaces as $namespace) { + $name = $namespace->get('name'); + $corePath = $name === 'core' + ? $this->modx->getOption('core_path', null, MODX_CORE_PATH) + : $namespace->getCorePath() + ; + $lexiconPath = str_replace('//', '/', $corePath . '/lexicon/' . $language . '/'); + + if (!is_dir($lexiconPath)) { + continue; + } + if ($topic) { + $file = $topic . '.inc.php'; + if (!is_readable($lexiconPath . $file)) { + continue; + } + } + $namespaceList[] = $name; + } + } + $c = $this->modx->newQuery(modLexiconEntry::class, [ + 'language' => $language + ]); + if (!empty($namespaceList)) { + $c->where([ + 'namespace:NOT IN' => $namespaceList + ]); + } + if ($topic) { + $c->where([ + 'topic' => $topic + ]); + } + $c->select(['namespace']); + $c->query['distinct'] = 'DISTINCT'; + if ($c->prepare() && $c->stmt->execute()) { + $entries = $c->stmt->fetchAll(\PDO::FETCH_ASSOC); + if (is_array($entries) and count($entries) > 0) { + foreach ($entries as $v) { + $namespaceList[] = $v['namespace']; + } + } + } + sort($namespaceList); + + return $namespaceList; + } + /** * Get a list of available Topics when given a Language and Namespace. * * @param string $language The language to filter by. - * @param string $namespace The language to filter by. + * @param string $namespace The namespace to filter by. * * @return array An array of Topic names. */ @@ -419,6 +479,7 @@ public function getTopicList($language = 'en', $namespace = 'core') $c->where([ 'namespace' => $namespace, 'topic:NOT IN' => $topics, + 'language' => $language ]); $c->select(['topic']); $c->query['distinct'] = 'DISTINCT'; @@ -506,6 +567,81 @@ public function getLanguageList($namespace = 'core') return $languages; } + /** + * Get a list of available languages for a Namespace and Topic + * + * @param string $namespace The Namespace to filter by + * @param string $topic The Topic to filter by + * + * @return array An array of available languages + */ + public function getFilterLanguageList($namespace = 'core', $topic = '') + { + $corePath = $this->getNamespacePath($namespace); + $lexiconPath = str_replace('//', '/', $corePath . '/lexicon/'); + if (!is_dir($lexiconPath)) { + return []; + } + $languageList = []; + + /** @var DirectoryIterator $language */ + foreach (new DirectoryIterator($lexiconPath) as $language) { + if (in_array($language, ['.', '..', '.svn', '.git', '_notes', 'country']) || !$language->isReadable()) { + continue; + } + + if ($language->isDir()) { + $languageKey = $language->getFilename(); + + /* + Only show languages that contain the selected topic + + Note that all custom (new, user-created) topics for core entries will only be + present in the database, which is queried below, so no need to assess whether a + file for a particular language and topic exists in that scenario + */ + if ($topic && $namespace !== 'core') { + $topicsPath = $language->getPath() . '/' . $languageKey . '/'; + if (!is_dir($topicsPath)) { + continue; + } + $file = $topic . '.inc.php'; + if (!is_readable($topicsPath . $file)) { + continue; + } + } + $languageList[] = $languageKey; + } + } + + $c = $this->modx->newQuery(modLexiconEntry::class, [ + 'namespace' => $namespace + ]); + if (!empty($languageList)) { + $c->where([ + 'language:NOT IN' => $languageList + ]); + } + if ($topic) { + $c->where([ + 'topic' => $topic + ]); + } + $c->select(['language']); + $c->query['distinct'] = 'DISTINCT'; + if ($c->prepare() && $c->stmt->execute()) { + $entries = $c->stmt->fetchAll(\PDO::FETCH_ASSOC); + if (is_array($entries) and count($entries) > 0) { + foreach ($entries as $v) { + $languageList[] = $v['language']; + } + } + } + sort($languageList); + + return $languageList; + } + /** * Get a lexicon string by its index. * diff --git a/manager/assets/modext/workspace/lexicon/lexicon.grid.js b/manager/assets/modext/workspace/lexicon/lexicon.grid.js index 892fa82f85b..ee70bbf6cb0 100644 --- a/manager/assets/modext/workspace/lexicon/lexicon.grid.js +++ b/manager/assets/modext/workspace/lexicon/lexicon.grid.js @@ -6,19 +6,29 @@ * @param {Object} config An object of configuration properties * @xtype modx-grid-lexicon */ -MODx.grid.Lexicon = function(config) { - config = config || {}; +MODx.grid.Lexicon = function(config = {}) { + this.languageFilterValue = MODx.util.url.getParamValue('language') || this.currentLanguage; + this.topicFilterValue = MODx.util.url.getParamValue('topic') || 'default'; + this.namespaceFilterValue = MODx.util.url.getParamValue('ns') || 'core'; + Ext.applyIf(config,{ id: 'modx-grid-lexicon' ,url: MODx.config.connector_url - ,fields: ['name','value','namespace','topic','language','editedon','overridden'] + ,fields: [ + 'name', + 'value', + 'namespace', + 'topic', + 'language', + 'editedon', + 'overridden' + ] ,baseParams: { - action: 'Workspace/Lexicon/GetList' - ,'namespace': MODx.request['ns'] ? MODx.request['ns'] : 'core' - ,topic: '' - ,language: MODx.config.cultureKey || 'en' + action: 'Workspace/Lexicon/GetList', + namespace: this.namespaceFilterValue, + topic: this.topicFilterValue, + language: this.languageFilterValue } - ,width: '98%' ,paging: true ,autosave: true ,save_action: 'Workspace/Lexicon/UpdateFromGrid' @@ -41,96 +51,177 @@ MODx.grid.Lexicon = function(config) { ,width: 125 ,renderer: this._renderLastModDate }] - ,tbar: [{ - xtype: 'button' - ,text: _('create') - ,cls:'primary-button' - ,handler: this.createEntry - ,scope: this - }, - '->' - ,{ - xtype: 'tbtext' - ,text: _('namespace')+':' - },{ - xtype: 'modx-combo-namespace' - ,id: 'modx-lexicon-filter-namespace' - ,itemId: 'namespace' - ,preselectValue: MODx.request['ns'] ? MODx.request['ns'] : '' - ,width: 150 - ,listeners: { - 'select': {fn: this.changeNamespace,scope:this} - } - },{ - xtype: 'tbtext' - ,text: _('topic')+':' - },{ - xtype: 'modx-combo-lexicon-topic' - ,id: 'modx-lexicon-filter-topic' - ,itemId: 'topic' - ,value: 'default' - ,width: 150 - ,baseParams: { - action: 'Workspace/Lexicon/Topic/GetList' - ,'namespace': MODx.request['ns'] ? MODx.request['ns'] : '' - ,'language': 'en' - } - ,listeners: { - 'select': {fn:this.changeTopic,scope:this} - } - },{ - xtype: 'tbtext' - ,text: _('language')+':' - },{ - xtype: 'modx-combo-language' - ,name: 'language' - ,id: 'modx-lexicon-filter-language' - ,itemId: 'language' - ,value: MODx.config.cultureKey || 'en' - ,width: 100 - ,baseParams: { - action: 'System/Language/GetList' - ,'namespace': MODx.request['ns'] ? MODx.request['ns'] : '' - } - ,listeners: { - 'select': {fn:this.changeLanguage,scope:this} - } - },{ - xtype: 'textfield' - ,name: 'name' - ,id: 'modx-lexicon-filter-search' - ,cls: 'x-form-filter' - ,itemId: 'search' - ,emptyText: _('search') - ,listeners: { - 'change': {fn:this.filter.createDelegate(this,['search'],true),scope:this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-lexicon-filter-clear' - ,cls: 'x-form-filter-clear' - ,itemId: 'clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } + ,tbar: { + cls: 'has-nested-filters', + items: [ + { + xtype: 'button' + ,text: _('create') + ,cls: 'primary-button' + ,handler: this.createEntry + ,scope: this + }, + '->', + { + xtype: 'container', + layout: 'form', + itemId: 'filter-namespace-container', + cls: 'grid-filter', + width: 150, + defaults: { + anchor: '100%' + }, + items: [ + { + xtype: 'label', + html: _('namespace') + }, + { + xtype: 'modx-combo-namespace', + itemId: 'filter-namespace', + hideLabel: true, + submitValue: false, + value: this.namespaceFilterValue, + baseParams: { + action: 'Workspace/PackageNamespace/GetList', + language: this.languageFilterValue, + topic: this.topicFilterValue, + isGridFilter: true, + targetGrid: 'MODx.grid.Lexicon' + }, + listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.updateDependentFilter('filter-language', 'namespace', record.data.name); + this.updateDependentFilter('filter-topic', 'namespace', record.data.name); + this.applyGridFilter(cmp, 'ns'); + }, + scope: this + }, + change: { + // Support typed-in value (where the select event is not triggered) + fn: function(cmp, newValue, oldValue) { + this.updateDependentFilter('filter-language', 'namespace', newValue); + this.updateDependentFilter('filter-topic', 'namespace', newValue); + this.applyGridFilter(cmp, 'ns'); + }, + scope: this + } + } + } + ] + }, + { + xtype: 'container', + layout: 'form', + itemId: 'filter-language-container', + cls: 'grid-filter', + width: 100, + defaults: { + anchor: '100%' + }, + items: [ + { + xtype: 'label', + html: _('language') + }, + { + xtype: 'modx-combo-language', + itemId: 'filter-language', + hideLabel: true, + submitValue: false, + queryParam: 'query', + value: this.languageFilterValue, + baseParams: { + action: 'System/Language/GetList', + namespace: this.namespaceFilterValue, + topic: this.topicFilterValue, + isGridFilter: true, + targetGrid: 'MODx.grid.Lexicon' + }, + listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.updateDependentFilter('filter-topic', 'language', record.data.name); + this.updateDependentFilter('filter-namespace', 'language', record.data.name); + this.applyGridFilter(cmp, 'language'); + }, + scope: this + }, + change: { + // Support typed-in value (where the select event is not triggered) + fn: function(cmp, newValue, oldValue) { + this.updateDependentFilter('filter-topic', 'language', newValue); + this.updateDependentFilter('filter-namespace', 'language', newValue); + this.applyGridFilter(cmp, 'language'); + }, + scope: this + } + } + } + ] + }, + { + xtype: 'container', + layout: 'form', + itemId: 'filter-topic-container', + cls: 'grid-filter', + width: 150, + defaults: { + anchor: '100%' + }, + items: [ + { + xtype: 'label', + html: _('topic') + }, + { + xtype: 'modx-combo-lexicon-topic', + itemId: 'filter-topic', + hideLabel: true, + submitValue: false, + queryParam: 'query', + value: this.topicFilterValue, + baseParams: { + action: 'Workspace/Lexicon/Topic/GetList', + namespace: this.namespaceFilterValue, + language: this.languageFilterValue, + isGridFilter: true, + targetGrid: 'MODx.grid.Lexicon' + }, + listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.updateDependentFilter('filter-namespace', 'topic', record.data.name); + this.updateDependentFilter('filter-language', 'topic', record.data.name); + this.applyGridFilter(cmp, 'topic'); + }, + scope: this + }, + change: { + // Support typed-in value (where the select event is not triggered) + fn: function(cmp, newValue, oldValue) { + this.updateDependentFilter('filter-namespace', 'topic', newValue); + this.updateDependentFilter('filter-language', 'topic', newValue); + this.applyGridFilter(cmp, 'topic'); + }, + scope: this + } + } + } + ] + }, + this.getQueryFilterField(), + this.getClearFiltersButton(`filter-namespace:core, filter-topic:default, filter-language:${this.currentLanguage}, filter-query`) + ] + } + ,pagingItems: [ + { + text: _('reload_from_base') + ,handler: this.reloadFromBase + ,scope: this } - }] - ,pagingItems: [{ - text: _('reload_from_base') - ,handler: this.reloadFromBase - ,scope: this - }] + ] }); MODx.grid.Lexicon.superclass.constructor.call(this,config); }; @@ -156,86 +247,6 @@ Ext.extend(MODx.grid.Lexicon,MODx.grid.Grid,{ return new Date(value*1000).format(MODx.config.manager_date_format + ' ' + MODx.config.manager_time_format); } - ,filter: function(cb,r,i,name) { - if (!name) {return false;} - this.store.baseParams[name] = cb.getValue(); - this.getBottomToolbar().changePage(1); - return true; - } - ,clearFilter: function() { - this.store.baseParams = { - action: 'Workspace/Lexicon/GetList' - ,'namespace': 'core' - ,topic: 'default' - ,language: 'en' - }; - this.getBottomToolbar().changePage(1); - var tb = this.getTopToolbar(); - tb.getComponent('namespace').setValue('core'); - - var tcb = tb.getComponent('topic'); - tcb.store.baseParams['namespace'] = 'core'; - tcb.store.load(); - tcb.setValue('default'); - - var tcl = tb.getComponent('language'); - tcb.store.baseParams['namespace'] = 'core'; - tcb.store.load(); - tcl.setValue('en'); - - tb.getComponent('search').setValue(''); - } - ,changeNamespace: function(cb,nv,ov) { - this.setFilterParams(cb.getValue(),'default','en'); - } - ,changeTopic: function(cb,nv,ov) { - this.setFilterParams(null,cb.getValue()); - } - ,changeLanguage: function(cb,nv,ov) { - this.setFilterParams(null,null,cb.getValue()); - } - - ,setFilterParams: function(ns,t,l) { - var tb = this.getTopToolbar(); - if (!tb) {return false;} - - var tcb,tcl; - if (ns) { - tb.getComponent('namespace').setValue(ns); - - tcl = tb.getComponent('language'); - if (tcl) { - tcl.store.baseParams['namespace'] = ns; - tcl.store.load({ - callback: function() { - tcl.setValue(l || 'en'); - } - }); - } - tcb = tb.getComponent('topic'); - if (tcb) { - tcb.store.baseParams['namespace'] = ns; - tcb.store.baseParams['language'] = l ? l : (tcl ? tcl.getValue() : 'en'); - tcb.store.load({ - callback: function() { - tcb.setValue(t || 'default'); - } - }); - } - } else if (t) { - tcb = tb.getComponent('topic'); - if (tcb) {tcb.setValue(t);} - } - - var s = this.getStore(); - if (s) { - if (ns) {s.baseParams['namespace'] = ns;} - if (t) {s.baseParams['topic'] = t || 'default';} - if (l) {s.baseParams['language'] = l || 'en';} - s.removeAll(); - } - this.getBottomToolbar().changePage(1); - } ,loadWindow2: function(btn,e,o) { var tb = this.getTopToolbar(); this.menu.record = { @@ -298,33 +309,34 @@ Ext.extend(MODx.grid.Lexicon,MODx.grid.Grid,{ return m; } - ,createEntry: function(btn,e) { - var r = this.menu.record || {}; + ,createEntry: function(btn, e) { + const record = this.menu.record || {}; - var tb = this.getTopToolbar(); - r['namespace'] = tb.getComponent('namespace').getValue(); - r.language = tb.getComponent('language').getValue(); - r.topic = tb.getComponent('topic').getValue(); + record.namespace = this.getFilterComponent('filter-namespace').getValue(); + record.language = this.getFilterComponent('filter-language').getValue(); + record.topic = this.getFilterComponent('filter-topic').getValue(); if (!this.createEntryWindow) { this.createEntryWindow = MODx.load({ - xtype: 'modx-window-lexicon-entry-create' - ,record: r - ,listeners: { - 'success':{fn:function(o) { - this.refresh(); - },scope:this} + xtype: 'modx-window-lexicon-entry-create', + record: record, + listeners: { + success: { + fn: function(o) { + this.refresh(); + }, + scope: this + } } }); } this.createEntryWindow.reset(); - this.createEntryWindow.setValues(r); + this.createEntryWindow.setValues(record); this.createEntryWindow.show(e.target); } }); Ext.reg('modx-grid-lexicon',MODx.grid.Lexicon); - /** * Generates the export lexicon window. * @@ -387,8 +399,6 @@ MODx.window.ExportLexicon = function(config) { Ext.extend(MODx.window.ExportLexicon,MODx.Window); Ext.reg('modx-window-lexicon-export',MODx.window.ExportLexicon); - - MODx.window.LexiconEntryCreate = function(config) { config = config || {}; this.ident = config.ident || 'lexentc'+Ext.id(); @@ -416,16 +426,6 @@ MODx.window.LexiconEntryCreate = function(config) { ,anchor: '100%' ,msgTarget: 'under' ,allowBlank: false - ,listeners: { - 'select': {fn: function(cb,r,i) { - cle = this.fp.getComponent('topic'); - if (cle) { - cle.store.baseParams['namespace'] = cb.getValue(); - cle.setValue(''); - cle.store.reload(); - } else {MODx.debug('cle not found');} - },scope:this} - } },{ xtype: 'modx-combo-lexicon-topic' ,fieldLabel: _('topic') From ba2f36c68827d83facaee3383848ea4054aea8b6 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sun, 19 Feb 2023 19:48:10 -0500 Subject: [PATCH 32/33] Update UserGroup Namespaces Access Grid Missed adding this change to commit 504015e --- .../modext/widgets/security/modx.grid.user.group.namespace.js | 1 + 1 file changed, 1 insertion(+) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js index 1d086dc2c24..b526e308741 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js @@ -87,6 +87,7 @@ MODx.grid.UserGroupNamespace = function(config) { ,baseParams: { action: 'Workspace/PackageNamespace/GetList', isGridFilter: true, + targetGrid: 'MODx.grid.UserGroupNamespace', usergroup: config.usergroup } ,listeners: { From 4ac23c36d2ff97ba745211128b0f6b92a4fc81b8 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sun, 19 Feb 2023 19:53:33 -0500 Subject: [PATCH 33/33] Remove Ext Statefulness from TVs Panel Avoids conflicts with our new tab tracking functionality, and it's generally less confusing to have the tab panel initially show its first tab instead of the last one visited --- .../assets/modext/widgets/element/modx.panel.tv.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/manager/assets/modext/widgets/element/modx.panel.tv.js b/manager/assets/modext/widgets/element/modx.panel.tv.js index 51c998dd736..52cd8ded14b 100644 --- a/manager/assets/modext/widgets/element/modx.panel.tv.js +++ b/manager/assets/modext/widgets/element/modx.panel.tv.js @@ -502,18 +502,7 @@ MODx.panel.TV = function(config) { ,record: config.record }],{ id: 'modx-tv-editor-tabs' - ,forceLayout: true ,deferredRender: false - ,stateful: true - ,stateId: 'modx-tv-tabpanel-'+config.tv - ,stateEvents: ['tabchange'] - ,hideMode: 'offsets' - ,anchor: '100%' - ,getState: function() { - return { - activeTab: this.items.indexOf(this.getActiveTab()) - }; - } })] ,useLoadingMask: true ,listeners: {