Add record selection to Record List Modal

- Add support for click a link closing the modal
- Set link callbacks for module link fields
- Disable links for relate fields
- Allow receiving to optionally set a table adapter as input
This commit is contained in:
Clemente Raposo 2021-01-25 09:48:48 +00:00 committed by Dillon-Brown
parent e27aa2ee51
commit 5ca4ac7caa
6 changed files with 144 additions and 29 deletions

View file

@ -0,0 +1,13 @@
import {RecordListModalStore} from '@containers/record-list-modal/store/record-list-modal/record-list-modal.store';
import {TableConfig} from '@components/table/table.model';
export interface RecordListModalTableAdapterInterface {
/**
* Get table config
*
* @param {object} store to use
* @returns {object} TableConfig
*/
getTable(store: RecordListModalStore): TableConfig;
}

View file

@ -1,37 +1,96 @@
import {of} from 'rxjs';
import {Injectable} from '@angular/core';
import {SortDirection} from '@components/sort-button/sort-button.model';
import {TableConfig} from '@components/table/table.model';
import {RecordListModalStore} from '@containers/record-list-modal/store/record-list-modal/record-list-modal.store';
import {map} from 'rxjs/operators';
import {ColumnDefinition} from '@app-common/metadata/list.metadata.model';
import {Record} from '@app-common/record/record.model';
import {Field} from '@app-common/record/field.model';
import {RecordListModalTableAdapterInterface} from '@containers/record-list-modal/adapters/table-adapter.model';
@Injectable()
export class ModalRecordListTableAdapter {
export class ModalRecordListTableAdapter implements RecordListModalTableAdapterInterface {
constructor(protected store: RecordListModalStore) {
}
getTable(): TableConfig {
/**
* Get table config
*
* @param {object} store to use
* @returns {object} TableConfig
*/
getTable(store: RecordListModalStore): TableConfig {
return {
showHeader: true,
showFooter: true,
module: this.store.recordList.getModule(),
module: store.recordList.getModule(),
columns: this.store.columns$,
sort$: this.store.recordList.sort$,
columns: store.columns$.pipe(map(columns => this.mapColumns(store, columns))),
sort$: store.recordList.sort$,
maxColumns$: of(5),
loading$: this.store.recordList.loading$,
loading$: store.recordList.loading$,
dataSource: this.store.recordList,
pagination: this.store.recordList,
dataSource: store.recordList,
pagination: store.recordList,
toggleRecordSelection: (id: string): void => {
this.store.recordList.toggleSelection(id);
store.recordList.toggleSelection(id);
},
updateSorting: (orderBy: string, sortOrder: SortDirection): void => {
this.store.recordList.updateSorting(orderBy, sortOrder);
store.recordList.updateSorting(orderBy, sortOrder);
},
} as TableConfig;
}
/**
* Parse and override column definitions
*
* @param {object} store to use
* @param {[]} columns to map
* @returns {[]} ColumnDefinition[]
*/
protected mapColumns(store: RecordListModalStore, columns: ColumnDefinition[]): ColumnDefinition[] {
const mappedColumns = [];
columns.forEach(column => {
const mapped = {...column};
const metadata = column.metadata || {};
mapped.metadata = {...metadata};
this.disableRelateFieldsLink(mapped);
this.addLinkSelectHandler(store, mapped);
mappedColumns.push(mapped);
});
return mappedColumns;
}
/**
* Disable link for relate fields
*
* @param {object} definition to update
*/
protected disableRelateFieldsLink(definition: ColumnDefinition): void {
if (definition.type !== 'relate') {
return;
}
definition.link = false;
definition.metadata.link = false;
}
/**
* Add onClick handler for link fields
*
* @param {object} store to use
* @param {object} definition to update
*/
protected addLinkSelectHandler(store: RecordListModalStore, definition: ColumnDefinition): void {
if (!definition.link) {
return;
}
definition.metadata.onClick = (field: Field, record: Record): void => {
store.recordList.toggleSelection(record.id);
};
}
}

View file

@ -12,9 +12,14 @@ import {recordlistModalStoreFactoryMock} from '@containers/record-list-modal/sto
import {ThemeImagesStore} from '@store/theme-images/theme-images.store';
import {themeImagesStoreMock} from '@store/theme-images/theme-images.store.spec.mock';
import {ModuleNavigation} from '@services/navigation/module-navigation/module-navigation.service';
import {mockModuleNavigation} from '@services/navigation/module-navigation/module-navigation.service.spec.mock';
import {
mockModuleNavigation,
mockRouter
} from '@services/navigation/module-navigation/module-navigation.service.spec.mock';
import {interval} from 'rxjs';
import {take} from 'rxjs/operators';
import {RouterTestingModule} from '@angular/router/testing';
import {Router} from '@angular/router';
@Component({
selector: 'record-list-modal-test-host-component',
@ -48,6 +53,8 @@ describe('RecordListModalComponent', () => {
{provide: RecordListModalStoreFactory, useValue: recordlistModalStoreFactoryMock},
{provide: ThemeImagesStore, useValue: themeImagesStoreMock},
{provide: ModuleNavigation, useValue: mockModuleNavigation},
{provide: Router, useValue: mockRouter},
RouterTestingModule
],
})
.compileComponents();
@ -70,7 +77,7 @@ describe('RecordListModalComponent', () => {
const table = document.getElementsByTagName('scrm-table');
expect(table).toBeTruthy();
expect(table.length).toEqual(1);
expect(table.length).toBeTruthy();
component.modal.close();

View file

@ -1,15 +1,18 @@
import {Component, Input, OnInit} from '@angular/core';
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap';
import {animate, transition, trigger} from '@angular/animations';
import {ModalCloseFeedBack} from '@app-common/components/modal/modal.model';
import {ButtonInterface} from '@app-common/components/button/button.model';
import {ModalRecordListTableAdapter} from '@containers/record-list-modal/adapters/table.adapter';
import {LanguageStore} from '@store/language/language.store';
import {Observable, of} from 'rxjs';
import {Observable, of, Subscription} from 'rxjs';
import {TableConfig} from '@components/table/table.model';
import {RecordListModalStore} from '@containers/record-list-modal/store/record-list-modal/record-list-modal.store';
import {MaxColumnsCalculator} from '@services/ui/max-columns-calculator/max-columns-calculator.service';
import {RecordListModalStoreFactory} from '@containers/record-list-modal/store/record-list-modal/record-list-modal.store.factory';
import {RecordListModalTableAdapterInterface} from '@containers/record-list-modal/adapters/table-adapter.model';
import {distinctUntilChanged, skip} from 'rxjs/operators';
import {RecordListModalResult} from '@containers/record-list-modal/components/record-list-modal/record-list-modal.model';
@Component({
selector: 'scrm-record-list-modal',
@ -24,18 +27,19 @@ import {RecordListModalStoreFactory} from '@containers/record-list-modal/store/r
]),
]
})
export class RecordListModalComponent implements OnInit {
export class RecordListModalComponent implements OnInit, OnDestroy {
@Input() titleKey = '';
@Input() module: string;
@Input() adapter: RecordListModalTableAdapterInterface = null;
loading$: Observable<boolean>;
closeButton: ButtonInterface;
adapter: ModalRecordListTableAdapter;
tableConfig: TableConfig;
protected store: RecordListModalStore;
store: RecordListModalStore;
protected subs: Subscription[] = [];
constructor(
public activeModal: NgbActiveModal,
@ -43,6 +47,7 @@ export class RecordListModalComponent implements OnInit {
protected languages: LanguageStore,
protected maxColumnCalculator: MaxColumnsCalculator
) {
this.store = this.storeFactory.create();
}
ngOnInit(): void {
@ -59,6 +64,10 @@ export class RecordListModalComponent implements OnInit {
this.init();
}
ngOnDestroy(): void {
this.subs.forEach(sub => sub.unsubscribe());
}
init(): void {
if (!this.module) {
return;
@ -72,14 +81,28 @@ export class RecordListModalComponent implements OnInit {
}
protected initTableAdapter(): void {
this.adapter = new ModalRecordListTableAdapter(this.store);
this.tableConfig = this.adapter.getTable();
if (this.adapter === null) {
this.adapter = new ModalRecordListTableAdapter();
}
this.tableConfig = this.adapter.getTable(this.store);
this.tableConfig.maxColumns$ = this.getMaxColumns();
}
protected initStore(): void {
this.store = this.storeFactory.create();
this.store.init(this.module);
this.loading$ = this.store.metadataLoading$;
this.subs.push(this.store.recordList.selection$.pipe(distinctUntilChanged(), skip(1)).subscribe(selection => {
if (!selection || !selection.selected || Object.keys(selection.selected).length < 1) {
return;
}
this.activeModal.close({
selection,
records: this.store.recordList.records
} as RecordListModalResult);
}));
}
}

View file

@ -0,0 +1,9 @@
import {RecordSelection} from '@app-common/views/list/record-selection.model';
import {Record} from '@app-common/record/record.model';
export interface RecordListModalResult {
[key: string]: any;
selection: RecordSelection;
records: Record[];
}

View file

@ -114,12 +114,16 @@ export class FieldManager {
const formattedValue = this.typeFormatter.toUserFormat(viewField.type, value, {mode: 'edit'});
const metadata = viewField.metadata || {};
if (viewField.link) {
metadata.link = viewField.link;
}
const field = {
type: viewField.type,
value,
metadata: {
link: viewField.link,
},
metadata,
definition,
labelKey: viewField.label,
formControl: new FormControl(formattedValue, validators, asyncValidators),