Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bulk deletion functionality #122

Open
wants to merge 17 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion easydata.js/packs/crud/src/i18n/text_resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ function addEasyDataCRUDTexts() {
AddDlgCaption: 'Create {entity}',
EditDlgCaption: 'Edit {entity}',
DeleteDlgCaption: 'Delete {entity}',
BulkDeleteDlgCaption: 'Delete {entity} records',
DeleteDlgMessage: 'Are you sure you want to remove this record: {{recordId}}?',
BulkDeleteDlgMessage: 'Are you sure you want to remove these records: [recordIds]?',
EntityMenuDesc: 'Click on an entity to view/edit its content',
BackToEntities: 'Back to entities',
SearchBtn: 'Search',
SearchInputPlaceholder: 'Search...',
RootViewTitle: 'Entities',
ModelIsEmpty: 'No entity was found.'
ModelIsEmpty: 'No entity was found.',
BulkDeleteBtnTitle: 'Bulk Delete'
});
}

Expand Down
27 changes: 21 additions & 6 deletions easydata.js/packs/crud/src/main/data_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import { TextDataFilter } from '../filter/text_data_filter';
import { EasyDataServerLoader } from './easy_data_server_loader';

type EasyDataEndpointKey =
'GetMetaData' |
'FetchDataset' |
'FetchRecord' |
'CreateRecord' |
'UpdateRecord' |
'DeleteRecord' ;
'GetMetaData' |
'FetchDataset' |
'FetchRecord' |
'CreateRecord' |
'UpdateRecord' |
'DeleteRecord' |
'BulkDeleteRecords';


interface CompoundRecordKey {
Expand Down Expand Up @@ -168,6 +169,19 @@ export class DataContext {
.finally(() => this.endProcess());
}

/**
* Delete records in bulk.
* @param obj Instances primary keys.
* @param sourceId Entity Id.
*/
public bulkDeleteRecords(obj: {[key: string]: object[]}, sourceId?: string) {
const url = this.resolveEndpoint('BulkDeleteRecords', { sourceId: sourceId || this.activeEntity.id });

this.startProcess();
return this.http.post(url, obj, { dataType: 'json'})
.finally(() => this.endProcess());
}

public setEndpoint(key: EasyDataEndpointKey, value: string) : void
public setEndpoint(key: EasyDataEndpointKey | string, value: string) : void {
this.endpoints.set(key, value);
Expand Down Expand Up @@ -231,5 +245,6 @@ export class DataContext {
this.setEnpointIfNotExist('CreateRecord', combinePath(endpointBase, 'models/{modelId}/sources/{sourceId}/create'));
this.setEnpointIfNotExist('UpdateRecord', combinePath(endpointBase, 'models/{modelId}/sources/{sourceId}/update'));
this.setEnpointIfNotExist('DeleteRecord', combinePath(endpointBase, 'models/{modelId}/sources/{sourceId}/delete'));
this.setEnpointIfNotExist('BulkDeleteRecords', combinePath(endpointBase, 'models/{modelId}/sources/{sourceId}/bulk-delete'));
}
}
64 changes: 62 additions & 2 deletions easydata.js/packs/crud/src/views/entity_data_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DataRow, i18n, utils as dataUtils } from '@easydata/core';
import {
DefaultDialogService,
DialogService, domel, EasyGrid,
GridCellRenderer, GridColumn, RowClickEvent
GridCellRenderer, GridColumn, RowClickEvent, BulkDeleteClickEvent
} from '@easydata/ui';

import { EntityEditFormBuilder } from '../form/entity_form_builder';
Expand Down Expand Up @@ -84,11 +84,14 @@ export class EntityDataView {
},
showPlusButton: this.context.getActiveEntity().isEditable,
plusButtonTitle: i18n.getText('AddRecordBtnTitle'),
showBulkDeleteButton: this.context.getActiveEntity().isEditable,
bulkDeleteButtonTitle: i18n.getText('BulkDeleteBtnTitle'),
showActiveRow: false,
onPlusButtonClick: this.addClickHandler.bind(this),
onGetCellRenderer: this.manageCellRenderer.bind(this),
onRowDbClick: this.rowDbClickHandler.bind(this),
onSyncGridColumn: this.syncGridColumnHandler.bind(this)
onSyncGridColumn: this.syncGridColumnHandler.bind(this),
onBulkDeleteButtonClick: this.bulkDeleteClickHandler.bind(this)
}, this.options.grid || {}));

if (this.options.showFilterBox) {
Expand Down Expand Up @@ -133,6 +136,12 @@ export class EntityDataView {
}
}
}
else if (column.isSelectCol) {
column.width = 110;
return (value: any, column: GridColumn, cell: HTMLElement, rowEl: HTMLElement) => {
domel('div', cell).addChild('input', b => b.attr('type', 'checkbox'));
}
}
}

private addClickHandler() {
Expand Down Expand Up @@ -241,6 +250,57 @@ export class EntityDataView {
});
}

private bulkDeleteClickHandler(ev: BulkDeleteClickEvent) {
const activeEntity = this.context.getActiveEntity();
const keyAttrs = activeEntity.getPrimaryAttrs();

let promises: Promise<DataRow>[] = [];

// Get record rows to delete in bulk.
ev.rowIndices.forEach(index => {
promises.push(this.context.getData().getRow(index));
})

let recordKeys: object[] = [];

Promise.all(promises).then((rows) => {
recordKeys = rows.map(row => {
if (!row) return;
let keyVals = keyAttrs.map(attr => row.getValue(attr.id));
let keys = keyAttrs.reduce((val, attr, index) => {
const property = attr.id.substring(attr.id.lastIndexOf('.') + 1);
val[property] = keyVals[index];
return val;
}, {});
return keys;
});

if (recordKeys.length == 0) {
return;
}

this.dlg.openConfirm(
i18n.getText('BulkDeleteDlgCaption')
.replace('{entity}', activeEntity.caption),
i18n.getText('BulkDeleteDlgMessage')
.replace('recordIds', recordKeys.map(
keys => '{' + Object.keys(keys).map(key => `${key}:${keys[key]}`).join('; ') + '}'
).join("; ")
),
)
.then((result) => {
if (!result) return;
this.context.bulkDeleteRecords({'pks': recordKeys})
.then(() => {
return this.refreshData();
})
.catch((error) => {
this.processError(error);
});
});
});
}

private processError(error) {
this.dlg.open({
title: 'Ooops, something went wrong',
Expand Down
15 changes: 14 additions & 1 deletion easydata.js/packs/ui/assets/css/easy-grid.css
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@
font-size: 16px;
}

.keg-header-btn-plus {
.keg-header-btn-plus, .keg-header-btn-delete {
position: relative;
height: 23px;
width: 23px;
Expand All @@ -181,6 +181,19 @@
background-position: -25px 0 !important;
}

.keg-header-btn-delete a {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAAAGAAAABgAPBrQs8AAAAHdElNRQfmAwEEIQkBn0oRAAAA6UlEQVQoz73RLUuDYRTG8d997zY4XwZD2NCwJhO7n0KbdU0/gMlPYLUIFrUJFi2micF1v4BiFIQZfJuOhW2P4ZlDQbD5Txec6xzOdU6Qs27DKyg5cGZMMm1BsurUtYKBFWtu9N3rQrCvoGdWV3/UUvRmUtuOHsmSLc8CAsiQKdtW9kByac+tvkw0FA0FSd2Jdr5FzaGKOXUly0rq5lQdmc/LUWago6phwqakoaJj8JUijtMURVOiouQbcawyvxL9wb8ZoraWDxe6rjyObjqK9y6pudMUnAuaWJTyV5E8adnV+TF3xrGXXH4CCEs376+QugwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMDMtMDFUMDQ6MzM6MDktMDU6MDDpgVkiAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTAzLTAxVDA0OjMzOjA5LTA1OjAwmNzhngAAAABJRU5ErkJggg==') no-repeat;
width: 100%;
height: 100%;
display: block;
background-position: center;
}

.keg-header-btn-delete a:hover {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAABgAAAAYADwa0LPAAAAB3RJTUUH5gMBBCAyqY+SdAAAAxFJREFUSMfVlUtIVVEUhr+9zzl6vSnmNcJeFE6iKEjKIhGCBk0qkAqqgTSwEiIIqdAoGkQRVNog6GFBBGYURFhBL+hFQV2FoEnUpFDKNHpc9d7u1XPObrC2jzSpoEkLLj9r77X/9fPvtc9VDMWZ020vAVBkANDodWWAwWwuBQwkPtjilRZvAwry50nd2XY5ebURCAgAqrctWsy4oeBMY1s81wNC9NQ3suYtkO3dlwWf1Am2Vgo6xwSD7YAiLK0BDE75XBF65BGgMcFSwEF1bJD1VAOoQ2YywNa3pTPAFcVH9ktjp1WI0+IF5qlgyXPB+VYI5yzeBQzaK5Jyc1R4qtPSMPoZCODjesDBHLoEZp/qSncCVSACDMxZCPiENc+ANP7Xd7ZBhRCqHTZfY9G3uMridyt4ufBxEYjgxqoAD13bBoSYglPC11kxeAVWwL0eQKFOdMmB17Ntnw5LvFpQHxQMLYFusfkue6MyA8pdIXxzysSB5hgQIatrD+AzMDwDLjBAXpOMXV/xWqCX5N4WIEpWcAvwcCcVAikynR1AFG9aCZAmeL8XiKCnNAE+/qdTgE+BGwKa/sO5QJrUtSVAgnS4G6rrFpUPC9B2FDeJNcFFmd5eX15BUTGQjVM5E4jguQ0iaOtEYCp57kYgm6zKeqCQ3KIDgCLWm2MdbBaHzU55JWNfgR6VOxbjQIBxrkgafSx3qPsln3AM0Cgd2P04oNDuWTsDraP4xg09zrqyv6TN+4ZWJRIW0xZ7fh7GPw/9twf+dfw3AtQf7v+ublwBGhmewTkNLQ5+cAbvOhiRmxH7KQFjh3SIx4zqMybkU2zMK6v+PODiqFrAQ3cvk7L77wCFSrYDBnNnCzBAkOwDDOGD04BPf7cHuAzok5b/AqBRxAHnV/64gEsmWQ8ojFsHxIjOuglonDeHxY07x4EIjvomAq5ngBS+isi52+XAd4xxgSifZ8eAEOMWApqcVP4IN8YI0KgvjYAifKiBEN2QkEa9sVH1+eLEkPX2z4x+a/MLcSSvQOqaqoEcehI3gCTTxwr4AeSNAVcYdmHUAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIyLTAzLTAxVDA0OjMyOjUwLTA1OjAw2zt5qwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMi0wMy0wMVQwNDozMjo1MC0wNTowMKpmwRcAAAAASUVORK5CYII=') no-repeat;
background-position: center;
}

/* Pagination */
.keg-pagination-wrapper {
display: inline-flex;
Expand Down
130 changes: 106 additions & 24 deletions easydata.js/packs/ui/src/grid/easy_grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {
PageChangedEvent,
ColumnChangedEvent,
ColumnDeletedEvent,
ActiveRowChangedEvent
ActiveRowChangedEvent,
BulkDeleteClickEvent
} from './easy_grid_events';

import { GridColumnList, GridColumn, GridColumnAlign } from './easy_grid_columns';
Expand Down Expand Up @@ -110,7 +111,8 @@ export class EasyGrid {
},
showPlusButton: false,
viewportRowsCount: null,
showActiveRow: true
showActiveRow: true,
showBulkDeleteButton: false,
}

public readonly options: EasyGridOptions;
Expand Down Expand Up @@ -241,6 +243,9 @@ export class EasyGrid {
if (options.onActiveRowChanged) {
this.addEventListener('activeRowChanged', options.onActiveRowChanged);
}
if (options.onBulkDeleteButtonClick) {
this.addEventListener('bulkDeleteClick', options.onBulkDeleteButtonClick);
}

this.addEventListener('pageChanged', ev => this.activeRowIndex = -1);

Expand Down Expand Up @@ -450,6 +455,15 @@ export class EasyGrid {
domel(hd)
.addChildElement(this.renderHeaderButtons());
}

if (column.isSelectCol) {
domel(hd)
.addChildElement(domel('span')
.setStyle("margin-left", "4px")
.setStyle("display", "flex")
.setStyle("align-items", "center")
.addChildElement(this.renderSelectAllCheckbox()).toDOM());
}
});

const containerWidth = this.getContainerWidth();
Expand Down Expand Up @@ -478,7 +492,7 @@ export class EasyGrid {
domel('div', colDiv)
.addClass(`${this.cssPrefix}-header-cell-resize`)

if (!column.isRowNum) {
if (!column.isRowNum && !column.isSelectCol) {
domel('div', colDiv)
.addClass(`${this.cssPrefix}-header-cell-label`)
.text(column.label);
Expand Down Expand Up @@ -957,8 +971,21 @@ export class EasyGrid {
return;
}

const colindex = column.isRowNum ? -1 : this.dataTable.columns.getIndex(column.dataColumn.id);
let val = column.isRowNum ? indexGlobal + 1 : row.getValue(colindex);
let colindex: number;
let val: any;

if (column.isSelectCol) {
colindex = -2;
val = indexGlobal + 1;
}
else if (column.isRowNum) {
colindex = -1;
val = indexGlobal + 1;
}
else {
colindex = this.dataTable.columns.getIndex(column.dataColumn.id);
val = row.getValue(colindex)
}

rowElement.appendChild(this.renderCell(column, index, val, rowElement));
});
Expand Down Expand Up @@ -1174,6 +1201,7 @@ export class EasyGrid {
public addEventListener(eventType: 'columnMoved', handler: (ev: ColumnMovedEvent) => void): string;
public addEventListener(eventType: 'columnDeleted', handler: (ev: ColumnDeletedEvent) => void): string;
public addEventListener(eventType: 'activeRowChanged', handler: (ev: ActiveRowChangedEvent) => void): string;
public addEventListener(eventType: 'bulkDeleteClick', handler: (ev: BulkDeleteClickEvent) => void): string;
public addEventListener(eventType: GridEventType | string, handler: (data: any) => void): string {
return this.eventEmitter.subscribe(eventType, event => handler(event.data));
}
Expand All @@ -1183,26 +1211,80 @@ export class EasyGrid {
}

protected renderHeaderButtons(): HTMLElement {
if (this.options.showPlusButton) {
return domel('div')
.addClass(`${this.cssPrefix}-header-btn-plus`)
.title(this.options.plusButtonTitle || 'Add')
.addChild('a', builder => builder
.attr('href', 'javascript:void(0)')
.on('click', (e) => {
e.preventDefault();
this.fireEvent({
type: 'plusButtonClick',
sourceEvent: e
} as PlusButtonClickEvent);
})
)
if (!this.options.showBulkDeleteButton && !this.options.showPlusButton) {
return domel('span')
.addText('#')
.toDOM();
}

return domel('span')
.addText('#')
.toDOM();
let buttonBlock = domel('div')
.setStyle("display", "flex")
.setStyle("justify-content", "center");

// Generate Add Record button.
if (this.options.showPlusButton) {
buttonBlock.addChildElement(domel('div')
.addClass(`${this.cssPrefix}-header-btn-plus`)
.title(this.options.plusButtonTitle || 'Add')
.addChild('a', builder => builder
.attr('href', 'javascript:void(0)')
.on('click', (e) => {
e.preventDefault();
this.fireEvent({
type: 'plusButtonClick',
sourceEvent: e
} as PlusButtonClickEvent);
})
).toDOM());
}

// Generate Bulk Delete button.
if (this.options.showBulkDeleteButton) {
buttonBlock.addChildElement(domel('div')
.addClass(`${this.cssPrefix}-header-btn-delete`)
.title(this.options.bulkDeleteButtonTitle || 'Bulk delete')
.addChild('a', builder => builder
.attr('href', 'javascript:void(0)'))
.on('click', (e) => {
e.preventDefault();
this.fireEvent({
type: 'bulkDeleteClick',
rowIndices: this.getSelectedRowsIds()
} as BulkDeleteClickEvent);
}).toDOM());
}

return buttonBlock.toDOM();
}

/**
* Render checkbox to select all checkboxes.
*/
private renderSelectAllCheckbox(): HTMLElement {
return domel('input')
.attr('type', 'checkbox')
.on('change', (e) => {
var checkboxes = document.querySelectorAll('input[type="checkbox"]');
const targetCheckbox: HTMLInputElement = e.target as HTMLInputElement;

checkboxes.forEach(checkbox => {
const input: HTMLInputElement = checkbox as HTMLInputElement;

if (input != targetCheckbox)
input.checked = targetCheckbox.checked;
})
}).toDOM();
}

/**
* Get indices of selected rows.
*/
private getSelectedRowsIds(): number[] {
var checkboxes = document.querySelectorAll('div.keg-cell-value input[type="checkbox"]:checked');
const indices: number[] = Array.from(checkboxes, checkbox => {
return parseInt(checkbox.closest('div.keg-row').getAttribute('data-row-idx'));
})
return indices;
}


Expand Down Expand Up @@ -1372,11 +1454,11 @@ export class EasyGrid {

maxWidth += 3;

const maxOption = column.isRowNum
const maxOption = column.isRowNum || column.isSelectCol
? this.options.columnWidths.rowNumColumn.max || 500
: this.options.columnWidths[column.dataColumn.type].max || 2000;

const minOption = column.isRowNum
const minOption = column.isRowNum || column.isSelectCol
? this.options.columnWidths.rowNumColumn.min || 0
: this.options.columnWidths[column.dataColumn.type].min || 20;

Expand Down
Loading