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

/*global define */
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;
  });