question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Typeahed: allow search on focus

See original GitHub issue

In certain use-cases one might want to trigger typeahead search when the input field gets focused. You can see a good example of this on https://www.kayak.fr/flights

It should be trivial to implement this on our side since we should simply listen to the focus event, get whatever value we’ve got in the input and push it down the observable stream. The only tricky part might be taking into account the situation where a given input field has autofocus attribute (in this case we shouldn’t kick off things).

There should be a flag for this option (I guess false by default).

I’m going to mark this as medium difficulty and I think that it is a good issue for ambitious community members. As always test are essentials.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:15
  • Comments:14 (5 by maintainers)

github_iconTop GitHub Comments

30reactions
beaverusivcommented, Jun 11, 2017

I have a workaround for this for people who want this functionality now.

On the input element that has typeahead add a focus handler:

(focus)="onFocus($event)"

In the focus handler trigger an input event:

public onFocus(e: Event): void {
    e.stopPropagation();
    setTimeout(() => {
        const inputEvent: Event = new Event('input');
        e.target.dispatchEvent(inputEvent);
    }, 0);
}

It would be nice to not have to do this, but it works for now.

3reactions
ymeinecommented, Sep 15, 2017

Hello,

I worked on the issue to try figuring out solutions, ideas, and here they are.

The feature requirements

As seen by the end user:

  • be able to trigger a search on input focus so that the dropdown gets opened (or not if no result)

Additional requirements as seen by the application developer:

  • be able to opt-in/opt-out for this feature easily
  • be able to distinguish a search triggered by focus from one triggered by input change

An example of a scenario that should be possible with such a feature

  • application is initialized: input is empty, no input change has been made yet
  • I focus the input
    • ⇒ dropdown opens with all possible values
  • I type a few characters
    • ⇒ the list of result changes according to what I typed
  • I blur the input and close the dropdown
  • I focus back the input
    • ⇒ dropdown opens as it was just before closing it (i.e. with the same results corresponding to the same input value)
  • I remove all the characters by pressing backspace, so that the input gets empty, then two possible behaviors:
    • ⇒ I apply the general behavior, same as if the empty input just got focused: I display all possible values
    • ⇒ I decide that since I just intentionally removed the characters, it’s a different case: I don’t display anything

The last point illustrates that it is important to know whether the current search request comes from an input event or a focus event.

Possible implementations

Nothing

On the application side, it is already possible to add a focus event handler on the input. The listener can then send values to an internal Subject instance which would have been merged at some point with the observable given in the TypeAhead initialization function (ngbTypeahead).

Example usage:

import {Component} from '@angular/core';

import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject'; // ★

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/merge'; // ★
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';

@Component({
  selector: 'app-root',
  template: `
  	<form>
		<input
			name='input'
			[(ngModel)]='searchTerm'
			[ngbTypeahead]='search'
			(focus)='handleFocus()' <!-- ★ -->

			type='text'
		/>
	</form>
	`
})
export class AppComponent {
	public searchTerm: string;

	private focus$ = new Subject<null>(); // ★

	handleFocus() {
		this.focus$.next(null); // ★
	}

	search = (input$: Observable<string>): Observable<any> => {
		return input$
		.debounceTime(200)
		.distinctUntilChanged()
		.merge(this.focus$) // ★
		.map(searchTerm => ...);
	}
}

Pros:

  • full control from the user

Cons:

  • some boilerplate code (creating the Subject, handling focus event)

Create ourselves an observable from the focus event and give it to the user

Example usage:

import {Component} from '@angular/core';

import {Observable} from 'rxjs/Observable';

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/merge'; // ★
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';

@Component({
  selector: 'app-root',
  template: `
  	<form>
		<input
			name='input'
			[(ngModel)]='searchTerm'
			[ngbTypeahead]='search'

			type='text'
		/>
	</form>
	`
})
export class AppComponent {
	public searchTerm: string;

	search = (input$: Observable<string>, /* ★ */ focus$: Observable<string>): Observable<any> => {
		return input$
		.debounceTime(200)
		.distinctUntilChanged()
		.merge(focus$) // ★
		.map(searchTerm => ...);
	}
}

Pros:

  • still full control from the user
  • backward compatible
  • easy to understand and manage
  • no boilerplate code

Cons: I don’t see any. However it would require more API design, since I don’t think we should extend the arguments list too much for any possible event. Also, we could optimize the implementation by providing an option so that if the feature is not wanted there is no new event handler added and no corresponding observable created.

Merge ourselves the focus observable with the input observable

There are two fashions for this one: backward compatible and non-backward compatible.

Backward compatible

Example usage:

//...
export class AppComponent {
	public searchTerm: string;

	search = (input$: Observable<string>): Observable<any> => {
		return input$
		.debounceTime(200)
		.distinctUntilChanged()
		.map(searchTerm => ...);
	}
}

Pros:

  • “works” out of the box
  • no boilerplate code

Cons:

  • NO WAY to distinguish focus events from input events, so implementing different behaviors is not possible
    • debounceTime is probably not wanted in case of focus
    • distinctUntilChanged could mess up in case of focus (depending on how we keep the rest of the implementation)

Non backward compatible

Example usage:

//...
export class AppComponent {
	public searchTerm: string;

	search = (input$: Observable<{from, value}> /* ★ */): Observable<any> => {
		return input$
		.debounceTime(200)
		.distinctUntilChanged()
		.map(payload => {
			const {from, value} = payload; // ★
			if (from === 'focus') {
				// ...
			} else {
				// ...
			}
		});
	}
}

Pros:

  • no boilerplate code

Cons:

  • hard for the user to process observables differently depending on if it comes from focus or input (again, for debounceTime and distinctUntilChanged), it requires a good knowledge of observables
  • not backward compatible since the given observable sends object values instead of just string values

Conclusion

I personally prefer the second option for these reasons:

  • backward compatible
  • simple to use on the application side
  • full control over the different events/observables
  • simple to implement on our side
  • we still provide something to avoid boilerplate code on the application side, while relying on common and powerful concepts (observables)

I will leave this pending for comments for some days, and if no decision stands out we will make one and implement it.

PS: this whole feature based on focus events requires a very small and simple fix inside the TypeAhead to avoid trying to write an undefined value inside the input element. With the methods exposed above, this can happen if no input event has been triggered yet, and the dropdown is open through a focus event and closed afterwards without selecting anything. Therefore using the first exposed implementation (fully on your side) won’t work for that use case without this fix.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Bootstrap - show all Typeahead items on focus - Stack Overflow
So, what's your question? What doesn't work? · Hi John It works when I start to type, but not only on focus. The...
Read more >
jQuery Typeahead Search Configuration - RunningCoder
Configure jQuery Typeahead Search plugin, learn about the options and what they can do to improve your search bar.
Read more >
React Bootstrap Typeahead Example
The typeahead allows single-selection by default. Setting the multiple prop turns the component into a tokenizer, allowing multiple selections. Example.
Read more >
Place Autocomplete | Maps JavaScript API - Google Developers
Autocomplete is a feature of the Places library in the Maps JavaScript API. You can use autocomplete to give your applications the type-ahead-search...
Read more >
Hotwire: Typeahead searching - Thoughtbot
Let's build a collapsible search-as-you-type text box that expands to show its results in-line while searching, supports keyboard navigation ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found