Source: src/js/views/UserView.js

define([
  "jquery",
  "underscore",
  "backbone",
  "clipboard",
  "collections/UserGroup",
  "models/UserModel",
  "models/Stats",
  "views/SignInView",
  "views/StatsView",
  "views/DataCatalogView",
  "views/UserGroupView",
  "text!templates/userProfile.html",
  "text!templates/alert.html",
  "text!templates/loading.html",
  "text!templates/userProfileMenu.html",
  "text!templates/userSettings.html",
  "text!templates/noResults.html",
], function (
  $,
  _,
  Backbone,
  Clipboard,
  UserGroup,
  UserModel,
  Stats,
  SignInView,
  StatsView,
  DataCatalogView,
  UserGroupView,
  userProfileTemplate,
  AlertTemplate,
  LoadingTemplate,
  ProfileMenuTemplate,
  SettingsTemplate,
  NoResultsTemplate,
) {
  "use strict";

  /**
   * @class UserView
   * @classdesc A major view that displays a public profile for the user and a settings page for the logged-in user
   * to manage their account info, groups, identities, and API tokens.
   * @classcategory Views
   * @screenshot views/UserView.png
   * @extends Backbone.View
   */
  var UserView = Backbone.View.extend(
    /** @lends UserView.prototype */ {
      el: "#Content",

      //Templates
      profileTemplate: _.template(userProfileTemplate),
      alertTemplate: _.template(AlertTemplate),
      loadingTemplate: _.template(LoadingTemplate),
      settingsTemplate: _.template(SettingsTemplate),
      menuTemplate: _.template(ProfileMenuTemplate),
      noResultsTemplate: _.template(NoResultsTemplate),

      /**
       * A jQuery selector for the element that the PortalListView should be inserted into
       * @type {string}
       */
      portalListContainer: ".my-portals-container",

      events: {
        "click .section-link": "switchToSection",
        "click .subsection-link": "switchToSubSection",
        "click .token-generator": "getToken",
        "click #mod-save-btn": "saveUser",
        "click #map-request-btn": "sendMapRequest",
        "click .remove-identity-btn": "removeMap",
        "click .confirm-request-btn": "confirmMapRequest",
        "click .reject-request-btn": "rejectMapRequest",
        "click [highlight-subsection]": "highlightSubSection",
        "keypress #add-group-name": "preventSubmit",
        "click .token-tab": "switchTabs",
      },

      initialize: function () {
        this.subviews = new Array();
      },

      //------------------------------------------ Rendering the main parts of the view ------------------------------------------------//
      render: function (options) {
        //Don't render anything if the user profiles are turned off
        if (MetacatUI.appModel.get("enableUserProfiles") === false) {
          return;
        }

        this.stopListening();
        if (this.model) this.model.stopListening();

        //Create a Stats model
        this.statsModel = new Stats();

        this.activeSection =
          options && options.section ? options.section : "profile";
        this.activeSubSection =
          options && options.subsection ? options.subsection : "";
        this.username =
          options && options.username ? options.username : undefined;

        //Add the container element for our profile sections
        this.sectionHolder = $(document.createElement("section")).addClass(
          "user-view-section",
        );
        this.$el.html(this.sectionHolder);

        //Show the loading sign first
        //$(this.sectionHolder).html(this.loadingTemplate());
        this.$el.show();

        // set the header type
        MetacatUI.appModel.set("headerType", "default");

        //Render the user profile only after the app user's info has been checked
        //This prevents the app from rendering the profile before the login process has completed - which would
        //cause this profile to render twice (first before the user is logged in then again after they log in)
        if (MetacatUI.appUserModel.get("checked")) this.renderUser();
        else MetacatUI.appUserModel.on("change:checked", this.renderUser, this);

        return this;
      },

      /**
       * Update the window location path to route to /portals path
       * @param {string} username - Short identifier for the member node
       */
      forwardToPortals: function (username) {
        var pathName = decodeURIComponent(window.location.pathname)
          .substring(MetacatUI.root.length)
          // remove trailing forward slash if one exists in path
          .replace(/\/$/, "");

        // Routes the /profile/{node-id} to /portals/{node-id}
        var pathRE = new RegExp("\\/profile(\\/[^\\/]*)?$", "i");
        var newPathName =
          pathName.replace(pathRE, "") +
          "/" +
          MetacatUI.appModel.get("portalTermPlural") +
          "/" +
          username;

        // Update the window location
        MetacatUI.uiRouter.navigate(newPathName, {
          trigger: true,
          replace: true,
        });
        return;
      },

      renderUser: function () {
        this.model = MetacatUI.appUserModel;

        var username =
            MetacatUI.appModel.get("profileUsername") || view.username,
          currentUser = MetacatUI.appUserModel.get("username") || "";

        if (username.toUpperCase() == currentUser.toUpperCase()) {
          //Case-insensitive matching of usernames
          this.model = MetacatUI.appUserModel;
          this.model.set("type", "user");

          //If the user is logged in, display the settings options
          if (this.model.get("loggedIn")) {
            this.insertMenu();
            this.renderProfile();
            this.renderSettings();
            this.resetSections();
          }
        }

        //If this isn't the currently-logged in user, then let's find out more info about this account
        else {
          //Create a UserModel with the username given
          this.model = new UserModel({
            username: username,
          });

          //Is this a member node?
          if (MetacatUI.nodeModel.get("checked") && this.model.isNode()) {
            this.model.saveAsNode();
            this.model.set(
              "nodeInfo",
              _.find(MetacatUI.nodeModel.get("members"), function (nodeModel) {
                return (
                  nodeModel.identifier.toLowerCase() ==
                  "urn:node:" + username.toLowerCase()
                );
              }),
            );
            this.forwardToPortals(username);
            return;
          }
          //If the node model hasn't been checked yet
          else if (!MetacatUI.nodeModel.get("checked")) {
            var user = this.model,
              view = this;
            this.listenTo(MetacatUI.nodeModel, "change:checked", function () {
              if (user.isNode()) view.render();
            });
          }

          //When we get the infomration about this account, then crender the profile
          this.model.once("change:checked", this.renderProfile, this);
          this.model.once("change:checked", this.resetSections, this);
          //Get the info
          this.model.getInfo();
        }

        //When the model is reset, refresh the page
        this.listenTo(this.model, "reset", this.render);
      },

      renderProfile: function () {
        //Insert the template first
        var profileEl = $.parseHTML(
          this.profileTemplate({
            type: this.model.get("type"),
            logo: this.model.get("logo") || "",
            description: this.model.get("description") || "",
            user: this.model.toJSON(),
          }).trim(),
        );

        //If the profile is being redrawn, then replace it
        if (this.$profile && this.$profile.length) {
          //If the profile section is currently hidden, make sure we hide our new profile rendering too
          if (!this.$profile.is(":visible")) $(profileEl).hide();

          this.$profile.replaceWith(profileEl);
        }
        //If this is a fresh rendering, then append it to the page and save it
        else this.sectionHolder.append(profileEl);

        this.$profile = $(profileEl);

        //If this user hasn't uploaded anything yet, display so
        this.listenTo(this.statsModel, "change:totalCount", function () {
          if (!this.statsModel.get("totalCount")) this.noActivity();
        });

        //Insert the user data statistics
        this.insertStats();

        //Insert the user's basic information
        this.listenTo(this.model, "change:fullName", this.insertUserInfo);
        this.insertUserInfo();

        var view = this;
        //Listen to changes in the user's search terms
        this.listenTo(this.model, "change:searchModel", this.renderProfile);

        //Insert this user's data content
        this.insertContent();

        // create the UserGroupView to generate the membership list
        // this is the first call to UserGroupView so we instantiate it here
        var groupView = new UserGroupView({ model: this.model });
        this.subviews.push(groupView);
        this.renderMembershipList();
      },

      renderMembershipList: function () {
        //List the groups this user is in by creating usergroupview subview
        //List the groups this user is in by creating usergroupview subview
        var groupView = _.where(this.subviews, { type: "UserGroupView" }).at(0);

        if (this.model.get("type") == "group") {
          //Create the User Group collection
          var options = {
            name: this.model.get("fullName"),
            groupId: this.model.get("username"),
            rawData: this.model.get("rawData") || null,
          };
          var userGroup = new UserGroup([], options);
          //Create the group list and add it to the page
          var viewOptions = { collapsable: false, showGroupName: false };
          var groupList = groupView.createGroupList(userGroup, viewOptions);
          this.$("#user-membership-container").html(groupList);
        } else {
          var groups = _.sortBy(this.model.get("isMemberOf"), "name");
          if (!groups.length) {
            this.$("#user-membership-header").hide();
            return;
          }
          this.sectionHolder.append(
            groupView
              .insertMembership(groups, this.$("#user-membership-container"))
              .html(),
          );
        }
      },

      renderGroupsSection: function () {
        var groupView = _.where(this.subviews, { type: "UserGroupView" }).at(0);
        var container = this.$("#groups-container");
        container.append(groupView.render().el);
      },

      renderSettings: function () {
        //Don't render anything if the user profile settings are turned off
        if (MetacatUI.appModel.get("enableUserProfileSettings") === false) {
          return;
        }

        //Insert the template first
        this.sectionHolder.append(this.settingsTemplate(this.model.toJSON()));
        this.$settings = this.$("[data-section='settings']");

        //Draw the group list
        this.renderGroupsSection();

        //Listen for the identity list
        this.listenTo(this.model, "change:identities", this.insertIdentityList);
        this.insertIdentityList();

        //Listen for the pending list
        this.listenTo(this.model, "change:pending", this.insertPendingList);
        this.model.getPendingIdentities();

        //Render the portals subsection
        this.renderMyPortals();

        //Listen for updates to person details
        this.listenTo(
          this.model,
          "change:lastName change:firstName change:email change:registered",
          this.updateModForm,
        );
        this.updateModForm();

        // init autocomplete fields
        this.setUpAutocomplete();

        //Get the token right away
        this.getToken();
      },

      /*
       * Displays a menu for the user to switch between different views of the user profile
       */
      insertMenu: function () {
        //If the user is not logged in, then remove the menu
        if (!MetacatUI.appUserModel.get("loggedIn")) {
          this.$(".nav").remove();
          return;
        }

        //Otherwise, insert the menu
        var menu = this.menuTemplate({
          username: this.model.get("username"),
        });

        this.$el.prepend(menu);
      },

      //------------------------------------------ Navigating sections of view ------------------------------------------------//
      switchToSection: function (e, sectionName) {
        if (e) e.preventDefault();

        //Hide all the sections first
        $(this.sectionHolder).children().slideUp().removeClass(".active");

        //Get the section name
        if (!sectionName) var sectionName = $(e.target).attr("data-section");

        //Display the specified section
        var activeSection = this.$(
          ".section[data-section='" + sectionName + "']",
        );
        if (!activeSection.length)
          activeSection = this.$(".section[data-section='profile']");
        $(activeSection).addClass("active").slideDown();

        //Change the navigation tabs
        this.$(".nav-tab").removeClass("active");
        $(".nav-tab[data-section='" + sectionName + "']").addClass("active");

        //Find all the subsections, if there are any
        if ($(activeSection).find(".subsection").length > 0) {
          //Find any item classified as "active"
          var activeItem = $(activeSection).find(".active");
          if (activeItem.length > 0) {
            //Find the data section this active item is referring to
            if ($(activeItem).children("[data-subsection]").length > 0) {
              //Get the section name
              var subsectionName = $(activeItem)
                .find("[data-subsection]")
                .first()
                .attr("data-subsection");
              //If we found a section name, find the subsection element and display it
              if (subsectionName) this.switchToSubSection(null, subsectionName);
            } else
              this.switchToSubSection(
                null,
                $(activeSection)
                  .children("[data-section]")
                  .first()
                  .attr("data-section"),
              );
          }
        }
      },

      switchToSubSection: function (e, subsectionName) {
        if (e) {
          e.preventDefault();
          var subsectionName = $(e.target).attr("data-section");
          if (!subsectionName) {
            subsectionName = $(e.target)
              .parents("[data-section]")
              .first()
              .attr("data-section");
          }
        }

        //Mark its links as active
        $(".section.active").find(".subsection-link").removeClass("active");
        $(".section.active")
          .find(".subsection-link[data-section='" + subsectionName + "']")
          .addClass("active");

        //Hide all the other sections
        $(".section.active").find(".subsection").hide();
        $(".section.active")
          .find(".subsection[data-section='" + subsectionName + "']")
          .show();
      },

      resetSections: function () {
        //Hide all the sections first, then display the section specified in the URL (or the default)
        this.$(".subsection, .section").hide();
        this.switchToSection(null, this.activeSection);

        //Show the subsection
        if (this.activeSubSection)
          this.switchToSubSection(null, this.activeSubSection);
      },

      highlightSubSection: function (e, subsectionName) {
        if (e) e.preventDefault();

        if (!subsectionName && e) {
          //Get the subsection name
          var subsectionName = $(e.target).attr("highlight-subsection");
          if (!subsectionName) return;
        } else if (!subsectionName && !e) return false;

        //Find the subsection
        var subsection = this.$(
          ".subsection[data-section='" + subsectionName + "']",
        );
        if (!subsection.length)
          subsection = this.$("[data-subsection='add-account']");
        if (!subsection.length) return;

        //Visually highlight the subsection
        subsection.addClass("highlight");
        MetacatUI.appView.scrollTo(subsection);
        //Wait about a second and then remove the highlight style
        window.setTimeout(function () {
          subsection.removeClass("highlight");
        }, 1500);
      },

      //------------------------------------------ Inserting public profile UI elements ------------------------------------------------//
      insertStats: function () {
        if (this.model.noActivity && this.statsView) {
          this.statsView.$el.addClass("no-activity");
          this.$("#total-download-wrapper, section.downloads").hide();
          return;
        }

        var username = this.model.get("username"),
          view = this;

        //Insert a couple stats into the profile
        this.listenToOnce(
          this.statsModel,
          "change:firstUpload",
          this.insertFirstUpload,
        );

        this.listenToOnce(this.statsModel, "change:totalCount", function () {
          view
            .$("#total-upload-container")
            .text(
              MetacatUI.appView.commaSeparateNumber(
                view.statsModel.get("totalCount"),
              ),
            );
        });

        //Create a base query for the statistics
        var statsSearchModel = this.model.get("searchModel").clone();
        statsSearchModel
          .set("exclude", [], { silent: true })
          .set("formatType", [], { silent: true });
        this.statsModel.set("query", statsSearchModel.getQuery());
        this.statsModel.set("isSystemMetadataQuery", true);
        this.statsModel.set("searchModel", statsSearchModel);

        //Create the description for this profile
        var description;

        switch (this.model.get("type")) {
          case "node":
            description =
              "A summary of all datasets from the " +
              this.model.get("fullName") +
              " repository";
            break;
          case "group":
            description =
              "A summary of all datasets from the " +
              this.model.get("fullName") +
              " group";
            break;
          case "user":
            description =
              "A summary of all datasets from " + this.model.get("fullName");
            break;
          default:
            description = "";
            break;
        }

        //Render the Stats View for this person
        this.statsView = new StatsView({
          title: "Statistics and Figures",
          description: description,
          userType: "user",
          el: this.$("#user-stats"),
          model: this.statsModel,
        });
        this.subviews.push(this.statsView);
        this.statsView.render();
        if (this.model.noActivity) this.statsView.$el.addClass("no-activity");
      },

      /*
       * Insert the name of the user
       */
      insertUserInfo: function () {
        //Don't try to insert anything if we haven't gotten all the user info yet
        if (!this.model.get("fullName")) return;

        //Insert the name into this page
        var usernameLink = $(document.createElement("a"))
          .attr(
            "href",
            MetacatUI.root + "/profile/" + this.model.get("username"),
          )
          .text(this.model.get("fullName"));
        this.$(".insert-fullname").append(usernameLink);

        //Insert the username
        if (this.model.get("type") != "node") {
          if (!this.model.get("usernameReadable"))
            this.model.createReadableUsername();
          this.$(".insert-username").text(this.model.get("usernameReadable"));
        } else {
          $("#username-wrapper").hide();
        }

        //Show or hide ORCID logo
        if (this.model.isOrcid()) this.$(".show-orcid").show();
        else this.$(".show-orcid").hide();

        //Show the email
        if (this.model.get("email")) {
          this.$(".email-wrapper").show();
          var parts = this.model.get("email").split("@");
          this.$(".email-container").attr("data-user", parts[0]);
          this.$(".email-container").attr("data-domain", parts[1]);
        } else this.$(".email-wrapper").hide();
      },

      // Creates an HTML element to display in front of the user identity/subject.
      // Only used for the ORCID logo right now
      createIdPrefix: function () {
        if (this.model.isOrcid())
          return $(document.createElement("img"))
            .attr("src", MetacatUI.root + "/img/orcid_64x64.png")
            .addClass("orcid-logo");
        else return "";
      },

      /*
       * Insert the first year of contribution for this user
       */
      insertFirstUpload: function () {
        if (this.model.noActivity || !this.statsModel.get("firstUpload")) {
          this.$(
            "#first-upload-container, #first-upload-year-container",
          ).hide();
          return;
        }

        // Get the first upload or first operational date
        if (this.model.get("type") == "node") {
          //Get the member node object
          var node = _.findWhere(MetacatUI.nodeModel.get("members"), {
            identifier: "urn:node:" + this.model.get("username"),
          });

          //If there is no memberSince date, then hide this statistic and exit
          if (!node.memberSince) {
            this.$(
              "#first-upload-container, #first-upload-year-container",
            ).hide();
            return;
          } else {
            var firstUpload = node.memberSince
              ? new Date(
                  node.memberSince.substring(0, node.memberSince.indexOf("T")),
                )
              : new Date();
          }
        } else {
          var firstUpload = new Date(this.statsModel.get("firstUpload"));
        }

        // Construct the first upload date sentence
        var monthNames = [
            "January",
            "February",
            "March",
            "April",
            "May",
            "June",
            "July",
            "August",
            "September",
            "October",
            "November",
            "December",
          ],
          m = monthNames[firstUpload.getUTCMonth()],
          y = firstUpload.getUTCFullYear(),
          d = firstUpload.getUTCDate();

        //For Member Nodes, start all dates at July 2012, the beginning of DataONE
        if (this.model.get("type") == "node") {
          this.$("#first-upload-container").text(
            "DataONE Member Node since " + y,
          );
        } else
          this.$("#first-upload-container").text(
            "Contributor since " + m + " " + d + ", " + y,
          );

        //Construct the time-elapsed sentence
        var now = new Date(),
          msElapsed = now - firstUpload,
          years = msElapsed / 31556952000,
          months = msElapsed / 2629746000,
          weeks = msElapsed / 604800000,
          days = msElapsed / 86400000,
          time = "";

        //If one week or less, express in days
        if (weeks <= 1) {
          time = (Math.round(days) || 1) + " day";
          if (days > 1.5) time += "s";
        }
        //If one month or less, express in weeks
        else if (months < 1) {
          time = (Math.round(weeks) || 1) + " week";
          if (weeks > 1.5) time += "s";
        }
        //If less than 12 months, express in months
        else if (months <= 11.5) {
          time = (Math.round(months) || 1) + " month";
          if (months > 1.5) time += "s";
        }
        //If one year or more, express in years and months
        else {
          var yearsOnly = Math.floor(years) || 1,
            monthsOnly = Math.round((years % 1) * 12);

          if (monthsOnly == 12) {
            yearsOnly += 1;
            monthsOnly = 0;
          }

          time = yearsOnly + " year";
          if (yearsOnly > 1) time += "s";

          if (monthsOnly) time += ", " + monthsOnly + " month";
          if (monthsOnly > 1) time += "s";
        }

        this.$("#first-upload-year-container").text(time);
      },

      /*
       * Insert a list of this user's content
       */
      insertContent: function () {
        if (this.model.noActivity) {
          this.$("#data-list").html(
            this.noResultsTemplate({
              fullName: this.model.get("fullName"),
              username:
                this.model == MetacatUI.appUserModel &&
                MetacatUI.appUserModel.get("loggedIn")
                  ? this.model.get("username")
                  : null,
            }),
          );
          return;
        }

        var view = new DataCatalogView({
          el: this.$("#data-list")[0],
          searchModel: this.model.get("searchModel"),
          searchResults: this.model.get("searchResults"),
          mode: "list",
          isSubView: true,
          filters: false,
        });
        this.subviews.push(view);
        view.render();
        view.$el.addClass("list-only");
        view.$(".auto-height").removeClass("auto-height").css("height", "auto");
        $("#metacatui-app").removeClass("DataCatalog mapMode");
      },

      /*
       * When this user has not uploaded any content, render the profile differently
       */
      noActivity: function () {
        this.model.noActivity = true;
        this.insertContent();
        this.insertFirstUpload();
        this.insertStats();
      },

      //------------------------------------------------ Identities/Accounts -------------------------------------------------------//
      /*
       * Sends a new identity map request and displays notifications about the result
       */
      sendMapRequest: function (e) {
        e.preventDefault();

        //Get the identity entered into the input
        var equivalentIdentity = this.$("#map-request-field").val();
        if (!equivalentIdentity || equivalentIdentity.length < 1) {
          return;
        }
        //Clear the text input
        this.$("#map-request-field").val("");

        //Show notifications after the identity map request is a success or failure
        var viewRef = this,
          success = function () {
            var message =
              "An account map request has been sent to <a href=" +
              MetacatUI.root +
              "'/profile/" +
              equivalentIdentity +
              "'>" +
              equivalentIdentity +
              "</a>" +
              "<h4>Next step:</h4><p>Sign In with this other account and approve this request.</p>";
            viewRef.showAlert(message, null, "#request-alert-container");
          },
          error = function (xhr) {
            var errorMessage = xhr.responseText;

            if (xhr.responseText.indexOf("Request already issued") > -1) {
              viewRef.showAlert(
                "<p>You have already sent a request to map this account to " +
                  equivalentIdentity +
                  ".</p> <h4>Next Step:</h4><p> Sign In with your " +
                  equivalentIdentity +
                  " account and approve the request.</p>",
                "alert-info",
                "#request-alert-container",
              );
            } else {
              //Make a more understandable error message when the account isn't found
              if (
                xhr.responseText.indexOf(
                  "LDAP: error code 32 - No Such Object",
                ) > -1
              ) {
                xhr.responseText =
                  "The username " +
                  equivalentIdentity +
                  " does not exist in our system.";
              }

              viewRef.showAlert(
                xhr.responseText,
                "alert-error",
                "#request-alert-container",
              );
            }
          };

        //Send it
        this.model.addMap(equivalentIdentity, success, error);
      },

      /*
       * Removes a confirmed identity map request and displays notifications about the result
       */
      removeMap: function (e) {
        e.preventDefault();

        var equivalentIdentity = $(e.target).parents("a").attr("data-identity");
        if (!equivalentIdentity) return;

        var viewRef = this,
          success = function () {
            viewRef.showAlert(
              "Success! Your account is no longer associated with the user " +
                equivalentIdentity,
              "alert-success",
              "#identity-alert-container",
            );
          },
          error = function (xhr, textStatus, error) {
            viewRef.showAlert(
              "Something went wrong: " + xhr.responseText,
              "alert-error",
              "#identity-alert-container",
            );
          };

        this.model.removeMap(equivalentIdentity, success, error);
      },

      /*
       * Confirms an identity map request that was initiated from another user, and displays notifications about the result
       */
      confirmMapRequest: function (e) {
        var model = this.model;

        e.preventDefault();
        var otherUsername = $(e.target).parents("a").attr("data-identity"),
          mapRequestEl = $(e.target).parents(".pending.identity");

        var viewRef = this;

        var success = function (data, textStatus, xhr) {
          viewRef.showAlert(
            "Success! Your account is now linked with the username " +
              otherUsername,
            "alert-success",
            "#pending-alert-container",
          );

          mapRequestEl.remove();
        };
        var error = function (xhr, textStatus, error) {
          viewRef.showAlert(
            xhr.responseText,
            "alert-error",
            "#pending-alert-container",
          );
        };

        //Confirm this map request
        this.model.confirmMapRequest(otherUsername, success, error);
      },

      /*
       * Rejects an identity map request that was initiated by another user, and displays notifications about the result
       */
      rejectMapRequest: function (e) {
        e.preventDefault();

        var equivalentIdentity = $(e.target).parents("a").attr("data-identity"),
          mapRequestEl = $(e.target).parents(".pending.identity");

        if (!equivalentIdentity) return;

        var viewRef = this,
          success = function (data) {
            viewRef.showAlert(
              "Removed mapping request for " + equivalentIdentity,
              "alert-success",
              "#pending-alert-container",
            );
            $(mapRequestEl).remove();
          },
          error = function (xhr, textStatus, error) {
            viewRef.showAlert(
              xhr.responseText,
              "alert-error",
              "#pending-alert-container",
            );
          };

        this.model.denyMapRequest(equivalentIdentity, success, error);
      },

      insertIdentityList: function () {
        var identities = this.model.get("identities");

        //Remove the equivalentIdentities list if it was drawn already so we don't do it twice
        this.$("#identity-list-container").empty();

        if (!identities) return;

        //Create the list element
        if (identities.length < 1) {
          var identityList = $(document.createElement("p")).text(
            "You haven't linked to another account yet. Send a request below.",
          );
        } else
          var identityList = $(document.createElement("ul"))
            .addClass("list-identity")
            .attr("id", "identity-list");

        var view = this;
        //Create a list item for each identity
        _.each(identities, function (identity, i) {
          var listItem = view.createUserListItem(identity, { confirmed: true });

          //When/if the info from the equivalent identities is retrieved, update the item
          view.listenToOnce(identity, "change:fullName", function (identity) {
            var newListItem = view.createUserListItem(identity, {
              confirmed: true,
            });
            listItem.replaceWith(newListItem);
          });

          $(identityList).append(listItem);
        });

        //Add to the page
        //$(identityList).find(".collapsed").hide();
        this.$("#identity-list-container").append(identityList);
      },

      insertPendingList: function () {
        var pending = this.model.get("pending");

        //Remove the equivalentIdentities list if it was drawn already so we don't do it twice
        this.$("#pending-list-container").empty();

        //Create the list element
        if (pending.length < 1) {
          this.$("[data-subsection='pending-accounts']").hide();
          return;
        } else {
          this.$("[data-subsection='pending-accounts']").show();
          this.$("#pending-list-container").prepend(
            $(document.createElement("p")).text(
              "You have " +
                pending.length +
                " new request to map accounts. If these requests are from you, accept them below. If you do not recognize a username, reject the request.",
            ),
          );
          var pendingList = $(document.createElement("ul"))
            .addClass("list-identity")
            .attr("id", "pending-list");
          var pendingCount = $(document.createElement("span"))
            .addClass("badge")
            .attr("id", "pending-count")
            .text(pending.length);
          this.$("#pending-list-heading").append(pendingCount);
        }

        //Create a list item for each pending id
        var view = this;
        _.each(pending, function (pendingUser, i) {
          var listItem = view.createUserListItem(pendingUser, {
            pending: true,
          });
          $(pendingList).append(listItem);

          if (pendingUser.isOrcid()) {
            view.listenToOnce(
              pendingUser,
              "change:fullName",
              function (pendingUser) {
                var newListItem = view.createUserListItem(pendingUser, {
                  pending: true,
                });
                listItem.replaceWith(newListItem);
              },
            );
          }
        });

        //Add to the page
        this.$("#pending-list-container").append(pendingList);
      },

      createUserListItem: function (user, options) {
        var pending = false,
          confirmed = false;

        if (options && options.pending) pending = true;
        if (options && options.confirmed) confirmed = true;

        var username = user.get("username"),
          fullName = user.get("fullName") || username;

        var listItem = $(document.createElement("li")).addClass(
            "list-group-item identity",
          ),
          link = $(document.createElement("a"))
            .attr("href", MetacatUI.root + "/profile/" + username)
            .attr("data-identity", username)
            .text(fullName),
          details = $(document.createElement("span"))
            .addClass("subtle details")
            .text(username);

        listItem.append(link, details);

        if (pending) {
          var acceptIcon = $(document.createElement("i"))
              .addClass("icon icon-ok icon-large icon-positive tooltip-this")
              .attr("data-title", "Accept Request")
              .attr("data-trigger", "hover")
              .attr("data-placement", "top"),
            rejectIcon = $(document.createElement("i"))
              .addClass(
                "icon icon-remove icon-large icon-negative tooltip-this",
              )
              .attr("data-title", "Reject Request")
              .attr("data-trigger", "hover")
              .attr("data-placement", "top"),
            confirm = $(document.createElement("a"))
              .attr("href", "#")
              .addClass("confirm-request-btn")
              .attr("data-identity", username)
              .append(acceptIcon),
            reject = $(document.createElement("a"))
              .attr("href", "#")
              .addClass("reject-request-btn")
              .attr("data-identity", username)
              .append(rejectIcon);

          listItem.prepend(confirm, reject).addClass("pending");
        } else if (confirmed) {
          var removeIcon = $(document.createElement("i")).addClass(
              "icon icon-remove icon-large icon-negative",
            ),
            remove = $(document.createElement("a"))
              .attr("href", "#")
              .addClass("remove-identity-btn")
              .attr("data-identity", username)
              .append(removeIcon);
          $(remove).tooltip({
            trigger: "hover",
            placement: "top",
            title: "Remove equivalent account",
          });
          listItem.prepend(remove.append(removeIcon));
        }

        if (user.isOrcid()) {
          details.prepend(this.createIdPrefix(), " ORCID: ");
        } else details.prepend(" Username: ");

        return listItem;
      },

      updateModForm: function () {
        this.$("#mod-givenName").val(this.model.get("firstName"));
        this.$("#mod-familyName").val(this.model.get("lastName"));
        this.$("#mod-email").val(this.model.get("email"));

        if (!this.model.get("email")) {
          this.$("#mod-email").parent(".form-group").addClass("has-warning");
          this.$("#mod-email")
            .parent(".form-group")
            .find(".help-block")
            .text("Please provide an email address.");
        } else {
          this.$("#mod-email").parent(".form-group").removeClass("has-warning");
          this.$("#mod-email")
            .parent(".form-group")
            .find(".help-block")
            .text("");
        }

        if (this.model.get("registered")) {
          this.$("#registered-user-container").show();
        } else {
          this.$("#registered-user-container").hide();
        }
      },

      /*
       * Gets the user account settings, updates the UserModel and saves this new info to the server
       */
      saveUser: function (e) {
        e.preventDefault();

        var view = this,
          container =
            this.$('[data-subsection="edit-account"] .content') ||
            $(e.target).parent();

        var success = function (data) {
          $(container).find(".loading").detach();
          $(container).children().show();
          view.showAlert(
            "Success! Your profile has been updated.",
            "alert-success",
            container,
          );
        };
        var error = function (data) {
          $(container).find(".loading").detach();
          $(container).children().show();
          var msg =
            data && data.responseText
              ? data.responseText
              : "Sorry, updating your profile failed. Please try again.";
          if (!data.responseText) view.showAlert(msg, "alert-error", container);
        };

        //Get info entered into form
        var givenName = this.$("#mod-givenName").val();
        var familyName = this.$("#mod-familyName").val();
        var email = this.$("#mod-email").val();

        //Update the model
        this.model.set("firstName", givenName);
        this.model.set("lastName", familyName);
        this.model.set("email", email);

        //Loading icon
        $(container).children().hide();
        $(container).prepend(this.loadingTemplate());

        //Send the update
        this.model.update(success, error);
      },

      //---------------------------------- Token -----------------------------------------//
      getToken: function () {
        var model = this.model;

        //Show loading sign
        this.$("#token-generator-container").html(this.loadingTemplate());

        //When the token is retrieved, then show it
        this.listenToOnce(this.model, "change:token", this.showToken);

        //Get the token from the CN
        this.model.getToken(function (data, textStatus, xhr) {
          model.getTokenExpiration();
          model.set("token", data);
          model.trigger("change:token");
        });
      },

      showToken: function () {
        var token = this.model.get("token");

        if (!token || !this.model.get("loggedIn")) return;

        var expires = this.model.get("expires"),
          rTokenName =
            MetacatUI.appModel.get("d1CNBaseUrl").indexOf("cn.dataone.org") > -1
              ? "dataone_token"
              : "dataone_test_token",
          rToken = "options(" + rTokenName + ' = "' + token + '")',
          matlabToken =
            "import org.dataone.client.run.RunManager; mgr = RunManager.getInstance(); mgr.configuration.authentication_token = '" +
            token +
            "';",
          tokenInput = $(document.createElement("textarea"))
            .attr("type", "text")
            .attr("rows", "5")
            .addClass("token copy")
            .text(token),
          copyButton = $(document.createElement("a"))
            .addClass("btn btn-primary copy")
            .text("Copy")
            .attr("data-clipboard-text", token),
          copyRButton = $(document.createElement("a"))
            .addClass("btn btn-primary copy")
            .text("Copy")
            .attr("data-clipboard-text", rToken),
          copyMatlabButton = $(document.createElement("a"))
            .addClass("btn btn-primary copy")
            .text("Copy")
            .attr("data-clipboard-text", matlabToken),
          successIcon = $(document.createElement("i")).addClass("icon icon-ok"),
          copySuccess = $(document.createElement("div"))
            .addClass("notification success copy-success hidden")
            .append(successIcon, " Copied!"),
          expirationMsg = expires
            ? "<strong>Note:</strong> Your authentication token expires on " +
              expires.toLocaleDateString() +
              " at " +
              expires.toLocaleTimeString()
            : "",
          usernameMsg = "<div class='footnote'>Your user identity: ",
          usernamePrefix = this.createIdPrefix(),
          tabs = $(document.createElement("ul"))
            .addClass("nav nav-tabs")
            .append(
              $(document.createElement("li"))
                .addClass("active")
                .append(
                  $(document.createElement("a"))
                    .attr("href", "#token-code-panel")
                    .addClass("token-tab")
                    .text("Token"),
                ),
            )
            .append(
              $(document.createElement("li")).append(
                $(document.createElement("a"))
                  .attr("href", "#r-token-code-panel")
                  .addClass("token-tab")
                  .text("Token for DataONE R"),
              ),
            )
            .append(
              $(document.createElement("li")).append(
                $(document.createElement("a"))
                  .attr("href", "#matlab-token-code-panel")
                  .addClass("token-tab")
                  .text("Token for Matlab DataONE Toolbox"),
              ),
            ),
          tokenRInput = $(document.createElement("textarea"))
            .attr("type", "text")
            .attr("rows", "5")
            .addClass("token copy")
            .text(rToken),
          tokenRText = $(document.createElement("p")).text(
            "Copy this code snippet to use your token with the DataONE R package.",
          ),
          tokenMatlabInput = $(document.createElement("textarea"))
            .attr("type", "text")
            .attr("rows", "5")
            .addClass("token copy")
            .text(matlabToken),
          tokenMatlabText = $(document.createElement("p")).text(
            "Copy this code snippet to use your token with the Matlab DataONE toolbox.",
          ),
          tokenInputContain = $(document.createElement("div"))
            .attr("id", "token-code-panel")
            .addClass("tab-panel active")
            .append(tokenInput, copyButton, copySuccess),
          rTokenInputContain = $(document.createElement("div"))
            .attr("id", "r-token-code-panel")
            .addClass("tab-panel")
            .append(tokenRText, tokenRInput, copyRButton, copySuccess.clone())
            .addClass("hidden"),
          matlabTokenInputContain = $(document.createElement("div"))
            .attr("id", "matlab-token-code-panel")
            .addClass("tab-panel")
            .append(
              tokenMatlabText,
              tokenMatlabInput,
              copyMatlabButton,
              copySuccess.clone(),
            )
            .addClass("hidden");

        if (typeof usernamePrefix == "object")
          usernameMsg += usernamePrefix[0].outerHTML;
        else if (typeof usernamePrefix == "string")
          usernameMsg += usernamePrefix;

        usernameMsg += this.model.get("username") + "</div>";

        var successMessage = $.parseHTML(
          this.alertTemplate({
            msg:
              "Copy your authentication token: <br/> " +
              expirationMsg +
              usernameMsg,
            classes: "alert-success",
            containerClasses: "well",
          }),
        );
        $(successMessage).append(
          tabs,
          tokenInputContain,
          rTokenInputContain,
          matlabTokenInputContain,
        );
        this.$("#token-generator-container").html(successMessage);

        $(".token-tab").tab();

        //Create clickable "Copy" buttons to copy text (e.g. token) to the user's clipboard
        var clipboard = new Clipboard(".copy");

        clipboard.on("success", function (e) {
          $(".copy-success").show().delay(3000).fadeOut();
        });

        clipboard.on("error", function (e) {
          var textarea = $(e.trigger).parent().children("textarea.token");
          textarea.trigger("focus");
          textarea.tooltip({
            title: "Press Ctrl+c to copy",
            placement: "bottom",
          });
          textarea.tooltip("show");
        });
      },

      setUpAutocomplete: function () {
        var input = this.$(".account-autocomplete");
        if (!input || !input.length) return;

        // look up registered identities
        $(input).hoverAutocomplete({
          source: function (request, response) {
            var term = $.ui.autocomplete.escapeRegex(request.term);

            var list = [];

            //Ids/Usernames that we want to ignore in the autocompelte
            var ignoreEquivIds =
                $(this.element).attr("id") == "map-request-field",
              ignoreIds = ignoreEquivIds
                ? MetacatUI.appUserModel.get("identitiesUsernames")
                : [];
            ignoreIds.push(
              MetacatUI.appUserModel.get("username").toLowerCase(),
            );

            var url =
              MetacatUI.appModel.get("accountsUrl") +
              "?query=" +
              encodeURIComponent(term);
            var requestSettings = {
              url: url,
              success: function (data, textStatus, xhr) {
                _.each($(data).find("person"), function (person, i) {
                  var item = {};
                  item.value = $(person).find("subject").text();

                  //Don't display yourself in the autocomplete dropdown (prevents users from adding themselves as an equivalent identity or group member)
                  //Also don't display your equivalent identities in the autocomplete
                  if (_.contains(ignoreIds, item.value.toLowerCase())) return;

                  item.label =
                    $(person).find("fullName").text() ||
                    $(person).find("givenName").text() +
                      " " +
                      $(person).find("familyName").text();
                  list.push(item);
                });

                response(list);
              },
            };
            $.ajax(
              _.extend(
                requestSettings,
                MetacatUI.appUserModel.createAjaxSettings(),
              ),
            );

            //Send an ORCID search when the search string gets long enough
            if (request.term.length > 3)
              MetacatUI.appLookupModel.orcidSearch(
                request,
                response,
                false,
                ignoreIds,
              );
          },
          select: function (e, ui) {
            e.preventDefault();

            // set the text field
            $(e.target).val(ui.item.value);
            $(e.target)
              .parents("form")
              .find("input[name='fullName']")
              .val(ui.item.label);
          },
          position: {
            my: "left top",
            at: "left bottom",
            collision: "none",
          },
        });
      },

      /**
       * Renders a list of portals that this user is an owner of.
       */
      renderMyPortals: function () {
        //If my portals has been disabled, don't render the list
        if (MetacatUI.appModel.get("showMyPortals") === false) {
          return;
        }

        var view = this;

        //If Bookkeeper services are enabled, render the Portals via a PortalUsagesView,
        // which queries Bookkeeper for portal Usages
        if (MetacatUI.appModel.get("enableBookkeeperServices")) {
          require(["views/portals/PortalUsagesView"], function (
            PortalUsagesView,
          ) {
            var portalListView = new PortalUsagesView();
            //Render the Portal list view and insert it in the page
            portalListView.render();
            view.$(view.portalListContainer).html(portalListView.el);
          });
        }
        //If Bookkeeper services are disabled, render the Portals via a PortalListView,
        // which queries Solr for portal docs
        else {
          require(["views/portals/PortalListView"], function (PortalListView) {
            //Create a PortalListView
            var portalListView = new PortalListView();
            //Render the Portal list view and insert it in the page
            portalListView.render();
            view.$(view.portalListContainer).html(portalListView.el);
          });
        }
      },

      //---------------------------------- Misc. and Utilities -----------------------------------------//

      showAlert: function (msg, classes, container) {
        if (!classes) var classes = "alert-success";
        if (!container || !$(container).length) var container = this.$el;

        //Remove any alerts that are already in this container
        if ($(container).children(".alert-container").length > 0)
          $(container).children(".alert-container").remove();

        $(container).prepend(
          this.alertTemplate({
            msg: msg,
            classes: classes,
          }),
        );
      },

      switchTabs: function (e) {
        e.preventDefault();
        $(e.target).tab("show");
        this.$(".tab-panel").hide();
        this.$(".tab-panel" + $(e.target).attr("href")).show();
        this.$("#token-generator-container .copy-button").attr(
          "data-clipboard-text",
        );
      },

      preventSubmit: function (e) {
        if (e.keyCode != 13) return;

        e.preventDefault();
      },

      onClose: function () {
        //Clear the template
        this.$el.html("");

        //Reset the active section and subsection
        this.activeSection = "profile";
        this.activeSubSection = "";

        //Reset the model
        if (this.model) {
          this.model.noActivity = null;
          this.stopListening(this.model);
        }

        //Remove saved elements
        this.$profile = null;

        //Stop listening to changes in models
        this.stopListening(this.statsModel);
        this.stopListening(MetacatUI.appUserModel);

        //Close the subviews
        _.each(this.subviews, function (view) {
          view.onClose();
        });
        this.subviews = new Array();
      },
    },
  );

  return UserView;
});