mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-10 03:23:07 +00:00
Added possibility to show additional information to search results returned by form fields which provide search
This commit is contained in:
@@ -242,7 +242,14 @@
|
||||
"submit": "Submit",
|
||||
"cancel": "Cancel",
|
||||
"search": "Search",
|
||||
"search-help": "Click here to looking for an existing correspondence",
|
||||
"remove": "Remove",
|
||||
"clear": "Clear",
|
||||
"clear-help": "Click here to remove the selected value",
|
||||
"edit": "Edit",
|
||||
"edit-help": "Click here to edit the selected value",
|
||||
"save": "Save",
|
||||
"save-help": "Save changes",
|
||||
"first-name": "First name",
|
||||
"last-name": "Last name",
|
||||
"loading": "Loading...",
|
||||
@@ -251,7 +258,9 @@
|
||||
"group-collapse": "Collapse",
|
||||
"group-expand": "Expand",
|
||||
"group-collapse-help": "Click here to collapse",
|
||||
"group-expand-help": "Click here to expand and add more elements"
|
||||
"group-expand-help": "Click here to expand and add more element",
|
||||
"other-information": {
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"title": "Login",
|
||||
|
@@ -23,4 +23,8 @@ export class AuthorityValue extends IntegrationModel {
|
||||
hasValue(): boolean {
|
||||
return isNotEmpty(this.value);
|
||||
}
|
||||
|
||||
hasOtherInformation(): boolean {
|
||||
return isNotEmpty(this.otherInformation);
|
||||
}
|
||||
}
|
||||
|
@@ -1,15 +1,13 @@
|
||||
import { WorkspaceitemSectionFormObject } from './workspaceitem-section-form.model';
|
||||
import { WorkspaceitemSectionLicenseObject } from './workspaceitem-section-license.model';
|
||||
import { WorkspaceitemSectionUploadObject } from './workspaceitem-section-upload.model';
|
||||
import { isNotEmpty, isNotNull } from '../../../shared/empty.util';
|
||||
import { FormFieldLanguageValueObject } from '../../../shared/form/builder/models/form-field-language-value.model';
|
||||
import { isNotNull } from '../../../shared/empty.util';
|
||||
import { WorkspaceitemSectionRecycleObject } from './workspaceitem-section-recycle.model';
|
||||
import { WorkspaceitemSectionDetectDuplicateObject } from './workspaceitem-section-deduplication.model';
|
||||
import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||
|
||||
export class WorkspaceitemSectionsObject {
|
||||
[name: string]: WorkspaceitemSectionDataType;
|
||||
|
||||
}
|
||||
|
||||
export function isServerFormValue(obj: any): boolean {
|
||||
@@ -28,18 +26,16 @@ export function normalizeSectionData(obj: any) {
|
||||
if (typeof obj === 'object' && isServerFormValue(obj)) {
|
||||
// If authority property is set normalize as a FormFieldMetadataValueObject object
|
||||
/* NOTE: Data received from server could have authority property equal to null, but into form
|
||||
field's model is required a FormFieldMetadataValueObject object as field value, so double-check in
|
||||
field's parser and eventually instantiate it */
|
||||
// if (isNotEmpty(obj.authority)) {
|
||||
// result = new FormFieldMetadataValueObject(obj.value, obj.language, obj.authority, (obj.display || obj.value), obj.place, obj.confidence);
|
||||
// } else if (isNotEmpty(obj.language)) {
|
||||
// const languageValue = new FormFieldLanguageValueObject(obj.value, obj.language);
|
||||
// result = languageValue;
|
||||
// } else {
|
||||
// // Normalize as a string value
|
||||
// result = obj.value;
|
||||
// }
|
||||
result = new FormFieldMetadataValueObject(obj.value, obj.language, obj.authority, (obj.display || obj.value), obj.place, obj.confidence);
|
||||
field's model is required a FormFieldMetadataValueObject object as field value, so instantiate it */
|
||||
result = new FormFieldMetadataValueObject(
|
||||
obj.value,
|
||||
obj.language,
|
||||
obj.authority,
|
||||
(obj.display || obj.value),
|
||||
obj.place,
|
||||
obj.confidence,
|
||||
obj.otherInformation
|
||||
);
|
||||
} else if (Array.isArray(obj)) {
|
||||
result = [];
|
||||
obj.forEach((item, index) => {
|
||||
|
@@ -1,7 +1,12 @@
|
||||
<div [className]="'float-left w-100 ' + wrapperClass">
|
||||
<ul class="nav nav-pills d-flex flex-column flex-sm-row" [sortablejs]="chips.getChips()" [sortablejsOptions]="options">
|
||||
<ng-container *ngFor="let c of chips.getChips(); let i = index">
|
||||
<ng-template #tipContent>{{tipText}}</ng-template>
|
||||
<ng-template #tipContent>
|
||||
<p class="text-left p-0 m-0" *ngFor="let tip of tipText">
|
||||
{{tip}}
|
||||
</p>
|
||||
</ng-template>
|
||||
|
||||
<li class="nav-item mr-2 mb-1"
|
||||
#t="ngbTooltip"
|
||||
triggers="manual"
|
||||
|
@@ -7,6 +7,7 @@ import { isObject } from 'lodash';
|
||||
import { Chips } from './models/chips.model';
|
||||
import { ChipsItem } from './models/chips-item.model';
|
||||
import { UploaderService } from '../uploader/uploader.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-chips',
|
||||
@@ -25,9 +26,13 @@ export class ChipsComponent implements OnChanges {
|
||||
|
||||
options: SortablejsOptions;
|
||||
dragged = -1;
|
||||
tipText: string;
|
||||
tipText: string[];
|
||||
|
||||
constructor(
|
||||
private cdr: ChangeDetectorRef,
|
||||
private uploaderService: UploaderService,
|
||||
private translate: TranslateService) {
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef, private uploaderService: UploaderService) {
|
||||
this.options = {
|
||||
animation: 300,
|
||||
chosenClass: 'm-0',
|
||||
@@ -79,17 +84,30 @@ export class ChipsComponent implements OnChanges {
|
||||
|
||||
showTooltip(tooltip: NgbTooltip, index, field?) {
|
||||
tooltip.close();
|
||||
const item = this.chips.getChipByIndex(index);
|
||||
let textToDisplay: string;
|
||||
if (!item.editMode && this.dragged === -1) {
|
||||
const chipsItem = this.chips.getChipByIndex(index);
|
||||
const textToDisplay: string[] = [];
|
||||
if (!chipsItem.editMode && this.dragged === -1) {
|
||||
if (field) {
|
||||
textToDisplay = (isObject(item.item[field])) ? item.item[field].display : item.item[field];
|
||||
if (isObject(chipsItem.item[field])) {
|
||||
textToDisplay.push(chipsItem.item[field].display);
|
||||
if (chipsItem.item[field].hasOtherInformation()) {
|
||||
Object.keys(chipsItem.item[field].otherInformation)
|
||||
.forEach((otherField) => {
|
||||
this.translate.get('form.other-information.' + otherField)
|
||||
.subscribe((label) => {
|
||||
textToDisplay.push(label + ': ' + chipsItem.item[field].otherInformation[otherField]);
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
textToDisplay.push(chipsItem.item[field]);
|
||||
}
|
||||
} else {
|
||||
textToDisplay = item.display;
|
||||
textToDisplay.push(chipsItem.display);
|
||||
}
|
||||
|
||||
this.cdr.detectChanges();
|
||||
if (!item.hasIcons() || field) {
|
||||
if (!chipsItem.hasIcons() || field) {
|
||||
this.tipText = textToDisplay;
|
||||
tooltip.open();
|
||||
}
|
||||
|
@@ -56,7 +56,8 @@
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<div *ngIf="!chips.hasItems()">
|
||||
<ds-loading *ngIf="!chips" [showMessage]="false"></ds-loading>
|
||||
<div *ngIf="chips && !chips.hasItems()">
|
||||
<input type="text"
|
||||
class="border-0 form-control-plaintext tag-input mt-1 mb-1 pl-2 text-muted"
|
||||
readonly
|
||||
@@ -64,7 +65,7 @@
|
||||
value="{{'form.no-value' | translate}}">
|
||||
</div>
|
||||
<ds-chips
|
||||
*ngIf="chips.hasItems()"
|
||||
*ngIf="chips && chips.hasItems()"
|
||||
[chips]="chips"
|
||||
[editable]="true"
|
||||
(selected)="onChipSelected($event)"></ds-chips>
|
||||
|
@@ -9,8 +9,11 @@ import {
|
||||
Output,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { filter, flatMap, map, mergeMap, scan } from 'rxjs/operators';
|
||||
import { DynamicFormControlModel, DynamicFormGroupModel, DynamicInputModel } from '@ng-dynamic-forms/core';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
@@ -25,11 +28,10 @@ import { shrinkInOut } from '../../../../../animations/shrink';
|
||||
import { ChipsItem } from '../../../../../chips/models/chips-item.model';
|
||||
import { GlobalConfig } from '../../../../../../../config/global-config.interface';
|
||||
import { GLOBAL_CONFIG } from '../../../../../../../config';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { hasOnlyEmptyProperties } from '../../../../../object.util';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model';
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { IntegrationData } from '../../../../../../core/integration/integration-data';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-dynamic-group',
|
||||
@@ -59,6 +61,7 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
|
||||
@ViewChild('formRef') private formRef: FormComponent;
|
||||
|
||||
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||
private authorityService: AuthorityService,
|
||||
private formBuilderService: FormBuilderService,
|
||||
private formService: FormService,
|
||||
private cdr: ChangeDetectorRef) {
|
||||
@@ -75,7 +78,6 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
|
||||
} else {
|
||||
this.expandForm();
|
||||
}
|
||||
// this.formCollapsed = (isNotEmpty(value) && !(value.length === 1 && hasOnlyEmptyProperties(value[0]))) ? Observable.of(true) : Observable.of(false);
|
||||
});
|
||||
|
||||
this.formId = this.formService.getUniqueId(this.model.id);
|
||||
@@ -85,30 +87,10 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
|
||||
{},
|
||||
this.model.submissionScope,
|
||||
this.model.readOnly);
|
||||
const initChipsValue = this.model.isEmpty() ? [] : this.model.value;
|
||||
this.chips = new Chips(
|
||||
initChipsValue,
|
||||
'value',
|
||||
this.model.mandatoryField,
|
||||
this.EnvConfig.submission.metadata.icons);
|
||||
this.subs.push(
|
||||
this.chips.chipsItems
|
||||
.subscribe((subItems: any[]) => {
|
||||
const items = this.chips.getChipsItems();
|
||||
// Does not emit change if model value is equal to the current value
|
||||
if (!isEqual(items, this.model.value)) {
|
||||
// if ((isNotEmpty(items) && !this.model.isEmpty()) || (isEmpty(items) && !this.model.isEmpty())) {
|
||||
if (!(isEmpty(items) && this.model.isEmpty())) {
|
||||
this.model.valueUpdates.next(items);
|
||||
this.change.emit();
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
this.initChipsFromModelValue();
|
||||
}
|
||||
|
||||
isMandatoryFieldEmpty() {
|
||||
// formModel[0].group[0].value == null
|
||||
let res = true;
|
||||
this.formModel.forEach((row) => {
|
||||
const modelRow = row as DynamicFormGroupModel;
|
||||
@@ -136,11 +118,6 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
|
||||
|| this.selectedChipItem.item[model.name].value === PLACEHOLDER_PARENT_METADATA)
|
||||
? null
|
||||
: this.selectedChipItem.item[model.name];
|
||||
// if (value instanceof FormFieldMetadataValueObject || value instanceof AuthorityValueModel) {
|
||||
// model.valueUpdates.next(value.display);
|
||||
// } else {
|
||||
// model.valueUpdates.next(value);
|
||||
// }
|
||||
model.valueUpdates.next(value);
|
||||
});
|
||||
});
|
||||
@@ -226,6 +203,91 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
|
||||
return item;
|
||||
}
|
||||
|
||||
private initChipsFromModelValue() {
|
||||
|
||||
let initChipsValue$: Observable<any[]>;
|
||||
if (this.model.isEmpty()) {
|
||||
this.initChips([]);
|
||||
} else {
|
||||
initChipsValue$ = Observable.of(this.model.value);
|
||||
|
||||
// If authority
|
||||
this.subs.push(initChipsValue$.pipe(
|
||||
flatMap((valueModel) => {
|
||||
const returnList: Array<Observable<any>> = [];
|
||||
valueModel.forEach((valueObj) => {
|
||||
let returnObj = Object.create({});
|
||||
returnObj = Object.keys(valueObj).map((fieldName) => {
|
||||
let return$: Observable<any>;
|
||||
if (valueObj[fieldName].hasAuthority() && isNotEmpty(valueObj[fieldName].authority)) {
|
||||
const fieldId = fieldName.replace(/\./g, '_');
|
||||
const model = this.formBuilderService.findById(fieldId, this.formModel);
|
||||
const searchOptions: IntegrationSearchOptions = new IntegrationSearchOptions(
|
||||
(model as any).authorityOptions.scope,
|
||||
(model as any).authorityOptions.name,
|
||||
(model as any).authorityOptions.metadata,
|
||||
valueObj[fieldName].authority,
|
||||
(model as any).maxOptions,
|
||||
1);
|
||||
|
||||
return$ = this.authorityService.getEntryByValue(searchOptions)
|
||||
.map((result: IntegrationData) => result.payload[0]);
|
||||
} else {
|
||||
return$ = Observable.of(valueObj[fieldName]);
|
||||
}
|
||||
return return$.map((entry) => ({[fieldName]: entry}));
|
||||
});
|
||||
|
||||
returnList.push(Observable.combineLatest(returnObj));
|
||||
});
|
||||
return returnList;
|
||||
}),
|
||||
mergeMap((valueListObj: Observable<any>, index: number) => {
|
||||
return valueListObj.pipe(
|
||||
map((valueObj: any) => ({
|
||||
index: index, value: valueObj.reduce(
|
||||
(acc: any, value: any) => Object.assign({}, acc, value)
|
||||
)
|
||||
})
|
||||
)
|
||||
)
|
||||
}),
|
||||
scan((acc: any[], valueObj: any) => {
|
||||
if (acc.length === 0) {
|
||||
acc.push(valueObj.value);
|
||||
} else {
|
||||
acc.splice(valueObj.index, 0, valueObj.value);
|
||||
}
|
||||
return acc;
|
||||
}, []),
|
||||
filter((modelValues: any[]) => this.model.value.length === modelValues.length)
|
||||
).subscribe((modelValue) => {
|
||||
this.initChips(modelValue);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private initChips(initChipsValue) {
|
||||
this.chips = new Chips(
|
||||
initChipsValue,
|
||||
'value',
|
||||
this.model.mandatoryField,
|
||||
this.EnvConfig.submission.metadata.icons);
|
||||
this.subs.push(
|
||||
this.chips.chipsItems
|
||||
.subscribe((subItems: any[]) => {
|
||||
const items = this.chips.getChipsItems();
|
||||
// Does not emit change if model value is equal to the current value
|
||||
if (!isEqual(items, this.model.value)) {
|
||||
if (!(isEmpty(items) && this.model.isEmpty())) {
|
||||
this.model.valueUpdates.next(items);
|
||||
this.change.emit();
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
private resetForm() {
|
||||
if (this.formRef) {
|
||||
this.formService.resetForm(this.formRef.formGroup, this.formModel, this.formId);
|
||||
|
@@ -75,14 +75,31 @@
|
||||
*ngIf="optionsList && optionsList.length == 0"
|
||||
(click)="$event.stopPropagation(); clearFields(); sdRef.close();">{{'form.no-results' | translate}}
|
||||
</button>
|
||||
<button class="dropdown-item collection-item"
|
||||
<button class="dropdown-item lookup-item"
|
||||
*ngFor="let listEntry of optionsList"
|
||||
(click)="$event.stopPropagation(); onSelect(listEntry); sdRef.close();"
|
||||
title="{{ listEntry.display }}">
|
||||
{{listEntry.value}}
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="(listEntry.hasOtherInformation()) ? hasInfo : noInfo"
|
||||
[ngTemplateOutletContext]="{entry: listEntry}">
|
||||
</ng-container>
|
||||
</button>
|
||||
<div class="scrollable-dropdown-loading text-center" *ngIf="loading"><p>{{'form.loading' | translate}}</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ng-template #hasInfo let-entry="entry">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li class="list-item text-truncate text-primary font-weight-bold">{{entry.value}}</li>
|
||||
<li class="list-item text-truncate text-secondary" *ngFor="let item of entry.otherInformation | dsObjNgFor" >
|
||||
{{ 'form.other-information.' + item.key | translate }} : {{item.value}}
|
||||
</li>
|
||||
</ul>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #noInfo let-entry="entry">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li class="list-item text-truncate text-primary font-weight-bold">{{entry.value}}</li>
|
||||
</ul>
|
||||
</ng-template>
|
||||
|
@@ -20,11 +20,11 @@
|
||||
|
||||
/* align fa-spin */
|
||||
.left-addon .fa-spin {
|
||||
left: 0px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.right-addon .fa-spin {
|
||||
right: 0px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
/* add padding */
|
||||
@@ -55,3 +55,7 @@
|
||||
div {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.lookup-item {
|
||||
border-bottom: $dropdown-border-width solid $dropdown-border-color;
|
||||
}
|
||||
|
@@ -1,6 +1,25 @@
|
||||
<ng-template #rt let-r="result" let-t="term">
|
||||
{{ r.display}}
|
||||
<ng-template #rt let-listEntry="result" let-t="term">
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="(listEntry.hasOtherInformation()) ? hasInfo : noInfo"
|
||||
[ngTemplateOutletContext]="{entry: listEntry}">
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #hasInfo let-entry="entry">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li class="list-item text-truncate text-primary font-weight-bold">{{entry.value}}</li>
|
||||
<li class="list-item text-truncate text-secondary" *ngFor="let item of entry.otherInformation | dsObjNgFor" >
|
||||
{{ 'form.other-information.' + item.key | translate }} : {{item.value}}
|
||||
</li>
|
||||
</ul>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #noInfo let-entry="entry">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li class="list-item text-truncate text-primary font-weight-bold">{{entry.value}}</li>
|
||||
</ul>
|
||||
</ng-template>
|
||||
|
||||
<div class="position-relative right-addon">
|
||||
<i *ngIf="searching" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw text-primary position-absolute mt-1 p-0" aria-hidden="true"></i>
|
||||
<input class="form-control"
|
||||
|
@@ -16,6 +16,10 @@
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
:host /deep/ .dropdown-item {
|
||||
border-bottom: $dropdown-border-width solid $dropdown-border-color;
|
||||
}
|
||||
|
||||
:host /deep/ .dropdown-item.active,
|
||||
:host /deep/ .dropdown-item:active,
|
||||
:host /deep/ .dropdown-item:focus,
|
||||
|
@@ -10,6 +10,7 @@ export class FormFieldMetadataValueObject {
|
||||
place: number;
|
||||
closed: boolean;
|
||||
label: string;
|
||||
otherInformation: any;
|
||||
|
||||
constructor(value: any = null,
|
||||
language: any = null,
|
||||
@@ -17,6 +18,7 @@ export class FormFieldMetadataValueObject {
|
||||
display: string = null,
|
||||
place: number = 0,
|
||||
confidence: number = -1,
|
||||
otherInformation: any = null,
|
||||
metadata: string = null) {
|
||||
this.value = isNotNull(value) ? ((typeof value === 'string') ? value.trim() : value) : null;
|
||||
this.language = language;
|
||||
@@ -34,6 +36,8 @@ export class FormFieldMetadataValueObject {
|
||||
if (isNotEmpty(metadata)) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
this.otherInformation = otherInformation;
|
||||
}
|
||||
|
||||
hasAuthority(): boolean {
|
||||
@@ -43,4 +47,8 @@ export class FormFieldMetadataValueObject {
|
||||
hasValue(): boolean {
|
||||
return isNotEmpty(this.value);
|
||||
}
|
||||
|
||||
hasOtherInformation(): boolean {
|
||||
return isNotEmpty(this.otherInformation);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user