bug fixing

This commit is contained in:
lotte
2019-02-25 09:57:46 +01:00
parent 3c104a460b
commit 35be122eea
13 changed files with 93 additions and 80 deletions

View File

@@ -175,7 +175,7 @@
}, },
"view": { "view": {
"head": "View Item", "head": "View Item",
"title": "Item Edit - Item" "title": "Item Edit - View"
}, },
"curate": { "curate": {
"head": "Curate", "head": "Curate",

View File

@@ -7,19 +7,20 @@
<div *ngIf="(editable | async)" class="field-container"> <div *ngIf="(editable | async)" class="field-container">
<ds-input-suggestions [suggestions]="(metadataFieldSuggestions | async)" <ds-input-suggestions [suggestions]="(metadataFieldSuggestions | async)"
[(ngModel)]="metadata.key" [(ngModel)]="metadata.key"
(submitSuggestion)="update(suggestionControl.control)" (submitSuggestion)="update(suggestionControl)"
(clickSuggestion)="update(suggestionControl.control)" (clickSuggestion)="update(suggestionControl)"
(typeSuggestion)="update(suggestionControl.control)" (typeSuggestion)="update(suggestionControl)"
(blur)="checkValidity(suggestionControl.control)" (dsClickOutside)="checkValidity(suggestionControl)"
(findSuggestions)="findMetadataFieldSuggestions($event)" (findSuggestions)="findMetadataFieldSuggestions($event)"
#suggestionControl="ngModel" #suggestionControl="ngModel"
[dsInListValidator]="metadataFields | async" [dsInListValidator]="metadataFields"
[valid]="(valid | async)" [valid]="(valid | async) !== false"
dsAutoFocus autoFocusSelector=".suggestion_input" dsAutoFocus autoFocusSelector=".suggestion_input"
[ngModelOptions]="{standalone: true}"
></ds-input-suggestions> ></ds-input-suggestions>
</div> </div>
<small class="text-danger" <small class="text-danger"
*ngIf="!(valid | async)">{{"item.edit.metadata.metadatafield.invalid" | translate}}</small> *ngIf="(valid | async) === false">{{"item.edit.metadata.metadatafield.invalid" | translate}}</small>
</div> </div>
</td> </td>
<td class="w-100"> <td class="w-100">

View File

@@ -11,7 +11,7 @@ import { FieldChangeType } from '../../../../core/data/object-updates/object-upd
import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer'; import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { getSucceededRemoteData } from '../../../../core/shared/operators'; import { getSucceededRemoteData } from '../../../../core/shared/operators';
import { FormControl } from '@angular/forms'; import { NgModel } from '@angular/forms';
@Component({ @Component({
// tslint:disable-next-line:component-selector // tslint:disable-next-line:component-selector
@@ -27,14 +27,22 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
* The current field, value and state of the metadatum * The current field, value and state of the metadatum
*/ */
@Input() fieldUpdate: FieldUpdate; @Input() fieldUpdate: FieldUpdate;
/** /**
* The current url of this page * The current url of this page
*/ */
@Input() url: string; @Input() url: string;
/**
* List of strings with all metadata field keys available
*/
@Input() metadataFields: string[];
/** /**
* The metadatum of this field * The metadatum of this field
*/ */
metadata: Metadatum; metadata: Metadatum;
/** /**
* Emits whether or not this field is currently editable * Emits whether or not this field is currently editable
*/ */
@@ -50,11 +58,6 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
*/ */
metadataFieldSuggestions: BehaviorSubject<InputSuggestion[]> = new BehaviorSubject([]); metadataFieldSuggestions: BehaviorSubject<InputSuggestion[]> = new BehaviorSubject([]);
/**
* List of strings with all metadata field keys available
*/
metadataFields: Observable<string[]>;
constructor( constructor(
private metadataFieldService: RegistryService, private metadataFieldService: RegistryService,
private objectUpdatesService: ObjectUpdatesService, private objectUpdatesService: ObjectUpdatesService,
@@ -67,26 +70,26 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
ngOnInit(): void { ngOnInit(): void {
this.editable = this.objectUpdatesService.isEditable(this.url, this.metadata.uuid); this.editable = this.objectUpdatesService.isEditable(this.url, this.metadata.uuid);
this.valid = this.objectUpdatesService.isValid(this.url, this.metadata.uuid); this.valid = this.objectUpdatesService.isValid(this.url, this.metadata.uuid);
this.metadataFields = this.findMetadataFields()
} }
/** /**
* Sends a new change update for this field to the object updates service * Sends a new change update for this field to the object updates service
*/ */
update(control?: FormControl) { update(ngModel?: NgModel) {
this.objectUpdatesService.saveChangeFieldUpdate(this.url, this.metadata); this.objectUpdatesService.saveChangeFieldUpdate(this.url, this.metadata);
if (hasValue(control)) { if (hasValue(ngModel)) {
this.checkValidity(control); this.checkValidity(ngModel);
} }
} }
/** /**
* Method to check the validity of a form control * Method to check the validity of a form control
* @param control The form control to check * @param ngModel
*/ */
private checkValidity(control: FormControl) { private checkValidity(ngModel: NgModel) {
control.updateValueAndValidity(); ngModel.control.setValue(ngModel.viewModel);
this.objectUpdatesService.setValidFieldUpdate(this.url, this.metadata.uuid, control.valid); ngModel.control.updateValueAndValidity();
this.objectUpdatesService.setValidFieldUpdate(this.url, this.metadata.uuid, ngModel.control.valid);
} }
/** /**
@@ -124,6 +127,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
* @param query The query to look for * @param query The query to look for
*/ */
findMetadataFieldSuggestions(query: string): void { findMetadataFieldSuggestions(query: string): void {
if (isNotEmpty(query)) {
this.metadataFieldService.queryMetadataFields(query).pipe( this.metadataFieldService.queryMetadataFields(query).pipe(
// getSucceededRemoteData(), // getSucceededRemoteData(),
take(1), take(1),
@@ -134,20 +138,13 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
return { return {
displayValue: field.toString().split('.').join('.&#8203;'), displayValue: field.toString().split('.').join('.&#8203;'),
value: field.toString() value: field.toString()
} };
}) })
) )
); );
} else {
this.metadataFieldSuggestions.next([]);
} }
/**
* Method to request all metadata fields and convert them to a list of strings
*/
findMetadataFields(): Observable<string[]> {
return this.metadataFieldService.getAllMetadataFields().pipe(
getSucceededRemoteData(),
take(1),
map((remoteData$) => remoteData$.payload.page.map((field: MetadataField) => field.toString())));
} }
/** /**

View File

@@ -33,6 +33,7 @@
<tr *ngFor="let updateValue of ((updates$ | async)| dsObjectValues); trackBy: trackUpdate" <tr *ngFor="let updateValue of ((updates$ | async)| dsObjectValues); trackBy: trackUpdate"
ds-edit-in-place-field ds-edit-in-place-field
[fieldUpdate]="updateValue || {}" [fieldUpdate]="updateValue || {}"
[metadataFields]="metadataFields$ | async"
[url]="url" [url]="url"
[ngClass]="{ [ngClass]="{
'table-warning': updateValue.changeType === 0, 'table-warning': updateValue.changeType === 0,

View File

@@ -11,12 +11,14 @@ import {
Identifiable Identifiable
} from '../../../core/data/object-updates/object-updates.reducer'; } from '../../../core/data/object-updates/object-updates.reducer';
import { Metadatum } from '../../../core/shared/metadatum.model'; import { Metadatum } from '../../../core/shared/metadatum.model';
import { first, map, switchMap, tap } from 'rxjs/operators'; import { first, map, switchMap, take, tap } from 'rxjs/operators';
import { getSucceededRemoteData } from '../../../core/shared/operators'; import { getSucceededRemoteData } from '../../../core/shared/operators';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { RegistryService } from '../../../core/registry/registry.service';
import { MetadataField } from '../../../core/metadata/metadatafield.model';
@Component({ @Component({
selector: 'ds-item-metadata', selector: 'ds-item-metadata',
@@ -47,6 +49,11 @@ export class ItemMetadataComponent implements OnInit {
private notitifactionPrefix = 'item.edit.metadata.notifications.'; private notitifactionPrefix = 'item.edit.metadata.notifications.';
/**
* Observable with a list of strings with all existing metadata field keys
*/
metadataFields$: Observable<string[]>;
constructor( constructor(
private itemService: ItemDataService, private itemService: ItemDataService,
private objectUpdatesService: ObjectUpdatesService, private objectUpdatesService: ObjectUpdatesService,
@@ -54,7 +61,8 @@ export class ItemMetadataComponent implements OnInit {
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private translateService: TranslateService, private translateService: TranslateService,
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
private route: ActivatedRoute private route: ActivatedRoute,
private metadataFieldService: RegistryService,
) { ) {
} }
@@ -63,6 +71,7 @@ export class ItemMetadataComponent implements OnInit {
* Set up and initialize all fields * Set up and initialize all fields
*/ */
ngOnInit(): void { ngOnInit(): void {
this.metadataFields$ = this.findMetadataFields()
this.route.parent.data.pipe(map((data) => data.item)) this.route.parent.data.pipe(map((data) => data.item))
.pipe( .pipe(
first(), first(),
@@ -208,4 +217,14 @@ export class ItemMetadataComponent implements OnInit {
return this.translateService.instant(this.notitifactionPrefix + key + '.content'); return this.translateService.instant(this.notitifactionPrefix + key + '.content');
} }
/**
* Method to request all metadata fields and convert them to a list of strings
*/
findMetadataFields(): Observable<string[]> {
return this.metadataFieldService.getAllMetadataFields().pipe(
getSucceededRemoteData(),
take(1),
map((remoteData$) => remoteData$.payload.page.map((field: MetadataField) => field.toString())));
}
} }

View File

@@ -78,7 +78,7 @@ const initialFieldState = { editable: false, isNew: false, isValid: true };
/** /**
* Initial state for a newly added field * Initial state for a newly added field
*/ */
const initialNewFieldState = { editable: true, isNew: true, isValid: true }; const initialNewFieldState = { editable: true, isNew: true, isValid: undefined };
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`) // Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
const initialState = Object.create(null); const initialState = Object.create(null);

View File

@@ -2,13 +2,12 @@
[action]="action" (keydown)="onKeydown($event)" [action]="action" (keydown)="onKeydown($event)"
(keydown.arrowdown)="shiftFocusDown($event)" (keydown.arrowdown)="shiftFocusDown($event)"
(keydown.arrowup)="shiftFocusUp($event)" (keydown.esc)="close()" (keydown.arrowup)="shiftFocusUp($event)" (keydown.esc)="close()"
(dsClickOutside)="close()"> (dsClickOutside)="close();">
<input #inputField type="text" [(ngModel)]="value" [name]="name" <input #inputField type="text" [(ngModel)]="value" [name]="name"
class="form-control suggestion_input" class="form-control suggestion_input"
[ngClass]="{'is-invalid': !valid}" [ngClass]="{'is-invalid': !valid}"
[dsDebounce]="debounceTime" (onDebounce)="find($event)" [dsDebounce]="debounceTime" (onDebounce)="find($event)"
[placeholder]="placeholder" [placeholder]="placeholder"
(blur)="blur.emit($event);"
[ngModelOptions]="{standalone: true}" autocomplete="off"/> [ngModelOptions]="{standalone: true}" autocomplete="off"/>
<input type="submit" class="d-none"/> <input type="submit" class="d-none"/>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}"> <div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">

View File

@@ -85,11 +85,6 @@ export class InputSuggestionsComponent implements ControlValueAccessor, OnChange
*/ */
@Output() findSuggestions = new EventEmitter(); @Output() findSuggestions = new EventEmitter();
/**
* Emits event when the input field loses focus
*/
@Output() blur = new EventEmitter();
/** /**
* Emits true when the list of suggestions should be shown * Emits true when the list of suggestions should be shown
*/ */
@@ -119,9 +114,14 @@ export class InputSuggestionsComponent implements ControlValueAccessor, OnChange
*/ */
_value: string; _value: string;
/** Fields needed to add ngModel */
@Input() disabled = false;
propagateChange = (_: any) => { propagateChange = (_: any) => {
/* Empty implementation */ /* Empty implementation */
}; };
propagateTouch = (_: any) => {
/* Empty implementation */
};
/** /**
* When any of the inputs change, check if we should still show the suggestions * When any of the inputs change, check if we should still show the suggestions
@@ -214,9 +214,9 @@ export class InputSuggestionsComponent implements ControlValueAccessor, OnChange
find(data) { find(data) {
if (!this.blockReopen) { if (!this.blockReopen) {
this.findSuggestions.emit(data); this.findSuggestions.emit(data);
this.typeSuggestion.emit(data);
} }
this.blockReopen = false; this.blockReopen = false;
this.typeSuggestion.emit(data);
} }
onSubmit(data) { onSubmit(data) {
@@ -230,11 +230,11 @@ export class InputSuggestionsComponent implements ControlValueAccessor, OnChange
} }
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
/* no implementation */ this.propagateTouch = fn;
} }
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
/* no implementation */ this.disabled = isDisabled;
} }
writeValue(value: any): void { writeValue(value: any): void {

View File

@@ -14,7 +14,6 @@ export class AutoFocusDirective implements AfterViewInit {
ngAfterViewInit() { ngAfterViewInit() {
if (isNotEmpty(this.autoFocusSelector)) { if (isNotEmpty(this.autoFocusSelector)) {
return this.el.nativeElement.querySelector(this.autoFocusSelector).focus(); return this.el.nativeElement.querySelector(this.autoFocusSelector).focus();
} else { } else {
return this.el.nativeElement.focus(); return this.el.nativeElement.focus();
} }

View File

@@ -16,9 +16,11 @@ export class ClickOutsideDirective {
constructor(private _elementRef: ElementRef) { constructor(private _elementRef: ElementRef) {
} }
@HostListener('document:click', ['$event.target']) @HostListener('document:click')
public onClick(targetElement) { public onClick() {
const clickedInside = this._elementRef.nativeElement.contains(targetElement); const hostElement = this._elementRef.nativeElement;
const focusElement = hostElement.ownerDocument.activeElement;
const clickedInside = hostElement.contains(focusElement);
if (!clickedInside) { if (!clickedInside) {
this.dsClickOutside.emit(null); this.dsClickOutside.emit(null);
} }

View File

@@ -25,12 +25,6 @@ export class DebounceDirective implements OnInit, OnDestroy {
@Input() @Input()
public dsDebounce = 500; public dsDebounce = 500;
/**
* True if no changes have been made to the input field's value
*/
@Input()
private isFirstChange = true;
/** /**
* Subject to unsubscribe from * Subject to unsubscribe from
*/ */
@@ -47,11 +41,9 @@ export class DebounceDirective implements OnInit, OnDestroy {
this.model.valueChanges.pipe( this.model.valueChanges.pipe(
takeUntil(this.subject), takeUntil(this.subject),
debounceTime(this.dsDebounce), debounceTime(this.dsDebounce),
distinctUntilChanged(),) distinctUntilChanged())
.subscribe((modelValue) => { .subscribe((modelValue) => {
if (this.isFirstChange) { if (this.model.dirty) {
this.isFirstChange = false;
} else {
this.onDebounce.emit(modelValue); this.onDebounce.emit(modelValue);
} }
}); });

View File

@@ -24,9 +24,6 @@ export class InListValidator implements Validator {
* @param c The FormControl * @param c The FormControl
*/ */
validate(c: FormControl): ValidationErrors | null { validate(c: FormControl): ValidationErrors | null {
if (this.dsInListValidator !== null) {
return inListValidator(this.dsInListValidator)(c); return inListValidator(this.dsInListValidator)(c);
} }
return null;
}
} }

View File

@@ -1,4 +1,5 @@
import { AbstractControl, ValidatorFn } from '@angular/forms'; import { AbstractControl, ValidatorFn } from '@angular/forms';
import { isNotEmpty } from '../empty.util';
/** /**
* Returns a validator function to check if the control's value is in a given list * Returns a validator function to check if the control's value is in a given list
@@ -6,6 +7,11 @@ import { AbstractControl, ValidatorFn } from '@angular/forms';
*/ */
export function inListValidator(list: string[]): ValidatorFn { export function inListValidator(list: string[]): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => { return (control: AbstractControl): { [key: string]: any } | null => {
const contains = list.indexOf(control.value) > -1; const hasValue = isNotEmpty(control.value);
return contains ? null : {inList: {value: control.value}} }; let inList = true;
if (isNotEmpty(list)) {
inList = list.indexOf(control.value) > -1;
}
return (hasValue && inList) ? null : { inList: { value: control.value } }
};
} }