mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-08-29 07:50:08 +08:00
Add relate edit component
- Add relate edit component -- Add standard relate handling to BaseRelateComponent - Re-structure relate fields handling -- Store the object instead of just the name -- Map valueObject to the proper attributes when saving
This commit is contained in:
parent
9af33c927b
commit
d4493e5759
14 changed files with 516 additions and 17 deletions
153
core/app/fields/base/base-relate.component.ts
Normal file
153
core/app/fields/base/base-relate.component.ts
Normal file
|
@ -0,0 +1,153 @@
|
|||
import {LanguageStore} from '@store/language/language.store';
|
||||
import {OnDestroy, OnInit} from '@angular/core';
|
||||
import {Observable, of} from 'rxjs';
|
||||
import {DataTypeFormatter} from '@services/formatters/data-type.formatter.service';
|
||||
import {catchError, map, tap} from 'rxjs/operators';
|
||||
import {AttributeMap, Record} from '@app-common/record/record.model';
|
||||
import {RelateService} from '@services/record/relate/relate.service';
|
||||
import {BaseFieldComponent} from '@fields/base/base-field.component';
|
||||
import {ModuleNameMapper} from '@services/navigation/module-name-mapper/module-name-mapper.service';
|
||||
|
||||
|
||||
export class BaseRelateComponent extends BaseFieldComponent implements OnInit, OnDestroy {
|
||||
selectedValues: AttributeMap[] = [];
|
||||
|
||||
status: '' | 'searching' | 'not-found' | 'error' | 'found' = '';
|
||||
|
||||
constructor(
|
||||
protected languages: LanguageStore,
|
||||
protected typeFormatter: DataTypeFormatter,
|
||||
protected relateService: RelateService,
|
||||
protected moduleNameMapper: ModuleNameMapper
|
||||
) {
|
||||
super(typeFormatter);
|
||||
}
|
||||
|
||||
get module(): string {
|
||||
if (!this.record || !this.record.module) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.record.module;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initValue();
|
||||
|
||||
if (this.relateService) {
|
||||
this.relateService.init(this.getRelatedModule());
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.forEach(sub => sub.unsubscribe());
|
||||
}
|
||||
|
||||
search = (text: string): Observable<any> => {
|
||||
|
||||
this.status = 'searching';
|
||||
|
||||
return this.relateService.search(text, this.getRelateFieldName()).pipe(
|
||||
tap(() => this.status = 'found'),
|
||||
catchError(() => {
|
||||
this.status = 'error';
|
||||
return of([]);
|
||||
}),
|
||||
map(records => {
|
||||
if (!records || records.length < 1) {
|
||||
this.status = 'not-found';
|
||||
return [];
|
||||
}
|
||||
|
||||
const flatRecords: AttributeMap[] = [];
|
||||
|
||||
records.forEach((record: Record) => {
|
||||
if (record && record.attributes) {
|
||||
flatRecords.push(record.attributes);
|
||||
}
|
||||
});
|
||||
|
||||
this.status = '';
|
||||
|
||||
return flatRecords;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
getRelateFieldName(): string {
|
||||
return (this.field && this.field.definition && this.field.definition.rname) || 'name';
|
||||
}
|
||||
|
||||
getRelatedModule(): string {
|
||||
const legacyName = (this.field && this.field.definition && this.field.definition.module) || '';
|
||||
if (!legacyName) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return this.moduleNameMapper.toFrontend(legacyName);
|
||||
}
|
||||
|
||||
getMessage(): string {
|
||||
const messages = {
|
||||
searching: 'LBL_SEARCHING',
|
||||
'not-found': 'LBL_NOT_FOUND',
|
||||
error: 'LBL_SEARCH_ERROR',
|
||||
found: 'LBL_FOUND',
|
||||
'no-module': 'LBL_FOUND'
|
||||
};
|
||||
|
||||
if (messages[this.status]) {
|
||||
return messages[this.status];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
getInvalidClass(): string {
|
||||
if (this.field.formControl && this.field.formControl.invalid && this.field.formControl.touched) {
|
||||
return 'is-invalid';
|
||||
}
|
||||
|
||||
if (this.hasSearchError()) {
|
||||
return 'is-invalid';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
hasSearchError(): boolean {
|
||||
return this.status === 'error' || this.status === 'not-found';
|
||||
}
|
||||
|
||||
resetStatus(): void {
|
||||
this.status = '';
|
||||
}
|
||||
|
||||
getPlaceholderLabel(): string {
|
||||
return this.languages.getAppString('LBL_TYPE_TO_SEARCH') || '';
|
||||
}
|
||||
|
||||
protected buildRelate(id: string, relateValue: string): any {
|
||||
const relate = {id};
|
||||
|
||||
if (this.getRelateFieldName()) {
|
||||
relate[this.getRelateFieldName()] = relateValue;
|
||||
}
|
||||
|
||||
return relate;
|
||||
}
|
||||
|
||||
|
||||
protected initValue(): void {
|
||||
if (!this.field.valueObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.field.valueObject.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedValues = [];
|
||||
this.selectedValues.push(this.field.valueObject);
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ import {EmailListFieldsComponent} from '@fields/email/templates/list/email.compo
|
|||
import {TextDetailFieldComponent} from '@fields/text/templates/detail/text.component';
|
||||
import {TextDetailFieldModule} from '@fields/text/templates/detail/text.module';
|
||||
import {RelateDetailFieldsModule} from '@fields/relate/templates/detail/relate.module';
|
||||
import {RelateDetailFieldsComponent} from '@fields/relate/templates/detail/relate.component';
|
||||
import {RelateDetailFieldComponent} from '@fields/relate/templates/detail/relate.component';
|
||||
import {FullNameDetailFieldsComponent} from '@fields/fullname/templates/detail/fullname.component';
|
||||
import {FullNameDetailFieldsModule} from '@fields/fullname/templates/detail/fullname.module';
|
||||
import {DateEditFieldComponent} from '@fields/date/templates/edit/date.component';
|
||||
|
@ -50,6 +50,8 @@ import {MultiEnumFilterFieldModule} from '@fields/multienum/templates/filter/mul
|
|||
import {MultiEnumFilterFieldComponent} from '@fields/multienum/templates/filter/multienum.component';
|
||||
import {BooleanFilterFieldModule} from '@fields/boolean/templates/filter/boolean.module';
|
||||
import {BooleanFilterFieldComponent} from '@fields/boolean/templates/filter/boolean.component';
|
||||
import {RelateEditFieldModule} from '@fields/relate/templates/edit/relate.module';
|
||||
import {RelateEditFieldComponent} from '@fields/relate/templates/edit/relate.component';
|
||||
|
||||
export const fieldModules = [
|
||||
VarcharDetailFieldModule,
|
||||
|
@ -67,6 +69,7 @@ export const fieldModules = [
|
|||
EmailListFieldsModule,
|
||||
TextDetailFieldModule,
|
||||
RelateDetailFieldsModule,
|
||||
RelateEditFieldModule,
|
||||
FullNameDetailFieldsModule,
|
||||
EnumDetailFieldModule,
|
||||
EnumEditFieldModule,
|
||||
|
@ -94,7 +97,8 @@ export const fieldComponents = [
|
|||
CurrencyDetailFieldComponent,
|
||||
EmailListFieldsComponent,
|
||||
TextDetailFieldComponent,
|
||||
RelateDetailFieldsComponent,
|
||||
RelateDetailFieldComponent,
|
||||
RelateEditFieldComponent,
|
||||
FullNameDetailFieldsComponent,
|
||||
EnumDetailFieldComponent,
|
||||
EnumEditFieldComponent,
|
||||
|
@ -136,8 +140,9 @@ export const viewFieldsMap = {
|
|||
'email.list': EmailListFieldsComponent,
|
||||
'email.detail': EmailListFieldsComponent,
|
||||
'text.detail': TextDetailFieldComponent,
|
||||
'relate.detail': RelateDetailFieldsComponent,
|
||||
'relate.list': RelateDetailFieldsComponent,
|
||||
'relate.detail': RelateDetailFieldComponent,
|
||||
'relate.list': RelateDetailFieldComponent,
|
||||
'relate.edit': RelateEditFieldComponent,
|
||||
'fullname.list': FullNameDetailFieldsComponent,
|
||||
'fullname.detail': FullNameDetailFieldsComponent,
|
||||
'enum.list': EnumDetailFieldComponent,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {RelateDetailFieldsComponent} from './relate.component';
|
||||
import {RelateDetailFieldComponent} from './relate.component';
|
||||
import {Component} from '@angular/core';
|
||||
import {Field} from '@app-common/record/field.model';
|
||||
import {Record} from '@app-common/record/record.model';
|
||||
|
@ -48,7 +48,7 @@ describe('RelateRecordFieldsComponent', () => {
|
|||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
RelateDetailFieldTestHostComponent,
|
||||
RelateDetailFieldsComponent,
|
||||
RelateDetailFieldComponent,
|
||||
],
|
||||
imports: [RouterTestingModule],
|
||||
providers: [
|
||||
|
|
|
@ -7,7 +7,7 @@ import {DataTypeFormatter} from '@services/formatters/data-type.formatter.servic
|
|||
templateUrl: './relate.component.html',
|
||||
styleUrls: []
|
||||
})
|
||||
export class RelateDetailFieldsComponent extends BaseFieldComponent {
|
||||
export class RelateDetailFieldComponent extends BaseFieldComponent {
|
||||
|
||||
constructor(protected typeFormatter: DataTypeFormatter) {
|
||||
super(typeFormatter);
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {AppManagerModule} from '@base/app-manager/app-manager.module';
|
||||
import {RelateDetailFieldsComponent} from './relate.component';
|
||||
import {RelateDetailFieldComponent} from './relate.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [RelateDetailFieldsComponent],
|
||||
exports: [RelateDetailFieldsComponent],
|
||||
declarations: [RelateDetailFieldComponent],
|
||||
exports: [RelateDetailFieldComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
AppManagerModule.forChild(RelateDetailFieldsComponent)
|
||||
AppManagerModule.forChild(RelateDetailFieldComponent)
|
||||
]
|
||||
})
|
||||
export class RelateDetailFieldsModule {
|
||||
|
|
23
core/app/fields/relate/templates/edit/relate.component.html
Normal file
23
core/app/fields/relate/templates/edit/relate.component.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<tag-input #tag
|
||||
(onAdd)="onAdd($event)"
|
||||
(onBlur)="resetStatus()"
|
||||
(onRemove)="onRemove()"
|
||||
[(ngModel)]="selectedValues"
|
||||
[class]="getInvalidClass()"
|
||||
[clearOnBlur]="true"
|
||||
[displayBy]="getRelateFieldName()"
|
||||
[inputClass]="getInvalidClass()"
|
||||
[onTextChangeDebounce]="500"
|
||||
[onlyFromAutocomplete]="true"
|
||||
[placeholder]="getPlaceholderLabel()"
|
||||
[secondaryPlaceholder]="getPlaceholderLabel()"
|
||||
maxItems="1">
|
||||
<tag-input-dropdown [autocompleteObservable]="search"
|
||||
[displayBy]="getRelateFieldName()"
|
||||
[keepOpen]="false"
|
||||
[showDropdownIfEmpty]="true">
|
||||
</tag-input-dropdown>
|
||||
</tag-input>
|
||||
<small *ngIf="getMessage()" class="text-danger form-text text-muted">
|
||||
<scrm-label [labelKey]="getMessage()" [module]="module"></scrm-label>
|
||||
</small>
|
175
core/app/fields/relate/templates/edit/relate.component.spec.ts
Normal file
175
core/app/fields/relate/templates/edit/relate.component.spec.ts
Normal file
|
@ -0,0 +1,175 @@
|
|||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {Component} from '@angular/core';
|
||||
import {Field} from '@app-common/record/field.model';
|
||||
import {Record} from '@app-common/record/record.model';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {UserPreferenceStore} from '@store/user-preference/user-preference.store';
|
||||
import {userPreferenceStoreMock} from '@store/user-preference/user-preference.store.spec.mock';
|
||||
import {NumberFormatter} from '@services/formatters/number/number-formatter.service';
|
||||
import {numberFormatterMock} from '@services/formatters/number/number-formatter.spec.mock';
|
||||
import {DatetimeFormatter} from '@services/formatters/datetime/datetime-formatter.service';
|
||||
import {datetimeFormatterMock} from '@services/formatters/datetime/datetime-formatter.service.spec.mock';
|
||||
import {DateFormatter} from '@services/formatters/datetime/date-formatter.service';
|
||||
import {dateFormatterMock} from '@services/formatters/datetime/date-formatter.service.spec.mock';
|
||||
import {CurrencyFormatter} from '@services/formatters/currency/currency-formatter.service';
|
||||
import {currencyFormatterMock} from '@services/formatters/currency/currency-formatter.service.spec.mock';
|
||||
import {LanguageStore} from '@store/language/language.store';
|
||||
import {languageStoreMock} from '@store/language/language.store.spec.mock';
|
||||
import {RecordListStoreFactory} from '@store/record-list/record-list.store.factory';
|
||||
import {listStoreFactoryMock} from '@store/record-list/record-list.store.spec.mock';
|
||||
import {BrowserDynamicTestingModule} from '@angular/platform-browser-dynamic/testing';
|
||||
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {RelateEditFieldModule} from '@fields/relate/templates/edit/relate.module';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {interval} from 'rxjs';
|
||||
import {ModuleNameMapper} from '@services/navigation/module-name-mapper/module-name-mapper.service';
|
||||
import {moduleNameMapperMock} from '@services/navigation/module-name-mapper/module-name-mapper.service.spec.mock';
|
||||
|
||||
export const waitUntil = async (untilTruthy: Function): Promise<boolean> => {
|
||||
while (!untilTruthy()) {
|
||||
await interval(25).pipe(take(1)).toPromise();
|
||||
}
|
||||
// eslint-disable-next-line compat/compat
|
||||
return Promise.resolve(true);
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'relate-edit-field-test-host-component',
|
||||
template: '<scrm-relate-edit [field]="field" [record]="record"></scrm-relate-edit>'
|
||||
})
|
||||
class RelateEditFieldTestHostComponent {
|
||||
field: Field = {
|
||||
type: 'relate',
|
||||
value: 'Related Account',
|
||||
valueObject: {
|
||||
id: '123',
|
||||
name: 'Related Account',
|
||||
},
|
||||
definition: {
|
||||
module: 'accounts',
|
||||
// eslint-disable-next-line camelcase, @typescript-eslint/camelcase
|
||||
id_name: 'account_id',
|
||||
rname: 'name'
|
||||
}
|
||||
};
|
||||
|
||||
record: Record = {
|
||||
type: '',
|
||||
module: 'contacts',
|
||||
attributes: {
|
||||
// eslint-disable-next-line camelcase, @typescript-eslint/camelcase
|
||||
contact_id: '1'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe('RelateRecordEditFieldComponent', () => {
|
||||
let testHostComponent: RelateEditFieldTestHostComponent;
|
||||
let testHostFixture: ComponentFixture<RelateEditFieldTestHostComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
RelateEditFieldTestHostComponent,
|
||||
],
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
RelateEditFieldModule,
|
||||
BrowserDynamicTestingModule,
|
||||
NoopAnimationsModule
|
||||
],
|
||||
providers: [
|
||||
{provide: UserPreferenceStore, useValue: userPreferenceStoreMock},
|
||||
{provide: NumberFormatter, useValue: numberFormatterMock},
|
||||
{provide: DatetimeFormatter, useValue: datetimeFormatterMock},
|
||||
{provide: DateFormatter, useValue: dateFormatterMock},
|
||||
{provide: CurrencyFormatter, useValue: currencyFormatterMock},
|
||||
{provide: LanguageStore, useValue: languageStoreMock},
|
||||
{provide: RecordListStoreFactory, useValue: listStoreFactoryMock},
|
||||
{provide: ModuleNameMapper, useValue: moduleNameMapperMock},
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
testHostFixture = TestBed.createComponent(RelateEditFieldTestHostComponent);
|
||||
testHostComponent = testHostFixture.componentInstance;
|
||||
testHostFixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should create', () => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have value', async (done) => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
|
||||
testHostFixture.detectChanges();
|
||||
|
||||
const field = testHostFixture.nativeElement.getElementsByTagName('scrm-relate-edit')[0];
|
||||
|
||||
|
||||
await waitUntil(() => field.getElementsByTagName('tag-input').item(0));
|
||||
|
||||
const tagInput = field.getElementsByTagName('tag-input').item(0);
|
||||
|
||||
expect(field).toBeTruthy();
|
||||
|
||||
expect(tagInput).toBeTruthy();
|
||||
|
||||
const tag = tagInput.getElementsByTagName('tag').item(0);
|
||||
|
||||
expect(tag).toBeTruthy();
|
||||
|
||||
const tagText = tag.getElementsByClassName('tag__text').item(0);
|
||||
|
||||
expect(tagText.textContent).toContain('Related Account');
|
||||
|
||||
const deleteIcon = tagInput.getElementsByTagName('delete-icon').item(0);
|
||||
|
||||
expect(deleteIcon).toBeTruthy();
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should remove value', async (done) => {
|
||||
expect(testHostComponent).toBeTruthy();
|
||||
|
||||
testHostFixture.detectChanges();
|
||||
|
||||
const field = testHostFixture.nativeElement.getElementsByTagName('scrm-relate-edit')[0];
|
||||
|
||||
await testHostFixture.whenStable();
|
||||
await waitUntil(() => field.getElementsByTagName('tag-input').item(0));
|
||||
|
||||
const tagInput = field.getElementsByTagName('tag-input').item(0);
|
||||
|
||||
expect(field).toBeTruthy();
|
||||
|
||||
expect(tagInput).toBeTruthy();
|
||||
|
||||
let tag = tagInput.getElementsByTagName('tag').item(0);
|
||||
|
||||
expect(tag).toBeTruthy();
|
||||
|
||||
const tagText = tag.getElementsByClassName('tag__text').item(0);
|
||||
|
||||
expect(tagText.textContent).toContain('Related Account');
|
||||
|
||||
const deleteIcon = tagInput.getElementsByTagName('delete-icon').item(0);
|
||||
|
||||
expect(deleteIcon).toBeTruthy();
|
||||
|
||||
deleteIcon.click();
|
||||
|
||||
testHostFixture.detectChanges();
|
||||
|
||||
await waitUntil(() => !(tagInput.getElementsByTagName('tag').item(0)));
|
||||
|
||||
tag = tagInput.getElementsByTagName('tag').item(0);
|
||||
|
||||
expect(tag).toBeFalsy();
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
60
core/app/fields/relate/templates/edit/relate.component.ts
Normal file
60
core/app/fields/relate/templates/edit/relate.component.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import {Component, ViewChild} from '@angular/core';
|
||||
import {DataTypeFormatter} from '@services/formatters/data-type.formatter.service';
|
||||
import {LanguageStore} from '@store/language/language.store';
|
||||
import {TagInputComponent} from 'ngx-chips';
|
||||
import {RelateService} from '@services/record/relate/relate.service';
|
||||
import {BaseRelateComponent} from '@fields/base/base-relate.component';
|
||||
import {ModuleNameMapper} from '@services/navigation/module-name-mapper/module-name-mapper.service';
|
||||
|
||||
@Component({
|
||||
selector: 'scrm-relate-edit',
|
||||
templateUrl: './relate.component.html',
|
||||
styleUrls: [],
|
||||
providers: [RelateService]
|
||||
})
|
||||
export class RelateEditFieldComponent extends BaseRelateComponent {
|
||||
@ViewChild('tag') tag: TagInputComponent;
|
||||
|
||||
constructor(
|
||||
protected languages: LanguageStore,
|
||||
protected typeFormatter: DataTypeFormatter,
|
||||
protected relateService: RelateService,
|
||||
protected moduleNameMapper: ModuleNameMapper
|
||||
) {
|
||||
super(languages, typeFormatter, relateService, moduleNameMapper);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
onAdd(item): void {
|
||||
|
||||
if (item) {
|
||||
const relateName = this.getRelateFieldName();
|
||||
this.setValue(item.id, item[relateName]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setValue('', '');
|
||||
this.selectedValues = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
onRemove(): void {
|
||||
this.setValue('', '');
|
||||
this.selectedValues = [];
|
||||
|
||||
setTimeout(() => {
|
||||
this.tag.focus(true, true);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
protected setValue(id: string, relateValue: string): void {
|
||||
const relate = this.buildRelate(id, relateValue);
|
||||
this.field.value = relateValue;
|
||||
this.field.valueObject = relate;
|
||||
this.field.formControl.setValue(relateValue);
|
||||
}
|
||||
}
|
23
core/app/fields/relate/templates/edit/relate.module.ts
Normal file
23
core/app/fields/relate/templates/edit/relate.module.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {AppManagerModule} from '@base/app-manager/app-manager.module';
|
||||
import {RelateEditFieldComponent} from './relate.component';
|
||||
import {TagInputModule} from 'ngx-chips';
|
||||
import {LabelModule} from '@components/label/label.module';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {InlineLoadingSpinnerModule} from '@components/inline-loading-spinner/inline-loading-spinner.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [RelateEditFieldComponent],
|
||||
exports: [RelateEditFieldComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
AppManagerModule.forChild(RelateEditFieldComponent),
|
||||
TagInputModule,
|
||||
LabelModule,
|
||||
FormsModule,
|
||||
InlineLoadingSpinnerModule
|
||||
]
|
||||
})
|
||||
export class RelateEditFieldModule {
|
||||
}
|
|
@ -39,6 +39,7 @@ export interface FieldDefinition {
|
|||
inline_edit?: boolean;
|
||||
validation?: ValidationDefinition;
|
||||
validations?: ValidationDefinition[];
|
||||
template?: string;
|
||||
}
|
||||
|
||||
export interface FieldMetadata {
|
||||
|
@ -59,6 +60,7 @@ export interface Field {
|
|||
type: string;
|
||||
value?: string;
|
||||
valueList?: string[];
|
||||
valueObject?: any;
|
||||
name?: string;
|
||||
label?: string;
|
||||
labelKey?: string;
|
||||
|
|
|
@ -28,19 +28,19 @@ export class FieldManager {
|
|||
public buildField(record: Record, viewField: ViewFieldDefinition, language: LanguageStore = null): Field {
|
||||
|
||||
const definition = (viewField && viewField.fieldDefinition) || {} as FieldDefinition;
|
||||
const {value, valueList} = this.parseValue(viewField, definition, record);
|
||||
const {value, valueList, valueObject} = this.parseValue(viewField, definition, record);
|
||||
const {validators, asyncValidators} = this.getValidators(record, viewField);
|
||||
|
||||
return this.setupField(viewField, value, valueList, record, definition, validators, asyncValidators, language);
|
||||
return this.setupField(viewField, value, valueList, valueObject, record, definition, validators, asyncValidators, language);
|
||||
}
|
||||
|
||||
public buildFilterField(record: Record, viewField: ViewFieldDefinition, language: LanguageStore = null): Field {
|
||||
|
||||
const definition = (viewField && viewField.fieldDefinition) || {} as FieldDefinition;
|
||||
const {value, valueList} = this.parseValue(viewField, definition, record);
|
||||
const {value, valueList, valueObject} = this.parseValue(viewField, definition, record);
|
||||
const {validators, asyncValidators} = this.getFilterValidators(record, viewField);
|
||||
|
||||
return this.setupField(viewField, value, valueList, record, definition, validators, asyncValidators, language);
|
||||
return this.setupField(viewField, value, valueList, valueObject, record, definition, validators, asyncValidators, language);
|
||||
}
|
||||
|
||||
public getFieldLabel(label: string, module: string, language: LanguageStore): string {
|
||||
|
@ -52,7 +52,7 @@ export class FieldManager {
|
|||
viewField: ViewFieldDefinition,
|
||||
definition: FieldDefinition,
|
||||
record: Record
|
||||
): { value: string; valueList: string[] } {
|
||||
): { value: string; valueList: string[]; valueObject?: any } {
|
||||
|
||||
const type = (viewField && viewField.type) || '';
|
||||
const source = (definition && definition.source) || '';
|
||||
|
@ -65,6 +65,8 @@ export class FieldManager {
|
|||
value = '';
|
||||
} else if (type === 'relate' && source === 'non-db' && rname !== '') {
|
||||
value = record.attributes[viewName][rname];
|
||||
const valueObject = record.attributes[viewName];
|
||||
return {value, valueList, valueObject};
|
||||
} else {
|
||||
value = record.attributes[viewName];
|
||||
}
|
||||
|
@ -102,6 +104,7 @@ export class FieldManager {
|
|||
viewField: ViewFieldDefinition,
|
||||
value: string,
|
||||
valueList: string[],
|
||||
valueObject: any,
|
||||
record: Record,
|
||||
definition: FieldDefinition,
|
||||
validators: ValidatorFn[],
|
||||
|
@ -128,6 +131,10 @@ export class FieldManager {
|
|||
field.valueList = valueList;
|
||||
}
|
||||
|
||||
if (valueObject) {
|
||||
field.valueObject = valueObject;
|
||||
}
|
||||
|
||||
if (language) {
|
||||
field.label = this.getFieldLabel(viewField.label, record.module, language);
|
||||
}
|
||||
|
|
37
core/app/src/services/record/relate/relate.service.ts
Normal file
37
core/app/src/services/record/relate/relate.service.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {RecordListStoreFactory} from '@store/record-list/record-list.store.factory';
|
||||
import {RecordListStore} from '@store/record-list/record-list.store';
|
||||
import {map, shareReplay, take} from 'rxjs/operators';
|
||||
import {Record} from '@app-common/record/record.model';
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class RelateService {
|
||||
recordList: RecordListStore;
|
||||
|
||||
constructor(recordListStoreFactory: RecordListStoreFactory) {
|
||||
this.recordList = recordListStoreFactory.create();
|
||||
}
|
||||
|
||||
init(module: string): void {
|
||||
this.recordList.init(module, false);
|
||||
}
|
||||
|
||||
search(term: string, field: string): Observable<Record[]> {
|
||||
|
||||
const criteria = this.recordList.criteria;
|
||||
criteria.filters[field] = {
|
||||
field,
|
||||
operator: '=',
|
||||
values: [term]
|
||||
};
|
||||
|
||||
this.recordList.updateSearchCriteria(criteria, false);
|
||||
return this.recordList.load(false).pipe(
|
||||
map(value => value.records),
|
||||
take(1),
|
||||
shareReplay(1)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -164,6 +164,18 @@ export class RecordListStore implements StateStore, DataSource<Record>, Selectio
|
|||
return this.internalState.records;
|
||||
}
|
||||
|
||||
getRecord(id: string): Record {
|
||||
let record: Record = null;
|
||||
this.records.some(item => {
|
||||
if (item.id === id) {
|
||||
record = item;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean destroy
|
||||
*/
|
||||
|
|
|
@ -88,7 +88,9 @@ export class RecordManager {
|
|||
}
|
||||
|
||||
if (type === 'relate' && source === 'non-db' && rname !== '') {
|
||||
this.stagingState.attributes[fieldName][rname] = field.value;
|
||||
this.stagingState.attributes[fieldName][rname] = field.valueObject[rname];
|
||||
this.stagingState.attributes[fieldName].id = field.valueObject.id;
|
||||
this.stagingState.attributes[idName] = field.valueObject.id;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue