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