refactor(searchbar): clean up searchbar code, add more properties

added autocomplete, autocorrect, spellcheck, and type as properties
that get passed to the input.

BREAKING CHANGES:

Searchbar events now return the event, not the Searchbar.

references #6177
closes #5996
This commit is contained in:
Brandy Carney
2016-06-13 15:43:51 -04:00
parent d6b7d5dd9b
commit eb4261c323
7 changed files with 159 additions and 132 deletions

View File

@ -148,7 +148,7 @@ ion-searchbar {
// Searchbar Focused // Searchbar Focused
// ----------------------------------------- // -----------------------------------------
.searchbar-focused { .searchbar-has-focus {
.searchbar-ios-cancel { .searchbar-ios-cancel {
flex: 0 0 auto; flex: 0 0 auto;
@ -177,7 +177,7 @@ ion-searchbar {
} }
} }
.searchbar-focused .searchbar-ios-cancel { .searchbar-has-focus .searchbar-ios-cancel {
padding-left: 8px; padding-left: 8px;
} }

View File

@ -130,7 +130,7 @@ ion-searchbar {
// Searchbar Focused // Searchbar Focused
// ----------------------------------------- // -----------------------------------------
.searchbar-focused:not(.searchbar-hide-cancel) { .searchbar-has-focus:not(.searchbar-hide-cancel) {
.searchbar-search-icon { .searchbar-search-icon {
display: none; display: none;
} }

View File

@ -46,6 +46,6 @@ ion-searchbar {
min-height: 0; min-height: 0;
} }
.searchbar-has-value.searchbar-focused .searchbar-clear-icon { .searchbar-has-value.searchbar-has-focus .searchbar-clear-icon {
display: block; display: block;
} }

View File

@ -1,10 +1,7 @@
import {ElementRef, Component, Directive, Host, HostBinding, HostListener, ViewChild, Input, Output, EventEmitter, Optional, ViewEncapsulation} from '@angular/core'; import {Component, Directive, ElementRef, EventEmitter, HostBinding, Input, Optional, Output, ViewChild, ViewEncapsulation} from '@angular/core';
import {NgControl} from '@angular/common'; import {NgControl} from '@angular/common';
import {Button} from '../button/button';
import {Config} from '../../config/config'; import {Config} from '../../config/config';
import {Icon} from '../icon/icon';
import {Ion} from '../ion';
import {isPresent} from '../../util/util'; import {isPresent} from '../../util/util';
@ -41,39 +38,40 @@ export class SearchbarInput {
@Component({ @Component({
selector: 'ion-searchbar', selector: 'ion-searchbar',
host: { host: {
'[class.searchbar-has-value]': 'value', '[class.searchbar-has-value]': '_value',
'[class.searchbar-hide-cancel]': 'hideCancelButton' '[class.searchbar-hide-cancel]': 'hideCancelButton'
}, },
template: template:
'<div class="searchbar-input-container">' + '<div class="searchbar-input-container">' +
'<button (click)="cancelSearchbar()" (mousedown)="cancelSearchbar()" [hidden]="hideCancelButton" clear dark class="searchbar-md-cancel">' + '<button (click)="cancelSearchbar($event)" (mousedown)="cancelSearchbar($event)" [hidden]="hideCancelButton" clear dark class="searchbar-md-cancel">' +
'<ion-icon name="arrow-back"></ion-icon>' + '<ion-icon name="arrow-back"></ion-icon>' +
'</button>' + '</button>' +
'<div class="searchbar-search-icon"></div>' + '<div #searchbarIcon class="searchbar-search-icon"></div>' +
'<input [value]="value" (input)="inputChanged($event)" (blur)="inputBlurred()" (focus)="inputFocused()" class="searchbar-input" type="search" [attr.placeholder]="placeholder" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">' + '<input [(ngModel)]="_value" [attr.placeholder]="placeholder" (input)="inputChanged($event)" (blur)="inputBlurred($event)" (focus)="inputFocused($event)" class="searchbar-input">' +
'<button clear class="searchbar-clear-icon" (click)="clearInput()" (mousedown)="clearInput()"></button>' + '<button clear class="searchbar-clear-icon" (click)="clearInput($event)" (mousedown)="clearInput($event)"></button>' +
'</div>' + '</div>' +
'<button clear (click)="cancelSearchbar()" (mousedown)="cancelSearchbar()" [hidden]="hideCancelButton" class="searchbar-ios-cancel">{{cancelButtonText}}</button>', '<button clear (click)="cancelSearchbar($event)" (mousedown)="cancelSearchbar($event)" [hidden]="hideCancelButton" class="searchbar-ios-cancel">{{cancelButtonText}}</button>',
directives: [SearchbarInput], directives: [SearchbarInput],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None
}) })
export class Searchbar extends Ion { export class Searchbar {
private _value: string|number = '';
private _tmr: any; private _tmr: any;
private _shouldBlur: boolean = true;
private inputEle: any;
private iconEle: any;
private mode: string;
/** /**
* @private * @input {string} Set the the cancel button text. Default: `"Cancel"`.
*/ */
@ViewChild(SearchbarInput) searchbarInput: SearchbarInput; @Input() cancelButtonText: string = 'Cancel';
/** /**
* @input {string} Sets the cancel button text to the value passed in * @input {boolean} Whether to hide the cancel button or not. Default: `"false"`.
*/ */
@Input() cancelButtonText: string; @Input() hideCancelButton: any = false;
/**
* @input {boolean} Hides the cancel button
*/
@Input() hideCancelButton: any;
/** /**
* @input {number} How long, in milliseconds, to wait to trigger the `input` event after each keystroke. Default `250`. * @input {number} How long, in milliseconds, to wait to trigger the `input` event after each keystroke. Default `250`.
@ -81,70 +79,59 @@ export class Searchbar extends Ion {
@Input() debounce: number = 250; @Input() debounce: number = 250;
/** /**
* @input {string} Sets input placeholder to the value passed in * @input {string} Set the input's placeholder. Default `"Search"`.
*/ */
@Input() placeholder: string; @Input() placeholder: string = 'Search';
/** /**
* @input {any} Expression to evaluate when the Searchbar input has changed including cleared * @input {string} Set the input's autocomplete property. Values: `"on"`, `"off"`. Default `"off"`.
*/ */
@Input() ngModel: any; @Input() autocomplete: string;
/** /**
* @output {event} When the Searchbar input has changed including cleared * @input {string} Set the input's autocorrect property. Values: `"on"`, `"off"`. Default `"off"`.
*/ */
@Output() ionInput: EventEmitter<Searchbar> = new EventEmitter(); @Input() autocorrect: string;
/** /**
* @output {event} When the Searchbar input has blurred * @input {string|boolean} Set the input's spellcheck property. Values: `true`, `false`. Default `false`.
*/ */
@Output() ionBlur: EventEmitter<Searchbar> = new EventEmitter(); @Input() spellcheck: string|boolean;
/** /**
* @output {event} When the Searchbar input has focused * @input {string} Set the type of the input. Values: `"text"`, `"password"`, `"email"`, `"number"`, `"search"`, `"tel"`, `"url"`. Default `"search"`.
*/ */
@Output() ionFocus: EventEmitter<Searchbar> = new EventEmitter(); @Input() type: string = 'search';
/** /**
* @output {event} When the cancel button is clicked * @output {event} When the Searchbar input has changed including cleared.
*/ */
@Output() ionCancel: EventEmitter<Searchbar> = new EventEmitter(); @Output() ionInput: EventEmitter<UIEvent> = new EventEmitter();
/** /**
* @output {event} When the clear input button is clicked * @output {event} When the Searchbar input has blurred.
*/ */
@Output() ionClear: EventEmitter<Searchbar> = new EventEmitter(); @Output() ionBlur: EventEmitter<UIEvent> = new EventEmitter();
/**
* @output {event} When the Searchbar input has focused.
*/
@Output() ionFocus: EventEmitter<UIEvent> = new EventEmitter();
/**
* @output {event} When the cancel button is clicked.
*/
@Output() ionCancel: EventEmitter<UIEvent> = new EventEmitter();
/**
* @output {event} When the clear input button is clicked.
*/
@Output() ionClear: EventEmitter<UIEvent> = new EventEmitter();
/** /**
* @private * @private
*/ */
value: string = ''; @HostBinding('class.searchbar-has-focus') isFocused: boolean;
/**
* @private
*/
blurInput: boolean = true;
/**
* @private
*/
inputElement: any;
/**
* @private
*/
searchIconElement: any;
/**
* @private
*/
mode: string;
/**
* @private
*/
@HostBinding('class.searchbar-focused') isFocused: boolean;
/** /**
* @private * @private
@ -156,14 +143,49 @@ export class Searchbar extends Ion {
private _config: Config, private _config: Config,
@Optional() ngControl: NgControl @Optional() ngControl: NgControl
) { ) {
super(_elementRef);
// If the user passed a ngControl we need to set the valueAccessor // If the user passed a ngControl we need to set the valueAccessor
if (ngControl) { if (ngControl) {
ngControl.valueAccessor = this; ngControl.valueAccessor = this;
} }
} }
/**
* @private
*/
@ViewChild(SearchbarInput)
private set _searchbarInput(searchbarInput: SearchbarInput) {
this.inputEle = searchbarInput.elementRef.nativeElement;
// By defalt set autocomplete="off" unless specified by the input
let autoComplete = (this.autocomplete === '' || this.autocomplete === 'on') ? 'on' : this._config.get('autocomplete', 'off');
this.inputEle.setAttribute('autocomplete', autoComplete);
// by default set autocorrect="off" unless specified by the input
let autoCorrect = (this.autocorrect === '' || this.autocorrect === 'on') ? 'on' : this._config.get('autocorrect', 'off');
this.inputEle.setAttribute('autocorrect', autoCorrect);
// by default set spellcheck="false" unless specified by the input
let spellCheck = (this.spellcheck === '' || this.spellcheck === 'true' || this.spellcheck === true) ? true : this._config.getBoolean('spellcheck', false);
this.inputEle.setAttribute('spellcheck', spellCheck);
// by default set type="search" unless specified by the input
this.inputEle.setAttribute('type', this.type);
}
@ViewChild('searchbarIcon') _searchbarIcon: ElementRef;
/**
* @input {string} Set the input value.
*/
@Input()
get value() {
return this._value;
}
set value(val) {
this._value = val;
}
/** /**
* @private * @private
* On Initialization check for attributes * On Initialization check for attributes
@ -176,18 +198,7 @@ export class Searchbar extends Ion {
this.hideCancelButton = (hideCancelButton === '' || hideCancelButton === 'true'); this.hideCancelButton = (hideCancelButton === '' || hideCancelButton === 'true');
} }
this.cancelButtonText = this.cancelButtonText || 'Cancel'; this.shouldLeftAlign = this._value && this._value.toString().trim() !== '';
this.placeholder = this.placeholder || 'Search';
if (this.ngModel) this.value = this.ngModel;
this.onChange(this.value);
this.shouldLeftAlign = this.value && this.value.trim() !== '';
// Using querySelector instead of searchbarInput because at this point it doesn't exist
this.inputElement = this._elementRef.nativeElement.querySelector('.searchbar-input');
this.searchIconElement = this._elementRef.nativeElement.querySelector('.searchbar-search-icon');
this.setElementLeft();
} }
/** /**
@ -195,13 +206,8 @@ export class Searchbar extends Ion {
* After View Initialization check the value * After View Initialization check the value
*/ */
ngAfterViewInit() { ngAfterViewInit() {
// If the user passes an undefined variable to ngModel this will warn this.iconEle = this._searchbarIcon.nativeElement;
// and set the value to an empty string this.setElementLeft();
if (!isPresent(this.value)) {
console.warn('Searchbar was passed an undefined value in ngModel. Please make sure the variable is defined.');
this.value = '';
this.onChange(this.value);
}
} }
/** /**
@ -213,8 +219,8 @@ export class Searchbar extends Ion {
if (this.mode !== 'ios') return; if (this.mode !== 'ios') return;
if (this.shouldLeftAlign) { if (this.shouldLeftAlign) {
this.inputElement.removeAttribute('style'); this.inputEle.removeAttribute('style');
this.searchIconElement.removeAttribute('style'); this.iconEle.removeAttribute('style');
} else { } else {
this.addElementLeft(); this.addElementLeft();
} }
@ -237,11 +243,11 @@ export class Searchbar extends Ion {
// Set the input padding left // Set the input padding left
let inputLeft = 'calc(50% - ' + (textWidth / 2) + 'px)'; let inputLeft = 'calc(50% - ' + (textWidth / 2) + 'px)';
this.inputElement.style.paddingLeft = inputLeft; this.inputEle.style.paddingLeft = inputLeft;
// Set the icon margin left // Set the icon margin left
let iconLeft = 'calc(50% - ' + ((textWidth / 2) + 30) + 'px)'; let iconLeft = 'calc(50% - ' + ((textWidth / 2) + 30) + 'px)';
this.searchIconElement.style.marginLeft = iconLeft; this.iconEle.style.marginLeft = iconLeft;
} }
/** /**
@ -253,9 +259,9 @@ export class Searchbar extends Ion {
clearTimeout(this._tmr); clearTimeout(this._tmr);
this._tmr = setTimeout(() => { this._tmr = setTimeout(() => {
this.value = value; this._value = value;
this.onChange(value); this.onChange(this._value);
this.ionInput.emit(this); this.ionInput.emit(ev);
}, Math.round(this.debounce)); }, Math.round(this.debounce));
} }
@ -263,8 +269,8 @@ export class Searchbar extends Ion {
* @private * @private
* Sets the Searchbar to focused and aligned left on input focus. * Sets the Searchbar to focused and aligned left on input focus.
*/ */
inputFocused() { inputFocused(ev: UIEvent) {
this.ionFocus.emit(this); this.ionFocus.emit(ev);
this.isFocused = true; this.isFocused = true;
this.shouldLeftAlign = true; this.shouldLeftAlign = true;
@ -276,18 +282,18 @@ export class Searchbar extends Ion {
* Sets the Searchbar to not focused and checks if it should align left * Sets the Searchbar to not focused and checks if it should align left
* based on whether there is a value in the searchbar or not. * based on whether there is a value in the searchbar or not.
*/ */
inputBlurred() { inputBlurred(ev: UIEvent) {
// blurInput determines if it should blur // _shouldBlur determines if it should blur
// if we are clearing the input we still want to stay focused in the input // if we are clearing the input we still want to stay focused in the input
if (this.blurInput === false) { if (this._shouldBlur === false) {
this.searchbarInput.elementRef.nativeElement.focus(); this.inputEle.focus();
this.blurInput = true; this._shouldBlur = true;
return; return;
} }
this.ionBlur.emit(this); this.ionBlur.emit(ev);
this.isFocused = false; this.isFocused = false;
this.shouldLeftAlign = this.value && this.value.trim() !== ''; this.shouldLeftAlign = this._value && this._value.toString().trim() !== '';
this.setElementLeft(); this.setElementLeft();
} }
@ -295,16 +301,16 @@ export class Searchbar extends Ion {
* @private * @private
* Clears the input field and triggers the control change. * Clears the input field and triggers the control change.
*/ */
clearInput() { clearInput(ev: UIEvent) {
this.ionClear.emit(this); this.ionClear.emit(ev);
if (isPresent(this.value) && this.value !== '') { if (isPresent(this._value) && this._value !== '') {
this.value = ''; this._value = '';
this.onChange(this.value); this.onChange(this._value);
this.ionInput.emit(this); this.ionInput.emit(ev);
} }
this.blurInput = false; this._shouldBlur = false;
} }
/** /**
@ -313,19 +319,19 @@ export class Searchbar extends Ion {
* the clearInput function doesn't want the input to blur * the clearInput function doesn't want the input to blur
* then calls the custom cancel function if the user passed one in. * then calls the custom cancel function if the user passed one in.
*/ */
cancelSearchbar() { cancelSearchbar(ev: UIEvent) {
this.ionCancel.emit(this); this.ionCancel.emit(ev);
this.clearInput(); this.clearInput(ev);
this.blurInput = true; this._shouldBlur = true;
} }
/** /**
* @private * @private
* Write a new value to the element. * Write a new value to the element.
*/ */
writeValue(value: any) { writeValue(val: any) {
this.value = value; this._value = val;
} }
/** /**

View File

@ -115,7 +115,7 @@ ion-searchbar {
// Searchbar Focused // Searchbar Focused
// ----------------------------------------- // -----------------------------------------
.searchbar-focused .searchbar-input-container { .searchbar-has-focus .searchbar-input-container {
border-color: $searchbar-wp-border-color-focused; border-color: $searchbar-wp-border-color-focused;
} }
@ -158,7 +158,7 @@ ion-searchbar {
@each $color-name, $color-base, $color-contrast in get-colors($colors-wp) { @each $color-name, $color-base, $color-contrast in get-colors($colors-wp) {
ion-searchbar[#{$color-name}].searchbar-focused .searchbar-input-container { ion-searchbar[#{$color-name}].searchbar-has-focus .searchbar-input-container {
border-color: $color-base; border-color: $color-base;
} }

View File

@ -1,4 +1,4 @@
import {Component} from '@angular/core'; import {Component, ChangeDetectorRef} from '@angular/core';
import {FormBuilder, Validators, Control, ControlGroup} from '@angular/common'; import {FormBuilder, Validators, Control, ControlGroup} from '@angular/common';
import {ionicBootstrap} from '../../../../../src'; import {ionicBootstrap} from '../../../../../src';
@ -8,27 +8,44 @@ import {ionicBootstrap} from '../../../../../src';
}) })
class E2EApp { class E2EApp {
defaultSearch: string = 'test'; defaultSearch: string = 'test';
customPlaceholder: string = ''; customPlaceholder: number = 2;
defaultCancel: string = ''; defaultCancel: string = '';
onClearSearchbar(searchbar) { isAutocorrect: string = 'on';
console.log("ionClear", searchbar.value); isAutocomplete: string = 'on';
isSpellcheck: boolean = true;
constructor(private changeDetectorRef: ChangeDetectorRef) {
} }
onCancelSearchbar(searchbar) { onClearSearchbar(ev: any) {
console.log("ionCancel", searchbar.value); console.log("ionClear", ev.target.value);
} }
triggerInput(searchbar) { onCancelSearchbar(ev: any) {
console.log("ionInput", searchbar.value); console.log("ionCancel", ev.target.value);
} }
inputBlurred(searchbar) { triggerInput(ev: any) {
console.log("ionBlur", searchbar.value); console.log("ionInput", ev.target.value);
} }
inputFocused(searchbar) { inputBlurred(ev: any) {
console.log("ionFocus", searchbar.value); console.log("ionBlur", ev.target.value);
}
inputFocused(ev: any) {
console.log("ionFocus", ev.target.value);
}
ngAfterViewInit() {
this.customPlaceholder = 33;
this.changeDetectorRef.detectChanges();
}
changeValue() {
this.defaultSearch = "changed";
} }
} }

View File

@ -7,14 +7,14 @@
</p> </p>
<h5 padding-left> Search - Custom Placeholder </h5> <h5 padding-left> Search - Custom Placeholder </h5>
<ion-searchbar [(ngModel)]="customPlaceholder" (ionInput)="triggerInput($event)" (ionCancel)="onCancelSearchbar($event)" (ionClear)="onClearSearchbar($event)" placeholder="Filter Schedules" class="e2eCustomPlaceholderFloatingSearchbar"></ion-searchbar> <ion-searchbar [autocorrect]="isAutocorrect" [autocomplete]="isAutocomplete" [spellcheck]="isSpellcheck" type="number" [(ngModel)]="customPlaceholder" (ionInput)="triggerInput($event)" (ionCancel)="onCancelSearchbar($event)" (ionClear)="onClearSearchbar($event)" placeholder="Filter Schedules" class="e2eCustomPlaceholderFloatingSearchbar"></ion-searchbar>
<p padding-left> <p padding-left>
customPlaceholder: <b>{{ customPlaceholder }}</b> customPlaceholder: <b>{{ customPlaceholder }}</b>
</p> </p>
<h5 padding-left> Search - Hide Cancel Button </h5> <h5 padding-left> Search - Hide Cancel Button </h5>
<ion-searchbar [(ngModel)]="defaultCancel" (ionInput)="triggerInput($event)" (ionCancel)="onCancelSearchbar($event)" (ionClear)="onClearSearchbar($event)" hideCancelButton class="e2eDefaultCancelButtonFloatingSearchbar"></ion-searchbar> <ion-searchbar autocorrect="off" autocomplete="off" spellcheck="true" type="text" [(ngModel)]="defaultCancel" (ionInput)="triggerInput($event)" (ionCancel)="onCancelSearchbar($event)" (ionClear)="onClearSearchbar($event)" hideCancelButton class="e2eDefaultCancelButtonFloatingSearchbar"></ion-searchbar>
<p padding-left> <p padding-left>
defaultCancel: <b>{{ defaultCancel }}</b> defaultCancel: <b>{{ defaultCancel }}</b>
@ -22,4 +22,8 @@
<h5 padding-left> Search - Custom Cancel Button Danger </h5> <h5 padding-left> Search - Custom Cancel Button Danger </h5>
<ion-searchbar (ionInput)="triggerInput($event)" (ionCancel)="onCancelSearchbar($event)" (ionClear)="onClearSearchbar($event)" cancelButtonText="Really Long Cancel" class="e2eCustomCancelButtonFloatingSearchbar" danger></ion-searchbar> <ion-searchbar (ionInput)="triggerInput($event)" (ionCancel)="onCancelSearchbar($event)" (ionClear)="onClearSearchbar($event)" cancelButtonText="Really Long Cancel" class="e2eCustomCancelButtonFloatingSearchbar" danger></ion-searchbar>
<p padding>
<button block (click)="changeValue()">Change Value</button>
</p>
</ion-content> </ion-content>