Source: src/js/routers/router.js

/*global Backbone */
'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;
});