@legumeinfo/web-components
Version:
Web Components for the Legume Information System and other AgBio databases
516 lines • 20.9 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { live } from 'lit/directives/live.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { LisCancelPromiseController } from './controllers';
import { LisPaginatedSearchMixin } from './mixins';
/**
* @htmlElement `<lis-gene-search-element>`
*
* A Web Component that provides an interface for performing searches for genes and
* displaying results in a view table. Note that the component saves its state to the
* URL query string parameters and a search will be automatically performed if the
* parameters are present when the componnent is loaded. The component uses the
* {@link mixins!LisPaginatedSearchMixin | `LisPaginatedSearchMixin`} mixin. See the
* mixin docs for further details.
*
* @queryStringParameters
* - **genus:** The selected genus in the search form.
* - **species:** The selected species in the search form.
* - **strain:** The selected strain in the search form.
* - **identifier:** The identifier provided in the search form.
* - **description:** The description provided in the search form.
* - **family:** The gene family identifier provided in the search form.
* - **page:** What page of results to load.
*
* @example
* {@link !HTMLElement | `HTMLElement`} properties can only be set via
* JavaScript. This means the {@link searchFunction | `searchFunction`} property
* must be set on a `<lis-gene-search-element>` tag's instance of the
* {@link LisGeneSearchElement | `LisGeneSearchElement`} class. For example:
* ```html
* <!-- add the Web Component to your HTML -->
* <lis-gene-search-element id="gene-search"></lis-gene-search-element>
*
* <!-- configure the Web Component via JavaScript -->
* <script type="text/javascript">
* // a site-specific function that sends a request to a gene search API
* function getGenes(searchData, {abortSignal}) {
* // returns a Promise that resolves to a search result object
* }
* // get the gene search element
* const searchElement = document.getElementById('gene-search');
* // set the element's searchFunction property
* searchElement.searchFunction = getGenes;
* </script>
* ```
*
* @example
* Data must be provided for the genus, species, and strain selectors in the search form.
* This can be done by setting the form's {@link formData | `formData`}
* attribute/property directly or by setting the
* {@link formDataFunction | `formDataFunction`} property. Setting the latter will call
* the function immediately and set the {@link formData | `formData`} value using the
* result. For example:
* ```html
* <!-- add the Web Component to your HTML -->
* <lis-gene-search-element id="gene-search"></lis-gene-search-element>
*
* <!-- configure the Web Component via JavaScript -->
* <script type="text/javascript">
* // a site-specific function that gets genus, species, and strain data from an API
* function getFormData() {
* // returns a Promise that resolves to a form data object
* }
* // get the gene search element
* const searchElement = document.getElementById('gene-search');
* // set the element's formDataFunction property
* searchElement.formDataFunction = getGeneFormData;
* </script>
* ```
*
* @example
* The {@link genus | `genus`} and {@link species | `species`} properties can be used to limit all
* searches to a specific genus and species. This will cause the genus and species field of the
* search form to be automatically set and disabled so that users cannot change them. Additionally,
* these properties cannot be overridden using the `genus` and `species` querystring parameters.
* However, like the `genus` and `species` querystring parameters, if the genus/species set are not
* present in the `formData` then the genus/species form field will be set to the default `any`
* value. Note that setting the `species` value has no effect if the `genus` value is not also set.
* For example:
* ```html
* <!-- restrict the genus via HTML -->
* <lis-gene-search-element genus="Glycine"></lis-gene-search-element>
*
* <!-- restrict the genus and species via HTML -->
* <lis-gene-search-element genus="Glycine" species="max"></lis-gene-search-element>
*
* <!-- restrict the genus and species via JavaScript -->
* <lis-gene-search-element id="gene-search"></lis-gene-search-element>
*
* <script type="text/javascript">
* // get the gene search element
* const searchElement = document.getElementById('gene-search');
* // set the element's genus and species properties
* searchElement.genus = "Cicer";
* searchElement.species = "arietinum";
* </script>
* ```
*
* @example
* The {@link identifierExample | `identifierExample`}, {@link descriptionExample | `descriptionExample`},
* and {@link familyExample | `familyExample`} properties can be used to set the example text for the
* identifier, description, and gene family input fields, respectively. For example:
* ```html
* <!-- set the example text via HTML -->
* <lis-gene-search-element identifierExample="Glyma.13G357700" descriptionExample="protein disulfide isomerase-like protein" familyExample="L_HZ6G4Z"></lis-gene-search-element>
*
* <!-- set the example text via JavaScript -->
* <lis-gene-search-element id="gene-search"></lis-gene-search-element>
*
* <script type="text/javascript">
* // get the gene search element
* const searchElement = document.getElementById('gene-search');
* // set the element's example text properties
* searchElement.identifierExample = 'Glyma.13G357700';
* searchElement.descriptionExample = 'protein disulfide isomerase-like protein';
* searchElement.familyExample = 'L_HZ6G4Z';
* </script>
* ```
*/
let LisGeneSearchElement = class LisGeneSearchElement extends LisPaginatedSearchMixin(LitElement)() {
constructor() {
super();
/**
* The data used to construct the search form in the template.
*
* @attribute
*/
this.formData = { genuses: [] };
/**
* An optional property that can be used to load the form data via an external function.
* If used, the `formData` attribute/property will be updated using the result.
*/
this.formDataFunction = () => Promise.reject(new Error('No form data function provided'));
// the selected index of the genus select element
this.selectedGenus = 0;
// the selected index of the species select element
this.selectedSpecies = 0;
// the selected index of the strain select element
this.selectedStrain = 0;
// a controller that allows in-flight form data requests to be cancelled
this.formDataCancelPromiseController = new LisCancelPromiseController(this);
// bind to the loading element in the template
this._formLoadingRef = createRef();
// configure query string parameters
this.requiredQueryStringParams = [
['genus'],
['genus', 'species'],
['genus', 'species', 'strain'],
['identifier'],
['description'],
['family'],
];
this.resultAttributes = [
'name',
'identifier',
'locations',
'description',
'geneFamilyAssignments',
'panGeneSets',
'genus',
'species',
'strain',
];
this.tableHeader = {
name: 'Name',
identifier: 'Identifier',
description: 'Description',
genus: 'Genus',
species: 'Species',
strain: 'Strain',
geneFamilyAssignments: 'Gene Family Assignments',
panGeneSets: 'PanGene Sets',
locations: 'Locations',
};
this.tableColumnClasses = {
description: 'uk-table-expand',
};
}
_getDefaultGenus() {
return this.valueOrQuerystringParameter(this.genus, 'genus');
}
_getDefaultSpecies() {
return this.valueOrQuerystringParameter(this.species, 'species');
}
// called when the component is added to the DOM; attributes should have properties now
connectedCallback() {
super.connectedCallback();
// initialize the form data with querystring parameters so a search can be performed
// before the actual form data is loaded
const formData = { genuses: [] };
const genus = this._getDefaultGenus();
if (genus) {
formData.genuses.push({ genus, species: [] });
const species = this._getDefaultSpecies();
if (species) {
formData.genuses[0].species.push({ species, strains: [] });
const strain = this.queryStringController.getParameter('strain');
if (strain) {
formData.genuses[0].species[0].strains.push({ strain });
}
}
}
this.formData = formData;
// set the selector values before the DOM is updated when the querystring parameters change
this.queryStringController.addPreUpdateListener((_) => {
this._initializeSelections();
});
}
// called after every component update, e.g. when a property changes
updated(changedProperties) {
// call the formDataFunction every time its value changes
if (changedProperties.has('formDataFunction')) {
this._getFormData();
}
// use querystring parameters to update the selectors when the form data changes
if (changedProperties.has('formData') ||
changedProperties.has('genus') ||
changedProperties.has('species')) {
this._initializeSelections();
}
}
// gets the data for the search form
_getFormData() {
var _a;
// update the loading element
(_a = this._formLoadingRef.value) === null || _a === void 0 ? void 0 : _a.loading();
// make the form data function cancellable
this.formDataCancelPromiseController.cancel();
const options = {
abortSignal: this.formDataCancelPromiseController.abortSignal,
};
const formDataPromise = this.formDataFunction(options);
// call the cancellable function
this.formDataCancelPromiseController.wrapPromise(formDataPromise).then((formData) => {
var _a;
(_a = this._formLoadingRef.value) === null || _a === void 0 ? void 0 : _a.success();
this.formData = formData;
}, (error) => {
var _a;
// do nothing if the request was aborted
if (!(error instanceof Event && error.type === 'abort')) {
(_a = this._formLoadingRef.value) === null || _a === void 0 ? void 0 : _a.failure();
throw error;
}
});
}
// sets the selected indexes based on properties and querystring parameters
async _initializeSelections() {
const genus = this._getDefaultGenus();
if (genus) {
this.selectedGenus =
this.formData.genuses.map(({ genus }) => genus).indexOf(genus) + 1;
}
else {
this.selectedGenus = 0;
}
await this.updateComplete;
const species = this._getDefaultSpecies();
if (this.selectedGenus && species) {
this.selectedSpecies =
this.formData.genuses[this.selectedGenus - 1].species
.map(({ species }) => species)
.indexOf(species) + 1;
}
else {
this.selectedSpecies = 0;
}
await this.updateComplete;
const strain = this.queryStringController.getParameter('strain');
if (this.selectedSpecies && strain) {
this.selectedStrain =
this.formData.genuses[this.selectedGenus - 1].species[this.selectedSpecies - 1].strains
.map(({ strain }) => strain)
.indexOf(strain) + 1;
}
else {
this.selectedStrain = 0;
}
}
// called when a genus is selected
_selectGenus(event) {
if (event.target != null) {
this.selectedGenus = event.target.selectedIndex;
this.selectedSpecies = 0;
this.selectedStrain = 0;
}
}
// renders the genus selector
_renderGenusSelector() {
const options = this.formData.genuses.map(({ genus }) => {
return html `<option value="${genus}">${genus}</option>`;
});
// HACK: the disabled attribute can't be set via template literal...
if (this.genus !== undefined) {
const value = this.selectedGenus
? this.formData.genuses[this.selectedGenus - 1].genus
: '';
return html `
<select
class="uk-select uk-form-small"
disabled
.selectedIndex=${live(this.selectedGenus)}
="${this._selectGenus}"
>
<option value="">-- any --</option>
${options}
</select>
<input type="hidden" name="genus" value="${value}" />
`;
}
return html `
<select
class="uk-select uk-form-small"
name="genus"
.selectedIndex=${live(this.selectedGenus)}
="${this._selectGenus}"
>
<option value="">-- any --</option>
${options}
</select>
`;
}
// called when a species is selected
_selectSpecies(event) {
if (event.target != null) {
this.selectedSpecies = event.target.selectedIndex;
this.selectedStrain = 0;
}
}
// renders the species selector
_renderSpeciesSelector() {
let options = [html ``];
if (this.selectedGenus) {
options = this.formData.genuses[this.selectedGenus - 1].species.map(({ species }) => {
return html `<option value="${species}">${species}</option>`;
});
}
// HACK: the disabled attribute can't be set via template literal...
if (this.genus !== undefined && this.species !== undefined) {
const value = this.selectedGenus && this.selectedSpecies
? this.formData.genuses[this.selectedGenus - 1].species[this.selectedSpecies - 1].species
: '';
return html `
<select
class="uk-select uk-form-small"
disabled
.selectedIndex=${live(this.selectedSpecies)}
="${this._selectSpecies}"
>
<option value="">-- any --</option>
${options}
</select>
<input type="hidden" name="species" value="${value}" />
`;
}
return html `
<select
class="uk-select uk-form-small"
name="species"
.selectedIndex=${live(this.selectedSpecies)}
="${this._selectSpecies}"
>
<option value="">-- any --</option>
${options}
</select>
`;
}
// called when an strain is selected
_selectStrain(event) {
if (event.target != null) {
this.selectedStrain = event.target.selectedIndex;
}
}
// renders the strain selector
_renderStrainSelector() {
let options = [html ``];
if (this.selectedGenus && this.selectedSpecies) {
options = this.formData.genuses[this.selectedGenus - 1].species[this.selectedSpecies - 1].strains.map(({ strain }) => {
return html `<option value="${strain}">${strain}</option>`;
});
}
return html `
<select
class="uk-select uk-form-small"
name="strain"
.selectedIndex=${live(this.selectedStrain)}
="${this._selectStrain}"
>
<option value="">-- any --</option>
${options}
</select>
`;
}
/** @ignore */
// used by LisPaginatedSearchMixin to draw the search form part of template
renderForm() {
// render the form's selectors
const genusSelector = this._renderGenusSelector();
const speciesSelector = this._renderSpeciesSelector();
const strainSelector = this._renderStrainSelector();
// render the form
return html `
<form class="uk-form-stacked">
<fieldset class="uk-fieldset">
<legend class="uk-legend">Gene Search</legend>
<lis-loading-element
${ref(this._formLoadingRef)}
></lis-loading-element>
<div class="uk-margin uk-grid-small" uk-grid>
<div class="uk-width-1-3@s">
<label class="uk-form-label" for="genus">Genus</label>
${genusSelector}
</div>
<div class="uk-width-1-3@s">
<label class="uk-form-label" for="species">Species</label>
${speciesSelector}
</div>
<div class="uk-width-1-3@s">
<label class="uk-form-label" for="strain">Strain</label>
${strainSelector}
</div>
</div>
<div class="uk-margin uk-grid-small" uk-grid>
<div class="uk-width-1-3@s">
<label class="uk-form-label" for="identifier">Identifier</label>
<input
class="uk-input"
type="text"
name="identifier"
.value=${this.queryStringController.getParameter('identifier')}
/>
<lis-form-input-example-element
.text=${this.identifierExample}
></lis-form-input-example-element>
</div>
<div class="uk-width-1-3@s">
<label class="uk-form-label" for="description">Description</label>
<input
class="uk-input"
type="text"
name="description"
.value=${this.queryStringController.getParameter('description')}
/>
<lis-form-input-example-element
.text=${this.descriptionExample}
></lis-form-input-example-element>
</div>
<div class="uk-width-1-3@s">
<label class="uk-form-label" for="family">Gene Family ID</label>
<input
class="uk-input"
type="text"
name="family"
.value=${this.queryStringController.getParameter('family')}
/>
<lis-form-input-example-element
.text=${this.familyExample}
></lis-form-input-example-element>
</div>
</div>
<div class="uk-margin">
<button type="submit" class="uk-button uk-button-primary">
Search
</button>
</div>
</fieldset>
</form>
`;
}
};
/** @ignore */
// used by Lit to style the Shadow DOM
// not necessary but exclusion breaks TypeDoc
LisGeneSearchElement.styles = css ``;
__decorate([
property()
], LisGeneSearchElement.prototype, "formData", void 0);
__decorate([
property({ type: Function, attribute: false })
], LisGeneSearchElement.prototype, "formDataFunction", void 0);
__decorate([
property({ type: String })
], LisGeneSearchElement.prototype, "genus", void 0);
__decorate([
property({ type: String })
], LisGeneSearchElement.prototype, "species", void 0);
__decorate([
property({ type: String })
], LisGeneSearchElement.prototype, "identifierExample", void 0);
__decorate([
property({ type: String })
], LisGeneSearchElement.prototype, "descriptionExample", void 0);
__decorate([
property({ type: String })
], LisGeneSearchElement.prototype, "familyExample", void 0);
__decorate([
state()
], LisGeneSearchElement.prototype, "selectedGenus", void 0);
__decorate([
state()
], LisGeneSearchElement.prototype, "selectedSpecies", void 0);
__decorate([
state()
], LisGeneSearchElement.prototype, "selectedStrain", void 0);
LisGeneSearchElement = __decorate([
customElement('lis-gene-search-element')
], LisGeneSearchElement);
export { LisGeneSearchElement };
//# sourceMappingURL=lis-gene-search-element.js.map