New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(list editor): Adds support for editing lists (DSP-741) #365
Merged
Merged
Changes from 11 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
a3501a2
feat(list editor): Adds support for editing lists
mdelez 971492d
Merge branch 'main' into wip/DSP-741-edit-list-item-attempt-2
mdelez 77c39f4
feat(list editor): Adds support for editing lists
mdelez e19ea70
feat(list editor): updates label placeholder when updating labels
mdelez 5d882a0
Merge branch 'main' into wip/DSP-741-edit-list-item-attempt-2
mdelez 929aea5
Merge branch 'main' into wip/DSP-741-edit-list-item-attempt-2
mdelez ff312af
test(edit-list-item): adds unit tests
mdelez 772ef27
Merge branch 'main' into wip/DSP-741-edit-list-item-attempt-2
mdelez cdf8ee7
Merge branch 'main' into wip/DSP-741-edit-list-item-attempt-2
mdelez daac998
Merge branch 'main' into wip/DSP-741-edit-list-item-attempt-2
mdelez bba5d7a
test: cleanup and add additional test for form validation
mdelez 2c88e7e
chore: adds method descriptions and updates a label to use the transl…
mdelez be62277
chore: adds more method descriptions
mdelez File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<dsp-progress-indicator *ngIf="loading"></dsp-progress-indicator> | ||
|
||
<div *ngIf="!loading" class="form-content list-info"> | ||
|
||
<!-- list label --> | ||
<dsp-string-literal-input | ||
[placeholder]="'Child node label'" | ||
[value]="labels" | ||
(dataChanged)="handleData($event, 'labels')"> | ||
</dsp-string-literal-input> | ||
<span class="invalid-form" *ngIf="formInvalidMessage">{{ formInvalidMessage }}</span> | ||
|
||
<br><br> | ||
|
||
<!-- list description / comment --> | ||
<dsp-string-literal-input | ||
[textarea]="true" | ||
[placeholder]="'Child node description'" | ||
[value]="comments" | ||
(dataChanged)="handleData($event, 'comments')" | ||
[language]="labels.length ? labels[0].language : 'en'"> | ||
</dsp-string-literal-input> | ||
|
||
<div class="form-panel form-action"> | ||
<span> | ||
<button | ||
mat-button | ||
type="button" | ||
(click)="closeDialog.emit()"> | ||
{{ 'appLabels.form.action.cancel' | translate }} | ||
</button> | ||
</span> | ||
<span class="fill-remaining-space"></span> | ||
<span> | ||
<button | ||
mat-raised-button | ||
type="submit" | ||
color="primary" | ||
[disabled]="saveButtonDisabled" | ||
(click)="updateChildNode()"> | ||
Update | ||
</button> | ||
</span> | ||
</div> | ||
</div> |
4 changes: 4 additions & 0 deletions
4
src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.invalid-form{ | ||
color: red; | ||
font-size: 11px; | ||
} |
152 changes: 152 additions & 0 deletions
152
src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import { Component, DebugElement, OnInit, ViewChild } from '@angular/core'; | ||
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { By } from '@angular/platform-browser'; | ||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | ||
import { ApiResponseData, ListNodeInfoResponse, ListsEndpointAdmin, UpdateChildNodeRequest } from '@dasch-swiss/dsp-js'; | ||
import { DspActionModule, DspApiConnectionToken, ProgressIndicatorComponent } from '@dasch-swiss/dsp-ui'; | ||
import { TranslateModule } from '@ngx-translate/core'; | ||
import { of } from 'rxjs'; | ||
import { AjaxResponse } from 'rxjs/ajax'; | ||
import { EditListItemComponent } from './edit-list-item.component'; | ||
|
||
/** | ||
* Test host component to simulate parent component. | ||
*/ | ||
@Component({ | ||
template: `<app-edit-list-item #editListItem [iri]="iri" [projectIri]="projectIri"></app-edit-list-item>` | ||
}) | ||
class TestHostComponent implements OnInit { | ||
|
||
@ViewChild('editListItem') editListItem: EditListItemComponent; | ||
|
||
iri = 'http://rdfh.ch/lists/0001/otherTreeList01'; | ||
|
||
projectIri = 'http://rdfh.ch/projects/0001'; | ||
|
||
constructor() {} | ||
|
||
ngOnInit() { | ||
} | ||
|
||
} | ||
|
||
describe('EditListItemComponent', () => { | ||
let testHostComponent: TestHostComponent; | ||
let testHostFixture: ComponentFixture<TestHostComponent>; | ||
let editListItemComponentDe: DebugElement; | ||
let formInvalidMessageDe: DebugElement; | ||
|
||
beforeEach(async(() => { | ||
|
||
const listsEndpointSpyObj = { | ||
admin: { | ||
listsEndpoint: jasmine.createSpyObj('listsEndpoint', ['getListNodeInfo', 'updateChildNode']) | ||
} | ||
}; | ||
|
||
TestBed.configureTestingModule({ | ||
declarations: [ | ||
EditListItemComponent, | ||
TestHostComponent, | ||
ProgressIndicatorComponent, | ||
], | ||
imports: [ | ||
BrowserAnimationsModule, | ||
DspActionModule, | ||
TranslateModule.forRoot() | ||
], | ||
providers: [ | ||
{ | ||
provide: DspApiConnectionToken, | ||
useValue: listsEndpointSpyObj | ||
} | ||
] | ||
}) | ||
.compileComponents(); | ||
})); | ||
|
||
beforeEach(() => { | ||
const dspConnSpy = TestBed.inject(DspApiConnectionToken); | ||
|
||
(dspConnSpy.admin.listsEndpoint as jasmine.SpyObj<ListsEndpointAdmin>).getListNodeInfo.and.callFake( | ||
() => { | ||
const response = new ListNodeInfoResponse(); | ||
response.nodeinfo.id = 'http://rdfh.ch/lists/0001/otherTreeList01'; | ||
response.nodeinfo.labels = [{'value': 'Tree list node 01', 'language': 'en'}]; | ||
response.nodeinfo.comments = [{'value': 'My comment', 'language': 'en'}]; | ||
return of(ApiResponseData.fromAjaxResponse({response} as AjaxResponse)); | ||
} | ||
); | ||
|
||
testHostFixture = TestBed.createComponent(TestHostComponent); | ||
testHostComponent = testHostFixture.componentInstance; | ||
testHostFixture.detectChanges(); | ||
|
||
expect(testHostComponent).toBeTruthy(); | ||
|
||
const hostCompDe = testHostFixture.debugElement; | ||
editListItemComponentDe = hostCompDe.query(By.directive(EditListItemComponent)); | ||
expect(editListItemComponentDe).toBeTruthy(); | ||
}); | ||
|
||
it('should assign labels and comments', () => { | ||
const dspConnSpy = TestBed.inject(DspApiConnectionToken); | ||
expect(testHostComponent.editListItem.labels).toEqual([{'value': 'Tree list node 01', 'language': 'en'}]); | ||
expect(testHostComponent.editListItem.comments).toEqual([{'value': 'My comment', 'language': 'en'}]); | ||
expect(dspConnSpy.admin.listsEndpoint.getListNodeInfo).toHaveBeenCalledTimes(1); | ||
expect(dspConnSpy.admin.listsEndpoint.getListNodeInfo).toHaveBeenCalledWith('http://rdfh.ch/lists/0001/otherTreeList01'); | ||
|
||
}); | ||
|
||
it('should update labels when the value changes', () => { | ||
expect(testHostComponent.editListItem.labels).toEqual([{'value': 'Tree list node 01', 'language': 'en'}]); | ||
testHostComponent.editListItem.handleData([{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}], 'labels'); | ||
expect(testHostComponent.editListItem.labels).toEqual([{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}]); | ||
expect(testHostComponent.editListItem.saveButtonDisabled).toBeFalsy(); | ||
testHostComponent.editListItem.handleData([], 'labels'); | ||
expect(testHostComponent.editListItem.saveButtonDisabled).toBeTruthy(); | ||
testHostFixture.detectChanges(); | ||
formInvalidMessageDe = editListItemComponentDe.query(By.css('span.invalid-form')); | ||
expect(formInvalidMessageDe.nativeElement.innerText).toEqual('A label is required.'); | ||
}); | ||
|
||
it('should update comments when the value changes', () => { | ||
expect(testHostComponent.editListItem.comments).toEqual([{'value': 'My comment', 'language': 'en'}]); | ||
testHostComponent.editListItem.handleData([{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}], 'comments'); | ||
expect(testHostComponent.editListItem.comments).toEqual([{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}]); | ||
expect(testHostComponent.editListItem.saveButtonDisabled).toBeFalsy(); | ||
testHostComponent.editListItem.handleData([], 'comments'); | ||
expect(testHostComponent.editListItem.saveButtonDisabled).toBeFalsy(); | ||
}); | ||
|
||
it('should update the child node info', () => { | ||
const dspConnSpy = TestBed.inject(DspApiConnectionToken); | ||
|
||
testHostComponent.editListItem.handleData([{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}], 'labels'); | ||
testHostComponent.editListItem.handleData([{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}], 'comments'); | ||
|
||
(dspConnSpy.admin.listsEndpoint as jasmine.SpyObj<ListsEndpointAdmin>).updateChildNode.and.callFake( | ||
() => { | ||
const response = new ListNodeInfoResponse(); | ||
response.nodeinfo.id = 'http://rdfh.ch/lists/0001/otherTreeList01'; | ||
response.nodeinfo.labels = [{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}]; | ||
response.nodeinfo.comments = [{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}]; | ||
|
||
expect(testHostComponent.editListItem.labels).toEqual(response.nodeinfo.labels); | ||
expect(testHostComponent.editListItem.comments).toEqual(response.nodeinfo.comments); | ||
|
||
return of(ApiResponseData.fromAjaxResponse({response} as AjaxResponse)); | ||
} | ||
); | ||
|
||
const childNodeUpdateData: UpdateChildNodeRequest = new UpdateChildNodeRequest(); | ||
childNodeUpdateData.projectIri = testHostComponent.editListItem.projectIri; | ||
childNodeUpdateData.listIri = testHostComponent.editListItem.iri; | ||
childNodeUpdateData.labels = testHostComponent.editListItem.labels; | ||
childNodeUpdateData.comments = testHostComponent.editListItem.comments; | ||
|
||
testHostComponent.editListItem.updateChildNode(); | ||
expect(dspConnSpy.admin.listsEndpoint.updateChildNode).toHaveBeenCalledTimes(1); | ||
expect(dspConnSpy.admin.listsEndpoint.updateChildNode).toHaveBeenCalledWith(childNodeUpdateData); | ||
}); | ||
}); |
113 changes: 113 additions & 0 deletions
113
src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; | ||
import { ApiResponseData, ApiResponseError, ChildNodeInfoResponse, KnoraApiConnection, List, ListNodeInfo, ListNodeInfoResponse, StringLiteral, UpdateChildNodeRequest } from '@dasch-swiss/dsp-js'; | ||
import { DspApiConnectionToken } from '@dasch-swiss/dsp-ui'; | ||
|
||
@Component({ | ||
selector: 'app-edit-list-item', | ||
templateUrl: './edit-list-item.component.html', | ||
styleUrls: ['./edit-list-item.component.scss'] | ||
}) | ||
export class EditListItemComponent implements OnInit { | ||
loading: boolean; | ||
|
||
@Input() iri: string; | ||
|
||
@Input() projectIri: string; | ||
|
||
@Output() closeDialog: EventEmitter<List | ListNodeInfo> = new EventEmitter<List>(); | ||
|
||
list: ListNodeInfo; | ||
|
||
labels: StringLiteral[]; | ||
comments: StringLiteral[]; | ||
|
||
/** | ||
* error checking on the following fields | ||
*/ | ||
formErrors = { | ||
label: { | ||
'required': 'A label is required.' | ||
} | ||
}; | ||
|
||
/** | ||
* in case of an API error | ||
*/ | ||
errorMessage: any; | ||
|
||
saveButtonDisabled = false; | ||
|
||
formInvalidMessage: string; | ||
|
||
constructor(@Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection) { } | ||
|
||
ngOnInit(): void { | ||
this.loading = true; | ||
|
||
// get list | ||
this._dspApiConnection.admin.listsEndpoint.getListNodeInfo(this.iri).subscribe( | ||
(response: ApiResponseData<ListNodeInfoResponse>) => { | ||
this.list = response.body.nodeinfo; | ||
this.buildForm(response.body.nodeinfo); | ||
}, | ||
(error: ApiResponseError) => { | ||
console.error(error); | ||
} | ||
); | ||
} | ||
|
||
buildForm(list: ListNodeInfo): void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it be possible to add some description for each method, and possibly for tricky code lines? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added in 2c88e7e |
||
|
||
this.labels = []; | ||
this.comments = []; | ||
|
||
if (list && list.id) { | ||
this.labels = list.labels; | ||
this.comments = list.comments; | ||
} | ||
|
||
this.loading = false; | ||
} | ||
|
||
handleData(data: StringLiteral[], type: string) { | ||
switch (type) { | ||
case 'labels': | ||
this.labels = data; | ||
break; | ||
|
||
case 'comments': | ||
this.comments = data; | ||
break; | ||
} | ||
|
||
if (this.labels.length === 0) { | ||
// invalid form, don't let user submit | ||
this.saveButtonDisabled = true; | ||
this.formInvalidMessage = this.formErrors.label.required; | ||
} else { | ||
this.saveButtonDisabled = false; | ||
this.formInvalidMessage = null; | ||
} | ||
} | ||
|
||
updateChildNode() { | ||
|
||
const childNodeUpdateData: UpdateChildNodeRequest = new UpdateChildNodeRequest(); | ||
childNodeUpdateData.projectIri = this.projectIri; | ||
childNodeUpdateData.listIri = this.iri; | ||
childNodeUpdateData.labels = this.labels; | ||
childNodeUpdateData.comments = this.comments.length > 0 ? this.comments : []; | ||
|
||
this._dspApiConnection.admin.listsEndpoint.updateChildNode(childNodeUpdateData).subscribe( | ||
(response: ApiResponseData<ChildNodeInfoResponse>) => { | ||
this.loading = false; | ||
this.closeDialog.emit(response.body.nodeinfo); | ||
}, | ||
(error: ApiResponseError) => { | ||
this.errorMessage = error; | ||
this.loading = false; | ||
} | ||
); | ||
} | ||
|
||
} |
32 changes: 18 additions & 14 deletions
32
src/app/project/list/list-item-form/list-item-form.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,34 @@ | ||
<!-- add new node item --> | ||
<div class="new-list-item medium-field" *ngIf="parentIri && projectIri"> | ||
<dsp-string-literal-input class="list-item-label" [placeholder]="placeholder" [value]="[]" | ||
(dataChanged)="handleData($event)" [language]="language" (enter)="submitData()"> | ||
(dataChanged)="handleData($event)" [language]="language" (enter)="createChildNode()"> | ||
</dsp-string-literal-input> | ||
<button *ngIf="!loading" mat-icon-button matSuffix [disabled]="!labels && !labels?.length" class="add-node-btn" | ||
(click)="submitData()"> | ||
(click)="createChildNode()"> | ||
<mat-icon> | ||
add | ||
</mat-icon> | ||
</button> | ||
<dsp-progress-indicator [status]="0" *ngIf="loading" class="progress-indicator"></dsp-progress-indicator> | ||
</div> | ||
|
||
<!-- edit node item --> | ||
<div class="list-item medium-field" *ngIf="!(parentIri && projectIri) && labels"> | ||
<!-- TODO: at the moment (2019-09-12) we can't modify a node; the api doesn't support it; this is why we set the input to readonly --> | ||
<!-- node item --> | ||
<div class="list-item medium-field" | ||
*ngIf="!(parentIri && projectIri) && labels" | ||
(mouseenter)="mouseEnter()" | ||
(mouseleave)="mouseLeave()"> | ||
<dsp-string-literal-input class="list-item-label" | ||
[placeholder]="labels | dspStringifyStringLiteral:'all' | dspTruncate: 128" [value]="labels" [readonly]="true" | ||
(dataChanged)="handleData($event)" [language]="language" (touched)="toggleBtn($event)"> | ||
(dataChanged)="handleData($event)" [language]="language"> | ||
</dsp-string-literal-input> | ||
<!-- TODO: asap Knora api is ready to update single nodes set [disabled]="labels.length === 0" --> | ||
<button *ngIf="!loading && updateData" type="submit" mat-icon-button matSuffix class="edit-node-btn" | ||
[disabled]="labels.length === 0" (click)="submitData(); updateData = !updateData;"> | ||
<mat-icon> | ||
check | ||
</mat-icon> | ||
</button> | ||
<dsp-progress-indicator [status]="0" *ngIf="loading" class="progress-indicator"></dsp-progress-indicator> | ||
<div class="action-bubble" *ngIf="showActionBubble" [@simpleFadeAnimation]="'in'"> | ||
<div class="button-container"> | ||
<button mat-button | ||
class="edit" | ||
title="edit" | ||
(click)="$event.stopPropagation(); openDialog('editListNode', labels[0].value, iri)"> | ||
<mat-icon>edit</mat-icon> | ||
</button> | ||
</div> | ||
</div> | ||
</div> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you used
{{ 'appLabels.form.action.cancel' | translate }}
line 30, should use the format{{ 'appLabels.form.action.update' | translate }}
here as well?! It exists in theen.json
fileThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was André's code that I moved around. I changed the text to use the translate pipe for 'update' in 2c88e7e.