"use strict";
define(["backbone", "text!templates/maps/search-input.html"], (
Backbone,
Template,
) => {
const BASE_CLASS = "search-input";
const CLASS_NAMES = {
searchButton: `${BASE_CLASS}__search-button`,
searchButtonActive: `${BASE_CLASS}__search-button--active`,
cancelButton: `${BASE_CLASS}__cancel-button`,
cancelButtonContainer: `${BASE_CLASS}__cancel-button-container`,
input: `${BASE_CLASS}__input`,
inputField: `${BASE_CLASS}__field`,
errorInput: `${BASE_CLASS}__error-input`,
errorText: `${BASE_CLASS}__error-text`,
};
/**
* @class SearchInputView
* @classdesc SearchInputView is a shared component for searching information in the
* map toolbar.
* @classcategory Views/Maps
* @name SearchInputView
* @extends Backbone.View
* @since 2.28.0
* @constructs SearchInputView
*/
const SearchInputView = Backbone.View.extend(
/** @lends SearchInputView.prototype */ {
/**
* The type of View this is
* @type {string}
*/
type: "SearchInputView",
/**
* The HTML classes to use for this view's element
* @type {string}
*/
className: BASE_CLASS,
/**
* Values meant to be used by the rendered HTML template.
*/
templateVars: {
errorText: "",
placeholder: "",
classNames: CLASS_NAMES,
},
/**
* The events this view will listen to and the associated function to call.
* @type {Object}
*/
events() {
return {
[`click .${CLASS_NAMES.cancelButton}`]: "onCancel",
[`blur .${CLASS_NAMES.input}`]: "onBlur",
[`change .${CLASS_NAMES.input}`]: "onKeyup",
[`focus .${CLASS_NAMES.input}`]: "onFocus",
[`keydown .${CLASS_NAMES.input}`]: "onKeydown",
[`keyup .${CLASS_NAMES.input}`]: "onKeyup",
[`click .${CLASS_NAMES.searchButton}`]: "onSearch",
};
},
/**
* @typedef {Object} SearchInputViewOptions
* @property {Function} search A function that takes in a text input and returns
* a boolean for whether there is a match.
* @property {Function} keydownCallback A function that receives a key event
* on keydown.
* @property {Function} keyupCallback A function that receives a key event
* on keyup stroke.
* @property {Function} blurCallback A function that receives an event on
* blur of the input.
* @property {Function} focusCallback A function that receives an event on
* focus of the input.
* @property {Function} noMatchCallback A callback function to handle a no match
* situation.
* @property {String} placeholder The placeholder text for the input box.
*/
initialize(options) {
if (typeof options.search !== "function") {
throw new Error(
"Initializing SearchInputView without a search function.",
);
}
this.search = options.search;
this.keyupCallback = options.keyupCallback || noop;
this.keydownCallback = options.keydownCallback || noop;
this.blurCallback = options.blurCallback || noop;
this.focusCallback = options.focusCallback || noop;
this.noMatchCallback = options.noMatchCallback || noop;
this.templateVars.placeholder = options.placeholder;
},
/**
* Render the view by updating the HTML of the element.
* The new HTML is computed from an HTML template that
* is passed an object with relevant view state.
* */
render() {
this.el.innerHTML = _.template(Template)(this.templateVars);
},
/**
* Event handler for Backbone.View configuration that is called whenever
* the user types a key.
*/
onKeyup(event) {
if (event.key === "Enter") {
this.onSearch();
return;
}
if (this.getInputValue().toLowerCase() !== "") {
this.showCancelAndSearch();
} else {
this.hideCancelAndDimSearch();
}
this.keyupCallback(event);
},
/**
* Manage state change for the search button and cancel button when user has
* entered some input.
*/
showCancelAndSearch() {
this.getCancelButtonContainer().show();
this.getSearchButton().addClass(CLASS_NAMES.searchButtonActive);
},
/**
* Manage state change for the search button and cancel button when user has
* cleared the input.
*/
hideCancelAndDimSearch() {
this.getCancelButtonContainer().hide();
this.getSearchButton().removeClass(CLASS_NAMES.searchButtonActive);
},
/**
* Event handler for Backbone.View configuration that is called whenever
* the user types a key.
*/
onKeydown(event) {
this.keydownCallback(event);
if (this.getInputValue() === "") {
this.clearError();
}
},
/**
* Event handler for Backbone.View configuration that is called whenever
* the user focuses the input.
*/
onFocus(event) {
this.focusCallback(event);
},
/**
* Event handler for Backbone.View configuration that is called whenever
* the user blurs the input.
*/
onBlur(event) {
this.blurCallback(event);
},
/**
* Event handler for Backbone.View configuration that is called whenever
* the user clicks the search button or hits the Enter key.
*/
onSearch() {
this.getError().hide();
const inputField = this.getInputField();
const inputValue = this.getInputValue().toLowerCase();
const matched = this.search(inputValue);
if (matched) {
this.clearError();
} else if (typeof this.noMatchCallback === "function") {
this.noMatchCallback();
}
},
/**
* API for the view that conducts the search to toggle on the error message.
* @param {string} errorText
*/
setError(errorText) {
if (errorText) {
this.getInputField().addClass(CLASS_NAMES.errorInput);
const errorTextEl = this.getError();
errorTextEl.html(errorText);
errorTextEl.show();
} else {
this.clearError();
}
},
/**
* Clear error text, remove error styling and hide the error element.
*/
clearError() {
this.getInputField().removeClass(CLASS_NAMES.errorInput);
const errorTextEl = this.getError();
errorTextEl.hide();
errorTextEl.html("");
},
/**
* Handler function for the cancel icon button action.
*/
onCancel() {
this.hideCancelAndDimSearch();
this.getInput().val("");
this.onSearch();
this.focus();
this.clearError();
},
/**
* Focus the input field in this View.
*/
focus() {
this.getInput().trigger("focus");
},
/**
* Blur the input field in this View.
*/
blur() {
this.getInput().trigger("blur");
},
/**
* Get the search icon button.
* @return jQuery element representing the search icon button. Or an empty
* jQuery selector if the button is not found.
*/
getSearchButton() {
return this.$(`.${CLASS_NAMES.searchButton}`);
},
/**
* Get the cancel icon button container.
* @return jQuery element representing the cancel icon button container. Or
* an empty jQuery selector if the button is not found.
*/
getCancelButtonContainer() {
return this.$(`.${CLASS_NAMES.cancelButtonContainer}`);
},
/**
* Get the container element for the input.
* @return jQuery element representing the input container. Or an empty
* jQuery selector if the button is not found.
*/
getInputField() {
return this.$(`.${CLASS_NAMES.inputField}`);
},
/**
* Get the input.
* @return jQuery element representing the input. Or an empty
* jQuery selector if the button is not found.
*/
getInput() {
return this.$(`.${CLASS_NAMES.input}`);
},
/**
* Get the error text element.
* @return jQuery element representing the error text. Or an empty
* jQuery selector if the button is not found.
*/
getError() {
return this.$(`.${CLASS_NAMES.errorText}`);
},
/**
* Get the current value of the input field.
* @return The current value of the input field or empty string if the
* input field is not found.
*/
getInputValue() {
return this.getInput().val() || "";
},
/**
* Set the current value of the input field.
*/
setInputValue(value) {
this.getInput().val(value);
},
},
);
// A function that does nothing. Can be safely called as a default callback.
const noop = () => {};
return SearchInputView;
});