Use editable fields for project details widget

pull/7743/head
Henriette Dinger 5 years ago
parent c77de05b7d
commit 581b75e3e6
  1. 21
      frontend/src/app/modules/grids/widgets/project-details/project-details.component.html
  2. 124
      frontend/src/app/modules/grids/widgets/project-details/project-details.component.ts

@ -9,10 +9,19 @@
</widget-header> </widget-header>
<div class="grid--widget-content"> <div class="grid--widget-content">
<div class="attributes-map" <edit-form *ngIf="(project$ | async) as project"
#contentContainer> [resource]="project">
</div> <div class="attributes-map">
<no-results *ngIf="noFields" <ng-container *ngFor="let cf of customFields">
[title]="text.noResults"> <div class="attributes-map--key">
</no-results> {{ cf.label }}
</div>
<div class="attributes-map--value">
<editable-attribute-field [resource]="project"
[fieldName]="cf.key">
</editable-attribute-field>
</div>
</ng-container>
</div>
</edit-form>
</div> </div>

@ -26,53 +26,50 @@
// See doc/COPYRIGHT.rdoc for more details. // See doc/COPYRIGHT.rdoc for more details.
// ++ // ++
import {Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, Injector, ViewChild, ElementRef} from '@angular/core'; import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
Injector,
OnInit,
ViewChild
} from '@angular/core';
import {AbstractWidgetComponent} from "app/modules/grids/widgets/abstract-widget.component"; import {AbstractWidgetComponent} from "app/modules/grids/widgets/abstract-widget.component";
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; import {I18nService} from "core-app/modules/common/i18n/i18n.service";
import {ProjectDmService} from "core-app/modules/hal/dm-services/project-dm.service"; import {ProjectDmService} from "core-app/modules/hal/dm-services/project-dm.service";
import {CurrentProjectService} from "core-components/projects/current-project.service"; import {CurrentProjectService} from "core-components/projects/current-project.service";
import {SchemaResource} from "core-app/modules/hal/resources/schema-resource"; import {SchemaResource} from "core-app/modules/hal/resources/schema-resource";
import {DisplayFieldContext, DisplayFieldService} from "core-app/modules/fields/display/display-field.service";
import {ProjectResource} from "core-app/modules/hal/resources/project-resource";
import {PortalCleanupService} from 'core-app/modules/fields/display/display-portal/portal-cleanup.service';
import {DisplayField} from "core-app/modules/fields/display/display-field.module";
import {WorkPackageViewHighlightingService} from "core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-highlighting.service";
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {ProjectCacheService} from "core-components/projects/project-cache.service"; import {ProjectCacheService} from "core-components/projects/project-cache.service";
import {Observable} from "rxjs";
export const emptyPlaceholder = '-'; import {ProjectResource} from "core-app/modules/hal/resources/project-resource";
import {HalResourceEditingService} from "core-app/modules/fields/edit/services/hal-resource-editing.service";
@Component({ @Component({
templateUrl: './project-details.component.html', templateUrl: './project-details.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
providers: [ providers: [
// required by the displayField service to render the fields HalResourceEditingService
PortalCleanupService,
WorkPackageViewHighlightingService,
IsolatedQuerySpace
] ]
}) })
export class WidgetProjectDetailsComponent extends AbstractWidgetComponent implements OnInit { export class WidgetProjectDetailsComponent extends AbstractWidgetComponent implements OnInit {
@ViewChild('contentContainer', { static: true }) readonly contentContainer:ElementRef; @ViewChild('contentContainer', { static: true }) readonly contentContainer:ElementRef;
public noFields = false; public customFields:{key:string, label:string}[] = [];
public project$:Observable<ProjectResource>;
public text = {
noResults: this.i18n.t('js.grid.widgets.project_details.no_results'),
};
constructor(protected readonly i18n:I18nService, constructor(protected readonly i18n:I18nService,
protected readonly injector:Injector, protected readonly injector:Injector,
protected readonly projectDm:ProjectDmService, protected readonly projectDm:ProjectDmService,
protected readonly projectCache:ProjectCacheService, protected readonly projectCache:ProjectCacheService,
protected readonly currentProject:CurrentProjectService, protected readonly currentProject:CurrentProjectService,
protected readonly displayField:DisplayFieldService, protected readonly cdRef:ChangeDetectorRef) {
protected readonly cdr:ChangeDetectorRef) {
super(i18n, injector); super(i18n, injector);
} }
ngOnInit() { ngOnInit() {
this.loadAndRender(); this.loadAndRender();
this.project$ = this.projectCache.requireAndStream(this.currentProject.id!);
} }
public get isEditable() { public get isEditable() {
@ -80,96 +77,25 @@ export class WidgetProjectDetailsComponent extends AbstractWidgetComponent imple
} }
private loadAndRender() { private loadAndRender() {
Promise.all( Promise.all([
[this.loadCurrentProject(), this.loadProjectSchema()
this.loadProjectSchema()] ])
) .then(([schema]) => {
.then(([project, schema]) => { this.setCustomFields(schema);
this.renderCFs(project, schema as SchemaResource);
this.redraw();
}); });
} }
private loadCurrentProject() {
return this.projectCache.require(this.currentProject.id as string);
}
public get isLoaded() {
return this.projectCache.state(this.currentProject.id as string).value;
}
private loadProjectSchema() { private loadProjectSchema() {
return this.projectDm.schema(); return this.projectDm.schema();
} }
private renderCFs(project:ProjectResource, schema:SchemaResource) { private setCustomFields(schema:SchemaResource) {
const cfFields = this.collectFieldsForCfs(project, schema);
this.noFields = cfFields.length === 0;
this.sortFieldsLexicographically(cfFields);
this.renderFields(cfFields);
}
private collectFieldsForCfs(project:ProjectResource, schema:SchemaResource) {
let displayFields:Array<DisplayField> = [];
// passing an arbitrary context to displayField.getField only to satisfy the interface
let context:DisplayFieldContext = {injector: this.injector, container: 'table', options: []};
Object.entries(schema).forEach(([key, keySchema]) => { Object.entries(schema).forEach(([key, keySchema]) => {
if (key.match(/customField\d+/)) { if (key.match(/customField\d+/)) {
let field = this.displayField.getField(project, key, keySchema, context); this.customFields.push({key: key, label: keySchema.name });
displayFields.push(field);
} }
}); });
return displayFields; this.cdRef.detectChanges();
}
private sortFieldsLexicographically(fields:Array<DisplayField>) {
fields.sort((a, b) => { return a.label.localeCompare(b.label); });
}
private renderFields(fields:Array<DisplayField>) {
this.contentContainer.nativeElement.innerHTML = '';
fields.forEach(field => {
this.renderKeyValue(field);
});
}
private renderKeyValue(field:DisplayField) {
this.contentContainer.nativeElement.appendChild(this.labelElement(field));
this.contentContainer.nativeElement.appendChild(this.valueElement(field));
}
private labelElement(field:DisplayField) {
const label = document.createElement('div');
label.classList.add('attributes-map--key');
label.innerText = field.label;
return label;
}
private valueElement(field:DisplayField) {
const value = document.createElement('div');
value.classList.add('attributes-map--value');
field.render(value, this.getText(field));
return value;
}
private getText(field:DisplayField):string {
if (field.isEmpty()) {
return emptyPlaceholder;
} else {
return field.valueString;
}
}
private redraw() {
this.cdr.detectChanges();
} }
} }

Loading…
Cancel
Save