Source: src/js/app.js

"use strict";

MetacatUI.recaptchaURL =
  "https://www.google.com/recaptcha/api/js/recaptcha_ajax";
if (MetacatUI.mapKey) {
  var gmapsURL =
    "https://maps.googleapis.com/maps/api/js?v=3&libraries=places&key=" +
    MetacatUI.mapKey;
  define("gmaps", ["async!" + gmapsURL], function () {
    return google.maps;
  });
} else {
  define("gmaps", null);
}

MetacatUI.d3URL = "../components/d3.v3.min";

/* Configure the app to use requirejs, and map dependency aliases to their
   directory location (.js is ommitted). Shim libraries that don't natively
   support requirejs. */
require.config({
  baseUrl: MetacatUI.root + "/js/",
  waitSeconds: 180, //wait 3 minutes before throwing a timeout error
  map: MetacatUI.themeMap,
  urlArgs:
    "v=" + (MetacatUI.AppConfig.cachebuster || MetacatUI.metacatUIVersion),
  paths: {
    jquery: MetacatUI.root + "/components/jquery-1.9.1.min",
    jqueryui: MetacatUI.root + "/components/jquery-ui.min",
    jqueryform: MetacatUI.root + "/components/jquery.form",
    underscore: MetacatUI.root + "/components/underscore-min",
    backbone: MetacatUI.root + "/components/backbone-min",
    localforage: MetacatUI.root + "/components/localforage.min",
    bootstrap: MetacatUI.root + "/components/bootstrap.min",
    text: MetacatUI.root + "/components/require-text",
    jws: MetacatUI.root + "/components/jws-3.2.min",
    jsrasign: MetacatUI.root + "/components/jsrsasign-4.9.0.min",
    async: MetacatUI.root + "/components/async",
    recaptcha: [MetacatUI.recaptchaURL, "scripts/placeholder"],
    nGeohash: MetacatUI.root + "/components/geohash/main",
    fancybox: MetacatUI.root + "/components/fancybox/jquery.fancybox.pack", //v. 2.1.5
    annotator: MetacatUI.root + "/components/annotator/v1.2.10/annotator-full",
    bioportal: MetacatUI.root + "/components/bioportal/jquery.ncbo.tree-2.0.2",
    clipboard: MetacatUI.root + "/components/clipboard.min",
    uuid: MetacatUI.root + "/components/uuid",
    md5: MetacatUI.root + "/components/md5",
    rdflib: MetacatUI.root + "/components/rdflib.min",
    x2js: MetacatUI.root + "/components/xml2json",
    he: MetacatUI.root + "/components/he",
    citation: MetacatUI.root + "/components/citation.min",
    promise: MetacatUI.root + "/components/es6-promise.min",
    metacatuiConnectors: MetacatUI.root + "/js/connectors/Filters-Search",
    // showdown + extensions (used in the MarkdownView to convert markdown to html)
    showdown: MetacatUI.root + "/components/showdown/showdown.min",
    showdownHighlight:
      MetacatUI.root +
      "/components/showdown/extensions/showdown-highlight/showdown-highlight",
    highlight:
      MetacatUI.root +
      "/components/showdown/extensions/showdown-highlight/highlight.pack",
    showdownFootnotes:
      MetacatUI.root + "/components/showdown/extensions/showdown-footnotes",
    showdownBootstrap:
      MetacatUI.root + "/components/showdown/extensions/showdown-bootstrap",
    showdownDocbook:
      MetacatUI.root + "/components/showdown/extensions/showdown-docbook",
    showdownKatex:
      MetacatUI.root +
      "/components/showdown/extensions/showdown-katex/showdown-katex.min",
    showdownCitation:
      MetacatUI.root +
      "/components/showdown/extensions/showdown-citation/showdown-citation",
    showdownImages:
      MetacatUI.root + "/components/showdown/extensions/showdown-images",
    showdownXssFilter:
      MetacatUI.root +
      "/components/showdown/extensions/showdown-xss-filter/showdown-xss-filter",
    xss:
      MetacatUI.root +
      "/components/showdown/extensions/showdown-xss-filter/xss.min",
    showdownHtags:
      MetacatUI.root + "/components/showdown/extensions/showdown-htags",
    // woofmark - markdown editor
    woofmark: MetacatUI.root + "/components/woofmark.min",
    // drop zone creates drag and drop areas
    Dropzone: MetacatUI.root + "/components/dropzone-amd-module",
    // Packages that convert between json data to markdown table
    markdownTableFromJson:
      MetacatUI.root + "/components/markdown-table-from-json.min",
    markdownTableToJson: MetacatUI.root + "/components/markdown-table-to-json",
    // Polyfill required for using dropzone with older browsers
    corejs: MetacatUI.root + "/components/core-js",
    // Searchable multi-select dropdown component
    semanticUItransition:
      MetacatUI.root + "/components/semanticUI/transition.min",
    semanticUIdropdown: MetacatUI.root + "/components/semanticUI/dropdown.min",
    // To make elements drag and drop, sortable
    sortable: MetacatUI.root + "/components/sortable.min",
    //Cesium
    cesium:
      "https://cesium.com/downloads/cesiumjs/releases/1.91/Build/Cesium/Cesium",
    //Have a null fallback for our d3 components for browsers that don't support SVG
    d3: MetacatUI.d3URL,
    LineChart: ["views/LineChartView", null],
    BarChart: ["views/BarChartView", null],
    CircleBadge: ["views/CircleBadgeView", null],
    DonutChart: ["views/DonutChartView", null],
    MetricsChart: ["views/MetricsChartView", null],
  },
  shim: {
    /* used for libraries without native AMD support */
    underscore: {
      exports: "_",
    },
    backbone: {
      deps: ["underscore", "jquery"],
      exports: "Backbone",
    },
    bootstrap: {
      deps: ["jquery"],
      exports: "Bootstrap",
    },
    annotator: {
      exports: "Annotator",
    },
    bioportal: {
      exports: "Bioportal",
    },
    jws: {
      exports: "JWS",
      deps: ["jsrasign"],
    },
    nGeohash: {
      exports: "geohash",
    },
    fancybox: {
      deps: ["jquery"],
    },
    uuid: {
      exports: "uuid",
    },
    rdflib: {
      exports: "rdf",
    },
    xss: {
      exports: "filterXSS",
    },
    citation: {
      exports: "citationRequire",
    },
    promise: {
      exports: "Promise",
    },
    metacatuiConnectors: {
      exports: "FiltersSearchConnector",
    },
  },
});

MetacatUI.appModel = MetacatUI.appModel || {};
MetacatUI.appView = MetacatUI.appView || {};
MetacatUI.uiRouter = MetacatUI.uiRouter || {};
MetacatUI.appSearchResults = MetacatUI.appSearchResults || {};
MetacatUI.appSearchModel = MetacatUI.appSearchModel || {};
/**
 * @name MetacatUI.rootDataPackage
 * @type {string}
 * @description The top-level {@link DataPackage} that is currently being viewed or edited in MetacatUI.
 */
MetacatUI.rootDataPackage = MetacatUI.rootDataPackage || {};
MetacatUI.statsModel = MetacatUI.statsModel || {};
MetacatUI.mapModel = MetacatUI.mapModel || {};
MetacatUI.appLookupModel = MetacatUI.appLookupModel || {};
MetacatUI.nodeModel = MetacatUI.nodeModel || {};
MetacatUI.appUserModel = MetacatUI.appUserModel || {};
MetacatUI.analytics = MetacatUI.analytics || {};

/* Setup the application scaffolding first  */
require(["bootstrap", "views/AppView", "models/AppModel"], function (
  Bootstrap,
  AppView,
  AppModel,
) {
  "use strict";

  // Create an AppModel, which controls the global app configuration and app states
  //  To be compatible with MetacatUI 2.11.X and earlier, we need to set the metacat context attribute here.
  //  This supports the old way tof configuring the app via the index.html file.
  //  As of MetacatUI 2.12.0, it is recommended that you configure MetacatUI via an AppConfig file.
  MetacatUI.appModel = new AppModel({
    context: MetacatUI.AppConfig.metacatContext,
  });

  //Check for custom settings in the theme config file
  if (typeof MetacatUI.customAppConfig == "function")
    MetacatUI.customAppConfig();

  /* Now require the rest of the libraries for the application */
  require([
    "underscore",
    "backbone",
    "routers/router",
    "collections/SolrResults",
    "models/Search",
    "models/Stats",
    "models/Map",
    "models/LookupModel",
    "models/NodeModel",
    "models/UserModel",
    "models/DataONEObject",
    "collections/DataPackage",
  ], function (
    _,
    Backbone,
    UIRouter,
    SolrResultList,
    Search,
    Stats,
    MapModel,
    LookupModel,
    NodeModel,
    UserModel,
    DataONEObject,
    DataPackage,
  ) {
    "use strict";

    //Create all the other models and collections first
    MetacatUI.appSearchResults = new SolrResultList([], {});

    MetacatUI.appSearchModel = new Search();

    MetacatUI.statsModel = new Stats();

    MetacatUI.mapModel =
      typeof customMapModelOptions == "object"
        ? new MapModel(customMapModelOptions)
        : new MapModel();

    MetacatUI.appLookupModel = new LookupModel();

    MetacatUI.nodeModel = new NodeModel();

    MetacatUI.appUserModel = new UserModel();

    require(["models/analytics/GoogleAnalytics"], function (Analytics) {
      MetacatUI.analytics = new Analytics();
    });

    /* Create a general event dispatcher to enable
           communication across app components
        */
    MetacatUI.eventDispatcher = _.clone(Backbone.Events);

    //Load the App View now
    MetacatUI.appView = new AppView();
    MetacatUI.appView.render();

    // Initialize routing and start Backbone.history()
    (function () {
      /**
       * Backbone.routeNotFound
       *
       * Simple plugin that listens for false returns on Backbone.history.loadURL and fires an event
       * to let the application know that no routes matched.
       *
       * @author STRML
       */
      var oldLoadUrl = Backbone.History.prototype.loadUrl;

      _.extend(Backbone.History.prototype, {
        /*
         * Override loadUrl & watch return value. Trigger event if no route was matched.
         * @extends Backbone.History
         * @return {Boolean} True if a route was matched
         */
        loadUrl: function (fragment) {
          if (!this.matchRoot()) return false;
          fragment = this.fragment = this.getFragment(fragment);
          var match = _.some(this.handlers, function (handler) {
            if (handler.route.test(fragment)) {
              handler.callback(fragment);
              return true;
            }
          });

          if (!match) this.trigger("routeNotFound");
          return match;
        },
        matchRoot: function () {
          var path = this.decodeFragment(this.location.pathname);
          var rootPath = path.slice(0, this.root.length - 1) + "/";
          return rootPath === this.root;
        },
        decodeFragment: function (fragment) {
          return decodeURI(fragment.replace(/%25/g, "%2525"));
        },
      });
    }).call(this);

    //Make the router and begin the Backbone history
    //The router will figure out which view to load first based on window location
    MetacatUI.uiRouter = new UIRouter();

    //Take the protocol and origin out of the root URL when sending it to Backbone.history.
    // The root URL sent to Backbone.history should be either `/` or `/directory/...`
    var historyRoot = MetacatUI.root;

    //If there is a protocol
    if (historyRoot.indexOf("://") > -1) {
      //Get the substring after the ``://``
      historyRoot = historyRoot.substring(historyRoot.indexOf("://") + 3);

      //If there is no `/`, this must be the root directory
      if (historyRoot.indexOf("/") == -1) historyRoot = "/";
      //Otherwise get the substring after the first /
      else historyRoot = historyRoot.substring(historyRoot.indexOf("/"));
    }
    //If there are no colons, periods, or slashes, this is a directory name
    else if (
      historyRoot.indexOf(":") == -1 &&
      historyRoot.indexOf(".") == -1 &&
      historyRoot.indexOf("/") == -1
    ) {
      //So the root is a leading slash and the directory name
      historyRoot = "/" + historyRoot;
    }
    //If there is a slash, get the path name starting with the slash
    else if (historyRoot.indexOf("/") > -1) {
      historyRoot = historyRoot.substring(historyRoot.indexOf("/"));
    }
    //All other strings are the root directory
    else {
      historyRoot = "/";
    }

    Backbone.history.start({
      pushState: true,
      root: historyRoot,
    });

    $(document).on("click", "a:not([data-toggle],[target])", function (evt) {
      // Don't hijack the event if the user had Control or Command held down
      if (evt.ctrlKey || evt.metaKey) {
        return;
      }

      var href = { prop: $(this).prop("href"), attr: $(this).attr("href") };

      // Stop if the click happened on an a w/o an href
      // This is kind of a weird edge case where. This could be removed if
      // we remove these instances from the codebase
      if (
        typeof href === "undefined" ||
        typeof href.attr === "undefined" ||
        href.attr === ""
      ) {
        return;
      }

      //Don't route to URLs with the DataONE API, which are sometimes proxied
      // via Apache ProxyPass so start with the MetacatUI origin
      if (
        href.attr.indexOf("/cn/v2/") > 0 ||
        href.attr.indexOf("/mn/v2/") > 0
      ) {
        return;
      }

      var root =
        location.protocol +
        "//" +
        location.host +
        Backbone.history.options.root;
      // Remove the MetacatUI (plus a trailing /) from the value in the 'href'
      // attribute of the clicked element so Backbone.history.navigate works.
      // Note that a RegExp was used here to anchor the .replace call to the
      // front of the string so that this code works when MetacatUI.root is "".
      var route = href.attr.replace(new RegExp("^" + MetacatUI.root + "/"), "");

      // Catch routes hrefs that start with # and don't do anything with them
      if (href.attr.indexOf("#") == 0) {
        return;
      }

      //If the URL is not a route defined in the app router, then follow the link
      //If the URL is not at the MetacatUI root, then follow the link
      if (
        href.prop &&
        href.prop.slice(0, root.length) === root &&
        _.contains(
          MetacatUI.uiRouter.getRouteNames(),
          MetacatUI.uiRouter.getRouteName(route),
        )
      ) {
        evt.preventDefault();
        Backbone.history.navigate(route, true);
      }
    });

    MetacatUI.appModel.trigger("appInitialized");
  });
});