@fleetbase/ember-ui
Version:
Fleetbase UI provides all the interface components, helpers, services and utilities for building a Fleetbase extension into the Console.
259 lines (216 loc) • 7.01 kB
JavaScript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { isEmpty } from '@ember/utils';
import { action, set } from '@ember/object';
import { isArray } from '@ember/array';
import { assert } from '@ember/debug';
import { timeout, restartableTask } from 'ember-concurrency';
/**
* FetchSelectComponent is a Glimmer component responsible for rendering a
* select input and fetching options asynchronously based on user input.
*
* @class FetchSelectComponent
* @extends Component
* @memberof FleetbaseComponents
*
* @property {Service} fetch - The fetch service injected into the component.
* @property {Array} options - The list of selectable options.
* @property {Object} selected - The currently selected option.
* @property {number} debounceDuration - The duration to debounce the search input, in milliseconds.
*/
export default class FetchSelectComponent extends Component {
/**
* The fetch service is used to make network requests to fetch the options for the select input.
* @type {Service}
*/
fetch;
/**
* The list of selectable options.
* @type {Array}
*/
options = [];
/**
* The currently selected option.
* @type {Object}
*/
selected;
/**
* The power select API.
* @type {Object}
*/
api;
/**
* The duration to debounce the search input, in milliseconds.
* @type {number}
*/
debounceDuration = 250;
/**
* The constructor ensures that the endpoint argument is specified, and
* initializes the component's properties based on the arguments passed to it.
*/
constructor() {
super(...arguments);
assert('<FetchSelect /> requires a valid `endpoint`.', !isEmpty(this.args.endpoint));
this.endpoint = this.args.endpoint;
this.selected = this.setSelectedOption(this.args.selected);
// this.debounceDuration = this.args.debounceDuration || this.debounceDuration;
}
/**
* Searches for options based on the term provided. Debounces the search
* if it's not the initial load.
*
* @param {string} term - The search term.
* @param {Object} [options={}] - Additional options for the search.
* @param {boolean} [initialLoad=false] - Whether this is the initial load.
* @task
*/
searchOptions = function* (term, options = {}, initialLoad = false) {
if (!initialLoad) {
yield timeout(this.debounceDuration);
}
yield this.fetchOptions.perform(term, options);
};
/**
* Fetches options based on the term provided.
*
* @param {string} term - The search term.
* @param {Object} [options={}] - Additional options for the fetch.
* @task
*/
fetchOptions = function* (term, options = {}) {
// query might be an EmptyObject/{{hash}}, make it a normal Object
const query = Object.assign({}, this.args.query);
if (term) {
set(query, 'query', term);
}
let _options = yield this.fetch.get(this.endpoint, query, options);
// if options returns is an object and not array
if (this.isFetchResponseObject(_options)) {
_options = this.convertOptionsObjectToArray(_options);
}
// set options
this.options = _options;
return _options;
};
convertOptionsObjectToArray(_options) {
const objectKeys = Object.keys(_options);
const _optionsFromObject = [];
objectKeys.forEach((key) => {
_optionsFromObject.pushObject({
key,
value: _options[key],
});
});
return _optionsFromObject;
}
isFetchResponseObject(_options) {
return !isArray(_options) && typeof _options === 'object' && Object.keys(_options).length;
}
/**
* Set the selected option.
*
* @param {*} selected
* @memberof FetchSelectComponent
*/
setSelectedOption(selected) {
const { optionValue } = this.args;
if (optionValue) {
this.fetchOptions.perform().then((options) => {
let foundSelected = null;
if (isArray(options)) {
foundSelected = options.find((option) => option[optionValue] === selected);
}
if (foundSelected) {
this.select(foundSelected);
} else {
this.select(selected);
}
});
} else {
this.select(selected);
}
}
select(option) {
this.selected = option;
// set via api
if (this.api && this.api.actions && typeof this.api.actions.select === 'function') {
this.api.actions.select(option);
}
}
/**
* Loads the default set of options.
*/
loadDefaultOptions() {
const { loadDefaultOptions } = this.args;
if (loadDefaultOptions === undefined || loadDefaultOptions) {
this.fetchOptions.perform(null, {}, true);
}
}
/**
* Called when the select input is opened.
* @action
*/
onOpen() {
const { onOpen } = this.args;
this.loadDefaultOptions();
if (typeof onOpen === 'function') {
onOpen(...arguments);
}
}
/**
* Called when the user inputs a search term.
*
* @param {string} term - The search term.
* @action
*/
onInput(term) {
const { onInput } = this.args;
if (isEmpty(term)) {
this.loadDefaultOptions();
}
if (typeof onInput === 'function') {
onInput(...arguments);
}
}
/**
* Called when an option is selected.
*
* @param {Object} option - The selected option.
* @action
*/
onChange(option, ...rest) {
const { onChange, optionValue } = this.args;
// set selected
this.selected = option;
// if option value supplied
if (optionValue && typeof option === 'object') {
option = option[optionValue];
}
if (typeof onChange === 'function') {
onChange(option, ...rest);
}
}
/**
* Called when the select input is closed.
* @action
*/
onClose() {
const { onClose } = this.args;
this.fetchOptions.cancelAll();
if (typeof onClose === 'function') {
onClose(...arguments);
}
}
/**
* Register the power select API
*
* @memberof FetchSelectComponent
*/
registerAPI(api) {
this.api = api;
if (typeof this.args.registerAPI === 'function') {
this.args.registerAPI(...arguments);
}
}
}