mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-12 04:23:04 +00:00
59334: added validation
This commit is contained in:
@@ -233,6 +233,9 @@
|
||||
"language": "Lang",
|
||||
"edit": "Edit"
|
||||
},
|
||||
"metadatafield": {
|
||||
"invalid": "Please choose a valid metadata field"
|
||||
},
|
||||
"notifications": {
|
||||
"outdated": {
|
||||
"title": "Changed outdated",
|
||||
@@ -241,6 +244,10 @@
|
||||
"discarded": {
|
||||
"title": "Changed discarded",
|
||||
"content": "Your changes were discarded. To reinstate your changes click the 'Undo' button"
|
||||
},
|
||||
"invalid": {
|
||||
"title": "Metadata invalid",
|
||||
"content": "Please make sure all fields are valid"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,10 +13,14 @@
|
||||
[(ngModel)]="metadata.key"
|
||||
(submitSuggestion)="update()"
|
||||
(clickSuggestion)="update()"
|
||||
(typeSuggestion)="update()"
|
||||
(findSuggestions)="findMetadataFieldSuggestions($event)"
|
||||
[formControl]="formControl"
|
||||
ngDefaultControl
|
||||
></ds-input-suggestions>
|
||||
</div>
|
||||
<small class="text-danger"
|
||||
*ngIf="!(valid | async)">{{"item.edit.metadata.metadatafield.invalid" | translate}}</small>
|
||||
</td>
|
||||
<td class="col-7">
|
||||
<div *ngIf="!(editable | async)">
|
||||
@@ -38,10 +42,14 @@
|
||||
</td>
|
||||
<td class="col-1 text-center">
|
||||
<div>
|
||||
<i *ngIf="canSetEditable() | async" class="fas fa-edit fa-fw text-primary" (click)="setEditable(true)"></i>
|
||||
<i *ngIf="canSetUneditable() | async" class="fas fa-check fa-fw text-success" (click)="setEditable(false)"></i>
|
||||
<i *ngIf="canRemove() | async" class="fas fa-trash-alt fa-fw text-danger" (click)="remove()"></i>
|
||||
<i *ngIf="canUndo() | async" class="fas fa-undo-alt fa-fw text-warning" (click)="removeChangesFromField()"></i>
|
||||
<i *ngIf="canSetEditable() | async" class="fas fa-edit fa-fw text-primary"
|
||||
(click)="setEditable(true)"></i>
|
||||
<i *ngIf="canSetUneditable() | async" class="fas fa-check fa-fw text-success"
|
||||
(click)="setEditable(false)"></i>
|
||||
<i *ngIf="canRemove() | async" class="fas fa-trash-alt fa-fw text-danger"
|
||||
(click)="remove()"></i>
|
||||
<i *ngIf="canUndo() | async" class="fas fa-undo-alt fa-fw text-warning"
|
||||
(click)="removeChangesFromField()"></i>
|
||||
</div>
|
||||
</td>
|
||||
</div>
|
@@ -0,0 +1 @@
|
||||
@import '../../../../../styles/variables.scss';
|
||||
|
@@ -11,6 +11,9 @@ import { FieldChangeType } from '../../../../core/data/object-updates/object-upd
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
|
||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||
import { inListValidator } from '../../../../shared/utils/validator.functions';
|
||||
import { getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||
import { FormControl, ValidationErrors, ValidatorFn } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-edit-in-place-field',
|
||||
@@ -21,7 +24,6 @@ import { ObjectUpdatesService } from '../../../../core/data/object-updates/objec
|
||||
* Component that displays a single metadatum of an item on the edit page
|
||||
*/
|
||||
export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
|
||||
/**
|
||||
* The current field, value and state of the metadatum
|
||||
*/
|
||||
@@ -39,22 +41,43 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
*/
|
||||
editable: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Emits whether or not this field is currently valid
|
||||
*/
|
||||
valid: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* The current suggestions for the metadatafield when editing
|
||||
*/
|
||||
metadataFieldSuggestions: BehaviorSubject<InputSuggestion[]> = new BehaviorSubject([]);
|
||||
|
||||
formControl: FormControl;
|
||||
|
||||
constructor(
|
||||
private metadataFieldService: RegistryService,
|
||||
private objectUpdatesService: ObjectUpdatesService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up an observable that keeps track of the current editable and valid state of this field
|
||||
* Also creates a form control object for the input suggestions
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.editable = this.objectUpdatesService.isEditable(this.route, this.metadata.uuid);
|
||||
this.valid = this.objectUpdatesService.isValid(this.route, this.metadata.uuid);
|
||||
this.findMetadataFields().pipe(take(1)).subscribe((metadataFields: string[]) => {
|
||||
const validator = inListValidator(metadataFields);
|
||||
this.formControl = new FormControl('', validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a new change update for this field to the object updates service
|
||||
*/
|
||||
update() {
|
||||
this.objectUpdatesService.saveChangeFieldUpdate(this.route, this.metadata);
|
||||
this.objectUpdatesService.setValidFieldUpdate(this.route, this.metadata.uuid, this.formControl.valid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,13 +102,6 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
this.objectUpdatesService.removeSingleFieldUpdate(this.route, this.metadata.uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up an observable that keeps track of the current editable state of this field
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.editable = this.objectUpdatesService.isEditable(this.route, this.metadata.uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current metadatafield based on the fieldUpdate input field
|
||||
*/
|
||||
@@ -115,6 +131,13 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
);
|
||||
}
|
||||
|
||||
findMetadataFields(): Observable<string[]> {
|
||||
return this.metadataFieldService.getAllMetadataFields().pipe(
|
||||
getSucceededRemoteData(),
|
||||
take(1),
|
||||
map((remoteData$) => remoteData$.payload.page.map((field: MetadataField) => field.toString())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user should be allowed to edit this field
|
||||
* @return an observable that emits true when the user should be able to edit this field and false when they should not
|
||||
|
@@ -0,0 +1,5 @@
|
||||
@import '../../../../styles/variables.scss';
|
||||
|
||||
.button-row .btn {
|
||||
min-width: $button-min-width;
|
||||
}
|
@@ -11,7 +11,7 @@ import {
|
||||
Identifiable
|
||||
} from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { Metadatum } from '../../../core/shared/metadatum.model';
|
||||
import { first, switchMap, tap } from 'rxjs/operators';
|
||||
import { first, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
@@ -20,6 +20,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-metadata',
|
||||
styleUrls: ['./item-metadata.component.scss'],
|
||||
templateUrl: './item-metadata.component.html',
|
||||
})
|
||||
/**
|
||||
@@ -114,6 +115,8 @@ export class ItemMetadataComponent implements OnInit {
|
||||
* Makes sure the new version of the item is rendered on the page
|
||||
*/
|
||||
submit() {
|
||||
this.isValid().pipe(first()).subscribe((isValid) => {
|
||||
if (isValid) {
|
||||
const metadata$: Observable<Identifiable[]> = this.objectUpdatesService.getUpdatedFields(this.route, this.item.metadata) as Observable<Metadatum[]>;
|
||||
metadata$.pipe(
|
||||
first(),
|
||||
@@ -130,6 +133,12 @@ export class ItemMetadataComponent implements OnInit {
|
||||
this.updates$ = this.objectUpdatesService.getFieldUpdates(this.route, this.item.metadata);
|
||||
}
|
||||
)
|
||||
} else {
|
||||
const title = this.translateService.instant('item.edit.metadata.notifications.invalid.title');
|
||||
const content = this.translateService.instant('item.edit.metadata.notifications.invalid.content');
|
||||
this.notificationsService.error(title, content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,4 +172,8 @@ export class ItemMetadataComponent implements OnInit {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private isValid() {
|
||||
return this.objectUpdatesService.isValidPage(this.route);
|
||||
}
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ const ITEM_EDIT_PATH = ':id/edit';
|
||||
{
|
||||
path: ITEM_EDIT_PATH,
|
||||
loadChildren: './edit-item-page/edit-item-page.module#EditItemPageModule',
|
||||
canActivate: [AuthenticatedGuard]
|
||||
// canActivate: [AuthenticatedGuard]
|
||||
}
|
||||
])
|
||||
],
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import {
|
||||
ActionReducerMap,
|
||||
createFeatureSelector,
|
||||
createSelector,
|
||||
MemoizedSelector
|
||||
} from '@ngrx/store';
|
||||
|
||||
import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reducer';
|
||||
@@ -14,8 +12,6 @@ import {
|
||||
objectUpdatesReducer,
|
||||
ObjectUpdatesState
|
||||
} from './data/object-updates/object-updates.reducer';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
import { AppState } from '../app.reducer';
|
||||
|
||||
export interface CoreState {
|
||||
'cache/object': ObjectCacheState,
|
||||
|
@@ -9,6 +9,7 @@ import { INotification } from '../../../shared/notifications/models/notification
|
||||
export const ObjectUpdatesActionTypes = {
|
||||
INITIALIZE_FIELDS: type('dspace/core/cache/object-updates/INITIALIZE_FIELDS'),
|
||||
SET_EDITABLE_FIELD: type('dspace/core/cache/object-updates/SET_EDITABLE_FIELD'),
|
||||
SET_VALID_FIELD: type('dspace/core/cache/object-updates/SET_VALID_FIELD'),
|
||||
ADD_FIELD: type('dspace/core/cache/object-updates/ADD_FIELD'),
|
||||
DISCARD: type('dspace/core/cache/object-updates/DISCARD'),
|
||||
REINSTATE: type('dspace/core/cache/object-updates/REINSTATE'),
|
||||
@@ -109,6 +110,33 @@ export class SetEditableFieldUpdateAction implements Action {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action to set the isValid state of an existing field in the ObjectUpdates state for a certain page url
|
||||
*/
|
||||
export class SetValidFieldUpdateAction implements Action {
|
||||
type = ObjectUpdatesActionTypes.SET_VALID_FIELD;
|
||||
payload: {
|
||||
url: string,
|
||||
uuid: string,
|
||||
isValid: boolean,
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new SetEditableFieldUpdateAction
|
||||
*
|
||||
* @param url
|
||||
* the unique url of the page
|
||||
* @param fieldUUID The UUID of the field of which
|
||||
* @param isValid The new isValid value for the field
|
||||
*/
|
||||
constructor(
|
||||
url: string,
|
||||
fieldUUID: string,
|
||||
isValid: boolean) {
|
||||
this.payload = { url, uuid: fieldUUID, isValid };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action to discard all existing updates in the ObjectUpdates state for a certain page url
|
||||
*/
|
||||
|
@@ -7,7 +7,7 @@ import {
|
||||
ObjectUpdatesActionTypes,
|
||||
ReinstateObjectUpdatesAction,
|
||||
RemoveFieldUpdateAction,
|
||||
RemoveObjectUpdatesAction, SetEditableFieldUpdateAction
|
||||
RemoveObjectUpdatesAction, SetEditableFieldUpdateAction, SetValidFieldUpdateAction
|
||||
} from './object-updates.actions';
|
||||
import { hasNoValue, hasValue } from '../../../shared/empty.util';
|
||||
|
||||
@@ -15,7 +15,8 @@ export const OBJECT_UPDATES_TRASH_PATH = '/trash';
|
||||
|
||||
export interface FieldState {
|
||||
editable: boolean,
|
||||
isNew: boolean
|
||||
isNew: boolean,
|
||||
isValid: boolean
|
||||
}
|
||||
|
||||
export interface FieldStates {
|
||||
@@ -46,8 +47,8 @@ export interface ObjectUpdatesState {
|
||||
[url: string]: ObjectUpdatesEntry;
|
||||
}
|
||||
|
||||
const initialFieldState = { editable: false, isNew: false };
|
||||
const initialNewFieldState = { editable: true, isNew: true };
|
||||
const initialFieldState = { editable: false, isNew: false, isValid: true };
|
||||
const initialNewFieldState = { editable: true, isNew: true, isValid: true };
|
||||
|
||||
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
||||
const initialState = Object.create(null);
|
||||
@@ -80,6 +81,9 @@ export function objectUpdatesReducer(state = initialState, action: ObjectUpdates
|
||||
case ObjectUpdatesActionTypes.SET_EDITABLE_FIELD: {
|
||||
return setEditableFieldUpdate(state, action as SetEditableFieldUpdateAction);
|
||||
}
|
||||
case ObjectUpdatesActionTypes.SET_VALID_FIELD: {
|
||||
return setValidFieldUpdate(state, action as SetValidFieldUpdateAction);
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
@@ -147,8 +151,8 @@ function discardObjectUpdates(state: any, action: DiscardObjectUpdatesAction) {
|
||||
Object.keys(pageState.fieldStates).forEach((uuid: string) => {
|
||||
const fieldState: FieldState = pageState.fieldStates[uuid];
|
||||
if (!fieldState.isNew) {
|
||||
/* After discarding we don't want the reset fields to stay editable */
|
||||
newFieldStates[uuid] = Object.assign({}, fieldState, { editable: false });
|
||||
/* After discarding we don't want the reset fields to stay editable or invalid */
|
||||
newFieldStates[uuid] = Object.assign({}, fieldState, { editable: false, isValid: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -215,7 +219,7 @@ function removeFieldUpdate(state: any, action: RemoveFieldUpdateAction) {
|
||||
/* If this field was added, just throw it away */
|
||||
delete newFieldStates[uuid];
|
||||
} else {
|
||||
newFieldStates[uuid] = Object.assign({}, newFieldStates[uuid], { editable: false });
|
||||
newFieldStates[uuid] = Object.assign({}, newFieldStates[uuid], { editable: false, isValid: true });
|
||||
}
|
||||
}
|
||||
newPageState = Object.assign({}, state[url], {
|
||||
@@ -243,7 +247,7 @@ function determineChangeType(oldType: FieldChangeType, newType: FieldChangeType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of a specific action's url and uuid to false or true
|
||||
* Set the editable state of a specific action's url and uuid to false or true
|
||||
* @param state The current state
|
||||
* @param action The action to perform on the current state
|
||||
*/
|
||||
@@ -264,6 +268,28 @@ function setEditableFieldUpdate(state: any, action: SetEditableFieldUpdateAction
|
||||
return Object.assign({}, state, { [url]: newPageState });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the isValid state of a specific action's url and uuid to false or true
|
||||
* @param state The current state
|
||||
* @param action The action to perform on the current state
|
||||
*/
|
||||
function setValidFieldUpdate(state: any, action: SetValidFieldUpdateAction) {
|
||||
const url: string = action.payload.url;
|
||||
const uuid: string = action.payload.uuid;
|
||||
const isValid: boolean = action.payload.isValid;
|
||||
|
||||
const pageState: ObjectUpdatesEntry = state[url];
|
||||
|
||||
const fieldState = pageState.fieldStates[uuid];
|
||||
const newFieldState = Object.assign({}, fieldState, { isValid });
|
||||
|
||||
const newFieldStates = Object.assign({}, pageState.fieldStates, { [uuid]: newFieldState });
|
||||
|
||||
const newPageState = Object.assign({}, pageState, { fieldStates: newFieldStates });
|
||||
|
||||
return Object.assign({}, state, { [url]: newPageState });
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to create an initial FieldStates object based on a list of Identifiable objects
|
||||
* @param fields Identifiable objects
|
||||
|
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||
import { coreSelector, CoreState } from '../../core.reducers';
|
||||
import {
|
||||
FieldState,
|
||||
FieldUpdates,
|
||||
Identifiable, OBJECT_UPDATES_TRASH_PATH,
|
||||
ObjectUpdatesEntry,
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
InitializeFieldsAction,
|
||||
ReinstateObjectUpdatesAction,
|
||||
RemoveFieldUpdateAction,
|
||||
SetEditableFieldUpdateAction
|
||||
SetEditableFieldUpdateAction, SetValidFieldUpdateAction
|
||||
} from './object-updates.actions';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util';
|
||||
@@ -102,6 +103,33 @@ export class ObjectUpdatesService {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check if a specific field is currently valid in the store
|
||||
* @param url The URL of the page on which the field resides
|
||||
* @param uuid The UUID of the field
|
||||
*/
|
||||
isValid(url: string, uuid: string): Observable<boolean> {
|
||||
const objectUpdates = this.getObjectEntry(url);
|
||||
return objectUpdates.pipe(
|
||||
filter((objectEntry) => hasValue(objectEntry.fieldStates[uuid])),
|
||||
map((objectEntry) => objectEntry.fieldStates[uuid].isValid
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check if a specific page is currently valid in the store
|
||||
* @param url The URL of the page
|
||||
*/
|
||||
isValidPage(url: string): Observable<boolean> {
|
||||
const objectUpdates = this.getObjectEntry(url);
|
||||
return objectUpdates.pipe(
|
||||
map((entry: ObjectUpdatesEntry) => {
|
||||
return Object.values(entry.fieldStates).findIndex((state: FieldState) => !state.isValid) < 0
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the saveFieldUpdate method with FieldChangeType.ADD
|
||||
* @param url The page's URL for which the changes are saved
|
||||
@@ -139,6 +167,16 @@ export class ObjectUpdatesService {
|
||||
this.store.dispatch(new SetEditableFieldUpdateAction(url, uuid, editable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a SetValidFieldUpdateAction to the store to set a field's isValid state
|
||||
* @param url The URL of the page on which the field resides
|
||||
* @param uuid The UUID of the field that should be set
|
||||
* @param valid The new value of isValid in the store for this field
|
||||
*/
|
||||
setValidFieldUpdate(url: string, uuid: string, valid: boolean) {
|
||||
this.store.dispatch(new SetValidFieldUpdateAction(url, uuid, valid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to dispatch an DiscardObjectUpdatesAction to the store
|
||||
* @param url The page's URL for which the changes should be discarded
|
||||
|
@@ -1,3 +1,3 @@
|
||||
<ds-form *ngIf="formModel"
|
||||
[formId]="'comcol-form-id'"
|
||||
[formModel]="formModel" (submitForm)="onSubmit()"></ds-form>
|
||||
[formModel]="formModel" (submitForm)="onSubmit()" (cancel)="onCancel()"></ds-form>
|
||||
|
@@ -50,10 +50,7 @@ describe('ComColFormComponent', () => {
|
||||
];
|
||||
|
||||
/* tslint:disable:no-empty */
|
||||
const locationStub = {
|
||||
back: () => {
|
||||
}
|
||||
};
|
||||
const locationStub = jasmine.createSpyObj('location', ['back']);
|
||||
/* tslint:enable:no-empty */
|
||||
|
||||
beforeEach(async(() => {
|
||||
@@ -112,4 +109,11 @@ describe('ComColFormComponent', () => {
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
describe('onCancel', () => {
|
||||
it('should call the back method on the Location service', () => {
|
||||
comp.onCancel();
|
||||
expect(locationStub.back).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -112,4 +112,8 @@ export class ComColFormComponent<T extends DSpaceObject> implements OnInit {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.location.back();
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,13 @@
|
||||
import {
|
||||
Component,
|
||||
ElementRef, EventEmitter, forwardRef,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
forwardRef,
|
||||
Input,
|
||||
OnChanges,
|
||||
Output,
|
||||
QueryList, SimpleChanges,
|
||||
QueryList,
|
||||
SimpleChanges,
|
||||
ViewChild,
|
||||
ViewChildren
|
||||
} from '@angular/core';
|
||||
@@ -19,6 +23,8 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
// Usage of forwardRef necessary https://github.com/angular/angular.io/issues/1151
|
||||
// tslint:disable-next-line:no-forward-ref
|
||||
useExisting: forwardRef(() => InputSuggestionsComponent),
|
||||
multi: true
|
||||
}
|
||||
@@ -28,7 +34,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
/**
|
||||
* Component representing a form with a autocomplete functionality
|
||||
*/
|
||||
export class InputSuggestionsComponent implements ControlValueAccessor {
|
||||
export class InputSuggestionsComponent implements ControlValueAccessor, OnChanges {
|
||||
/**
|
||||
* The suggestions that should be shown
|
||||
*/
|
||||
@@ -64,6 +70,11 @@ export class InputSuggestionsComponent implements ControlValueAccessor {
|
||||
*/
|
||||
@Output() clickSuggestion = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Output for when something is typed in the input field
|
||||
*/
|
||||
@Output() typeSuggestion = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Output for when new suggestions should be requested
|
||||
*/
|
||||
@@ -195,6 +206,7 @@ export class InputSuggestionsComponent implements ControlValueAccessor {
|
||||
this.findSuggestions.emit(data);
|
||||
}
|
||||
this.blockReopen = false;
|
||||
this.typeSuggestion.emit(data);
|
||||
}
|
||||
|
||||
onSubmit(data) {
|
||||
|
7
src/app/shared/utils/validator.functions.ts
Normal file
7
src/app/shared/utils/validator.functions.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { AbstractControl, ValidatorFn } from '@angular/forms';
|
||||
|
||||
export function inListValidator(list: string[]): ValidatorFn {
|
||||
return (control: AbstractControl): {[key: string]: any} | null => {
|
||||
const contains = list.indexOf(control.value) > 0;
|
||||
return contains ? null : {inList: {value: control.value}} };
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
$content-spacing: $spacer * 1.5;
|
||||
|
||||
$button-height: $input-btn-padding-y * 2 + $input-btn-line-height + calculateRem($input-btn-border-width*2);
|
||||
$button-min-width: 100px;
|
||||
|
||||
$card-height-percentage:98%;
|
||||
$card-thumbnail-height:240px;
|
||||
$dropdown-menu-max-height: 200px;
|
||||
|
Reference in New Issue
Block a user