Source: src/js/views/portals/PortalView.js

define(["jquery",
  "underscore",
  "backbone",
  "models/portals/PortalModel",
  "models/UserModel",
  "text!templates/alert.html",
  "text!templates/loading.html",
  "text!templates/portals/portal.html",
  "text!templates/portals/editPortals.html",
  "views/portals/PortalHeaderView",
  "views/portals/PortalDataView",
  "views/portals/PortalSectionView",
  "views/portals/PortalMetricsView",
  "views/portals/PortalMembersView",
  "views/portals/PortalLogosView",
  "views/portals/PortalVisualizationsView"
],
  function ($, _, Backbone, Portal, User, AlertTemplate, LoadingTemplate, PortalTemplate, EditPortalsTemplate, PortalHeaderView,
    PortalDataView, PortalSectionView, PortalMetricsView, PortalMembersView, PortalLogosView, PortalVisualizationsView) {
    "use_strict";

    /**
    * @class PortalView
    * @classdesc The PortalView is a generic view to render
     * portals, it will hold portal sections
     * @classcategory Views/Portals
     * @extends Backbone.View
     * @constructor
     */
    var PortalView = Backbone.View.extend(
            /** @lends PortalView.prototype */{

        /**
         * The Portal element
         * @type {string}
         */
        el: "#Content",

        /**
         * The type of View this is
         * @type {string}
         */
        type: "Portal",

        /**
         * The currently active section view
         * @type {PortalSectionView}
         */
        activeSection: undefined,

        /**
        * The currently active section label. e.g. Data, Metrics, Settings, etc.
        * @type {string}
        */
        activeSectionLabel: "",

        /**
         * The names of all sections in this portal editor
         * @type {Array}
         */
        sectionNames: [],

        /**
         * The seriesId of the portal document
         * @type {string}
         */
        portalId: "",

        /**
         * The unique short name of the portal
         * @type {string}
         */
        label: "",

        /**
        * Flag to add section name to URL. Enabled by default.
        * @type {boolean}
        */
        displaySectionInUrl: true,

        /**
         * The subviews contained within this view to be removed with onClose
         * @type {Array}
         */
        subviews: new Array(), // Could be a literal object {} */

        /**
        * A reference to the Portal Logos View that displays the logos of this portal.
        * @type PortalLogosView
        */
        logosView: null,

        /**
         * A Portal Model is associated with this view and gets created during render()
         * @type {Portal}
         */
        model: null,

        /**
         * A User Model is associated with this view for rendering node/user views
         * @type {User}
         */
        userModel: null,

        /* Renders the compiled template into HTML */
        template: _.template(PortalTemplate),
        //A template to display a notification message
        alertTemplate: _.template(AlertTemplate),
        //A template for displaying a loading message
        loadingTemplate: _.template(LoadingTemplate),
        // Template for the 'edit portal' button
        editPortalsTemplate: _.template(EditPortalsTemplate),

        /**
        * A jQuery selector for the element that a single section link will be inserted into
        * @type {string}
        */
        sectionLinkContainer: ".section-link-container",
        /**
        * A jQuery selector for the elements that are links to the individual sections
        * @type {string}
        */
        sectionLinks: ".portal-section-link",
        /**
        * A jQuery selector for the section elements
        * @type {string}
        */
        sectionEls: ".portal-section-view",
        /**
        * A jQuery selection for the element that will contain the Edit button.
        * @type {string}
        * @since 2.14.0
        */
        editButtonContainer: ".edit-portal-link-container",
        /**
         * The events this view will listen to and the associated function to call.
         * @type {Object}
         */
        events: {
          "click .portal-section-link": "handleSwitchSection",
          "click .section-links-container": "toggleSectionLinks"
        },

        /**
         * Is executed when a new PortalView is created
         */
        initialize: function (options) {
          // Set the current PortalView properties
          this.portalId = options.portalId ? options.portalId : undefined;
          this.model = options.model ? options.model : undefined;
          this.nodeView = options.nodeView ? options.nodeView : undefined;
          this.label = options.label ? options.label : undefined;
          this.activeSection = options.activeSection ? options.activeSection : undefined;
          this.activeSectionLabel = options.activeSectionLabel ? options.activeSectionLabel : undefined;
        },

        /**
         * Initial render of the PortalView
         *
         * @return {PortalView} Returns itself for easy function stacking in the app
         */
        render: function () {

          var view = this;

          //Make sure the subviews array is reset
          this.subviews = new Array();

          // Add the overall class immediately so the navbar is styled correctly right away
          $("body").addClass("PortalView");

          this.$el.html(this.loadingTemplate({
            msg: "Loading..."
          }));

          //Perform specific label checks
          if (!MetacatUI.nodeModel.get("checked")) {
            this.listenToOnce(MetacatUI.nodeModel, "change:checked", function () {
              // perform node checks
              if (view.isNode(view.label)) {
                view.nodeView = true;
                view.renderAsNode();
              }
              else {
                view.nodeView = false;
                view.renderAsPortal();
              }
            });

            this.listenToOnce(MetacatUI.nodeModel, "error", function () {
              this.showError(null, "Couldn't get the DataONE Node info document");
            });
          }
          else if (MetacatUI.nodeModel.get("error")) {
            this.showError(null, "Couldn't get the DataONE Node info document");
          }
          else if (this.isNode(this.label)) {
            this.nodeView = true;
            this.renderAsNode();
          }
          else if (!this.isNode(this.label)) {
            this.nodeView = false;
            this.renderAsPortal();
          }

          return this;
        },

        /**
         * Entry point for portal rendering
         */
        renderAsPortal: function () {

          // At this point we know that the given label is not a
          // repository short identifier

          // Create a new Portal model
          if (this.model === undefined || this.model === null) {
            this.model = new Portal({
              seriesId: this.portalId,
              label: this.label
            });
          }

          // When the model has been synced, render the results
          this.stopListening();
          this.listenToOnce(this.model, "sync", this.renderPortal);

          //If the portal isn't found, display a 404 message
          this.listenTo(this.model, "notFound", this.handleNotFound);

          //Listen to errors that might occur during fetch()
          this.listenToOnce(this.model, "error", this.showError);

          //Fetch the model
          this.model.fetch({ objectOnly: true });

        },

        /**
         * Entry point for a repository portal view
         * At this point we know for sure that a given label/username is a repository user
         */
        renderAsNode: function () {
          var view = this;

          //Create a UserModel with the username given
          this.userModel = new User({
            username: view.label
          });
          this.userModel.saveAsNode();
          // get the node Info
          var nodeInfo = _.find(MetacatUI.nodeModel.get("members"), function (nodeModel) {
            return nodeModel.identifier.toLowerCase() == "urn:node:" + view.label.toLowerCase();
          });
          this.nodeInfo = nodeInfo;
          this.nodeName = this.nodeInfo.name;
          this.portalId = this.nodeInfo.identifier;

          // create a portal model for repository
          this.model = new Portal({
            seriesId: this.portalId,
            label: view.label,
            name: this.nodeInfo.name,
            description: this.nodeInfo.description,
          });

          // remove the members section directly from the model
          this.model.removeSection("members");

          this.model.createNodeAttributes(this.nodeInfo);

          //Setting the repo specific statsModel
          var statsSearchModel = this.userModel.get("searchModel").clone();
          statsSearchModel.set("exclude", [], { silent: true }).set("formatType", [], { silent: true });
          MetacatUI.statsModel.set("query", statsSearchModel.getQuery());
          MetacatUI.statsModel.set("searchModel", statsSearchModel);

          if (_.contains(MetacatUI.appModel.get("dataoneHostedRepos"), this.nodeInfo.identifier)) {
            MetacatUI.statsModel.set("mdqImageId", this.nodeInfo.identifier);
          }

          // render repository view as portal view
          this.renderPortal();
        },

        /**
         * Render the Portal view
         */
        renderPortal: function () {

          // Set the document title to the portal name
          MetacatUI.appModel.set("title", this.model.get("name"))
          MetacatUI.appModel.set("description", this.model.get("description"))

          // Getting the correct portal label and seriesID
          this.label = this.model.get("label");
          this.portalId = this.model.get("seriesId");

          // Remove the listeners that were set during the fetch() process
          this.stopListening(this.model, "notFound", this.handleNotFound);
          this.stopListening(this.model, "error", this.showError);

          //If this is in DataONE Plus Preview Mode, check that the portal is
          // a Plus portal before rendering. Member Node portals are always displayed.
          if (MetacatUI.appModel.get("dataonePlusPreviewMode") && !this.nodeView) {
            var sourceMN = this.model.get("datasource");

            //Check if the portal source node is from the active alt repo OR is
            // configured as a Plus portal.
            if (typeof sourceMN != "string" ||
              (sourceMN != MetacatUI.appModel.get("defaultAlternateRepositoryId") &&
                !_.findWhere(MetacatUI.appModel.get("dataonePlusPreviewPortals"),
                  { datasource: sourceMN, seriesId: this.model.get("seriesId") }))) {

              //Get the name of the source member node
              var sourceMNName = "original data repository",
                mnURL = "";
              if (typeof sourceMN == "string") {
                var sourceMNObject = MetacatUI.nodeModel.getMember(sourceMN);
                if (sourceMNObject) {
                  sourceMNName = sourceMNObject.name;

                  //If there is a baseURL string
                  if (sourceMNObject.baseURL) {
                    //Parse out the origin of the baseURL string. We want to crop out the /metacat/d1/mn parts.
                    mnURL = sourceMNObject.baseURL.substring(0, sourceMNObject.baseURL.lastIndexOf(".")) +
                      sourceMNObject.baseURL.substring(sourceMNObject.baseURL.lastIndexOf("."),
                        sourceMNObject.baseURL.indexOf("/", sourceMNObject.baseURL.lastIndexOf(".")));
                  }
                }
              }

              //Show a message that the portal can be found on the repository website.
              var message = $(document.createElement("h3")).addClass("center stripe");
              message.text("The " + this.model.get("name") + " " + MetacatUI.appModel.get("portalTermSingular") +
                " can be viewed in the ");

              if (mnURL) {
                message.append($(document.createElement("a"))
                  .attr("href", mnURL)
                  .attr("target", "_blank")
                  .text(sourceMNName));
              }
              else {
                message.append(sourceMNName);
              }

              this.$el.html(message);

              return;
            }
          }

          // Check for theme/layout settings and add the required files
          this.addTheming();

          // Insert the overall portal template
          this.$el.html(this.template(this.model.toJSON()));

          // Render the header view
          this.headerView = new PortalHeaderView({
            model: this.model,
            nodeView: this.nodeView
          });
          this.headerView.render();
          this.subviews.push(this.headerView);

          // only displaying the edit button for non-repository profiles
          if (!this.nodeView) {
            // Add edit button if user is authorized
            this.insertOwnerControls();
          }

          // Render the content sections
          _.each(this.model.get("sections"), function (section) {
            this.addSection(section);
          }, this);

          // Render the Data section
          if (this.model.get("hideData") !== true) {
            this.sectionDataView = new PortalDataView({
              model: this.model,
              sectionName: "Data",
              id: "Data",
              nodeView: this.nodeView
            });
            this.subviews.push(this.sectionDataView);

            this.$("#portal-sections").append(this.sectionDataView.el);

            //Render the section view and add it to the page
            this.sectionDataView.render();

            this.addSectionLink(this.sectionDataView);
          }

          //Render the metrics section link
          if (this.model.get("hideMetrics") !== true) {

            //Create a PortalMetricsView
            this.metricsView = new PortalMetricsView({
              model: this.model,
              id: this.model.get("metricsLabel"),
              uniqueSectionName: this.model.get("metricsLabel"),
              nodeView: this.nodeView,
              nodeName: this.nodeName
            });

            this.subviews.push(this.metricsView);
            this.$("#portal-sections").append(this.metricsView.el);

            this.metricsView.render();

            this.addSectionLink(this.metricsView);

          }

          // Render the members section
          if (this.model.get("hideMembers") !== true &&
            (this.model.get("associatedParties").length || this.model.get("acknowledgments"))) {

            this.sectionMembersView = new PortalMembersView({
              model: this.model,
              id: "Members",
              sectionName: "Members"
            });
            this.subviews.push(this.sectionMembersView);

            this.$("#portal-sections").append(this.sectionMembersView.el);

            //Render the section view and add it to the page
            this.sectionMembersView.render();

            this.addSectionLink(this.sectionMembersView);
          }

          // Render the logos at the bottom of the portal page
          var ackLogos = this.model.get("acknowledgmentsLogos") || [];
          if (ackLogos.length) {
            this.logosView = new PortalLogosView();
            this.logosView.logos = ackLogos;
            this.subviews.push(this.logosView);
            this.logosView.render();
            this.$(".portal-view").append(this.logosView.el);
          }

          // Re-order the section tabs according the the portal editor's preference,
          // if one has been set
          try {
            var pageOrder = this.model.get("pageOrder");
            if (pageOrder && pageOrder.length) {
              var linksContainer = this.el.querySelector("#portal-section-tabs"),
                sortableLinks = this.el.querySelectorAll("#portal-section-tabs .section-link-container"),
                sortableLinksArray = Array.prototype.slice.call(sortableLinks, 0);
              // sort the links according the pageOrder
              sortableLinksArray.sort(function (a, b) {
                var aName = $(a).text();
                var bName = $(b).text();
                var aIndex = pageOrder.indexOf(aName);
                var bIndex = pageOrder.indexOf(bName);
                // If the label can't be found in the list of labels, place it at the end
                if (bIndex === -1) {
                  return +1
                }
                if (aIndex === -1) {
                  return -1
                }
                // Sort backwards, because we use preprend
                return bIndex - aIndex;
              })
              // Rearrange the links in the DOM
              for (i = 0; i < sortableLinksArray.length; ++i) {
                linksContainer.prepend(sortableLinksArray[i]);
              }
            }
          } catch (error) {
            console.log("Error re-arranging tabs according to the pageOrder option. Error message: " + error)
          }

          //Switch to the active section
          this.switchSection();

          //Scroll to an inner-page link if there is one specified
          if (window.location.hash && this.$(window.location.hash).length) {
            MetacatUI.appView.scrollTo(this.$(window.location.hash));
          }

          // Save reference to this view
          var view = this;

          // On mobile, hide section tabs a moment after page loads so
          // users notice where they are
          setTimeout(function () {
            view.toggleSectionLinks();
          }, 700);

          // On mobile where the section-links-container is set to fixed,
          // hide the portal navigation element when user scrolls down,
          // show again when the user scrolls up.
          MetacatUI.appView.prevScrollpos = window.pageYOffset;
          $(window).on("scroll", "", undefined, this.handleScroll);

        },

        /**
         * Checks the portal model for theme or layout options. If there are any, and if
         * they are supported, then add the associated CSS.
         */
        addTheming: function () {
          try {
            // Check for theme and layout settings.
            var theme = this.model.get("theme");
            var layout = this.model.get("layout");
            // TODO: make supported themes an app model config option?
            var supportedThemes = ["dark", "light"];
            var supportedLayouts = ["panels"];
            // We must remove theme/layout CSS when the user navigates away from the
            // portal in onClose(). To do this, we need to keep track of which CSS is
            // added during this step.
            var view = this
            view.addedThemeCSS = []
            // Layout should be added before theme for CSS rules to work together properly
            // when there is a theme + layout
            if (layout && supportedLayouts.includes(layout)) {
              require(
                ["text!" + MetacatUI.root + "/css/portal-layouts/" + layout + ".css"],
                function (ThemeCss) {
                  var cssID = "portal-layout-" + layout;
                  MetacatUI.appModel.addCSS(ThemeCss, cssID);
                  view.addedThemeCSS.push(cssID)
                })
            }
            if (theme && supportedThemes.includes(theme)) {
              require(
                ["text!" + MetacatUI.root + "/css/portal-themes/" + theme + ".css"],
                function (ThemeCss) {
                  var cssID = "portal-theme-" + theme;
                  MetacatUI.appModel.addCSS(ThemeCss, cssID);
                  view.addedThemeCSS.push(cssID)
                })
            }
          }
          catch (error) {
            console.log(
              'There was an error adding theme and/or layout styles in a PortalView' +
              '. Error details: ' + error
            );
          }
        },

        /**
         * toggleSectionLinks - show or hide the section links nav. Used for
         * mobile/small screens only.
         */
        toggleSectionLinks: function () {
          try {
            // Only toggle the section links on mobile. On mobile, the
            // ".show-sections-toggle" is visible.
            if (this.$(".show-sections-toggle").is(":visible")) {
              this.$("#portal-section-tabs").slideToggle();
            }
          } catch (e) {
            console.log("Failed to toggle section links, error message: " + e);
          }
        },

        /*
         * Checks the authority for the logged in user for this portal and
         * inserts control elements onto the page for the user to interact
         * with the portal. So far, this is just an 'edit portal' button.
         */
        insertOwnerControls: function () {

          // Insert the button into the navbar
          var container = $(this.editButtonContainer);

          var model = this.model;

          this.listenToOnce(this.model, "change:isAuthorized", function () {
            if (!model.get("isAuthorized")) {
              return false;
            } else {
              container.html(
                this.editPortalsTemplate({
                  editButtonText: "Edit " + MetacatUI.appModel.get('portalTermSingular'),
                  pathToEdit: MetacatUI.root + "/edit/" + MetacatUI.appModel.get("portalTermPlural") + "/" + model.get("label")
                })
              );
            }
          });

          this.model.checkAuthority("write");
        },

        /**
         * Update the window location path with the active section name
         * @param {boolean} [showSectionLabel] - If true, the section label will be added to the path
        */
        updatePath: function (showSectionLabel) {

          var label = this.model.get("label") || this.newPortalTempName,
            originalLabel = this.model.get("originalLabel") || this.newPortalTempName,
            pathName = decodeURIComponent(window.location.pathname)
              .substring(MetacatUI.root.length)
              // remove trailing forward slash if one exists in path
              .replace(/\/$/, "");

          // Add or replace the label and section part of the path with updated values.
          // pathRE matches "/label/section", where the "/section" part is optional
          var pathRE = new RegExp("\\/(" + label + "|" + originalLabel + ")(\\/[^\\/]*)?$", "i");
          newPathName = pathName.replace(pathRE, "") + "/" + label;

          if (showSectionLabel && this.activeSection) {
            newPathName += "/" + this.activeSection.uniqueSectionLabel;
          }

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

        },

        /**
         * Gets a list of section names from tab elements and updates the
         * sectionNames attribute on this view.
         */
        updateSectionNames: function () {

          // Get the section names from the tab elements
          var sectionNames = [];
          this.$(this.sectionLinks)
            .each(function (i, anchorEl) {
              sectionNames[i] = $(anchorEl)
                .attr("href")
                .substring(1)
            });

          // Set the array of sectionNames on the view
          this.sectionNames = sectionNames
        },

        /**
        * Manually switch to a section subview by making the tab and tab panel active.
        * Navigation between sections is usually handled automatically by the Bootstrap
        * library, but a manual switch may be necessary sometimes
        * @param {PortalSectionView} [sectionView] - The section view to switch to. If not given, defaults to the activeSection set on the view.
        */
        switchSection: function (sectionView) {

          //Create a flag for whether the section label should be shown in the URL
          var showSectionLabelInURL = true;

          // If no section view is given, use the active section in the view.
          if (!sectionView) {
            //Use the sectionView set already
            if (this.activeSection) {
              var sectionView = this.activeSection;
            }
            //Or find the section view by name, which may have been passed through the URL
            else if (this.activeSectionLabel) {
              var sectionView = this.getSectionByLabel(this.activeSectionLabel);
            }
          }

          //If no section view was indicated, just default to the first visible one
          if (!sectionView) {
            var sectionView = this.$(this.sectionLinkContainer).first().data("view");

            //If we are defaulting to the first section, don't show the section label in the URL
            showSectionLabelInURL = false;

            //If there are no section views on the page at all, exit now
            if (!sectionView) {
              return;
            }
          }

          // Update the activeSection set on the view
          this.activeSection = sectionView;

          // Activate the section content
          this.$(this.sectionEls).each(function (i, contentEl) {
            if ($(contentEl).data("view") == sectionView) {
              $(contentEl).addClass("active");
            } else {
              // make sure no other sections are active
              $(contentEl).removeClass("active");
            }
          });

          // Activate the link to the content
          this.$(this.sectionLinkContainer).each(function (i, linkEl) {
            if ($(linkEl).data("view") == sectionView) {
              $(linkEl).addClass("active")
            } else {
              // make sure no other sections are active
              $(linkEl).removeClass("active")
            };
          });

          //If the section view has post-render functionality, execute it now
          if (typeof sectionView.postRender == "function") {
            sectionView.postRender();
          }

          // Eventually, the panels layout will allow showing multiple sections at the
          // same time in different panels. For now, the visualizations sections should
          // take up the full height of the viewport (minus the header elements), and the
          // footer should be hidden.
          if (
            (this.model.get("layout") === "panels") &&
            (sectionView instanceof PortalVisualizationsView)
          ) {
            if( this.logosView ){
              this.logosView.el.style.setProperty('display', 'none')
            }
            if( MetacatUI.footerView ){
              MetacatUI.footerView.hide()
            }
          } else {
            if( this.logosView ){
              this.logosView.el.style.removeProperty('display')
            }
            if( MetacatUI.footerView ){
              MetacatUI.footerView.show()
            }
          }

          if (!this.nodeView) {
            //Update the location path with the new section name
            this.updatePath(showSectionLabelInURL);
          }


        },

        /**
        * When a section link has been clicked, switch to that section
        * @param {Event} e - The click event on the section link
        */
        handleSwitchSection: function (e) {

          e.preventDefault();

          var sectionView = $(e.target).parents(this.sectionLinkContainer).first().data("view");

          if (sectionView) {
            this.switchSection(sectionView);

            // If the user clicks a link and is not near the top of the page
            // (i.e. on mobile), scroll to the top of the section content.
            // Otherwise it might look like the page hasn't changed (e.g.
            // when focus is on the footer)
            if (window.pageYOffset > this.$("#portal-sections").offset().top) {
              MetacatUI.appView.scrollTo(this.$("#portal-sections"));
            }

          }

        },

        /**
        * Returns the section view that has a label matching the one given.
        * @param {string} label - The label for the section
        * @return {PortalSectionView|false} - Returns false if a matching section view isn't found
        */
        getSectionByLabel: function (label) {

          //If no label is given, exit
          if (!label) {
            return;
          }

          //Find the section view whose unique label matches the given label. Case-insensitive matching.
          return _.find(this.subviews, function (view) {
            if (typeof view.uniqueSectionLabel == "string") {
              return view.uniqueSectionLabel.toLowerCase() == label.toLowerCase();
            }
            else {
              return false;
            }
          });
        },

        /**
        * Creates and returns a unique label for the given section. This label is just used in the view,
        * because portal sections can have duplicate labels. But unique labels need to be used for navigation in the view.
        * @param {PortEditorSection} sectionModel - The section for which to create a unique label
        * @return {string} The unique label string
        */
        getUniqueSectionLabel: function (sectionModel) {
          //Get the label for this section
          var sectionLabel = sectionModel.get("label").replace(/[^a-zA-Z0-9 ]/g, "").replace(/ /g, "-"),
            unalteredLabel = sectionLabel,
            sectionLabels = this.sectionLabels || [],
            i = 2;

          //Concatenate a number to the label if this one already exists
          while (sectionLabels.includes(sectionLabel)) {
            sectionLabel = unalteredLabel + i;
            i++;
          }

          return sectionLabel;
        },

        /**
         * Creates a PortalSectionView to display the content in the given portal
         * section. Also creates a navigation link to the section.
         *
         * @param {PortalSectionModel} sectionModel - The section to render in this view
         */
        addSection: function (sectionModel) {

          //If this is a visualization Section, render it differently with PortalVizSectionView
          if (sectionModel.get("sectionType") == "visualization") {
            this.addVizSection(sectionModel);
            return;
          }
          //All other portal section types are rendered with the basic PortalSectionView
          else {
            //Create a new PortalSectionView
            var sectionView = new PortalSectionView({
              model: sectionModel
            });

            //Render the section
            sectionView.render();

            //Add the section view to this portal view
            this.$("#portal-sections").append(sectionView.el);

            this.addSectionLink(sectionView);

            //Create a unique label for this section and save it
            var uniqueLabel = this.getUniqueSectionLabel(sectionModel);
            //Set the unique section label for this view
            sectionView.uniqueSectionLabel = uniqueLabel;

            this.subviews.push(sectionView);
          }

        },

        /**
         * Creates a PortalSectionView to display the content in the given portal
         * section. Also creates a navigation link to the section.
         * @param {PortalVizSectionModel} sectionModel - The visualization section to render in this view
         *
         */
        addVizSection: function (sectionModel) {

          //Create a new PortalSectionView
          var sectionView = new PortalVisualizationsView({
            model: sectionModel
          });

          //Render the section
          sectionView.render();

          //Add the section view to this portal view
          this.$("#portal-sections").append(sectionView.el);

          this.addSectionLink(sectionView);

          //Create a unique label for this section and save it
          var uniqueLabel = this.getUniqueSectionLabel(sectionModel);

          //Set the unique section label for this view
          sectionView.uniqueSectionLabel = uniqueLabel;

          this.subviews.push(sectionView);

        },

        /**
         * Add a link to a section of this portal page
         * @param {PortalSectionView} sectionView - The view to add a link to
         */
        addSectionLink: function (sectionView) {

          var label = sectionView.getName();
          var hrefLabel = sectionView.getName({ linkFriendly: true });

          //Create a navigation link
          this.$("#portal-section-tabs").append(
            $(document.createElement("li"))
              .addClass("section-link-container")
              .data("view", sectionView)
              .append($(document.createElement("a"))
                .text(label)
                .attr("href", "#" + hrefLabel)
                .attr("data-toggle", "tab")
                .addClass("portal-section-link")
                .data("view", sectionView)));

        },

        /**
        * Handles the case where the PortalModel is fetched and nothing is found.
        */
        handleNotFound: function () {

          var view = this;

          //If the user is NOT logged in OR
          // if the user is logged in, and the last fetch was done with user credentials, then this Portal is either not accessible or non-existent
          if (MetacatUI.appUserModel.get("checked") && !MetacatUI.appUserModel.get("loggedIn") ||
            (MetacatUI.appUserModel.get("checked") && MetacatUI.appUserModel.get("loggedIn") && this.model.get("fetchedWithAuth"))) {

            //Check if there is an indexing queue, because this model may still be indexing
            var onError = function () {
              //If the request to the monitor/status API fails, then show the not-found message
              view.showNotFound.call(view);
            },
              onSuccess = function (sizeOfQueue) {

                if (sizeOfQueue > 0) {
                  //Show a warning message about the index queue
                  MetacatUI.appView.showAlert(
                    "<p>We couldn't find a data portal named \" <span id='portal-view-not-found-name'></span>" +
                    "\".</p><p><i class='icon icon-exclamation-sign'></i> If this portal was created in the last few minutes, it may still be processing, since there are currently <b>" + sizeOfQueue +
                    "</b> submissions in the queue.</p>",
                    "alert-warning",
                    view.$el
                  );
                  view.$(".loading").remove();

                  view.$("#portal-view-not-found-name").text(view.label || view.portalId);
                }
                else {
                  //If the size of the queue is 0, then show the not-found message
                  view.showNotFound.call(view);
                }

              }

            //Get the size of the index queue
            MetacatUI.appLookupModel.getSizeOfIndexQueue(onSuccess, onError);

          }
          //If the user IS logged in and we haven't fetched the model with user authentication yet
          else if (MetacatUI.appUserModel.get("checked") && MetacatUI.appUserModel.get("loggedIn")) {
            //Fetch again now that the user is logged in
            this.model.fetch();
          }
          //If the user login status is unknown, because authentication is still pending
          else if (!MetacatUI.appUserModel.get("checked")) {
            //Wait for the authentication to be checked, and then start this function over again
            this.listenToOnce(MetacatUI.appUserModel, "change:checked", this.handleNotFound);
          }

        },

        /**
         * If the given portal doesn't exist, display a Not Found message.
         */
        showNotFound: function () {

          var notFoundMessage = "The data portal \"<span id='portal-view-not-found-name'></span>" +
            "\" doesn't exist.",
            notification = this.alertTemplate({
              classes: "alert-error",
              msg: notFoundMessage,
              includeEmail: true
            });

          this.$el.html(notification);

          this.$("#portal-view-not-found-name").text(this.label || this.portalId);

        },

        /**
        * Show an error message in this view
        * @param {SolrResult} model
        * @param {XMLHttpRequest.response|string} reponse
        */
        showError: function (model, response) {

          try {
            var errorMsg = "",
              errorClass = "alert-error",
              icon = "frown",
              portalTerm = MetacatUI.appModel.get("portalTermSingular") || "portal",
              errorTitle = "Something went wrong displaying this " + portalTerm + ".";

            // For errors resulting from authorization errors, use a friendlier and more
            // helpful error message than the default message returned from fetch
            if (response && response.status == 401) {
              errorTitle = 'You need permission to view this ' + portalTerm + ".";
              errorClass = "alert-info";
              icon = "lock"
              // Make a suggestion of how to fix the error based on whether the user is logged in or not.
              if (!MetacatUI.appUserModel.get("loggedIn")) {
                // If not logged in, suggest that the user signs in
                errorMsg = '<strong><a href="' +
                  MetacatUI.appModel.get('signInUrlOrcid') + window.location.href +
                  '">Sign in</a></strong> to see if you have already been given access to view this ' +
                  portalTerm + '.';
              } else {
                // If signed in, suggest that the user contacts that portal owner
                errorMsg = "Contact the owner of this " + portalTerm + " to request access."
              }
              // For all other types of errors
            } else {
              if (response && response.responseText) {
                errorMsg = "Error details: " + $(response.responseText).text();
              }
              if (typeof response == "string") {
                errorMsg = "Error details: " + response;
              }
            }

            if (errorMsg) {
              errorMsg = "<p>" + errorMsg + "</p>"
            }

            //Show the error message
            MetacatUI.appView.showAlert(
              "<h4><i class='icon icon-" + icon + "'></i>" + errorTitle + "</h4>" + errorMsg,
              errorClass + " portal-alert-container",
              this.$el,
              0,
              { includeEmail: true }
            );

            //Remove the loading message from this view
            this.$el.find(".loading").remove();

          } catch (error) {
            console.log("There was a problem trying to display the error message in the Portal View. Error details: " + error);
          }

        },

        /**
        * This function is called whenever the window is scrolled.
        */
        handleScroll: function () {
          var menu = $(".section-links-container")[0],
            menuHeight = $(menu).height(),
            hiddenHeight = (menuHeight * -1);
          var currentScrollPos = window.pageYOffset;
          if (MetacatUI.appView.prevScrollpos > currentScrollPos) {
            //Get the height of any menu that may be displayed at the bottom of the page, too

            menu.style.bottom = "0px";
          } else {
            menu.style.bottom = hiddenHeight + "px";
          }
          MetacatUI.appView.prevScrollpos = currentScrollPos;
        },

        /**
         * This function is called when the app navigates away from this view.
         * Any clean-up or housekeeping happens at this time.
         */
        onClose: function () {

          MetacatUI.appModel.resetTitle();
          MetacatUI.appModel.resetDescription();

          // Run subView onClose functions if they exist
          for (const subView of this.subviews) {
            if (typeof subView?.onClose === "function") {
              subView.onClose();
            }
          }

          //Remove each subview from the DOM and remove listeners
          _.invoke(this.subviews, "remove");

          this.subviews = new Array();

          // Remove any CSS that was added for the theme or layout
          if (this.addedThemeCSS && this.addedThemeCSS.length) {
            this.addedThemeCSS.forEach(function (cssID) {
              MetacatUI.appModel.removeCSS(cssID);
            });
          }

          //Remove all listeners
          this.stopListening();

          //Reset the active alternate repository
          //MetacatUI.appModel.set("activeAlternateRepositoryId", null);

          //Delete the metrics view from this view
          delete this.sectionMetricsView;
          //Delete the model from this view
          delete this.model;

          //Remove the scroll listener
          $(window).off("scroll", "", this.handleScroll);

          $("body").removeClass("PortalView");

          // Make sure the footer is visible (hidden for dataViz sections + panels layout)
          MetacatUI.footerView.el.style.removeProperty('display')
          document.body.style.removeProperty('--footer-height')

          $("#editPortal").remove();

          this.undelegateEvents();
        },

        /**
         * Checks if the label is a repository
         *
         * @param {string} username - The portal label or the member node repository identifier
         */
        isNode: function (username) {

          if (username === undefined) {
            this.showNotFound();
            return;
          }
          var model = this;
          var node = _.find(MetacatUI.nodeModel.get("members"), function (nodeModel) {
            return nodeModel.shortIdentifier.toLowerCase() == (username).toLowerCase();
          });

          return (node && (node !== undefined))
        }
      });

    return PortalView;
  });