Skip to content

Commit

Permalink
feat(Input): implementa la funziona autocomplete nel component Input
Browse files Browse the repository at this point in the history
fix #77
  • Loading branch information
Mario Traetta authored and ciccio86 committed Sep 14, 2018
1 parent dc19c65 commit 809f60d
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 10 deletions.
16 changes: 15 additions & 1 deletion e2e/src/form-input/form-input.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { browser } from 'protractor';
import { browser, ElementFinder } from 'protractor';
import { FormInputPage } from './form-input.po';

describe('Form Input', () => {
Expand Down Expand Up @@ -195,4 +195,18 @@ describe('Form Input', () => {
expect(await page.hasPatternModelError()).toBeTruthy();
});

it('dovrebbe poter sfruttare la modalità autocomplete del tipo search', async () => {
await page.clickSearchRadio();
await page.typeInsideFormInput('p');

expect(await page.hasRelatedEntries()).toBeTruthy();

const relatedEntriesElements: ElementFinder[] = await page.getRelatedEntries();
const chosenEntryText = await relatedEntriesElements[0].getText();

await relatedEntriesElements[0].click();

expect(await page.getFormInputValue()).toBe(chosenEntryText);
});

});
31 changes: 25 additions & 6 deletions e2e/src/form-input/form-input.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ export class FormInputPage {
private readonly ID_RADIO_TYPE_EMAIL = 'radio-2';
private readonly ID_RADIO_TYPE_PASSWORD = 'radio-3';
private readonly ID_RADIO_TYPE_NUMBER = 'radio-4';
private readonly ID_RADIO_TYPE_SEARCH = 'radio-5';

private readonly ID_RADIO_ICON_NONE = 'radio-6';
private readonly ID_RADIO_ICON_FAVORITE = 'radio-7';
private readonly ID_RADIO_ICON_LINK = 'radio-8';

private readonly ID_RADIO_ICON_NONE = 'radio-7';
private readonly ID_RADIO_ICON_FAVORITE = 'radio-8';
private readonly ID_RADIO_ICON_LINK = 'radio-9';

// [for="checkbox-0"]
private readonly CSS_SELECTOR_LABEL_CHECKBOX_DISABLED = this.getLabelForAttribute(this.ID_CHECKBOX_DISABLED);
Expand Down Expand Up @@ -48,13 +50,16 @@ export class FormInputPage {
// [for="radio-4"]
private readonly CSS_SELECTOR_LABEL_RADIO_TYPE_NUMBER = this.getLabelForAttribute(this.ID_RADIO_TYPE_NUMBER);

// [for="radio-6"]
private readonly CSS_SELECTOR_LABEL_RADIO_ICON_NONE = this.getLabelForAttribute(this.ID_RADIO_ICON_NONE);
// [for="radio-5"]
private readonly CSS_SELECTOR_LABEL_RADIO_TYPE_SEARCH = this.getLabelForAttribute(this.ID_RADIO_TYPE_SEARCH);

// [for="radio-7"]
private readonly CSS_SELECTOR_LABEL_RADIO_ICON_FAVORITE = this.getLabelForAttribute(this.ID_RADIO_ICON_FAVORITE);
private readonly CSS_SELECTOR_LABEL_RADIO_ICON_NONE = this.getLabelForAttribute(this.ID_RADIO_ICON_NONE);

// [for="radio-8"]
private readonly CSS_SELECTOR_LABEL_RADIO_ICON_FAVORITE = this.getLabelForAttribute(this.ID_RADIO_ICON_FAVORITE);

// [for="radio-9"]
private readonly CSS_SELECTOR_LABEL_RADIO_ICON_LINK = this.getLabelForAttribute(this.ID_RADIO_ICON_LINK);

// Template Driven Validation
Expand Down Expand Up @@ -114,6 +119,10 @@ export class FormInputPage {
await element(by.css(this.CSS_SELECTOR_LABEL_RADIO_TYPE_EMAIL)).click();
}

async clickSearchRadio() {
await element(by.css(this.CSS_SELECTOR_LABEL_RADIO_TYPE_SEARCH)).click();
}

async clickNoIconRadio() {
await element(by.css(this.CSS_SELECTOR_LABEL_RADIO_ICON_NONE)).click();
}
Expand Down Expand Up @@ -250,4 +259,14 @@ export class FormInputPage {
async hasPatternModelError() {
return element(by.id(this.ID_ERROR_MDV_PATTERN)).isPresent();
}

async hasRelatedEntries() {
return await element(by.css('.autocomplete-wrap')).isPresent();
}

async getRelatedEntries() {
const list = element(by.css('.autocomplete-wrap'));
const entries = await list.all(by.tagName('li'));
return entries;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
[placeholder]="placeholder"
[ngClass]="{
'form-control' : !readonly,
'form-control-plaintext' : readonly
'form-control-plaintext' : readonly,
'autocomplete' : isAutocompletable()
}"
[attr.aria-labelledby]="note ? noteId : undefined"/>
<span class="btn-eye"
Expand All @@ -24,6 +25,11 @@
'eye-off' : isPasswordVisible
}">
</span>
<ul class="autocomplete-wrap" *ngIf="isAutocompletable()">
<li *ngFor="let entry of getRelatedEntries()" (click)="onEntryClick(entry)">
{{entry}}
</li>
</ul>
<label [attr.for]="id" [ngClass]="{ 'active' : isLabelActive }">
{{label}}
</label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ class PasswordInputComponent {
value = '';
}

/**
* Componente per testare una input di tipo search.
*/
@Component({
template: `
<div>
<it-input type="search" [(ngModel)]="value" [autoCompleteData]="autoCompleteData"></it-input>
</div>`
})
class SearchInputComponent {
value = '';
autoCompleteData = ['prova'];
}

function sendInput(fixture: ComponentFixture<any>, element: HTMLInputElement, text: string) {
element.value = text;
element.dispatchEvent(new Event('input'));
Expand All @@ -63,7 +77,8 @@ describe('FormInputComponent', () => {
FormInputComponent,
TextInputComponent,
NumberInputComponent,
PasswordInputComponent
PasswordInputComponent,
SearchInputComponent
]
})
.compileComponents();
Expand Down Expand Up @@ -200,4 +215,42 @@ describe('FormInputComponent', () => {
expect(inputElement.type).toBe('password');
});
});

describe('comportamenti base con type search', () => {
let fixture: ComponentFixture<SearchInputComponent>;
let inputDebugElement: DebugElement;
let inputNativeElement: HTMLElement;
let inputInstance: FormInputComponent;
let testComponent: SearchInputComponent;
let inputElement: HTMLInputElement;
let spanElement: HTMLSpanElement;

beforeEach(() => {
fixture = TestBed.createComponent(SearchInputComponent);
fixture.detectChanges();

inputDebugElement = fixture.debugElement.query(By.directive(FormInputComponent));
inputNativeElement = inputDebugElement.nativeElement;
inputInstance = inputDebugElement.componentInstance;
testComponent = fixture.debugElement.componentInstance;
inputElement = <HTMLInputElement>inputNativeElement.querySelector('input');
spanElement = <HTMLLabelElement>inputNativeElement.querySelector('span');
});

it('dovrebbe poter sfruttare la funzionalità di autocomplete', () => {
const newText = 'pr';
sendInput(fixture, inputElement, newText);
inputElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));

fixture.detectChanges();

const autocompleteList = <HTMLUListElement>inputNativeElement.querySelector('ul');
const autocompleteEntries = Array.from(autocompleteList.querySelectorAll('li'));
autocompleteEntries[0].click();

fixture.detectChanges();

expect(inputElement.value).toBe('prova');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export class FormInputComponent implements AfterContentInit, ControlValueAccesso

this._isPasswordMode = this._type === INPUT_TYPES.PASSWORD;
this._isPasswordVisible = false;
this._showAutocompletion = false;
}
private _type = INPUT_TYPES.TEXT;

Expand Down Expand Up @@ -144,6 +145,16 @@ export class FormInputComponent implements AfterContentInit, ControlValueAccesso
get value(): any { return this._inputElement.nativeElement.value; }
set value(value: any) { this._inputElement.nativeElement.value = value; }

/**
* Opzionale.
* Disponibile solo se il type è search.
* Indica la lista di elementi ricercabili su cui basare il sistema di autocompletamento della input
*/
@Input()
get autoCompleteData(): Array<string> { return this._autoCompleteData; }
set autoCompleteData(value: Array<string>) { this._autoCompleteData = value; }
private _autoCompleteData: Array<string>;

/**
* Evento emesso quando il valore dell'input cambia.
* Gli eventi di change sono emessi soltanto quando il valore cambia a causa dell'interazione dell'utente
Expand Down Expand Up @@ -178,6 +189,7 @@ export class FormInputComponent implements AfterContentInit, ControlValueAccesso
}
private _isPasswordVisible = false;

private _showAutocompletion = false;
private _isInitialized = false;
private _controlValueAccessorChangeFn: (value: any) => void = () => { };
private _onTouched: () => any = () => { };
Expand Down Expand Up @@ -220,6 +232,10 @@ export class FormInputComponent implements AfterContentInit, ControlValueAccesso
}

onInput() {
if (this._type === INPUT_TYPES.SEARCH && this.isAutocompletable() && !this._showAutocompletion) {
this._showAutocompletion = true;
}

this._emitChangeEvent();
this._controlValueAccessorChangeFn(this.value);
}
Expand All @@ -242,4 +258,38 @@ export class FormInputComponent implements AfterContentInit, ControlValueAccesso
return `${this.id}-note`;
}

getRelatedEntries() {
if (this.value && this._showAutocompletion) {
const lowercaseValue = this.value.toLowerCase();
const lowercaseData = this._autoCompleteData.map(string => {
return { original : string, lowercase : string.toLowerCase() };
});

const relatedEntries = [];
lowercaseData.forEach(lowercaseEntry => {
if ((lowercaseEntry.lowercase).includes(lowercaseValue)) {
relatedEntries.push(lowercaseEntry.original);
}
});

return relatedEntries;
} else {
return [];
}
}

isAutocompletable() {
if (this._autoCompleteData && this._type === INPUT_TYPES.SEARCH) {
return this._autoCompleteData.length > 0;
} else {
return false;
}
}

onEntryClick(entry) {
this.value = entry;
this._showAutocompletion = false;
this.onChange();
}

}
3 changes: 3 additions & 0 deletions projects/design-angular-kit/src/lib/models/InputType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ export const InputType = t.keyof({
email: null,
password: null,
number: null,
search: null
});

const TEXT = 'text';
const EMAIL = 'email';
const PASSWORD = 'password';
const NUMBER = 'number';
const SEARCH = 'search';

export const INPUT_TYPES = {
TEXT: TEXT,
EMAIL: EMAIL,
PASSWORD: PASSWORD,
NUMBER: NUMBER,
SEARCH: SEARCH
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ <h4 class="card-title">Interazione con Form Input</h4>

<div class="card-text">
<it-input id="interactive-input-0" name="interactive-input-example" [label]="label" [placeholder]="placeholder" [note]="note"
[disabled]="disabled" [readonly]="readOnly" [type]="type" [icon]="icon" [(ngModel)]="value" [note]="note">
[disabled]="disabled" [readonly]="readOnly" [type]="type" [icon]="icon" [(ngModel)]="value" [note]="note" [autoCompleteData]="autoCompleteData">
</it-input>

<div class="row">
Expand All @@ -23,6 +23,7 @@ <h5>Tipo Input</h5>
<it-radio-button id="radio-email" name="email" value="email" label="email"></it-radio-button>
<it-radio-button id="radio-password" name="password" value="password" label="password"></it-radio-button>
<it-radio-button id="radio-number" name="number" value="number" label="number"></it-radio-button>
<it-radio-button id="radio-search" name="search" value="search" label="search"></it-radio-button>
</it-radio-group>
</div>
<div class="form-check col-4">
Expand All @@ -34,6 +35,14 @@ <h5>Icona</h5>
</it-radio-group>
</div>
</div>

<div class="form-group" aria-labelledby="autocompleteInputTextHelp" *ngIf="type === 'search'">
<input type="text" class="form-control" id="autocompleteInputText"
[value]="autoCompleteData.join(', ')"
(input)="autoCompleteData = $event.target.value.split(',')">
<label class="active" for="autocompleteInputText">Stringhe per l'autocompletamento</label>
<small id="autocompleteInputTextHelp" class="form-text text-muted">da separare con una virgola</small>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,12 @@ export class FormInputExampleComponent {
}
private _hasNote = false;

get autoCompleteData(): Array<string> {
return this._autoCompleteData;
}
set autoCompleteData(value: Array<string>) {
this._autoCompleteData = value.map(string => string.trim());
}
private _autoCompleteData = ['prova', 'TeSt', 'l\'esempio'];

}

0 comments on commit 809f60d

Please sign in to comment.