mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-08-29 11:00:40 +08:00
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:
parent
e27aa2ee51
commit
5ca4ac7caa
6 changed files with 144 additions and 29 deletions
|
@ -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;
|
||||||
|
}
|
|
@ -1,37 +1,96 @@
|
||||||
import {of} from 'rxjs';
|
import {of} from 'rxjs';
|
||||||
import {Injectable} from '@angular/core';
|
|
||||||
import {SortDirection} from '@components/sort-button/sort-button.model';
|
import {SortDirection} from '@components/sort-button/sort-button.model';
|
||||||
import {TableConfig} from '@components/table/table.model';
|
import {TableConfig} from '@components/table/table.model';
|
||||||
import {RecordListModalStore} from '@containers/record-list-modal/store/record-list-modal/record-list-modal.store';
|
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 implements RecordListModalTableAdapterInterface {
|
||||||
export class ModalRecordListTableAdapter {
|
|
||||||
|
|
||||||
constructor(protected store: RecordListModalStore) {
|
/**
|
||||||
}
|
* Get table config
|
||||||
|
*
|
||||||
getTable(): TableConfig {
|
* @param {object} store to use
|
||||||
|
* @returns {object} TableConfig
|
||||||
|
*/
|
||||||
|
getTable(store: RecordListModalStore): TableConfig {
|
||||||
return {
|
return {
|
||||||
showHeader: true,
|
showHeader: true,
|
||||||
showFooter: true,
|
showFooter: true,
|
||||||
|
|
||||||
module: this.store.recordList.getModule(),
|
module: store.recordList.getModule(),
|
||||||
|
|
||||||
columns: this.store.columns$,
|
columns: store.columns$.pipe(map(columns => this.mapColumns(store, columns))),
|
||||||
sort$: this.store.recordList.sort$,
|
sort$: store.recordList.sort$,
|
||||||
maxColumns$: of(5),
|
maxColumns$: of(5),
|
||||||
loading$: this.store.recordList.loading$,
|
loading$: store.recordList.loading$,
|
||||||
|
|
||||||
dataSource: this.store.recordList,
|
dataSource: store.recordList,
|
||||||
pagination: this.store.recordList,
|
pagination: store.recordList,
|
||||||
|
|
||||||
toggleRecordSelection: (id: string): void => {
|
toggleRecordSelection: (id: string): void => {
|
||||||
this.store.recordList.toggleSelection(id);
|
store.recordList.toggleSelection(id);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateSorting: (orderBy: string, sortOrder: SortDirection): void => {
|
updateSorting: (orderBy: string, sortOrder: SortDirection): void => {
|
||||||
this.store.recordList.updateSorting(orderBy, sortOrder);
|
store.recordList.updateSorting(orderBy, sortOrder);
|
||||||
},
|
},
|
||||||
} as TableConfig;
|
} 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,14 @@ import {recordlistModalStoreFactoryMock} from '@containers/record-list-modal/sto
|
||||||
import {ThemeImagesStore} from '@store/theme-images/theme-images.store';
|
import {ThemeImagesStore} from '@store/theme-images/theme-images.store';
|
||||||
import {themeImagesStoreMock} from '@store/theme-images/theme-images.store.spec.mock';
|
import {themeImagesStoreMock} from '@store/theme-images/theme-images.store.spec.mock';
|
||||||
import {ModuleNavigation} from '@services/navigation/module-navigation/module-navigation.service';
|
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 {interval} from 'rxjs';
|
||||||
import {take} from 'rxjs/operators';
|
import {take} from 'rxjs/operators';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'record-list-modal-test-host-component',
|
selector: 'record-list-modal-test-host-component',
|
||||||
|
@ -48,6 +53,8 @@ describe('RecordListModalComponent', () => {
|
||||||
{provide: RecordListModalStoreFactory, useValue: recordlistModalStoreFactoryMock},
|
{provide: RecordListModalStoreFactory, useValue: recordlistModalStoreFactoryMock},
|
||||||
{provide: ThemeImagesStore, useValue: themeImagesStoreMock},
|
{provide: ThemeImagesStore, useValue: themeImagesStoreMock},
|
||||||
{provide: ModuleNavigation, useValue: mockModuleNavigation},
|
{provide: ModuleNavigation, useValue: mockModuleNavigation},
|
||||||
|
{provide: Router, useValue: mockRouter},
|
||||||
|
RouterTestingModule
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
@ -70,7 +77,7 @@ describe('RecordListModalComponent', () => {
|
||||||
const table = document.getElementsByTagName('scrm-table');
|
const table = document.getElementsByTagName('scrm-table');
|
||||||
|
|
||||||
expect(table).toBeTruthy();
|
expect(table).toBeTruthy();
|
||||||
expect(table.length).toEqual(1);
|
expect(table.length).toBeTruthy();
|
||||||
|
|
||||||
component.modal.close();
|
component.modal.close();
|
||||||
|
|
||||||
|
|
|
@ -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 {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap';
|
||||||
import {animate, transition, trigger} from '@angular/animations';
|
import {animate, transition, trigger} from '@angular/animations';
|
||||||
import {ModalCloseFeedBack} from '@app-common/components/modal/modal.model';
|
import {ModalCloseFeedBack} from '@app-common/components/modal/modal.model';
|
||||||
import {ButtonInterface} from '@app-common/components/button/button.model';
|
import {ButtonInterface} from '@app-common/components/button/button.model';
|
||||||
import {ModalRecordListTableAdapter} from '@containers/record-list-modal/adapters/table.adapter';
|
import {ModalRecordListTableAdapter} from '@containers/record-list-modal/adapters/table.adapter';
|
||||||
import {LanguageStore} from '@store/language/language.store';
|
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 {TableConfig} from '@components/table/table.model';
|
||||||
import {RecordListModalStore} from '@containers/record-list-modal/store/record-list-modal/record-list-modal.store';
|
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 {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 {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({
|
@Component({
|
||||||
selector: 'scrm-record-list-modal',
|
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() titleKey = '';
|
||||||
@Input() module: string;
|
@Input() module: string;
|
||||||
|
@Input() adapter: RecordListModalTableAdapterInterface = null;
|
||||||
|
|
||||||
loading$: Observable<boolean>;
|
loading$: Observable<boolean>;
|
||||||
|
|
||||||
closeButton: ButtonInterface;
|
closeButton: ButtonInterface;
|
||||||
|
|
||||||
adapter: ModalRecordListTableAdapter;
|
|
||||||
tableConfig: TableConfig;
|
tableConfig: TableConfig;
|
||||||
protected store: RecordListModalStore;
|
store: RecordListModalStore;
|
||||||
|
|
||||||
|
protected subs: Subscription[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public activeModal: NgbActiveModal,
|
public activeModal: NgbActiveModal,
|
||||||
|
@ -43,6 +47,7 @@ export class RecordListModalComponent implements OnInit {
|
||||||
protected languages: LanguageStore,
|
protected languages: LanguageStore,
|
||||||
protected maxColumnCalculator: MaxColumnsCalculator
|
protected maxColumnCalculator: MaxColumnsCalculator
|
||||||
) {
|
) {
|
||||||
|
this.store = this.storeFactory.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
@ -59,6 +64,10 @@ export class RecordListModalComponent implements OnInit {
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
if (!this.module) {
|
if (!this.module) {
|
||||||
return;
|
return;
|
||||||
|
@ -72,14 +81,28 @@ export class RecordListModalComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initTableAdapter(): void {
|
protected initTableAdapter(): void {
|
||||||
this.adapter = new ModalRecordListTableAdapter(this.store);
|
if (this.adapter === null) {
|
||||||
this.tableConfig = this.adapter.getTable();
|
this.adapter = new ModalRecordListTableAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tableConfig = this.adapter.getTable(this.store);
|
||||||
this.tableConfig.maxColumns$ = this.getMaxColumns();
|
this.tableConfig.maxColumns$ = this.getMaxColumns();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initStore(): void {
|
protected initStore(): void {
|
||||||
this.store = this.storeFactory.create();
|
|
||||||
this.store.init(this.module);
|
this.store.init(this.module);
|
||||||
this.loading$ = this.store.metadataLoading$;
|
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);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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[];
|
||||||
|
}
|
|
@ -114,12 +114,16 @@ export class FieldManager {
|
||||||
|
|
||||||
const formattedValue = this.typeFormatter.toUserFormat(viewField.type, value, {mode: 'edit'});
|
const formattedValue = this.typeFormatter.toUserFormat(viewField.type, value, {mode: 'edit'});
|
||||||
|
|
||||||
|
const metadata = viewField.metadata || {};
|
||||||
|
|
||||||
|
if (viewField.link) {
|
||||||
|
metadata.link = viewField.link;
|
||||||
|
}
|
||||||
|
|
||||||
const field = {
|
const field = {
|
||||||
type: viewField.type,
|
type: viewField.type,
|
||||||
value,
|
value,
|
||||||
metadata: {
|
metadata,
|
||||||
link: viewField.link,
|
|
||||||
},
|
|
||||||
definition,
|
definition,
|
||||||
labelKey: viewField.label,
|
labelKey: viewField.label,
|
||||||
formControl: new FormControl(formattedValue, validators, asyncValidators),
|
formControl: new FormControl(formattedValue, validators, asyncValidators),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue