# Source: src/js/views/portals/editor/PortEditorSectionsView.js

    define(['underscore',
            'jquery',
            'backbone',
            'sortable',
            'models/portals/PortalModel',
            'models/portals/PortalSectionModel',
            "views/portals/editor/PortEditorSectionView",
            "views/portals/editor/PortEditorSettingsView",
            "views/portals/editor/PortEditorDataView",
            "views/portals/editor/PortEditorMdSectionView",
            "text!templates/portals/editor/portEditorSections.html",
            "text!templates/portals/editor/portEditorMetrics.html",
            "text!templates/portals/editor/portEditorSectionLink.html",
            "text!templates/portals/editor/portEditorSectionOptionImgs/metrics.svg"],
    function(_, $, Backbone, Sortable, Portal, PortalSection,
              PortEditorSectionView, PortEditorSettingsView, PortEditorDataView,
              PortEditorMdSectionView,
              Template, MetricsSectionTemplate, SectionLinkTemplate,
              MetricsSVG){

      /**
      * @class PortEditorSectionsView
      * @classdesc A view of one or more Portal Editor sections
      * @classcategory Views/Portals/Editor
      * @extends Backbone.View
      * @constructor
      */
      var PortEditorSectionsView = Backbone.View.extend(
        /** @lends PortEditorSectionsView.prototype */{

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

        /**
        * The HTML tag name for this view's element
        * @type {string}
        */
        tagName: "div",

        /**
        * The HTML classes to use for this view's element
        * @type {string}
        */
        className: "port-editor-sections",

        /**
        * The PortalModel that is being edited
        * @type {Portal}
        */
        model: undefined,

        /**
        * A reference to the currently active editor section. e.g. Data, Metrics, Settings, etc.
        * @type {PortEditorSectionView}
        */
        activeSection: undefined,

        /**
        * The name of the active section when the view is first loaded. This is retrieved from the router/URL
        * @type {string}
        */
        activeSectionLabel: undefined,

        /**
        * The unique labels for each section in this Portal
        * @type {string[]}
        */
        sectionLabels: [],

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

        /**
        * A reference to the PortalEditorView
        * @type {PortalEditorView}
        */
        editorView: undefined,

        /**
        * References to templates for this view. HTML files are converted to Underscore.js templates
        */
        /**
        @type {Underscore.Template}
        */
        template: _.template(Template),
        /**
        @type {Underscore.Template}
        */
        sectionLinkTemplate: _.template(SectionLinkTemplate),
        /**
        @type {Underscore.Template}
        */
        metricsSectionTemplate: _.template(MetricsSectionTemplate),

        /**
        * A jQuery selector for the elements that are links to the individual sections
        * @type {string}
        */
        sectionLinks: ".portal-section-link",
        /**
        * A jQuery selector for the element that the section links should be inserted into
        * @type {string}
        */
        sectionLinksContainer: ".section-links-container",
        /**
        * 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 element that the editor sections should be inserted into
        * @type {string}
        */
        sectionsContainer: ".sections-container",

        /**
        * A jQuery selector for the section elements
        * @type {string}
        */
        sectionEls: ".port-editor-section",

        /**
        * A selector for link or tab elements that the user is allowed to re-order,
        * starting from the sectionLinksContainer
        * @type {string}
        */
        sortableLinksSelector: ">li:not(.unsortable)",

        /**
        * A class name for the handles on tabs that the user can drag to re-order
        * @type {string}
        */
        handleClass: "handle",

        /**
        * A label for the section used to add a new page
        * @type {string}
        */
        addPageLabel: "AddPage",

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

        /**
        * @borrows PortalEditorView.newPortalTempName as newPortalTempName
        */
        newPortalTempName: "",

        /**
        * The events this view will listen to and the associated function to call.
        * @type {Object}
        */
        events: {
          "click .rename-section" : "renameSection",
          "dblclick .portal-section-link": "renameSection",
          "click .show-section"   : "showSection",
          "click .portal-section-link"   : "handleSwitchSection",
          "focusout .portal-section-link[contenteditable=true]" : "updateName",
          "click .cancelled-section-removal" : "closePopovers",
          "click .confirmed-section-removal" : "removeSection",
          // both keyup and keydown events are needed for limitLabelLength function
          "keyup .portal-section-link[contenteditable=true]"    : "limitLabelInput",
          "keydown .portal-section-link[contenteditable=true]"  : "limitLabelInput",
          "click #link-to-data"                                 :  "navigateToData"
        },

        /**
        * Creates a new PortEditorSectionsView
        * @constructs PortEditorSectionsView
        * @param {Object} options - A literal object with options to pass to the view
        */
        initialize: function(options){
          //Reset arrays and objects set on this View, otherwise they will be shared across intances, causing errors
          this.subviews = new Array();
          this.editorView = null;

          // Get all the options and apply them to this view
          if (options) {
              var optionKeys = Object.keys(options);
              _.each(optionKeys, function(key, i) {
                  this[key] = options[key];
              }, this);
          }
        },

        /**
        * Renders the PortEditorSectionsView
        */
        render: function(){

          //Insert the template into the view
          this.$el.html(this.template());

          //Render the Data section
          this.renderDataSection();

          //Render the Metrics section
          this.renderMetricsSection();

          //Render the Add Section tab
          this.renderAddSection();

          //Render the Settings
          this.renderSettings();

          //Render a Section View for each content section in the Portal
          this.renderContentSections();

          // Disable the delete/hide section option if there is only one section
          this.toggleRemoveSectionOption();

          var view = this,
              linksContainer = view.el.querySelector(view.sectionLinksContainer),
              sortableLinksSelector = view.sortableLinksSelector,
              sortableLinks = view.el.querySelectorAll(view.sectionLinksContainer + view.sortableLinksSelector),
              sortableLinksArray = Array.prototype.slice.call(sortableLinks, 0),
              pageOrder = this.model.get("pageOrder");

          // Arrange tabs in the order the user has pre-selected
          try {
            if(pageOrder && pageOrder.length){
              // sort the links according the pageOrder
              sortableLinksArray.sort(function(a,b){
                var aName = $(a).data("section-name");
                var bName = $(b).data("section-name");
                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 prepend
                return bIndex - aIndex;
              })
              // Rearrange the links in the DOM
              for (i = 0; i < sortableLinksArray.length; ++i) {
                // Use preprend so that Settings and AddPage tabs remain last in list
                linksContainer.prepend(sortableLinksArray[i]);
              }
            }
          } catch (error) {
            console.log("Error re-arranging tabs according to the pageOrder option. Error message: " + error)
          }

          // Initialize user-controlled tab re-ordering
          var sortable = Sortable.create(linksContainer, {
            direction: 'horizontal',
            easing: "cubic-bezier(1, 0, 0, 1)",
            animation: 200,
            // Only tabs that have an element with this class will be draggable
            handle: "." + view.handleClass,
            draggable: sortableLinksSelector,
            // When the tab order is changed, update the portal model option with new order
            onUpdate: function (evt) {
              view.updatePageOrder();
            },
          })

          // Switch to the active section, if there is one
          if( this.activeSectionLabel ){
            this.activeSection = this.getSectionByLabel(this.activeSectionLabel);

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

            //Reset the active section label, since it is only used during the initial rendering
            this.activeSectionLabel = undefined;

          }
          else{
            //Switch to the default section
            this.switchSection();
          }
        },

        /**
        * Render a section for adding a new section
        */
        renderAddSection: function(){

          //Create a unique label for this section and save it
          this.updateSectionLabelsList(this.addPageLabel);

          // Add a "Add section" button/tab
          var addSectionView = new PortEditorSectionView({
            model: this.model,
            uniqueSectionLabel: this.addPageLabel,
            sectionType: "addpage",
            editorView: this.editorView
          });

          addSectionView.$el.addClass("tab-pane")
            .addClass("port-editor-add-section-container")
            .attr("id", this.addPageLabel);

          //Add the section element to this view
          this.$(this.sectionsContainer).append(addSectionView.$el);

          //Render the section view
          addSectionView.render();

          // Add the tab to the tab navigation
          this.addSectionLink(addSectionView);

          // Replace the name "AddSection" with fontawsome "+" icon
          this.$el.find( this.sectionLinkContainer + "[data-section-name='" + this.addPageLabel + "'] a")
                  .html("<i class='icon icon-plus'></i>")
                  .attr("title", "Add a new page");

          // When a sectionOption is clicked in the addSectionView subview,
          // the "addNewSection" event is triggered.
          this.listenTo(addSectionView, "addNewSection", this.addSection);

          //Add the view to the subviews array
          this.subviews.push(addSectionView);

        },

        /**
        * Render all sections in the editor for each content section in the Portal
        */
        renderContentSections: function(){

          // Get the sections from the Portal
          var sections = this.model.get("sections");

          // Render each markdown (aka "freeform") section already in the PortalModel
          _.each(sections, function(section){

            try{
              if(section){
                //Render the content section
                this.renderContentSection(section);
              }
            }
            catch(e){
              console.error(e);
            }
          }, this);

        },

        /**
        * Render a single markdown section in the editor (sectionView + link)
        * @param {PortalSectionModel} section - The section to render
        * @param {boolean} isNew - If true, this section will be rendered as a section that was just added by the user
        */
        renderContentSection: function(section, isNew){

          try{

            if( typeof isNew == "undefined" || isNew == null) {
              var isNew = false;
            }

            if(section){

              // Create and render and markdown section view
              var sectionView = new PortEditorMdSectionView({
                model: section
              });

              // Pass the portal editor view onto the section
              sectionView.editorView = this.editorView;

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

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

              this.updateSectionLabelsList(uniqueLabel);

              //Attach the editor view to this view
              sectionView.editorView = this.editorView;

              sectionView.$el.attr("id", uniqueLabel);

              //Insert the PortEditorMdSectionView element into this view
              this.$(this.sectionsContainer).append(sectionView.$el);

              //Render the PortEditorMdSectionView
              sectionView.render();

              // Add the tab to the tab navigation
              this.addSectionLink(sectionView, ["Rename", "Delete"], isNew);

              // Add the sections to the list of subviews
              this.subviews.push(sectionView);

            }
          }
          catch(e){
            console.error(e);
          }

        },

        /**
        * Renders a Data section in this view
        */
        renderDataSection: function(){

          try{

            this.updateSectionLabelsList("Data");

            // Render a PortEditorDataView and corresponding tab
            var dataView = new PortEditorDataView({
              model: this.model,
              uniqueSectionLabel: "Data",
              editorView: this.editorView
            });

            //Save a reference to this view
            this.dataView = dataView;

            //Add the view to the page
            this.$(this.sectionsContainer).append(dataView.el);

            //Render the PortEditorDataView
            dataView.render();

            //Create the menu options for the Data section link
            var menuOptions = [];
            if( this.model.get("hideData") === true ){
              menuOptions.push("Show");
            }
            else{
              menuOptions.push("Hide");
            }

            // Add the tab to the tab navigation
            this.addSectionLink(dataView, menuOptions);

            //When the Data section has been hidden or shown, update the section link
            this.stopListening(this.model, "change:hideData");
            this.listenTo(this.model, "change:hideData", function(){
              //Create the menu options for the Data section link
              var menuOptions = [];
              if( this.model.get("hideData") === true ){
                menuOptions.push("Show");
              }
              else{
                menuOptions.push("Hide");
              }

              this.updateSectionLink(dataView, menuOptions);
            });

            // Add the data section to the list of subviews
            this.subviews.push(dataView);
          }
          catch(e){
            console.error(e);
          }
        },

        /**
        * Renders the Metrics section of the editor
        */
        renderMetricsSection: function(){

          // Render a PortEditorSectionView for the Metrics section if metrics is set
          // to show, and the view hasn't already been rendered.
          if(this.model.get("hideMetrics") !== true && !this.metricsView){

            //Create a unique label for this section and save it
            this.updateSectionLabelsList("Metrics");

            //Create a section view
            this.metricsView = new PortEditorSectionView({
              model: this.model,
              uniqueSectionLabel: "Metrics",
              template: this.metricsSectionTemplate,
              sectionType: "metrics",
              editorView: this.editorView
            });

            this.metricsView.$el.attr("id", "Metrics");
            this.$(this.sectionsContainer).append(this.metricsView.el);

            //Render the view
            this.metricsView.render();

            // Insert the metrics illustration
            $(this.metricsView.el).find(".metrics-figure-container").html(MetricsSVG);

            // Add the data section to the list of subviews
            this.subviews.push(this.metricsView);

          }
          //If the metrics aren't hidden AND the metrics view was created already, then show it
          else if( this.model.get("hideMetrics") !== true && this.metricsView ){

            this.$(this.sectionsContainer).append(this.metricsView.$el);
            this.metricsView.$el.data({
              view: this.metricsView,
              model: this.model
            })

          }

          //When the metrics section has been toggled, remove or add the link
          this.toggleMetricsLink();

        },

        /**
         * navigateToData - Navigate to the data tab.
         */
        navigateToData: function(){
          if(this.dataView){
            this.switchSection(this.dataView);
          }
        },

        /**
        * Adds or removes the metrics link depending on the 'hideMetrics' option in
        * the model.
        */
        toggleMetricsLink: function(){

          try{
            // Need a metrics view to exist already if metrics is set to show
          /*  if(!this.metricsView && !this.model.get("hideMetrics") === true){
              this.renderMetricsSection();
            }*/
            //If hideMetrics has been set to true, remove the link
            if( this.model.get("hideMetrics") === true ){
              this.removeSectionLink(this.metricsView);
            // Otherwise add it
            } else {
              this.addSectionLink(this.metricsView, ["Delete"]);
            }
          }
          catch(e){
            console.error(e);
          }
        },

        /**
        * Renders the Settings section of the editor
        */
        renderSettings: function(){

          //Create a unique label for this section and save it
          this.updateSectionLabelsList("Settings");

          //Create a PortEditorSettingsView
          var settingsView = new PortEditorSettingsView({
            model: this.model,
            uniqueSectionLabel: "Settings"
          });

          settingsView.editorView = this.editorView;

          //Add the Settings view to the page
          this.$(this.sectionsContainer).append(settingsView.$el);

          //Render the PortEditorSettingsView
          settingsView.render();

          //Create and add a section link
          this.addSectionLink(settingsView);

          // Add the data section to the list of subviews
          this.subviews.push(settingsView);

        },

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

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

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

          //If there is a label, add it to the new path.
          // (there will always be a label unless this is a new portal)
          if( label ){
            newPathName += "/" + label;
          }

          //If there is an active section, and the showSectionLabel parameter is true, add the section label to the path.
          if( showSectionLabel && this.activeSection ){
            newPathName += "/" + this.activeSection.uniqueSectionLabel;
          }

          //If the path has changed, navigate to the new path, which creates a record in the browser history
          if( pathName != newPathName ){
            // Update the window location
            MetacatUI.uiRouter.navigate( newPathName, {
              trigger: false
            });
          }

        },

        /**
        * Returns the section view that has a label matching the one given.
        * @param {string} label - The label for the section
        * @return {PortEditorSectionView|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;
            }
          });
        },

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

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

          //Find the section view whose unique label matches the given label. Case-insensitive matching.
          return _.findWhere( this.subviews, { model: section });
        },

        /**
        * 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;
        },

        /**
        * 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 {PortEditorSectionView} [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 + ":not(.removing)").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");
              sectionView.trigger("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")
            };
          });

          //Never show the section label in the URL, since it messes with the back/forward browser navigation. See #1364
          showSectionLabelInURL = false;
          //Update the location path
          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();

          // Make sure any markdown editor toolbar modals are closed
          // (otherwise they persist in new tab)
          $("body").find(".wk-prompt").remove();

          // Make sure any markdown editor toolbar modals are closed
          // (otherwise they persist in new tab)
          $("body").find(".wk-prompt").remove();

          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
            if(window.pageYOffset > $("#editor-body").offset().top){
              MetacatUI.appView.scrollTo($("#editor-body"));
            }
          }

        },

        /**
        * Add a link to the given editor section
        * @param {PortEditorSectionView} sectionView - The view to add a link to
        * @param {string[]} menuOptions - An array of menu options for this section. e.g. Rename, Delete
        * @param {boolean} isFocused - A boolean flag to enable focus on new section link
        */
        addSectionLink: function(sectionView, menuOptions, isFocused){

          try{

            if (typeof isFocused != "boolean") {
              var isFocused = false;
            }

            var view = this;

            var newLink = this.createSectionLink(sectionView, menuOptions);
            var isMarkdownSection = $(newLink).data("view").type == "PortEditorMdSection"

            // Make the tab hidden to start
            $(newLink)
              .find(this.sectionLinks)
              .css('max-width','0px')
              .css('opacity','0.2')
              .css('white-space', 'nowrap');

            $(newLink)
              .find(".section-menu-link")
              .css('opacity','0.5')
              .css('transition', 'opacity 0.1s');

            // Find the "+" link to help determine the order in which we should add links
            var addSectionEl = this.$(this.sectionLinksContainer)
                                   .find(this.sectionLinkContainer + "[data-section-name='" + this.addPageLabel + "']")[0];

            // If the new link is for a markdown section and there's no user-defined page
            // order, then insert the markdown sections before the data and metrics section
            // (this is the default order when there is no page ordering).
            if(
              isMarkdownSection &&
              (!view.model.get("pageOrder") || !view.model.get("pageOrder").length)
            ){

              // Find the last markdown section in the list of links
              var currentLinks = this.$(this.sectionLinksContainer).find(this.sectionLinkContainer);
              var i = _.map(currentLinks, function (li) {
                return $(li).data("view") ? $(li).data("view").type : "";
              }).lastIndexOf("PortEditorMdSection");
              var lastMdSection = currentLinks[i];
              // Append the new link after the last markdown section, or add it first.
              if (lastMdSection) {
                $(lastMdSection).after(newLink);
              } else {
                this.$(this.sectionLinksContainer).prepend(newLink);
              }
            // If there is already some user-defined page ordering, or if not a markdown
            // section and not the Settings section, and if there is already a "+" link, add
            // new link before the "+" link
            } else if (addSectionEl && sectionView.uniqueSectionLabel != "Settings"){
              $(addSectionEl).before(newLink);
            // If the new link is "Settings", or there's no "+" link yet, insert new link last.
            } else {
              this.$(this.sectionLinksContainer).append(newLink);
            }

            // If this is a newly added markdown section, highlight the section name and make
            // it content editable. Currently only markdown sections labels are editable.
            if (isFocused && isMarkdownSection) {
              var newSectionLink = $(newLink).children(".portal-section-link");
              newSectionLink.attr("contenteditable", true);
              newSectionLink.focus();

              //Select the text of the link
              if (window.getSelection && window.document.createRange) {
                var selection = window.getSelection();
                var range = window.document.createRange();
                range.selectNodeContents(newSectionLink[0]);
                selection.removeAllRanges();
                selection.addRange(range);
              } else if (window.document.body.createTextRange) {
                range = window.document.body.createTextRange();
                range.moveToElementText(newSectionLink[0]);
                range.select();
              }
            }

            // Animate the link to full width / opacity
            $(newLink).find(this.sectionLinks).animate({
                'max-width': "500px",
                overflow: "hidden",
                opacity: 1
              }, {
              duration: 300,
              complete: function(){
                $(newLink)
                  .find(".section-menu-link")
                  .css('opacity','1')
              }
            });
          }
          catch(e) {
            console.error("Could not add a new section link. Error message: "+ e);
          }

        },

        /**
         * toggleRemoveSectionOption - Disables the hide and remove option from
         * section link if it's the only section left. Re-enables the remove/hide
         * link when a new section is added. Called on initial render and each time
         * a section is added, removed, shown, or hidden.
         */
        toggleRemoveSectionOption: function(){
          try {

            // Determine the number of pages (sections + metrics + data)
            var totalPages         = this.model.get("sections").length +
                                      !this.model.get("hideMetrics") +
                                      !this.model.get("hideData"),
                removeSectionLinks = this.$(this.sectionLinkContainer)
                                      .find(".remove-section");

            // If there's just one section, hide the delete and hide option on last
            // remaining section link
            if(totalPages == 1){

              removeSectionLinks.addClass("disabled");

              if(!removeSectionLinks.closest("li").find(".tooltip").length){
                removeSectionLinks.closest("li").tooltip({
                  placement: "bottom",
                  trigger: "hover",
                  title: "At least one displayed page is required. To remove this page, first add or show another page."
                });
              }

            // If there are 2 sections, re-show the delete or hide options.
            } else if(totalPages == 2){

              removeSectionLinks.removeClass("disabled");
              removeSectionLinks.closest("li").tooltip("destroy");

            // If there are three or more pages, nothing needs to be changed.
            } else {
              return
            }

          } catch (e) {
            console.log("Failure to show/hide the remove section option. Error message: " + e);
          }
        },

        /**
        * Add a link to the given editor section
        * @param {PortEditorSectionView} sectionView - The view to add a link to
        * @param {string[]} menuOptions - An array of menu options for this section. e.g. Rename, Delete
        * @return {Element}
        */
        createSectionLink: function(sectionView, menuOptions){

          var classes = "";
          if( Array.isArray(menuOptions) && menuOptions.includes("Show") ){
            classes = "hidden-section";
          }

          // Do not allow dragging/sorting of the Settings or Add Page sections
          var sortable = true;
          if(["addpage", "settings"].includes(sectionView.sectionType)){
            sortable = false;
          }

          //Create a section link
          var sectionLink = $(this.sectionLinkTemplate({
            menuOptions: menuOptions || [],
            uniqueLabel: sectionView.uniqueSectionLabel,
            sectionLabel: PortalSection.prototype.isPrototypeOf(sectionView.model) ?
                          sectionView.model.get("label") : sectionView.uniqueSectionLabel,
            sectionURL: this.model.get("label") + "/" + sectionView.uniqueSectionLabel,
            sectionType: sectionView.sectionType,
            classes: classes,
            handleClass: this.handleClass,
            sortable: sortable
          }));

          //Attach the section model to the link
          sectionLink.data({
            model: sectionView.model,
            view:  sectionView
          });

          if( sectionView.sectionType == "freeform" && menuOptions.includes("Delete") ){
            var content = $(document.createElement("div"))
                            .append(  $(document.createElement("div"))
                                        .append( $(document.createElement("p"))
                                                   .text("Deleting this page will premanently remove it from this " +
                                                         MetacatUI.appModel.get("portalTermSingular") + ".") ),
                                      $(document.createElement("div"))
                                        .addClass("inline-buttons")
                                        .append( $(document.createElement("button"))
                                                   .addClass("btn cancelled-section-removal")
                                                   .text("No, keep page"),
                                                 $(document.createElement("button"))
                                                   .addClass("btn btn-danger confirmed-section-removal")
                                                   .text("Yes, delete page")));

            // Create a popover with the confirmation buttons
            sectionLink.find(".remove-section").addClass("popover-this").popover({
              html            : true,
              placement       : 'right',
              title           : 'Delete this page?',
              content         : content,
              container       : sectionLink,
              trigger         : "click"
            });
          }

          return sectionLink[0];
        },

        /**
        * Add a link to the given editor section
        * @param {PortEditorSectionView} sectionView - The view to add a link to
        * @param {string[]} menuOptions - An array of menu options for this section. e.g. Rename, Delete
        */
        updateSectionLink: function(sectionView, menuOptions){

          //Create a new link to the section
          var sectionLink = this.createSectionLink(sectionView, menuOptions);

          //Replace the existing link
          this.$(this.sectionLinksContainer).children().each(function(i, link){
            if( $(link).data("view") == sectionView ){
              $(link).replaceWith(sectionLink);
            }
          });
        },

        /**
        * Remove the link to the given section view
        * @param {View} sectionView - The view to remove the link to
        */
        removeSectionLink: function(sectionView){

          // Switch to the default section the user is deleting the active section
          if (sectionView == this.activeSection){
            this.switchSection();
          };

          try{

            var view = this;
            //Find the section link associated with this section view
            this.$(this.sectionLinksContainer).children().each(function(i, link){
              if( $(link).data("view") == sectionView ){

                //Remove the menu link
                $(link).addClass("removing").find(".section-menu-link").remove();

                //Destroy any popovers
                $(link).popover("destroy");
                $(link).find(".popover-this").popover("destroy");

                //Hide the section name link with an animation
                $(link).animate({width: "0px", overflow: "hidden"}, {
                  duration: 300,
                  complete: function(){
                    this.remove();
                    // If there's a page order option set on the model, update it
                    var pageOrder = view.model.get("pageOrder");
                    if(pageOrder && pageOrder.length){
                      view.updatePageOrder();
                    }
                  }
                });
              }
            });
          }
          catch(e){
            console.error(e);
          }
        },

        /**
        * Adds a section and tab to this view and the PortalModel
        * @param {string} sectionType - The type of section to add
        */
        addSection: function(sectionType){

          try{

            //Create a new section to the Portal model
            this.model.addSection(sectionType);

            if(typeof sectionType == "string"){

              switch( sectionType.toLowerCase() ){
                case "data":
                  this.switchSection(this.dataView);
                  break;
                case "metrics":
                  this.renderMetricsSection();
                  this.switchSection(this.metricsView);
                  break;
                case "freeform":
                  // Set up page ordering if it isn't already. This allows us to add a new
                  // freeform page at the end of the list of tabs, instead of before Data and
                  // Metrics (the default before page ordering was enabled).
                  var pageOrder = this.model.get("pageOrder");
                  if (!pageOrder || !pageOrder.length) {
                    this.updatePageOrder();
                  }
                  // Get the section model that was just added
                  var newestSection = this.model.get("sections")[this.model.get("sections").length-1];
                  //Render the content section view for it
                  this.renderContentSection(newestSection, true);
                  //Switch to that new view
                  this.switchSection( this.getSectionByModel(newestSection) );
                  break;
                case "members":
                  // TODO
                  // this.switchSection(this.getSectionByLabel("Members"));
                  break;
              }

              // If the section we just added is now one of two sections, re-enable
              // the hide/delete button on the other section link.
              this.toggleRemoveSectionOption();

              this.editorView.showControls();

              // Add the item to the the pageOrder option on the model, if it exists
              var pageOrder = this.model.get("pageOrder");
              if(pageOrder && pageOrder.length){
                this.updatePageOrder();
              }

            }
            else{
              return;
            }
          }
          catch(e){
            console.error(e);
          }
        },

        /**
        * Removes a section and its tab from this view and the PortalModel.
        * At least one of the parameters is required, but not both
        * @param {Event} [e] - (optional) The click event on the Remove button
        * @param {Element|jQuery} [sectionLink] - The link element of the section to be removed.
        */
        removeSection: function(e, sectionLink){

          try{
            if( !sectionLink || !sectionLink.length ) {

              var clickedEl = $(e.target);

              //Get the PortalSection model for this remove button
              var sectionLink = clickedEl.parents(this.sectionLinkContainer).first();

              //Exit if no section link was found
              if( !sectionLink || !sectionLink.length ){
                return;
              }
            }

            //Get the section model and view
            var sectionModel        = sectionLink.data("model"),
                sectionView         = sectionLink.data("view"),
                sectionType         = sectionLink.data("section-type"),
                uniqueSectionLabel  = sectionView.uniqueSectionLabel,
                sectionLabelIndex   = this.sectionLabels.indexOf(uniqueSectionLabel);

            if( PortalSection.prototype.isPrototypeOf(sectionModel) ){
              // Remove this section from the Portal
              this.model.removeSection(sectionModel);
              // Remove the section label from the list of unique section labels
              if(sectionLabelIndex > -1){
                this.sectionLabels.splice(sectionLabelIndex, 1);
              }
            }
            else{
              //Remove this section type from the model
              this.model.removeSection(sectionType);
            }

            try {

              //If no section view was found, exit now
              if( !sectionView  ){
                return;
              }

              //If this is not the Data section, remove the view, since Data sections can only be hidden
              if( sectionType.toLowerCase() != "data" ){
                // remove the sectionView
                this.removeSectionLink(sectionView);

                // remove the section view from the subviews array
                this.subviews.splice($.inArray(sectionView, this.subviews), 1);

                //Remove the view from the page
                sectionView.$el.remove();

                //Reset the active section, if the one that was removed is currently active
                if( this.activeSection == sectionView ){
                  this.activeSection = undefined;

                  //Switch to the default section
                  this.switchSection();
                }
              }

            } catch (error) {
              console.error(error);
            }

            // If the section just removed was the second-to-last section, disable
            // the hide/delete button on the last section link.
            this.toggleRemoveSectionOption();

            this.editorView.showControls();

          }
          catch(e){
            console.error(e);
            MetacatUI.appView.showAlert("The section could not be deleted. (" + e.message + ")", "alert-error");
          }

        },

        /**
        * Shows a previously-hidden section
        * @param {Event} [e] - (optional) The click event on the Show button
        */
        showSection: function(e){

          try{

            //Get the PortalSection model for this show button
            var sectionLink = $(e.target).parents(this.sectionLinkContainer),
                section = sectionLink.data("model");

            //If this section is not a PortalSection model, get the section type
            if( !PortalSection.prototype.isPrototypeOf(section) ){
              section = sectionLink.data("section-type");
            }

            //If no section was found, exit now
            if( !section ){
              return;
            }

            //Mark this section as shown
            this.model.addSection(section);

            // If the section we're now showing is now one of two sections, re-enable
            // the hide/delete button on the other section link.
            this.toggleRemoveSectionOption();

          }
          catch(e){
            console.error(e);
            MetacatUI.appView.showAlert("The section could not be shown. (" + e.message + ")", "alert-error");
          }

        },

        /**
        * Renames a section in the tab in this view and in the PortalSectionModel
        * @param {Event} [e] - (optional) The click event on the Rename button
        */
        renameSection: function(e){
          try {
            //Get the PortalSection model for this rename button
            var sectionLink = $(e.target).parents(this.sectionLinkContainer),
                targetLink = sectionLink.children(this.sectionLinks),
                section = sectionLink.data("model");

            // double-click events
            if (e.type === "dblclick") {
              // Continue editing tab-name on double click only for markdown sections
              if($(sectionLink).data("view").type != "PortEditorMdSection"){
                return;
              }
            }

            // make the text editable
            targetLink.attr("contenteditable", true);

            // add focus to the text
            targetLink.focus();

            //Select the text of the link
            if (window.getSelection && window.document.createRange) {
                var selection = window.getSelection();
                var range = window.document.createRange();
                range.selectNodeContents( targetLink[0] );
                selection.removeAllRanges();
                selection.addRange(range);
            } else if (window.document.body.createTextRange) {
                range = window.document.body.createTextRange();
                range.moveToElementText( targetLink[0] );
                range.select();
            }

          } catch (error) {
            console.error(error);
          }

        },

        /**
         * Stops user from entering more than 50 characters, and shows a message
         * if user tries to exceed the limit. Also stops a user from entering
         * RETURN or TAB characters, and instead re-directs to updateName().
         * In the case of the TAB key, the focus moves to the title field.
         * @param {Event} e - The keyup or keydown event when the user types in the portal-section-link field
        */
        limitLabelInput: function(e){

          try{

            // Character limit for the labels
            var limit = 50;
            var currentLabel = $(e.target).text();

            // If the RETURN key is pressed
            if(e.which == 13){
              // Don't allow character to be entered
              e.preventDefault();
              e.stopPropagation();
              // Update name and exit function
              this.updateName(e);
              return
            }

            // If the TAB key is pressed
            if(e.which == 9){
              // Don't allow character to be entered
              e.preventDefault();
              e.stopPropagation();
              // Update name, change focus to title, and exit function
              this.updateName(e);
              $("textarea.title").focus();
              return
            }

            // Keys that a user can use as normal, even if character limit is met
            var allowedKeys = [
              8,  // DELETE
              35, // END
              36, // HOME
              37, // LEFT
              38, // UP
              39, // RIGHT
              40, // DOWN
              46,  // DEL
              17   // CTRL
            ];

            // Stop addition of more characters and show message
            if(
              // If at or greater than limit and
              currentLabel.length >= limit &&
              // key isn't a special key and
              !allowedKeys.includes(e.which) &&
              // cmd key isn't held down and
              !e.metaKey &&
              // user doesn't have some of the text selected
              !window.getSelection().toString().length
            ){
              // Don't allow character to be entered
              e.preventDefault();
              e.stopPropagation();
              // Add a tooltip if one doesn't exist yet
              if(!$(e.delegateTarget).find(".tooltip").length){
                $(e.target).tooltip({
                  placement: "top",
                  trigger: "manual",
                  title: "Limit of " + limit + " characters or fewer"
                });
              }
              // Show the tooltip
              $(e.target).tooltip('show');
            // If under the character limit, proceed as normal.
            } else {
              // Make sure there's no tooltip showing.
              $(e.delegateTarget).find(".tooltip").remove();
            }
          }
          catch(error){
            "Error limiting user input in label field, error message: " + error
          }

        },

        /**
         * Update the section label
         *
         * @param e The event triggering this method
         */
        updateName: function(e) {

          // Remove tooltip incase one was set by limitLabelInput function
          $(e.delegateTarget).find(".tooltip").remove();

          try {
            //Get the PortalSection model for this rename button
            var sectionLink   =   $(e.target).parents(this.sectionLinkContainer),
                targetLink    =   sectionLink.find(this.sectionLinks),
                sectionModel  =   sectionLink.data("model"),
                sectionView   =   sectionLink.data("view"),
                // Clean up the typed in name so it's valid for XML
                oldLabel      =   sectionModel.get("label"),
                newLabel      =   this.model.cleanXMLText(targetLink.text().trim()),
                pageOrder     =   this.model.get("pageOrder");

            // Remove the content editable attribute
            targetLink.attr("contenteditable", false);

            // If this section is an object of PortalSection model, update the label.
            if( sectionModel && PortalSection.prototype.isPrototypeOf(sectionModel) ){
              // update the label on the model
              sectionModel.set("label", newLabel);
            }
            else {
              // TODO: handle the case for non-markdown sections
            }

            // Update the name set on the link element, since it's used for setting the pageOrder option
            sectionLink.data("section-name", newLabel);

            // Update the name in the pageOrder option, if it exists
            if(pageOrder && pageOrder.length){
              this.updatePageOrder();
            }

            // Update the array of unique section labels

            // Get the position of the unique label in the list
            var indexIDs = this.sectionLabels.indexOf(sectionView.uniqueSectionLabel),
                newUniqueLabel = "";

            // Remove the old unique label so when we create a new unique label,
            // we don't consider the label that we're replacing in determining uniqueness
            if(indexIDs > -1){
              this.sectionLabels.splice(indexIDs, 1);
              newUniqueLabel = this.getUniqueSectionLabel(sectionModel);
              this.sectionLabels.splice(indexIDs, 0, newUniqueLabel)
            } else {
              newUniqueLabel = this.getUniqueSectionLabel(sectionModel);
              this.sectionLabels.push(newUniqueLabel);
            }

            // Update the label set on the view
            sectionView.uniqueSectionLabel = newUniqueLabel;

          } catch (error) {
            console.error(error);
          }
        },

        /**
         * Using the "section-name" data attribute set on each section link,
         * and the order that the links are displayed in the DOM, update the
         * pageOrder option in the portal model.
         */
        updatePageOrder: function(){
          try {
            var view = this;
            // Get the links as they are ordered in the UI
            var links = view.el.querySelectorAll(view.sectionLinksContainer + view.sortableLinksSelector),
            pageOrder = [];
            _.each(links, function(link){
              // Use the value set on section-name to re-order pages
              var label = $(link).data("section-name");
              if(label){ pageOrder.push(label) }
            });
            view.model.set("pageOrder", pageOrder);
            view.editorView.showControls();
          } catch (error) {
            console.log("Error updating the portal page order, message: " + error)
          }
        },

        /**
        * Add a new unique label to the list of unique section labels
        * (used the ensure that tab links and anchors are unique,
        * otherwise, tab switching does not work)
        */
        updateSectionLabelsList: function(newLabel){
          try {
            if( !this.sectionLabels ){
              this.sectionLabels = [];
            }
            this.sectionLabels.push(newLabel);
          } catch (error) {
            console.log("Error updating the list of unique section labels. Error message: " + error)
          }
        },

        /**
        * Shows a validation error message and adds error styling to the given elements
        * @param {jQuery} elements - The elements to add error styling and messaging to
        */
        showValidation: function(elements){
          try{
            //Get the parent elements that have ids set
            var sectionEls = elements.parents(this.sectionEls);

            //See if there is a matching section link
            for(var i=0; i<sectionEls.length; i++){

              //Get the section view attached to the section element
              var sectionView = sectionEls.data("view");

              //If a section view was found,
              if( sectionView ){
                //Find the section link that links to this section view
                var matchingLink = _.find($(this.sectionLinkContainer), function(link){
                  return $(link).data("view") == sectionView;
                });

                //Add the error class and display the error icon
                if( matchingLink ){
                  $(matchingLink).addClass("error").find(".icon.error").show();
                  //Exit the loop
                  i=sectionEls.length+1;
                }
              }
            }
          }
          catch(e){
            console.error("Error showing validation message: ", e);
          }
        },

        /**
        * Closes all the popovers in this view
        */
        closePopovers: function(){
          this.$(".popover-this").popover("hide");
        },

        /**
         * This function is called when the app navigates away from this view.
         * Any clean-up or housekeeping happens at this time.
         */
        onClose: function() {
            //Remove each subview from the DOM and remove listeners
            _.invoke(this.subviews, "remove");

            this.subviews = new Array();

            //Remove the reference to the EditorView
            this.editorView = null;
        }

      });

      return PortEditorSectionsView;

    });