Source: src/js/views/searchSelect/AccountSelectView.js

define([
  "jquery",
  "underscore",
  "backbone",
  "views/searchSelect/SearchableSelectView",
  "models/LookupModel",
], function ($, _, Backbone, SearchableSelect, LookupModel) {
  /**
   * @class AccountSelectView
   * @classdesc A select interface that allows the user to search for and select one or
   * more accountIDs
   * @classcategory Views/SearchSelect
   * @extends SearchableSelect
   * @constructor
   * @since 2.15.0
   * @screenshot views/searchSelect/AccountSelectViewView.png
   */
  var AccountSelectView = SearchableSelect.extend(
    /** @lends AccountSelectViewView.prototype */
    {
      /**
       * The type of View this is
       * @type {string}
       * @since 2.15.0
       */
      type: "AccountSelect",

      /**
       * The HTML class names for this view element
       * @type {string}
       * @since 2.15.0
       */
      className: SearchableSelect.prototype.className + " account-select",

      /**
       * Text to show in the input field before any value has been entered
       * @type {string}
       * @default "Start typing a name"
       * @since 2.15.0
       */
      placeholderText: "Start typing a name",

      /**
       * Label for the input element
       * @type {string}
       * @default "Search for a person or group"
       * @since 2.15.0
       */
      inputLabel: "Search for a person or group",

      /**
       * Whether to allow users to select more than one value
       * @type {boolean}
       * @default true
       * @since 2.15.0
       */
      allowMulti: true,

      /**
       * Setting to true gives users the ability to add their own options that
       * are not listed in this.options. This can work with either single
       * or multiple search select dropdowns
       * @type {boolean}
       * @default true
       */
      allowAdditions: true,

      /**
       * Can be set to an object to specify API settings for retrieving remote selection
       * menu content from an API endpoint. Details of what can be set here are
       * specified by the Semantic-UI / Fomantic-UI package. Set to false if not
       * retrieving remote content.
       * @type {Object|booealn}
       * @since 2.15.0
       * @see {@link https://fomantic-ui.com/modules/dropdown.html#remote-settings}
       * @see {@link https://fomantic-ui.com/behaviors/api.html#/settings}
       */
      apiSettings: {
        // Use the Accounts lookup to search for a person or group when the user types
        // something into the input
        responseAsync: function (settings, callback) {
          var view = $(this).data("view");

          // The search term that the user has typed into the input
          var searchTerm = settings.urlData.query;

          // Only use the account lookup service is the user has typed at least two
          // characters. Otherwise, the callback function is never called.
          if (searchTerm.length < 2) {
            callback({
              success: false,
            });
            return;
          }

          // For search terms at least 2 characters long, use the Lookup Model
          MetacatUI.appLookupModel.getAccountsAutocomplete(
            { term: searchTerm },
            function (results) {
              // If no match was found to the search term
              if (results && results.length < 1) {
                callback({
                  success: false,
                });
                return;
              }

              // If there were results, format for the semantic UI dropdown function
              results = view.formatResults(results);

              callback({
                success: true,
                results: results,
              });
            },
          );
        },
      },

      /**
       * Creates a new SearchableSelectView
       * @param {Object} options - A literal object with options to pass to the view
       * @since 2.15.0
       */
      initialize: function (options) {
        try {
          var view = this;

          // Ensure there is a lookup model ready for this view to use.
          if (MetacatUI.appLookupModel === "undefined") {
            MetacatUI.appLookupModel = new LookupModel();
          }

          SearchableSelect.prototype.initialize.call(view, options);
        } catch (e) {
          console.log(
            "Failed to initialize an Account Select view, error message:",
            e,
          );
        }
      },

      /**
       * Takes the results returned from from
       * {@link LookupModel#getAccountsAutocomplete} and re-formats them for other
       * functions. When the forTemplate argument is false, the results are formatted as
       * a list mapping dropdown content specifically for the FomanticUI API. When
       * forTemplate is true, then the results are formatted for use in the
       * SearchableSelectView template: {@link SearchableSelectView#template}.
       *
       * @param  {Object[]} results The response from
       * {@link LookupModel#getAccountsAutocomplete}
       * @param  {boolean} forTemplate=false Whether or not to format the results for
       * the {@link SearchableSelectView#template}
       * @return {Object[]} The re-formatted results
       *
       * @see {@link https://fomantic-ui.com/modules/dropdown.html#remote-settings}
       * @since 2.15.0
       */
      formatResults: function (results, forTemplate = false) {
        results = _.map(results, function (result) {
          if (forTemplate) {
            // Get the ID which is saved in the parentheses
            var regExp = /\(([^)]+)\)$/;
            var matches = regExp.exec(result.label);
            //matches[1] contains the value between the parentheses
            result.description = "Account ID: " + matches[1];
            result.label = result.label.replace(regExp, "");
            result.label = result.label.trim();
          } else {
            result.label = result.label.replace(
              "(",
              '<span class="description">',
            );
            result.label = result.label.replace(")", "</span>");
          }

          var icon = "";

          if (result.type === "person") {
            icon = "user";
          }
          if (result.type === "group") {
            icon = "group";
          }

          if (icon) {
            if (forTemplate) {
              result.icon = icon;
            } else {
              result.label =
                '<i class="icon icon-on-left icon-' +
                icon +
                '"></i>' +
                result.label;
            }
          }

          if (!forTemplate) {
            result = {
              name: result.label,
              value: result.value,
            };
          }
          return result;
        });

        return results;
      },

      /**
       * Render the view
       *
       * @return {AccountSelectView}  Returns the view
       * @since 2.15.0
       */
      render: function () {
        var view = this;

        // Use the account lookup service to match the pre-selected values to
        // the account holder's name to use as a label.

        // If we haven't started looking up user/organization names yet...
        if (typeof view.labelsToFetch === "undefined") {
          // Keep a count of the number of accounts we need to lookup
          view.labelsToFetch = this.selected ? this.selected.length : 0;

          if (view.labelsToFetch > 0) {
            view.options = [];

            view.selected.forEach(function (accountId) {
              MetacatUI.appLookupModel.getAccountsAutocomplete(
                { term: accountId },
                function (results) {
                  // The value should match only one account (since the value is an
                  // account ID). If we found the match, format it for the
                  // SearchableSelectView, and the icon and tooltip will automatically be
                  // added to the pre-selected labels.
                  if (results && results.length === 1) {
                    results = view.formatResults(results, true);
                    view.options.push(results[0]);
                  }
                  // Whether or not we found a match, count this lookup as complete
                  --view.labelsToFetch;

                  // Once we've looked up all of the accounts, call this render function
                  // again
                  if (view.labelsToFetch === 0) {
                    view.render();
                  }
                },
              );
            });

            return;
          }
        }

        // Once we've fetched the labels for any pre-selected account IDs,
        // render as usual
        SearchableSelect.prototype.render.call(view);
      },

      /**
       * addTooltip - Add a tooltip to a given element using the description in the
       * options object that's set on the view.
       *
       * @param  {HTMLElement} element The HTML element a tooltip should be added
       * @param  {string} position how to position the tooltip - top | bottom | left |
       * right
       * @return {jQuery} The element with a tooltip wrapped by jQuery
       * @since 2.15.0
       */
      addTooltip: function (element, position = "bottom") {
        try {
          if (!element) {
            return;
          }

          // The account ID is saved in a <span> element in the label with the
          // description class when the label is added from the list of search results
          var descEl = $(element).find(".description");
          var id = descEl.text();
          descEl.remove();

          // Otherwise, the ID is always saved as a data attribute
          if (!id) {
            id = $(element).attr("data-value");
          }

          // Show the account ID as a tooltip rather than in the label. Otherwise
          // the input gets too crowded.
          $(element)
            .tooltip({
              title: id ? "Account ID: " + id : "",
              placement: position,
              container: "body",
              delay: {
                show: 900,
                hide: 50,
              },
            })
            .on("show.bs.popover", function () {
              var $el = $(this);
              // Allow time for the popup to be added to the DOM
              setTimeout(function () {
                // Then add a special class to identify
                // these popups if they need to be removed.
                $el.data("tooltip").$tip.addClass("search-select-tooltip");
              }, 10);
            });

          return $(element);
        } catch (e) {
          console.log(
            "Failed to add tooltips in a searchable select view, error message: " +
              e,
          );
        }
      },

      // TODO: We may want to add a custom is valid option to warn the user when
      // a value entered cannot be found in the accounts lookup service.

      // /**
      //  * isValidOption - Checks if a value is one of the values given in view.options
      //  *
      //  * @param  {string} value The value to check
      //  * @return {boolean}      returns true if the value is one of the values given in
      //  * view.options
      //  */
      // isValidOption: function(value){
      // },
    },
  );

  return AccountSelectView;
});