Improved icons handler in chips.component.ts

This commit is contained in:
Giuseppe Digilio
2018-07-11 18:45:56 +02:00
parent 683c2f2f6b
commit c4f1738520
9 changed files with 191 additions and 142 deletions

View File

@@ -153,7 +153,13 @@
"recent-submissions": "Error fetching recent submissions",
"item": "Error fetching item",
"objects": "Error fetching objects",
"search-results": "Error fetching search results"
"search-results": "Error fetching search results",
"validation": {
"pattern": "This input is restricted by the current pattern: {{ pattern }}.",
"license": {
"notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission."
}
}
},
"form": {
"submit": "Submit",
@@ -168,11 +174,6 @@
"group-collapse": "Collapse",
"group-expand": "Expand",
"group-collapse-help": "Click here to collapse",
"group-expand-help": "Click here to expand and add more element",
"error": {
"validation": {
"pattern": "This input is restricted by the current pattern: {{ pattern }}."
}
}
"group-expand-help": "Click here to expand and add more element"
}
}

View File

@@ -2,25 +2,27 @@
<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>
<li [ngbTooltip]="tipContent"
triggers="manual"
#t="ngbTooltip"
class="nav-item mr-2 mb-1"
(click)="t.close()"
(dragstart)="onDragStart(t, i)"
(dragend)="onDragEnd(i)"
(mouseover)="showTooltip(t, i, c.display)"
(mouseout)="t.close()">
<li class="nav-item mr-2 mb-1"
(dragstart)="onDragStart(i)"
(dragend)="onDragEnd(i)">
<a class="flex-sm-fill text-sm-center nav-link active"
href="#"
[ngClass]="{'chip-selected disabled': (editable && c.editMode) || dragged == i}"
(click)="t.close();chipsSelected($event, i);">
(click)="chipsSelected($event, i);">
<span>
<ng-container *ngIf="c.hasIcons()">
<i *ngFor="let icon of c.icons; let i = index; let l = last"
<i *ngFor="let icon of c.icons; let l = last"
[ngbTooltip]="tipContent"
triggers="manual"
#t="ngbTooltip"
class="fa {{icon.style}}"
[class.mr-1]="!l"
[class.mr-2]="l"
aria-hidden="true"></i>
[class.text-muted]="!(icon.hasAuthority)"
aria-hidden="true"
(dragstart)="tooltip.close();"
(mouseover)="showTooltip(t, i, icon.metadata)"
(mouseout)="t.close()"></i>
</ng-container>
<p class="chip-label text-truncate d-table-cell">{{c.display}}</p><i class="fa fa-times ml-2" (click)="removeChips($event, i)"></i>
</span>

View File

@@ -9,16 +9,8 @@ import { ChipsComponent } from './chips.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { SortablejsModule } from 'angular-sortablejs';
import { By } from '@angular/platform-browser';
function createTestComponent<T>(html: string, type: { new(...args: any[]): T }): ComponentFixture<T> {
TestBed.overrideComponent(type, {
set: {template: html}
});
const fixture = TestBed.createComponent(type);
fixture.detectChanges();
return fixture as ComponentFixture<T>;
}
import { FormFieldMetadataValueObject } from '../form/builder/models/form-field-metadata-value.model';
import { createTestComponent, hasClass } from '../testing/utils';
describe('ChipsComponent test suite', () => {
@@ -51,6 +43,7 @@ describe('ChipsComponent test suite', () => {
}));
describe('', () => {
// synchronous beforeEach
beforeEach(() => {
html = `
@@ -67,7 +60,9 @@ describe('ChipsComponent test suite', () => {
it('should create Chips Component', inject([ChipsComponent], (app: ChipsComponent) => {
expect(app).toBeDefined();
}));
});
describe('when has items as string', () => {
beforeEach(() => {
chips = new Chips(['a', 'b', 'c']);
chipsFixture = TestBed.createComponent(ChipsComponent);
@@ -128,7 +123,7 @@ describe('ChipsComponent test suite', () => {
de.triggerEventHandler('dragstart', null);
expect(chipsComp.dragged).toBe(0)
expect(chipsComp.dragged).toBe(0);
}));
it('should update chips item order when drag and drop end', fakeAsync(() => {
@@ -137,17 +132,56 @@ describe('ChipsComponent test suite', () => {
de.triggerEventHandler('dragend', null);
expect(chipsComp.dragged).toBe(-1)
expect(chipsComp.dragged).toBe(-1);
expect(chipsComp.chips.updateOrder).toHaveBeenCalled();
}));
});
it('should show item tooltip on mouse over', fakeAsync(() => {
describe('when has items as object', () => {
beforeEach(() => {
const item = {
mainField: new FormFieldMetadataValueObject('main test', null, 'test001'),
relatedField: new FormFieldMetadataValueObject('related test', null, 'test002'),
otherRelatedField: new FormFieldMetadataValueObject('other related test')
};
const iconsConfig = {
mainField: 'fa-user',
relatedField: 'fa-user-alt',
otherRelatedField: 'fa-user-alt'
};
chips = new Chips([item], 'display', 'mainField', iconsConfig);
chipsFixture = TestBed.createComponent(ChipsComponent);
chipsComp = chipsFixture.componentInstance; // TruncatableComponent test instance
chipsComp.editable = true;
chipsComp.chips = chips;
chipsFixture.detectChanges();
});
it('should show icon for every field that has a configured icon', () => {
const de = chipsFixture.debugElement.query(By.css('li.nav-item'));
const icons = de.queryAll(By.css('i.fa'));
de.triggerEventHandler('mouseover', null);
expect(icons.length).toBe(4);
expect(chipsComp.tipText).toBe('a')
}));
});
it('should has text-muted on icon style when field value had not authority', () => {
const de = chipsFixture.debugElement.query(By.css('li.nav-item'));
const icons = de.queryAll(By.css('i.fa'));
expect(hasClass(icons[2].nativeElement, 'text-muted')).toBeTruthy();
});
it('should show tooltip on mouse over an icon', () => {
const de = chipsFixture.debugElement.query(By.css('li.nav-item'));
const icons = de.queryAll(By.css('i.fa'));
icons[0].triggerEventHandler('mouseover', null);
expect(chipsComp.tipText).toBe('main test')
});
});
});
// declare a test component

View File

@@ -65,8 +65,7 @@ export class ChipsComponent implements OnChanges {
}
}
onDragStart(tooltip: NgbTooltip, index) {
tooltip.close();
onDragStart(index) {
this.uploaderService.overrideDragOverPage();
this.dragged = index;
}
@@ -77,10 +76,16 @@ export class ChipsComponent implements OnChanges {
this.chips.updateOrder();
}
showTooltip(tooltip: NgbTooltip, index, content) {
showTooltip(tooltip: NgbTooltip, index, field?) {
tooltip.close();
if (!this.chips.getChipByIndex(index).editMode && this.dragged === -1) {
this.tipText = content;
const item = this.chips.getChipByIndex(index);
if (!item.editMode && this.dragged === -1) {
if (field) {
this.tipText = item.item[field].display;
} else {
this.tipText = item.display;
}
this.cdr.detectChanges();
tooltip.open();
}

View File

@@ -1,7 +1,9 @@
import { uniqueId } from 'lodash';
import { uniqueId, isObject } from 'lodash';
import { isNotEmpty } from '../../empty.util';
export interface ChipsItemIcon {
metadata: string;
hasAuthority: boolean;
style: string;
tooltip?: any;
}
@@ -54,15 +56,12 @@ export class ChipsItem {
private setDisplayText(): void {
let value = this.item;
if ( typeof this.item === 'object') {
if (isObject(this.item)) {
// Check If displayField is in an internal object
const obj = this.objToDisplay ? this.item[this.objToDisplay] : this.item;
const displayFieldBkp = 'value';
if (obj instanceof Object && obj && obj[this.fieldToDisplay]) {
value = obj[this.fieldToDisplay];
} else if (obj instanceof Object && obj && obj[displayFieldBkp]) {
value = obj[displayFieldBkp];
if (isObject(obj) && obj) {
value = obj[this.fieldToDisplay] || obj.value;
} else {
value = obj;
}

View File

@@ -1,9 +1,7 @@
import { findIndex, isEqual } from 'lodash';
import { findIndex, isEqual, isObject } from 'lodash';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ChipsItem, ChipsItemIcon } from './chips-item.model';
import { hasValue } from '../../empty.util';
import { FormFieldMetadataValueObject } from '../../form/builder/models/form-field-metadata-value.model';
import { AuthorityValueModel } from '../../../core/integration/models/authority-value.model';
export interface ChipsIconsConfig {
[metadata: string]: string;
@@ -99,16 +97,26 @@ export class Chips {
Object.keys(item)
.forEach((metadata) => {
const value = item[metadata];
if (hasValue(value)
&& (value instanceof FormFieldMetadataValueObject || value instanceof AuthorityValueModel)
&& ((value as FormFieldMetadataValueObject).authority || (value as AuthorityValueModel).id)
&& this.iconsConfig.hasOwnProperty(metadata)) {
if (hasValue(value) && isObject(value) && this.iconsConfig.hasOwnProperty(metadata)) {
const icon: ChipsItemIcon = {
let icon: ChipsItemIcon;
const hasAuthority: boolean = ((value.hasOwnProperty('authority') && value.authority)
|| (value.hasOwnProperty('id') && value.id)) ? true : false;
// Set icons
if ((this.displayObj && this.displayObj === metadata && hasAuthority)
|| (this.displayObj && this.displayObj !== metadata)) {
icon = {
metadata,
hasAuthority: hasAuthority,
style: this.iconsConfig[metadata]
};
}
if (icon) {
icons.push(icon);
}
}
});
return icons;

View File

@@ -34,14 +34,14 @@ describe('ChipsItem model test suite', () => {
});
it('should update icons', () => {
const icons: ChipsItemIcon[] = [{style: 'fa fa-plus'}];
const icons: ChipsItemIcon[] = [{metadata: 'test', hasAuthority: false, style: 'fa fa-plus'}];
item.updateIcons(icons);
expect(item.icons).toEqual(icons);
});
it('should return true if has icons', () => {
const icons: ChipsItemIcon[] = [{style: 'fa fa-plus'}];
const icons: ChipsItemIcon[] = [{metadata: 'test', hasAuthority: false, style: 'fa fa-plus'}];
item.updateIcons(icons);
const hasIcons = item.hasIcons();

View File

@@ -216,7 +216,7 @@ export abstract class FieldParser {
controlModel.errorMessages = Object.assign(
{},
controlModel.errorMessages,
{pattern: 'form.error.validation.pattern'});
{pattern: 'error.validation.pattern'});
}

View File

@@ -40,7 +40,7 @@ export class GroupFieldParser extends FieldParser {
const mandatoryFieldEntries: FormFieldMetadataValueObject[] = this.getInitFieldValues(modelConfiguration.mandatoryField);
mandatoryFieldEntries.forEach((entry, index) => {
const item = Object.create(null);
const listFields = modelConfiguration.relationFields.concat(modelConfiguration.mandatoryField);
const listFields = [modelConfiguration.mandatoryField].concat(modelConfiguration.relationFields);
listFields.forEach((fieldId) => {
const value = this.getInitFieldValue(0, index, [fieldId]);
item[fieldId] = isNotEmpty(value) ? value : PLACEHOLDER_PARENT_METADATA;