UNPKG

@legumeinfo/web-components

Version:

Web Components for the Legume Information System and other AgBio databases

516 lines 20.9 kB
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)} @change="${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)} @change="${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)} @change="${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)} @change="${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)} @change="${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