import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element" export class RemoteSelect extends AlchemyHTMLElement { static properties = { allowClear: { default: false }, selection: { default: undefined }, placeholder: { default: "" }, queryParams: { default: "{}" }, url: { default: "" } } connected() { this.input.classList.add("alchemy_selectbox") $(this.input) .select2(this.select2Config) .on("select2-open", (evt) => { this.onOpen(evt) }) .on("change", (evt) => { this.onChange(evt) }) } /** * Optional on change handler called by Select2. * @param {Event} event */ onChange(event) { this.dispatchCustomEvent("RemoteSelect.Change", { removed: event.removed, added: event.added }) } /** * Optional on open handler called by Select2. * @param {Event} event */ onOpen(event) { // add focus to the search input. Select2 is handling the focus on the first opening, // but it does not work the second time. One process in select2 is "stealing" the focus // if the command is not delayed. It is an intermediate solution until we are going to // move away from Select2 setTimeout(() => { document.querySelector("#select2-drop .select2-input").focus() }, 100) } get input() { return this.getElementsByTagName("input")[0] } get select2Config() { return { placeholder: this.placeholder, allowClear: this.allowClear, initSelection: (_$el, callback) => { if (this.selection) { callback(JSON.parse(this.selection)) } }, ajax: this.ajaxConfig, formatSelection: (item) => this._renderResult(item), formatResult: (item, _el, query) => this._renderListEntry(item, query.term) } } /** * Ajax configuration for Select2 * @returns {object} */ get ajaxConfig() { return { url: this.url, datatype: "json", quietMillis: 300, data: (term, page) => this._searchQuery(term, page), results: (response) => this._parseResponse(response) } } /** * Search query send to server from select2 * @param {string} term * @param {number} page * @returns {object} * @private */ _searchQuery(term, page) { return { q: { name_cont: term, ...JSON.parse(this.queryParams) }, page: page } } /** * Parses server response into select2 results object * @param {object} response * @returns {object} * @private */ _parseResponse(response) { const meta = response.meta return { results: response.data, more: meta.page * meta.per_page < meta.total_count } } /** * result which is visible if a page was selected * @param {object} item * @returns {string} * @private */ _renderResult() { throw new Error( "You need to define a _renderResult function on your sub class!" ) } /** * html template for each list entry * @param {object} item * @param {string} term * @returns {string} * @private */ _renderListEntry() { throw new Error( "You need to define a _renderListEntry function on your sub class!" ) } /** * hightlighted search term * @param {string} name * @param {string} term * @returns {string} * @private */ _hightlightTerm(name, term) { return name.replace(new RegExp(term, "gi"), (match) => `${match}`) } }