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

define(["jquery",
    "underscore",
    "backbone",
    "collections/Filters",
    "collections/SolrResults",
    "views/PagerView",
    "text!templates/portals/portalList.html"],
    function($, _, Backbone, Filters, SearchResults, PagerView, Template){

      /**
      * @class PortalListView
      * @classdesc A view that shows a list of Portals
      * @classcategory Views/Portals
      * @extends Backbone.View
      * @screenshot views/portals/PortalListView.png
      * @constructor
      */
      return Backbone.View.extend(
        /** @lends PortalListView.prototype */{

        /**
        * An array of Filter models  or Filter model JSON to use in the query.
        * If not provided, a default query will be used.
        * @type {Filter[]}
        */
        filters: null,

        /**
        * A SolrResults collection that contains the results of the search for the portals
        * @type {SolrResults}
        */
        searchResults: new SearchResults(),

        /**
        * A comma-separated list of Solr index fields to retrieve when searching for portals
        * @type {string}
        * @default "id,seriesId,title,formatId,label,logo"
        */
        searchFields: "id,seriesId,title,formatId,label,logo,datasource,writePermission,changePermission,rightsHolder,abstract",

        /**
        * The number of portals to dispaly per page
        * @default 10
        * @type {number}
        */
        numPortalsPerPage: 10,

        /**
        * The number of portals to retrieve and render in this view
        * @default 100
        * @type {number}
        */
        numPortals: 100,

        /**
        * An array of additional SolrResult models for portals that will be displayed
        * in this view in addition to the SolrResults found as a result of the search.
        * These could be portals that wouldn't otherwise be found by a search but should be displayed anyway.
        * @type {SolrResult[]}
        */
        additionalPortalsToDisplay: [],

        /**
        * The message to display when there are no portals in this list
        * @type {string}
        */
        noResultsMessage: "You haven't created or have access to any " + MetacatUI.appModel.get("portalTermPlural") + " yet.",

        /**
        * A jQuery selector for the element that the list should be inserted into
        * @type {string}
        */
        listContainer: ".portal-list-container",
        /**
        * A jQuery selector for the element that the Create Portal should be inserted into
        * @type {string}
        */
        createBtnContainer: ".create-btn-container",

        /**
        * References to templates for this view. HTML files are converted to Underscore.js templates
        */
        template: _.template(Template),

        /**
        * Initializes a new view
        */
        initialize: function(){
          //Create a new SearchResults collection
          this.searchResults = new SearchResults();
        },

        /**
        * Renders the list of portals
        */
        render: function(){

          try{

            //If the "my portals" feature is disabled, exit now
            if(MetacatUI.appModel.get("showMyPortals") === false){
              return;
            }

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

            //If there are no given filters, create default ones
            if( !this.filters ){
              //Create search filters for finding the portals
              var filters = new Filters();

              //Filter datasets that the user has ownership of
              filters.addWritePermissionFilter();

              this.filters = filters;
            }
            //If the filters set on this view is an array of JSON, add it to a Filters collection
            else if( this.filters.length && !Filters.prototype.isPrototypeOf(this.filters) ){
              //Create search filters for finding the portals
              var filters = new Filters();

              filters.add( this.filters );

              this.filters = filters;
            }
            //If there is an empty array, create a new Filters collection
            else if( !this.filters.length ){
              this.filters = new Filters();
            }

            //Get the search results and render them
            this.getSearchResults();

            //Display any additional portals in the list that have been passed to
            // the view directly.
            _.each(this.additionalPortalsToDisplay, function(searchResult){
              //Get the list container element
              var listContainer = this.$(this.listContainer);

              //Remove any 'loading' elements before adding items to the list
              listContainer.find(".loading").remove();

              //Create a list item element and add the search result element
              // to the list container
              listContainer.append(this.createListItem(searchResult));
            }, this);

            if( this.additionalPortalsToDisplay.length ){
              //While the search is being sent for the other portals in this list,
              // show a loading sign underneath the additional portals we just displayed.
              var loadingListItem = this.createListItem();
              loadingListItem.html("<td class='loading subtle' colspan='4'>Loading more " +
                                     MetacatUI.appModel.get("portalTermPlural") + "...</td>");
              this.$(this.listContainer).append(loadingListItem);
            }

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

        },

        /**
        * Queries for the portal objects using the SearchResults collection
        */
        getSearchResults: function(){

          try{

            //Filter by the portal format ID
            this.filters.add({
              fields: ["formatId"],
              values: ["dataone.org/portals"],
              matchSubstring: true,
              exclude: false
            });

            //Filter datasets by their ownership
            this.filters.add({
              fields: ["obsoletedBy"],
              values: ["*"],
              matchSubstring: false,
              exclude: true
            });

            //Get 100 rows
            this.searchResults.rows = this.numPortals;

            //The fields to return
            this.searchResults.fields = this.searchFields;

            //Set the query service URL
            try{
              if( MetacatUI.appModel.get("defaultAlternateRepositoryId") ){
                var mnToQuery = _.findWhere( MetacatUI.appModel.get("alternateRepositories"), { identifier: MetacatUI.appModel.get("defaultAlternateRepositoryId") } );
                if( mnToQuery ){
                  this.searchResults.queryServiceUrl = mnToQuery.queryServiceUrl;
                }
              }
            }
            catch(e){
              console.error("Could not get active alt repo. ", e);
            }

            //Set the query on the SearchResults
            this.searchResults.setQuery( this.filters.getQuery() );

            //Listen to the search results collection and render the results when the search is complete
            this.listenToOnce( this.searchResults, "reset", this.renderList );
            //Listen to the search results collection for errors
            this.listenToOnce( this.searchResults, "error", this.showError );

            //Get the first page of results
            this.searchResults.toPage(0);
          }
          catch(e){
            this.showError();
            console.error("Failed to fetch the SearchResults for the PortalsList: ", e);
          }
        },

        /**
        * Renders each search result from the SolrResults collection
        */
        renderList: function(){

          try{

            //Get the list container element
            var listContainer = this.$(this.listContainer);

            //If no search results were found, display a message
            if( (!this.searchResults || !this.searchResults.length) && !this.additionalPortalsToDisplay.length){
              var row = this.createListItem();
              row.html("<div class='no-results'>" + this.noResultsMessage + "</div>");
              listContainer.html(row);

              //Add a "Create" button to create a new portal
              this.renderCreateButton();

              return;
            }

            //Remove any 'loading' elements before adding items to the list
            listContainer.find(".loading").remove();

            //Iterate over each search result and render it
            this.searchResults.each(function(searchResult){

              //Create a list item element and add the search result element
              // to the list container
              listContainer.append(this.createListItem(searchResult));

            }, this);

            //Add a "Create" button to create a new portal
            this.renderCreateButton();

            // Create a pager for this list if there are many portals
            if( this.$(".portals-list-entry").length > this.numPortalsPerPage ){
              var pager = new PagerView({
                  pages: this.$(".portals-list-entry"),
                  itemsPerPage: this.numPortalsPerPage
              });

              this.$el.append(pager.render().el);
            }

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

            this.showError();

          }

        },

        /**
        * Creates a table row for the given portal SolrResult model
        * @param {SolrResult} - The SolrResult model that represent the portal
        * @return {Element}
        */
        createListItem: function(searchResult){

          try{
            var listItem = $(document.createElement("div")).addClass("portals-list-entry");

            if( searchResult && typeof searchResult.get == "function" ){

              //Don't render a list item for a portal that is already there
              if( this.$("tr[data-seriesId='" + searchResult.get("seriesId") + "']").length ){
                return listItem;
              }

              //Add an id to the list element
              listItem.attr("data-seriesId", searchResult.get("seriesId"));

              //Create a logo image
              var logoImg = "";
              var logoDiv = "";

              // Add link to the portal to the list item
              var link = $(document.createElement("a"))
                          .attr("href", MetacatUI.root + "/" + MetacatUI.appModel.get("portalTermPlural")
                          + "/" + encodeURIComponent((searchResult.get("label") || searchResult.get("seriesId") || searchResult.get("id"))) );

              if( searchResult.get("logo")) {
                if( !searchResult.get("logo").startsWith("http") ){

                  var urlBase = "";

                  //If there are alt repos configured, use the datasource obbject service URL
                  if( MetacatUI.appModel.get("alternateRepositories").length && searchResult.get("datasource") ){
                    var sourceMN = _.findWhere(MetacatUI.appModel.get("alternateRepositories"), { identifier: searchResult.get("datasource") });
                    if( sourceMN ){
                      urlBase = sourceMN.objectServiceUrl;
                    }
                  }

                  if( !urlBase ){
                    // use the resolve service if there is no object service url
                    // (e.g. in DataONE theme)
                    urlBase = MetacatUI.appModel.get("objectServiceUrl") ||
                              MetacatUI.appModel.get("resolveServiceUrl");
                  }

                  searchResult.set("logo", urlBase + searchResult.get("logo") );
                }

                var logoImg = $(document.createElement("img"))
                          .attr("src", searchResult.get("logo"))
                          .attr("alt", searchResult.get("title") + " logo");
                var logoLink = link.clone().append(logoImg);

                logoDiv = $(document.createElement("div"))
                          .addClass("portal-logo")
                          .append(logoLink);

              } else {
                // Create an empty <div>, as no portal image is available.
                logoDiv = $(document.createElement("div"))
                          .addClass("portal-logo");
              }

              var portalTitle = $(document.createElement("h5"))
                                .addClass("portal-title")
                                .text(searchResult.get("title"));
              var titleLink = link.clone().append(portalTitle);

              var descriptionText = searchResult.get("abstract") || "",
                  maxLength = window.innerWidth < 800 ? 150 : 300;
              if( descriptionText.length > maxLength ){
                descriptionText = descriptionText.substr(0, maxLength);
                descriptionText = descriptionText.substr(0, Math.min(descriptionText.length, descriptionText.lastIndexOf(" ")));
                descriptionText += "...";
              }
              var description = $(document.createElement("div"))
                                .addClass("portal-description")
                                .append( $(document.createElement("p"))
                                          .text(descriptionText) );

              var portalInfo = $(document.createElement("div"))
                                 .addClass("portal-info")
                                 .append(titleLink, description);

              var editDiv = $(document.createElement("div"))
                            .addClass("portal-edit-link")
                            .addClass("controls");

              //Add all the elements to the row
              listItem.append(logoDiv, portalInfo, editDiv);

              //Construct an array of ownership subjects
              var wPermission = searchResult.get("writePermission"),
                  cPermission = searchResult.get("changePermission"),
                  rightsHolder = searchResult.get("rightsHolder");
              var owners = [];

              [wPermission, cPermission, rightsHolder].forEach( subjects => {
                if( typeof subjects == "string" ){
                  owners.push(subjects);
                }
                else if( Array.isArray(subjects) ){
                  owners = owners.concat(subjects);
                }
              });

              //Render an Edit button
              if ( MetacatUI.appUserModel.hasIdentityOverlap(owners) ){
                  //Create an Edit buttton
                  var editButton = $(document.createElement("a")).attr("href",
                               MetacatUI.root + "/edit/"+ MetacatUI.appModel.get("portalTermPlural") +"/" + encodeURIComponent((searchResult.get("label") || searchResult.get("seriesId") || searchResult.get("id"))) )
                               .text("Edit")
                               .addClass("btn edit");
                  editDiv.append(editButton);
              }

            }

            //Return the list item
            return listItem;
          }
          catch(e){
            console.error(e);
            return "";
          }
        },

        /**
        * Renders a "Create" button for the user to create a new portal
        */
        renderCreateButton: function(){
          try{

            //If the authorization hasn't been checked yet
            if( MetacatUI.appUserModel.get("isAuthorizedCreatePortal") !== true &&
                MetacatUI.appUserModel.get("isAuthorizedCreatePortal") !== false ){
              //Check is this user is authorized to create a new portal
              this.listenToOnce( MetacatUI.appUserModel, "change:isAuthorizedCreatePortal", this.renderCreateButton);
              MetacatUI.appUserModel.isAuthorizedCreatePortal();
            }
            else{

              //Create a New portal buttton
              var createButton = $(document.createElement("a"))
                                 .addClass("btn btn-primary")
                                 .append( $(document.createElement("i")).addClass("icon icon-plus icon-on-left"),
                                   "New " + MetacatUI.appModel.get('portalTermSingular'));

              var isNotAuthorizedNoBookkeeper   = !MetacatUI.appModel.get("enableBookkeeperServices") &&
                                                   MetacatUI.appUserModel.get("isAuthorizedCreatePortal") === false,
                  reachedLimitWithBookkeeper    = MetacatUI.appModel.get("enableBookkeeperServices") &&
                                                  MetacatUI.appUserModel.get("isAuthorizedCreatePortal") === false,
                  reachedLimitWithoutBookkeeper = !MetacatUI.appModel.get("enableBookkeeperServices") &&
                                                   MetacatUI.appModel.get("portalLimit") <= this.searchResults.length;

              //If creating portals is disabled in the entire app, or is only limited to certain groups,
              // then don't show the Create button.
              if( isNotAuthorizedNoBookkeeper ){
                return;
              }
              //If creating portals is enabled, but this person is unauthorized because of Bookkeeper info,
              // then show the Create button as disabled.
              else if( reachedLimitWithBookkeeper || reachedLimitWithoutBookkeeper ){

                 //Disable the button
                 createButton.addClass("disabled");

                 //Add the create button to the view
                 this.$(this.createBtnContainer).html(createButton);

                 var message = "You've already reached the " + MetacatUI.appModel.get("portalTermSingular") +
                               " limit for your ";

                 if( MetacatUI.appModel.get("enableBookkeeperServices") ){
                   message += MetacatUI.appModel.get("dataonePlusName");

                   if( MetacatUI.appModel.get("dataonePlusPreviewMode") ){
                     message += " free preview. ";
                   }
                   else{
                     message += " subscription. ";
                   }

                   var portalQuotas = MetacatUI.appUserModel.getQuotas("portal");
                   if( portalQuotas.length ){
                     message += "(" + portalQuotas[0].get("softLimit") + " " +
                                ((portalQuotas[0].get("softLimit") > 1)? MetacatUI.appModel.get("portalTermPlural") : MetacatUI.appModel.get("portalTermSingular")) + ")";
                   }

                   message += " Contact us to upgrade your subscription.";

                 }
                 else{
                   message += " account. ";

                   var portalLimit = MetacatUI.appModel.get("portalLimit");
                   if( portalLimit > 0 ){
                     message += "(" + portalLimit + " " +
                                ((portalLimit > 1)? MetacatUI.appModel.get("portalTermPlural") : MetacatUI.appModel.get("portalTermSingular")) +
                                ")"
                   }
                 }

                 //Add the tooltip to the button
                 createButton.tooltip({
                   placement: "top",
                   trigger: "hover click focus",
                   delay: {
                     show: 500
                   },
                   title: message
                 });
              }
              else{

                //Add the link URL to the button
                createButton.attr("href", MetacatUI.root + "/edit/" + MetacatUI.appModel.get("portalTermPlural"))

                //Add the create button to the view
                this.$(this.createBtnContainer).html(createButton);
              }

              //Reset the isAuthorizedCreatePortal attribute
              MetacatUI.appUserModel.set("isAuthorizedCreatePortal", null);
            }
          }
          catch(e){
            console.error(e);
          }
        },

        /**
        * Displays an error message when rendering this view has failed.
        */
        showError: function(){

          //Remove the loading elements
          this.$(this.listContainer).find(".loading").remove();

          if( this.$(this.listContainer).children("tr").length == 0 ){

            //Show an error message
            MetacatUI.appView.showAlert(
              "Something went wrong while getting this list of portals.",
              "alert-error",
              this.$(this.listContainer));
          }
        }

      });

    });