[iPad Scanning] Open Scanner on Scan Icon Click

[Fix] Select Filters In HitsOnly Call {Search}

Set Autocomplete Result on Click


Close Autoresults on Searchbar X Icon


Reset Focus on Input after Clicking Autocomplete Result
This commit is contained in:
Sebastian
2020-07-08 08:18:43 +02:00
parent 76afd571df
commit 86817060f3
9 changed files with 154 additions and 44 deletions

View File

@@ -45,7 +45,6 @@ export class ContentHeaderService {
switchMap((activeSection) => {
if (activeSection.includes('remission')) {
return this.remissionFilters$.pipe(
tap((selectedFilters) => console.log({ selectedFilters })),
map(
(selectedFilters) =>
selectedFilters && !!Object.entries(selectedFilters).length

View File

@@ -12,6 +12,7 @@ import {
import { AutocompleteDTO } from '@swagger/oms/lib';
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
import { ResultItemComponent } from './result-item';
import { AutocompleteOptions } from '../../defs';
@Component({
selector: 'app-autocomplete-results',
@@ -24,7 +25,7 @@ export class AutocompleteResultsComponent implements AfterViewInit {
@Input() results: AutocompleteDTO[];
@Output() selectItem = new EventEmitter<
AutocompleteDTO & { shouldNavigate?: boolean }
AutocompleteDTO & AutocompleteOptions
>();
private keyManager: ActiveDescendantKeyManager<ResultItemComponent>;
@@ -48,7 +49,8 @@ export class AutocompleteResultsComponent implements AfterViewInit {
this.keyManager.setActiveItem(this.getItemIndex(item));
this.selectItem.emit({
...this.keyManager.activeItem.result,
shouldNavigate: true,
shouldNavigate: false,
shouldUpdateAutocomplete: false,
});
}

View File

@@ -10,7 +10,7 @@
name="shelf-search"
id="shelf-search-input"
[formControl]="searchForm"
(keyup.enter)="triggerSearch('enter')"
(keyup.enter)="triggerSearch('search')"
(keyup)="onInput($event)"
/>
<span class="isa-input-error" *ngIf="!!errorMessage.length">{{
@@ -33,7 +33,7 @@
class="isa-input-submit"
[class.scan]="!errorMessage && !(searchQuery$ | async) && isIPad"
type="submit"
(click)="triggerSearch('click')"
(click)="handleBtnClick(submitButton)"
></button>
</ng-container>
</div>

View File

@@ -9,7 +9,6 @@ import {
ViewChild,
ElementRef,
} from '@angular/core';
import { Store } from '@ngxs/store';
import { Subject, Observable } from 'rxjs';
import { isNullOrUndefined } from 'util';
import { FormControl } from '@angular/forms';
@@ -40,14 +39,14 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
@Output() change = new EventEmitter<string>();
@Output() reset = new EventEmitter<void>();
@Output() search = new EventEmitter<{
type: 'scan' | 'search';
value: string;
target: 'click' | 'enter';
type: 'search' | 'scan';
}>();
@Output() scan = new EventEmitter<void>();
destroy$ = new Subject();
constructor(private store: Store) {}
constructor() {}
ngOnInit() {
this.initForm();
@@ -71,7 +70,15 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
this.errorMessage = message;
}
triggerSearch(target: 'click' | 'enter') {
handleBtnClick(targetEl: HTMLButtonElement) {
this.triggerSearch(this.getSearchType(targetEl));
}
triggerSearch(type: 'search' | 'scan') {
if (type === 'scan') {
return this.triggerScan();
}
const isValidInput = this.validateSearchInputBeforeSubmit(
this.searchForm.value
);
@@ -81,12 +88,15 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
}
this.search.emit({
type: this.isSearch() ? 'search' : 'scan',
value: this.searchForm.value,
target,
type,
});
}
triggerScan() {
this.scan.emit();
}
onInput(event: KeyboardEvent) {
if (!this.isNumberOrLetter(event.key)) {
this.change.emit(this.searchForm.value);
@@ -116,8 +126,11 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
return !isNullOrUndefined(searchInput) && searchInput.length >= 1;
}
private isSearch(): boolean {
return this.searchForm.value && this.searchForm.value.length;
private getSearchType(target: HTMLButtonElement): 'search' | 'scan' {
if (!target) {
return 'search';
}
return target.classList.contains('scan') ? 'scan' : 'search';
}
private isNumberOrLetter(key: string): boolean {

View File

@@ -0,0 +1,4 @@
export interface AutocompleteOptions {
shouldNavigate?: boolean;
shouldUpdateAutocomplete?: boolean;
}

View File

@@ -1,3 +1,4 @@
// start:ng42.barrel
export * from './autocomplete-options.model';
export * from './shelf-primary-filter-options';
// end:ng42.barrel

View File

@@ -17,7 +17,6 @@ import { startWith, first } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { ShelfSearchInputComponent } from '../../pages/shelf-search/search';
import { cloneFilter } from '../../../filter/utils';
import { SearchStateFacade } from '@shelf-store';
@Component({
selector: 'app-shelf-filter',
@@ -47,7 +46,6 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
constructor(
private overlayRef: IsaOverlayRef,
private filterService: ShelfFilterService,
private searchStateFacade: SearchStateFacade,
private renderer: Renderer2,
private cdr: ChangeDetectorRef
) {}
@@ -154,17 +152,12 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
this.searchInput.searchbar.searchForm.value) ||
'';
this.searchStateFacade.selectedFilter$
.pipe(first())
.subscribe((selectedFilters) =>
this.searchInput.triggerSearch({
type: 'search',
value: searchbarValue,
selectedFilters,
bypassValidation: true,
closeOverlay: () => this.close(),
})
);
this.searchInput.triggerSearch({
type: 'search',
value: searchbarValue,
bypassValidation: true,
closeOverlay: () => this.close(),
});
}
}
}

View File

@@ -6,12 +6,13 @@
[mode]="searchMode$ | async"
(change)="updateAutocomplete($event)"
(search)="triggerSearch($event)"
(scan)="openScanner()"
(reset)="reset()"
></app-shelf-searchbar>
<app-autocomplete-results
*ngIf="(searchMode$ | async) === 'autocomplete'"
[results]="autocompleteResult$ | async"
(selectItem)="selectedItem$.next($event)"
(selectItem)="handleAutocompleteAction($event)"
></app-autocomplete-results>
<lib-collecting-shelf-scanner

View File

@@ -7,7 +7,7 @@ import {
ViewChild,
AfterViewInit,
} from '@angular/core';
import { Subject, BehaviorSubject, Observable, of } from 'rxjs';
import { Subject, BehaviorSubject, Observable, of, NEVER } from 'rxjs';
import {
ShelfSearchFacadeService,
ShelfNavigationService,
@@ -32,6 +32,9 @@ import { SHELF_SCROLL_INDEX } from 'apps/sales/src/app/core/utils/app.constants'
import { ShelfSearchbarComponent } from '../../../components';
import { ShelfFilterService } from '../../../services/shelf-filter.service';
import { SearchStateFacade } from '@shelf-store';
import { CollectingShelfScannerScanditComponent } from 'shared/public_api';
import { AutocompleteOptions } from '../../../defs';
import { ClickOutsideDirective } from 'apps/sales/src/app/shared/directives';
@Component({
selector: 'app-shelf-search-input',
@@ -47,6 +50,8 @@ export class ShelfSearchInputComponent
@ViewChild(ShelfSearchbarComponent, { static: true })
searchbar: ShelfSearchbarComponent;
@ViewChild(CollectingShelfScannerScanditComponent, { static: false })
scanner: CollectingShelfScannerScanditComponent;
destroy$ = new Subject();
@@ -59,9 +64,12 @@ export class ShelfSearchInputComponent
searchMode$: Observable<'standalone' | 'autocomplete'>;
selectedItem$ = new BehaviorSubject<
AutocompleteDTO & { shouldNavigate?: boolean }
>(null);
selectedItem$ = new BehaviorSubject<AutocompleteDTO & AutocompleteOptions>(
null
);
selectedFilters$: Observable<{
[key: string]: string;
}>;
constructor(
private shelfSearchService: ShelfSearchFacadeService,
@@ -76,6 +84,7 @@ export class ShelfSearchInputComponent
}
this.setUpSearchMode();
this.initSelectedFilters();
}
ngAfterViewInit() {
@@ -101,7 +110,6 @@ export class ShelfSearchInputComponent
{
type,
value,
selectedFilters,
bypassValidation,
closeOverlay,
}: {
@@ -124,18 +132,30 @@ export class ShelfSearchInputComponent
if (this.isAutocompleteSelected()) {
searchQuery = this.selectedItem$.value.query;
result$ = this.shelfSearchService.search(searchQuery, {
hitsOnly: true,
bypassValidation,
selectedFilters,
});
result$ = this.selectedFilters$.pipe(
debounceTime(250),
first(),
switchMap((selectedFilters) =>
this.shelfSearchService.search(searchQuery, {
hitsOnly: true,
bypassValidation,
selectedFilters,
})
)
);
this.updateSearchbarValue(searchQuery);
} else if (type === 'search') {
result$ = this.shelfSearchService.search(value, {
hitsOnly: true,
bypassValidation,
selectedFilters,
});
result$ = this.selectedFilters$.pipe(
debounceTime(250),
first(),
switchMap((selectedFilters) =>
this.shelfSearchService.search(value, {
hitsOnly: true,
bypassValidation,
selectedFilters,
})
)
);
} else if (type === 'scan') {
result$ = this.shelfSearchService.searchWithBarcode(value);
}
@@ -157,10 +177,43 @@ export class ShelfSearchInputComponent
);
}
updateAutocomplete(searchQuery: string) {
openScanner() {
if (this.scanner) {
this.scanner.openNativeScanner();
}
}
updateAutocomplete(searchQuery: string | Event) {
if (this.isEvent(searchQuery)) {
if (this.isClearSearchbarEvent(searchQuery)) {
this.setSelectedAutocompleteItem(null);
return this.autocompleteQueryString$.next('');
}
return;
}
this.autocompleteQueryString$.next(searchQuery);
}
handleAutocompleteAction(selectItem: AutocompleteDTO & AutocompleteOptions) {
const oldValue = this.selectedItem$.value && this.selectedItem$.value.query;
const newValue = selectItem.query;
console.log({
oldValue,
newValue,
shouldOverwrite: this.isAutocompleteOverwritten(oldValue, newValue),
});
if (this.isAutocompleteOverwritten(oldValue, newValue)) {
return this.setSelectedAutocompleteItem(null);
}
this.setSelectedAutocompleteItem(selectItem);
if (selectItem.query) {
this.updateSearchbarValue(selectItem.query);
}
}
private handleSearchResult(
result: ListResponseArgsOfOrderItemListItemDTO,
value: string,
@@ -214,6 +267,10 @@ export class ShelfSearchInputComponent
distinctUntilChanged(this.filterDistinctStrings),
withLatestFrom(this.searchStateFacade.selectedFilter$),
switchMap(([queryString, selectedFilters]) => {
if (this.isAutocompleteSelected()) {
return NEVER;
}
if (this.isValidQuery(queryString)) {
return this.shelfSearchService
.searchForAutocomplete(queryString, {
@@ -236,6 +293,10 @@ export class ShelfSearchInputComponent
);
}
private initSelectedFilters() {
this.selectedFilters$ = this.searchStateFacade.selectedFilter$;
}
private setUpInputFocus() {
this.filterService.overlayClosed$
.pipe(takeUntil(this.destroy$))
@@ -268,6 +329,12 @@ export class ShelfSearchInputComponent
this.errorMessage$.next(message);
}
private setSelectedAutocompleteItem(
item: (AutocompleteDTO & AutocompleteOptions) | null
) {
this.selectedItem$.next(item);
}
private isValidQuery(queryString: string): boolean {
return !!queryString && queryString.length >= 3;
}
@@ -280,9 +347,39 @@ export class ShelfSearchInputComponent
return !!this.selectedItem$.value;
}
private isAutocompleteOverwritten(
oldQuery: string,
newQuery: string
): boolean {
return this.isEqual(oldQuery, newQuery);
}
private isEqual<T>(val1: T, val2: T): boolean {
return val1 === val2;
}
private isEvent(value: any): value is Event {
return value && value instanceof Event;
}
private isClearSearchbarEvent(event: Event): boolean {
return event.target instanceof HTMLInputElement;
}
private inputOutOfFocus(): boolean {
return (
document.activeElement &&
document.activeElement.tagName &&
document.activeElement.tagName.toLowerCase() === 'body'
);
}
private updateSearchbarValue(queryString: string) {
if (this.searchbar) {
this.searchbar.setInputValue(queryString);
}
if (this.inputOutOfFocus()) {
this.setFocus();
}
}
}