(core) close sort&filter menu when clicking Save/Revert buttons

Summary:
- close sort&filter menu when clicking Save/Revert buttons
- also closes when clicking Apply/Cancel from a nested filter menu

Test Plan:
 - updated existing test to match new spec
 - added new test to cover new behaviour

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2799
This commit is contained in:
Cyprien P 2021-05-06 14:23:50 +02:00
parent 570baa95a5
commit 8056bb0069
4 changed files with 24 additions and 16 deletions

View File

@ -650,8 +650,8 @@ BaseView.prototype.getLastDataRowIndex = function() {
/** /**
* Creates and opens ColumnFilterMenu for a given field, and returns its PopupControl. * Creates and opens ColumnFilterMenu for a given field, and returns its PopupControl.
*/ */
BaseView.prototype.createFilterMenu = function(openCtl, field) { BaseView.prototype.createFilterMenu = function(openCtl, field, onClose) {
return createFilterMenu(openCtl, this._sectionFilter, field, this._filteredRowSource, this.tableModel.tableData); return createFilterMenu(openCtl, this._sectionFilter, field, this._filteredRowSource, this.tableModel.tableData, onClose);
}; };
/** /**

View File

@ -60,7 +60,7 @@ declare module "app/client/components/BaseView" {
constructor(gristDoc: GristDoc, viewSectionModel: any); constructor(gristDoc: GristDoc, viewSectionModel: any);
public setCursorPos(cursorPos: CursorPos): void; public setCursorPos(cursorPos: CursorPos): void;
public createFilterMenu(ctl: IOpenController, field: ViewFieldRec): HTMLElement; public createFilterMenu(ctl: IOpenController, field: ViewFieldRec, onClose?: () => void): HTMLElement;
public buildTitleControls(): DomArg; public buildTitleControls(): DomArg;
public getLoadingDonePromise(): Promise<void>; public getLoadingDonePromise(): Promise<void>;
public onResize(): void; public onResize(): void;

View File

@ -5,6 +5,7 @@
*/ */
import {allInclusive, ColumnFilter, isEquivalentFilter} from 'app/client/models/ColumnFilter'; import {allInclusive, ColumnFilter, isEquivalentFilter} from 'app/client/models/ColumnFilter';
import {ColumnFilterMenuModel, IFilterCount} from 'app/client/models/ColumnFilterMenuModel';
import {ViewFieldRec, ViewSectionRec} from 'app/client/models/DocModel'; import {ViewFieldRec, ViewSectionRec} from 'app/client/models/DocModel';
import {FilteredRowSource} from 'app/client/models/rowset'; import {FilteredRowSource} from 'app/client/models/rowset';
import {SectionFilter} from 'app/client/models/SectionFilter'; import {SectionFilter} from 'app/client/models/SectionFilter';
@ -17,9 +18,8 @@ import {menuCssClass, menuDivider} from 'app/client/ui2018/menus';
import {CellValue} from 'app/common/DocActions'; import {CellValue} from 'app/common/DocActions';
import {Computed, Disposable, dom, DomElementMethod, IDisposableOwner, input, makeTestId, styled} from 'grainjs'; import {Computed, Disposable, dom, DomElementMethod, IDisposableOwner, input, makeTestId, styled} from 'grainjs';
import identity = require('lodash/identity'); import identity = require('lodash/identity');
import noop = require('lodash/noop');
import {IOpenController, IPopupOptions, setPopupToCreateDom} from 'popweasel'; import {IOpenController, IPopupOptions, setPopupToCreateDom} from 'popweasel';
import {ColumnFilterMenuModel, IFilterCount} from '../models/ColumnFilterMenuModel';
interface IFilterMenuOptions { interface IFilterMenuOptions {
model: ColumnFilterMenuModel; model: ColumnFilterMenuModel;
@ -258,7 +258,7 @@ function buildSummary(label: string, SummaryModelCtor: SummaryModelCreator, mode
* Returns content for the newly created columnFilterMenu; for use with setPopupToCreateDom(). * Returns content for the newly created columnFilterMenu; for use with setPopupToCreateDom().
*/ */
export function createFilterMenu(openCtl: IOpenController, sectionFilter: SectionFilter, field: ViewFieldRec, export function createFilterMenu(openCtl: IOpenController, sectionFilter: SectionFilter, field: ViewFieldRec,
rowSource: FilteredRowSource, tableData: TableData) { rowSource: FilteredRowSource, tableData: TableData, onClose: () => void = noop) {
// Go through all of our shown and hidden rows, and count them up by the values in this column. // Go through all of our shown and hidden rows, and count them up by the values in this column.
const valueGetter = tableData.getRowPropFunc(field.column().colId())!; const valueGetter = tableData.getRowPropFunc(field.column().colId())!;
const labelGetter = tableData.getRowPropFunc(field.displayColModel().colId())!; const labelGetter = tableData.getRowPropFunc(field.displayColModel().colId())!;
@ -280,7 +280,7 @@ export function createFilterMenu(openCtl: IOpenController, sectionFilter: Sectio
return columnFilterMenu(openCtl, { return columnFilterMenu(openCtl, {
model, model,
valueCounts, valueCounts,
onClose: () => openCtl.close(), onClose: () => { openCtl.close(); onClose(); },
doSave: (reset: boolean = false) => { doSave: (reset: boolean = false) => {
const spec = columnFilter.makeFilterJson(); const spec = columnFilter.makeFilterJson();
// If filter is moot and filter bar is hidden, let's remove the filter. // If filter is moot and filter bar is hidden, let's remove the filter.
@ -321,14 +321,19 @@ const defaultPopupOptions: IPopupOptions = {
trigger: ['click'], trigger: ['click'],
}; };
interface IColumnFilterMenuOptions extends IPopupOptions {
// callback for when the content of the menu is closed by clicking the apply or revert buttons
onCloseContent?: () => void;
}
// Helper to attach the column filter menu. // Helper to attach the column filter menu.
export function attachColumnFilterMenu(viewSection: ViewSectionRec, field: ViewFieldRec, export function attachColumnFilterMenu(viewSection: ViewSectionRec, field: ViewFieldRec,
popupOptions: IPopupOptions): DomElementMethod { popupOptions: IColumnFilterMenuOptions): DomElementMethod {
const options = {...defaultPopupOptions, ...popupOptions}; const options = {...defaultPopupOptions, ...popupOptions};
return (elem) => { return (elem) => {
const instance = viewSection.viewInstance(); const instance = viewSection.viewInstance();
if (instance && instance.createFilterMenu) { // Should be set if using BaseView if (instance && instance.createFilterMenu) { // Should be set if using BaseView
setPopupToCreateDom(elem, ctl => instance.createFilterMenu(ctl, field), options); setPopupToCreateDom(elem, ctl => instance.createFilterMenu(ctl, field, popupOptions.onCloseContent), options);
} }
}; };
} }

View File

@ -1,10 +1,11 @@
import {flipColDirection, parseSortColRefs} from 'app/client/lib/sortUtil'; import {flipColDirection, parseSortColRefs} from 'app/client/lib/sortUtil';
import {reportError} from 'app/client/models/AppModel';
import {ColumnRec, DocModel, ViewFieldRec, ViewRec, ViewSectionRec} from 'app/client/models/DocModel'; import {ColumnRec, DocModel, ViewFieldRec, ViewRec, ViewSectionRec} from 'app/client/models/DocModel';
import {CustomComputed} from 'app/client/models/modelUtil'; import {CustomComputed} from 'app/client/models/modelUtil';
import {attachColumnFilterMenu} from 'app/client/ui/ColumnFilterMenu'; import {attachColumnFilterMenu} from 'app/client/ui/ColumnFilterMenu';
import {addFilterMenu} from 'app/client/ui/FilterBar'; import {addFilterMenu} from 'app/client/ui/FilterBar';
import {makeViewLayoutMenu} from 'app/client/ui/ViewLayoutMenu';
import {hoverTooltip} from 'app/client/ui/tooltips'; import {hoverTooltip} from 'app/client/ui/tooltips';
import {makeViewLayoutMenu} from 'app/client/ui/ViewLayoutMenu';
import {basicButton, primaryButton} from 'app/client/ui2018/buttons'; import {basicButton, primaryButton} from 'app/client/ui2018/buttons';
import {colors, vars} from 'app/client/ui2018/cssVars'; import {colors, vars} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons'; import {icon} from 'app/client/ui2018/icons';
@ -43,7 +44,7 @@ export function viewSectionMenu(owner: IDisposableOwner, docModel: DocModel, vie
|| !use(viewSection.activeFilterBar.isSaved) || !use(viewSection.activeFilterBar.isSaved)
)); ));
const save = () => doSave(docModel, viewSection); const save = () => { doSave(docModel, viewSection).catch(reportError); };
const revert = () => doRevert(viewSection); const revert = () => doRevert(viewSection);
return [ return [
@ -58,23 +59,23 @@ export function viewSectionMenu(owner: IDisposableOwner, docModel: DocModel, vie
cssFilterIconWrapper.cls('-any', anyFilter), cssFilterIconWrapper.cls('-any', anyFilter),
cssFilterIcon('Filter') cssFilterIcon('Filter')
), ),
menu(_ctl => [ menu(ctl => [
dom.domComputed(use => { dom.domComputed(use => {
use(viewSection.activeSortJson.isSaved); // Rebuild sort panel if sort gets saved. A little hacky. use(viewSection.activeSortJson.isSaved); // Rebuild sort panel if sort gets saved. A little hacky.
return makeSortPanel(viewSection, use(viewSection.activeSortSpec), return makeSortPanel(viewSection, use(viewSection.activeSortSpec),
(row: number) => docModel.columns.getRowModel(row)); (row: number) => docModel.columns.getRowModel(row));
}), }),
dom.domComputed(viewSection.filteredFields, fields => dom.domComputed(viewSection.filteredFields, fields =>
makeFilterPanel(viewSection, fields, popupControls)), makeFilterPanel(viewSection, fields, popupControls, () => ctl.close())),
makeAddFilterButton(viewSection, popupControls), makeAddFilterButton(viewSection, popupControls),
makeFilterBarToggle(viewSection.activeFilterBar), makeFilterBarToggle(viewSection.activeFilterBar),
dom.domComputed(displaySaveObs, displaySave => [ dom.domComputed(displaySaveObs, displaySave => [
displaySave ? cssMenuInfoHeader( displaySave ? cssMenuInfoHeader(
cssSaveButton('Save', testId('btn-save'), cssSaveButton('Save', testId('btn-save'),
dom.on('click', save), dom.on('click', () => { save(); ctl.close(); }),
dom.boolAttr('disabled', isReadonly)), dom.boolAttr('disabled', isReadonly)),
basicButton('Revert', testId('btn-revert'), basicButton('Revert', testId('btn-revert'),
dom.on('click', revert)) dom.on('click', () => { revert(); ctl.close(); }))
) : null, ) : null,
]), ]),
]), ]),
@ -180,7 +181,8 @@ export function makeFilterBarToggle(activeFilterBar: CustomComputed<boolean>) {
function makeFilterPanel(section: ViewSectionRec, filteredFields: ViewFieldRec[], function makeFilterPanel(section: ViewSectionRec, filteredFields: ViewFieldRec[],
popupControls: WeakMap<ViewFieldRec, PopupControl>) { popupControls: WeakMap<ViewFieldRec, PopupControl>,
onCloseContent: () => void) {
const fields = filteredFields.map(field => { const fields = filteredFields.map(field => {
const fieldChanged = Computed.create(null, fromKo(field.activeFilter.isSaved), (_use, isSaved) => !isSaved); const fieldChanged = Computed.create(null, fromKo(field.activeFilter.isSaved), (_use, isSaved) => !isSaved);
return cssMenuText( return cssMenuText(
@ -191,6 +193,7 @@ function makeFilterPanel(section: ViewSectionRec, filteredFields: ViewFieldRec[]
attachColumnFilterMenu(section, field, { attachColumnFilterMenu(section, field, {
placement: 'bottom-end', placement: 'bottom-end',
trigger: ['click', (_el, popupControl) => popupControls.set(field, popupControl)], trigger: ['click', (_el, popupControl) => popupControls.set(field, popupControl)],
onCloseContent,
}), }),
testId('filter-icon'), testId('filter-icon'),
), ),