coveo-search-ui
Version:
Coveo JavaScript Search Framework
290 lines (244 loc) • 8.47 kB
text/typescript
import { each, find, isUndefined } from 'underscore';
import { KEYBOARD } from '../Core';
import { $$ } from '../utils/Dom';
import { doMagicBoxExport } from './doMagicBoxExport';
import { Grammar } from './Grammar';
import { InputManager } from './InputManager';
import { MagicBoxClear } from './MagicBoxClear';
import { Result } from './Result/Result';
import { Suggestion, SuggestionsManager } from './SuggestionsManager';
export interface Options {
inline?: boolean;
selectableSuggestionClass?: string;
selectedSuggestionClass?: string;
suggestionTimeout?: number;
}
export class MagicBoxInstance {
public onblur: () => void;
public onfocus: () => void;
public onchange: () => void;
public onSuggestions: (suggestions: Suggestion[]) => void;
public onsubmit: () => void;
public onselect: (suggestion: Suggestion) => void;
public onclear: () => void;
public onmove: () => void;
public ontabpress: () => void;
public getSuggestions: () => Array<Promise<Suggestion[]> | Suggestion[]>;
private inputManager: InputManager;
private suggestionsManager: SuggestionsManager;
private magicBoxClear: MagicBoxClear;
private lastSuggestions: Suggestion[] = [];
private result: Result;
private displayedResult: Result;
private get firstSuggestionWithText(): Suggestion {
return find(this.lastSuggestions, suggestion => suggestion.text);
}
private get firstSuggestionText() {
const firstSuggestionWithText = this.firstSuggestionWithText;
return firstSuggestionWithText ? firstSuggestionWithText.text : '';
}
constructor(public element: HTMLElement, public grammar: Grammar, public options: Options = {}) {
if (isUndefined(this.options.inline)) {
this.options.inline = false;
}
$$(element).addClass('magic-box');
if (this.options.inline) {
$$(element).addClass('magic-box-inline');
}
this.result = this.grammar.parse('');
this.displayedResult = this.result.clean();
let inputContainer = $$(element).find('.magic-box-input');
if (!inputContainer) {
inputContainer = document.createElement('div');
inputContainer.className = 'magic-box-input';
element.appendChild(inputContainer);
}
this.inputManager = new InputManager(
inputContainer,
(text, wordCompletion) => {
if (!wordCompletion) {
this.setText(text);
this.addSuggestions();
this.onchange && this.onchange();
} else {
this.setText(text);
this.onselect && this.onselect(this.firstSuggestionWithText);
}
},
this
);
this.inputManager.ontabpress = () => {
this.ontabpress && this.ontabpress();
};
const existingValue = this.inputManager.getValue();
if (existingValue) {
this.displayedResult.input = existingValue;
}
this.inputManager.setResult(this.displayedResult);
const suggestionsContainer = document.createElement('div');
suggestionsContainer.className = 'magic-box-suggestions';
this.element.appendChild(suggestionsContainer);
this.suggestionsManager = new SuggestionsManager(suggestionsContainer, this.element, this.inputManager, {
suggestionClass: this.options.selectableSuggestionClass,
selectedClass: this.options.selectedSuggestionClass,
timeout: this.options.suggestionTimeout
});
this.magicBoxClear = new MagicBoxClear(this);
this.setupHandler();
}
public getResult() {
return this.result;
}
public getDisplayedResult() {
return this.displayedResult;
}
public setText(text: string) {
$$(this.element).toggleClass('magic-box-notEmpty', text.length > 0);
this.magicBoxClear.toggleTabindexAndAriaHidden(text.length > 0);
this.result = this.grammar.parse(text);
this.displayedResult = this.result.clean();
this.inputManager.setResult(this.displayedResult);
}
public setCursor(index: number) {
this.inputManager.setCursor(index);
}
public getCursor() {
return this.inputManager.getCursor();
}
public resultAtCursor(match?: string | { (result: Result): boolean }): Result[] {
return this.displayedResult.resultAt(this.getCursor(), match);
}
private setupHandler() {
this.inputManager.onblur = () => {
$$(this.element).removeClass('magic-box-hasFocus');
this.onblur && this.onblur();
if (!this.options.inline) {
this.clearSuggestion();
}
};
this.inputManager.onfocus = () => {
$$(this.element).addClass('magic-box-hasFocus');
this.addSuggestions();
this.onfocus && this.onfocus();
};
this.inputManager.onkeydown = (key: number) => {
if (this.shouldMoveInSuggestions(key)) {
return false;
}
if (key === KEYBOARD.ENTER) {
const suggestion = this.suggestionsManager.selectAndReturnKeyboardFocusedElement();
if (suggestion == null) {
this.onsubmit && this.onsubmit();
}
return false;
} else if (key === KEYBOARD.ESCAPE) {
this.clearSuggestion();
this.blur();
} else {
this.suggestionsManager.clearKeyboardFocusedElement();
}
return true;
};
this.inputManager.onchangecursor = () => {
this.addSuggestions();
};
this.inputManager.onkeyup = (key: number) => {
this.onmove && this.onmove();
if (!this.shouldMoveInSuggestions(key)) {
return true;
}
switch (key) {
case KEYBOARD.UP_ARROW:
this.suggestionsManager.moveUp();
break;
case KEYBOARD.DOWN_ARROW:
this.suggestionsManager.moveDown();
break;
case KEYBOARD.LEFT_ARROW:
this.suggestionsManager.moveLeft();
break;
case KEYBOARD.RIGHT_ARROW:
this.suggestionsManager.moveRight();
break;
}
if (this.suggestionsManager.selectedSuggestion) {
this.focusOnSuggestion(this.suggestionsManager.selectedSuggestion);
}
this.onchange && this.onchange();
return false;
};
}
public async addSuggestions() {
const suggestions = await this.suggestionsManager.receiveSuggestions(this.getSuggestions != null ? this.getSuggestions() : []);
this.addSelectEventHandlers(suggestions);
this.inputManager.setWordCompletion(this.firstSuggestionText);
this.onSuggestions(suggestions);
}
private shouldMoveInSuggestions(key: KEYBOARD) {
switch (key) {
case KEYBOARD.UP_ARROW:
case KEYBOARD.DOWN_ARROW:
return true;
case KEYBOARD.LEFT_ARROW:
case KEYBOARD.RIGHT_ARROW:
if (this.suggestionsManager.hasFocus && this.suggestionsManager.hasPreviews) {
return true;
}
}
return false;
}
private addSelectEventHandlers(suggestions: Suggestion[]) {
each(suggestions, (suggestion: Suggestion) => {
if (suggestion.onSelect == null && suggestion.text != null) {
suggestion.onSelect = () => {
this.setText(suggestion.text);
this.onselect && this.onselect(suggestion);
};
}
});
}
public focus() {
$$(this.element).addClass('magic-box-hasFocus');
this.inputManager.focus();
}
public blur() {
this.inputManager.blur();
}
public async clearSuggestion() {
this.inputManager.setWordCompletion(null);
this.suggestionsManager.clearSuggestions();
this.onSuggestions([]);
}
private focusOnSuggestion(suggestion: Suggestion) {
if (suggestion == null || suggestion.text == null) {
this.inputManager.setResult(this.displayedResult, this.firstSuggestionText);
} else {
this.inputManager.setResult(this.grammar.parse(suggestion.text).clean(), suggestion.text);
}
}
public getText() {
return this.inputManager.getValue();
}
public getWordCompletion() {
return this.inputManager.getWordCompletion();
}
public clear() {
this.setText('');
this.clearSuggestion();
this.focus();
this.onclear && this.onclear();
}
public hasSuggestions() {
return this.suggestionsManager.hasSuggestions;
}
}
export function createMagicBox(element: HTMLElement, grammar: Grammar, options?: Options) {
return new MagicBoxInstance(element, grammar, options);
}
export function requestAnimationFrame(callback: () => void) {
if ('requestAnimationFrame' in window) {
return window.requestAnimationFrame(callback);
}
return setTimeout(callback);
}
doMagicBoxExport();