define([
"underscore",
"jquery",
"backbone",
"models/portals/PortalModel",
"models/portals/PortalImage",
"collections/Filters",
"views/EditorView",
"views/SignInView",
"views/portals/editor/PortEditorSectionsView",
"views/portals/editor/PortEditorImageView",
"text!templates/loading.html",
"text!templates/portals/editor/portalEditor.html",
"text!templates/portals/editor/portalEditorSubmitMessage.html",
"text!templates/portals/editor/portalLoginPage.html",
], function (
_,
$,
Backbone,
Portal,
PortalImage,
Filters,
EditorView,
SignInView,
PortEditorSectionsView,
ImageEdit,
LoadingTemplate,
Template,
portalEditorSubmitMessageTemplate,
LoginTemplate,
) {
/**
* @class PortalEditorView
* @classdesc A view of a form for creating and editing DataONE Portal documents
* @classcategory Views/Portals/Editor
* @name PortalEditorView
* @extends EditorView
* @constructs
*/
var PortalEditorView = EditorView.extend(
/** @lends PortalEditorView.prototype */ {
/**
* The type of View this is
* @type {string}
*/
type: "PortalEditor",
/**
* The short name OR pid for the portal
* @type {string}
*/
portalIdentifier: "",
/**
* The PortalModel that is being edited
* @type {Portal}
*/
model: undefined,
/**
* The currently active editor section. e.g. Data, Metrics, Settings, etc.
* @type {string}
*/
activeSectionLabel: "",
/**
* When a new portal is being created, this is the label of the section that will be active when the editor first renders
* @type {string}
*/
newPortalActiveSectionLabel:
(MetacatUI.appModel.get("portalDefaults")
? MetacatUI.appModel.get("portalDefaults").newPortalActiveSectionLabel
: "") || "Settings",
/**
* References to templates for this view. HTML files are converted to Underscore.js templates
*/
template: _.template(Template),
loadingTemplate: _.template(LoadingTemplate),
loginTemplate: _.template(LoginTemplate),
// Over-ride the default editor submit message template (which is currently
// used by the metadata editor) with the portal editor version
editorSubmitMessageTemplate: _.template(
portalEditorSubmitMessageTemplate,
),
/**
* An array of Backbone Views that are contained in this view.
* @type {Backbone.View[]}
*/
subviews: [],
/**
* A reference to the PortEditorSectionsView for this instance of the PortEditorView
* @type {PortEditorSectionsView}
*/
sectionsView: null,
/**
* The text to use in the editor submit button
* @type {string}
*/
submitButtonText: "Save",
/**
* A jQuery selector for the element that the PortEditorSectionsView should be inserted into
* @type {string}
*/
portEditSectionsContainer: ".port-editor-sections-container",
/**
* A jQuery selector for the element that the portal logo image uploader
* should be inserted into
* @type {string}
*/
portEditLogoContainer: ".logo-editor-container",
/**
* A jQuery selector for links to view this portal
* @type {string}
*/
viewPortalLinks: ".view-portal-link",
/**
* A temporary name to use for portals when they are first created but don't have a label yet.
* This name should only be used in views, and never set on the model so it doesn't risk getting
* serialized and saved.
* @type {string}
*/
newPortalTempName: "new",
/**
* The events this view will listen to and the associated function to call.
* This view will inherit events from the parent class, EditorView.
* @type {Object}
*/
events: _.extend(EditorView.prototype.events, {
"focusout .basic-text": "updateBasicText",
"click .section-links-toggle-container": "toggleSectionLinks",
}),
/**
* Is executed when a new PortalEditorView is created
* @param {Object} options - A literal object with options to pass to the view
*/
initialize: function (options) {
EditorView.prototype.initialize.call(this, options);
//Reset arrays and objects set on this View, otherwise they will be shared across intances, causing errors
this.subviews = new Array();
this.sectionsView = null;
if (typeof options == "object") {
// initializing the PortalEditorView properties
this.portalIdentifier = options.portalIdentifier
? options.portalIdentifier
: undefined;
this.activeSectionLabel = options.activeSectionLabel || "";
}
},
/**
* Renders the PortalEditorView
*/
render: function () {
//Execute the superclass render() function, which will add some basic Editor functionality
EditorView.prototype.render.call(this);
$("body").addClass("Portal");
// Display a spinner to indicate loading until model is created.
this.$el.html(
this.loadingTemplate({
msg: "Retrieving portal details...",
}),
);
//Create the model
this.createModel();
// An existing portal should have a portalIdentifier already set
// from the router, that does not equal the newPortalTempName ("new"),
// plus a seriesId or label set during createModel()
if (
(this.model.get("seriesId") || this.model.get("label")) &&
this.portalIdentifier &&
this.portalIdentifier != this.newPortalTempName
) {
var view = this;
this.listenToOnce(this.model, "change:isAuthorized", function () {
if (this.model.get("isAuthorized")) {
// When an existing model has been synced render the results
view.stopListening(view.model, "sync", view.renderPortalEditor);
view.listenToOnce(view.model, "sync", view.renderPortalEditor);
// If the portal model already exists - fetch it.
view.model.fetch();
// Listens to the focus event on the window to detect when a user
// switches back to this browser tab from somewhere else
// When a user checks back, we want to check for log-in status
MetacatUI.appView.listenForActivity();
// Determine the length of time until the user's current token expires
// Asks to sign in in case of time out
MetacatUI.appView.listenForTimeout();
} else {
// generate error message
var msg = MetacatUI.appModel.get("portalEditNotAuthEditMessage");
//Show the not authorized error message
MetacatUI.appView.showAlert(
msg,
"alert-error non-fixed",
this.$el,
);
}
});
// Check if the user is Authorized to edit the portal
this.authorizeUser();
}
//If there is no portal identifier given, this is a new portal.
else {
// if the user is not signed in, display the sign in view
if (
MetacatUI.appUserModel.get("tokenChecked") &&
!MetacatUI.appUserModel.get("loggedIn")
) {
this.showSignIn();
} else {
//Check the user's quota to create a new Portal
this.listenToOnce(
MetacatUI.appUserModel,
"change:isAuthorizedCreatePortal",
function () {
if (MetacatUI.appUserModel.get("isAuthorizedCreatePortal")) {
// Start new portals on the settings tab
this.activeSectionLabel = this.newPortalActiveSectionLabel;
// Render the default model if the portal is new
this.renderPortalEditor();
} else {
//If the user doesn't have quota left, display this message
if (MetacatUI.appUserModel.get("portalQuota") == 0) {
var errorMessage = MetacatUI.appModel.get(
"portalEditNoQuotaMessage",
);
}
//Otherwise, display a more generic error message
else {
var errorMessage = MetacatUI.appModel.get(
"portalEditNotAuthCreateMessage",
);
}
//Hide the loading icon
this.hideLoading();
//Show the error message
MetacatUI.appView.showAlert(
errorMessage,
"alert-error non-fixed",
this.$el,
);
}
//Reset the isAuthorizedCreatePortal attribute
MetacatUI.appUserModel.set("isAuthorizedCreatePortal", null);
},
);
//If the user authentication hasn't been checked yet, then wait for it
if (!MetacatUI.appUserModel.get("tokenChecked")) {
this.listenTo(
MetacatUI.appUserModel,
"change:tokenChecked",
function () {
if (MetacatUI.appUserModel.get("loggedIn")) {
//Check if this user is authorized to create a new portal
MetacatUI.appUserModel.isAuthorizedCreatePortal();
}
//If the user is not logged in, show the sign in buttons
else if (!MetacatUI.appUserModel.get("loggedIn")) {
this.showSignIn();
}
},
);
return;
}
//If the user is logged in,
else if (MetacatUI.appUserModel.get("loggedIn")) {
//Check if this user is authorized to create a new portal
MetacatUI.appUserModel.isAuthorizedCreatePortal();
}
//If the user is not logged in, show the sign in buttons
else if (!MetacatUI.appUserModel.get("loggedIn")) {
this.showSignIn();
}
}
}
return this;
},
/**
* Renders the portal editor view once the portal view is created
*/
renderPortalEditor: function () {
var view = this;
//Check if this is a plus portal
if (MetacatUI.appModel.get("dataonePlusPreviewMode")) {
var sourceMN = this.model.get("datasource");
//Check if the portal source node is from the active alt repo OR is
// configured as a Plus portal.
if (
!this.model.isNew() &&
(typeof sourceMN != "string" ||
(sourceMN !=
MetacatUI.appModel.get("defaultAlternateRepositoryId") &&
!_.findWhere(
MetacatUI.appModel.get("dataonePlusPreviewPortals"),
{
datasource: sourceMN,
seriesId: this.model.get("seriesId"),
},
)))
) {
//Get the name of the source member node
var sourceMNName = "original data repository",
mnURL = "";
if (typeof sourceMN == "string") {
var sourceMNObject = MetacatUI.nodeModel.getMember(sourceMN);
if (sourceMNObject) {
sourceMNName = sourceMNObject.name;
//If there is a baseURL string
if (sourceMNObject.baseURL) {
//Parse out the origin of the baseURL string. We want to crop out the /metacat/d1/mn parts.
mnURL =
sourceMNObject.baseURL.substring(
0,
sourceMNObject.baseURL.lastIndexOf("."),
) +
sourceMNObject.baseURL.substring(
sourceMNObject.baseURL.lastIndexOf("."),
sourceMNObject.baseURL.indexOf(
"/",
sourceMNObject.baseURL.lastIndexOf("."),
),
);
}
}
}
//Show a message that the portal can be found on the repository website.
var message = $(document.createElement("h3")).addClass(
"center stripe",
);
message.text(
"The " +
this.model.get("name") +
" " +
MetacatUI.appModel.get("portalTermSingular") +
" can be edited in the ",
);
if (mnURL) {
message.append(
$(document.createElement("a"))
.attr("href", mnURL)
.attr("target", "_blank")
.text(sourceMNName),
);
} else {
message.append(sourceMNName);
}
this.$el.html(message);
return;
}
}
// Add the template to the view and give the body the "Editor" class
this.$el.html(
this.template({
name: this.model.get("name"),
submitButtonText: this.submitButtonText,
primaryColor: this.model.get("primaryColor"),
secondaryColor: this.model.get("secondaryColor"),
accentColor: this.model.get("accentColor"),
primaryColorTransparent: this.model.get("primaryColorTransparent"),
secondaryColorTransparent: this.model.get(
"secondaryColorTransparent",
),
accentColorTransparent: this.model.get("accentColorTransparent"),
}),
);
//Render the editor controls
this.renderEditorControls();
//Hide the Save controls
this.hideControls();
//Remove the rendering class from the body element
$("body").removeClass("rendering");
// On mobile where the section-links-toggle-container is set to fixed,
// hide the portal navigation element when user scrolls down,
// show again when the user scrolls up.
MetacatUI.appView.prevScrollpos = window.pageYOffset;
$(window).off("scroll");
$(window).scroll(_.throttle(view.handleScroll, 400));
// Functions to perform when the window is resized
var onResize = function () {
// Auto-resize the portal title
$("textarea.portal-title").trigger("windowResize");
// Ensure that the menu is always shown when switching from mobile to full width
view.toggleSectionLinks();
};
$(window).off("resize");
$(window).resize(_.throttle(onResize, 400));
// Auto-resize the height of the portal title field on user-input and on
// window resize events.
this.$("textarea.portal-title")
.each(function () {
this.style.height = "0px"; // note: textfield MUST have a min-height set
this.style.height = this.scrollHeight + "px";
})
.on("input windowResize", function () {
this.style.height = "0px"; // note: textfield MUST have a min-height set
this.style.height = this.scrollHeight + "px";
});
// Get the portal identifier
// or set it to a default value in the case that it's a new portal
var portalIdentifier = this.portalIdentifier;
if (!portalIdentifier) {
portalIdentifier = this.newPortalTempName;
}
//Create a view for the editor sections
this.sectionsView = new PortEditorSectionsView({
model: this.model,
activeSectionLabel: this.activeSectionLabel,
newPortalTempName: this.newPortalTempName,
});
//Save the PortEditorSectionsView as a subview
this.subviews.push(this.sectionsView);
//Attach a reference to this view
this.sectionsView.editorView = this;
//Add the view element to this view
this.$(this.portEditSectionsContainer).html(this.sectionsView.el);
//Render the sections view
this.sectionsView.render();
//If this portal is a free trial DataONE Plus portal, then display some messaging
this.renderSubscriptionInfo();
//Show the required fields for this editor
this.renderRequiredIcons(
MetacatUI.appModel.get("portalEditorRequiredFields"),
);
// Insert the logo editor
this.renderLogoEditor();
// When the collection definition is changed, show the Save button
var definition = this.model.get("definition"),
definitionEvents = "update change";
this.stopListening(definition, definitionEvents);
this.listenTo(definition, definitionEvents, function (model, record) {
// Don't show the controls for the addition of an empty filter model, or the
// controls will show right away when we add a new blank query rule
if (
record &&
record.changes &&
record.changes.added &&
record.changes.added.length
) {
if (
record.changes.added[0].isEmpty &&
record.changes.added[0].isEmpty()
) {
return;
}
}
this.showControls();
});
// On mobile, hide section tabs a moment after page loads so
// users notice where they are
setTimeout(function () {
view.toggleSectionLinks();
}, 700);
//Show a link to view the portal, if it is not a new portal
if (!this.model.isNew()) {
var viewURL =
MetacatUI.root +
"/" +
MetacatUI.appModel.get("portalTermPlural") +
"/" +
portalIdentifier;
//Update the view URL in any other portal view links
this.$(this.viewPortalLinks).attr("href", viewURL).show();
} else {
//Remove the href attribute and hide the link
this.$(this.viewPortalLinks).attr("href", "").hide();
}
},
/**
* Create a PortalModel object
*/
createModel: function () {
// Look up the portal document seriesId by its registered name if given
if (
this.portalIdentifier &&
this.portalIdentifier != this.newPortalTempName
) {
// Create a new portal model with the identifier
this.model = new Portal({
label: this.portalIdentifier,
edit: true,
});
// Save the original label in case a user changes it. During URL
// validation, the original label will always be shown as available.
// TODO: if user navigates to portal using a SID or PID, we will need
// to get the matching label and then save it to the model
this.model.set("originalLabel", this.portalIdentifier);
// Otherwise, create a new portal
} else {
// Create a new, default portal model
this.model = new Portal({
//Set the isNew attribute so the model will execute certain functions when a Portal is new
isNew: true,
rightsHolder: MetacatUI.appUserModel.get("username"),
isAuthorized_read: true,
isAuthorized_write: true,
isAuthorized_changePermission: true,
edit: true,
});
}
// set listeners on the new model
this.setListeners();
},
/**
* The authorizeUser function checks if the current user is authorized
* to edit the given PortalModel. If not, a message is displayed and
* the view doesn't render anything else.
*
* If the user isn't logged in at all, don't check for authorization and
* display a message and login button.
*/
authorizeUser: function () {
//If the user authentication hasn't been checked yet, wait for it to finish.
if (!MetacatUI.appUserModel.get("tokenChecked")) {
this.listenToOnce(
MetacatUI.appUserModel,
"change:tokenChecked",
this.authorizeUser,
);
return;
}
//If the user authentication has been checked and they are not logged in, then display the Sign In buttons
else if (
MetacatUI.appUserModel.get("tokenChecked") &&
!MetacatUI.appUserModel.get("loggedIn")
) {
//Remove the loading message
this.hideLoading();
// show the sign in view
this.showSignIn();
return;
} else {
//If the seriesId hasn't been found yet, but we have the label
if (
!this.model.get("seriesId") &&
!this.model.get("latestVersion") &&
this.model.get("label")
) {
//When the seriesId or latest pid is found, come back to this function
this.listenToOnce(
this.model,
"change:seriesId",
this.authorizeUser,
);
this.listenToOnce(
this.model,
"latestVersionFound",
this.authorizeUser,
);
//If the portal isn't found, display a 404 message
this.listenToOnce(this.model, "notFound", this.showNotFound);
//Get the seriesId or latest pid
this.model.getSeriesIdByLabel();
return;
} else {
//Remove the listeners for the seriesId and latest pid
this.stopListening(
this.model,
"change:seriesId",
this.authorizeUser,
);
this.stopListening(
this.model,
"latestVersionFound",
this.authorizeUser,
);
}
// checking for the write Permission
this.model.checkAuthority("write");
}
},
/**
* Hides the loading
*/
hideLoading: function () {
// Find the loading object and remove it.
if (this.$el.find(".loading")) {
this.$el.find(".loading").remove();
}
},
/**
* toggleSectionLinks - show or hide the section links. Used for the
* mobile/small screen view of the portal.
*/
toggleSectionLinks: function (e) {
try {
// Don't close the menu if the user clicked the dropdown for the rename/delete menu,
// or something within that menu. Also do not close when the user clicked to update
// the tab name in a content editable element.
if (e && e.target) {
if (
$(e.target).closest(".section-menu-link").length ||
$(e.target).closest(".dropdown-menu").length ||
$(e.target).attr("contentEditable") == "true"
) {
return;
}
}
var tabs = this.$("#portal-section-tabs");
if (!tabs) {
return;
}
// Only toggle the section links on mobile. On mobile, the
// ".show-sections-toggle" is visible.
if (this.$(".show-sections-toggle").is(":visible")) {
tabs.slideToggle();
// If not on mobile, the section tabs should always be visible
} else {
tabs.show();
}
} catch (e) {
console.error("Failed to toggle section links, error message: " + e);
}
},
/**
* renderLogoEditor - Creates a new PortalImage model for the portal logo if
* one doesn't exist already, then inserts an ImageEdit view into the
* portEditLogoContainer.
*/
renderLogoEditor: function () {
try {
// If the portal has no logo, add the default model for one
if (!this.model.get("logo")) {
this.model.set(
"logo",
new PortalImage({
label: "logo",
nodeName: "logo",
}),
);
}
// Add the image view (incl. uploader) for the portal logo
this.logoEdit = new ImageEdit({
model: this.model.get("logo"),
imageUploadInstructions: "Drag & drop a logo or click to upload",
imageWidth: 100,
imageHeight: 100,
minWidth: 64,
minHeight: 64,
maxHeight: 300,
maxWidth: 300,
nameLabel: false,
urlLabel: false,
imageTagName: "img",
removeButton: false,
});
this.$(this.portEditLogoContainer).append(this.logoEdit.el);
this.logoEdit.render();
this.logoEdit.editorView = this;
this.listenTo(this.model.get("logo"), "change", this.showControls);
} catch (e) {
console.error(
"Logo editor view could not be rendered. Error message: " + e,
);
}
},
/**
* When a simple text input field loses focus, the corresponding model
* attribute is updated with the value from the input field
*
* @param {Event} [e] - The focusout event
*/
updateBasicText: function (e) {
if (!e) return false;
//Get the category, new value, and model
var category = $(e.target).attr("data-category"),
value = $(e.target).val(),
model = $(e.target).data("model") || this.model;
//We can't update anything without a category
if (!category) return false;
//Clean up the value string so it's valid for XML
value = this.model.cleanXMLText(value);
//If the value is an empty string,
if (typeof value == "string" && !value.length) {
//Remove the value from the input
$(e.target).val("");
}
//If the value is only spaces,
else if (typeof value == "string" && !value.trim().length) {
//Remove the value from the input
$(e.target).val("");
//Update the model as if this is an empty string
value = "";
}
//Get the current value
var currentValue = model.get(category);
//Insert the new value into the array
if (Array.isArray(currentValue)) {
//Find the position this text input is in
var position = $(e.target)
.parents("div.text-container")
.first()
.children("div")
.index($(e.target).parent());
//Set the value in that position in the array
currentValue[position] = value;
//Set the changed array on this model
model.set(category, currentValue);
model.trigger("change:" + category);
}
//Update the model if the current value is a string
else if (typeof currentValue == "string" || !currentValue) {
model.set(category, value);
model.trigger("change:" + category);
}
//TODO: Add another blank text input (write addBasicText function)
// if($(e.target).is(".new") && value != '' && category != "title"){
// $(e.target).removeClass("new");
// this.addBasicText(e);
// }
},
/**
* When the object is saved successfully, tell the user.
* @param {object} savedObject - the object that was successfully saved
*/
saveSuccess: function (savedObject) {
var identifier =
this.model.get("label") ||
this.model.get("seriesId") ||
this.model.get("id"),
viewURL =
MetacatUI.root +
"/" +
MetacatUI.appModel.get("portalTermPlural") +
"/" +
identifier;
var message = this.editorSubmitMessageTemplate({
messageText: "Your changes have been submitted.",
viewURL: viewURL,
buttonText:
"View your " + MetacatUI.appModel.get("portalTermSingular"),
});
MetacatUI.appView.showAlert(message, "alert-success", this.$el, null, {
remove: true,
});
//Update the view URL in any other portal view links
this.$(this.viewPortalLinks).attr("href", viewURL).show();
this.hideSaving();
this.removeValidation();
// Update the path in case the user selected a new portal label
this.sectionsView.updatePath();
// Reset the original label (note: this MUST occur AFTER updatePath())
this.model.set("originalLabel", this.model.get("label"));
},
/**
* When the Portal model has been flagged as invalid, show the validation error messages
*/
showValidation: function () {
//First clear all the error messaging
this.removeValidation();
var errors = this.model.validationError;
_.each(
errors,
function (errorMsg, category) {
var categoryEls = this.$("[data-category='" + category + "']");
//The label category is unique, because it is duplicated in the PortalImage, which can cause bugs
if (category == "label") {
categoryEls = this.$(
".change-label-container [data-category='label']",
);
var settingsView = _.findWhere(this.sectionsView.subviews, {
type: "PortEditorSettings",
});
//Show the "change label" elements so the validation will appear
settingsView.changeLabel();
}
//Get the elements that have views attached to them
var elsWithViews = _.filter(categoryEls, function (el) {
return (
$(el).data("view") &&
$(el).data("view").showValidation &&
!$(el).data("view").isNew
);
});
//If at least one element of this category has a view,
if (elsWithViews.length) {
//Use the view's showValidation function, if it exists.
_.each(elsWithViews, function (el) {
var view = $(el).data("view");
if (view && view.showValidation) {
view.showValidation();
}
});
} else {
//Show the validation message
this.showValidationMessage(categoryEls, errorMsg);
}
},
this,
);
if (errors) {
MetacatUI.appView.showAlert(
"Provide the content flagged below before submitting.",
"alert-error",
this.$el,
null,
{
remove: true,
},
);
//Hide the saving styling
this.hideSaving();
}
},
/**
* 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
* @param {string} errorMsg - The error message to display
*/
showValidationMessage: function (elements, errorMsg) {
//Show the error message
elements.filter(".notification").addClass("error").text(errorMsg);
//Add the error class to inputs
var inputs = elements.filter("textarea, input").addClass("error");
//Show the validation message in the portal sections
if (this.sectionsView) {
this.sectionsView.showValidation(elements);
}
},
/**
* Removes all the validation error styling and messaging from this view
*/
removeValidation: function () {
EditorView.prototype.removeValidation.call(this);
this.$(
".section-link-container.error, input.error, textarea.error",
).removeClass("error");
},
/**
* Show Sign In buttons
*/
showSignIn: function () {
// Messsage if the user is trying to edit an existing portal
var title = "Sign in with your ORCID to edit this portal";
// Message to create a portal if the portal is new
if (this.model.get("isNew")) {
title =
"<strong>You're one step away from the portal builder</strong><br>Start by signing in with your ORCID";
}
this.$el.html(
this.loginTemplate({
title: title,
portalInfoLink: MetacatUI.appModel.get("portalInfoURL"),
portalImageSrc:
MetacatUI.root + "/img/portals/portal-data-page-example.png",
altText:
"Screen shot of a portal data page for a climate research lab. The page shows a search bar, customized filters, and a map of the the geographic area the data covers.",
}),
);
},
/**
* The DataONE Plus Subscription if fetched from Bookkeeper and the status of the
* Subscription is rendered on the page.
* Subviews in this view should have their own renderSubscriptionInfo() function
* that inserts subscription details into the subview.
*/
renderSubscriptionInfo: function () {
if (MetacatUI.appModel.get("enableBookkeeperServices")) {
if (
MetacatUI.appUserModel.get("loggedIn") &&
MetacatUI.appUserModel.get("dataoneSubscription")
) {
//Show the free trial message for this portal, if the subscription is in a free trial
var subscription = MetacatUI.appUserModel.get(
"dataoneSubscription",
),
isFreeTrial = false;
//If the Subscription is in free trial mode
if (subscription && subscription.isTrialing()) {
if (MetacatUI.appModel.get("dataonePlusPreviewMode")) {
//If this portal is not in the configured list of Plus portals
var trialExceptions = MetacatUI.appModel.get(
"dataonePlusPreviewPortals",
);
isFreeTrial = !_.findWhere(trialExceptions, {
seriesId: this.model.get("seriesId"),
});
} else {
isFreeTrial = true;
}
if (isFreeTrial) {
//Show a free trial message in the editor footer
var freeTrialMessage =
"This " +
MetacatUI.appModel.get("portalTermSingular") +
" is a free preview of " +
MetacatUI.appModel.get("dataonePlusName");
var messageEl = $(document.createElement("span"))
.addClass("free-trial-message")
.text(freeTrialMessage)
.prepend(
$(document.createElement("i")).addClass(
"dataone-plus-icon-container",
),
);
this.$("#editor-footer").prepend(messageEl);
// Update the label element to randomly generated label
// And disable the input
var labelEL = $(".label-input-text");
labelEL.val(this.model.get("label"));
//When the Portal Model label is changed, update the input
this.listenTo(this.model, "change:label", function () {
$(".label-input-text").val(this.model.get("label"));
});
labelEL.attr("disabled", "disabled");
//Remove the "Change URL" button that toggles the label input
this.$(".btn.change-label").remove();
// Show edit label message if the edit button is disabled
var editLabelMessage =
"Create a custom " +
MetacatUI.appModel.get("portalTermSingular") +
" name for the URL when your free preview of " +
"<i class='dataone-plus-icon-container'></i>" +
MetacatUI.appModel.get("dataonePlusName") +
" ends.";
var messageContainer = this.$(".label-container .notification")
.html(editLabelMessage)
.addClass("free-trial");
if (!messageContainer.is(":visible")) {
messageContainer
.detach()
.appendTo(this.$(".change-label-container"));
}
//Insert the DataONE Plus icon
var viewRef = this;
require(["text!templates/dataonePlusIcon.html"], function (
iconTemplate,
) {
viewRef.$(".dataone-plus-icon-container").html(iconTemplate);
});
}
}
} else {
this.listenTo(
MetacatUI.appUserModel,
"change:dataoneSubscription",
this.renderSubscriptionInfo,
);
}
}
},
/**
* @inheritdoc
*/
isAccessPolicyEditEnabled: function () {
if (!MetacatUI.appModel.get("allowAccessPolicyChanges")) {
return false;
}
if (!MetacatUI.appModel.get("allowAccessPolicyChangesPortals")) {
return false;
}
let limitedTo = MetacatUI.appModel.get(
"allowAccessPolicyChangesPortalsForSubjects",
);
if (Array.isArray(limitedTo) && limitedTo.length) {
return (
_.intersection(
limitedTo,
MetacatUI.appUserModel.get("allIdentitiesAndGroups"),
).length > 0
);
} else {
return true;
}
},
/**
* If the given portal doesn't exist, display a Not Found message.
*/
showNotFound: function () {
this.hideLoading();
var notFoundMessage = $(document.createElement("p")).text(
"The " + MetacatUI.appModel.get("portalTermSingular") + " ",
);
notFoundMessage
.append(
$(document.createElement("span")).text(
this.model.get("label") || this.portalIdentifier,
),
)
.append($(document.createElement("span")).text(" doesn't exist."));
MetacatUI.appView.showAlert(
notFoundMessage,
"alert-error non-fixed",
this.$el,
undefined,
{ remove: true },
);
},
/**
* This function is called whenever the window is scrolled.
*/
handleScroll: function () {
try {
var menu = $(".section-links-toggle-container")[0],
editorFooter = this.$("#editor-footer")[0],
editorFooterHeight = editorFooter ? editorFooter.offsetHeight : 0,
menuHeight = menu ? menu.offsetHeight : 0,
hiddenHeight = menuHeight * -1 + editorFooterHeight,
currentScrollPos = window.pageYOffset;
if (!menu) {
return;
}
if (MetacatUI.appView.prevScrollpos >= currentScrollPos) {
// when scrolling upward
menu.style.bottom = editorFooterHeight + "px";
} else {
// when scrolling downward
menu.style.bottom = hiddenHeight + "px";
}
MetacatUI.appView.prevScrollpos = currentScrollPos;
} catch (error) {
console.log(
"There was an error adjusting menu position on scroll. Error details: " +
error,
);
}
},
/**
* @inheritdoc
*/
onClose: function () {
//Call the superclass onClose() function
EditorView.prototype.onClose.call(this);
//Remove the Portal class from the body element
$("body").removeClass("Portal");
//Remove the scroll and resize listener
$(window).off("scroll");
$(window).off("resize");
//Close and remove all of the subviews
_.invoke(this.subviews, "onClose");
_.invoke(this.subviews, "remove");
//Reset the subviews array
this.subviews = new Array();
//Reset the sectionsView reference
this.sectionsView = null;
},
},
);
return PortalEditorView;
});