mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-08-29 04:47:10 +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 {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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 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),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue