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
  });