mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-28 17:23:19 -05:00
a couple more fixes for table filter (#15121)
* a few fixes * add comments and fix for non-ideal scenario
This commit is contained in:
@@ -132,25 +132,24 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.slick-header-menuicon
|
||||
.slick-header-menu a.monaco-button.monaco-text-button.slick-header-menuicon
|
||||
{
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
width: 16px;
|
||||
background-position: 2px center;
|
||||
background-repeat: no-repeat;
|
||||
padding-left: 18px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
padding: 0 0 0 18px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.slick-header-menuicon.ascending {
|
||||
background: url('sort-asc.gif');
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('sort-asc.gif');
|
||||
}
|
||||
|
||||
.slick-header-menuicon.descending {
|
||||
background: url('sort-desc.gif');
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('sort-desc.gif');
|
||||
}
|
||||
|
||||
.slick-header-menucontent {
|
||||
|
||||
@@ -12,15 +12,18 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IDisposableDataProvider } from 'sql/base/common/dataProvider';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IInputBoxStyles, InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
|
||||
export type HeaderFilterCommands = 'sort-asc' | 'sort-desc';
|
||||
|
||||
export interface CommandEventArgs<T extends Slick.SlickData> {
|
||||
grid: Slick.Grid<T>,
|
||||
column: Slick.Column<T>,
|
||||
command: HeaderFilterCommands
|
||||
}
|
||||
|
||||
export interface ITableFilterStyle extends IButtonStyles, IInputBoxStyles { }
|
||||
|
||||
const ShowFilterText: string = localize('headerFilter.showFilter', "Show Filter");
|
||||
|
||||
export class HeaderFilter<T extends Slick.SlickData> {
|
||||
@@ -35,9 +38,12 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
private okButton?: Button;
|
||||
private clearButton?: Button;
|
||||
private cancelButton?: Button;
|
||||
private sortAscButton?: Button;
|
||||
private sortDescButton?: Button;
|
||||
private searchInputBox?: InputBox;
|
||||
private workingFilters!: Array<string>;
|
||||
private columnDef!: FilterableColumn<T>;
|
||||
private buttonStyles?: IButtonStyles;
|
||||
private filterStyles?: ITableFilterStyle;
|
||||
private disposableStore = new DisposableStore();
|
||||
private _enabled: boolean = true;
|
||||
|
||||
@@ -117,36 +123,31 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
.remove();
|
||||
}
|
||||
|
||||
private addMenuItem(menu: JQuery<HTMLElement>, columnDef: Slick.Column<T>, title: string, command: HeaderFilterCommands) {
|
||||
const $item = jQuery('<div class="slick-header-menuitem">')
|
||||
.data('command', command)
|
||||
.data('column', columnDef)
|
||||
.bind('click', async (e) => await this.handleMenuItemClick(e, command, columnDef))
|
||||
.appendTo(menu);
|
||||
|
||||
const $icon = jQuery('<div class="slick-header-menuicon">')
|
||||
.appendTo($item);
|
||||
|
||||
if (title === 'Sort Ascending') {
|
||||
$icon.get(0).className += ' ascending';
|
||||
} else if (title === 'Sort Descending') {
|
||||
$icon.get(0).className += ' descending';
|
||||
}
|
||||
|
||||
jQuery('<span class="slick-header-menucontent">')
|
||||
.text(title)
|
||||
.appendTo($item);
|
||||
private createButtonMenuItem(menu: HTMLElement, columnDef: Slick.Column<T>, title: string, command: HeaderFilterCommands, iconClass: string): Button {
|
||||
const buttonContainer = document.createElement('div');
|
||||
menu.appendChild(buttonContainer);
|
||||
const button = new Button(buttonContainer);
|
||||
button.icon = { classNames: `slick-header-menuicon ${iconClass}` };
|
||||
button.label = title;
|
||||
button.onDidClick(async () => {
|
||||
await this.handleMenuItemClick(command, columnDef);
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
private addMenuInput(menu: JQuery<HTMLElement>, columnDef: Slick.Column<T>): void {
|
||||
const self = this;
|
||||
jQuery('<input class="input" placeholder="Search" style="margin-top: 5px; width: 206px">')
|
||||
.data('column', columnDef)
|
||||
.bind('keyup', async (e) => {
|
||||
const filterVals = await this.getFilterValuesByInput(jQuery(e.target));
|
||||
self.updateFilterInputs(menu, columnDef, filterVals);
|
||||
})
|
||||
.appendTo(menu);
|
||||
private createSearchInput(menu: JQuery<HTMLElement>, columnDef: Slick.Column<T>): InputBox {
|
||||
const inputContainer = document.createElement('div');
|
||||
inputContainer.style.width = '206px';
|
||||
inputContainer.style.marginTop = '5px';
|
||||
menu[0].appendChild(inputContainer);
|
||||
const input = new InputBox(inputContainer, this.contextViewProvider, {
|
||||
placeholder: localize('table.searchPlaceHolder', "Search")
|
||||
});
|
||||
input.onDidChange(async (newString) => {
|
||||
const filterVals = await this.getFilterValuesByInput(columnDef, newString);
|
||||
this.updateFilterInputs(menu, columnDef, filterVals);
|
||||
});
|
||||
return input;
|
||||
}
|
||||
|
||||
private updateFilterInputs(menu: JQuery<HTMLElement>, columnDef: FilterableColumn<T>, filterItems: Array<string>) {
|
||||
@@ -171,11 +172,32 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
});
|
||||
}
|
||||
|
||||
private showFilter(filterButton: HTMLElement): void {
|
||||
private async showFilter(filterButton: HTMLElement): Promise<void> {
|
||||
await this.createFilterMenu(filterButton);
|
||||
const menuElement = this.$menu[0];
|
||||
// Get the absolute coordinates of the filter button
|
||||
const offset = jQuery(filterButton).offset();
|
||||
// Calculate the position of menu item
|
||||
let menuleft = offset.left - menuElement.offsetWidth + filterButton.offsetWidth;
|
||||
let menutop = offset.top + filterButton.offsetHeight;
|
||||
// Make sure the entire menu is on screen.
|
||||
// If there is not enough vertical space under the filter button, we will move up the menu.
|
||||
// If the left of the menu is off screen (negative value), we will show the menu next to the left edge of window.
|
||||
// We don't really consider the case when there is not enough space to show the entire menu since in that case the application is not usable already.
|
||||
if (menutop + menuElement.offsetHeight > window.innerHeight) {
|
||||
menutop = window.innerHeight - menuElement.offsetHeight;
|
||||
}
|
||||
menuleft = menuleft > 0 ? menuleft : 0;
|
||||
|
||||
this.contextViewProvider.showContextView({
|
||||
getAnchor: () => filterButton,
|
||||
getAnchor: () => {
|
||||
return {
|
||||
x: menuleft,
|
||||
y: menutop
|
||||
};
|
||||
},
|
||||
render: (container: HTMLElement) => {
|
||||
this.renderFilter(filterButton, container).catch(onUnexpectedError);
|
||||
container.appendChild(menuElement);
|
||||
return {
|
||||
dispose: () => {
|
||||
if (this.$menu) {
|
||||
@@ -184,11 +206,14 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
focus: () => {
|
||||
this.okButton.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async renderFilter(filterButton: HTMLElement, container: HTMLElement) {
|
||||
private async createFilterMenu(filterButton: HTMLElement) {
|
||||
const target = withNullAsUndefined(filterButton);
|
||||
const $menuButton = jQuery(target);
|
||||
this.columnDef = $menuButton.data('column');
|
||||
@@ -220,16 +245,18 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
}
|
||||
|
||||
if (!this.$menu) {
|
||||
this.$menu = jQuery('<div class="slick-header-menu">').appendTo(container);
|
||||
// first add it to the document so that we can get the actual size of the menu
|
||||
// later, it will be added to the correct container
|
||||
this.$menu = jQuery('<div class="slick-header-menu">').appendTo(document.body);
|
||||
}
|
||||
|
||||
this.$menu.empty();
|
||||
|
||||
this.addMenuItem(this.$menu, this.columnDef, localize('table.sortAscending', "Sort Ascending"), 'sort-asc');
|
||||
this.addMenuItem(this.$menu, this.columnDef, localize('table.sortDescending', "Sort Descending"), 'sort-desc');
|
||||
this.addMenuInput(this.$menu, this.columnDef);
|
||||
this.sortAscButton = this.createButtonMenuItem(this.$menu[0], this.columnDef, localize('table.sortAscending', "Sort Ascending"), 'sort-asc', 'ascending');
|
||||
this.sortDescButton = this.createButtonMenuItem(this.$menu[0], this.columnDef, localize('table.sortDescending', "Sort Descending"), 'sort-desc', 'descending');
|
||||
this.searchInputBox = this.createSearchInput(this.$menu, this.columnDef);
|
||||
|
||||
let filterOptions = '<label><input type="checkbox" value="-1" />(Select All)</label>';
|
||||
let filterOptions = '<label class="filter-option"><input type="checkbox" value="-1" />(Select All)</label>';
|
||||
|
||||
for (let i = 0; i < filterItems.length; i++) {
|
||||
const filtered = this.workingFilters.some(x => x === filterItems[i]);
|
||||
@@ -251,58 +278,50 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
this.okButton.label = localize('headerFilter.ok', "OK");
|
||||
this.okButton.title = localize('headerFilter.ok', "OK");
|
||||
this.okButton.element.id = 'filter-ok-button';
|
||||
const okElement = jQuery('#filter-ok-button');
|
||||
okElement.bind('click', (ev) => {
|
||||
this.okButton.onDidClick(() => {
|
||||
this.columnDef.filterValues = this.workingFilters.splice(0);
|
||||
this.setButtonImage($menuButton, this.columnDef.filterValues.length > 0);
|
||||
this.handleApply(ev, this.columnDef);
|
||||
this.handleApply(this.columnDef);
|
||||
});
|
||||
|
||||
this.clearButton = new Button($clearButtonDiv.get(0), { secondary: true });
|
||||
this.clearButton.label = localize('headerFilter.clear', "Clear");
|
||||
this.clearButton.title = localize('headerFilter.clear', "Clear");
|
||||
this.clearButton.element.id = 'filter-clear-button';
|
||||
const clearElement = jQuery('#filter-clear-button');
|
||||
clearElement.bind('click', (ev) => {
|
||||
this.okButton.onDidClick(() => {
|
||||
this.columnDef.filterValues!.length = 0;
|
||||
this.setButtonImage($menuButton, false);
|
||||
this.handleApply(ev, this.columnDef);
|
||||
this.handleApply(this.columnDef);
|
||||
});
|
||||
|
||||
this.cancelButton = new Button($cancelButtonDiv.get(0), { secondary: true });
|
||||
this.cancelButton.label = localize('headerFilter.cancel', "Cancel");
|
||||
this.cancelButton.title = localize('headerFilter.cancel', "Cancel");
|
||||
this.cancelButton.element.id = 'filter-cancel-button';
|
||||
const cancelElement = jQuery('#filter-cancel-button');
|
||||
cancelElement.bind('click', () => this.hideMenu());
|
||||
this.cancelButton.onDidClick(() => {
|
||||
this.hideMenu();
|
||||
});
|
||||
|
||||
this.applyStyles();
|
||||
|
||||
jQuery(':checkbox', $filter).bind('click', (e) => {
|
||||
this.workingFilters = this.changeWorkingFilter(filterItems, this.workingFilters, jQuery(e.target));
|
||||
});
|
||||
this.okButton.focus();
|
||||
}
|
||||
|
||||
public style(styles: IButtonStyles): void {
|
||||
this.buttonStyles = styles;
|
||||
public style(styles: ITableFilterStyle): void {
|
||||
this.filterStyles = styles;
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
private applyStyles() {
|
||||
if (this.buttonStyles) {
|
||||
const styles = this.buttonStyles;
|
||||
if (this.okButton) {
|
||||
this.okButton.style(styles);
|
||||
}
|
||||
|
||||
if (this.clearButton) {
|
||||
this.clearButton.style(styles);
|
||||
}
|
||||
|
||||
if (this.cancelButton) {
|
||||
this.cancelButton.style(styles);
|
||||
}
|
||||
if (this.filterStyles) {
|
||||
this.okButton?.style(this.filterStyles);
|
||||
this.cancelButton?.style(this.filterStyles);
|
||||
this.clearButton?.style(this.filterStyles);
|
||||
this.sortAscButton?.style(this.filterStyles);
|
||||
this.sortDescButton?.style(this.filterStyles);
|
||||
this.searchInputBox?.style(this.filterStyles);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +374,7 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
}
|
||||
}
|
||||
|
||||
private handleApply(e: JQuery.Event<HTMLElement, null>, columnDef: Slick.Column<T>) {
|
||||
private handleApply(columnDef: Slick.Column<T>) {
|
||||
this.hideMenu();
|
||||
const provider = this.grid.getData() as IDisposableDataProvider<T>;
|
||||
|
||||
@@ -365,9 +384,7 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
this.grid.updateRowCount();
|
||||
this.grid.render();
|
||||
}
|
||||
this.onFilterApplied.notify({ grid: this.grid, column: columnDef }, e, self);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.onFilterApplied.notify({ grid: this.grid, column: columnDef });
|
||||
}
|
||||
|
||||
private getFilterValues(dataView: Slick.DataProvider<T>, column: Slick.Column<T>): Array<any> {
|
||||
@@ -381,10 +398,8 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
return Array.from(seen);
|
||||
}
|
||||
|
||||
private async getFilterValuesByInput($input: JQuery<HTMLElement>): Promise<Array<string>> {
|
||||
const column = $input.data('column'),
|
||||
filter = $input.val() as string,
|
||||
dataView = this.grid.getData() as IDisposableDataProvider<T>,
|
||||
private async getFilterValuesByInput(column: Slick.Column<T>, filter: string): Promise<Array<string>> {
|
||||
const dataView = this.grid.getData() as IDisposableDataProvider<T>,
|
||||
seen: Set<any> = new Set();
|
||||
|
||||
let columnValues: any[];
|
||||
@@ -423,7 +438,7 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
return Array.from(seen).sort((v) => { return v; });
|
||||
}
|
||||
|
||||
private async handleMenuItemClick(e: JQuery.Event<HTMLElement, null>, command: HeaderFilterCommands, columnDef: Slick.Column<T>) {
|
||||
private async handleMenuItemClick(command: HeaderFilterCommands, columnDef: Slick.Column<T>) {
|
||||
this.hideMenu();
|
||||
const provider = this.grid.getData() as IDisposableDataProvider<T>;
|
||||
|
||||
@@ -442,10 +457,7 @@ export class HeaderFilter<T extends Slick.SlickData> {
|
||||
grid: this.grid,
|
||||
column: columnDef,
|
||||
command: command
|
||||
}, e, self);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
|
||||
Reference in New Issue
Block a user