searchbar.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import { Component, ElementRef, EventEmitter, Input, Optional, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core';
  2. import { NgControl } from '@angular/forms';
  3. import { Config } from '../../config/config';
  4. import { BaseInput } from '../../util/base-input';
  5. import { isPresent, isTrueProperty } from '../../util/util';
  6. import { TimeoutDebouncer } from '../../util/debouncer';
  7. import { Platform } from '../../platform/platform';
  8. /**
  9. * @name Searchbar
  10. * @module ionic
  11. * @description
  12. * Manages the display of a Searchbar which can be used to search or filter items.
  13. *
  14. * @usage
  15. * ```html
  16. * <ion-searchbar
  17. * [(ngModel)]="myInput"
  18. * [showCancelButton]="shouldShowCancel"
  19. * (ionInput)="onInput($event)"
  20. * (ionCancel)="onCancel($event)">
  21. * </ion-searchbar>
  22. * ```
  23. *
  24. * @demo /docs/demos/src/searchbar/
  25. * @see {@link /docs/components#searchbar Searchbar Component Docs}
  26. */
  27. export class Searchbar extends BaseInput {
  28. constructor(config, _plt, elementRef, renderer, ngControl) {
  29. super(config, elementRef, renderer, 'searchbar', '', null, null, ngControl);
  30. this._plt = _plt;
  31. this._shouldBlur = true;
  32. this._shouldAlignLeft = true;
  33. this._isCancelVisible = false;
  34. this._spellcheck = false;
  35. this._autocomplete = 'off';
  36. this._autocorrect = 'off';
  37. this._isActive = false;
  38. this._showCancelButton = false;
  39. this._animated = false;
  40. this._inputDebouncer = new TimeoutDebouncer(0);
  41. /**
  42. * @input {string} Set the the cancel button text. Default: `"Cancel"`.
  43. */
  44. this.cancelButtonText = 'Cancel';
  45. /**
  46. * @input {string} Set the input's placeholder. Default `"Search"`.
  47. */
  48. this.placeholder = 'Search';
  49. /**
  50. * @input {string} Set the type of the input. Values: `"text"`, `"password"`, `"email"`, `"number"`, `"search"`, `"tel"`, `"url"`. Default `"search"`.
  51. */
  52. this.type = 'search';
  53. /**
  54. * @output {event} Emitted when the Searchbar input has changed, including when it's cleared.
  55. */
  56. this.ionInput = new EventEmitter();
  57. /**
  58. * @output {event} Emitted when the cancel button is clicked.
  59. */
  60. this.ionCancel = new EventEmitter();
  61. /**
  62. * @output {event} Emitted when the clear input button is clicked.
  63. */
  64. this.ionClear = new EventEmitter();
  65. this.debounce = 250;
  66. }
  67. /**
  68. * @input {boolean} If true, show the cancel button. Default `false`.
  69. */
  70. get showCancelButton() {
  71. return this._showCancelButton;
  72. }
  73. set showCancelButton(val) {
  74. this._showCancelButton = isTrueProperty(val);
  75. }
  76. /**
  77. * @input {number} How long, in milliseconds, to wait to trigger the `ionInput` event after each keystroke. Default `250`.
  78. */
  79. get debounce() {
  80. return this._debouncer.wait;
  81. }
  82. set debounce(val) {
  83. this._debouncer.wait = val;
  84. this._inputDebouncer.wait = val;
  85. }
  86. /**
  87. * @input {string} Set the input's autocomplete property. Values: `"on"`, `"off"`. Default `"off"`.
  88. */
  89. set autocomplete(val) {
  90. this._autocomplete = (val === '' || val === 'on') ? 'on' : this._config.get('autocomplete', 'off');
  91. }
  92. /**
  93. * @input {string} Set the input's autocorrect property. Values: `"on"`, `"off"`. Default `"off"`.
  94. */
  95. set autocorrect(val) {
  96. this._autocorrect = (val === '' || val === 'on') ? 'on' : this._config.get('autocorrect', 'off');
  97. }
  98. /**
  99. * @input {string|boolean} Set the input's spellcheck property. Values: `true`, `false`. Default `false`.
  100. */
  101. set spellcheck(val) {
  102. this._spellcheck = (val === '' || val === 'true' || val === true) ? true : this._config.getBoolean('spellcheck', false);
  103. }
  104. /**
  105. * @input {boolean} If true, enable searchbar animation. Default `false`.
  106. */
  107. get animated() {
  108. return this._animated;
  109. }
  110. set animated(val) {
  111. this._animated = isTrueProperty(val);
  112. }
  113. /**
  114. * @hidden
  115. * On Initialization check for attributes
  116. */
  117. ngOnInit() {
  118. const showCancelButton = this.showCancelButton;
  119. if (typeof showCancelButton === 'string') {
  120. this.showCancelButton = (showCancelButton === '' || showCancelButton === 'true');
  121. }
  122. }
  123. /**
  124. * @hidden
  125. */
  126. _inputUpdated() {
  127. const ele = this._searchbarInput.nativeElement;
  128. const value = this._value;
  129. // It is important not to re-assign the value if it is the same, because,
  130. // otherwise, the caret is moved to the end of the input
  131. if (ele.value !== value) {
  132. ele.value = value;
  133. }
  134. this.positionElements();
  135. }
  136. /**
  137. * @hidden
  138. * Positions the input search icon, placeholder, and the cancel button
  139. * based on the input value and if it is focused. (ios only)
  140. */
  141. positionElements() {
  142. const isAnimated = this._animated;
  143. const prevAlignLeft = this._shouldAlignLeft;
  144. const shouldAlignLeft = (!isAnimated || (this._value && this._value.toString().trim() !== '') || this._isFocus === true);
  145. this._shouldAlignLeft = shouldAlignLeft;
  146. if (this._mode !== 'ios') {
  147. return;
  148. }
  149. if (prevAlignLeft !== shouldAlignLeft) {
  150. this.positionPlaceholder();
  151. }
  152. if (isAnimated) {
  153. this.positionCancelButton();
  154. }
  155. }
  156. positionPlaceholder() {
  157. const inputEle = this._searchbarInput.nativeElement;
  158. const iconEle = this._searchbarIcon.nativeElement;
  159. if (this._shouldAlignLeft) {
  160. inputEle.removeAttribute('style');
  161. iconEle.removeAttribute('style');
  162. }
  163. else {
  164. // Create a dummy span to get the placeholder width
  165. var doc = this._plt.doc();
  166. var tempSpan = doc.createElement('span');
  167. tempSpan.innerHTML = this.placeholder;
  168. doc.body.appendChild(tempSpan);
  169. // Get the width of the span then remove it
  170. var textWidth = tempSpan.offsetWidth;
  171. doc.body.removeChild(tempSpan);
  172. // Set the input padding start
  173. var inputLeft = 'calc(50% - ' + (textWidth / 2) + 'px)';
  174. if (this._plt.isRTL) {
  175. inputEle.style.paddingRight = inputLeft;
  176. }
  177. else {
  178. inputEle.style.paddingLeft = inputLeft;
  179. }
  180. // Set the icon margin start
  181. var iconLeft = 'calc(50% - ' + ((textWidth / 2) + 30) + 'px)';
  182. if (this._plt.isRTL) {
  183. iconEle.style.marginRight = iconLeft;
  184. }
  185. else {
  186. iconEle.style.marginLeft = iconLeft;
  187. }
  188. }
  189. }
  190. /**
  191. * @hidden
  192. * Show the iOS Cancel button on focus, hide it offscreen otherwise
  193. */
  194. positionCancelButton() {
  195. const showShowCancel = this._isFocus;
  196. if (showShowCancel !== this._isCancelVisible) {
  197. var cancelStyleEle = this._cancelButton.nativeElement;
  198. var cancelStyle = cancelStyleEle.style;
  199. this._isCancelVisible = showShowCancel;
  200. if (showShowCancel) {
  201. if (this._plt.isRTL) {
  202. cancelStyle.marginLeft = '0';
  203. }
  204. else {
  205. cancelStyle.marginRight = '0';
  206. }
  207. }
  208. else {
  209. var offset = cancelStyleEle.offsetWidth;
  210. if (offset > 0) {
  211. if (this._plt.isRTL) {
  212. cancelStyle.marginLeft = -offset + 'px';
  213. }
  214. else {
  215. cancelStyle.marginRight = -offset + 'px';
  216. }
  217. }
  218. }
  219. }
  220. }
  221. /**
  222. * @hidden
  223. * Update the Searchbar input value when the input changes
  224. */
  225. inputChanged(ev) {
  226. this.value = ev.target.value;
  227. this._inputDebouncer.debounce(() => {
  228. this.ionInput.emit(ev);
  229. });
  230. }
  231. /**
  232. * @hidden
  233. * Sets the Searchbar to focused and active on input focus.
  234. */
  235. inputFocused() {
  236. this._isActive = true;
  237. this._fireFocus();
  238. this.positionElements();
  239. }
  240. /**
  241. * @hidden
  242. * Sets the Searchbar to not focused and checks if it should align left
  243. * based on whether there is a value in the searchbar or not.
  244. */
  245. inputBlurred() {
  246. // _shouldBlur determines if it should blur
  247. // if we are clearing the input we still want to stay focused in the input
  248. if (this._shouldBlur === false) {
  249. this._searchbarInput.nativeElement.focus();
  250. this._shouldBlur = true;
  251. return;
  252. }
  253. this._fireBlur();
  254. this.positionElements();
  255. }
  256. /**
  257. * @hidden
  258. * Clears the input field and triggers the control change.
  259. */
  260. clearInput(ev) {
  261. this.ionClear.emit(ev);
  262. // setTimeout() fixes https://github.com/ionic-team/ionic/issues/7527
  263. // wait for 4 frames
  264. setTimeout(() => {
  265. let value = this._value;
  266. if (isPresent(value) && value !== '') {
  267. this.value = ''; // DOM WRITE
  268. this.ionInput.emit(ev);
  269. }
  270. }, 16 * 4);
  271. this._shouldBlur = false;
  272. }
  273. /**
  274. * @hidden
  275. * Clears the input field and tells the input to blur since
  276. * the clearInput function doesn't want the input to blur
  277. * then calls the custom cancel function if the user passed one in.
  278. */
  279. cancelSearchbar(ev) {
  280. this.ionCancel.emit(ev);
  281. this.clearInput(ev);
  282. this._shouldBlur = true;
  283. this._isActive = false;
  284. }
  285. setFocus() {
  286. this._renderer.invokeElementMethod(this._searchbarInput.nativeElement, 'focus');
  287. }
  288. }
  289. Searchbar.decorators = [
  290. { type: Component, args: [{
  291. selector: 'ion-searchbar',
  292. template: '<div class="searchbar-input-container">' +
  293. '<button ion-button mode="md" (click)="cancelSearchbar($event)" (mousedown)="cancelSearchbar($event)" clear color="dark" class="searchbar-md-cancel" type="button">' +
  294. '<ion-icon name="md-arrow-back"></ion-icon>' +
  295. '</button>' +
  296. '<div #searchbarIcon class="searchbar-search-icon"></div>' +
  297. '<input #searchbarInput class="searchbar-input" (input)="inputChanged($event)" (blur)="inputBlurred()" (focus)="inputFocused()" ' +
  298. 'dir="auto" ' +
  299. '[attr.placeholder]="placeholder" ' +
  300. '[attr.type]="type" ' +
  301. '[attr.autocomplete]="_autocomplete" ' +
  302. '[attr.autocorrect]="_autocorrect" ' +
  303. '[attr.spellcheck]="_spellcheck">' +
  304. '<button ion-button clear class="searchbar-clear-icon" [mode]="_mode" (click)="clearInput($event)" (mousedown)="clearInput($event)" type="button"></button>' +
  305. '</div>' +
  306. '<button ion-button #cancelButton mode="ios" [tabindex]="_isActive ? 1 : -1" clear (click)="cancelSearchbar($event)" (mousedown)="cancelSearchbar($event)" class="searchbar-ios-cancel" type="button">{{cancelButtonText}}</button>',
  307. host: {
  308. '[class.searchbar-animated]': '_animated',
  309. '[class.searchbar-has-value]': '_value',
  310. '[class.searchbar-active]': '_isActive',
  311. '[class.searchbar-show-cancel]': '_showCancelButton',
  312. '[class.searchbar-left-aligned]': '_shouldAlignLeft',
  313. '[class.searchbar-has-focus]': '_isFocus'
  314. },
  315. encapsulation: ViewEncapsulation.None
  316. },] },
  317. ];
  318. /** @nocollapse */
  319. Searchbar.ctorParameters = () => [
  320. { type: Config, },
  321. { type: Platform, },
  322. { type: ElementRef, },
  323. { type: Renderer, },
  324. { type: NgControl, decorators: [{ type: Optional },] },
  325. ];
  326. Searchbar.propDecorators = {
  327. 'cancelButtonText': [{ type: Input },],
  328. 'showCancelButton': [{ type: Input },],
  329. 'debounce': [{ type: Input },],
  330. 'placeholder': [{ type: Input },],
  331. 'autocomplete': [{ type: Input },],
  332. 'autocorrect': [{ type: Input },],
  333. 'spellcheck': [{ type: Input },],
  334. 'type': [{ type: Input },],
  335. 'animated': [{ type: Input },],
  336. 'ionInput': [{ type: Output },],
  337. 'ionCancel': [{ type: Output },],
  338. 'ionClear': [{ type: Output },],
  339. '_searchbarInput': [{ type: ViewChild, args: ['searchbarInput',] },],
  340. '_searchbarIcon': [{ type: ViewChild, args: ['searchbarIcon',] },],
  341. '_cancelButton': [{ type: ViewChild, args: ['cancelButton', { read: ElementRef },] },],
  342. };
  343. //# sourceMappingURL=searchbar.js.map