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

define([
  "jquery",
  "underscore",
  "backbone",
  "models/filters/Filter",
  "views/filters/FilterView",
  "views/searchSelect/AnnotationFilterView",
], function ($, _, Backbone, Filter, FilterView, AnnotationFilterView) {
  "use strict";

  /**
   * @class SemanticFilterView
   * @classdesc Render a specialized view of a single Filter model using the
   *   AnnotationFilterView.
   * @classcategory Views/Filters
   * @extends FilterView
   * @screenshot views/filters/SemanticFilterView.png
   * @since 2.22.0
   */
  var SemanticFilterView = FilterView.extend(
    /** @lends SemanticFilterView.prototype */ {
      /**
       * @inheritdoc
       */
      model: null,

      /**
       * @inheritdoc
       */
      modelClass: Filter,

      className: "filter semantic",

      // Template is an empty function because this view delegates to the
      // AnnotationFilterView. See render() method.
      template: function () {},

      /**
       * Render an instance of a Semantic Filter View.
       *
       * Note that this View doesn't have a template and instead delegates to
       * the AnnotationFilterView which renders a SearchableSelectView which
       * renders an NCBOTree.
       * @since 2.22.0
       */
      render: function () {
        try {
          var templateVars = this.model.toJSON();
          templateVars.id = this.model.cid;

          // Renders the template and inserts the FilterEditorView if the mode is uiBuilder
          FilterView.prototype.render.call(this, templateVars);

          var viewOpts = {
            useSearchableSelect: true,
            placeholderText: templateVars.placeholder,
            inputLabel: null, // Hides label and uses FilterView label
            ontology: this.model.get("ontology"),
            startingRoot: this.model.get("startingRoot"),
          };

          var subView = new AnnotationFilterView(viewOpts);

          this.$el.append(subView.el);
          subView.render();

          var view = this;
          subView.on("annotationSelected", function (event, item) {
            // Get the value of the associated input
            var term = !item || !item.value ? input.val() : item.value;
            var label = !item || !item.filterLabel ? null : item.filterLabel;

            // Set up a label mapping for the term so we can display a
            // human-readable label for it in the UI
            view.setLabelMapping(term, label);

            // Set the value, supports multiple values
            var currentValue = view.model.get("values");
            var newValuesArray = _.flatten(new Array(currentValue, term));
            view.model.set("values", newValuesArray);

            view.defocus();
          });
        } catch (error) {
          console.log(
            "There was an error rendering a SemanticFilterView." +
              " Error details: " +
              error,
          );
        }
      },

      /**
       * Helper function which defocuses the dropdown portion of the
       * SearchableSelectView used by this View's AnnotationFilterView. When the
       * user clicks an item in the NCBOTree widget, we want the
       * SearchableSelectView's dropdown to go away and I couldn't find any API
       * to do that so we have this code. See the render() method to see how it's
       * called.
       *
       * Note: This isn't really a stable API and is really something we might
       * remove in the future if we refactor the NCBOTree widget.
       * @since 2.22.0
       */
      defocus: function () {
        this.$el.find("div.menu").removeClass("visible").addClass("hidden");
        this.$el
          .find("div.fluid.ui.dropdown")
          .removeClass("active")
          .removeClass("visible");
        this.$el.find("input").blur();
      },

      /**
       * 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: function (term, label) {
        var newMappings;

        if (this.model.get("valueLabels")) {
          newMappings = _.clone(this.model.get("valueLabels"));
        } else {
          newMappings = new Object();
        }

        newMappings[term] = label;
        this.model.set("valueLabels", newMappings);
      },
    },
  );

  return SemanticFilterView;
});