UNPKG

@legumeinfo/web-components

Version:

Web Components for the Legume Information System and other AgBio databases

502 lines 20.6 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 } from 'lit/decorators.js'; import { LisPaginatedSearchMixin } from './mixins'; import { property, state } from 'lit/decorators.js'; import { LisCancelPromiseController } from './controllers'; import { createRef, ref } from 'lit/directives/ref.js'; import { live } from 'lit/directives/live.js'; /** * @htmlElement `<lis-trait-association-search-element>` * * A Web Component that provides a search form for searching for GWAS and QTL trait associations and * displaying the 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 for. * - **species:** The selected species in the search for. * - **type:** The selected type in the search form. Either 'GWAS' or 'QTL'. * - **traits:** The traits provided in the search form. * - **pubid** The publication ID provided in the search form. Either a PubMed ID or a DOI. * - **author** The author provided in the search form. * - **page:** What page of results is loaded. Starts at 1. * * @example * {@link !HTMLElement | `HTMLElement`} properties can only be set via * JavaScript. This means the {@link searchFunction | `searchFunction`} property * must be set on a `<lis-trait-association-search-element>` tag's instance of the * {@link LisTraitAssociationSearchElement | `LisTraitAssociationSearchElement`} class. For example: * ```html * <!-- add the Web Component to your HTML --> * <lis-trait-association-search-element id="trait-association-search"></lis-trait-association-search-element> * * <!-- configure the Web Component via JavaScript --> * <script type="text/javascript"> * // a site-specific function that sends a request to a trait association search API * function getTraits(searchData, {abortSignal}) { * // returns a Promise that resolves to a search result object * } * // get the trait association search element * const searchElement = document.getElementById('trait-association-search'); * // set the element's searchFunction property * searchElement.searchFunction = getTraits; * </script> * ``` * * @example * Data must be provided for the genus and species 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-trait-association-search-element id="trait-association-search"></lis-trait-association-search-element> * * <!-- configure the Web Component via JavaScript --> * <script type="text/javascript"> * // a site-specific function that gets genus and species data from an API * function getFormData() { * // returns a Promise that resolves to a form data object * } * // get the trait association search element * const searchElement = document.getElementById('trait-association-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 fields 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 fields 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-trait-association-search-element genus="Glycine"></lis-trait-association-search-element> * * <!-- restrict the genus and species via HTML --> * <lis-trait-association-search-element genus="Glycine" species="max"></lis-trait-association-search-element> * * <!-- restrict the genus and species via JavaScript --> * <lis-trait-association-search-element id="trait-association-search"></lis-trait-association-search-element> * * <script type="text/javascript"> * // get the trait association search element * const searchElement = document.getElementById('trait-association-search'); * // set the element's genus and species properties * searchElement.genus = "Cicer"; * searchElement.species = "arietinum"; * </script> * ``` * * @example * The {@link traitsExample | `traitsExample`}, {@link publicationExample | `publicationExample`}, and * {@link authorExample | `authorExample`} properties can be used to set the example text for the * Traits, Publication ID, and Author input fields, respectively. For example: * ```html * <!-- set the example text via HTML --> * <lis-trait-association-search-element traitsExample="R8 full maturity" publicationExample="10.2135/cropsci2005.05-0168" authorExample="Specht"></lis-trait-association-search-element> * * <!-- set the example text via JavaScript --> * <lis-trait-association-search-element id="trait-association-search"></lis-trait-association-search-element> * * <script type="text/javascript"> * // get the trait association search element * const searchElement = document.getElementById('trait-association-search'); * // set the element's example text properties * searchElement.traitsExample = 'R8 full maturity'; * searchElement.publicationExample = '10.2135/cropsci2005.05-0168'; * searchElement.authorExample = 'Specht'; * </script> * ``` */ let LisTraitAssociationSearchElement = class LisTraitAssociationSearchElement 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; // available study types this._studyTypes = ['GWAS', 'QTL']; // the index of the selected type of study (GWAS or QTL) this.selectedType = 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'], ['traits'], ['type'], ['pubid'], ['author'], ]; this.resultAttributes = [ 'identifier', 'type', 'synopsis', 'description', 'name', 'genotypes', ]; this.tableHeader = { identifier: 'Study Name', type: 'Study Type', synopsis: 'Synopsis', description: 'Description', name: 'Name', genotypes: 'Genotypes', }; 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 }); } } 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 type = this.queryStringController.getParameter('type'); if (type) { this.selectedType = this._studyTypes.indexOf(type) + 1; } else { this.selectedType = 0; } } // called when a genus is selected _selectGenus(event) { if (event.target != null) { this.selectedGenus = event.target.selectedIndex; this.selectedSpecies = 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; } } // 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 a type is selected _selectType(event) { if (event.target != null) { this.selectedType = event.target.selectedIndex; } } // renders the type selector _renderTypeSelector() { const options = this._studyTypes.map((type) => { return html `<option value="${type}">${type}</option>`; }); return html ` <select class="uk-select uk-form-small" name="type" .selectedIndex=${live(this.selectedType)} @change="${this._selectType}" > <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 typeSelector = this._renderTypeSelector(); // render the form return html ` <form class="uk-form-stacked"> <fieldset class="uk-fieldset"> <legend class="uk-legend">Trait Association 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="type">Study Type</label> ${typeSelector} </div> </div> <div class="uk-margin uk-grid-small" uk-grid> <div class="uk-width-1-3@s"> <label class="uk-form-label" for="traits">Traits</label> <input class="uk-input" type="text" name="traits" .value=${this.queryStringController.getParameter('traits')} /> <lis-form-input-example-element .text=${this.traitsExample} ></lis-form-input-example-element> </div> <div class="uk-width-1-3@s"> <label class="uk-form-label" for="pubId" >Publication ID (DOI or PMID)</label > <input class="uk-input" type="text" name="pubId" .value=${this.queryStringController.getParameter('pubid')} /> <lis-form-input-example-element .text=${this.publicationExample} ></lis-form-input-example-element> </div> <div class="uk-width-1-3@s"> <label class="uk-form-label" for="author">Author</label> <input class="uk-input" type="text" name="author" .value=${this.queryStringController.getParameter('author')} /> <lis-form-input-example-element .text=${this.authorExample} ></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 LisTraitAssociationSearchElement.styles = css ``; __decorate([ property() ], LisTraitAssociationSearchElement.prototype, "formData", void 0); __decorate([ property({ type: Function, attribute: false }) ], LisTraitAssociationSearchElement.prototype, "formDataFunction", void 0); __decorate([ property({ type: String }) ], LisTraitAssociationSearchElement.prototype, "genus", void 0); __decorate([ property({ type: String }) ], LisTraitAssociationSearchElement.prototype, "species", void 0); __decorate([ property({ type: String }) ], LisTraitAssociationSearchElement.prototype, "traitsExample", void 0); __decorate([ property({ type: String }) ], LisTraitAssociationSearchElement.prototype, "publicationExample", void 0); __decorate([ property({ type: String }) ], LisTraitAssociationSearchElement.prototype, "authorExample", void 0); __decorate([ state() ], LisTraitAssociationSearchElement.prototype, "selectedGenus", void 0); __decorate([ state() ], LisTraitAssociationSearchElement.prototype, "selectedSpecies", void 0); __decorate([ state() ], LisTraitAssociationSearchElement.prototype, "selectedType", void 0); LisTraitAssociationSearchElement = __decorate([ customElement('lis-trait-association-search-element') ], LisTraitAssociationSearchElement); export { LisTraitAssociationSearchElement }; //# sourceMappingURL=lis-trait-association-search-element.js.map