Source: src/js/views/filters/SemanticFilterView.js

"use strict";

define([
  "views/filters/FilterView",
  "views/searchSelect/BioontologySelectView",
], (FilterView, BioontologySelectView) => {
  /**
   * @class SemanticFilterView
   * @classdesc Render a specialized view of a single Filter model using the
   * BioontologySelectView. Essentially acts as a connector between the Filter
   * model and the Bioontology model. Uses logic from both the FilterView and
   * the BioontologySelectView.
   * @classcategory Views/Filters
   * @augments FilterView
   * @screenshot views/filters/SemanticFilterView.png
   * @since 2.22.0
   */
  const SemanticFilterView = FilterView.extend(
    /** @lends SemanticFilterView.prototype */ {
      /** @inheritdoc */
      className: "filter semantic",

      /**
       * The ontologies to search for terms in.
       * @type {Array.<{label: string, ontology: string, subTree: string}>}
       * @since 2.31.0
       */
      ontologies: MetacatUI.appModel.get("bioportalOntologies"),

      /** override the template function and use subView instead */
      template() {},

      /**
       * Initialize the SemanticFilterView
       * @param {object} [options] - The options to initialize the view with
       * @param {Array.<{label: string, ontology: string, subTree: string}>} [options.ontologies]
       *  - The ontologies to search for terms in
       * @since 2.31.0
       */
      initialize(options = {}) {
        if (options?.ontologies) this.ontologies = options.ontologies;
        FilterView.prototype.initialize.call(this, options);
      },

      /**
       * Render an instance of a Semantic Filter View. Note that this View
       * doesn't have a template and instead delegates to the
       * BioontologySelectView which renders a SearchSelectView which renders
       * the BioontologySelectView.
       * @returns {SemanticFilterView} This instance
       * @since 2.22.0
       */
      render() {
        // Inserts the FilterEditorView if the mode is uiBuilder
        FilterView.prototype.render.call(this);

        this.subView = new BioontologySelectView({
          placeholderText: this.model.get("placeholder"),
          inputLabel: null,
          ontologies: this.ontologies,
          compact: true,
        }).render();
        this.el.appendChild(this.subView.el);
        this.listenTo(
          this.subView.model,
          "change:selected",
          this.onSubViewSelection,
        );
        return this;
      },

      /**
       * Update the filter model when a class is selected in the
       * BioontologySelectView. Clear the selection/search input from the
       * SelectView and collapse the menu.
       * @since 2.31.0
       */
      onSubViewSelection() {
        const view = this;
        requestAnimationFrame(() => {
          const selected = view.subView.model.getSelectedModels()?.[0];
          if (!selected) return;

          const value = selected.get("value");
          const label = selected.get("label") || value;
          const description = selected.get("description") || "";

          view.setLabelMapping(value, label);
          view.model.set("description", description);
          const newValuesArray = [...view.model.get("values"), value];
          view.model.set("values", newValuesArray);
          view.subView.reset(true);
        });
      },

      /**
       * Set the human-readable label for a term URI.
       * For most uses of the Filter model, the value(s) set on the model can
       * be shown directly in the UI. But for Semantic searches, we need to
       * be able to display a human-readable label for the value because the
       * value is likely an opaque URI.
       * Rather than fetch and/or store all the possible labels for all
       * possible URIs, we store a label for whichever terms the user chooses
       * and keep that around until we need it in the UI.
       * @param {string} term The term URI to set a label for
       * @param {string} label The label to set
       * @since 2.22.0
       */
      setLabelMapping(term, label) {
        const newMappings = { ...(this.model.get("valueLabels") || {}) };
        newMappings[term] = label;
        this.model.set("valueLabels", newMappings);
      },
    },
  );

  return SemanticFilterView;
});