"use strict";
define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
/**
* @class UIRouter
* @classdesc MetacatUI Router
* @classcategory Router
* @extends Backbone.Router
* @constructor
*/
var UIRouter = Backbone.Router.extend(
/** @lends UIRouter.prototype */ {
routes: {
"": "renderIndex", // the default route
"about(/:anchorId)(/)": "renderAbout", // about page
"help(/:page)(/:anchorId)(/)": "renderHelp",
"tools(/:anchorId)(/)": "renderTools", // tools page
"data/my-data(/page/:page)(/)": "renderMyData", // data search page
"data(/mode=:mode)(/query=:query)(/page/:page)(/)": "renderData", // data search page
"data/my-data(/)": "renderMyData",
"profile(/*username)(/s=:section)(/s=:subsection)(/)": "renderProfile",
"my-profile(/s=:section)(/s=:subsection)(/)": "renderMyProfile",
"logout(/)": "logout", // logout the user
"signout(/)": "logout", // logout the user
"signin(/)": "renderSignIn", // signin the user
"signinsuccess(/)": "renderSignInSuccess",
"signinldaperror(/)": "renderLdapSignInError",
"signinLdap(/)": "renderLdapSignIn",
"signinSuccessLdap(/)": "renderLdapSignInSuccess",
"signin-help": "renderSignInHelp", //The Sign In troubleshotting page
"share(/*pid)(/)": "renderEditor", // registry page
"submit(/*pid)(/)": "renderEditor", // registry page
"quality(/s=:suiteId)(/:pid)(/)": "renderMdqRun", // MDQ page
"api(/:anchorId)(/)": "renderAPI", // API page
"edit/:portalTermPlural(/:portalIdentifier)(/:portalSection)(/)":
"renderPortalEditor",
drafts: "renderDrafts",
},
helpPages: {
search: "searchTips",
defaultPage: "searchTips",
},
initialize: function () {
// Add routes to portal dynamically using the appModel portal term
var portalTermPlural = MetacatUI.appModel.get("portalTermPlural");
this.route(
portalTermPlural + "(/:portalId)(/:portalSection)(/)",
["portalId", "portalSection"],
this.renderPortal,
);
this.listenTo(
Backbone.history,
"routeNotFound",
this.navigateToDefault,
);
// This route handler replaces the route handler we had in the
// routes table before which was "view/*pid". The * only finds URL
// parts until the ? but DataONE PIDs can have ? in them so we need
// to make this route more inclusive.
this.route(/^view\/(.*)$/, "renderMetadata");
this.on("route", this.trackPathName);
// Clear stale JSONLD and meta tags
this.on("route", this.clearJSONLD);
this.on("route", this.clearHighwirePressMetaTags);
},
//Keep track of navigation movements
routeHistory: new Array(),
pathHistory: new Array(),
// Will return the last route, which is actually the second to last item in the route history,
// since the last item is the route being currently viewed
lastRoute: function () {
if (
typeof this.routeHistory === "undefined" ||
this.routeHistory.length <= 1
)
return false;
else return this.routeHistory[this.routeHistory.length - 2];
},
trackPathName: function (e) {
if (_.last(this.pathHistory) != window.location.pathname)
this.pathHistory.push(window.location.pathname);
},
//If the user or app cancelled the last route, call this function to revert
// the window location pathname back to the correct value
undoLastRoute: function () {
this.routeHistory.pop();
// Remove the last route and pathname from the history
if (_.last(this.pathHistory) == window.location.pathname)
this.pathHistory.pop();
//Change the pathname in the window location back
this.navigate(_.last(this.pathHistory), { replace: true });
},
renderIndex: function (param) {
this.routeHistory.push("index");
if (!MetacatUI.appView.indexView) {
require(["views/IndexView"], function (IndexView) {
MetacatUI.appView.indexView = new IndexView();
MetacatUI.appView.showView(MetacatUI.appView.indexView);
});
} else MetacatUI.appView.showView(MetacatUI.appView.indexView);
},
renderText: function (options) {
if (!MetacatUI.appView.textView) {
require(["views/TextView"], function (TextView) {
MetacatUI.appView.textView = new TextView();
MetacatUI.appView.showView(MetacatUI.appView.textView, options);
});
} else MetacatUI.appView.showView(MetacatUI.appView.textView, options);
},
renderHelp: function (page, anchorId) {
this.routeHistory.push("help");
MetacatUI.appModel.set("anchorId", anchorId);
if (page) var pageName = this.helpPages[page];
else var pageName = this.helpPages["defaultPage"]; //default
var options = {
pageName: pageName,
anchorId: anchorId,
};
this.renderText(options);
},
renderSignInHelp: function () {
this.routeHistory.push("signin-help");
this.renderText({ pageName: "signInHelp" });
},
renderAbout: function (anchorId) {
this.routeHistory.push("about");
MetacatUI.appModel.set("anchorId", anchorId);
var options = {
pageName: "about",
anchorId: anchorId,
};
this.renderText(options);
},
renderAPI: function (anchorId) {
this.routeHistory.push("api");
MetacatUI.appModel.set("anchorId", anchorId);
var options = {
pageName: "api",
anchorId: anchorId,
};
this.renderText(options);
},
renderProjects: function () {
require(["views/projects/ProjectView"], function (ProjectView) {
MetacatUI.appView.projectView = new ProjectView();
MetacatUI.appView.showView(MetacatUI.appView.projectView);
});
},
/*
* Renders the editor view given a root package identifier,
* or a metadata identifier. If the latter, the corresponding
* package identifier will be queried and then rendered.
*/
renderEditor: function (pid) {
//If there is no EML211EditorView yet, create one
if (!MetacatUI.appView.eml211EditorView) {
var router = this;
//Load the EML211EditorView file
require(["views/metadata/EML211EditorView"], function (
EML211EditorView,
) {
//Add the submit route to the router history
router.routeHistory.push("submit");
//Create a new EML211EditorView
MetacatUI.appView.eml211EditorView = new EML211EditorView({
pid: pid,
});
//Set the pid from the pid given in the URL
MetacatUI.appView.eml211EditorView.pid = pid;
//Render the EML211EditorView
MetacatUI.appView.showView(MetacatUI.appView.eml211EditorView);
});
} else {
//Set the pid from the pid given in the URL
MetacatUI.appView.eml211EditorView.pid = pid;
//Add the submit route to the router history
this.routeHistory.push("submit");
//Render the Editor View
MetacatUI.appView.showView(MetacatUI.appView.eml211EditorView);
}
},
/**
* Renders the Drafts view which is a simple view backed by LocalForage that
* lists drafts created in the Editor so users can recover any failed
* submissions.
*/
renderDrafts: function () {
require(["views/DraftsView"], function (DraftsView) {
MetacatUI.appView.draftsView = new DraftsView();
MetacatUI.appView.showView(MetacatUI.appView.draftsView);
});
},
renderMdqRun: function (suiteId, pid) {
this.routeHistory.push("quality");
if (!MetacatUI.appView.mdqRunView) {
require(["views/MdqRunView"], function (MdqRunView) {
MetacatUI.appView.mdqRunView = new MdqRunView();
MetacatUI.appView.mdqRunView.suiteId = suiteId;
MetacatUI.appView.mdqRunView.pid = pid;
MetacatUI.appView.showView(MetacatUI.appView.mdqRunView);
});
} else {
MetacatUI.appView.mdqRunView.suiteId = suiteId;
MetacatUI.appView.mdqRunView.pid = pid;
MetacatUI.appView.showView(MetacatUI.appView.mdqRunView);
}
},
renderTools: function (anchorId) {
this.routeHistory.push("tools");
MetacatUI.appModel.set("anchorId", anchorId);
var options = {
pageName: "tools",
anchorId: anchorId,
};
this.renderText(options);
},
renderMyData: function (page) {
//Only display this is the user is logged in
if (
!MetacatUI.appUserModel.get("loggedIn") &&
MetacatUI.appUserModel.get("checked")
)
this.navigate("data", { trigger: true });
else if (!MetacatUI.appUserModel.get("checked")) {
var router = this;
this.listenToOnce(
MetacatUI.appUserModel,
"change:checked",
function () {
if (MetacatUI.appUserModel.get("loggedIn"))
router.renderMyData(page);
else this.navigate("data", { trigger: true });
},
);
return;
}
this.routeHistory.push("data");
///Check for a page URL parameter
if (typeof page === "undefined") MetacatUI.appModel.set("page", 0);
else MetacatUI.appModel.set("page", page);
if (!MetacatUI.appView.dataCatalogView) {
require(["views/DataCatalogView"], function (DataCatalogView) {
MetacatUI.appView.dataCatalogView = new DataCatalogView();
MetacatUI.appView.dataCatalogView.searchModel =
MetacatUI.appUserModel.get("searchModel").clone();
MetacatUI.appView.showView(MetacatUI.appView.dataCatalogView);
});
} else {
MetacatUI.appView.dataCatalogView.searchModel = MetacatUI.appUserModel
.get("searchModel")
.clone();
MetacatUI.appView.showView(MetacatUI.appView.dataCatalogView);
}
},
renderData: function (mode, query, page) {
this.routeHistory.push("data");
// Check for a page URL parameter
page = parseInt(page);
page = isNaN(page) || page < 1 ? 1 : page;
MetacatUI.appModel.set("page", page - 1);
// Check if we are using the new CatalogSearchView
if (!MetacatUI.appModel.get("useDeprecatedDataCatalogView")) {
require(["views/search/CatalogSearchView"], function (
CatalogSearchView,
) {
MetacatUI.appView.catalogSearchView = new CatalogSearchView({
initialQuery: query,
});
MetacatUI.appView.showView(MetacatUI.appView.catalogSearchView);
});
return;
}
// Check for a query URL parameter
if (typeof query !== "undefined" && query) {
MetacatUI.appSearchModel.set("additionalCriteria", [query]);
}
require(["views/DataCatalogView"], function (DataCatalogView) {
if (!MetacatUI.appView.dataCatalogView) {
MetacatUI.appView.dataCatalogView = new DataCatalogView();
}
if (mode) MetacatUI.appView.dataCatalogView.mode = mode;
MetacatUI.appView.showView(MetacatUI.appView.dataCatalogView);
});
},
/**
* Renders the Portals Search view.
*/
renderPortalsSearch: function () {
require(["views/portals/PortalsSearchView"], function (
PortalsSearchView,
) {
MetacatUI.appView.showView(new PortalsSearchView({ el: "#Content" }));
});
},
/**
* renderPortal - Render the portal view based on the given name or id, as
* well as optional section
*
* @param {string} label The portal ID or name
* @param {string} portalSection A specific section within the portal
*/
renderPortal: function (label, portalSection) {
//If no portal was specified, go to the portal search view
if (!label) {
this.renderPortalsSearch();
return;
}
// Add the overall class immediately so the navbar is styled correctly right away
$("body").addClass("PortalView");
// Look up the portal document seriesId by its registered name if given
if (portalSection) {
this.routeHistory.push(
MetacatUI.appModel.get("portalTermPlural") +
"/" +
label +
"/" +
portalSection,
);
} else {
this.routeHistory.push(
MetacatUI.appModel.get("portalTermPlural") + "/" + label,
);
}
require(["views/portals/PortalView"], function (PortalView) {
MetacatUI.appView.portalView = new PortalView({
label: label,
activeSectionLabel: portalSection,
});
MetacatUI.appView.showView(MetacatUI.appView.portalView);
});
},
/**
* Renders the PortalEditorView
* @param {string} [portalTermPlural] - This should match the `portalTermPlural` configured in the AppModel.
* @param {string} [portalIdentifier] - The id or labebl of the portal
* @param {string} [portalSection] - The name of the section within the portal to navigate to (e.g. "data")
*/
renderPortalEditor: function (
portalTermPlural,
portalIdentifier,
portalSection,
) {
//If the user navigated to a route with a portal term other than the one supported, then this is not a portal editor route.
if (portalTermPlural != MetacatUI.appModel.get("portalTermPlural")) {
this.navigateToDefault();
return;
}
// Add the overall class immediately so the navbar is styled correctly right away
$("body").addClass("Editor").addClass("Portal");
// Look up the portal document seriesId by its registered name if given
if (portalSection) {
this.routeHistory.push(
"edit/" +
MetacatUI.appModel.get("portalTermPlural") +
"/" +
portalIdentifier +
"/" +
portalSection,
);
} else {
if (!portalIdentifier) {
this.routeHistory.push(
"edit/" + MetacatUI.appModel.get("portalTermPlural"),
);
} else {
this.routeHistory.push(
"edit/" +
MetacatUI.appModel.get("portalTermPlural") +
"/" +
portalIdentifier,
);
}
}
require(["views/portals/editor/PortalEditorView"], function (
PortalEditorView,
) {
MetacatUI.appView.portalEditorView = new PortalEditorView({
activeSectionLabel: portalSection,
portalIdentifier: portalIdentifier,
});
MetacatUI.appView.showView(MetacatUI.appView.portalEditorView);
});
},
renderMetadata: function (pid) {
pid = decodeURIComponent(pid);
this.routeHistory.push("metadata");
MetacatUI.appModel.set("lastPid", MetacatUI.appModel.get("pid"));
var seriesId;
//Check for a seriesId
if (pid.indexOf("version:") > -1) {
seriesId = pid.substr(0, pid.indexOf(", version:"));
pid = pid.substr(pid.indexOf(", version: ") + ", version: ".length);
}
//Save the id in the app model
MetacatUI.appModel.set("pid", pid);
if (!MetacatUI.appView.metadataView) {
require(["views/MetadataView"], function (MetadataView) {
MetacatUI.appView.metadataView = new MetadataView();
//Send the id(s) to the view
MetacatUI.appView.metadataView.seriesId = seriesId;
MetacatUI.appView.metadataView.pid = pid;
MetacatUI.appView.showView(MetacatUI.appView.metadataView);
});
} else {
//Send the id(s) to the view
MetacatUI.appView.metadataView.seriesId = seriesId;
MetacatUI.appView.metadataView.pid = pid;
// MetacatUI resets the dataPackage and dataPackageSynced
// attributes before rendering the view. These attributes are
// initialized on a per-dataset basis to prevent displaying the
// same dataset repeatedly.
MetacatUI.appView.metadataView.dataPackage = null;
MetacatUI.appView.metadataView.dataPackageSynced = false;
MetacatUI.appView.showView(MetacatUI.appView.metadataView);
}
},
renderProfile: function (username, section, subsection) {
this.closeLastView();
var viewChoice;
//If there is a username specified and user profiles are disabled,
// forward to the entire repo profile view.
if (username && !MetacatUI.appModel.get("enableUserProfiles")) {
this.navigate("profile", { trigger: true, replace: true });
return;
}
if (!username) {
this.routeHistory.push("summary");
// flag indicating /profile view
var viewOptions = { nodeSummaryView: true };
if (!MetacatUI.appView.statsView) {
require(["views/StatsView"], function (StatsView) {
MetacatUI.appView.statsView = new StatsView({
userType: "repository",
});
MetacatUI.appView.showView(
MetacatUI.appView.statsView,
viewOptions,
);
});
} else
MetacatUI.appView.showView(
MetacatUI.appView.statsView,
viewOptions,
);
} else {
this.routeHistory.push("profile");
MetacatUI.appModel.set("profileUsername", username);
if (section || subsection) {
var viewOptions = { section: section, subsection: subsection };
}
if (!MetacatUI.appView.userView) {
require(["views/UserView"], function (UserView) {
MetacatUI.appView.userView = new UserView();
MetacatUI.appView.showView(
MetacatUI.appView.userView,
viewOptions,
);
});
} else
MetacatUI.appView.showView(MetacatUI.appView.userView, viewOptions);
}
},
renderMyProfile: function (section, subsection) {
if (
MetacatUI.appUserModel.get("checked") &&
!MetacatUI.appUserModel.get("loggedIn")
)
this.renderSignIn();
else if (!MetacatUI.appUserModel.get("checked")) {
this.listenToOnce(
MetacatUI.appUserModel,
"change:checked",
function () {
if (MetacatUI.appUserModel.get("loggedIn"))
this.renderProfile(
MetacatUI.appUserModel.get("username"),
section,
subsection,
);
else this.renderSignIn();
},
);
} else if (
MetacatUI.appUserModel.get("checked") &&
MetacatUI.appUserModel.get("loggedIn")
) {
this.renderProfile(
MetacatUI.appUserModel.get("username"),
section,
subsection,
);
}
},
logout: function (param) {
//Clear our browsing history when we log out
this.routeHistory.length = 0;
if (
(typeof MetacatUI.appModel.get("tokenUrl") == "undefined" ||
!MetacatUI.appModel.get("tokenUrl")) &&
!MetacatUI.appView.registryView
) {
require(["views/RegistryView"], function (RegistryView) {
MetacatUI.appView.registryView = new RegistryView();
if (MetacatUI.appView.currentView.onClose)
MetacatUI.appView.currentView.onClose();
MetacatUI.appUserModel.logout();
});
} else {
if (
MetacatUI.appView.currentView &&
MetacatUI.appView.currentView.onClose
)
MetacatUI.appView.currentView.onClose();
MetacatUI.appUserModel.logout();
}
},
renderSignIn: function () {
var router = this;
//If there is no SignInView yet, create one
if (!MetacatUI.appView.signInView) {
require(["views/SignInView"], function (SignInView) {
MetacatUI.appView.signInView = new SignInView({
el: "#Content",
fullPage: true,
});
router.renderSignIn();
});
return;
}
//If the user status has been checked and they are already logged in, we will forward them to their profile
if (
MetacatUI.appUserModel.get("checked") &&
MetacatUI.appUserModel.get("loggedIn")
) {
this.navigate("my-profile", { trigger: true });
return;
}
//If the user status has been checked and they are NOT logged in, show the SignInView
else if (
MetacatUI.appUserModel.get("checked") &&
!MetacatUI.appUserModel.get("loggedIn")
) {
this.routeHistory.push("signin");
MetacatUI.appView.showView(MetacatUI.appView.signInView);
}
//If the user status has not been checked yet, wait for it
else if (!MetacatUI.appUserModel.get("checked")) {
this.listenToOnce(
MetacatUI.appUserModel,
"change:checked",
this.renderSignIn,
);
MetacatUI.appView.showView(MetacatUI.appView.signInView);
}
},
renderSignInSuccess: function () {
$("body").html("Sign-in successful.");
setTimeout(window.close, 1000);
},
renderLdapSignInSuccess: function () {
//If there is an LDAP sign in error message
if (
window.location.pathname.indexOf(
"error=Unable%20to%20authenticate%20LDAP%20user",
) > -1
) {
this.renderLdapOnlySignInError();
} else {
this.renderSignInSuccess();
}
},
renderLdapSignInError: function () {
this.routeHistory.push("signinldaperror");
if (!MetacatUI.appView.signInView) {
require(["views/SignInView"], function (SignInView) {
MetacatUI.appView.signInView = new SignInView({ el: "#Content" });
MetacatUI.appView.signInView.ldapError = true;
MetacatUI.appView.signInView.ldapOnly = true;
MetacatUI.appView.signInView.fullPage = true;
MetacatUI.appView.showView(MetacatUI.appView.signInView);
});
} else {
MetacatUI.appView.signInView.ldapError = true;
MetacatUI.appView.signInView.ldapOnly = true;
MetacatUI.appView.signInView.fullPage = true;
MetacatUI.appView.showView(MetacatUI.appView.signInView);
}
},
renderLdapOnlySignInError: function () {
this.routeHistory.push("signinldaponlyerror");
if (!MetacatUI.appView.signInView) {
require(["views/SignInView"], function (SignInView) {
var signInView = new SignInView({ el: "#Content" });
signInView.ldapError = true;
signInView.ldapOnly = true;
signInView.fullPage = true;
MetacatUI.appView.showView(signInView);
});
} else {
var signInView = new SignInView({ el: "#Content" });
signInView.ldapError = true;
signInView.ldapOnly = true;
signInView.fullPage = true;
MetacatUI.appView.showView(signInView);
}
},
renderLdapSignIn: function () {
this.routeHistory.push("signinLdap");
if (!MetacatUI.appView.signInView) {
require(["views/SignInView"], function (SignInView) {
MetacatUI.appView.signInView = new SignInView({ el: "#Content" });
MetacatUI.appView.signInView.ldapOnly = true;
MetacatUI.appView.signInView.fullPage = true;
MetacatUI.appView.showView(MetacatUI.appView.signInView);
});
} else {
var signInLdapView = new SignInView({ el: "#Content" });
MetacatUI.appView.signInView.ldapOnly = true;
MetacatUI.appView.signInView.fullPage = true;
MetacatUI.appView.showView(signInLdapView);
}
},
navigateToDefault: function () {
//Navigate to the default view
this.navigate(MetacatUI.appModel.defaultView, { trigger: true });
},
/*
* Gets an array of route names that are set on this router.
* @return {Array} - An array of route names, not including any special characters
*/
getRouteNames: function () {
var router = this;
var routeNames = _.map(Object.keys(this.routes), function (routeName) {
return router.getRouteName(routeName);
});
//The "view" and portals routes are not included in the route hash (they are set up during initialize),
// so we have to manually add it here.
routeNames.push("view");
if (!routeNames.includes(MetacatUI.appModel.get("portalTermPlural"))) {
routeNames.push(MetacatUI.appModel.get("portalTermPlural"));
}
return routeNames;
},
/*
* Gets the route name based on the route pattern given
* @param {string} routePattern - A string that represents the route pattern e.g. "view(/pid)"
* @return {string} - The name of the route without any pattern special characters e.g. "view"
*/
getRouteName: function (routePattern) {
var specialChars = ["/", "(", "*", ":"];
_.each(specialChars, function (specialChar) {
var substring = routePattern.substring(
0,
routePattern.indexOf(specialChar),
);
if (substring && substring.length < routePattern.length) {
routePattern = substring;
}
});
return routePattern;
},
closeLastView: function () {
//Get the last route and close the view
var lastRoute = _.last(this.routeHistory);
if (lastRoute == "summary") MetacatUI.appView.statsView.onClose();
else if (lastRoute == "profile") MetacatUI.appView.userView.onClose();
},
clearJSONLD: function () {
$("#jsonld").remove();
},
clearHighwirePressMetaTags: function () {
$("head > meta[name='citation_title']").remove();
$("head > meta[name='citation_authors']").remove();
$("head > meta[name='citation_publisher']").remove();
$("head > meta[name='citation_date']").remove();
},
},
);
return UIRouter;
});