MetacatUI 3.0.0 is coming soon
@@ -14,9 +15,10 @@ id: homepage
{% include homepage.html %}
## About
-MetacatUI is a client-side web interface for Metacat science data repositories and other repository software that implement the DataONE REST API. Currently, it is used as the basis for the [KNB Data Repository](http://knb.ecoinformatics.org), the [NSF Arctic Data Center](https://arcticdata.io/catalog/), the [DataONE federation](https://search.dataone.org), and other organizations.
-MetacatUI is an open source, community project. We [welcome contributions](https://github.com/NCEAS/metacatui/blob/main/CONTRIBUTING.md) in many forms, including code, graphics, documentation, bug reports, testing, etc. Use the [Github discussion list](https://github.com/NCEAS/metacatui/issues) to discuss these contributions with us.
+MetacatUI is a client-side web interface for Metacat science data repositories and other repository software that implement the DataONE REST API. Currently, it is used as the basis for the [KNB Data Repository](http://knb.ecoinformatics.org), the [NSF Arctic Data Center](https://arcticdata.io/catalog/), the [DataONE federation](https://search.dataone.org), and other organizations.
+
+MetacatUI is an open source, community project. We [welcome contributions](https://github.com/NCEAS/metacatui/blob/main/CONTRIBUTING.md) in many forms, including code, graphics, documentation, bug reports, testing, etc. Use the [Github discussion list](https://github.com/NCEAS/metacatui/issues) to discuss these contributions with us.
## Citation
diff --git a/docs/install/apache.md b/docs/install/apache.md
index 6d48b6599..f79148ec9 100644
--- a/docs/install/apache.md
+++ b/docs/install/apache.md
@@ -1,6 +1,7 @@
# Configuring Apache for MetacatUI
There are two Apache configurations needed for MetacatUI to work properly.
+
1. Configure Apache to serve `index.html` instead of a 404 for the MetacatUI directory
2. Allow encoded slashes in MetacatUI paths
@@ -22,59 +23,59 @@ MetacatUI page for every MetacatUI path. Otherwise, your server will return a 40
Add the `FallbackResource` Apache directive:
- ```apache
- ...
- # Serve index.html instead of a 404 error in the MetacatUI directory
-
- FallbackResource /metacatui/index.html
-
- ...
- ```
+```apache
+ ...
+ # Serve index.html instead of a 404 error in the MetacatUI directory
+
+ FallbackResource /metacatui/index.html
+
+ ...
+```
#### Apache v2.2.15 and earlier
Add a `mod_rewrite` Apache directive for the MetacatUI index.html file:
- ```apache
-
- ...
- ...
-
-
- RewriteEngine On
- RewriteBase /
- RewriteRule ^index\.html$ - [L]
- RewriteCond %{REQUEST_FILENAME} !-f
- RewriteCond %{REQUEST_FILENAME} !-d
- RewriteRule . /index.html [L]
-
-
- ```
+```apache
+
+ ...
+ ...
+
+
+ RewriteEngine On
+ RewriteBase /
+ RewriteRule ^index\.html$ - [L]
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule . /index.html [L]
+
+
+```
### Allow encoded slashes in MetacatUI paths
Add the following to your Apache configuration file:
- ```apache
- ...
- # Allow encoded slashes in URLs so encoded identifiers can be sent in MetacatUI URLs
- AllowEncodedSlashes On
- ...
- ```
+```apache
+...
+# Allow encoded slashes in URLs so encoded identifiers can be sent in MetacatUI URLs
+AllowEncodedSlashes On
+...
+```
**Why do I need this?**
`/` characters are commonly used in identifiers for data and metadata objects in Metacat
repositories. These identifiers are used in MetacatUI paths and are usually URL-encoded.
-
## Configuring Apache locally on Mac OS X, for MetacatUI development
Note: The following instructions are general guidelines on how to set up a local Apache server so
-you can develop MetacatUI features/bugs on your local machine. These instructions are *not*
+you can develop MetacatUI features/bugs on your local machine. These instructions are _not_
updated regularly, since we recommend you use the [NodeJS Express server instead](https://github.com/NCEAS/metacatui/blob/master/server.js).
#### Step 1. Create a directory for your MetacatUI
-- Choose a location from which to serve *all* your Apache website files. A good location is `/Users/{username}/Sites`
+
+- Choose a location from which to serve _all_ your Apache website files. A good location is `/Users/{username}/Sites`
- Make a subdirectory in `~/Sites` specifically for MetacatUI. The default directory name for MetacatUI is `metacatui`.
```bash
@@ -82,14 +83,16 @@ updated regularly, since we recommend you use the [NodeJS Express server instead
```
#### Step 2. Tell Apache to use the directory from Step 1
+
- Configure Apache to serve files from your `Sites` directory by opening `/etc/apache2/httpd.conf` and changing the `DocumentRoot` pathname. Example:
- ```apache
- DocumentRoot "/Users/walker/Sites"
-
- ```
+ ```apache
+ DocumentRoot "/Users/walker/Sites"
+
+ ```
#### Step 3. Configure a VirtualHost in Apache for MetacatUI
+
- First, create a backup of the default httpd-vhosts.conf file:
```bash
@@ -130,13 +133,15 @@ updated regularly, since we recommend you use the [NodeJS Express server instead
- Save your `/etc/hosts` changes
#### Step 4. Move MetacatUI files to Apache
+
- Move the MetacatUI application code to the directory we chose in Step 2.
- ```bash
- cp -rf metacatui-2.0.0/src/* /Users/walker/Sites/metacatui/
- ```
+ ```bash
+ cp -rf metacatui-2.0.0/src/* /Users/walker/Sites/metacatui/
+ ```
#### Step 5. Start Apache
+
- Start (or restart) Apache:
```bash
diff --git a/docs/install/configuration/index.md b/docs/install/configuration/index.md
index b612a0dec..7a4874767 100644
--- a/docs/install/configuration/index.md
+++ b/docs/install/configuration/index.md
@@ -4,9 +4,11 @@ MetacatUI comes with a few themes already installed: `default`, `knb`, `arctic`,
The `default` theme can be used out-of-box. It is recommended since it has an unbranded style and has is configured for general use. The other themes are included as examples, but are very repository-specific.
## Creating a custom theme
+
The look and feel of your MetacatUI can be customized by creating a custom theme.
The basic components of a custom theme are:
+
1. A navigation bar at the top of the page (`navbar.html` template)
2. A footer at the bottom of the page (`footer.html` template)
3. Custom CSS so you can use your own colors, fonts, etc.
@@ -15,27 +17,28 @@ The basic components of a custom theme are:
To create a custom theme, do the following:
### Step 1. Copy the default theme as a starting point
+
Copy and paste the `js/themes/default` directory to a new directory with the name of
your theme:
- ```bash
- cp -rf src/js/themes/default src/js/themes/{my-theme-name}
- ```
+```bash
+cp -rf src/js/themes/default src/js/themes/{my-theme-name}
+```
Where `{my-theme-name}` is replaced with your chosen theme name.
### Step 2. Create a `templates` directory:
- ```bash
- mkdir src/js/themes/{my-theme-name}/templates
- ```
+```bash
+mkdir src/js/themes/{my-theme-name}/templates
+```
### Step 3. Customize the default navbar and footer templates
- ```bash
- cp src/js/templates/navbar.html src/js/themes/{my-theme-name}/templates/navbar.html
- cp src/js/templates/footer.html src/js/themes/{my-theme-name}/templates/footer.html
- ```
+```bash
+cp src/js/templates/navbar.html src/js/themes/{my-theme-name}/templates/navbar.html
+cp src/js/templates/footer.html src/js/themes/{my-theme-name}/templates/footer.html
+```
Open up the navbar.html and footer.html files in a text editor and change the HTML
so that it shows the links, logo, and other content you'd like.
@@ -45,11 +48,12 @@ which is the root path of your MetacatUI installation, taken directly from `src/
For example, to add your organization's logo, put your logo file in `src/js/themes/{my-theme-name}/img/` and add
HTML for the logo to a template:
- ```
-
-
-
- ```
+
+```
+
+
+
+```
**Note:** The `navbar` template contains links that you will want to keep in your custom `navbar` template,
such as links to "My profile" and "My datasets" when a user is logged in, the "Sign In" button, etc.
@@ -70,37 +74,49 @@ What this does is map the default location of these app components to the custom
An example for the `footer.html` and `navbar.html` files is below. Your CSS does not need to be included here.
- ```js
- MetacatUI.themeMap =
- {
- '*': {
- // example overrides are provided here
- 'templates/navbar.html' : MetacatUI.root + '/js/themes/' + MetacatUI.theme + '/templates/navbar.html',
- 'templates/footer.html' : MetacatUI.root + '/js/themes/' + MetacatUI.theme + '/templates/footer.html'
- }
- };
- ```
+```js
+MetacatUI.themeMap = {
+ "*": {
+ // example overrides are provided here
+ "templates/navbar.html":
+ MetacatUI.root +
+ "/js/themes/" +
+ MetacatUI.theme +
+ "/templates/navbar.html",
+ "templates/footer.html":
+ MetacatUI.root +
+ "/js/themes/" +
+ MetacatUI.theme +
+ "/templates/footer.html",
+ },
+};
+```
You can also include app configuration for this theme by defining a `MetacatUI.AppConfig` variable with
configuration options. For example, if you want your theme to always use a certain `emailContact`, you can specify that configuration value like so:
- ```js
- MetacatUI.AppConfig = Object.assign({
- emailContact: "contact@ourrepo.org"
- }, (MetacatUI.AppConfig || {}));
- ```
+```js
+MetacatUI.AppConfig = Object.assign(
+ {
+ emailContact: "contact@ourrepo.org",
+ },
+ MetacatUI.AppConfig || {},
+);
+```
-You could use this `AppConfig` as your primary application configuration by setting the [`appConfigPath`]((../docs/global.html#appConfigPath))
+You could use this `AppConfig` as your primary application configuration by setting the [`appConfigPath`](<(../docs/global.html#appConfigPath)>)
to this file path. Or it can be used as additional configurations on top of a primary configuration file.
## Advanced customization by extending models and views
+
All the models, collections, views, routers, and templates required to run
MetacatUI are in the `js` directory. Any of these files can be extended by your custom
theme to provide additional functionality on top of the default MetacatUI functionality.
> **Warning**
-While extending models and views is supported, we suggest that you proceed carefully,
-because:
+> While extending models and views is supported, we suggest that you proceed carefully,
+> because:
+
- Your customizations may break after updating MetacatUI to a new version. You may need to make manual changes to get them working again.
- If you completely override a function inside of a model or view, you might break something.
@@ -109,8 +125,9 @@ make an extension that you think would be useful to other MetacatUI users, we su
you make a Pull Request to MetacatUI so your code can be merged into the MetacatUI source code.
This is mutually beneficial, since your code will be maintained by the MetacatUI dev team,
meaning you have less maintenance to do!
- - Submit a [pull request](https://github.com/NCEAS/metacatui/compare)
- - More details on [contributing](https://github.com/NCEAS/metacatui/blob/master/CONTRIBUTING.md).
+
+- Submit a [pull request](https://github.com/NCEAS/metacatui/compare)
+- More details on [contributing](https://github.com/NCEAS/metacatui/blob/master/CONTRIBUTING.md).
### How to extend a model or view
@@ -121,10 +138,10 @@ file organization as the source code by creating a `model`, `view`, or other sub
For example, say we want to extend the `TextView`:
- ```bash
- mkdir src/js/themes/{my-theme-name}/views
- touch src/js/themes/{my-theme-name}/views/TextView.js
- ```
+```bash
+mkdir src/js/themes/{my-theme-name}/views
+touch src/js/themes/{my-theme-name}/views/TextView.js
+```
#### Step 2. Add the extended component to your themeMap
@@ -133,16 +150,17 @@ First, we want to override the name of the `views/TextView` component. In your `
file, edit the `themeMap` variable.
Add a line for the base `TextView` and rename it something like `views/BaseTextView`:
- ```js
- "views/BaseTextView" : MetacatUI.root + "/js/views/TextView.js",
- ```
+```js
+"views/BaseTextView" : MetacatUI.root + "/js/views/TextView.js",
+```
+
**What does this do?** This maps a new component, `views/BaseTextView`, in the application to the `/js/views/TextView.js` file. If you were to stop here, you would see no change in MetacatUI, since the `views/BaseTextView` component isn't used by any view in the app.
Next, add a line that maps your extended `TextView` to the `views/TextView` name:
- ```js
- "views/TextView" : MetacatUI.root + "/js/themes/" + MetacatUI.theme + "/views/TextView.js",
- ```
+```js
+"views/TextView" : MetacatUI.root + "/js/themes/" + MetacatUI.theme + "/views/TextView.js",
+```
**What does this do?** This maps the existing component,`views/TextView`, to the custom `TextView.js` file
that we created in Step 1. (If you were to stop here, your TextView would break and throw errors because your
@@ -158,20 +176,23 @@ Then we create and return a Backbone view that `extend`s the `BaseTextView`.
Then we add the custom functionality to the view. In this example, we just added a function that
logs "Hello world!" to the console.
- ```js
- define(["jquery", "underscore", "backbone",
- "views/BaseTextView"],
- function($, _, Backbone, BaseTextView) {
- "use strict";
-
- var TextView = BaseTextView.extend({
- myNewFunction: function(){
- console.log("Hello world!")
- }
- });
- return TextView;
+```js
+define(["jquery", "underscore", "backbone", "views/BaseTextView"], function (
+ $,
+ _,
+ Backbone,
+ BaseTextView,
+) {
+ "use strict";
+
+ var TextView = BaseTextView.extend({
+ myNewFunction: function () {
+ console.log("Hello world!");
+ },
});
- ```
+ return TextView;
+});
+```
#### Step 4. Done!
diff --git a/docs/install/configuration/pre-2.12.0.md b/docs/install/configuration/pre-2.12.0.md
index dee6443e9..4f154d816 100644
--- a/docs/install/configuration/pre-2.12.0.md
+++ b/docs/install/configuration/pre-2.12.0.md
@@ -1,19 +1,22 @@
-# Configuring MetacatUI for versions 2.11.5 and earlier
+# Configuring MetacatUI for versions 2.11.5 and earlier
## Step 1. Configure `index.html`
+
- Open `src/index.html` in a text editor and change the following values:
- - Set `data-metacat-context` to match the Metacat directory name of the Metacat you will be using.
+ - Set `data-metacat-context` to match the Metacat directory name of the Metacat you will be using.
For example, if your Metacat is installed at https://your-site.com/metacat, your `data-metacat-context` would be set to `metacat`. (Most Metacat installations are in a `metacat` directory sice that is the default Metacat directory name).
- - Make sure the path to the `loader.js` file is correct. By default, it is set to `/metacatui/loader.js`, but if you have your MetacatUI installed at root, (e.g. http://localhost:3000/), the path would be `/loader.js`.
- - *Optional:* Set the `data-theme` to your chosen theme name, e.g. `default`, `knb`, `arctic`.
- - *Optional:* Replace `YOUR-GOOGLE-MAPS-API-KEY` with your [Google Maps API key](https://developers.google.com/maps/documentation/javascript/get-api-key) to enable the Google Map features of MetacatUI. If no API key is given, MetacatUI will still work, it just will not include the map features.
+ - Make sure the path to the `loader.js` file is correct. By default, it is set to `/metacatui/loader.js`, but if you have your MetacatUI installed at root, (e.g. http://localhost:3000/), the path would be `/loader.js`.
+ - _Optional:_ Set the `data-theme` to your chosen theme name, e.g. `default`, `knb`, `arctic`.
+ - _Optional:_ Replace `YOUR-GOOGLE-MAPS-API-KEY` with your [Google Maps API key](https://developers.google.com/maps/documentation/javascript/get-api-key) to enable the Google Map features of MetacatUI. If no API key is given, MetacatUI will still work, it just will not include the map features.
## Step 2. Configure `loader.js`
+
- Open `src/loader.js` in a text editor and change the following values:
- - Make sure the `MetacatUI.root` path is the correct path to your MetacatUI installation. By default, it is set to `/metacatui`, but if you have your MetacatUI installed at root, (e.g. http://localhost:3000/), the path would be `/`.
+ - Make sure the `MetacatUI.root` path is the correct path to your MetacatUI installation. By default, it is set to `/metacatui`, but if you have your MetacatUI installed at root, (e.g. http://localhost:3000/), the path would be `/`.
## Step 3. Configure the `AppModel`
+
- Open `src/js/models/AppModel.js` (or if using a theme other than the default theme, `src/js/themes/{theme name}/models/AppModel.js`) and change the following values:
- - Set `baseUrl` to the URL where Metacat is deployed (e.g. `https://your-site.com`). This is concatinated with `data-metacat-context` earlier to create the full Metacat URL.
- - Set `d1CNBaseUrl` to the base URL of the DataONE Coordinating Node environment that the Metacat repository is a part of. (e.g. the DataONE test member Metacat repository, `urn:node:mnTestKNB`, is in the `urn:node:cnStage2` Coordinating Node, so this attribute would be set to `https://cn-stage-2.test.dataone.org/`)
- - *Optional:* The `AppModel` contains MetacatUI settings that control many MetacatUI features. You may want to configure some of these differently, according to your needs. See the [`AppModel` documentation](https://nceas.github.io/metacatui/docs/AppModel.html#defaults) for details.
+ - Set `baseUrl` to the URL where Metacat is deployed (e.g. `https://your-site.com`). This is concatinated with `data-metacat-context` earlier to create the full Metacat URL.
+ - Set `d1CNBaseUrl` to the base URL of the DataONE Coordinating Node environment that the Metacat repository is a part of. (e.g. the DataONE test member Metacat repository, `urn:node:mnTestKNB`, is in the `urn:node:cnStage2` Coordinating Node, so this attribute would be set to `https://cn-stage-2.test.dataone.org/`)
+ - _Optional:_ The `AppModel` contains MetacatUI settings that control many MetacatUI features. You may want to configure some of these differently, according to your needs. See the [`AppModel` documentation](https://nceas.github.io/metacatui/docs/AppModel.html#defaults) for details.
diff --git a/docs/install/dev-env.md b/docs/install/dev-env.md
index 554e09ce3..908e22441 100644
--- a/docs/install/dev-env.md
+++ b/docs/install/dev-env.md
@@ -7,6 +7,7 @@ The first task is to get a development environment set up on your machine, which
3. [Install a local instance of MetacatUI](#install-a-local-instance-of-metacatui)
## Get familiar with MetacatUI
+
Read about the MetacatUI software on the MetacatUI Github (https://github.com/NCEAS/metacatui) and documentation website (https://nceas.github.io/metacatui/)
## Fork the MetacatUI repository
@@ -24,6 +25,7 @@ Open the forked repository (the `metacatui` folder) in your preferred code edito
Test that your fork is ready to work with by making a small change to a file (e.g. add a space character) and committing and pushing the commit to your fork.
## Install a local instance of MetacatUI
+
Follow the [installation instructions](local.html) for using MetacatUI locally with a remote Metacat data repository.
When you have MetacatUI running locally, you should be able to navigate in your web browser to the localhost path that you have your server running at, and you'll see MetacatUI running like this:
diff --git a/docs/install/index.md b/docs/install/index.md
index 22c1bbaa9..fb38cb58f 100644
--- a/docs/install/index.md
+++ b/docs/install/index.md
@@ -11,6 +11,7 @@ is bundled in the Metacat release.
## Step 2. Configure MetacatUI
### For MetacatUI v2.12.0 and later
+
MetacatUI will work out-of-box without a configuration file, but if you have customized
any part of the Metacat installation process, you may need to configure MetacatUI.
@@ -20,33 +21,34 @@ all available config options.
A quick-start `config.js` could look like:
- ```javascript
- MetacatUI.AppConfig = {
- //The path to the root location of MetacatUI, i.e. where index.html is
- root: "/",
- //The path to the root location of Metacat, i.e. name of the Metacat Tomcat webapp
- metacatContext: "/metacat",
- //Your Google Maps API key, for map features
- mapKey: "YOUR-GOOGLE-MAPS-KEY"
- }
- ```
+```javascript
+MetacatUI.AppConfig = {
+ //The path to the root location of MetacatUI, i.e. where index.html is
+ root: "/",
+ //The path to the root location of Metacat, i.e. name of the Metacat Tomcat webapp
+ metacatContext: "/metacat",
+ //Your Google Maps API key, for map features
+ mapKey: "YOUR-GOOGLE-MAPS-KEY",
+};
+```
Change the `appConfigPath` in `index.html` to the location where you will be deploying your `config.js` file.
- ```html
- ...
-
- ...
- ```
+```html
+...
+
+...
+```
### For MetacatUI v2.11.5 and earlier
+
See the [config documentation for MetacatUI 2.11.5 and earlier](configuration/pre-2.12.0.html)
> MetacatUI 2.12.0+ can still be configured via index.html like it used to, but that will be deprecated in future releases.
-It's recommended that MetacatUI be configured via an external config.js file in v 2.12.0 and later.
+> It's recommended that MetacatUI be configured via an external config.js file in v 2.12.0 and later.
## Step 3. Configure Apache for MetacatUI
@@ -54,10 +56,10 @@ Follow the steps in the [Apache configuration instructions](apache).
Then, move the MetacatUI files in the `src` directory to the Apache web directory. Example:
- ```bash
- cd metacatui-2.11.2
- mv -rf src/* /var/www/
- ```
+```bash
+ cd metacatui-2.11.2
+ mv -rf src/* /var/www/
+```
## Step 4. DONE!
diff --git a/docs/install/local.md b/docs/install/local.md
index 1f04dcd93..bd2862801 100644
--- a/docs/install/local.md
+++ b/docs/install/local.md
@@ -20,7 +20,7 @@ Following are instructions for two local web server options - Node & Express JS
### Server Option 1. NodeJS & ExpressJS (recommended)
-*Requirements:* [NodeJS](https://nodejs.org/en/download/) and ExpressJS.
+_Requirements:_ [NodeJS](https://nodejs.org/en/download/) and ExpressJS.
MetacatUI also comes with a simple script that runs a [node.js](https://nodejs.org) application called [Express.js](https://expressjs.com), which can serve MetacatUI.
@@ -39,11 +39,12 @@ npm run dev # or equivalent to node server.js
### Server Option 2. Apache
See the [Apache configuration instructions](apache.html).
-The Apache instructions are *not* updated regularly, since we recommend you use the NodeJS Express server instead.
+The Apache instructions are _not_ updated regularly, since we recommend you use the NodeJS Express server instead.
## Step 3. Configure MetacatUI
### For MetacatUI v2.12.0 and later
+
MetacatUI will work out-of-box without a configuration file, but if you have customized
any part of the Metacat installation process, you may need to configure MetacatUI.
@@ -53,33 +54,34 @@ all available config options.
A quick-start `config.js` could look like:
- ```javascript
- MetacatUI.AppConfig = {
- //The path to the root location of MetacatUI, i.e. where index.html is
- root: "/",
- //The path to the root location of Metacat, i.e. name of the Metacat Tomcat webapp
- metacatContext: "/metacat",
- //Your Google Maps API key, for map features
- mapKey: "YOUR-GOOGLE-MAPS-KEY"
- }
- ```
+```javascript
+MetacatUI.AppConfig = {
+ //The path to the root location of MetacatUI, i.e. where index.html is
+ root: "/",
+ //The path to the root location of Metacat, i.e. name of the Metacat Tomcat webapp
+ metacatContext: "/metacat",
+ //Your Google Maps API key, for map features
+ mapKey: "YOUR-GOOGLE-MAPS-KEY",
+};
+```
Change the `appConfigPath` in `index.html` to the location where you will be deploying your `config.js` file.
- ```html
- ...
-
- ...
- ```
+```html
+...
+
+...
+```
### For MetacatUI v2.11.5 and earlier
+
See the [config documentation for MetacatUI 2.11.5 and earlier](configuration/pre-2.12.0.html)
> MetacatUI 2.12.0+ can still be configured via index.html like it used to, but that will be deprecated in future releases.
-It's recommended that MetacatUI be configured via an external config.js file in v 2.12.0 and later.
+> It's recommended that MetacatUI be configured via an external config.js file in v 2.12.0 and later.
## Step 4. DONE!
diff --git a/docs/install/use-with-cn.md b/docs/install/use-with-cn.md
index ebc8d49f5..4c20a7720 100644
--- a/docs/install/use-with-cn.md
+++ b/docs/install/use-with-cn.md
@@ -1,6 +1,7 @@
# Installing MetacatUI locally to use with a remote DataONE Coordinating Node
## Step 1. Clone MetacatUI
+
- Clone the MetacatUI git repository:
```
@@ -10,6 +11,7 @@ git clone https://github.com/NCEAS/metacatui.git
## Step 2. Configure MetacatUI
### For MetacatUI v2.12.0 and later
+
MetacatUI will work out-of-box without a configuration file, but if you have customized
any part of the Metacat installation process, you may need to configure MetacatUI.
@@ -19,39 +21,41 @@ all available config options.
A quick-start `config.js` could look like:
- ```javascript
- MetacatUI.AppConfig = {
- //The path to the root location of MetacatUI, i.e. where index.html is
- root: "/",
- //Your Google Maps API key, for map features
- mapKey: "YOUR-GOOGLE-MAPS-KEY",
- //Choose the dataone theme, which is already set up with all the CN values
- theme: "dataone"
- }
- ```
+```javascript
+MetacatUI.AppConfig = {
+ //The path to the root location of MetacatUI, i.e. where index.html is
+ root: "/",
+ //Your Google Maps API key, for map features
+ mapKey: "YOUR-GOOGLE-MAPS-KEY",
+ //Choose the dataone theme, which is already set up with all the CN values
+ theme: "dataone",
+};
+```
Change the `appConfigPath` in `index.html` to the location where you will be deploying your `config.js` file.
- ```html
- ...
-
- ...
- ```
+```html
+...
+
+...
+```
### For MetacatUI v2.11.5 and earlier
+
See the [config documentation for MetacatUI 2.11.5 and earlier](configuration/pre-2.12.0.html) but
make the following adjustments to Step 1 (index.html):
- - Set the `data-theme` to `dataone`.
- - Remove the value of `data-metacat-context` since DataONE CN URLs do not have a metacat directory
+- Set the `data-theme` to `dataone`.
+- Remove the value of `data-metacat-context` since DataONE CN URLs do not have a metacat directory
> MetacatUI 2.12.0+ can still be configured via index.html like it used to, but that will be deprecated in future releases.
-It's recommended that MetacatUI be configured via an external config.js file in v 2.12.0 and later.
+> It's recommended that MetacatUI be configured via an external config.js file in v 2.12.0 and later.
## Step 3. Set up a web server
+
Follow Step 2 from the ["Installing MetacatUI locally for development"](local.html#step-2-set-up-a-local-web-server) instructions to configure a web server on your local machine, or follow the [Apache configuration instructions](apache.html) if you already have Apache installed.
## Step 4. DONE!
diff --git a/docs/jsdoc-templates/metacatui/conf.js b/docs/jsdoc-templates/metacatui/conf.js
index 39e0ade06..ee0dc3f72 100644
--- a/docs/jsdoc-templates/metacatui/conf.js
+++ b/docs/jsdoc-templates/metacatui/conf.js
@@ -1,29 +1,29 @@
-'use strict';
+"use strict";
module.exports = {
- "source": {
- "excludePattern": "js/themes/|components/|config/|css/|font/|img/",
- "include": ["./src", "docs/other/addtlDocs.jsdoc"]
+ source: {
+ excludePattern: "js/themes/|components/|config/|css/|font/|img/",
+ include: ["./src", "docs/other/addtlDocs.jsdoc"],
},
- "plugins": ["./docs/jsdoc-templates/metacatui/plugins/screenshot",
- "./docs/jsdoc-templates/metacatui/plugins/classcategory"],
- "screenshot": {
- "dir": "../screenshots"
+ plugins: [
+ "./docs/jsdoc-templates/metacatui/plugins/screenshot",
+ "./docs/jsdoc-templates/metacatui/plugins/classcategory",
+ ],
+ screenshot: {
+ dir: "../screenshots",
},
- "templates": {
- "default": {
- "outputSourceFiles" : true,
- "layoutFile" : "./docs/jsdoc-templates/metacatui/tmpl/layout.tmpl",
- "staticFiles": {
- "include": [
- "./docs/jsdoc-templates/metacatui/static/styles/style.css"
- ]
- }
- }
+ templates: {
+ default: {
+ outputSourceFiles: true,
+ layoutFile: "./docs/jsdoc-templates/metacatui/tmpl/layout.tmpl",
+ staticFiles: {
+ include: ["./docs/jsdoc-templates/metacatui/static/styles/style.css"],
+ },
+ },
},
- "opts": {
- "template": "./docs/jsdoc-templates/metacatui",
- "destination": "./docs/docs",
- "recurse": true
- }
-}
+ opts: {
+ template: "./docs/jsdoc-templates/metacatui",
+ destination: "./docs/docs",
+ recurse: true,
+ },
+};
diff --git a/docs/jsdoc-templates/metacatui/plugins/classcategory.js b/docs/jsdoc-templates/metacatui/plugins/classcategory.js
index 26b8e1d5d..7c40b6205 100644
--- a/docs/jsdoc-templates/metacatui/plugins/classcategory.js
+++ b/docs/jsdoc-templates/metacatui/plugins/classcategory.js
@@ -1,13 +1,13 @@
-exports.defineTags = function(dictionary) {
+exports.defineTags = function (dictionary) {
/* Define the @classcategory tag */
- dictionary.defineTag('classcategory', {
- canHaveType: false,
- canHaveName: false,
- isNamespace: false,
- mustHaveValue: true,
- mustNotHaveDescription: false,
- onTagged: function(doclet, tag){
- doclet.classcategory = tag.value;
- }
+ dictionary.defineTag("classcategory", {
+ canHaveType: false,
+ canHaveName: false,
+ isNamespace: false,
+ mustHaveValue: true,
+ mustNotHaveDescription: false,
+ onTagged: function (doclet, tag) {
+ doclet.classcategory = tag.value;
+ },
});
};
diff --git a/docs/jsdoc-templates/metacatui/plugins/screenshot.js b/docs/jsdoc-templates/metacatui/plugins/screenshot.js
index c4c911d57..192413444 100644
--- a/docs/jsdoc-templates/metacatui/plugins/screenshot.js
+++ b/docs/jsdoc-templates/metacatui/plugins/screenshot.js
@@ -1,61 +1,64 @@
/*
-* Screenshot JSDoc plugin
-* This is a simple JSDoc plugin that inserts images into the JSDoc HTML files.
-* This plugin creates a new tag, named @screenshot, whose value is an absolute or relative
-* URL to an image file. Although the image contents don't need to be of a screenshot,
-* the tag name (@screenshot) was chosen to provide context tto the image contents
-* when looking at the code itself.
-*
-* ----- To use -----
-* Step 1. Add this screenshot.js file as a plugin to your JSDoc configuration, and specify a
-* directory where your screenshot images will be stored.
-*
-* JSDoc config file snippet:
-* "plugins": ["jsdoc-plugins/screenshot"],
-* "screenshot": {
-* "dir": "../screenshots"
-* }
-*
-* Step 2. Add the following code to your JSDoc template(s) to show the screenshot image.
-*
-*
-*
Screenshot
-*
-*
-*
-*
-* Step 3. Use the new @screenshot tag in your JSDoc comments.
-* Example @screenshot tags:
-* @screenshot MyView.png
-* @screenshot https://my-site.com/MyView.png
-*
-* Author: Lauren Walker https://github.com/laurenwalker
-* @todo: Create a template partial for the screenshot image
-* @todo: Externalize this documentation
-*/
+ * Screenshot JSDoc plugin
+ * This is a simple JSDoc plugin that inserts images into the JSDoc HTML files.
+ * This plugin creates a new tag, named @screenshot, whose value is an absolute or relative
+ * URL to an image file. Although the image contents don't need to be of a screenshot,
+ * the tag name (@screenshot) was chosen to provide context tto the image contents
+ * when looking at the code itself.
+ *
+ * ----- To use -----
+ * Step 1. Add this screenshot.js file as a plugin to your JSDoc configuration, and specify a
+ * directory where your screenshot images will be stored.
+ *
+ * JSDoc config file snippet:
+ * "plugins": ["jsdoc-plugins/screenshot"],
+ * "screenshot": {
+ * "dir": "../screenshots"
+ * }
+ *
+ * Step 2. Add the following code to your JSDoc template(s) to show the screenshot image.
+ *
+ *
+ *
Screenshot
+ *
+ *
+ *
+ *
+ * Step 3. Use the new @screenshot tag in your JSDoc comments.
+ * Example @screenshot tags:
+ * @screenshot MyView.png
+ * @screenshot https://my-site.com/MyView.png
+ *
+ * Author: Lauren Walker https://github.com/laurenwalker
+ * @todo: Create a template partial for the screenshot image
+ * @todo: Externalize this documentation
+ */
-exports.defineTags = function(dictionary) {
+exports.defineTags = function (dictionary) {
/* Define the @screenshot tag */
- dictionary.defineTag('screenshot', {
- canHaveType: false,
- canHaveName: false,
- isNamespace: false,
- mustHaveValue: true,
- mustNotHaveDescription: false,
- onTagged: function(doclet, tag){
+ dictionary.defineTag("screenshot", {
+ canHaveType: false,
+ canHaveName: false,
+ isNamespace: false,
+ mustHaveValue: true,
+ mustNotHaveDescription: false,
+ onTagged: function (doclet, tag) {
+ var imageURL = "";
- var imageURL = "";
-
- //Relative links will use the "screenshot.dir" attribute from the JSDoc configuration file.
- if(env.conf.screenshot && env.conf.screenshot.dir && tag.value.substring(0,4) != "http"){
- imageURL = env.conf.screenshot.dir + "/" + tag.value;
- }
- //Use the tag value as-is if it starts with "http" (absolute URL), or if there is no screenshot directory configured
- else{
- imageURL = tag.value;
- }
-
- doclet.screenshot = imageURL;
+ //Relative links will use the "screenshot.dir" attribute from the JSDoc configuration file.
+ if (
+ env.conf.screenshot &&
+ env.conf.screenshot.dir &&
+ tag.value.substring(0, 4) != "http"
+ ) {
+ imageURL = env.conf.screenshot.dir + "/" + tag.value;
}
+ //Use the tag value as-is if it starts with "http" (absolute URL), or if there is no screenshot directory configured
+ else {
+ imageURL = tag.value;
+ }
+
+ doclet.screenshot = imageURL;
+ },
});
};
diff --git a/docs/jsdoc-templates/metacatui/publish.js b/docs/jsdoc-templates/metacatui/publish.js
index 721d5dd63..2aacf061b 100644
--- a/docs/jsdoc-templates/metacatui/publish.js
+++ b/docs/jsdoc-templates/metacatui/publish.js
@@ -1,35 +1,29 @@
-const _ = require('lodash');
-const commonPathPrefix = require('common-path-prefix');
-const env = require('jsdoc/env');
-const fs = require('fs');
-const helper = require('jsdoc/util/templateHelper');
-const { log } = require('@jsdoc/util');
-const { lsSync } = require('@jsdoc/util').fs;
-const path = require('path');
-const { taffy } = require('@jsdoc/salty');
-const template = require('jsdoc/template');
+const _ = require("lodash");
+const commonPathPrefix = require("common-path-prefix");
+const env = require("jsdoc/env");
+const fs = require("fs");
+const helper = require("jsdoc/util/templateHelper");
+const { log } = require("@jsdoc/util");
+const { lsSync } = require("@jsdoc/util").fs;
+const path = require("path");
+const { taffy } = require("@jsdoc/salty");
+const template = require("jsdoc/template");
const htmlsafe = helper.htmlsafe;
const linkto = helper.linkto;
const resolveAuthorLinks = helper.resolveAuthorLinks;
const hasOwnProp = Object.prototype.hasOwnProperty;
-
const FONT_NAMES = [
- 'OpenSans-Bold',
- 'OpenSans-BoldItalic',
- 'OpenSans-Italic',
- 'OpenSans-Light',
- 'OpenSans-LightItalic',
- 'OpenSans-Regular'
-];
-const PRETTIFIER_CSS_FILES = [
- 'tomorrow.min.css'
-];
-const PRETTIFIER_SCRIPT_FILES = [
- 'lang-css.js',
- 'prettify.js'
+ "OpenSans-Bold",
+ "OpenSans-BoldItalic",
+ "OpenSans-Italic",
+ "OpenSans-Light",
+ "OpenSans-LightItalic",
+ "OpenSans-Regular",
];
+const PRETTIFIER_CSS_FILES = ["tomorrow.min.css"];
+const PRETTIFIER_SCRIPT_FILES = ["lang-css.js", "prettify.js"];
let data;
let view;
@@ -37,242 +31,255 @@ let view;
let outdir = path.normalize(env.opts.destination);
function mkdirpSync(filepath) {
- return fs.mkdirSync(filepath, { recursive: true });
+ return fs.mkdirSync(filepath, { recursive: true });
}
function find(spec) {
- return helper.find(data, spec);
+ return helper.find(data, spec);
}
function getAncestorLinks(doclet) {
- return helper.getAncestorLinks(data, doclet);
+ return helper.getAncestorLinks(data, doclet);
}
function hashToLink(doclet, hash) {
- let url;
+ let url;
- if ( !/^(#.+)/.test(hash) ) {
- return hash;
- }
+ if (!/^(#.+)/.test(hash)) {
+ return hash;
+ }
- url = helper.createLink(doclet);
- url = url.replace(/(#.+|$)/, hash);
+ url = helper.createLink(doclet);
+ url = url.replace(/(#.+|$)/, hash);
- return `${hash} `;
+ return `${hash} `;
}
-function needsSignature({kind, type, meta}) {
- let needsSig = false;
-
- // function and class definitions always get a signature
- if (kind === 'function' || kind === 'class') {
+function needsSignature({ kind, type, meta }) {
+ let needsSig = false;
+
+ // function and class definitions always get a signature
+ if (kind === "function" || kind === "class") {
+ needsSig = true;
+ }
+ // typedefs that contain functions get a signature, too
+ else if (kind === "typedef" && type && type.names && type.names.length) {
+ for (let i = 0, l = type.names.length; i < l; i++) {
+ if (type.names[i].toLowerCase() === "function") {
needsSig = true;
+ break;
+ }
}
- // typedefs that contain functions get a signature, too
- else if (kind === 'typedef' && type && type.names &&
- type.names.length) {
- for (let i = 0, l = type.names.length; i < l; i++) {
- if (type.names[i].toLowerCase() === 'function') {
- needsSig = true;
- break;
- }
- }
- }
- // and namespaces that are functions get a signature (but finding them is a
- // bit messy)
- else if (kind === 'namespace' && meta && meta.code &&
- meta.code.type && meta.code.type.match(/[Ff]unction/)) {
- needsSig = true;
- }
-
- return needsSig;
+ }
+ // and namespaces that are functions get a signature (but finding them is a
+ // bit messy)
+ else if (
+ kind === "namespace" &&
+ meta &&
+ meta.code &&
+ meta.code.type &&
+ meta.code.type.match(/[Ff]unction/)
+ ) {
+ needsSig = true;
+ }
+
+ return needsSig;
}
-function getSignatureAttributes({optional, nullable}) {
- const attributes = [];
+function getSignatureAttributes({ optional, nullable }) {
+ const attributes = [];
- if (optional) {
- attributes.push('opt');
- }
+ if (optional) {
+ attributes.push("opt");
+ }
- if (nullable === true) {
- attributes.push('nullable');
- }
- else if (nullable === false) {
- attributes.push('non-null');
- }
+ if (nullable === true) {
+ attributes.push("nullable");
+ } else if (nullable === false) {
+ attributes.push("non-null");
+ }
- return attributes;
+ return attributes;
}
function updateItemName(item) {
- const attributes = getSignatureAttributes(item);
- let itemName = item.name || '';
+ const attributes = getSignatureAttributes(item);
+ let itemName = item.name || "";
- if (item.variable) {
- itemName = `…${itemName}`;
- }
+ if (item.variable) {
+ itemName = `…${itemName}`;
+ }
- if (attributes && attributes.length) {
- itemName = `${itemName}${attributes.join(', ')} `;
- }
+ if (attributes && attributes.length) {
+ itemName = `${itemName}${attributes.join(", ")} `;
+ }
- return itemName;
+ return itemName;
}
function addParamAttributes(params) {
- return params.filter(({name}) => name && !name.includes('.')).map(updateItemName);
+ return params
+ .filter(({ name }) => name && !name.includes("."))
+ .map(updateItemName);
}
function buildItemTypeStrings(item) {
- const types = [];
+ const types = [];
- if (item && item.type && item.type.names) {
- item.type.names.forEach(name => {
- types.push( linkto(name, htmlsafe(name)) );
- });
- }
+ if (item && item.type && item.type.names) {
+ item.type.names.forEach((name) => {
+ types.push(linkto(name, htmlsafe(name)));
+ });
+ }
- return types;
+ return types;
}
function buildAttribsString(attribs) {
- let attribsString = '';
+ let attribsString = "";
- if (attribs && attribs.length) {
- htmlsafe(`(${attribs.join(', ')}) `);
- }
+ if (attribs && attribs.length) {
+ htmlsafe(`(${attribs.join(", ")}) `);
+ }
- return attribsString;
+ return attribsString;
}
function addNonParamAttributes(items) {
- let types = [];
+ let types = [];
- items.forEach(item => {
- types = types.concat( buildItemTypeStrings(item) );
- });
+ items.forEach((item) => {
+ types = types.concat(buildItemTypeStrings(item));
+ });
- return types;
+ return types;
}
function addSignatureParams(f) {
- const params = f.params ? addParamAttributes(f.params) : [];
+ const params = f.params ? addParamAttributes(f.params) : [];
- f.signature = `${f.signature || ''}(${params.join(', ')})`;
+ f.signature = `${f.signature || ""}(${params.join(", ")})`;
}
function addSignatureReturns(f) {
- const attribs = [];
- let attribsString = '';
- let returnTypes = [];
- let returnTypesString = '';
- const source = f.yields || f.returns;
-
- // jam all the return-type attributes into an array. this could create odd results (for example,
- // if there are both nullable and non-nullable return types), but let's assume that most people
- // who use multiple @return tags aren't using Closure Compiler type annotations, and vice-versa.
- if (source) {
- source.forEach(item => {
- helper.getAttribs(item).forEach(attrib => {
- if (!attribs.includes(attrib)) {
- attribs.push(attrib);
- }
- });
- });
+ const attribs = [];
+ let attribsString = "";
+ let returnTypes = [];
+ let returnTypesString = "";
+ const source = f.yields || f.returns;
+
+ // jam all the return-type attributes into an array. this could create odd results (for example,
+ // if there are both nullable and non-nullable return types), but let's assume that most people
+ // who use multiple @return tags aren't using Closure Compiler type annotations, and vice-versa.
+ if (source) {
+ source.forEach((item) => {
+ helper.getAttribs(item).forEach((attrib) => {
+ if (!attribs.includes(attrib)) {
+ attribs.push(attrib);
+ }
+ });
+ });
- attribsString = buildAttribsString(attribs);
- }
+ attribsString = buildAttribsString(attribs);
+ }
- if (source) {
- returnTypes = addNonParamAttributes(source);
- }
- if (returnTypes.length) {
- returnTypesString = ` → ${attribsString}{${returnTypes.join('|')}}`;
- }
+ if (source) {
+ returnTypes = addNonParamAttributes(source);
+ }
+ if (returnTypes.length) {
+ returnTypesString = ` → ${attribsString}{${returnTypes.join("|")}}`;
+ }
- f.signature = `${f.signature || ''} ` +
- `${returnTypesString} `;
+ f.signature =
+ `${f.signature || ""} ` +
+ `${returnTypesString} `;
}
function addSignatureTypes(f) {
- const types = f.type ? buildItemTypeStrings(f) : [];
+ const types = f.type ? buildItemTypeStrings(f) : [];
- f.signature = `${f.signature || ''}` +
- `${types.length ? ` :${types.join('|')}` : ''} `;
+ f.signature =
+ `${f.signature || ""}` +
+ `${types.length ? ` :${types.join("|")}` : ""} `;
}
function addAttribs(f) {
- const attribs = helper.getAttribs(f);
- const attribsString = buildAttribsString(attribs);
+ const attribs = helper.getAttribs(f);
+ const attribsString = buildAttribsString(attribs);
- f.attribs = `${attribsString} `;
+ f.attribs = `${attribsString} `;
}
function shortenPaths(files, commonPrefix) {
- Object.keys(files).forEach(file => {
- files[file].shortened = files[file].resolved.replace(commonPrefix, '')
- // always use forward slashes
- .replace(/\\/g, '/');
- });
-
- return files;
+ Object.keys(files).forEach((file) => {
+ files[file].shortened = files[file].resolved
+ .replace(commonPrefix, "")
+ // always use forward slashes
+ .replace(/\\/g, "/");
+ });
+
+ return files;
}
-function getPathFromDoclet({meta}) {
- if (!meta) {
- return null;
- }
+function getPathFromDoclet({ meta }) {
+ if (!meta) {
+ return null;
+ }
- return meta.path && meta.path !== 'null' ?
- path.join(meta.path, meta.filename) :
- meta.filename;
+ return meta.path && meta.path !== "null"
+ ? path.join(meta.path, meta.filename)
+ : meta.filename;
}
function generate(title, docs, filename, resolveLinks) {
- let docData;
- let html;
- let outpath;
+ let docData;
+ let html;
+ let outpath;
- resolveLinks = resolveLinks !== false;
+ resolveLinks = resolveLinks !== false;
- docData = {
- env: env,
- title: title,
- docs: docs
- };
+ docData = {
+ env: env,
+ title: title,
+ docs: docs,
+ };
- outpath = path.join(outdir, filename);
- html = view.render('container.tmpl', docData);
+ outpath = path.join(outdir, filename);
+ html = view.render("container.tmpl", docData);
- if (resolveLinks) {
- html = helper.resolveLinks(html); // turn {@link foo} into foo
- }
+ if (resolveLinks) {
+ html = helper.resolveLinks(html); // turn {@link foo} into foo
+ }
- fs.writeFileSync(outpath, html, 'utf8');
+ fs.writeFileSync(outpath, html, "utf8");
}
-function generateSourceFiles(sourceFiles, encoding = 'utf8') {
- Object.keys(sourceFiles).forEach(file => {
- let source;
- // links are keyed to the shortened path in each doclet's `meta.shortpath` property
- const sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened);
-
- helper.registerLink(sourceFiles[file].shortened, sourceOutfile);
-
- try {
- source = {
- kind: 'source',
- code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, encoding) )
- };
- }
- catch (e) {
- log.error(`Error while generating source file ${file}: ${e.message}`);
- }
+function generateSourceFiles(sourceFiles, encoding = "utf8") {
+ Object.keys(sourceFiles).forEach((file) => {
+ let source;
+ // links are keyed to the shortened path in each doclet's `meta.shortpath` property
+ const sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened);
+
+ helper.registerLink(sourceFiles[file].shortened, sourceOutfile);
+
+ try {
+ source = {
+ kind: "source",
+ code: helper.htmlsafe(
+ fs.readFileSync(sourceFiles[file].resolved, encoding),
+ ),
+ };
+ } catch (e) {
+ log.error(`Error while generating source file ${file}: ${e.message}`);
+ }
- generate(`Source: ${sourceFiles[file].shortened}`, [source], sourceOutfile,
- false);
- });
+ generate(
+ `Source: ${sourceFiles[file].shortened}`,
+ [source],
+ sourceOutfile,
+ false,
+ );
+ });
}
/**
@@ -287,108 +294,109 @@ function generateSourceFiles(sourceFiles, encoding = 'utf8') {
* @param {Array.} modules - The array of module doclets to search.
*/
function attachModuleSymbols(doclets, modules) {
- const symbols = {};
-
- // build a lookup table
- doclets.forEach(symbol => {
- symbols[symbol.longname] = symbols[symbol.longname] || [];
- symbols[symbol.longname].push(symbol);
- });
+ const symbols = {};
+
+ // build a lookup table
+ doclets.forEach((symbol) => {
+ symbols[symbol.longname] = symbols[symbol.longname] || [];
+ symbols[symbol.longname].push(symbol);
+ });
+
+ modules.forEach((module) => {
+ if (symbols[module.longname]) {
+ module.modules = symbols[module.longname]
+ // Only show symbols that have a description. Make an exception for classes, because
+ // we want to show the constructor-signature heading no matter what.
+ .filter(({ description, kind }) => description || kind === "class")
+ .map((symbol) => {
+ symbol = _.cloneDeep(symbol);
+
+ if (symbol.kind === "class" || symbol.kind === "function") {
+ symbol.name = `${symbol.name.replace("module:", '(require("')}"))`;
+ }
- modules.forEach(module => {
- if (symbols[module.longname]) {
- module.modules = symbols[module.longname]
- // Only show symbols that have a description. Make an exception for classes, because
- // we want to show the constructor-signature heading no matter what.
- .filter(({description, kind}) => description || kind === 'class')
- .map(symbol => {
- symbol = _.cloneDeep(symbol);
-
- if (symbol.kind === 'class' || symbol.kind === 'function') {
- symbol.name = `${symbol.name.replace('module:', '(require("')}"))`;
- }
-
- return symbol;
- });
- }
- });
+ return symbol;
+ });
+ }
+ });
}
function buildMemberNav(items, itemHeading, itemsSeen, linktoFn) {
- let nav = '';
+ let nav = "";
- if (items.length) {
- let itemsNav = '';
+ if (items.length) {
+ let itemsNav = "";
- //Organize the items into categories based on the 'classcategory' tag.
- var organizedItems = {},
- categoryNames = [];
+ //Organize the items into categories based on the 'classcategory' tag.
+ var organizedItems = {},
+ categoryNames = [];
- for( var i=0; i -1) {
- categoryNames.splice(index, 1);
- categoryNames.push("Deprecated")
- }
+ //Sort the category names alphabetically, with "other" placed last
+ categoryNames.sort();
+ //Add the Other category to the end
+ categoryNames.push("Other");
- categoryNames.forEach(category => {
+ //Move the Deprecated category to the end
+ const index = categoryNames.indexOf("Deprecated");
+ if (index > -1) {
+ categoryNames.splice(index, 1);
+ categoryNames.push("Deprecated");
+ }
- //Add a heading for the category, only if there is more than one category
- // and there is at least one item in the category
- if( categoryNames.length > 1 && category != "Other" ){
- itemsNav += "" + category + " ";
+ categoryNames.forEach((category) => {
+ //Add a heading for the category, only if there is more than one category
+ // and there is at least one item in the category
+ if (categoryNames.length > 1 && category != "Other") {
+ itemsNav +=
+ "" +
+ category +
+ " ";
+ }
+
+ organizedItems[category].forEach((item) => {
+ let displayName;
+
+ if (!hasOwnProp.call(item, "longname")) {
+ itemsNav += `${linktoFn("", item.name)} `;
+ } else if (!hasOwnProp.call(itemsSeen, item.longname)) {
+ if (env.conf.templates.default.useLongnameInNav) {
+ displayName = item.longname;
+ } else {
+ displayName = item.name;
}
+ itemsNav += `${linktoFn(item.longname, displayName.replace(/\b(module|event):/g, ""))} `;
- organizedItems[category].forEach(item => {
- let displayName;
-
- if ( !hasOwnProp.call(item, 'longname') ) {
- itemsNav += `${linktoFn('', item.name)} `;
- }
- else if ( !hasOwnProp.call(itemsSeen, item.longname) ) {
- if (env.conf.templates.default.useLongnameInNav) {
- displayName = item.longname;
- } else {
- displayName = item.name;
- }
- itemsNav += `${linktoFn(item.longname, displayName.replace(/\b(module|event):/g, ''))} `;
-
- itemsSeen[item.longname] = true;
- }
- });
- });
-
- if (itemsNav !== '') {
- nav += `${itemHeading} `;
+ itemsSeen[item.longname] = true;
}
+ });
+ });
+
+ if (itemsNav !== "") {
+ nav += `${itemHeading} `;
}
+ }
- return nav;
+ return nav;
}
function linktoExternal(longName, name) {
- return linkto(longName, name.replace(/(^"|"$)/g, ''));
+ return linkto(longName, name.replace(/(^"|"$)/g, ""));
}
/**
@@ -405,44 +413,43 @@ function linktoExternal(longName, name) {
* @return {string} The HTML for the navigation sidebar.
*/
function buildNav(members) {
- let globalNav;
- let nav = ''; //'';
- const seen = {};
-
- nav += buildMemberNav(members.modules, 'Modules', {}, linkto);
- nav += buildMemberNav(members.externals, 'Externals', seen, linktoExternal);
- nav += buildMemberNav(members.namespaces, 'Namespaces', seen, linkto);
- nav += buildMemberNav(members.classes, 'Classes', seen, linkto);
- nav += buildMemberNav(members.interfaces, 'Interfaces', seen, linkto);
- nav += buildMemberNav(members.events, 'Events', seen, linkto);
- nav += buildMemberNav(members.mixins, 'Mixins', seen, linkto);
-
- if (members.globals.length) {
- globalNav = '';
-
- members.globals.forEach(({kind, longname, name}) => {
- if ( kind !== 'typedef' && !hasOwnProp.call(seen, longname) ) {
- globalNav += `${linkto(longname, name)} `;
- }
- seen[longname] = true;
- });
+ let globalNav;
+ let nav = ""; //'';
+ const seen = {};
+
+ nav += buildMemberNav(members.modules, "Modules", {}, linkto);
+ nav += buildMemberNav(members.externals, "Externals", seen, linktoExternal);
+ nav += buildMemberNav(members.namespaces, "Namespaces", seen, linkto);
+ nav += buildMemberNav(members.classes, "Classes", seen, linkto);
+ nav += buildMemberNav(members.interfaces, "Interfaces", seen, linkto);
+ nav += buildMemberNav(members.events, "Events", seen, linkto);
+ nav += buildMemberNav(members.mixins, "Mixins", seen, linkto);
+
+ if (members.globals.length) {
+ globalNav = "";
+
+ members.globals.forEach(({ kind, longname, name }) => {
+ if (kind !== "typedef" && !hasOwnProp.call(seen, longname)) {
+ globalNav += `${linkto(longname, name)} `;
+ }
+ seen[longname] = true;
+ });
- if (!globalNav) {
- // turn the heading into a link so you can actually get to the global page
- nav += `${linkto('global', 'Global')} `;
- }
- else {
- nav += `Global `;
- }
+ if (!globalNav) {
+ // turn the heading into a link so you can actually get to the global page
+ nav += `${linkto("global", "Global")} `;
+ } else {
+ nav += `Global `;
}
+ }
- return nav;
+ return nav;
}
function sourceToDestination(parentDir, sourcePath, destDir) {
- const relativeSource = path.relative(parentDir, sourcePath);
+ const relativeSource = path.relative(parentDir, sourcePath);
- return path.resolve(path.join(destDir, relativeSource));
+ return path.resolve(path.join(destDir, relativeSource));
}
/**
@@ -450,136 +457,142 @@ function sourceToDestination(parentDir, sourcePath, destDir) {
@param {object} opts
*/
exports.publish = (taffyData, opts) => {
- let classes;
- let conf;
- let cwd;
- let externals;
- let files;
- let fromDir;
- let globalUrl;
- let indexUrl;
- let interfaces;
- let members;
- let mixins;
- let modules;
- let namespaces;
- let outputSourceFiles;
- let packageInfo;
- let packages;
- const sourceFilePaths = [];
- let sourceFiles = {};
- let staticFileFilter;
- let staticFilePaths;
- let staticFiles;
- let staticFileScanner;
- let templatePath;
-
- data = taffyData;
-
- conf = env.conf.templates || {};
- conf.default = conf.default || {};
-
- templatePath = path.normalize(opts.template);
- view = new template.Template( path.join(templatePath, 'tmpl') );
-
- // claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness
- // doesn't try to hand them out later
- indexUrl = helper.getUniqueFilename('index');
- // don't call registerLink() on this one! 'index' is also a valid longname
-
- globalUrl = helper.getUniqueFilename('global');
- helper.registerLink('global', globalUrl);
-
- // set up templating
- view.layout = conf.default.layoutFile ?
- path.resolve(conf.default.layoutFile) :
- 'layout.tmpl';
-
- data = helper.prune(data);
- data.sort('longname, version, since');
- helper.addEventListeners(data);
-
- data().each(doclet => {
- let sourcePath;
-
- doclet.attribs = '';
-
- if (doclet.examples) {
- doclet.examples = doclet.examples.map(example => {
- let caption;
- let code;
-
- if (example.match(/^\s*([\s\S]+?)<\/caption>(\s*[\n\r])([\s\S]+)$/i)) {
- caption = RegExp.$1;
- code = RegExp.$3;
- }
-
- return {
- caption: caption || '',
- code: code || example
- };
- });
- }
- if (doclet.see) {
- doclet.see.forEach((seeItem, i) => {
- doclet.see[i] = hashToLink(doclet, seeItem);
- });
+ let classes;
+ let conf;
+ let cwd;
+ let externals;
+ let files;
+ let fromDir;
+ let globalUrl;
+ let indexUrl;
+ let interfaces;
+ let members;
+ let mixins;
+ let modules;
+ let namespaces;
+ let outputSourceFiles;
+ let packageInfo;
+ let packages;
+ const sourceFilePaths = [];
+ let sourceFiles = {};
+ let staticFileFilter;
+ let staticFilePaths;
+ let staticFiles;
+ let staticFileScanner;
+ let templatePath;
+
+ data = taffyData;
+
+ conf = env.conf.templates || {};
+ conf.default = conf.default || {};
+
+ templatePath = path.normalize(opts.template);
+ view = new template.Template(path.join(templatePath, "tmpl"));
+
+ // claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness
+ // doesn't try to hand them out later
+ indexUrl = helper.getUniqueFilename("index");
+ // don't call registerLink() on this one! 'index' is also a valid longname
+
+ globalUrl = helper.getUniqueFilename("global");
+ helper.registerLink("global", globalUrl);
+
+ // set up templating
+ view.layout = conf.default.layoutFile
+ ? path.resolve(conf.default.layoutFile)
+ : "layout.tmpl";
+
+ data = helper.prune(data);
+ data.sort("longname, version, since");
+ helper.addEventListeners(data);
+
+ data().each((doclet) => {
+ let sourcePath;
+
+ doclet.attribs = "";
+
+ if (doclet.examples) {
+ doclet.examples = doclet.examples.map((example) => {
+ let caption;
+ let code;
+
+ if (
+ example.match(
+ /^\s*([\s\S]+?)<\/caption>(\s*[\n\r])([\s\S]+)$/i,
+ )
+ ) {
+ caption = RegExp.$1;
+ code = RegExp.$3;
}
- // build a list of source files
- if (doclet.meta) {
- sourcePath = getPathFromDoclet(doclet);
- sourceFiles[sourcePath] = {
- resolved: sourcePath,
- shortened: null
- };
- if (!sourceFilePaths.includes(sourcePath)) {
- sourceFilePaths.push(sourcePath);
- }
- }
- });
+ return {
+ caption: caption || "",
+ code: code || example,
+ };
+ });
+ }
+ if (doclet.see) {
+ doclet.see.forEach((seeItem, i) => {
+ doclet.see[i] = hashToLink(doclet, seeItem);
+ });
+ }
- // update outdir if necessary, then create outdir
- packageInfo = ( find({kind: 'package'}) || [] )[0];
- if (packageInfo && packageInfo.name) {
- outdir = path.join( outdir, packageInfo.name, (packageInfo.version || '') );
+ // build a list of source files
+ if (doclet.meta) {
+ sourcePath = getPathFromDoclet(doclet);
+ sourceFiles[sourcePath] = {
+ resolved: sourcePath,
+ shortened: null,
+ };
+ if (!sourceFilePaths.includes(sourcePath)) {
+ sourceFilePaths.push(sourcePath);
+ }
}
- mkdirpSync(outdir);
+ });
- // copy the template's static files to outdir
- fromDir = path.join(templatePath, 'static');
- staticFiles = lsSync(fromDir);
+ // update outdir if necessary, then create outdir
+ packageInfo = (find({ kind: "package" }) || [])[0];
+ if (packageInfo && packageInfo.name) {
+ outdir = path.join(outdir, packageInfo.name, packageInfo.version || "");
+ }
+ mkdirpSync(outdir);
- staticFiles.forEach(fileName => {
- const toPath = sourceToDestination(fromDir, fileName, outdir);
+ // copy the template's static files to outdir
+ fromDir = path.join(templatePath, "static");
+ staticFiles = lsSync(fromDir);
- mkdirpSync(path.dirname(toPath));
- fs.copyFileSync(fileName, toPath);
- });
+ staticFiles.forEach((fileName) => {
+ const toPath = sourceToDestination(fromDir, fileName, outdir);
- // copy the fonts used by the template to outdir
- staticFiles = lsSync(path.join(require.resolve('open-sans-fonts'), '..', 'open-sans'));
+ mkdirpSync(path.dirname(toPath));
+ fs.copyFileSync(fileName, toPath);
+ });
- staticFiles.forEach(fileName => {
- const toPath = path.join(outdir, 'fonts', path.basename(fileName));
+ // copy the fonts used by the template to outdir
+ staticFiles = lsSync(
+ path.join(require.resolve("open-sans-fonts"), "..", "open-sans"),
+ );
- if (FONT_NAMES.includes(path.parse(fileName).name)) {
- mkdirpSync(path.dirname(toPath));
- fs.copyFileSync(fileName, toPath);
- }
- });
+ staticFiles.forEach((fileName) => {
+ const toPath = path.join(outdir, "fonts", path.basename(fileName));
- // copy the prettify script to outdir
- PRETTIFIER_SCRIPT_FILES.forEach(fileName => {
- const toPath = path.join(outdir, 'scripts', path.basename(fileName));
+ if (FONT_NAMES.includes(path.parse(fileName).name)) {
+ mkdirpSync(path.dirname(toPath));
+ fs.copyFileSync(fileName, toPath);
+ }
+ });
- fs.copyFileSync(
- path.join(require.resolve('code-prettify'), '..', fileName),
- toPath
- );
- });
+ // copy the prettify script to outdir
+ PRETTIFIER_SCRIPT_FILES.forEach((fileName) => {
+ const toPath = path.join(outdir, "scripts", path.basename(fileName));
- // copy the prettify CSS to outdir
+ fs.copyFileSync(
+ path.join(require.resolve("code-prettify"), "..", fileName),
+ toPath,
+ );
+ });
+
+ // copy the prettify CSS to outdir
/* PRETTIFIER_CSS_FILES.forEach(fileName => {
const toPath = path.join(outdir, 'styles', path.basename(fileName));
@@ -600,157 +613,192 @@ exports.publish = (taffyData, opts) => {
);
});*/
- // copy user-specified static files to outdir
- if (conf.default.staticFiles) {
- // The canonical property name is `include`. We accept `paths` for backwards compatibility
- // with a bug in JSDoc 3.2.x.
- staticFilePaths = conf.default.staticFiles.include ||
- conf.default.staticFiles.paths ||
- [];
- staticFileFilter = new (require('jsdoc/src/filter').Filter)(conf.default.staticFiles);
- staticFileScanner = new (require('jsdoc/src/scanner').Scanner)();
- cwd = process.cwd();
-
- staticFilePaths.forEach(filePath => {
- let extraStaticFiles;
-
- filePath = path.resolve(cwd, filePath);
- extraStaticFiles = staticFileScanner.scan([filePath], 10, staticFileFilter);
-
- extraStaticFiles.forEach(fileName => {
- const toPath = sourceToDestination(fromDir, fileName, outdir);
-
- mkdirpSync(path.dirname(toPath));
- fs.copyFileSync(fileName, toPath);
- });
- });
- }
-
- if (sourceFilePaths.length) {
- sourceFiles = shortenPaths( sourceFiles, commonPathPrefix(sourceFilePaths) );
- }
- data().each(doclet => {
- let docletPath;
- const url = helper.createLink(doclet);
-
- helper.registerLink(doclet.longname, url);
-
- // add a shortened version of the full path
- if (doclet.meta) {
- docletPath = getPathFromDoclet(doclet);
- docletPath = sourceFiles[docletPath].shortened;
- if (docletPath) {
- doclet.meta.shortpath = docletPath;
- }
- }
- });
-
- data().each(doclet => {
- const url = helper.longnameToUrl[doclet.longname];
-
- if (url.includes('#')) {
- doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop();
- }
- else {
- doclet.id = doclet.name;
- }
-
- if ( needsSignature(doclet) ) {
- addSignatureParams(doclet);
- addSignatureReturns(doclet);
- addAttribs(doclet);
- }
- });
-
- // do this after the urls have all been generated
- data().each(doclet => {
- doclet.ancestors = getAncestorLinks(doclet);
-
- if (doclet.kind === 'member') {
- addSignatureTypes(doclet);
- addAttribs(doclet);
- }
+ // copy user-specified static files to outdir
+ if (conf.default.staticFiles) {
+ // The canonical property name is `include`. We accept `paths` for backwards compatibility
+ // with a bug in JSDoc 3.2.x.
+ staticFilePaths =
+ conf.default.staticFiles.include || conf.default.staticFiles.paths || [];
+ staticFileFilter = new (require("jsdoc/src/filter").Filter)(
+ conf.default.staticFiles,
+ );
+ staticFileScanner = new (require("jsdoc/src/scanner").Scanner)();
+ cwd = process.cwd();
+
+ staticFilePaths.forEach((filePath) => {
+ let extraStaticFiles;
+
+ filePath = path.resolve(cwd, filePath);
+ extraStaticFiles = staticFileScanner.scan(
+ [filePath],
+ 10,
+ staticFileFilter,
+ );
+
+ extraStaticFiles.forEach((fileName) => {
+ const toPath = sourceToDestination(fromDir, fileName, outdir);
- if (doclet.kind === 'constant') {
- addSignatureTypes(doclet);
- addAttribs(doclet);
- doclet.kind = 'member';
- }
+ mkdirpSync(path.dirname(toPath));
+ fs.copyFileSync(fileName, toPath);
+ });
});
+ }
+
+ if (sourceFilePaths.length) {
+ sourceFiles = shortenPaths(sourceFiles, commonPathPrefix(sourceFilePaths));
+ }
+ data().each((doclet) => {
+ let docletPath;
+ const url = helper.createLink(doclet);
+
+ helper.registerLink(doclet.longname, url);
+
+ // add a shortened version of the full path
+ if (doclet.meta) {
+ docletPath = getPathFromDoclet(doclet);
+ docletPath = sourceFiles[docletPath].shortened;
+ if (docletPath) {
+ doclet.meta.shortpath = docletPath;
+ }
+ }
+ });
- members = helper.getMembers(data);
+ data().each((doclet) => {
+ const url = helper.longnameToUrl[doclet.longname];
- // output pretty-printed source files by default
- outputSourceFiles = conf.default && conf.default.outputSourceFiles !== false;
+ if (url.includes("#")) {
+ doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop();
+ } else {
+ doclet.id = doclet.name;
+ }
- // add template helpers
- view.find = find;
- view.linkto = linkto;
- view.resolveAuthorLinks = resolveAuthorLinks;
- view.htmlsafe = htmlsafe;
- view.outputSourceFiles = outputSourceFiles;
+ if (needsSignature(doclet)) {
+ addSignatureParams(doclet);
+ addSignatureReturns(doclet);
+ addAttribs(doclet);
+ }
+ });
- // once for all
- view.nav = buildNav(members);
- attachModuleSymbols( find({ longname: {left: 'module:'} }), members.modules );
+ // do this after the urls have all been generated
+ data().each((doclet) => {
+ doclet.ancestors = getAncestorLinks(doclet);
- // generate the pretty-printed source files first so other pages can link to them
- if (outputSourceFiles) {
- generateSourceFiles(sourceFiles, opts.encoding);
+ if (doclet.kind === "member") {
+ addSignatureTypes(doclet);
+ addAttribs(doclet);
}
- if (members.globals.length) { generate('Global', [{kind: 'globalobj'}], globalUrl); }
-
- // index page displays information from package.json and lists files
- files = find({kind: 'file'});
- packages = find({kind: 'package'});
-
- generate('Home',
- packages.concat(
- [{
- kind: 'mainpage',
- longname: (opts.mainpagetitle) ? opts.mainpagetitle : 'Main Page'
- }]
- ).concat(files), indexUrl);
-
- // set up the lists that we'll use to generate pages
- classes = taffy(members.classes);
- modules = taffy(members.modules);
- namespaces = taffy(members.namespaces);
- mixins = taffy(members.mixins);
- externals = taffy(members.externals);
- interfaces = taffy(members.interfaces);
-
- Object.keys(helper.longnameToUrl).forEach(longname => {
- const myClasses = helper.find(classes, {longname: longname});
- const myExternals = helper.find(externals, {longname: longname});
- const myInterfaces = helper.find(interfaces, {longname: longname});
- const myMixins = helper.find(mixins, {longname: longname});
- const myModules = helper.find(modules, {longname: longname});
- const myNamespaces = helper.find(namespaces, {longname: longname});
-
- if (myModules.length) {
- generate(`Module: ${myModules[0].name}`, myModules, helper.longnameToUrl[longname]);
- }
+ if (doclet.kind === "constant") {
+ addSignatureTypes(doclet);
+ addAttribs(doclet);
+ doclet.kind = "member";
+ }
+ });
+
+ members = helper.getMembers(data);
+
+ // output pretty-printed source files by default
+ outputSourceFiles = conf.default && conf.default.outputSourceFiles !== false;
+
+ // add template helpers
+ view.find = find;
+ view.linkto = linkto;
+ view.resolveAuthorLinks = resolveAuthorLinks;
+ view.htmlsafe = htmlsafe;
+ view.outputSourceFiles = outputSourceFiles;
+
+ // once for all
+ view.nav = buildNav(members);
+ attachModuleSymbols(find({ longname: { left: "module:" } }), members.modules);
+
+ // generate the pretty-printed source files first so other pages can link to them
+ if (outputSourceFiles) {
+ generateSourceFiles(sourceFiles, opts.encoding);
+ }
+
+ if (members.globals.length) {
+ generate("Global", [{ kind: "globalobj" }], globalUrl);
+ }
+
+ // index page displays information from package.json and lists files
+ files = find({ kind: "file" });
+ packages = find({ kind: "package" });
+
+ generate(
+ "Home",
+ packages
+ .concat([
+ {
+ kind: "mainpage",
+ longname: opts.mainpagetitle ? opts.mainpagetitle : "Main Page",
+ },
+ ])
+ .concat(files),
+ indexUrl,
+ );
+
+ // set up the lists that we'll use to generate pages
+ classes = taffy(members.classes);
+ modules = taffy(members.modules);
+ namespaces = taffy(members.namespaces);
+ mixins = taffy(members.mixins);
+ externals = taffy(members.externals);
+ interfaces = taffy(members.interfaces);
+
+ Object.keys(helper.longnameToUrl).forEach((longname) => {
+ const myClasses = helper.find(classes, { longname: longname });
+ const myExternals = helper.find(externals, { longname: longname });
+ const myInterfaces = helper.find(interfaces, { longname: longname });
+ const myMixins = helper.find(mixins, { longname: longname });
+ const myModules = helper.find(modules, { longname: longname });
+ const myNamespaces = helper.find(namespaces, { longname: longname });
+
+ if (myModules.length) {
+ generate(
+ `Module: ${myModules[0].name}`,
+ myModules,
+ helper.longnameToUrl[longname],
+ );
+ }
- if (myClasses.length) {
- generate(`Class: ${myClasses[0].name}`, myClasses, helper.longnameToUrl[longname]);
- }
+ if (myClasses.length) {
+ generate(
+ `Class: ${myClasses[0].name}`,
+ myClasses,
+ helper.longnameToUrl[longname],
+ );
+ }
- if (myNamespaces.length) {
- generate(`Namespace: ${myNamespaces[0].name}`, myNamespaces, helper.longnameToUrl[longname]);
- }
+ if (myNamespaces.length) {
+ generate(
+ `Namespace: ${myNamespaces[0].name}`,
+ myNamespaces,
+ helper.longnameToUrl[longname],
+ );
+ }
- if (myMixins.length) {
- generate(`Mixin: ${myMixins[0].name}`, myMixins, helper.longnameToUrl[longname]);
- }
+ if (myMixins.length) {
+ generate(
+ `Mixin: ${myMixins[0].name}`,
+ myMixins,
+ helper.longnameToUrl[longname],
+ );
+ }
- if (myExternals.length) {
- generate(`External: ${myExternals[0].name}`, myExternals, helper.longnameToUrl[longname]);
- }
+ if (myExternals.length) {
+ generate(
+ `External: ${myExternals[0].name}`,
+ myExternals,
+ helper.longnameToUrl[longname],
+ );
+ }
- if (myInterfaces.length) {
- generate(`Interface: ${myInterfaces[0].name}`, myInterfaces, helper.longnameToUrl[longname]);
- }
- });
+ if (myInterfaces.length) {
+ generate(
+ `Interface: ${myInterfaces[0].name}`,
+ myInterfaces,
+ helper.longnameToUrl[longname],
+ );
+ }
+ });
};
diff --git a/docs/jsdoc-templates/metacatui/static/scripts/linenumber.js b/docs/jsdoc-templates/metacatui/static/scripts/linenumber.js
index bdc5b4a8c..2e055f538 100644
--- a/docs/jsdoc-templates/metacatui/static/scripts/linenumber.js
+++ b/docs/jsdoc-templates/metacatui/static/scripts/linenumber.js
@@ -1,25 +1,25 @@
/* global document */
(() => {
- const source = document.getElementsByClassName('prettyprint source linenums');
- let i = 0;
- let lineNumber = 0;
- let lineId;
- let lines;
- let totalLines;
- let anchorHash;
+ const source = document.getElementsByClassName("prettyprint source linenums");
+ let i = 0;
+ let lineNumber = 0;
+ let lineId;
+ let lines;
+ let totalLines;
+ let anchorHash;
- if (source && source[0]) {
- anchorHash = document.location.hash.substring(1);
- lines = source[0].getElementsByTagName('li');
- totalLines = lines.length;
+ if (source && source[0]) {
+ anchorHash = document.location.hash.substring(1);
+ lines = source[0].getElementsByTagName("li");
+ totalLines = lines.length;
- for (; i < totalLines; i++) {
- lineNumber++;
- lineId = `line${lineNumber}`;
- lines[i].id = lineId;
- if (lineId === anchorHash) {
- lines[i].className += ' selected';
- }
- }
+ for (; i < totalLines; i++) {
+ lineNumber++;
+ lineId = `line${lineNumber}`;
+ lines[i].id = lineId;
+ if (lineId === anchorHash) {
+ lines[i].className += " selected";
+ }
}
+ }
})();
diff --git a/docs/jsdoc-templates/metacatui/static/styles/jsdoc-default.css b/docs/jsdoc-templates/metacatui/static/styles/jsdoc-default.css
index 940877795..65e0906c2 100644
--- a/docs/jsdoc-templates/metacatui/static/styles/jsdoc-default.css
+++ b/docs/jsdoc-templates/metacatui/static/styles/jsdoc-default.css
@@ -1,281 +1,302 @@
-html
-{
- overflow: auto;
- background-color: #fff;
- font-size: 14px;
+html {
+ overflow: auto;
+ background-color: #fff;
+ font-size: 14px;
}
-body
-{
- font-family: 'Open Sans', sans-serif;
- line-height: 1.5;
- color: #4d4e53;
- background-color: white;
+body {
+ font-family: "Open Sans", sans-serif;
+ line-height: 1.5;
+ color: #4d4e53;
+ background-color: white;
}
-a, a:visited, a:active {
- color: #0095dd;
- text-decoration: none;
+a,
+a:visited,
+a:active {
+ color: #0095dd;
+ text-decoration: none;
}
a:hover {
- text-decoration: underline;
+ text-decoration: underline;
}
-header
-{
- display: block;
- padding: 0px 4px;
+header {
+ display: block;
+ padding: 0px 4px;
}
-tt, code, kbd, samp {
- font-family: Consolas, Monaco, 'Andale Mono', monospace;
+tt,
+code,
+kbd,
+samp {
+ font-family: Consolas, Monaco, "Andale Mono", monospace;
}
.class-description {
- font-size: 130%;
- line-height: 140%;
- margin-bottom: 1em;
- margin-top: 1em;
+ font-size: 130%;
+ line-height: 140%;
+ margin-bottom: 1em;
+ margin-top: 1em;
}
.class-description:empty {
- margin: 0;
+ margin: 0;
}
#main {
- float: left;
- width: 70%;
+ float: left;
+ width: 70%;
}
article dl {
- margin-bottom: 40px;
+ margin-bottom: 40px;
}
article img {
max-width: 100%;
}
-section
-{
- display: block;
- background-color: #fff;
- padding: 12px 24px;
- border-bottom: 1px solid #ccc;
- margin-right: 30px;
+section {
+ display: block;
+ background-color: #fff;
+ padding: 12px 24px;
+ border-bottom: 1px solid #ccc;
+ margin-right: 30px;
}
.variation {
- display: none;
+ display: none;
}
.signature-attributes {
- font-size: 60%;
- color: #aaa;
- font-style: italic;
- font-weight: lighter;
+ font-size: 60%;
+ color: #aaa;
+ font-style: italic;
+ font-weight: lighter;
}
-nav
-{
- display: block;
- float: right;
- margin-top: 28px;
- width: 30%;
- box-sizing: border-box;
- border-left: 1px solid #ccc;
- padding-left: 16px;
+nav {
+ display: block;
+ float: right;
+ margin-top: 28px;
+ width: 30%;
+ box-sizing: border-box;
+ border-left: 1px solid #ccc;
+ padding-left: 16px;
}
nav ul {
- font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
- font-size: 100%;
- line-height: 17px;
- padding: 0;
- margin: 0;
- list-style-type: none;
+ font-family: "Lucida Grande", "Lucida Sans Unicode", arial, sans-serif;
+ font-size: 100%;
+ line-height: 17px;
+ padding: 0;
+ margin: 0;
+ list-style-type: none;
}
-nav ul a, nav ul a:visited, nav ul a:active {
- font-family: Consolas, Monaco, 'Andale Mono', monospace;
- line-height: 18px;
- color: #4D4E53;
+nav ul a,
+nav ul a:visited,
+nav ul a:active {
+ font-family: Consolas, Monaco, "Andale Mono", monospace;
+ line-height: 18px;
+ color: #4d4e53;
}
nav h3 {
- margin-top: 12px;
+ margin-top: 12px;
}
nav li {
- margin-top: 6px;
+ margin-top: 6px;
}
footer {
- display: block;
- padding: 6px;
- margin-top: 12px;
- font-style: italic;
- font-size: 90%;
+ display: block;
+ padding: 6px;
+ margin-top: 12px;
+ font-style: italic;
+ font-size: 90%;
}
-h1, h2, h3, h4 {
- font-weight: 200;
- margin: 0;
+h1,
+h2,
+h3,
+h4 {
+ font-weight: 200;
+ margin: 0;
}
-h1
-{
- font-family: 'Open Sans Light', sans-serif;
- font-size: 48px;
- letter-spacing: -2px;
- margin: 12px 24px 20px;
+h1 {
+ font-family: "Open Sans Light", sans-serif;
+ font-size: 48px;
+ letter-spacing: -2px;
+ margin: 12px 24px 20px;
}
-h2, h3.subsection-title
-{
- font-size: 30px;
- font-weight: 700;
- letter-spacing: -1px;
- margin-bottom: 12px;
+h2,
+h3.subsection-title {
+ font-size: 30px;
+ font-weight: 700;
+ letter-spacing: -1px;
+ margin-bottom: 12px;
}
-h3
-{
- font-size: 24px;
- letter-spacing: -0.5px;
- margin-bottom: 12px;
+h3 {
+ font-size: 24px;
+ letter-spacing: -0.5px;
+ margin-bottom: 12px;
}
-h4
-{
- font-size: 18px;
- letter-spacing: -0.33px;
- margin-bottom: 12px;
- color: #4d4e53;
+h4 {
+ font-size: 18px;
+ letter-spacing: -0.33px;
+ margin-bottom: 12px;
+ color: #4d4e53;
}
-h5, .container-overview .subsection-title
-{
- font-size: 120%;
- font-weight: bold;
- letter-spacing: -0.01em;
- margin: 8px 0 3px 0;
+h5,
+.container-overview .subsection-title {
+ font-size: 120%;
+ font-weight: bold;
+ letter-spacing: -0.01em;
+ margin: 8px 0 3px 0;
}
-h6
-{
- font-size: 100%;
- letter-spacing: -0.01em;
- margin: 6px 0 3px 0;
- font-style: italic;
+h6 {
+ font-size: 100%;
+ letter-spacing: -0.01em;
+ margin: 6px 0 3px 0;
+ font-style: italic;
}
-table
-{
- border-spacing: 0;
- border: 0;
- border-collapse: collapse;
+table {
+ border-spacing: 0;
+ border: 0;
+ border-collapse: collapse;
}
-td, th
-{
- border: 1px solid #ddd;
- margin: 0px;
- text-align: left;
- vertical-align: top;
- padding: 4px 6px;
- display: table-cell;
+td,
+th {
+ border: 1px solid #ddd;
+ margin: 0px;
+ text-align: left;
+ vertical-align: top;
+ padding: 4px 6px;
+ display: table-cell;
}
-thead tr
-{
- background-color: #ddd;
- font-weight: bold;
+thead tr {
+ background-color: #ddd;
+ font-weight: bold;
}
-th { border-right: 1px solid #aaa; }
-tr > th:last-child { border-right: 1px solid #ddd; }
+th {
+ border-right: 1px solid #aaa;
+}
+tr > th:last-child {
+ border-right: 1px solid #ddd;
+}
-.ancestors, .attribs { color: #999; }
-.ancestors a, .attribs a
-{
- color: #999 !important;
- text-decoration: none;
+.ancestors,
+.attribs {
+ color: #999;
+}
+.ancestors a,
+.attribs a {
+ color: #999 !important;
+ text-decoration: none;
}
-.clear
-{
- clear: both;
+.clear {
+ clear: both;
}
-.important
-{
- font-weight: bold;
- color: #950B02;
+.important {
+ font-weight: bold;
+ color: #950b02;
}
.yes-def {
- text-indent: -1000px;
+ text-indent: -1000px;
}
.type-signature {
- color: #aaa;
+ color: #aaa;
}
-.name, .signature {
- font-family: Consolas, Monaco, 'Andale Mono', monospace;
+.name,
+.signature {
+ font-family: Consolas, Monaco, "Andale Mono", monospace;
}
-.details { margin-top: 14px; border-left: 2px solid #DDD; }
-.details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; }
-.details dd { margin-left: 70px; }
-.details ul { margin: 0; }
-.details ul { list-style-type: none; }
-.details li { margin-left: 30px; padding-top: 6px; }
-.details pre.prettyprint { margin: 0 }
-.details .object-value { padding-top: 0; }
+.details {
+ margin-top: 14px;
+ border-left: 2px solid #ddd;
+}
+.details dt {
+ width: 120px;
+ float: left;
+ padding-left: 10px;
+ padding-top: 6px;
+}
+.details dd {
+ margin-left: 70px;
+}
+.details ul {
+ margin: 0;
+}
+.details ul {
+ list-style-type: none;
+}
+.details li {
+ margin-left: 30px;
+ padding-top: 6px;
+}
+.details pre.prettyprint {
+ margin: 0;
+}
+.details .object-value {
+ padding-top: 0;
+}
.description {
- margin-bottom: 1em;
- margin-top: 1em;
+ margin-bottom: 1em;
+ margin-top: 1em;
}
-.code-caption
-{
- font-style: italic;
- font-size: 107%;
- margin: 0;
+.code-caption {
+ font-style: italic;
+ font-size: 107%;
+ margin: 0;
}
-.source
-{
- border: 1px solid #ddd;
- width: 80%;
- overflow: auto;
+.source {
+ border: 1px solid #ddd;
+ width: 80%;
+ overflow: auto;
}
.prettyprint.source {
- width: inherit;
+ width: inherit;
}
-.source code
-{
- font-size: 100%;
- line-height: 18px;
- display: block;
- padding: 4px 12px;
- margin: 0;
- background-color: #fff;
- color: #4D4E53;
+.source code {
+ font-size: 100%;
+ line-height: 18px;
+ display: block;
+ padding: 4px 12px;
+ margin: 0;
+ background-color: #fff;
+ color: #4d4e53;
}
-.prettyprint code span.line
-{
+.prettyprint code span.line {
display: inline-block;
}
-.prettyprint.linenums
-{
+.prettyprint.linenums {
padding-left: 70px;
-webkit-user-select: none;
-moz-user-select: none;
@@ -283,50 +304,46 @@ tr > th:last-child { border-right: 1px solid #ddd; }
user-select: none;
}
-.prettyprint.linenums ol
-{
+.prettyprint.linenums ol {
padding-left: 0;
}
-.prettyprint.linenums li
-{
+.prettyprint.linenums li {
border-left: 3px #ddd solid;
}
.prettyprint.linenums li.selected,
-.prettyprint.linenums li.selected *
-{
+.prettyprint.linenums li.selected * {
background-color: lightyellow;
}
-.prettyprint.linenums li *
-{
+.prettyprint.linenums li * {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
-.params .name, .props .name, .name code {
- color: #4D4E53;
- font-family: Consolas, Monaco, 'Andale Mono', monospace;
- font-size: 100%;
+.params .name,
+.props .name,
+.name code {
+ color: #4d4e53;
+ font-family: Consolas, Monaco, "Andale Mono", monospace;
+ font-size: 100%;
}
.params td.description > p:first-child,
-.props td.description > p:first-child
-{
- margin-top: 0;
- padding-top: 0;
+.props td.description > p:first-child {
+ margin-top: 0;
+ padding-top: 0;
}
.params td.description > p:last-child,
-.props td.description > p:last-child
-{
- margin-bottom: 0;
- padding-bottom: 0;
+.props td.description > p:last-child {
+ margin-bottom: 0;
+ padding-bottom: 0;
}
.disabled {
- color: #454545;
+ color: #454545;
}
diff --git a/docs/jsdoc-templates/metacatui/static/styles/style.css b/docs/jsdoc-templates/metacatui/static/styles/style.css
index 2a11a9cd8..377691735 100644
--- a/docs/jsdoc-templates/metacatui/static/styles/style.css
+++ b/docs/jsdoc-templates/metacatui/static/styles/style.css
@@ -1,11 +1,11 @@
-:root{
- --deprecated: #D62828;
- --neutral: #EEEAD2;
+:root {
+ --deprecated: #d62828;
+ --neutral: #eeead2;
--neutral-dark: #003049;
--info-light: #e4edd9;
--info-dark: #684500;
- --primary-brand: #FCBF49;
- --primary-brand-on-dark: #FCBF49;
+ --primary-brand: #fcbf49;
+ --primary-brand-on-dark: #fcbf49;
--primary-brand-dark: #684500;
--secondary-brand: #2a9989;
}
@@ -14,21 +14,21 @@ Color pallete:
https://coolors.co/003049-2a9989-fcbf49-eae2b7-d62828
*/
-body{
+body {
margin: 0px;
font-family: "Open Sans", Helvetica, Arial, sans-serif;
display: grid;
grid-template-columns: 20% 80%;
- background-color: #FFF;
+ background-color: #fff;
color: var(--neutral-dark);
}
-#main{
+#main {
float: none;
width: auto;
height: 100vh;
overflow-y: auto;
}
-#nav{
+#nav {
float: none;
width: auto;
height: 100vh;
@@ -38,55 +38,63 @@ body{
padding-top: 20px;
border: 0px;
}
-nav h2{
+nav h2 {
font-size: 1em;
}
nav h3 {
- color: var(--neutral);
- font-size: 1.5em;
- margin-top: 20px;
- margin-bottom: 0px;
- font-weight: bold;
- letter-spacing: 0;
-}
-nav ul a, nav ul a:visited, nav ul a:active, nav h3, nav a{
+ color: var(--neutral);
+ font-size: 1.5em;
+ margin-top: 20px;
+ margin-bottom: 0px;
+ font-weight: bold;
+ letter-spacing: 0;
+}
+nav ul a,
+nav ul a:visited,
+nav ul a:active,
+nav h3,
+nav a {
font-family: inherit;
color: var(--neutral);
- font-size: .95em;
+ font-size: 0.95em;
}
.category-heading ~ li:not(.category-heading) {
- margin-left: 10px;
+ margin-left: 10px;
}
.category-heading {
color: var(--neutral);
margin-top: 10px;
font-weight: bold;
- font-size: .95em;
+ font-size: 0.95em;
}
-.category-heading[data-category='Deprecated']{
+.category-heading[data-category="Deprecated"] {
color: var(--deprecated);
font-style: italic;
}
-.important{
+.important {
color: var(--deprecated);
}
-h1, h2, h3, h4{
+h1,
+h2,
+h3,
+h4 {
color: var(--neutral-dark);
}
-h1{
+h1 {
font-family: inherit;
font-size: 2em;
letter-spacing: normal;
- border-bottom: 1px solid #DDD;
+ border-bottom: 1px solid #ddd;
padding-bottom: 20px;
}
-h2, h3.subsection-title{
+h2,
+h3.subsection-title {
font-weight: normal;
}
-header{
+header {
padding: 0px;
}
-.class-description{
+.class-description {
font-size: 1em;
line-height: inherit;
padding: 20px;
@@ -95,71 +103,71 @@ header{
color: var(--primary-brand-dark);
}
.description + h5 {
- font-weight: bold;
- font-size: 1em;
- display: inline;
- margin-right: 20px;
+ font-weight: bold;
+ font-size: 1em;
+ display: inline;
+ margin-right: 20px;
}
-.description + h5 + ul{
+.description + h5 + ul {
list-style: none;
- display: inline;
- padding: 0px;
+ display: inline;
+ padding: 0px;
}
-.description + h5 + ul li{
+.description + h5 + ul li {
list-style: none;
display: inline;
}
-.name{
+.name {
padding: 10px 20px;
-background-color: var(--neutral-dark);
-border-bottom: 1px solid var(--neutral);
+ background-color: var(--neutral-dark);
+ border-bottom: 1px solid var(--neutral);
}
h4.name {
- color: var(--neutral);
+ color: var(--neutral);
}
-.name .type-signature a{
+.name .type-signature a {
color: var(--primary-brand-on-dark);
}
-pre{
+pre {
background-color: #eee;
padding: 20px;
border-radius: 4px;
border: 1px solid #ddd;
}
-table{
+table {
width: 100%;
}
-thead th{
+thead th {
color: #fff;
background-color: #272727;
border-color: #000000;
vertical-align: bottom;
border-bottom: 2px solid #dee2e6;
- padding: .75rem;
+ padding: 0.75rem;
}
.props thead th,
.params thead th {
- background-color: var(--neutral);
- color: var(--neutral-dark);
- border: 0px;
- border-image-width: 0px;
- border-bottom: 1px solid var(--neutral);
-}
-table td{
- padding: .75rem;
+ background-color: var(--neutral);
+ color: var(--neutral-dark);
+ border: 0px;
+ border-image-width: 0px;
+ border-bottom: 1px solid var(--neutral);
+}
+table td {
+ padding: 0.75rem;
vertical-align: top;
}
-td.name{
+td.name {
background-color: var(--neutral);
}
-.class-screenshot{
+.class-screenshot {
margin-top: 20px;
margin-bottom: 20px;
}
-.class-screenshot h3{
+.class-screenshot h3 {
text-align: center;
}
-.class-screenshot img{
+.class-screenshot img {
margin: auto;
display: flex;
max-width: 600px;
@@ -167,10 +175,10 @@ td.name{
border-radius: 4px;
padding: 10px;
}
-section{
+section {
background-color: transparent;
}
-.link-icon svg{
+.link-icon svg {
height: 1em;
vertical-align: middle;
}
diff --git a/docs/other/build.js b/docs/other/build.js
index bee31f0a2..9d1c00a84 100644
--- a/docs/other/build.js
+++ b/docs/other/build.js
@@ -1,114 +1,120 @@
/*
-* ----- Require JS Optimization Build File -----
-* This is an experimental build config file that isn't quite working yet, but
-* is a work-in-progress for getting Require Optimization to work.
-* It is meant to be executed from the root directory of metacatui
-* but it lives in /docs directory for now since it's not working as intended yet.
-*
-* Run `r.js -o build.js` to build
-*
-* Documentation: https://requirejs.org/docs/optimization.html
-* Example build file: https://github.com/requirejs/r.js/blob/master/build/example.build.js
-*/
+ * ----- Require JS Optimization Build File -----
+ * This is an experimental build config file that isn't quite working yet, but
+ * is a work-in-progress for getting Require Optimization to work.
+ * It is meant to be executed from the root directory of metacatui
+ * but it lives in /docs directory for now since it's not working as intended yet.
+ *
+ * Run `r.js -o build.js` to build
+ *
+ * Documentation: https://requirejs.org/docs/optimization.html
+ * Example build file: https://github.com/requirejs/r.js/blob/master/build/example.build.js
+ */
({
appDir: "src",
baseUrl: "js/",
dir: "dist",
modules: [
{
- name: "models/AppModel"
+ name: "models/AppModel",
},
{
- name: "views/AppView"
- }
+ name: "views/AppView",
+ },
],
optimize: "none",
-// include: ["../loader"],
-// name: "models/AppModel",
+ // include: ["../loader"],
+ // name: "models/AppModel",
paths: {
- jquery: '../components/jquery-1.9.1.min',
- jqueryui: '../components/jquery-ui.min',
- jqueryform: '../components/jquery.form',
- underscore: '../components/underscore-min',
- backbone: '../components/backbone-min',
- bootstrap: '../components/bootstrap.min',
- text: '../components/require-text',
- jws: '../components/jws-3.2.min',
- jsrasign: '../components/jsrsasign-4.9.0.min',
- async: '../components/async',
- nGeohash: '../components/geohash/main',
- fancybox: '../components/fancybox/jquery.fancybox.pack', //v. 2.1.5
- annotator: '../components/annotator/v1.2.10/annotator-full',
- bioportal: '../components/bioportal/jquery.ncbo.tree-2.0.2',
- clipboard: '../components/clipboard.min',
- uuid: '../components/uuid',
- md5: '../components/md5',
- rdflib: '../components/rdflib.min',
- x2js: '../components/xml2json',
- he: '../components/he',
- citation: '../components/citation.min',
+ jquery: "../components/jquery-1.9.1.min",
+ jqueryui: "../components/jquery-ui.min",
+ jqueryform: "../components/jquery.form",
+ underscore: "../components/underscore-min",
+ backbone: "../components/backbone-min",
+ bootstrap: "../components/bootstrap.min",
+ text: "../components/require-text",
+ jws: "../components/jws-3.2.min",
+ jsrasign: "../components/jsrsasign-4.9.0.min",
+ async: "../components/async",
+ nGeohash: "../components/geohash/main",
+ fancybox: "../components/fancybox/jquery.fancybox.pack", //v. 2.1.5
+ annotator: "../components/annotator/v1.2.10/annotator-full",
+ bioportal: "../components/bioportal/jquery.ncbo.tree-2.0.2",
+ clipboard: "../components/clipboard.min",
+ uuid: "../components/uuid",
+ md5: "../components/md5",
+ rdflib: "../components/rdflib.min",
+ x2js: "../components/xml2json",
+ he: "../components/he",
+ citation: "../components/citation.min",
// showdown + extensions (used in the markdownView to convert markdown to html)
- showdown: '../components/showdown/showdown.min',
- showdownHighlight: '../components/showdown/extensions/showdown-highlight/showdown-highlight',
- highlight: '../components/showdown/extensions/showdown-highlight/highlight.pack',
- showdownFootnotes: '../components/showdown/extensions/showdown-footnotes',
- showdownBootstrap: '../components/showdown/extensions/showdown-bootstrap',
- showdownDocbook: '../components/showdown/extensions/showdown-docbook',
- showdownKatex: '../components/showdown/extensions/showdown-katex/showdown-katex.min',
- showdownCitation: '../components/showdown/extensions/showdown-citation/showdown-citation',
- showdownImages: '../components/showdown/extensions/showdown-images',
- showdownXssFilter: '../components/showdown/extensions/showdown-xss-filter/showdown-xss-filter',
- xss: '../components/showdown/extensions/showdown-xss-filter/xss.min',
- showdownHtags: '../components/showdown/extensions/showdown-htags',
+ showdown: "../components/showdown/showdown.min",
+ showdownHighlight:
+ "../components/showdown/extensions/showdown-highlight/showdown-highlight",
+ highlight:
+ "../components/showdown/extensions/showdown-highlight/highlight.pack",
+ showdownFootnotes: "../components/showdown/extensions/showdown-footnotes",
+ showdownBootstrap: "../components/showdown/extensions/showdown-bootstrap",
+ showdownDocbook: "../components/showdown/extensions/showdown-docbook",
+ showdownKatex:
+ "../components/showdown/extensions/showdown-katex/showdown-katex.min",
+ showdownCitation:
+ "../components/showdown/extensions/showdown-citation/showdown-citation",
+ showdownImages: "../components/showdown/extensions/showdown-images",
+ showdownXssFilter:
+ "../components/showdown/extensions/showdown-xss-filter/showdown-xss-filter",
+ xss: "../components/showdown/extensions/showdown-xss-filter/xss.min",
+ showdownHtags: "../components/showdown/extensions/showdown-htags",
// drop zone creates drag and drop areas
- Dropzone: '../components/dropzone-amd-module',
+ Dropzone: "../components/dropzone-amd-module",
//Have a null fallback for our d3 ../components for browsers that don't support SVG
- d3: '../components/d3.v3.min',
- LineChart: 'views/LineChartView',
- BarChart: 'views/BarChartView',
- CircleBadge: 'views/CircleBadgeView',
- DonutChart: 'views/DonutChartView',
- MetricsChart: 'views/MetricsChartView',
- },
- shim: { /* used for libraries without native AMD support */
+ d3: "../components/d3.v3.min",
+ LineChart: "views/LineChartView",
+ BarChart: "views/BarChartView",
+ CircleBadge: "views/CircleBadgeView",
+ DonutChart: "views/DonutChartView",
+ MetricsChart: "views/MetricsChartView",
+ },
+ shim: {
+ /* used for libraries without native AMD support */
underscore: {
- exports: '_',
+ exports: "_",
},
backbone: {
- deps: ['underscore', 'jquery'],
- exports: 'Backbone'
+ deps: ["underscore", "jquery"],
+ exports: "Backbone",
},
bootstrap: {
- deps: ['jquery'],
- exports: 'Bootstrap'
+ deps: ["jquery"],
+ exports: "Bootstrap",
},
annotator: {
- exports: 'Annotator'
+ exports: "Annotator",
},
bioportal: {
- exports: 'Bioportal'
+ exports: "Bioportal",
},
jws: {
- exports: 'JWS',
- deps: ['jsrasign'],
+ exports: "JWS",
+ deps: ["jsrasign"],
},
- nGeohash: {
- exports: "geohash"
- },
- fancybox: {
- deps: ['jquery']
- },
- uuid: {
- exports: 'uuid'
+ nGeohash: {
+ exports: "geohash",
+ },
+ fancybox: {
+ deps: ["jquery"],
+ },
+ uuid: {
+ exports: "uuid",
},
rdflib: {
- exports: 'rdf'
+ exports: "rdf",
+ },
+ xss: {
+ exports: "filterXSS",
+ },
+ citation: {
+ exports: "citationRequire",
},
- xss: {
- exports: 'filterXSS'
},
- citation: {
- exports: 'citationRequire'
- }
-}
-})
+});
diff --git a/docs/releases/release-notes-template.md b/docs/releases/release-notes-template.md
index 7e972b21f..bf3c89ca8 100644
--- a/docs/releases/release-notes-template.md
+++ b/docs/releases/release-notes-template.md
@@ -1,4 +1,4 @@
-# New features :tada:
+# New features :tada:
## Feature 1
@@ -12,8 +12,8 @@ Description with screenshot
Important information that software developers who use MetacatUI for their repository UI should know
-
### New configuration options
+
- [configOption](https://nceas.github.io/metacatui/docs/AppConfig.html#configOption) - Description
### See the complete changelog: [https://github.com/NCEAS/metacatui/compare/2.17.0...2.18.0](https://github.com/NCEAS/metacatui/compare/2.17.0...2.18.0)
diff --git a/docs/screenshots/index.md b/docs/screenshots/index.md
index 565df9cab..8d041f762 100644
--- a/docs/screenshots/index.md
+++ b/docs/screenshots/index.md
@@ -1,18 +1,23 @@
# Screenshots
### Home Page
+
![Home Page](https://raw.githubusercontent.com/NCEAS/metacatui/master/docs/screenshots/screenshot-home.png)
### Data Search
+
![Data Search Page](https://raw.githubusercontent.com/NCEAS/metacatui/master/docs/screenshots/screenshot-search.png)
### Dataset Landing Page
+
![Dataset Landing Page](https://raw.githubusercontent.com/NCEAS/metacatui/master/docs/screenshots/screenshot-dataset.png)
### Dataset Editor
+
![Dataset Editor](https://raw.githubusercontent.com/NCEAS/metacatui/master/docs/screenshots/screenshot-editor.png)
### Data Summary View
+
![Data Summary View](https://raw.githubusercontent.com/NCEAS/metacatui/master/docs/screenshots/screenshot-summary.png)
### Other Themes
@@ -22,10 +27,13 @@ There are a few other themes that come preinstalled with MetacatUI, although the
[See more information on MetacatUI theming](../install/configuration/index.html).
#### KNB
+
![KNB](https://raw.githubusercontent.com/NCEAS/metacatui/master/docs/screenshots/metacatui-knb-1200w.png)
#### DataONE
+
![DataONE](https://raw.githubusercontent.com/NCEAS/metacatui/master/docs/screenshots/metacatui-dataone-1000w.png)
#### Arctic
+
![Artic](https://raw.githubusercontent.com/NCEAS/metacatui/master/docs/screenshots/metacatui-arctic-1200w.png)
diff --git a/server.js b/server.js
index ee97eed60..3c00665d4 100644
--- a/server.js
+++ b/server.js
@@ -18,7 +18,7 @@ const app = express();
const src_dir = "src";
app.use(express.static(__dirname + "/" + src_dir));
-app.get("*", function(request, response) {
+app.get("*", function (request, response) {
response.sendFile(path.resolve(__dirname, src_dir, "index.html"));
});
app.listen(port);
diff --git a/src/css/bootstrap-responsive.css b/src/css/bootstrap-responsive.css
index 09e88ce3f..f27ab6667 100644
--- a/src/css/bootstrap-responsive.css
+++ b/src/css/bootstrap-responsive.css
@@ -36,8 +36,8 @@
width: 100%;
min-height: 30px;
-webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
}
@-ms-viewport {
@@ -226,8 +226,8 @@
margin-left: 2.564102564102564%;
*margin-left: 2.5109110747408616%;
-webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
}
.row-fluid [class*="span"]:first-child {
margin-left: 0;
@@ -576,8 +576,8 @@
margin-left: 2.7624309392265194%;
*margin-left: 2.709239449864817%;
-webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
}
.row-fluid [class*="span"]:first-child {
margin-left: 0;
@@ -844,15 +844,15 @@
width: 100%;
margin-left: 0;
-webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
}
.span12,
.row-fluid .span12 {
width: 100%;
-webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
}
.row-fluid [class*="offset"]:first-child {
margin-left: 0;
@@ -868,8 +868,8 @@
width: 100%;
min-height: 30px;
-webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
}
.input-prepend input,
.input-append input,
@@ -1002,15 +1002,15 @@
font-weight: bold;
color: #777777;
-webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
}
.nav-collapse .btn {
padding: 4px 10px 4px;
font-weight: normal;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
}
.nav-collapse .dropdown-menu li + li a {
margin-bottom: 2px;
@@ -1047,11 +1047,11 @@
background-color: transparent;
border: none;
-webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
-webkit-box-shadow: none;
- -moz-box-shadow: none;
- box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
}
.nav-collapse .open > .dropdown-menu {
display: block;
@@ -1074,9 +1074,15 @@
margin: 10px 0;
border-top: 1px solid #f2f2f2;
border-bottom: 1px solid #f2f2f2;
- -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
- -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ -webkit-box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.1),
+ 0 1px 0 rgba(255, 255, 255, 0.1);
+ -moz-box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.1),
+ 0 1px 0 rgba(255, 255, 255, 0.1);
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.1),
+ 0 1px 0 rgba(255, 255, 255, 0.1);
}
.navbar-inverse .nav-collapse .navbar-form,
.navbar-inverse .nav-collapse .navbar-search {
diff --git a/src/css/bootstrap.css b/src/css/bootstrap.css
index ef3bb2c1a..a3cae4354 100644
--- a/src/css/bootstrap.css
+++ b/src/css/bootstrap.css
@@ -36,8 +36,8 @@
width: 100%;
min-height: 30px;
-webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
}
article,
@@ -68,7 +68,7 @@ audio:not([controls]) {
html {
font-size: 100%;
-webkit-text-size-adjust: 100%;
- -ms-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
}
a:focus {
@@ -154,8 +154,8 @@ input[type="checkbox"] {
input[type="search"] {
-webkit-box-sizing: content-box;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
-webkit-appearance: textfield;
}
@@ -206,7 +206,7 @@ textarea {
img {
max-width: 100% !important;
}
- @page {
+ @page {
margin: 0.5cm;
}
p,
@@ -243,8 +243,8 @@ a:focus {
.img-rounded {
-webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
}
.img-polaroid {
@@ -253,14 +253,14 @@ a:focus {
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.img-circle {
-webkit-border-radius: 500px;
- -moz-border-radius: 500px;
- border-radius: 500px;
+ -moz-border-radius: 500px;
+ border-radius: 500px;
}
.row {
@@ -412,8 +412,8 @@ a:focus {
margin-left: 2.127659574468085%;
*margin-left: 2.074468085106383%;
-webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
}
.row-fluid [class*="span"]:first-child {
@@ -931,7 +931,7 @@ blockquote small {
}
blockquote small:before {
- content: '\2014 \00A0';
+ content: "\2014 \00A0";
}
blockquote.pull-right {
@@ -948,11 +948,11 @@ blockquote.pull-right small {
}
blockquote.pull-right small:before {
- content: '';
+ content: "";
}
blockquote.pull-right small:after {
- content: '\00A0 \2014';
+ content: "\00A0 \2014";
}
q:before,
@@ -976,8 +976,8 @@ pre {
font-size: 12px;
color: #333333;
-webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
}
code {
@@ -1002,8 +1002,8 @@ pre {
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.15);
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
}
pre.prettyprint {
@@ -1099,8 +1099,8 @@ input[type="color"],
color: #555555;
vertical-align: middle;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
}
input,
@@ -1132,12 +1132,20 @@ input[type="color"],
background-color: #ffffff;
border: 1px solid #cccccc;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
- -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
- -o-transition: border linear 0.2s, box-shadow linear 0.2s;
- transition: border linear 0.2s, box-shadow linear 0.2s;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -webkit-transition:
+ border linear 0.2s,
+ box-shadow linear 0.2s;
+ -moz-transition:
+ border linear 0.2s,
+ box-shadow linear 0.2s;
+ -o-transition:
+ border linear 0.2s,
+ box-shadow linear 0.2s;
+ transition:
+ border linear 0.2s,
+ box-shadow linear 0.2s;
}
textarea:focus,
@@ -1161,9 +1169,15 @@ input[type="color"]:focus,
outline: thin dotted \9;
/* IE6-9 */
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ -webkit-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 8px rgba(82, 168, 236, 0.6);
+ -moz-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 8px rgba(82, 168, 236, 0.6);
+ box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 8px rgba(82, 168, 236, 0.6);
}
input[type="radio"],
@@ -1222,8 +1236,8 @@ input[type="checkbox"]:focus {
background-color: #fcfcfc;
border-color: #cccccc;
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
- -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
}
.uneditable-input {
@@ -1473,17 +1487,23 @@ input[type="checkbox"][readonly] {
.control-group.warning textarea {
border-color: #c09853;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.control-group.warning input:focus,
.control-group.warning select:focus,
.control-group.warning textarea:focus {
border-color: #a47e3c;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+ -webkit-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #dbc59e;
+ -moz-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #dbc59e;
+ box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #dbc59e;
}
.control-group.warning .input-prepend .add-on,
@@ -1512,17 +1532,23 @@ input[type="checkbox"][readonly] {
.control-group.error textarea {
border-color: #b94a48;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.control-group.error input:focus,
.control-group.error select:focus,
.control-group.error textarea:focus {
border-color: #953b39;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+ -webkit-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #d59392;
+ -moz-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #d59392;
+ box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #d59392;
}
.control-group.error .input-prepend .add-on,
@@ -1551,17 +1577,23 @@ input[type="checkbox"][readonly] {
.control-group.success textarea {
border-color: #468847;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.control-group.success input:focus,
.control-group.success select:focus,
.control-group.success textarea:focus {
border-color: #356635;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+ -webkit-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #7aba7b;
+ -moz-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #7aba7b;
+ box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #7aba7b;
}
.control-group.success .input-prepend .add-on,
@@ -1590,17 +1622,23 @@ input[type="checkbox"][readonly] {
.control-group.info textarea {
border-color: #3a87ad;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.control-group.info input:focus,
.control-group.info select:focus,
.control-group.info textarea:focus {
border-color: #2d6987;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+ -webkit-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #7ab5d3;
+ -moz-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #7ab5d3;
+ box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #7ab5d3;
}
.control-group.info .input-prepend .add-on,
@@ -1622,8 +1660,8 @@ textarea:focus:invalid:focus,
select:focus:invalid:focus {
border-color: #e9322d;
-webkit-box-shadow: 0 0 6px #f8b9b7;
- -moz-box-shadow: 0 0 6px #f8b9b7;
- box-shadow: 0 0 6px #f8b9b7;
+ -moz-box-shadow: 0 0 6px #f8b9b7;
+ box-shadow: 0 0 6px #f8b9b7;
}
.form-actions {
@@ -1697,8 +1735,8 @@ select:focus:invalid:focus {
*margin-left: 0;
vertical-align: top;
-webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
}
.input-append input:focus,
@@ -1734,8 +1772,8 @@ select:focus:invalid:focus {
.input-prepend .btn-group > .dropdown-toggle {
vertical-align: top;
-webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
}
.input-append .active,
@@ -1752,24 +1790,24 @@ select:focus:invalid:focus {
.input-prepend .add-on:first-child,
.input-prepend .btn:first-child {
-webkit-border-radius: 4px 0 0 4px;
- -moz-border-radius: 4px 0 0 4px;
- border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
}
.input-append input,
.input-append select,
.input-append .uneditable-input {
-webkit-border-radius: 4px 0 0 4px;
- -moz-border-radius: 4px 0 0 4px;
- border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
}
.input-append input + .btn-group .btn:last-child,
.input-append select + .btn-group .btn:last-child,
.input-append .uneditable-input + .btn-group .btn:last-child {
-webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
}
.input-append .add-on,
@@ -1782,40 +1820,40 @@ select:focus:invalid:focus {
.input-append .btn:last-child,
.input-append .btn-group:last-child > .dropdown-toggle {
-webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
}
.input-prepend.input-append input,
.input-prepend.input-append select,
.input-prepend.input-append .uneditable-input {
-webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
}
.input-prepend.input-append input + .btn-group .btn,
.input-prepend.input-append select + .btn-group .btn,
.input-prepend.input-append .uneditable-input + .btn-group .btn {
-webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
}
.input-prepend.input-append .add-on:first-child,
.input-prepend.input-append .btn:first-child {
margin-right: -1px;
-webkit-border-radius: 4px 0 0 4px;
- -moz-border-radius: 4px 0 0 4px;
- border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
}
.input-prepend.input-append .add-on:last-child,
.input-prepend.input-append .btn:last-child {
margin-left: -1px;
-webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
}
.input-prepend.input-append .btn-group:first-child {
@@ -1831,8 +1869,8 @@ input.search-query {
margin-bottom: 0;
-webkit-border-radius: 15px;
- -moz-border-radius: 15px;
- border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
}
/* Allow for input prepend/append in search forms */
@@ -1840,32 +1878,32 @@ input.search-query {
.form-search .input-append .search-query,
.form-search .input-prepend .search-query {
-webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
}
.form-search .input-append .search-query {
-webkit-border-radius: 14px 0 0 14px;
- -moz-border-radius: 14px 0 0 14px;
- border-radius: 14px 0 0 14px;
+ -moz-border-radius: 14px 0 0 14px;
+ border-radius: 14px 0 0 14px;
}
.form-search .input-append .btn {
-webkit-border-radius: 0 14px 14px 0;
- -moz-border-radius: 0 14px 14px 0;
- border-radius: 0 14px 14px 0;
+ -moz-border-radius: 0 14px 14px 0;
+ border-radius: 0 14px 14px 0;
}
.form-search .input-prepend .search-query {
-webkit-border-radius: 0 14px 14px 0;
- -moz-border-radius: 0 14px 14px 0;
- border-radius: 0 14px 14px 0;
+ -moz-border-radius: 0 14px 14px 0;
+ border-radius: 0 14px 14px 0;
}
.form-search .input-prepend .btn {
-webkit-border-radius: 14px 0 0 14px;
- -moz-border-radius: 14px 0 0 14px;
- border-radius: 14px 0 0 14px;
+ -moz-border-radius: 14px 0 0 14px;
+ border-radius: 14px 0 0 14px;
}
.form-search input,
@@ -2051,8 +2089,8 @@ table {
*border-collapse: collapse;
border-left: 0;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
}
.table-bordered th,
@@ -2076,7 +2114,7 @@ table {
.table-bordered tbody:first-child tr:first-child > td:first-child,
.table-bordered tbody:first-child tr:first-child > th:first-child {
-webkit-border-top-left-radius: 4px;
- border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
-moz-border-radius-topleft: 4px;
}
@@ -2084,7 +2122,7 @@ table {
.table-bordered tbody:first-child tr:first-child > td:last-child,
.table-bordered tbody:first-child tr:first-child > th:last-child {
-webkit-border-top-right-radius: 4px;
- border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
-moz-border-radius-topright: 4px;
}
@@ -2094,7 +2132,7 @@ table {
.table-bordered tfoot:last-child tr:last-child > td:first-child,
.table-bordered tfoot:last-child tr:last-child > th:first-child {
-webkit-border-bottom-left-radius: 4px;
- border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
-moz-border-radius-bottomleft: 4px;
}
@@ -2104,19 +2142,19 @@ table {
.table-bordered tfoot:last-child tr:last-child > td:last-child,
.table-bordered tfoot:last-child tr:last-child > th:last-child {
-webkit-border-bottom-right-radius: 4px;
- border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
-moz-border-radius-bottomright: 4px;
}
.table-bordered tfoot + tbody:last-child tr:last-child td:first-child {
-webkit-border-bottom-left-radius: 0;
- border-bottom-left-radius: 0;
+ border-bottom-left-radius: 0;
-moz-border-radius-bottomleft: 0;
}
.table-bordered tfoot + tbody:last-child tr:last-child td:last-child {
-webkit-border-bottom-right-radius: 0;
- border-bottom-right-radius: 0;
+ border-bottom-right-radius: 0;
-moz-border-radius-bottomright: 0;
}
@@ -2125,7 +2163,7 @@ table {
.table-bordered colgroup + thead tr:first-child th:first-child,
.table-bordered colgroup + tbody tr:first-child td:first-child {
-webkit-border-top-left-radius: 4px;
- border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
-moz-border-radius-topleft: 4px;
}
@@ -2134,7 +2172,7 @@ table {
.table-bordered colgroup + thead tr:first-child th:last-child,
.table-bordered colgroup + tbody tr:first-child td:last-child {
-webkit-border-top-right-radius: 4px;
- border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
-moz-border-radius-topright: 4px;
}
@@ -2279,7 +2317,7 @@ table th[class*="span"],
width: 14px;
height: 14px;
margin-top: 1px;
- *margin-right: .3em;
+ *margin-right: 0.3em;
line-height: 14px;
vertical-align: text-top;
background-position: 14px 14px;
@@ -2333,14 +2371,14 @@ table th[class*="span"],
*border-right-width: 2px;
*border-bottom-width: 2px;
-webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
- -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
- box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-webkit-background-clip: padding-box;
- -moz-background-clip: padding;
- background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
}
.dropdown-menu.pull-right {
@@ -2376,7 +2414,13 @@ table th[class*="span"],
text-decoration: none;
background-color: #0081c2;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#0088cc),
+ to(#0077b3)
+ );
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
@@ -2391,7 +2435,13 @@ table th[class*="span"],
text-decoration: none;
background-color: #0081c2;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#0088cc),
+ to(#0077b3)
+ );
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
@@ -2461,8 +2511,8 @@ table th[class*="span"],
margin-top: -6px;
margin-left: -1px;
-webkit-border-radius: 0 6px 6px 6px;
- -moz-border-radius: 0 6px 6px 6px;
- border-radius: 0 6px 6px 6px;
+ -moz-border-radius: 0 6px 6px 6px;
+ border-radius: 0 6px 6px 6px;
}
.dropdown-submenu:hover > .dropdown-menu {
@@ -2475,8 +2525,8 @@ table th[class*="span"],
margin-top: 0;
margin-bottom: -2px;
-webkit-border-radius: 5px 5px 5px 0;
- -moz-border-radius: 5px 5px 5px 0;
- border-radius: 5px 5px 5px 0;
+ -moz-border-radius: 5px 5px 5px 0;
+ border-radius: 5px 5px 5px 0;
}
.dropdown-submenu > a:after {
@@ -2505,8 +2555,8 @@ table th[class*="span"],
left: -100%;
margin-left: 10px;
-webkit-border-radius: 6px 0 6px 6px;
- -moz-border-radius: 6px 0 6px 6px;
- border-radius: 6px 0 6px 6px;
+ -moz-border-radius: 6px 0 6px 6px;
+ border-radius: 6px 0 6px 6px;
}
.dropdown .dropdown-menu .nav-header {
@@ -2518,8 +2568,8 @@ table th[class*="span"],
z-index: 1051;
margin-top: 2px;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
}
.well {
@@ -2529,11 +2579,11 @@ table th[class*="span"],
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
}
.well blockquote {
@@ -2544,23 +2594,23 @@ table th[class*="span"],
.well-large {
padding: 24px;
-webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
}
.well-small {
padding: 9px;
-webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
}
.fade {
opacity: 0;
-webkit-transition: opacity 0.15s linear;
- -moz-transition: opacity 0.15s linear;
- -o-transition: opacity 0.15s linear;
- transition: opacity 0.15s linear;
+ -moz-transition: opacity 0.15s linear;
+ -o-transition: opacity 0.15s linear;
+ transition: opacity 0.15s linear;
}
.fade.in {
@@ -2572,9 +2622,9 @@ table th[class*="span"],
height: 0;
overflow: hidden;
-webkit-transition: height 0.35s ease;
- -moz-transition: height 0.35s ease;
- -o-transition: height 0.35s ease;
- transition: height 0.35s ease;
+ -moz-transition: height 0.35s ease;
+ -o-transition: height 0.35s ease;
+ transition: height 0.35s ease;
}
.collapse.in {
@@ -2614,7 +2664,7 @@ button.close {
*display: inline;
padding: 4px 12px;
margin-bottom: 0;
- *margin-left: .3em;
+ *margin-left: 0.3em;
font-size: 14px;
line-height: 20px;
color: #333333;
@@ -2625,7 +2675,13 @@ button.close {
background-color: #f5f5f5;
*background-color: #e6e6e6;
background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#ffffff),
+ to(#e6e6e6)
+ );
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
@@ -2636,14 +2692,20 @@ button.close {
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
border-bottom-color: #b3b3b3;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
*zoom: 1;
- -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -webkit-box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.2),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.2),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.2),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
}
.btn:hover,
@@ -2672,9 +2734,9 @@ button.close {
text-decoration: none;
background-position: 0 -15px;
-webkit-transition: background-position 0.1s linear;
- -moz-transition: background-position 0.1s linear;
- -o-transition: background-position 0.1s linear;
- transition: background-position 0.1s linear;
+ -moz-transition: background-position 0.1s linear;
+ -o-transition: background-position 0.1s linear;
+ transition: background-position 0.1s linear;
}
.btn:focus {
@@ -2687,9 +2749,15 @@ button.close {
.btn:active {
background-image: none;
outline: 0;
- -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -webkit-box-shadow:
+ inset 0 2px 4px rgba(0, 0, 0, 0.15),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow:
+ inset 0 2px 4px rgba(0, 0, 0, 0.15),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow:
+ inset 0 2px 4px rgba(0, 0, 0, 0.15),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
}
.btn.disabled,
@@ -2699,16 +2767,16 @@ button.close {
opacity: 0.65;
filter: alpha(opacity=65);
-webkit-box-shadow: none;
- -moz-box-shadow: none;
- box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
}
.btn-large {
padding: 11px 19px;
font-size: 17.5px;
-webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
}
.btn-large [class^="icon-"],
@@ -2720,8 +2788,8 @@ button.close {
padding: 2px 10px;
font-size: 11.9px;
-webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
}
.btn-small [class^="icon-"],
@@ -2738,8 +2806,8 @@ button.close {
padding: 0 6px;
font-size: 10.5px;
-webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
}
.btn-block {
@@ -2748,8 +2816,8 @@ button.close {
padding-right: 0;
padding-left: 0;
-webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
}
.btn-block + .btn-block {
@@ -2777,7 +2845,13 @@ input[type="button"].btn-block {
background-color: #006dcc;
*background-color: #0044cc;
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#0088cc),
+ to(#0044cc)
+ );
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
@@ -2810,7 +2884,13 @@ input[type="button"].btn-block {
background-color: #faa732;
*background-color: #f89406;
background-image: -moz-linear-gradient(top, #fbb450, #f89406);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#fbb450),
+ to(#f89406)
+ );
background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
background-image: -o-linear-gradient(top, #fbb450, #f89406);
background-image: linear-gradient(to bottom, #fbb450, #f89406);
@@ -2843,7 +2923,13 @@ input[type="button"].btn-block {
background-color: #da4f49;
*background-color: #bd362f;
background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#ee5f5b),
+ to(#bd362f)
+ );
background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
background-image: linear-gradient(to bottom, #ee5f5b, #bd362f);
@@ -2876,7 +2962,13 @@ input[type="button"].btn-block {
background-color: #5bb75b;
*background-color: #51a351;
background-image: -moz-linear-gradient(top, #62c462, #51a351);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#62c462),
+ to(#51a351)
+ );
background-image: -webkit-linear-gradient(top, #62c462, #51a351);
background-image: -o-linear-gradient(top, #62c462, #51a351);
background-image: linear-gradient(to bottom, #62c462, #51a351);
@@ -2909,7 +3001,13 @@ input[type="button"].btn-block {
background-color: #49afcd;
*background-color: #2f96b4;
background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#5bc0de),
+ to(#2f96b4)
+ );
background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
background-image: linear-gradient(to bottom, #5bc0de, #2f96b4);
@@ -2942,7 +3040,13 @@ input[type="button"].btn-block {
background-color: #363636;
*background-color: #222222;
background-image: -moz-linear-gradient(top, #444444, #222222);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#444444),
+ to(#222222)
+ );
background-image: -webkit-linear-gradient(top, #444444, #222222);
background-image: -o-linear-gradient(top, #444444, #222222);
background-image: linear-gradient(to bottom, #444444, #222222);
@@ -3005,8 +3109,8 @@ input[type="submit"].btn.btn-mini {
background-color: transparent;
background-image: none;
-webkit-box-shadow: none;
- -moz-box-shadow: none;
- box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
}
.btn-link {
@@ -3014,8 +3118,8 @@ input[type="submit"].btn.btn-mini {
cursor: pointer;
border-color: transparent;
-webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
}
.btn-link:hover,
@@ -3035,7 +3139,7 @@ input[type="submit"].btn.btn-mini {
position: relative;
display: inline-block;
*display: inline;
- *margin-left: .3em;
+ *margin-left: 0.3em;
font-size: 0;
white-space: nowrap;
vertical-align: middle;
@@ -3065,8 +3169,8 @@ input[type="submit"].btn.btn-mini {
.btn-group > .btn {
position: relative;
-webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
}
.btn-group > .btn + .btn {
@@ -3094,9 +3198,9 @@ input[type="submit"].btn.btn-mini {
.btn-group > .btn:first-child {
margin-left: 0;
-webkit-border-bottom-left-radius: 4px;
- border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
-webkit-border-top-left-radius: 4px;
- border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
-moz-border-radius-bottomleft: 4px;
-moz-border-radius-topleft: 4px;
}
@@ -3104,9 +3208,9 @@ input[type="submit"].btn.btn-mini {
.btn-group > .btn:last-child,
.btn-group > .dropdown-toggle {
-webkit-border-top-right-radius: 4px;
- border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
-webkit-border-bottom-right-radius: 4px;
- border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
-moz-border-radius-topright: 4px;
-moz-border-radius-bottomright: 4px;
}
@@ -3114,9 +3218,9 @@ input[type="submit"].btn.btn-mini {
.btn-group > .btn.large:first-child {
margin-left: 0;
-webkit-border-bottom-left-radius: 6px;
- border-bottom-left-radius: 6px;
+ border-bottom-left-radius: 6px;
-webkit-border-top-left-radius: 6px;
- border-top-left-radius: 6px;
+ border-top-left-radius: 6px;
-moz-border-radius-bottomleft: 6px;
-moz-border-radius-topleft: 6px;
}
@@ -3124,9 +3228,9 @@ input[type="submit"].btn.btn-mini {
.btn-group > .btn.large:last-child,
.btn-group > .large.dropdown-toggle {
-webkit-border-top-right-radius: 6px;
- border-top-right-radius: 6px;
+ border-top-right-radius: 6px;
-webkit-border-bottom-right-radius: 6px;
- border-bottom-right-radius: 6px;
+ border-bottom-right-radius: 6px;
-moz-border-radius-topright: 6px;
-moz-border-radius-bottomright: 6px;
}
@@ -3148,9 +3252,18 @@ input[type="submit"].btn.btn-mini {
padding-right: 8px;
*padding-bottom: 5px;
padding-left: 8px;
- -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -webkit-box-shadow:
+ inset 1px 0 0 rgba(255, 255, 255, 0.125),
+ inset 0 1px 0 rgba(255, 255, 255, 0.2),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow:
+ inset 1px 0 0 rgba(255, 255, 255, 0.125),
+ inset 0 1px 0 rgba(255, 255, 255, 0.2),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow:
+ inset 1px 0 0 rgba(255, 255, 255, 0.125),
+ inset 0 1px 0 rgba(255, 255, 255, 0.2),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
}
.btn-group > .btn-mini + .dropdown-toggle {
@@ -3174,9 +3287,15 @@ input[type="submit"].btn.btn-mini {
.btn-group.open .dropdown-toggle {
background-image: none;
- -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -webkit-box-shadow:
+ inset 0 2px 4px rgba(0, 0, 0, 0.15),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow:
+ inset 0 2px 4px rgba(0, 0, 0, 0.15),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow:
+ inset 0 2px 4px rgba(0, 0, 0, 0.15),
+ 0 1px 2px rgba(0, 0, 0, 0.05);
}
.btn-group.open .btn.dropdown-toggle {
@@ -3254,8 +3373,8 @@ input[type="submit"].btn.btn-mini {
float: none;
max-width: 100%;
-webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
}
.btn-group-vertical > .btn + .btn {
@@ -3265,26 +3384,26 @@ input[type="submit"].btn.btn-mini {
.btn-group-vertical > .btn:first-child {
-webkit-border-radius: 4px 4px 0 0;
- -moz-border-radius: 4px 4px 0 0;
- border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
}
.btn-group-vertical > .btn:last-child {
-webkit-border-radius: 0 0 4px 4px;
- -moz-border-radius: 0 0 4px 4px;
- border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
}
.btn-group-vertical > .btn-large:first-child {
-webkit-border-radius: 6px 6px 0 0;
- -moz-border-radius: 6px 6px 0 0;
- border-radius: 6px 6px 0 0;
+ -moz-border-radius: 6px 6px 0 0;
+ border-radius: 6px 6px 0 0;
}
.btn-group-vertical > .btn-large:last-child {
-webkit-border-radius: 0 0 6px 6px;
- -moz-border-radius: 0 0 6px 6px;
- border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
}
.alert {
@@ -3294,8 +3413,8 @@ input[type="submit"].btn.btn-mini {
background-color: #fcf8e3;
border: 1px solid #fbeed5;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
}
.alert,
@@ -3485,8 +3604,8 @@ input[type="submit"].btn.btn-mini {
line-height: 20px;
border: 1px solid transparent;
-webkit-border-radius: 4px 4px 0 0;
- -moz-border-radius: 4px 4px 0 0;
- border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
}
.nav-tabs > li > a:hover,
@@ -3510,8 +3629,8 @@ input[type="submit"].btn.btn-mini {
margin-top: 2px;
margin-bottom: 2px;
-webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
}
.nav-pills > .active > a,
@@ -3536,24 +3655,24 @@ input[type="submit"].btn.btn-mini {
.nav-tabs.nav-stacked > li > a {
border: 1px solid #ddd;
-webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
}
.nav-tabs.nav-stacked > li:first-child > a {
-webkit-border-top-right-radius: 4px;
- border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
-webkit-border-top-left-radius: 4px;
- border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
-moz-border-radius-topright: 4px;
-moz-border-radius-topleft: 4px;
}
.nav-tabs.nav-stacked > li:last-child > a {
-webkit-border-bottom-right-radius: 4px;
- border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
-webkit-border-bottom-left-radius: 4px;
- border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
-moz-border-radius-bottomright: 4px;
-moz-border-radius-bottomleft: 4px;
}
@@ -3574,14 +3693,14 @@ input[type="submit"].btn.btn-mini {
.nav-tabs .dropdown-menu {
-webkit-border-radius: 0 0 6px 6px;
- -moz-border-radius: 0 0 6px 6px;
- border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
}
.nav-pills .dropdown-menu {
-webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
}
.nav .dropdown-toggle .caret {
@@ -3687,8 +3806,8 @@ input[type="submit"].btn.btn-mini {
.tabs-below > .nav-tabs > li > a {
-webkit-border-radius: 0 0 4px 4px;
- -moz-border-radius: 0 0 4px 4px;
- border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
}
.tabs-below > .nav-tabs > li > a:hover,
@@ -3724,8 +3843,8 @@ input[type="submit"].btn.btn-mini {
.tabs-left > .nav-tabs > li > a {
margin-right: -1px;
-webkit-border-radius: 4px 0 0 4px;
- -moz-border-radius: 4px 0 0 4px;
- border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
}
.tabs-left > .nav-tabs > li > a:hover,
@@ -3749,8 +3868,8 @@ input[type="submit"].btn.btn-mini {
.tabs-right > .nav-tabs > li > a {
margin-left: -1px;
-webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
}
.tabs-right > .nav-tabs > li > a:hover,
@@ -3789,20 +3908,26 @@ input[type="submit"].btn.btn-mini {
padding-left: 20px;
background-color: #fafafa;
background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#ffffff),
+ to(#f2f2f2)
+ );
background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);
background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);
background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);
background-repeat: repeat-x;
border: 1px solid #d4d4d4;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);
*zoom: 1;
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
- -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+ -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
}
.navbar-inner:before,
@@ -3938,8 +4063,8 @@ input[type="submit"].btn.btn-mini {
font-weight: normal;
line-height: 1;
-webkit-border-radius: 15px;
- -moz-border-radius: 15px;
- border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
}
.navbar-static-top {
@@ -3949,8 +4074,8 @@ input[type="submit"].btn.btn-mini {
.navbar-static-top .navbar-inner {
-webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
}
.navbar-fixed-top,
@@ -3976,8 +4101,8 @@ input[type="submit"].btn.btn-mini {
padding-right: 0;
padding-left: 0;
-webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
}
.navbar-static-top .container,
@@ -3993,8 +4118,8 @@ input[type="submit"].btn.btn-mini {
.navbar-fixed-top .navbar-inner,
.navbar-static-top .navbar-inner {
-webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
- -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
- box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
}
.navbar-fixed-bottom {
@@ -4003,8 +4128,8 @@ input[type="submit"].btn.btn-mini {
.navbar-fixed-bottom .navbar-inner {
-webkit-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
- -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
- box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
}
.navbar .nav {
@@ -4050,8 +4175,8 @@ input[type="submit"].btn.btn-mini {
text-decoration: none;
background-color: #e5e5e5;
-webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
- -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
- box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+ -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+ box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
}
.navbar .btn-navbar {
@@ -4065,7 +4190,13 @@ input[type="submit"].btn.btn-mini {
background-color: #ededed;
*background-color: #e5e5e5;
background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#f2f2f2),
+ to(#e5e5e5)
+ );
background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);
background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);
background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);
@@ -4074,9 +4205,15 @@ input[type="submit"].btn.btn-mini {
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
- -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
- -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+ -webkit-box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.1),
+ 0 1px 0 rgba(255, 255, 255, 0.075);
+ -moz-box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.1),
+ 0 1px 0 rgba(255, 255, 255, 0.075);
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.1),
+ 0 1px 0 rgba(255, 255, 255, 0.075);
}
.navbar .btn-navbar:hover,
@@ -4101,11 +4238,11 @@ input[type="submit"].btn.btn-mini {
height: 2px;
background-color: #f5f5f5;
-webkit-border-radius: 1px;
- -moz-border-radius: 1px;
- border-radius: 1px;
+ -moz-border-radius: 1px;
+ border-radius: 1px;
-webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
- -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
- box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
}
.btn-navbar .icon-bar + .icon-bar {
@@ -4121,7 +4258,7 @@ input[type="submit"].btn.btn-mini {
border-bottom: 7px solid #ccc;
border-left: 7px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.2);
- content: '';
+ content: "";
}
.navbar .nav > li > .dropdown-menu:after {
@@ -4132,7 +4269,7 @@ input[type="submit"].btn.btn-mini {
border-right: 6px solid transparent;
border-bottom: 6px solid #ffffff;
border-left: 6px solid transparent;
- content: '';
+ content: "";
}
.navbar-fixed-bottom .nav > li > .dropdown-menu:before {
@@ -4200,14 +4337,20 @@ input[type="submit"].btn.btn-mini {
margin-right: -1px;
margin-left: 0;
-webkit-border-radius: 6px 0 6px 6px;
- -moz-border-radius: 6px 0 6px 6px;
- border-radius: 6px 0 6px 6px;
+ -moz-border-radius: 6px 0 6px 6px;
+ border-radius: 6px 0 6px 6px;
}
.navbar-inverse .navbar-inner {
background-color: #1b1b1b;
background-image: -moz-linear-gradient(top, #222222, #111111);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#222222),
+ to(#111111)
+ );
background-image: -webkit-linear-gradient(top, #222222, #111111);
background-image: -o-linear-gradient(top, #222222, #111111);
background-image: linear-gradient(to bottom, #222222, #111111);
@@ -4293,13 +4436,19 @@ input[type="submit"].btn.btn-mini {
color: #ffffff;
background-color: #515151;
border-color: #111111;
- -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
- -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ -webkit-box-shadow:
+ inset 0 1px 2px rgba(0, 0, 0, 0.1),
+ 0 1px 0 rgba(255, 255, 255, 0.15);
+ -moz-box-shadow:
+ inset 0 1px 2px rgba(0, 0, 0, 0.1),
+ 0 1px 0 rgba(255, 255, 255, 0.15);
+ box-shadow:
+ inset 0 1px 2px rgba(0, 0, 0, 0.1),
+ 0 1px 0 rgba(255, 255, 255, 0.15);
-webkit-transition: none;
- -moz-transition: none;
- -o-transition: none;
- transition: none;
+ -moz-transition: none;
+ -o-transition: none;
+ transition: none;
}
.navbar-inverse .navbar-search .search-query:-moz-placeholder {
@@ -4323,8 +4472,8 @@ input[type="submit"].btn.btn-mini {
border: 0;
outline: 0;
-webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
- -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
- box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
}
.navbar-inverse .btn-navbar {
@@ -4333,7 +4482,13 @@ input[type="submit"].btn.btn-mini {
background-color: #0e0e0e;
*background-color: #040404;
background-image: -moz-linear-gradient(top, #151515, #040404);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#151515),
+ to(#040404)
+ );
background-image: -webkit-linear-gradient(top, #151515, #040404);
background-image: -o-linear-gradient(top, #151515, #040404);
background-image: linear-gradient(to bottom, #151515, #040404);
@@ -4366,8 +4521,8 @@ input[type="submit"].btn.btn-mini {
list-style: none;
background-color: #f5f5f5;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
}
.breadcrumb > li {
@@ -4396,12 +4551,12 @@ input[type="submit"].btn.btn-mini {
margin-bottom: 0;
margin-left: 0;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
*zoom: 1;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.pagination ul > li {
@@ -4445,9 +4600,9 @@ input[type="submit"].btn.btn-mini {
.pagination ul > li:first-child > span {
border-left-width: 1px;
-webkit-border-bottom-left-radius: 4px;
- border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
-webkit-border-top-left-radius: 4px;
- border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
-moz-border-radius-bottomleft: 4px;
-moz-border-radius-topleft: 4px;
}
@@ -4455,9 +4610,9 @@ input[type="submit"].btn.btn-mini {
.pagination ul > li:last-child > a,
.pagination ul > li:last-child > span {
-webkit-border-top-right-radius: 4px;
- border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
-webkit-border-bottom-right-radius: 4px;
- border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
-moz-border-radius-topright: 4px;
-moz-border-radius-bottomright: 4px;
}
@@ -4479,9 +4634,9 @@ input[type="submit"].btn.btn-mini {
.pagination-large ul > li:first-child > a,
.pagination-large ul > li:first-child > span {
-webkit-border-bottom-left-radius: 6px;
- border-bottom-left-radius: 6px;
+ border-bottom-left-radius: 6px;
-webkit-border-top-left-radius: 6px;
- border-top-left-radius: 6px;
+ border-top-left-radius: 6px;
-moz-border-radius-bottomleft: 6px;
-moz-border-radius-topleft: 6px;
}
@@ -4489,9 +4644,9 @@ input[type="submit"].btn.btn-mini {
.pagination-large ul > li:last-child > a,
.pagination-large ul > li:last-child > span {
-webkit-border-top-right-radius: 6px;
- border-top-right-radius: 6px;
+ border-top-right-radius: 6px;
-webkit-border-bottom-right-radius: 6px;
- border-bottom-right-radius: 6px;
+ border-bottom-right-radius: 6px;
-moz-border-radius-topright: 6px;
-moz-border-radius-bottomright: 6px;
}
@@ -4501,9 +4656,9 @@ input[type="submit"].btn.btn-mini {
.pagination-mini ul > li:first-child > span,
.pagination-small ul > li:first-child > span {
-webkit-border-bottom-left-radius: 3px;
- border-bottom-left-radius: 3px;
+ border-bottom-left-radius: 3px;
-webkit-border-top-left-radius: 3px;
- border-top-left-radius: 3px;
+ border-top-left-radius: 3px;
-moz-border-radius-bottomleft: 3px;
-moz-border-radius-topleft: 3px;
}
@@ -4513,9 +4668,9 @@ input[type="submit"].btn.btn-mini {
.pagination-mini ul > li:last-child > span,
.pagination-small ul > li:last-child > span {
-webkit-border-top-right-radius: 3px;
- border-top-right-radius: 3px;
+ border-top-right-radius: 3px;
-webkit-border-bottom-right-radius: 3px;
- border-bottom-right-radius: 3px;
+ border-bottom-right-radius: 3px;
-moz-border-radius-topright: 3px;
-moz-border-radius-bottomright: 3px;
}
@@ -4561,8 +4716,8 @@ input[type="submit"].btn.btn-mini {
background-color: #fff;
border: 1px solid #ddd;
-webkit-border-radius: 15px;
- -moz-border-radius: 15px;
- border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
}
.pager li > a:hover,
@@ -4622,23 +4777,31 @@ input[type="submit"].btn.btn-mini {
border: 1px solid rgba(0, 0, 0, 0.3);
*border: 1px solid #999;
-webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
outline: none;
-webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
- -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
- box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
-webkit-background-clip: padding-box;
- -moz-background-clip: padding-box;
- background-clip: padding-box;
+ -moz-background-clip: padding-box;
+ background-clip: padding-box;
}
.modal.fade {
top: -25%;
- -webkit-transition: opacity 0.3s linear, top 0.3s ease-out;
- -moz-transition: opacity 0.3s linear, top 0.3s ease-out;
- -o-transition: opacity 0.3s linear, top 0.3s ease-out;
- transition: opacity 0.3s linear, top 0.3s ease-out;
+ -webkit-transition:
+ opacity 0.3s linear,
+ top 0.3s ease-out;
+ -moz-transition:
+ opacity 0.3s linear,
+ top 0.3s ease-out;
+ -o-transition:
+ opacity 0.3s linear,
+ top 0.3s ease-out;
+ transition:
+ opacity 0.3s linear,
+ top 0.3s ease-out;
}
.modal.fade.in {
@@ -4677,12 +4840,12 @@ input[type="submit"].btn.btn-mini {
background-color: #f5f5f5;
border-top: 1px solid #ddd;
-webkit-border-radius: 0 0 6px 6px;
- -moz-border-radius: 0 0 6px 6px;
- border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
*zoom: 1;
-webkit-box-shadow: inset 0 1px 0 #ffffff;
- -moz-box-shadow: inset 0 1px 0 #ffffff;
- box-shadow: inset 0 1px 0 #ffffff;
+ -moz-box-shadow: inset 0 1px 0 #ffffff;
+ box-shadow: inset 0 1px 0 #ffffff;
}
.modal-footer:before,
@@ -4753,8 +4916,8 @@ input[type="submit"].btn.btn-mini {
text-decoration: none;
background-color: #000000;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
}
.tooltip-arrow {
@@ -4811,14 +4974,14 @@ input[type="submit"].btn.btn-mini {
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
-webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
- -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
- box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-webkit-background-clip: padding-box;
- -moz-background-clip: padding;
- background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
}
.popover.top {
@@ -4846,8 +5009,8 @@ input[type="submit"].btn.btn-mini {
background-color: #f7f7f7;
border-bottom: 1px solid #ebebeb;
-webkit-border-radius: 5px 5px 0 0;
- -moz-border-radius: 5px 5px 0 0;
- border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ border-radius: 5px 5px 0 0;
}
.popover-title:empty {
@@ -4974,23 +5137,23 @@ input[type="submit"].btn.btn-mini {
line-height: 20px;
border: 1px solid #ddd;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
- -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
-webkit-transition: all 0.2s ease-in-out;
- -moz-transition: all 0.2s ease-in-out;
- -o-transition: all 0.2s ease-in-out;
- transition: all 0.2s ease-in-out;
+ -moz-transition: all 0.2s ease-in-out;
+ -o-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
}
a.thumbnail:hover,
a.thumbnail:focus {
border-color: #0088cc;
-webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
- -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
- box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
}
.thumbnail > img {
@@ -5058,16 +5221,16 @@ a.thumbnail:focus {
.label {
-webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
}
.badge {
padding-right: 9px;
padding-left: 9px;
-webkit-border-radius: 9px;
- -moz-border-radius: 9px;
- border-radius: 9px;
+ -moz-border-radius: 9px;
+ border-radius: 9px;
}
.label:empty,
@@ -5196,18 +5359,24 @@ a.badge:focus {
overflow: hidden;
background-color: #f7f7f7;
background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#f5f5f5),
+ to(#f9f9f9)
+ );
background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
background-repeat: repeat-x;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
- -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
}
.progress .bar {
@@ -5220,56 +5389,121 @@ a.badge:focus {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
background-color: #0e90d2;
background-image: -moz-linear-gradient(top, #149bdf, #0480be);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#149bdf),
+ to(#0480be)
+ );
background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
background-image: -o-linear-gradient(top, #149bdf, #0480be);
background-image: linear-gradient(to bottom, #149bdf, #0480be);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
- -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
- box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
-webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
-webkit-transition: width 0.6s ease;
- -moz-transition: width 0.6s ease;
- -o-transition: width 0.6s ease;
- transition: width 0.6s ease;
+ -moz-transition: width 0.6s ease;
+ -o-transition: width 0.6s ease;
+ transition: width 0.6s ease;
}
.progress .bar + .bar {
- -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
- -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
- box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -webkit-box-shadow:
+ inset 1px 0 0 rgba(0, 0, 0, 0.15),
+ inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow:
+ inset 1px 0 0 rgba(0, 0, 0, 0.15),
+ inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow:
+ inset 1px 0 0 rgba(0, 0, 0, 0.15),
+ inset 0 -1px 0 rgba(0, 0, 0, 0.15);
}
.progress-striped .bar {
background-color: #149bdf;
- background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -webkit-gradient(
+ linear,
+ 0 100%,
+ 100% 0,
+ color-stop(0.25, rgba(255, 255, 255, 0.15)),
+ color-stop(0.25, transparent),
+ color-stop(0.5, transparent),
+ color-stop(0.5, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, transparent),
+ to(transparent)
+ );
+ background-image: -webkit-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -moz-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -o-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
-webkit-background-size: 40px 40px;
- -moz-background-size: 40px 40px;
- -o-background-size: 40px 40px;
- background-size: 40px 40px;
+ -moz-background-size: 40px 40px;
+ -o-background-size: 40px 40px;
+ background-size: 40px 40px;
}
.progress.active .bar {
-webkit-animation: progress-bar-stripes 2s linear infinite;
- -moz-animation: progress-bar-stripes 2s linear infinite;
- -ms-animation: progress-bar-stripes 2s linear infinite;
- -o-animation: progress-bar-stripes 2s linear infinite;
- animation: progress-bar-stripes 2s linear infinite;
+ -moz-animation: progress-bar-stripes 2s linear infinite;
+ -ms-animation: progress-bar-stripes 2s linear infinite;
+ -o-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite;
}
.progress-danger .bar,
.progress .bar-danger {
background-color: #dd514c;
background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#ee5f5b),
+ to(#c43c35)
+ );
background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
@@ -5280,18 +5514,71 @@ a.badge:focus {
.progress-danger.progress-striped .bar,
.progress-striped .bar-danger {
background-color: #ee5f5b;
- background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -webkit-gradient(
+ linear,
+ 0 100%,
+ 100% 0,
+ color-stop(0.25, rgba(255, 255, 255, 0.15)),
+ color-stop(0.25, transparent),
+ color-stop(0.5, transparent),
+ color-stop(0.5, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, transparent),
+ to(transparent)
+ );
+ background-image: -webkit-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -moz-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -o-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
}
.progress-success .bar,
.progress .bar-success {
background-color: #5eb95e;
background-image: -moz-linear-gradient(top, #62c462, #57a957);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#62c462),
+ to(#57a957)
+ );
background-image: -webkit-linear-gradient(top, #62c462, #57a957);
background-image: -o-linear-gradient(top, #62c462, #57a957);
background-image: linear-gradient(to bottom, #62c462, #57a957);
@@ -5302,18 +5589,71 @@ a.badge:focus {
.progress-success.progress-striped .bar,
.progress-striped .bar-success {
background-color: #62c462;
- background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -webkit-gradient(
+ linear,
+ 0 100%,
+ 100% 0,
+ color-stop(0.25, rgba(255, 255, 255, 0.15)),
+ color-stop(0.25, transparent),
+ color-stop(0.5, transparent),
+ color-stop(0.5, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, transparent),
+ to(transparent)
+ );
+ background-image: -webkit-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -moz-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -o-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
}
.progress-info .bar,
.progress .bar-info {
background-color: #4bb1cf;
background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#5bc0de),
+ to(#339bb9)
+ );
background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
@@ -5324,18 +5664,71 @@ a.badge:focus {
.progress-info.progress-striped .bar,
.progress-striped .bar-info {
background-color: #5bc0de;
- background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -webkit-gradient(
+ linear,
+ 0 100%,
+ 100% 0,
+ color-stop(0.25, rgba(255, 255, 255, 0.15)),
+ color-stop(0.25, transparent),
+ color-stop(0.5, transparent),
+ color-stop(0.5, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, transparent),
+ to(transparent)
+ );
+ background-image: -webkit-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -moz-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -o-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
}
.progress-warning .bar,
.progress .bar-warning {
background-color: #faa732;
background-image: -moz-linear-gradient(top, #fbb450, #f89406);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-gradient(
+ linear,
+ 0 0,
+ 0 100%,
+ from(#fbb450),
+ to(#f89406)
+ );
background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
background-image: -o-linear-gradient(top, #fbb450, #f89406);
background-image: linear-gradient(to bottom, #fbb450, #f89406);
@@ -5346,11 +5739,58 @@ a.badge:focus {
.progress-warning.progress-striped .bar,
.progress-striped .bar-warning {
background-color: #fbb450;
- background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -webkit-gradient(
+ linear,
+ 0 100%,
+ 100% 0,
+ color-stop(0.25, rgba(255, 255, 255, 0.15)),
+ color-stop(0.25, transparent),
+ color-stop(0.5, transparent),
+ color-stop(0.5, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, transparent),
+ to(transparent)
+ );
+ background-image: -webkit-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -moz-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -o-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
}
.accordion {
@@ -5361,8 +5801,8 @@ a.badge:focus {
margin-bottom: 2px;
border: 1px solid #e5e5e5;
-webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
}
.accordion-heading {
@@ -5399,9 +5839,9 @@ a.badge:focus {
position: relative;
display: none;
-webkit-transition: 0.6s ease-in-out left;
- -moz-transition: 0.6s ease-in-out left;
- -o-transition: 0.6s ease-in-out left;
- transition: 0.6s ease-in-out left;
+ -moz-transition: 0.6s ease-in-out left;
+ -o-transition: 0.6s ease-in-out left;
+ transition: 0.6s ease-in-out left;
}
.carousel-inner > .item > img,
@@ -5463,8 +5903,8 @@ a.badge:focus {
background: #222222;
border: 3px solid #ffffff;
-webkit-border-radius: 23px;
- -moz-border-radius: 23px;
- border-radius: 23px;
+ -moz-border-radius: 23px;
+ border-radius: 23px;
opacity: 0.5;
filter: alpha(opacity=50);
}
@@ -5540,8 +5980,8 @@ a.badge:focus {
color: inherit;
background-color: #eeeeee;
-webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
}
.hero-unit h1 {
diff --git a/src/css/catalog-search-view.css b/src/css/catalog-search-view.css
index e8e0bf3bc..0bf328710 100644
--- a/src/css/catalog-search-view.css
+++ b/src/css/catalog-search-view.css
@@ -18,7 +18,6 @@
--cs-border-radius: 0.5rem;
}
-
/* ------ PAGE LAYOUT ------ */
/* organize the page elements that are outside of the catalog search view */
@@ -72,8 +71,7 @@
.catalog {
display: grid;
grid-template-columns: min-content 1fr 1fr;
- grid-template-areas:
- "filters results map";
+ grid-template-areas: "filters results map";
grid-template-rows: 100%;
height: 100%;
overflow: hidden;
@@ -135,8 +133,10 @@
/* ------ MAP CONTROLS ------ */
/* the toggle buttons and their labels */
-.catalog__map-toggle, .catalog__filters-toggle,
-.catalog__toggle-map-label, .catalog__toggle-filters-label {
+.catalog__map-toggle,
+.catalog__filters-toggle,
+.catalog__toggle-map-label,
+.catalog__toggle-filters-label {
position: absolute;
top: var(--cs-padding-large);
box-shadow: var(--cs-element-shadow);
@@ -144,7 +144,8 @@
}
/* buttons */
-.catalog__map-toggle, .catalog__filters-toggle {
+.catalog__map-toggle,
+.catalog__filters-toggle {
top: var(--cs-padding-small);
height: var(--cs-toggle-height, 2.5rem);
width: var(--cs-toggle-width, 1.9rem);
@@ -165,12 +166,14 @@
border-radius: 0 var(--cs-border-radius) var(--cs-border-radius) 0;
}
-.catalog__map-toggle:hover, .catalog__filters-toggle:hover {
+.catalog__map-toggle:hover,
+.catalog__filters-toggle:hover {
filter: brightness(0.9);
}
/* labels */
-.catalog__toggle-map-label, .catalog__toggle-filters-label {
+.catalog__toggle-map-label,
+.catalog__toggle-filters-label {
display: none;
/* show on hover only */
top: 0.9rem;
@@ -182,15 +185,14 @@
.catalog__toggle-map-label {
right: calc(var(--cs-padding-small) + var(--cs-toggle-width));
-
}
.catalog__toggle-filters-label {
left: calc(var(--cs-padding-small) + var(--cs-toggle-width));
}
-.catalog__map-toggle:hover~.catalog__toggle-map-label,
-.catalog__filters-toggle:hover~.catalog__toggle-filters-label {
+.catalog__map-toggle:hover ~ .catalog__toggle-map-label,
+.catalog__filters-toggle:hover ~ .catalog__toggle-filters-label {
display: block;
}
@@ -220,14 +222,13 @@
}
/* the rule has high specificity to override bootstrap rules */
-.catalog__map-filter-toggle input[type=checkbox] {
+.catalog__map-filter-toggle input[type="checkbox"] {
margin: 0;
order: -1;
transform: scale(1.35);
margin-right: 0.5rem;
}
-
/* ------ MAP HIDDEN (formerly "LIST MODE") ------ */
/* catalog is styled as map mode by default. Modifications needed for list-mode
(map hidden) are below */
@@ -247,8 +248,7 @@
.catalog--map-hidden .catalog {
grid-template-columns: min-content auto;
- grid-template-areas:
- "filters results";
+ grid-template-areas: "filters results";
overflow: scroll;
}
@@ -262,8 +262,7 @@ filters visible are below */
.catalog--filters-hidden .catalog {
grid-template-columns: 1fr 1fr;
- grid-template-areas:
- "results map";
+ grid-template-areas: "results map";
}
/* ------ FILTERS HIDDEN, MAP HIDDEN ------ */
@@ -272,6 +271,5 @@ entire width of the page */
.catalog--map-hidden.catalog--filters-hidden .catalog {
grid-template-columns: 1fr;
- grid-template-areas:
- "results";
-}
\ No newline at end of file
+ grid-template-areas: "results";
+}
diff --git a/src/css/jquery.sidr.dark.css b/src/css/jquery.sidr.dark.css
index df2149df8..6dd2b0056 100644
--- a/src/css/jquery.sidr.dark.css
+++ b/src/css/jquery.sidr.dark.css
@@ -1 +1,190 @@
-.sidr{display:none;position:absolute;position:fixed;top:0;height:100%;z-index:999999;width:260px;overflow-x:none;overflow-y:auto;font-family:"lucida grande",tahoma,verdana,arial,sans-serif;font-size:15px;background:#333;color:#fff;-webkit-box-shadow:inset 0 0 5px 5px #222;-moz-box-shadow:inset 0 0 5px 5px #222;box-shadow:inset 0 0 5px 5px #222}.sidr .sidr-inner{padding:0 0 15px}.sidr .sidr-inner>p{margin-left:15px;margin-right:15px}.sidr.right{left:auto;right:-260px}.sidr.left{left:0px;right:auto}.sidr h1,.sidr h2,.sidr h3,.sidr h4,.sidr h5,.sidr h6{font-size:11px;font-weight:normal;padding:0 15px;margin:0 0 5px;color:#fff;line-height:24px;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #4d4d4d), color-stop(100%, #1a1a1a));background-image:-webkit-linear-gradient(#4d4d4d,#1a1a1a);background-image:-moz-linear-gradient(#4d4d4d,#1a1a1a);background-image:-o-linear-gradient(#4d4d4d,#1a1a1a);background-image:linear-gradient(#4d4d4d,#1a1a1a);-webkit-box-shadow:0 5px 5px 3px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 5px 3px rgba(0,0,0,0.2);box-shadow:0 5px 5px 3px rgba(0,0,0,0.2)}.sidr p{font-size:13px;margin:0 0 12px}.sidr p a{color:rgba(255,255,255,0.9)}.sidr>p{margin-left:15px;margin-right:15px}.sidr>ul{display:block;margin:0 0 15px;padding:0;border-top:1px solid #1a1a1a;border-bottom:1px solid #4d4d4d}.sidr > ul li{display:block;margin:0;line-height:48px;border-top:1px solid #4d4d4d;border-bottom:1px solid #1a1a1a}.sidr ul li:hover,.sidr ul li.active,.sidr ul li.sidr-class-active{border-top:none;line-height:49px}.sidr ul li:hover>a,.sidr ul li:hover>span,.sidr ul li.active>a,.sidr ul li.active>span,.sidr ul li.sidr-class-active>a,.sidr ul li.sidr-class-active>span{-webkit-box-shadow:inset 0 0 15px 3px #222;-moz-box-shadow:inset 0 0 15px 3px #222;box-shadow:inset 0 0 15px 3px #222}.sidr ul li a,.sidr ul li span{padding:0 15px;display:block;text-decoration:none;color:#fff}.sidr ul li ul{border-bottom:none;margin:0}.sidr ul li ul li{line-height:40px;font-size:13px}.sidr ul li ul li:last-child{border-bottom:none}.sidr ul li ul li:hover,.sidr ul li ul li.active,.sidr ul li ul li.sidr-class-active{border-top:none;line-height:41px}.sidr ul li ul li:hover>a,.sidr ul li ul li:hover>span,.sidr ul li ul li.active>a,.sidr ul li ul li.active>span,.sidr ul li ul li.sidr-class-active>a,.sidr ul li ul li.sidr-class-active>span{-webkit-box-shadow:inset 0 0 15px 3px #222;-moz-box-shadow:inset 0 0 15px 3px #222;box-shadow:inset 0 0 15px 3px #222}.sidr ul li ul li a,.sidr ul li ul li span{color:rgba(255,255,255,0.8);padding-left:30px}.sidr form{margin:0 15px}.sidr label{font-size:13px}.sidr input[type="text"],.sidr input[type="password"],.sidr input[type="date"],.sidr input[type="datetime"],.sidr input[type="email"],.sidr input[type="number"],.sidr input[type="search"],.sidr input[type="tel"],.sidr input[type="time"],.sidr input[type="url"],.sidr textarea,.sidr select{width:100%;font-size:13px;padding:5px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0 0 10px;-webkit-border-radius:2px;-moz-border-radius:2px;-ms-border-radius:2px;-o-border-radius:2px;border-radius:2px;border:none;background:rgba(0,0,0,0.1);color:rgba(255,255,255,0.6);display:block;clear:both}.sidr input[type=checkbox]{width:auto;display:inline;clear:none}.sidr input[type=button],.sidr input[type=submit]{color:#333;background:#fff}.sidr input[type=button]:hover,.sidr input[type=submit]:hover{background:rgba(255,255,255,0.9)}
+.sidr {
+ display: none;
+ position: absolute;
+ position: fixed;
+ top: 0;
+ height: 100%;
+ z-index: 999999;
+ width: 260px;
+ overflow-x: none;
+ overflow-y: auto;
+ font-family: "lucida grande", tahoma, verdana, arial, sans-serif;
+ font-size: 15px;
+ background: #333;
+ color: #fff;
+ -webkit-box-shadow: inset 0 0 5px 5px #222;
+ -moz-box-shadow: inset 0 0 5px 5px #222;
+ box-shadow: inset 0 0 5px 5px #222;
+}
+.sidr .sidr-inner {
+ padding: 0 0 15px;
+}
+.sidr .sidr-inner > p {
+ margin-left: 15px;
+ margin-right: 15px;
+}
+.sidr.right {
+ left: auto;
+ right: -260px;
+}
+.sidr.left {
+ left: 0px;
+ right: auto;
+}
+.sidr h1,
+.sidr h2,
+.sidr h3,
+.sidr h4,
+.sidr h5,
+.sidr h6 {
+ font-size: 11px;
+ font-weight: normal;
+ padding: 0 15px;
+ margin: 0 0 5px;
+ color: #fff;
+ line-height: 24px;
+ background-image: -webkit-gradient(
+ linear,
+ 50% 0%,
+ 50% 100%,
+ color-stop(0%, #4d4d4d),
+ color-stop(100%, #1a1a1a)
+ );
+ background-image: -webkit-linear-gradient(#4d4d4d, #1a1a1a);
+ background-image: -moz-linear-gradient(#4d4d4d, #1a1a1a);
+ background-image: -o-linear-gradient(#4d4d4d, #1a1a1a);
+ background-image: linear-gradient(#4d4d4d, #1a1a1a);
+ -webkit-box-shadow: 0 5px 5px 3px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 5px 3px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 5px 3px rgba(0, 0, 0, 0.2);
+}
+.sidr p {
+ font-size: 13px;
+ margin: 0 0 12px;
+}
+.sidr p a {
+ color: rgba(255, 255, 255, 0.9);
+}
+.sidr > p {
+ margin-left: 15px;
+ margin-right: 15px;
+}
+.sidr > ul {
+ display: block;
+ margin: 0 0 15px;
+ padding: 0;
+ border-top: 1px solid #1a1a1a;
+ border-bottom: 1px solid #4d4d4d;
+}
+.sidr > ul li {
+ display: block;
+ margin: 0;
+ line-height: 48px;
+ border-top: 1px solid #4d4d4d;
+ border-bottom: 1px solid #1a1a1a;
+}
+.sidr ul li:hover,
+.sidr ul li.active,
+.sidr ul li.sidr-class-active {
+ border-top: none;
+ line-height: 49px;
+}
+.sidr ul li:hover > a,
+.sidr ul li:hover > span,
+.sidr ul li.active > a,
+.sidr ul li.active > span,
+.sidr ul li.sidr-class-active > a,
+.sidr ul li.sidr-class-active > span {
+ -webkit-box-shadow: inset 0 0 15px 3px #222;
+ -moz-box-shadow: inset 0 0 15px 3px #222;
+ box-shadow: inset 0 0 15px 3px #222;
+}
+.sidr ul li a,
+.sidr ul li span {
+ padding: 0 15px;
+ display: block;
+ text-decoration: none;
+ color: #fff;
+}
+.sidr ul li ul {
+ border-bottom: none;
+ margin: 0;
+}
+.sidr ul li ul li {
+ line-height: 40px;
+ font-size: 13px;
+}
+.sidr ul li ul li:last-child {
+ border-bottom: none;
+}
+.sidr ul li ul li:hover,
+.sidr ul li ul li.active,
+.sidr ul li ul li.sidr-class-active {
+ border-top: none;
+ line-height: 41px;
+}
+.sidr ul li ul li:hover > a,
+.sidr ul li ul li:hover > span,
+.sidr ul li ul li.active > a,
+.sidr ul li ul li.active > span,
+.sidr ul li ul li.sidr-class-active > a,
+.sidr ul li ul li.sidr-class-active > span {
+ -webkit-box-shadow: inset 0 0 15px 3px #222;
+ -moz-box-shadow: inset 0 0 15px 3px #222;
+ box-shadow: inset 0 0 15px 3px #222;
+}
+.sidr ul li ul li a,
+.sidr ul li ul li span {
+ color: rgba(255, 255, 255, 0.8);
+ padding-left: 30px;
+}
+.sidr form {
+ margin: 0 15px;
+}
+.sidr label {
+ font-size: 13px;
+}
+.sidr input[type="text"],
+.sidr input[type="password"],
+.sidr input[type="date"],
+.sidr input[type="datetime"],
+.sidr input[type="email"],
+.sidr input[type="number"],
+.sidr input[type="search"],
+.sidr input[type="tel"],
+.sidr input[type="time"],
+.sidr input[type="url"],
+.sidr textarea,
+.sidr select {
+ width: 100%;
+ font-size: 13px;
+ padding: 5px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ margin: 0 0 10px;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ -ms-border-radius: 2px;
+ -o-border-radius: 2px;
+ border-radius: 2px;
+ border: none;
+ background: rgba(0, 0, 0, 0.1);
+ color: rgba(255, 255, 255, 0.6);
+ display: block;
+ clear: both;
+}
+.sidr input[type="checkbox"] {
+ width: auto;
+ display: inline;
+ clear: none;
+}
+.sidr input[type="button"],
+.sidr input[type="submit"] {
+ color: #333;
+ background: #fff;
+}
+.sidr input[type="button"]:hover,
+.sidr input[type="submit"]:hover {
+ background: rgba(255, 255, 255, 0.9);
+}
diff --git a/src/css/jquery.sidr.light.css b/src/css/jquery.sidr.light.css
index 50d0685c7..399deceb7 100644
--- a/src/css/jquery.sidr.light.css
+++ b/src/css/jquery.sidr.light.css
@@ -1 +1,190 @@
-.sidr{display:none;position:absolute;position:fixed;top:0;height:100%;z-index:999999;width:260px;overflow-x:none;overflow-y:auto;font-family:"lucida grande",tahoma,verdana,arial,sans-serif;font-size:15px;background:#f8f8f8;color:#333;-webkit-box-shadow:inset 0 0 5px 5px #ebebeb;-moz-box-shadow:inset 0 0 5px 5px #ebebeb;box-shadow:inset 0 0 5px 5px #ebebeb}.sidr .sidr-inner{padding:0 0 15px}.sidr .sidr-inner>p{margin-left:15px;margin-right:15px}.sidr.right{left:auto;right:-260px}.sidr.left{left:-260px;right:auto}.sidr h1,.sidr h2,.sidr h3,.sidr h4,.sidr h5,.sidr h6{font-size:11px;font-weight:normal;padding:0 15px;margin:0 0 5px;color:#333;line-height:24px;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #dfdfdf));background-image:-webkit-linear-gradient(#ffffff,#dfdfdf);background-image:-moz-linear-gradient(#ffffff,#dfdfdf);background-image:-o-linear-gradient(#ffffff,#dfdfdf);background-image:linear-gradient(#ffffff,#dfdfdf);-webkit-box-shadow:0 5px 5px 3px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 5px 3px rgba(0,0,0,0.2);box-shadow:0 5px 5px 3px rgba(0,0,0,0.2)}.sidr p{font-size:13px;margin:0 0 12px}.sidr p a{color:rgba(51,51,51,0.9)}.sidr>p{margin-left:15px;margin-right:15px}.sidr ul{display:block;margin:0 0 15px;padding:0;border-top:1px solid #dfdfdf;border-bottom:1px solid #fff}.sidr ul li{display:block;margin:0;line-height:48px;border-top:1px solid #fff;border-bottom:1px solid #dfdfdf}.sidr ul li:hover,.sidr ul li.active,.sidr ul li.sidr-class-active{border-top:none;line-height:49px}.sidr ul li:hover>a,.sidr ul li:hover>span,.sidr ul li.active>a,.sidr ul li.active>span,.sidr ul li.sidr-class-active>a,.sidr ul li.sidr-class-active>span{-webkit-box-shadow:inset 0 0 15px 3px #ebebeb;-moz-box-shadow:inset 0 0 15px 3px #ebebeb;box-shadow:inset 0 0 15px 3px #ebebeb}.sidr ul li a,.sidr ul li span{padding:0 15px;display:block;text-decoration:none;color:#333}.sidr ul li ul{border-bottom:none;margin:0}.sidr ul li ul li{line-height:40px;font-size:13px}.sidr ul li ul li:last-child{border-bottom:none}.sidr ul li ul li:hover,.sidr ul li ul li.active,.sidr ul li ul li.sidr-class-active{border-top:none;line-height:41px}.sidr ul li ul li:hover>a,.sidr ul li ul li:hover>span,.sidr ul li ul li.active>a,.sidr ul li ul li.active>span,.sidr ul li ul li.sidr-class-active>a,.sidr ul li ul li.sidr-class-active>span{-webkit-box-shadow:inset 0 0 15px 3px #ebebeb;-moz-box-shadow:inset 0 0 15px 3px #ebebeb;box-shadow:inset 0 0 15px 3px #ebebeb}.sidr ul li ul li a,.sidr ul li ul li span{color:rgba(51,51,51,0.8);padding-left:30px}.sidr form{margin:0 15px}.sidr label{font-size:13px}.sidr input[type="text"],.sidr input[type="password"],.sidr input[type="date"],.sidr input[type="datetime"],.sidr input[type="email"],.sidr input[type="number"],.sidr input[type="search"],.sidr input[type="tel"],.sidr input[type="time"],.sidr input[type="url"],.sidr textarea,.sidr select{width:100%;font-size:13px;padding:5px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0 0 10px;-webkit-border-radius:2px;-moz-border-radius:2px;-ms-border-radius:2px;-o-border-radius:2px;border-radius:2px;border:none;background:rgba(0,0,0,0.1);color:rgba(51,51,51,0.6);display:block;clear:both}.sidr input[type=checkbox]{width:auto;display:inline;clear:none}.sidr input[type=button],.sidr input[type=submit]{color:#f8f8f8;background:#333}.sidr input[type=button]:hover,.sidr input[type=submit]:hover{background:rgba(51,51,51,0.9)}
+.sidr {
+ display: none;
+ position: absolute;
+ position: fixed;
+ top: 0;
+ height: 100%;
+ z-index: 999999;
+ width: 260px;
+ overflow-x: none;
+ overflow-y: auto;
+ font-family: "lucida grande", tahoma, verdana, arial, sans-serif;
+ font-size: 15px;
+ background: #f8f8f8;
+ color: #333;
+ -webkit-box-shadow: inset 0 0 5px 5px #ebebeb;
+ -moz-box-shadow: inset 0 0 5px 5px #ebebeb;
+ box-shadow: inset 0 0 5px 5px #ebebeb;
+}
+.sidr .sidr-inner {
+ padding: 0 0 15px;
+}
+.sidr .sidr-inner > p {
+ margin-left: 15px;
+ margin-right: 15px;
+}
+.sidr.right {
+ left: auto;
+ right: -260px;
+}
+.sidr.left {
+ left: -260px;
+ right: auto;
+}
+.sidr h1,
+.sidr h2,
+.sidr h3,
+.sidr h4,
+.sidr h5,
+.sidr h6 {
+ font-size: 11px;
+ font-weight: normal;
+ padding: 0 15px;
+ margin: 0 0 5px;
+ color: #333;
+ line-height: 24px;
+ background-image: -webkit-gradient(
+ linear,
+ 50% 0%,
+ 50% 100%,
+ color-stop(0%, #ffffff),
+ color-stop(100%, #dfdfdf)
+ );
+ background-image: -webkit-linear-gradient(#ffffff, #dfdfdf);
+ background-image: -moz-linear-gradient(#ffffff, #dfdfdf);
+ background-image: -o-linear-gradient(#ffffff, #dfdfdf);
+ background-image: linear-gradient(#ffffff, #dfdfdf);
+ -webkit-box-shadow: 0 5px 5px 3px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 5px 3px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 5px 3px rgba(0, 0, 0, 0.2);
+}
+.sidr p {
+ font-size: 13px;
+ margin: 0 0 12px;
+}
+.sidr p a {
+ color: rgba(51, 51, 51, 0.9);
+}
+.sidr > p {
+ margin-left: 15px;
+ margin-right: 15px;
+}
+.sidr ul {
+ display: block;
+ margin: 0 0 15px;
+ padding: 0;
+ border-top: 1px solid #dfdfdf;
+ border-bottom: 1px solid #fff;
+}
+.sidr ul li {
+ display: block;
+ margin: 0;
+ line-height: 48px;
+ border-top: 1px solid #fff;
+ border-bottom: 1px solid #dfdfdf;
+}
+.sidr ul li:hover,
+.sidr ul li.active,
+.sidr ul li.sidr-class-active {
+ border-top: none;
+ line-height: 49px;
+}
+.sidr ul li:hover > a,
+.sidr ul li:hover > span,
+.sidr ul li.active > a,
+.sidr ul li.active > span,
+.sidr ul li.sidr-class-active > a,
+.sidr ul li.sidr-class-active > span {
+ -webkit-box-shadow: inset 0 0 15px 3px #ebebeb;
+ -moz-box-shadow: inset 0 0 15px 3px #ebebeb;
+ box-shadow: inset 0 0 15px 3px #ebebeb;
+}
+.sidr ul li a,
+.sidr ul li span {
+ padding: 0 15px;
+ display: block;
+ text-decoration: none;
+ color: #333;
+}
+.sidr ul li ul {
+ border-bottom: none;
+ margin: 0;
+}
+.sidr ul li ul li {
+ line-height: 40px;
+ font-size: 13px;
+}
+.sidr ul li ul li:last-child {
+ border-bottom: none;
+}
+.sidr ul li ul li:hover,
+.sidr ul li ul li.active,
+.sidr ul li ul li.sidr-class-active {
+ border-top: none;
+ line-height: 41px;
+}
+.sidr ul li ul li:hover > a,
+.sidr ul li ul li:hover > span,
+.sidr ul li ul li.active > a,
+.sidr ul li ul li.active > span,
+.sidr ul li ul li.sidr-class-active > a,
+.sidr ul li ul li.sidr-class-active > span {
+ -webkit-box-shadow: inset 0 0 15px 3px #ebebeb;
+ -moz-box-shadow: inset 0 0 15px 3px #ebebeb;
+ box-shadow: inset 0 0 15px 3px #ebebeb;
+}
+.sidr ul li ul li a,
+.sidr ul li ul li span {
+ color: rgba(51, 51, 51, 0.8);
+ padding-left: 30px;
+}
+.sidr form {
+ margin: 0 15px;
+}
+.sidr label {
+ font-size: 13px;
+}
+.sidr input[type="text"],
+.sidr input[type="password"],
+.sidr input[type="date"],
+.sidr input[type="datetime"],
+.sidr input[type="email"],
+.sidr input[type="number"],
+.sidr input[type="search"],
+.sidr input[type="tel"],
+.sidr input[type="time"],
+.sidr input[type="url"],
+.sidr textarea,
+.sidr select {
+ width: 100%;
+ font-size: 13px;
+ padding: 5px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ margin: 0 0 10px;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ -ms-border-radius: 2px;
+ -o-border-radius: 2px;
+ border-radius: 2px;
+ border: none;
+ background: rgba(0, 0, 0, 0.1);
+ color: rgba(51, 51, 51, 0.6);
+ display: block;
+ clear: both;
+}
+.sidr input[type="checkbox"] {
+ width: auto;
+ display: inline;
+ clear: none;
+}
+.sidr input[type="button"],
+.sidr input[type="submit"] {
+ color: #f8f8f8;
+ background: #333;
+}
+.sidr input[type="button"]:hover,
+.sidr input[type="submit"]:hover {
+ background: rgba(51, 51, 51, 0.9);
+}
diff --git a/src/css/map-view.css b/src/css/map-view.css
index 11d67e2f3..af88a7f53 100644
--- a/src/css/map-view.css
+++ b/src/css/map-view.css
@@ -30,8 +30,12 @@
--map-col-content-bkg: var(--portal-col-content-bkg);
--map-col-content-bkg-highlight: var(--portal-col-content-bkg-highlight);
--map-col-content-bkg-muted: var(--portal-col-content-bkg-muted);
- --map-col-content-bkg-active-highlight: var(--portal-col-content-bkg-active-highlight);
- --map-col-content-bkg-hover-highlight: var(--portal-col-content-bkg-hover-highlight);
+ --map-col-content-bkg-active-highlight: var(
+ --portal-col-content-bkg-active-highlight
+ );
+ --map-col-content-bkg-hover-highlight: var(
+ --portal-col-content-bkg-hover-highlight
+ );
--map-col-content-bkg-transparent: var(--portal-col-content-bkg-transparent);
--map-col-buttons-bkg: var(--portal-col-buttons-bkg);
@@ -39,20 +43,28 @@
--map-col-buttons-bkg-hover: var(--portal-col-buttons-bkg-hover);
--map-col-buttons-bkg-highlight: var(--portal-col-buttons-bkg-highlight);
--map-col-buttons-bkg-muted: var(--portal-col-buttons-bkg-muted);
- --map-col-buttons-bkg-white-content-contrast: var(--portal-col-buttons-bkg-white-content-contrast);
+ --map-col-buttons-bkg-white-content-contrast: var(
+ --portal-col-buttons-bkg-white-content-contrast
+ );
--map-col-buttons-text: var(--portal-col-buttons-text);
--map-col-buttons-text-highlight: var(--portal-col-buttons-text-highlight);
--map-col-buttons-icon: var(--portal-col-buttons-icon);
--map-col-buttons-icon-highlight: var(--portal-col-buttons-icon-highlight);
--map-col-buttons-icon-muted: var(--portal-col-buttons-icon-muted);
--map-col-utility-buttons: var(--portal-col-utility-buttons);
- --map-col-buttons-icon-active-highlight: var(--portal-col-buttons-icon-active-highlight);
- --map-col-buttons-icon-hover-highlight: var(--portal-col-buttons-icon-hover-highlight);
+ --map-col-buttons-icon-active-highlight: var(
+ --portal-col-buttons-icon-active-highlight
+ );
+ --map-col-buttons-icon-hover-highlight: var(
+ --portal-col-buttons-icon-hover-highlight
+ );
--map-col-border: var(--portal-col-border);
--map-col-border-highlight: var(--portal-col-border-highlight);
--map-col-border-muted: var(--portal-col-border-muted);
- --map-col-border-highlight-search-box: var(--portal-col-border-highlight-search-box);
+ --map-col-border-highlight-search-box: var(
+ --portal-col-border-highlight-search-box
+ );
--map-col-border-panel: var(--portal-col-border-panel);
--map-col-section-divider: var(--portal-col-section-divider);
@@ -70,12 +82,23 @@
--map-col-opacity-slider-active: var(--portal-col-opacity-slider-active);
--map-col-opacity-slider-inactive: var(--portal-col-opacity-slider-inactive);
- --map-col-input-error-bkg: var(--portal-col-input-error-bkg, rgba(30,127,196, .5));
- --map-col-input-error-border: var(--portal-col-input-error-border, rgba(82, 168, 236, .8));
- --map-col-input-error-text: var(--portal-col-input-error-text, #CA6C0B);
- --map-col-search-match-highlight: var(--portal-col-search-match-highlight, rgba(30,127,196, .5));
-
- --map-no-brightness-or-opacity-tweaks: var(--portal-no-brightness-or-opacity-tweaks);
+ --map-col-input-error-bkg: var(
+ --portal-col-input-error-bkg,
+ rgba(30, 127, 196, 0.5)
+ );
+ --map-col-input-error-border: var(
+ --portal-col-input-error-border,
+ rgba(82, 168, 236, 0.8)
+ );
+ --map-col-input-error-text: var(--portal-col-input-error-text, #ca6c0b);
+ --map-col-search-match-highlight: var(
+ --portal-col-search-match-highlight,
+ rgba(30, 127, 196, 0.5)
+ );
+
+ --map-no-brightness-or-opacity-tweaks: var(
+ --portal-no-brightness-or-opacity-tweaks
+ );
--map-col-green: #069868;
--map-col-text-on-green: white;
@@ -91,9 +114,15 @@
--map-col-bkg-lighter__deprecate: #1b2538;
--map-col-bkg-lightest__deprecate: #242e42;
--map-col-buttons__deprecate: #313c52;
- --map-col-text__deprecate: #F9FAFB;
- --map-col-buttons-emphasis__deprecate: var(--portal-secondary-color, var(--map-col-green));
- --map-col-highlight__deprecate: var(--portal-primary-color, var(--map-col-blue));
+ --map-col-text__deprecate: #f9fafb;
+ --map-col-buttons-emphasis__deprecate: var(
+ --portal-secondary-color,
+ var(--map-col-green)
+ );
+ --map-col-highlight__deprecate: var(
+ --portal-primary-color,
+ var(--map-col-blue)
+ );
/* SIZING: */
--map-size-toolbar-link: 2.5rem;
--map-size-toolbar-link-margin: 1rem;
@@ -102,12 +131,18 @@
--map-border-radius-big: 0.5rem;
--map-width-toolbar: 21rem;
--map-width-toolbar-wide: 30rem;
- --map-toolbar-link-width: calc(var(--map-size-toolbar-link) + 2 * var(--map-size-toolbar-link-padding));
+ --map-toolbar-link-width: calc(
+ var(--map-size-toolbar-link) + 2 * var(--map-size-toolbar-link-padding)
+ );
--map-width-feature-info: calc(var(--map-width-toolbar) * 1.25);
--map-width-feature-info-wide: calc(var(--map-width-toolbar-wide) * 1.25);
--map-padding-feature-info: 1.5rem;
/* SHADOWS */
- --map-shadow-md: var(--portal-shadow-md, 0 1px 9px -1px rgba(0, 0, 0, 0.2), 0 1px 2px 0px rgba(0, 0, 0, 0.5));
+ --map-shadow-md: var(
+ --portal-shadow-md,
+ 0 1px 9px -1px rgba(0, 0, 0, 0.2),
+ 0 1px 2px 0px rgba(0, 0, 0, 0.5)
+ );
/* NOTE: 768px is used as the mobile -> desktop breakpoint throughout the map view, but we cannot use a CSS variable for this. */
/* FONTS */
@@ -137,24 +172,39 @@
}
.map-tooltip.top .tooltip-arrow {
- border-top-color: var(--map-col-tooltip-background, var(--map-col-buttons__deprecate));
+ border-top-color: var(
+ --map-col-tooltip-background,
+ var(--map-col-buttons__deprecate)
+ );
}
.map-tooltip.right .tooltip-arrow {
- border-right-color: var(--map-col-tooltip-background, var(--map-col-buttons__deprecate));
+ border-right-color: var(
+ --map-col-tooltip-background,
+ var(--map-col-buttons__deprecate)
+ );
}
.map-tooltip.left .tooltip-arrow {
- border-left-color: var(--map-col-tooltip-background, var(--map-col-buttons__deprecate));
+ border-left-color: var(
+ --map-col-tooltip-background,
+ var(--map-col-buttons__deprecate)
+ );
}
.map-tooltip.bottom .tooltip-arrow {
- border-bottom-color: var(--map-col-tooltip-background, var(--map-col-buttons__deprecate));
+ border-bottom-color: var(
+ --map-col-tooltip-background,
+ var(--map-col-buttons__deprecate)
+ );
}
.map-tooltip .tooltip-inner {
padding: 0.3rem;
- background-color: var(--map-col-tooltip-background, var(--map-col-buttons__deprecate));
+ background-color: var(
+ --map-col-tooltip-background,
+ var(--map-col-buttons__deprecate)
+ );
color: var(--map-col-tooltip-text);
}
@@ -184,14 +234,20 @@
}
.map-view__button--emphasis {
- background-color: var(--map-col-buttons-bkg, var(--map-col-buttons-emphasis__deprecate));
+ background-color: var(
+ --map-col-buttons-bkg,
+ var(--map-col-buttons-emphasis__deprecate)
+ );
padding: 0.5rem 0.55rem;
font-size: 1rem;
border: 1px solid var(--map-col-border);
}
.map-view__button--active {
- background-color: var(--map-col-buttons-bkg-highlight, var(--map-col-highlight__deprecate));
+ background-color: var(
+ --map-col-buttons-bkg-highlight,
+ var(--map-col-highlight__deprecate)
+ );
color: var(--map-col-buttons-text-highlight);
}
@@ -201,7 +257,10 @@
padding: 0.2rem 0.4rem;
margin: 0 0.25rem;
font-size: 0.66rem;
- background-color: var(--map-col-content-bkg-muted, var(--map-col-bkg-lighter__deprecate));
+ background-color: var(
+ --map-col-content-bkg-muted,
+ var(--map-col-bkg-lighter__deprecate)
+ );
color: var(--map-col-text-muted, var(--map-col-text__deprecate));
border-radius: var(--map-border-radius-small);
line-height: 1;
@@ -233,16 +292,15 @@
/* ---- Animation ---- */
-@keyframes fadeIn {
- from {
+@keyframes fadeIn {
+ from {
opacity: 0;
- }
- to {
+ }
+ to {
opacity: 1;
- }
+ }
}
-
/*****************************************************************************************
*
* Containers
@@ -265,10 +323,10 @@
/* Create shadow on top of the map */
.map-view::before {
- content: '';
+ content: "";
position: absolute;
height: 2rem;
- box-shadow: 0 0.2rem 0.2rem 0 rgba(0, 0, 0, .16) inset;
+ box-shadow: 0 0.2rem 0.2rem 0 rgba(0, 0, 0, 0.16) inset;
width: 100%;
z-index: var(--map-z-index-top-shadow);
}
@@ -298,8 +356,14 @@
.map-view__map-widget-container {
order: 1;
position: relative;
- width: calc(100% + var(--map-toolbar-link-width) + (2* var(--map-size-toolbar-link-margin)));
- margin-left: calc(-1 * var(--map-toolbar-link-width) - (2* var(--map-size-toolbar-link-margin)));
+ width: calc(
+ 100% + var(--map-toolbar-link-width) +
+ (2 * var(--map-size-toolbar-link-margin))
+ );
+ margin-left: calc(
+ -1 * var(--map-toolbar-link-width) - (2 *
+ var(--map-size-toolbar-link-margin))
+ );
}
}
@@ -342,10 +406,11 @@
/* required to be placed above map widget in firefox: */
z-index: var(--map-z-index-feature-info);
- .feature-info__zoom-button, .feature-info__layer-details-button {
+ .feature-info__zoom-button,
+ .feature-info__layer-details-button {
border: 1px solid var(--map-col-border);
border-radius: var(--map-border-radius-small);
- padding: .5rem 1rem;
+ padding: 0.5rem 1rem;
text-wrap: wrap;
}
}
@@ -369,14 +434,15 @@
*
*/
-.cesium-widget-view {}
+.cesium-widget-view {
+}
-.cesium-widget-view>.cesium-widget {
+.cesium-widget-view > .cesium-widget {
width: 100%;
height: 100%;
}
-.cesium-widget>canvas {
+.cesium-widget > canvas {
position: absolute;
width: 100%;
height: 100%;
@@ -439,12 +505,16 @@ represents 1 unit of the given distance measurement. */
.scale-bar__bar {
height: 0.25rem;
transition: width 0.3s ease-in-out;
- background-color: var(--map-col-buttons-icon-muted, var(--map-col-text__deprecate));
+ background-color: var(
+ --map-col-buttons-icon-muted,
+ var(--map-col-text__deprecate)
+ );
}
/* The elements that contains the distance measurement */
-.scale-bar__distance {}
+.scale-bar__distance {
+}
/*****************************************************************************************
*
@@ -464,11 +534,14 @@ represents 1 unit of the given distance measurement. */
display: flex;
flex-direction: column;
height: min-content;
- background-color: var(--map-col-content-bkg-highlight, var(--map-col-bkg__deprecate));
+ background-color: var(
+ --map-col-content-bkg-highlight,
+ var(--map-col-bkg__deprecate)
+ );
border-radius: var(--map-border-radius-big);
margin: var(--map-size-toolbar-link-margin);
- padding: .5rem;
- row-gap: .5rem;
+ padding: 0.5rem;
+ row-gap: 0.5rem;
pointer-events: all;
color: var(--map-col-buttons-bkg-highlight);
box-shadow: var(--map-shadow-md);
@@ -531,22 +604,31 @@ represents 1 unit of the given distance measurement. */
}
.toolbar__link:hover {
- background-color: var(--map-col-buttons-bkg-hover, var(--map-col-bkg-lighter__deprecate));
+ background-color: var(
+ --map-col-buttons-bkg-hover,
+ var(--map-col-bkg-lighter__deprecate)
+ );
}
.toolbar__link--active {
- background-color: var(--map-col-buttons-bkg-highlight, var(--map-col-highlight__deprecate));
+ background-color: var(
+ --map-col-buttons-bkg-highlight,
+ var(--map-col-highlight__deprecate)
+ );
color: var(--map-col-buttons-icon-highlight);
}
.toolbar__link--active:hover {
- background-color: var(--map-col-buttons-bkg-highlight, var(--map-col-highlight__deprecate));
+ background-color: var(
+ --map-col-buttons-bkg-highlight,
+ var(--map-col-highlight__deprecate)
+ );
}
.toolbar__link-title {
position: absolute;
- left: calc(100% + .5rem);
- top: .25rem;
+ left: calc(100% + 0.5rem);
+ top: 0.25rem;
color: var(--map-col-tooltip-text, var(--map-col-text__deprecate));
font-size: 0.75rem;
font-weight: 700;
@@ -555,21 +637,27 @@ represents 1 unit of the given distance measurement. */
}
.toolbar__link-title:before {
- content: '';
+ content: "";
position: absolute;
- width: .5rem;
- height: .5rem;
- left: -.5rem;
+ width: 0.5rem;
+ height: 0.5rem;
+ left: -0.5rem;
top: 50%;
- transform: translate(50%,-50%) rotate(-45deg);
- background-color: var(--map-col-tooltip-background, var(--map-col-buttons__deprecate));
+ transform: translate(50%, -50%) rotate(-45deg);
+ background-color: var(
+ --map-col-tooltip-background,
+ var(--map-col-buttons__deprecate)
+ );
}
.toolbar__link:hover .toolbar__link-title {
display: block;
padding: 0.25rem 0.5rem;
border-radius: var(--map-border-radius-small);
- background-color: var(--map-col-tooltip-background, var(--map-col-buttons__deprecate));
+ background-color: var(
+ --map-col-tooltip-background,
+ var(--map-col-buttons__deprecate)
+ );
}
.toolbar__link-icon {
@@ -588,7 +676,6 @@ represents 1 unit of the given distance measurement. */
*
*/
-
.search-input {
padding-bottom: 1rem;
background-color: var(--map-col-content-bkg, var(--map-col-bkg));
@@ -603,11 +690,12 @@ represents 1 unit of the given distance measurement. */
border: 1px solid;
border-color: var(--map-col-border-muted);
border-radius: var(--map-border-radius-small);
-
+
.search-input__input {
width: 100%;
-
- input[type=text]& { /* Override bootstrap */
+
+ input[type="text"]& {
+ /* Override bootstrap */
height: 100%;
margin: unset;
padding-left: 1rem;
@@ -624,14 +712,17 @@ represents 1 unit of the given distance measurement. */
}
&:focus-within {
- border-color: var(--map-col-border-highlight-search-box, var(--map-col-blue));
+ border-color: var(
+ --map-col-border-highlight-search-box,
+ var(--map-col-blue)
+ );
}
&.search-input__error-input {
border-color: var(--map-col-input-error-border);
background-color: var(--map-col-input-error-bkg);
}
-
+
.search-input__cancel-button-container {
align-items: center;
display: flex;
@@ -650,7 +741,7 @@ represents 1 unit of the given distance measurement. */
position: relative;
flex-shrink: 0;
margin: 0.25rem;
-
+
.search-input__button-icon {
font-size: 1rem;
}
@@ -668,7 +759,10 @@ represents 1 unit of the given distance measurement. */
&.search-input__search-button--active {
color: var(--map-col-buttons-icon, currentColor);
- background: var(--map-col-buttons-bkg-active, var(--map-col-bkg-lighter__deprecate));
+ background: var(
+ --map-col-buttons-bkg-active,
+ var(--map-col-bkg-lighter__deprecate)
+ );
}
}
.search-input__cancel-button {
@@ -693,7 +787,6 @@ represents 1 unit of the given distance measurement. */
*
*/
-
.layers-panel {
width: 100%;
display: flex;
@@ -704,10 +797,12 @@ represents 1 unit of the given distance measurement. */
}
.layers-panel__layers {
- border: solid var(--map-col-section-divider, var(--map-col-bkg-lightest__deprecate));
+ border: solid
+ var(--map-col-section-divider, var(--map-col-bkg-lightest__deprecate));
border-width: 1px 0;
overflow: auto;
- border-top: 1px solid var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate));
+ border-top: 1px solid
+ var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate));
}
}
@@ -719,7 +814,6 @@ represents 1 unit of the given distance measurement. */
*
*/
-
.layer-list {
display: grid;
grid-template-columns: 100%;
@@ -753,7 +847,7 @@ represents 1 unit of the given distance measurement. */
.layer-item__icon {
fill: var(--map-col-buttons-icon-muted, currentColor);
- svg>path {
+ svg > path {
fill: var(--map-col-buttons-icon-muted, currentColor);
}
@@ -765,7 +859,7 @@ represents 1 unit of the given distance measurement. */
&:hover {
background-color: var(--map-col-content-bkg-muted, #323745);
- transition: background-color .3s ease-in-out;
+ transition: background-color 0.3s ease-in-out;
&.layer-item--shown {
background-color: var(--map-col-content-bkg-highlight, #15324e);
@@ -777,20 +871,33 @@ represents 1 unit of the given distance measurement. */
border: 1px solid var(--map-col-border-muted);
/* Use the same style on button hover and when selected. */
- &.layer-item--selected, &:hover {
- background-color: var(--map-col-buttons-bkg-hover, var(--map-col-bkg-lighter__deprecate));
+ &.layer-item--selected,
+ &:hover {
+ background-color: var(
+ --map-col-buttons-bkg-hover,
+ var(--map-col-bkg-lighter__deprecate)
+ );
}
}
}
&.layer-item--shown {
.layer-item__visibility-toggle {
- color: var(--map-col-buttons-bkg-highlight, var(--map-col-highlight__deprecate));
+ color: var(
+ --map-col-buttons-bkg-highlight,
+ var(--map-col-highlight__deprecate)
+ );
.layer-item__icon {
- fill: var(--map-col-buttons-bkg-highlight, var(--map-col-highlight__deprecate));
- svg>path {
- fill: var(--map-col-buttons-bkg-highlight, var(--map-col-highlight__deprecate));
+ fill: var(
+ --map-col-buttons-bkg-highlight,
+ var(--map-col-highlight__deprecate)
+ );
+ svg > path {
+ fill: var(
+ --map-col-buttons-bkg-highlight,
+ var(--map-col-highlight__deprecate)
+ );
}
}
}
@@ -814,24 +921,25 @@ represents 1 unit of the given distance measurement. */
border-radius: var(--map-border-radius-small);
.layer-item__settings-toggle {
- font-size: .85rem;
+ font-size: 0.85rem;
width: 1rem;
height: 1rem;
display: flex;
align-items: center;
justify-content: center;
- margin: .5rem;
+ margin: 0.5rem;
color: var(--map-col-utility-buttons, currentColor);
}
.layer-item__legend-container {
/* By default, don't show legend. */
display: none;
- margin-left: -.5rem;
+ margin-left: -0.5rem;
}
/* Use the same style on button hover and when selected. */
- &.layer-item--selected, &:hover {
+ &.layer-item--selected,
+ &:hover {
display: flex;
background-color: var(--map-col-content-bkg-highlight, #15324e);
border: 1px solid var(--map-col-content-bkg-highlight, #15324e);
@@ -858,7 +966,7 @@ represents 1 unit of the given distance measurement. */
background-color: var(--map-col-search-match-highlight);
}
-.layer-item__icon>svg {
+.layer-item__icon > svg {
height: 100%;
width: auto;
}
@@ -877,7 +985,8 @@ represents 1 unit of the given distance measurement. */
.layer-category-list {
.layer-category-item {
- box-shadow: 0 1px var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate)) inset;
+ box-shadow: 0 1px
+ var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate)) inset;
.layer-category-item__metadata {
display: grid;
@@ -889,7 +998,7 @@ represents 1 unit of the given distance measurement. */
&:hover {
background-color: var(--map-col-content-bkg-highlight, #15324e);
- transition: background-color .3s ease-in-out;
+ transition: background-color 0.3s ease-in-out;
}
}
}
@@ -912,7 +1021,7 @@ represents 1 unit of the given distance measurement. */
justify-content: center;
}
-.layer-category-item__icon>svg {
+.layer-category-item__icon > svg {
height: 1rem;
}
@@ -925,14 +1034,13 @@ represents 1 unit of the given distance measurement. */
.layer-category-item__layers {
display: none;
- animation: fadeIn .3s ease-in-out;
+ animation: fadeIn 0.3s ease-in-out;
}
.layer-category-item__layers.open {
display: block;
}
-
/*****************************************************************************************
*
* Layer Details
@@ -945,7 +1053,10 @@ represents 1 unit of the given distance measurement. */
width: var(--map-width-toolbar);
border-top-right-radius: var(--map-border-radius-big);
border-top-left-radius: var(--map-border-radius-big);
- background-color: var(--map-col-content-bkg-highlight, var(--map-col-bkg-lighter__deprecate));
+ background-color: var(
+ --map-col-content-bkg-highlight,
+ var(--map-col-bkg-lighter__deprecate)
+ );
color: var(--map-col-text-body, var(--map-col-text__deprecate));
box-shadow: var(--map-shadow-md);
grid-template-columns: auto min-content;
@@ -955,7 +1066,7 @@ represents 1 unit of the given distance measurement. */
/* Don't show the details panel unless it also has the layer-details--open class */
display: none;
border: 1px solid var(--map-col-border);
- animation: fadeIn .3s ease-in-out;
+ animation: fadeIn 0.3s ease-in-out;
}
@media only screen and (min-width: 31.5rem) {
@@ -970,7 +1081,10 @@ represents 1 unit of the given distance measurement. */
.layer-details__label {
/* the important is needed to overwrite more specific styles set on portal title tags */
- color: var(--map-col-text-highlight, var(--map-col-text__deprecate)) !important;
+ color: var(
+ --map-col-text-highlight,
+ var(--map-col-text__deprecate)
+ ) !important;
margin: 0;
font-size: 1.33rem;
font-weight: 700;
@@ -989,9 +1103,12 @@ represents 1 unit of the given distance measurement. */
/* The notification div holds a badge & message, if one is set */
.layer-details__notification {
- margin: .5rem 0 1rem;
+ margin: 0.5rem 0 1rem;
padding: 0.75rem;
- background-color: var(--map-col-content-bkg, var(--map-col-buttons__deprecate));
+ background-color: var(
+ --map-col-content-bkg,
+ var(--map-col-buttons__deprecate)
+ );
border-radius: var(--map-border-radius-small);
border: 1px solid var(--map-col-border);
}
@@ -1052,7 +1169,7 @@ represents 1 unit of the given distance measurement. */
/* Do not display this contents of this detail section unless the open class is added */
display: none;
align-items: center;
- margin: .75rem 0;
+ margin: 0.75rem 0;
}
.layer-detail--open .layer-detail__content {
@@ -1111,8 +1228,8 @@ represents 1 unit of the given distance measurement. */
border: 1px solid var(--map-col-border);
border-radius: var(--map-border-radius-small);
font-size: 1rem;
- margin-right: .5rem;
- padding: .5rem 1rem;
+ margin-right: 0.5rem;
+ padding: 0.5rem 1rem;
}
.layer-opacity {
@@ -1127,7 +1244,10 @@ represents 1 unit of the given distance measurement. */
border-radius: var(--map-border-radius-big);
background: var(--c-neutral-3);
position: relative;
- background-color: var(--map-col-opacity-slider-inactive, var(--map-col-buttons__deprecate));
+ background-color: var(
+ --map-col-opacity-slider-inactive,
+ var(--map-col-buttons__deprecate)
+ );
}
/* The shaded part of the slider that stretches from 0 to the current opacity */
@@ -1160,7 +1280,7 @@ other class: .ui-slider-range */
align-items: center;
justify-content: center;
border: none;
- background-color: var(--map-col-buttons-bkg-highlight, #EFEFEF);
+ background-color: var(--map-col-buttons-bkg-highlight, #efefef);
}
/* The element that displays the current opacity as a percentage */
@@ -1189,12 +1309,15 @@ other class: .ui-slider-range */
padding: var(--map-padding-feature-info);
row-gap: 0.8rem;
border-radius: var(--map-border-radius-big);
- background-color: var(--map-col-content-bkg-highlight, var(--map-col-bkg-lighter__deprecate));
+ background-color: var(
+ --map-col-content-bkg-highlight,
+ var(--map-col-bkg-lighter__deprecate)
+ );
color: var(--map-col-text-body, var(--map-col-text__deprecate));
box-shadow: var(--map-shadow-md);
/* Don't show the details panel unless it also has the feature-info--open class */
display: none;
- animation: fadeIn .3s ease-in-out;
+ animation: fadeIn 0.3s ease-in-out;
}
@media only screen and (min-width: 38.5rem) {
@@ -1208,7 +1331,8 @@ other class: .ui-slider-range */
}
.feature-info__label {
- border-bottom: 1px solid var(--map-col-border, var(--map-col-bkg-lightest__deprecate));
+ border-bottom: 1px solid
+ var(--map-col-border, var(--map-col-bkg-lightest__deprecate));
color: var(--map-col-text-title, var(--map-col-text__deprecate)) !important;
font-weight: 600;
font-size: 1.33rem;
@@ -1221,7 +1345,7 @@ other class: .ui-slider-range */
.feature-info__content {
overflow-y: scroll;
width: 100%;
- transition: height .5s ease-in-out;
+ transition: height 0.5s ease-in-out;
border-width: 0;
}
@@ -1244,7 +1368,10 @@ other class: .ui-slider-range */
/* styles for the (default) table template */
.feature-info__table {
- background-color: var(--map-col-content-bkg-muted, var(--map-col-bkg-lightest__deprecate));
+ background-color: var(
+ --map-col-content-bkg-muted,
+ var(--map-col-bkg-lightest__deprecate)
+ );
border-radius: var(--map-border-radius-big);
box-shadow: var(--map-shadow-md);
width: 100%;
@@ -1255,12 +1382,15 @@ other class: .ui-slider-range */
.feature-info__table-row {
padding-bottom: 0.6rem;
- border: 1px solid #FFFFFF0D;
+ border: 1px solid #ffffff0d;
border-top-right-radius: var(--map-border-radius-big);
}
.feature-info__table-row:nth-child(even) {
- background-color: var(--map-col-content-bkg, var(--map-col-bkg-lighter__deprecate));
+ background-color: var(
+ --map-col-content-bkg,
+ var(--map-col-bkg-lighter__deprecate)
+ );
}
.feature-info__table-cell {
@@ -1287,7 +1417,7 @@ other class: .ui-slider-range */
font-size: 0.75em;
font-weight: 500;
display: block;
- margin: 0 0 .5rem;
+ margin: 0 0 0.5rem;
padding: 0;
color: var(--map-col-text-muted);
}
@@ -1304,15 +1434,14 @@ other class: .ui-slider-range */
text-decoration: none;
color: var(--map-col-buttons-text, var(--portal-col-highlight__deprecate));
display: inline-block;
- padding: .5rem 1rem;
+ padding: 0.5rem 1rem;
svg {
- margin-left: .5rem;
+ margin-left: 0.5rem;
vertical-align: middle;
}
}
-
/*****************************************************************************************
*
* Map Legend
@@ -1337,7 +1466,7 @@ other class: .ui-slider-range */
/* allow images previews to bleed into the padding a little, so that more detail is visible */
height: 100%;
max-width: 100%;
- margin-top: calc(-0.5* var(--img-overflow));
+ margin-top: calc(-0.5 * var(--img-overflow));
/* imagery appears lighter on the map */
filter: var(--map-no-brightness-or-opacity-tweaks, brightness(1.75));
}
@@ -1377,10 +1506,13 @@ other class: .ui-slider-range */
}
.nav-help .map-view__button {
- background-color: var(--map-col-content-bkg-highlight, var(--map-col-bkg-lighter__deprecate));
+ background-color: var(
+ --map-col-content-bkg-highlight,
+ var(--map-col-bkg-lighter__deprecate)
+ );
border-radius: var(--map-border-radius-small);
color: var(--map-col-text-label);
- margin-right: .5rem;
+ margin-right: 0.5rem;
padding: 0.25rem 1rem;
}
@@ -1395,12 +1527,13 @@ other class: .ui-slider-range */
background: var(--map-col-buttons-bkg-hover, #505561);
border-radius: var(--map-border-radius-big);
height: 3rem;
- padding: .5rem;
+ padding: 0.5rem;
width: 3rem;
}
.nav-help__instructions {
- border-top: 1px solid var(--map-col-section-divider, var(--map-col-bkg-lightest__deprecate));
+ border-top: 1px solid
+ var(--map-col-section-divider, var(--map-col-bkg-lightest__deprecate));
display: grid;
margin: 1rem 0;
padding: 0;
@@ -1420,12 +1553,13 @@ other class: .ui-slider-range */
}
.nav-help__instruction {
- border-bottom: 1px solid var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate));
+ border-bottom: 1px solid
+ var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate));
display: grid;
grid-template-columns: 3rem auto;
gap: 1rem;
align-items: center;
- padding: .5rem 0;
+ padding: 0.5rem 0;
}
.map-help-panel__section:not(:first-child) {
@@ -1439,7 +1573,8 @@ other class: .ui-slider-range */
width: 100%;
.viewfinder__search {
- border-bottom: 1px solid var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate));
+ border-bottom: 1px solid
+ var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate));
}
.viewfinder__zoom-presets {
@@ -1454,8 +1589,8 @@ other class: .ui-slider-range */
.viewfinder-predictions {
list-style: none;
- margin: -.5rem 1.5rem 0 1.5rem;
- min-height: .5rem;
+ margin: -0.5rem 1.5rem 0 1.5rem;
+ min-height: 0.5rem;
.viewfinder-prediction__content {
align-items: center;
@@ -1466,13 +1601,13 @@ other class: .ui-slider-range */
color: var(--map-col-text-highlight, white);
cursor: pointer;
display: flex;
- gap: .5rem;
+ gap: 0.5rem;
height: 3rem;
- margin: 0 0 .5rem;
- padding: .5rem 1rem;
- transition: background-color .3s ease-in-out;
+ margin: 0 0 0.5rem;
+ padding: 0.5rem 1rem;
+ transition: background-color 0.3s ease-in-out;
- >* {
+ > * {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -1489,7 +1624,10 @@ other class: .ui-slider-range */
&:hover,
&.viewfinder-prediction__focused {
border-color: var(--map-col-border-highlight);
- background-color: var(--map-col-content-bkg-highlight, var(--map-col-bkg-lighter__deprecate));
+ background-color: var(
+ --map-col-content-bkg-highlight,
+ var(--map-col-bkg-lighter__deprecate)
+ );
}
}
}
@@ -1497,23 +1635,30 @@ other class: .ui-slider-range */
.viewfinder-zoom-presets {
display: flex;
flex-direction: column;
- padding: .5rem 1.5rem 1rem 1.5rem;
- row-gap: .5rem;
+ padding: 0.5rem 1.5rem 1rem 1.5rem;
+ row-gap: 0.5rem;
.viewfinder-zoom-preset__preset {
border-radius: var(--map-border-radius-big);
- border: 1px solid var(--map-col-border-panel, var(--map-col-bkg-lightest__deprecate));
+ border: 1px solid
+ var(--map-col-border-panel, var(--map-col-bkg-lightest__deprecate));
cursor: pointer;
display: flex;
flex-direction: column;
padding: 1rem 1.5rem 1rem 2.5rem;
- row-gap: .5rem;
+ row-gap: 0.5rem;
&:hover {
- background-color: var(--map-col-content-bkg-highlight, var(--map-col-bkg-lighter__deprecate));
+ background-color: var(
+ --map-col-content-bkg-highlight,
+ var(--map-col-bkg-lighter__deprecate)
+ );
.viewfinder-zoom-preset__layer {
- background-color: var(--map-col-content-bkg-hover-highlight, var(--map-col-bkg__deprecate));
+ background-color: var(
+ --map-col-content-bkg-hover-highlight,
+ var(--map-col-bkg__deprecate)
+ );
.viewfinder-zoom-preset__layer-content {
color: var(--map-col-text-hover-highlight);
@@ -1532,43 +1677,47 @@ other class: .ui-slider-range */
.viewfinder-zoom-preset__description {
color: var(--map-col-text-label, white);
- font-size: .85rem;
- margin-bottom: .5rem;
+ font-size: 0.85rem;
+ margin-bottom: 0.5rem;
}
.viewfinder-zoom-preset__layers {
- column-gap: .5rem;
+ column-gap: 0.5rem;
display: flex;
flex-wrap: wrap;
- row-gap: .5rem;
+ row-gap: 0.5rem;
}
.viewfinder-zoom-preset__layer {
align-items: center;
- background: var(--map-col-content-bkg-transparent, var(--map-col-bkg-lighter__deprecate));
+ background: var(
+ --map-col-content-bkg-transparent,
+ var(--map-col-bkg-lighter__deprecate)
+ );
border-radius: var(--map-border-radius-big);
display: flex;
height: 1.5rem;
justify-content: center;
- padding: 0 1rem 0 .5rem;
+ padding: 0 1rem 0 0.5rem;
.viewfinder-zoom-preset__layer-content {
overflow: hidden;
text-overflow: ellipsis;
text-wrap: nowrap;
color: var(--map-col-text-muted, white);
- font-size: .8rem;
+ font-size: 0.8rem;
max-width: 10rem;
-
+
i {
- padding: 0 .25rem;
+ padding: 0 0.25rem;
color: var(--map-col-buttons-icon, white);
}
}
}
}
- .viewfinder-zoom-preset.viewfinder-zoom-preset--active .viewfinder-zoom-preset__preset {
+ .viewfinder-zoom-preset.viewfinder-zoom-preset--active
+ .viewfinder-zoom-preset__preset {
background-color: var(--map-col-buttons-bkg-highlight, #15324e);
.viewfinder-zoom-preset__title,
@@ -1577,7 +1726,10 @@ other class: .ui-slider-range */
}
.viewfinder-zoom-preset__layer {
- background: var(--map-col-content-bkg-active-highlight, var(--map-col-bkg__deprecate));
+ background: var(
+ --map-col-content-bkg-active-highlight,
+ var(--map-col-bkg__deprecate)
+ );
.viewfinder-zoom-preset__layer-content {
color: var(--map-col-text-active-highlight, white);
@@ -1592,8 +1744,10 @@ other class: .ui-slider-range */
/** START - Expansion panel View. */
.expansion-panel {
- box-shadow: 0 1px var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate)) inset;
- border-bottom: 1px solid var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate));
+ box-shadow: 0 1px
+ var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate)) inset;
+ border-bottom: 1px solid
+ var(--map-col-item-divider, var(--map-col-bkg-lightest__deprecate));
font-size: 1rem;
.expansion-panel__toggle {
@@ -1607,7 +1761,7 @@ other class: .ui-slider-range */
&:hover {
background-color: var(--map-col-content-bkg-highlight, #15324e);
- transition: background-color .3s ease-in-out;
+ transition: background-color 0.3s ease-in-out;
}
}
@@ -1663,7 +1817,7 @@ other class: .ui-slider-range */
.expansion-panel__content {
display: none;
- animation: fadeIn .3s ease-in-out;
+ animation: fadeIn 0.3s ease-in-out;
}
}
/** END - Expansion panel View. */
diff --git a/src/css/metacatui-common.css b/src/css/metacatui-common.css
index f563d1f65..8a4a02935 100644
--- a/src/css/metacatui-common.css
+++ b/src/css/metacatui-common.css
@@ -1,119 +1,119 @@
-:root{
- --pad: 20px;
+:root {
+ --pad: 20px;
- --loading-background-color: #f1f1f19e;
- --loading-background-color-darker: #e6e4e49e;
+ --loading-background-color: #f1f1f19e;
+ --loading-background-color-darker: #e6e4e49e;
}
/******************************************
** General Styling ***
******************************************/
-a:hover{
- cursor: pointer;
+a:hover {
+ cursor: pointer;
}
-.center{
- text-align: center;
+.center {
+ text-align: center;
}
-.subtle{
- color: #999;
+.subtle {
+ color: #999;
}
-.btn-disabled{
- pointer-events: none;
- opacity: .8;
+.btn-disabled {
+ pointer-events: none;
+ opacity: 0.8;
}
.reset-btn-styles {
- background: none;
- color: inherit;
- border: none;
- padding: 0;
- font: inherit;
- cursor: pointer;
- outline: inherit;
+ background: none;
+ color: inherit;
+ border: none;
+ padding: 0;
+ font: inherit;
+ cursor: pointer;
+ outline: inherit;
}
-.footnote{
- font-size: .7em;
+.footnote {
+ font-size: 0.7em;
}
-.underline{
- text-decoration: underline;
+.underline {
+ text-decoration: underline;
}
-.alert .footnote{
- font-size: .9em;
- margin-top: 10px;
+.alert .footnote {
+ font-size: 0.9em;
+ margin-top: 10px;
}
-.badge{
- margin-right: 10px;
- text-shadow: none;
- font-weight: normal;
+.badge {
+ margin-right: 10px;
+ text-shadow: none;
+ font-weight: normal;
}
.danger,
-.error{
- color: #dc0000;
+.error {
+ color: #dc0000;
}
-.tooltip-error .tooltip-inner{
- background-color: #960303;
+.tooltip-error .tooltip-inner {
+ background-color: #960303;
}
-.tooltip-error .tooltip .tooltip-arrow{
- border-top-color: #960303;
+.tooltip-error .tooltip .tooltip-arrow {
+ border-top-color: #960303;
}
-.strong{
- font-weight: bold;
+.strong {
+ font-weight: bold;
}
-.emphasis{
- font-style: italic;
+.emphasis {
+ font-style: italic;
}
-.large{
- font-size: 1.5em;
- padding: 1em;
+.large {
+ font-size: 1.5em;
+ padding: 1em;
}
-.large .btn{
- font-size: 1em;
- padding: .5em;
+.large .btn {
+ font-size: 1em;
+ padding: 0.5em;
}
-.success{
- color: green;
+.success {
+ color: green;
}
-.icon.warning{
- color: #ffbc00;
+.icon.warning {
+ color: #ffbc00;
}
.list-group-item.success {
- background-color: #dff0d8;
+ background-color: #dff0d8;
}
-.list-group-item.warning{
- background-color: #fcf8e3;
- color: #c09853;
+.list-group-item.warning {
+ background-color: #fcf8e3;
+ color: #c09853;
}
.list-group-item.danger {
- background-color: #f2dede;
+ background-color: #f2dede;
}
-.list-group-item.info{
- background-color: #d9edf7;
- color: #3a87ad;
+.list-group-item.info {
+ background-color: #d9edf7;
+ color: #3a87ad;
}
-.tooltip{
- font-size: 12px;
- pointer-events: none;
- z-index: 9999;
+.tooltip {
+ font-size: 12px;
+ pointer-events: none;
+ z-index: 9999;
}
-[contenteditable]{
- cursor: text;
+[contenteditable] {
+ cursor: text;
}
-.row-striped{
- border: 1px solid #e5e5e5;
- margin-bottom: 40px;
- border-radius: 4px;
+.row-striped {
+ border: 1px solid #e5e5e5;
+ margin-bottom: 40px;
+ border-radius: 4px;
}
-.row-striped > .row-fluid{
- padding: 20px;
+.row-striped > .row-fluid {
+ padding: 20px;
}
-.row-striped > .row-fluid:nth-child(even){
- background-color: #EFEFEF;
+.row-striped > .row-fluid:nth-child(even) {
+ background-color: #efefef;
}
-td.center{
+td.center {
text-align: center;
}
/******************************************
** General Structure ***
******************************************/
-html{
+html {
height: 100%;
width: 100%;
margin: 0;
@@ -128,125 +128,125 @@ body {
}
article > .container,
article.container {
- padding: 2%;
- width: 96%;
+ padding: 2%;
+ width: 96%;
}
-#Content{
+#Content {
padding: 2.5rem;
min-height: 200px;
}
-.DataCatalog #Content{
- box-sizing: border-box;
+.DataCatalog #Content {
+ box-sizing: border-box;
}
-.mapMode #Content{
- width: 100%;
+.mapMode #Content {
+ width: 100%;
margin: 0px;
- padding: 0px;
+ padding: 0px;
}
-footer{
- background-color: #FFFFFF;
- height: 3em; /** Keeps the footer down **/
- position: absolute; /** Keeps the footer down **/
- bottom: 0px; /** Keeps the footer down **/
+footer {
+ background-color: #ffffff;
+ height: 3em; /** Keeps the footer down **/
+ position: absolute; /** Keeps the footer down **/
+ bottom: 0px; /** Keeps the footer down **/
}
-.mapMode footer{
- position: relative;
- height: auto;
+.mapMode footer {
+ position: relative;
+ height: auto;
}
-.hide-in-map-mode{
- display: block;
+.hide-in-map-mode {
+ display: block;
}
-.show-in-map-mode{
- display: none;
+.show-in-map-mode {
+ display: none;
}
-.mapMode .show-in-map-mode{
- display: block;
+.mapMode .show-in-map-mode {
+ display: block;
}
-.mapMode .hide-in-map-mode{
- display: none;
+.mapMode .hide-in-map-mode {
+ display: none;
}
-.annotator-wrapper{
- /** Keeps the footer down **/
- min-height: 100%;
+.annotator-wrapper {
+ /** Keeps the footer down **/
+ min-height: 100%;
}
/******************************************
** Navigation ***
******************************************/
-.breadcrumb{
+.breadcrumb {
width: 100%;
box-sizing: border-box;
- font-size: .9em;
+ font-size: 0.9em;
}
-.breadcrumb>li+li:before {
+.breadcrumb > li + li:before {
padding: 0 5px;
color: #ccc;
content: "/\00a0";
}
-.breadcrumb>li>a{
- font-weight: normal;
+.breadcrumb > li > a {
+ font-weight: normal;
}
-.breadcrumb>li>a.inactive{
- color: #999;
+.breadcrumb > li > a.inactive {
+ color: #999;
}
-.breadcrumb .back{
- margin-right: 20px;
- display: inline;
- border-right: 1px solid #999;
- padding-right: 20px;
+.breadcrumb .back {
+ margin-right: 20px;
+ display: inline;
+ border-right: 1px solid #999;
+ padding-right: 20px;
}
-.nav-tabs li i{
- margin-right: 5px;
+.nav-tabs li i {
+ margin-right: 5px;
}
-.nav > li > form{
- margin-bottom: 0px;
+.nav > li > form {
+ margin-bottom: 0px;
}
.nav .input > form > label,
-.header .input > form > label{
- display: inline-block;
- top: 5px;
- position: relative;
- margin-right: 10px;
+.header .input > form > label {
+ display: inline-block;
+ top: 5px;
+ position: relative;
+ margin-right: 10px;
}
.nav .input > form > input[type="text"],
-.header .input > form > input[type="text"]{
- display: inline-block;
- width: 60px;
+.header .input > form > input[type="text"] {
+ display: inline-block;
+ width: 60px;
}
.nav .input > form > .btn,
.header .input > form > .btn {
- -moz-box-shadow: none;
- -webkit-box-shadow: none;
- box-shadow: none;
- background: none;
- display:inline-block;
- cursor:pointer;
- text-decoration:none;
+ -moz-box-shadow: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ background: none;
+ display: inline-block;
+ cursor: pointer;
+ text-decoration: none;
}
.nav .right .dropdown-menu {
left: -50%;
}
-#nav-trigger{
+#nav-trigger {
color: white;
- float: right;
- margin-top: 15px;
- font-size: 2em;
- display: none;
+ float: right;
+ margin-top: 15px;
+ font-size: 2em;
+ display: none;
}
-#nav-trigger:hover{
+#nav-trigger:hover {
text-decoration: none;
}
#Navbar {
- z-index: 3;
+ z-index: 3;
}
-#Navbar .home{
+#Navbar .home {
display: inline-block;
}
-#Navbar a .title{
- color: #FFF;
+#Navbar a .title {
+ color: #fff;
vertical-align: middle;
}
-#Navbar .logo{
+#Navbar .logo {
max-height: 30px;
margin-right: 10px;
margin-top: 5px;
@@ -254,83 +254,83 @@ footer{
/******************************************
** Titles ***
******************************************/
-h3{
- font-size: 1.5em;
+h3 {
+ font-size: 1.5em;
font-weight: normal;
}
header .citation {
- margin-bottom: 20px;
- display: block;
+ margin-bottom: 20px;
+ display: block;
}
/******************************************
** Buttons ***
******************************************/
-.btn, .btn-primary{
- background-image: none;
+.btn,
+.btn-primary {
+ background-image: none;
}
.inline-buttons {
margin-bottom: 20px;
margin-top: 20px;
clear: both;
}
-.inline-buttons > .btn:not(:last-child){
- margin-right: 10px;
+.inline-buttons > .btn:not(:last-child) {
+ margin-right: 10px;
}
-.inline-buttons > .text-btwn-btns{
- margin-right: 10px;
- margin-left: 10px;
+.inline-buttons > .text-btwn-btns {
+ margin-right: 10px;
+ margin-left: 10px;
}
-.btn-large{
- font-size: 1.2em;
- line-height: 1.5em;
+.btn-large {
+ font-size: 1.2em;
+ line-height: 1.5em;
}
-.btn-large.center{
- margin-bottom: 20px;
- display: block;
- max-width: 30%;
+.btn-large.center {
+ margin-bottom: 20px;
+ display: block;
+ max-width: 30%;
}
-.orcid-signin.btn-large.center{
- max-width: 45%;
- margin-bottom: 0px;
+.orcid-signin.btn-large.center {
+ max-width: 45%;
+ margin-bottom: 0px;
}
-#Content > .login{
- max-width: 300px;
+#Content > .login {
+ max-width: 300px;
margin-top: 50px;
margin-bottom: 20px;
display: grid;
align-self: center;
}
-#Content > .login .btn{
+#Content > .login .btn {
font-size: 1.5em;
}
.login > span {
- top: -.2em;
+ top: -0.2em;
position: relative;
}
-#Content > .login > .alert-container{
- font-size: inherit;
+#Content > .login > .alert-container {
+ font-size: inherit;
}
-
/******************************************
** Download Buttons ***
******************************************/
-.btn.download{
- -webkit-transition: color 500ms;
- transition: color 500ms;
+.btn.download {
+ -webkit-transition: color 500ms;
+ transition: color 500ms;
}
-.btn.download.in-progress{
- font-size: .9em;
+.btn.download.in-progress {
+ font-size: 0.9em;
}
-.btn.download.complete{
- color: rgb(17, 162, 17);
+.btn.download.complete {
+ color: rgb(17, 162, 17);
}
-.btn.download.complete.btn-primary{
- color: white;
+.btn.download.complete.btn-primary {
+ color: white;
}
-.downloading.icon-spin.hidden{
- display: none;
+.downloading.icon-spin.hidden {
+ display: none;
}
.download.btn.error {
border-color: #960303;
@@ -339,119 +339,119 @@ header .citation {
/******************************************
** Notifications and Alerts ***
******************************************/
-.notification.loading .icon{
- font-size: 40px;
- margin-left: 43%;
- margin-left: calc(50% - 23px);
- margin-top: 40px;
+.notification.loading .icon {
+ font-size: 40px;
+ margin-left: 43%;
+ margin-left: calc(50% - 23px);
+ margin-top: 40px;
color: #333;
}
-.notification.loading.page .icon{
- margin-top: 10px;
+.notification.loading.page .icon {
+ margin-top: 10px;
}
.panel-body .notification.loading .icon {
- margin-top: 10px;
+ margin-top: 10px;
}
.accordion-inner .notification.loading .icon {
- margin-left: 0px;
-}
-.notification.loading p{
- font-size: 1em;
- text-align: center;
- width: 100%;
- margin: 20px auto;
- display: block;
+ margin-left: 0px;
+}
+.notification.loading p {
+ font-size: 1em;
+ text-align: center;
+ width: 100%;
+ margin: 20px auto;
+ display: block;
color: #333;
}
-.filter-contain .notification.loading i{
- color: #999;
- font-size: .5em;
+.filter-contain .notification.loading i {
+ color: #999;
+ font-size: 0.5em;
}
-.filter-contain .notification.loading p{
- font-size: .3em;
- line-height: 3em;
- text-align: center;
- width: auto;
- color: #999;
- margin: 0;
+.filter-contain .notification.loading p {
+ font-size: 0.3em;
+ line-height: 3em;
+ text-align: center;
+ width: auto;
+ color: #999;
+ margin: 0;
}
-#results .notification.loading .icon{
- margin-top: 20px;
+#results .notification.loading .icon {
+ margin-top: 20px;
}
-.alert h4{
- margin-top: 10px;
+.alert h4 {
+ margin-top: 10px;
margin-bottom: 20px;
}
-.alert .icon-left{
- float: left;
- margin-right: 10px;
+.alert .icon-left {
+ float: left;
+ margin-right: 10px;
}
-.alert.plain{
- background-color: #FFF;
- border-color: #DDD;
- color: #333;
+.alert.plain {
+ background-color: #fff;
+ border-color: #ddd;
+ color: #333;
}
-.alert .close{
- font-size: 3em;
+.alert .close {
+ font-size: 3em;
position: absolute;
right: 20px;
top: 20px;
}
-.alert-error .close{
- color: #FF0000;
+.alert-error .close {
+ color: #ff0000;
}
.notification.success,
-.icon.success{
- color: #008000;
+.icon.success {
+ color: #008000;
}
-.notification.error{
- color: #a94442;
+.notification.error {
+ color: #a94442;
}
-.notification.warning{
- color: rgb(197, 166, 0);
+.notification.warning {
+ color: rgb(197, 166, 0);
}
.notification.success,
-.notification.error{
- font-weight: bold;
+.notification.error {
+ font-weight: bold;
}
.notification.error .icon,
-.notification.success .icon{
- margin-right: 10px;
+.notification.success .icon {
+ margin-right: 10px;
}
-.input-append .notification{
- font-size: 15px;
+.input-append .notification {
+ font-size: 15px;
}
.notification > .msg {
- width: 90%;
- display: inline;
- white-space: normal;
+ width: 90%;
+ display: inline;
+ white-space: normal;
}
-.form-feedback{
- min-height: 30px;
+.form-feedback {
+ min-height: 30px;
}
#Content > .alert-container {
- max-width: 90%;
- margin-left: 5%;
- margin-top: 2rem;
+ max-width: 90%;
+ margin-left: 5%;
+ margin-top: 2rem;
}
-body > .alert-container.important-app-message{
+body > .alert-container.important-app-message {
bottom: 0px;
top: auto;
width: 100%;
- /* required for IE11: */
- z-index: 1000;
+ /* required for IE11: */
+ z-index: 1000;
}
-body > .alert-container.important-app-message > .alert{
+body > .alert-container.important-app-message > .alert {
margin-bottom: 0px;
display: grid;
}
-.temporary-message{
+.temporary-message {
margin-bottom: 0px;
position: relative;
}
-.alert.subtle{
+.alert.subtle {
background-color: #f9f7f7;
- border-color: #CCC;
+ border-color: #ccc;
color: #999;
border-width: 0px;
}
@@ -459,237 +459,239 @@ body > .alert-container.important-app-message > .alert{
/******************************************
** Icons ***
******************************************/
-.btn > i{
- margin-left: 5px;
+.btn > i {
+ margin-left: 5px;
}
h3 > .icon,
-h4 > .icon{
- margin-right: 10px;
- max-height: 1em;
+h4 > .icon {
+ margin-right: 10px;
+ max-height: 1em;
}
-.provenance-statement-container i{
- font-size: 1.2em;
+.provenance-statement-container i {
+ font-size: 1.2em;
}
-.icon-on-right{
- margin-left: 5px;
+.icon-on-right {
+ margin-left: 5px;
}
-.icon-on-left{
- margin-right: 5px;
+.icon-on-left {
+ margin-right: 5px;
}
-.icon-no-margin{
- margin: 0 !important;
+.icon-no-margin {
+ margin: 0 !important;
}
-.filter-contain img.icon{
- float: left;
- height: 20px;
+.filter-contain img.icon {
+ float: left;
+ height: 20px;
}
-img.icon{
- max-height: 1.4em;
+img.icon {
+ max-height: 1.4em;
}
svg.icon {
- max-height: 19px;
- max-width: 19px;
- fill: #555;
+ max-height: 19px;
+ max-width: 19px;
+ fill: #555;
}
-.icon-positive{
- color: green;
+.icon-positive {
+ color: green;
}
-.icon-negative{
- color: #F15959;
+.icon-negative {
+ color: #f15959;
}
-.icon-warning{
- color: #c09853;
+.icon-warning {
+ color: #c09853;
}
-.icon-circle-badge{
- background-color: #999;
- padding: 6px;
- border-radius: 7px;
- color: #FFF;
+.icon-circle-badge {
+ background-color: #999;
+ padding: 6px;
+ border-radius: 7px;
+ color: #fff;
}
-.icon-text{
- font-family: inherit;
- font-weight: bold;
- font-size: .9em;
+.icon-text {
+ font-family: inherit;
+ font-weight: bold;
+ font-size: 0.9em;
}
-.private.icon-stack .icon-circle{
- color: #FF7600;
+.private.icon-stack .icon-circle {
+ color: #ff7600;
}
-.private.icon-stack .icon-stack-top{
- color: #FFF;
+.private.icon-stack .icon-stack-top {
+ color: #fff;
margin-top: -1px;
font-size: 1.1em;
}
-.icon.hidden{
+.icon.hidden {
display: none;
}
-.dataone-plus-icon{
+.dataone-plus-icon {
height: 1em;
vertical-align: text-top;
}
-.pill{
+.pill {
border-radius: 5px;
-width: auto;
-display: inline;
-padding: .25em .5em;
-text-shadow: none;
+ width: auto;
+ display: inline;
+ padding: 0.25em 0.5em;
+ text-shadow: none;
}
/* Data Tag Icons */
-.data-tag{
+.data-tag {
height: 1.25em;
width: auto;
}
-.data-tag-icon.blue g{
- fill: #3FA9F5;
+.data-tag-icon.blue g {
+ fill: #3fa9f5;
}
-.data-tag-icon.green g{
- fill: #00CE01;
+.data-tag-icon.green g {
+ fill: #00ce01;
}
-.data-tag-icon.yellow g{
+.data-tag-icon.yellow g {
fill: #ead10a;
}
-.data-tag-icon.orange g{
- fill: #F38D08;
+.data-tag-icon.orange g {
+ fill: #f38d08;
}
-.data-tag-icon.red g{
- fill: #FE0300;
+.data-tag-icon.red g {
+ fill: #fe0300;
}
-.data-tag-icon.crimson g{
+.data-tag-icon.crimson g {
fill: #800000;
}
/******************************************
** CatalogSearchView ***
******************************************/
-.catalog-search-view .catalog-search-inner{
- display: grid;
- justify-content: stretch;
- align-items: stretch;
- grid-template-columns: auto 1fr 1fr;
+.catalog-search-view .catalog-search-inner {
+ display: grid;
+ justify-content: stretch;
+ align-items: stretch;
+ grid-template-columns: auto 1fr 1fr;
}
.catalog-search-view .filter-groups-container {
- width: 215px;
- padding: var(--pad)
+ width: 215px;
+ padding: var(--pad);
}
.catalog-search-view .search-results-container,
.catalog-search-view .map-container {
}
-.catalog-search-body.mapMode{
- height: 100vh;
- width: 100vw;
- padding-bottom: 0px;
- display: grid;
- align-items: stretch;
- justify-content: stretch;
- overflow: hidden;
+.catalog-search-body.mapMode {
+ height: 100vh;
+ width: 100vw;
+ padding-bottom: 0px;
+ display: grid;
+ align-items: stretch;
+ justify-content: stretch;
+ overflow: hidden;
}
-.catalog-search-body.mapMode #Content{
- padding: 40px 0px 0px 0px;
+.catalog-search-body.mapMode #Content {
+ padding: 40px 0px 0px 0px;
}
-.catalog-search-body.mapMode .search-results-view .result-row:last-child{
- margin-bottom: 100px;
+.catalog-search-body.mapMode .search-results-view .result-row:last-child {
+ margin-bottom: 100px;
}
.catalog-search-body.mapMode .search-results-view {
- overflow-y: scroll;
- height: 100vh;
- padding-bottom: 200px; /* Leaving room for the last row to show */
- padding-right: 15px; /* Padding for the scrollbar */
+ overflow-y: scroll;
+ height: 100vh;
+ padding-bottom: 200px; /* Leaving room for the last row to show */
+ padding-right: 15px; /* Padding for the scrollbar */
}
-.catalog-search-body.mapMode .search-results-panel-container .map-toggle-container{
- display: none;
+.catalog-search-body.mapMode
+ .search-results-panel-container
+ .map-toggle-container {
+ display: none;
}
-.catalog-search-body.listMode .map-panel-container{
- display: none;
+.catalog-search-body.listMode .map-panel-container {
+ display: none;
}
-.catalog-search-body.listMode .catalog-search-inner{
- grid-template-columns: auto 1fr 0;
+.catalog-search-body.listMode .catalog-search-inner {
+ grid-template-columns: auto 1fr 0;
}
.catalog-search-view .cesium-widget-view {
- width: inherit;
- margin-left: 0;
+ width: inherit;
+ margin-left: 0;
}
-.search-results-view .result-row{
- padding: var(--pad);
+.search-results-view .result-row {
+ padding: var(--pad);
}
-.catalog-search-view .no-search-results{
- padding: var(--pad);
- text-align: center;
+.catalog-search-view .no-search-results {
+ padding: var(--pad);
+ text-align: center;
}
/******************************************
** Results and Result Rows ***
******************************************/
-.result-row{
- box-sizing: border-box;
- padding: 10px 20px;
- width: auto;
- border-top: 1px solid #EEE;
-}
-.result-row-loading .citation-loading{
- width: 100%;
- height: 3em;
- background-color: var(--loading-background-color-darker);
- border-radius: 6px;
-}
-.result-row-loading .circles-loading{
- display: flex;
- justify-items: left;
- column-gap: 1em;
-}
-.result-row-loading .circle-loading{
- background-color: var(--loading-background-color-darker);
- height: 1em;
- width: 1em;
- margin-top: var(--pad);
-}
-.result-row .info-icons .icons{
- float: left;
- margin-right: 1em;
- position: relative;
-}
-.result-row .info-icons .icon{
- font-size: 1.1em;
-}
-.result-row .info-icons .icon.inactive{
- visibility: hidden;
-}
-.result-row .info-icons .icons.resource-map{
- width: 20px;
- height: 1em;
-}
-.result-row .info-icons .icons.download .icon{
- font-size: 1.2em;
-}
-.result-row .info-icons .icons.download .tooltip{
- width: 120px;
+.result-row {
+ box-sizing: border-box;
+ padding: 10px 20px;
+ width: auto;
+ border-top: 1px solid #eee;
+}
+.result-row-loading .citation-loading {
+ width: 100%;
+ height: 3em;
+ background-color: var(--loading-background-color-darker);
+ border-radius: 6px;
+}
+.result-row-loading .circles-loading {
+ display: flex;
+ justify-items: left;
+ column-gap: 1em;
+}
+.result-row-loading .circle-loading {
+ background-color: var(--loading-background-color-darker);
+ height: 1em;
+ width: 1em;
+ margin-top: var(--pad);
+}
+.result-row .info-icons .icons {
+ float: left;
+ margin-right: 1em;
+ position: relative;
+}
+.result-row .info-icons .icon {
+ font-size: 1.1em;
+}
+.result-row .info-icons .icon.inactive {
+ visibility: hidden;
+}
+.result-row .info-icons .icons.resource-map {
+ width: 20px;
+ height: 1em;
+}
+.result-row .info-icons .icons.download .icon {
+ font-size: 1.2em;
+}
+.result-row .info-icons .icons.download .tooltip {
+ width: 120px;
}
.info-icons .download.disabled-download {
color: inherit;
cursor: default;
}
-.result-row .info-icons .icon.views{
+.result-row .info-icons .icon.views {
margin-left: 5px;
color: inherit;
}
-.result-row .icons.provenance i{
- font-size: 1.5em;
+.result-row .icons.provenance i {
+ font-size: 1.5em;
}
-.result-row .info-icons img.icon{
- height: 1em;
+.result-row .info-icons img.icon {
+ height: 1em;
}
-.info-icons .icon.private{
- color: #FF7600;
+.info-icons .icon.private {
+ color: #ff7600;
}
-.result-row .info-icons .label{
- background-color: #5E8497;
+.result-row .info-icons .label {
+ background-color: #5e8497;
}
-.search-results.loading .notification{
+.search-results.loading .notification {
margin-top: 20px;
min-height: 700px;
}
.search-results #no-results-found {
- margin: 20px;
+ margin: 20px;
}
-.result-row .route-to-metadata{
- cursor: pointer;
+.result-row .route-to-metadata {
+ cursor: pointer;
}
.result-row .logo {
width: 50px;
@@ -701,638 +703,640 @@ text-shadow: none;
.result-row > .logo.ONEShare,
.result-row > .logo.CLOEBIRD,
.result-row > .logo.EDACGSTORE,
-.result-row > .logo.GLEON{
- width: 90px;
+.result-row > .logo.GLEON {
+ width: 90px;
}
-.result-row > .logo.KNB{
- width: 30px;
+.result-row > .logo.KNB {
+ width: 30px;
}
.result-row > .logo.IARC,
-.result-row > .logo.LTER{
- width: 40px;
+.result-row > .logo.LTER {
+ width: 40px;
}
-.result-row .tooltip{
- z-index: 99999;
- min-width: 100px;
+.result-row .tooltip {
+ z-index: 99999;
+ min-width: 100px;
}
.result-row .info-icons .label {
- background-color: #FFF;
+ background-color: #fff;
color: inherit;
text-shadow: none;
padding: 1px 5px;
- border: 1px solid #CCC;
+ border: 1px solid #ccc;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
margin-top: -2px;
}
-.result-row .tooltip-inner{
- font-size: 1.2em;
+.result-row .tooltip-inner {
+ font-size: 1.2em;
}
-.result-row .download-icon-placeholder{
- width: 16px;
- display: inline-block;
+.result-row .download-icon-placeholder {
+ width: 16px;
+ display: inline-block;
}
-.result-row.collection .info-icons{
+.result-row.collection .info-icons {
display: none;
}
-.result-row.collection .logo{
+.result-row.collection .logo {
width: auto;
max-width: 100px;
}
/* Don't italicize the title in the citation in search results */
.result-row .title i {
- font-style: normal;
+ font-style: normal;
}
/******************************************
** Provenance flow-charts ***
******************************************/
-.prov-chart{
- width: 60px;
- padding-left: 20px;
- padding-right: 20px;
- margin-top: 67px;
- display: inline-block;
- vertical-align: top;
- position: relative;
-}
-.prov-chart.sources{
- margin-right: 15px;
- padding-right: 10px;
- border-right-width: 1px;
- border-right-style: solid;
-}
-.prov-chart.derivations{
- margin-left: 17px;
- padding-left: 16px;
- border-left-width: 1px;
- border-left-style: solid;
-}
-.sources-programs{
- padding-left: 0px;
- padding-right: 0px;
- margin-left: 77px;
-}
-.derivations-programs{
- padding-left: 0px;
- padding-right: 0px;
- margin-left: -84px;
-}
-.prov-chart.derivations.has-programs{
- margin-left: 17px;
+.prov-chart {
+ width: 60px;
+ padding-left: 20px;
+ padding-right: 20px;
+ margin-top: 67px;
+ display: inline-block;
+ vertical-align: top;
+ position: relative;
+}
+.prov-chart.sources {
+ margin-right: 15px;
+ padding-right: 10px;
+ border-right-width: 1px;
+ border-right-style: solid;
+}
+.prov-chart.derivations {
+ margin-left: 17px;
+ padding-left: 16px;
+ border-left-width: 1px;
+ border-left-style: solid;
+}
+.sources-programs {
+ padding-left: 0px;
+ padding-right: 0px;
+ margin-left: 77px;
+}
+.derivations-programs {
+ padding-left: 0px;
+ padding-right: 0px;
+ margin-left: -84px;
+}
+.prov-chart.derivations.has-programs {
+ margin-left: 17px;
}
.prov-chart.sources .programs .connecter {
- margin-left: 15px;
- width: 10px;
+ margin-left: 15px;
+ width: 10px;
}
.prov-chart.derivations .programs .connecter {
- left: -76px;
- width: 9px;
+ left: -76px;
+ width: 9px;
}
-.prov-chart .node.program{
- fill: #FFFFFF;
+.prov-chart .node.program {
+ fill: #ffffff;
}
-.prov-chart polygon.program.editor.node{
- stroke: grey;
- stroke-width: 1px;
- stroke-dasharray: 2px,2px;
+.prov-chart polygon.program.editor.node {
+ stroke: grey;
+ stroke-width: 1px;
+ stroke-dasharray: 2px, 2px;
}
-.prov-chart .programs svg{
- position: absolute;
+.prov-chart .programs svg {
+ position: absolute;
}
-.prov-chart .node{
- border-width: 1px;
- border-style: solid;
- border-radius: 50%;
- -moz-border-radius: 50%;
- -webkit-border-radius: 50%;
- background-color: #FFF;
- width: 25px;
- height: 25px;
- padding: 15px;
- font-size: 25px;
- margin-bottom: 10px;
- color: #666;
- position: absolute;
- cursor: pointer;
+.prov-chart .node {
+ border-width: 1px;
+ border-style: solid;
+ border-radius: 50%;
+ -moz-border-radius: 50%;
+ -webkit-border-radius: 50%;
+ background-color: #fff;
+ width: 25px;
+ height: 25px;
+ padding: 15px;
+ font-size: 25px;
+ margin-bottom: 10px;
+ color: #666;
+ position: absolute;
+ cursor: pointer;
}
-.prov-chart .node:focus{
- outline: none;
+.prov-chart .node:focus {
+ outline: none;
}
.prov-chart .node.active,
#article-container.active,
-.entitydetails.active{
- box-shadow: 0 0 5px rgb(50, 161, 19),0 0 12px rgb(0, 255, 213);
- -webkit-box-shadow: 0 0 5px rgb(50, 161, 19),0 0 12px rgb(0, 255, 213);
+.entitydetails.active {
+ box-shadow:
+ 0 0 5px rgb(50, 161, 19),
+ 0 0 12px rgb(0, 255, 213);
+ -webkit-box-shadow:
+ 0 0 5px rgb(50, 161, 19),
+ 0 0 12px rgb(0, 255, 213);
}
-.prov-chart .node.previous{
- border-width: 1px;
+.prov-chart .node.previous {
+ border-width: 1px;
}
.prov-chart .node .icon.remove,
-.prov-chart .programs .icon.remove{
- color: #960303;
- fill: #960303;
- position:absolute;
- display: block;
- font-size: 20px;
+.prov-chart .programs .icon.remove {
+ color: #960303;
+ fill: #960303;
+ position: absolute;
+ display: block;
+ font-size: 20px;
}
.prov-chart .node .icon.remove:hover,
-.prov-chart .programs .icon.remove:hover{
- color: #FF0000;
- fill: #FF0000;
+.prov-chart .programs .icon.remove:hover {
+ color: #ff0000;
+ fill: #ff0000;
}
-.prov-chart .node .data.icon.remove{
- top: 0px;
- right: -5px;
+.prov-chart .node .data.icon.remove {
+ top: 0px;
+ right: -5px;
}
-.prov-chart .connecter{
- border-top-width: 1px;
- border-top-style: solid;
- position: absolute;
- top: 0px;
- height: 10px;
- width: 15px;
- left: 0px;
+.prov-chart .connecter {
+ border-top-width: 1px;
+ border-top-style: solid;
+ position: absolute;
+ top: 0px;
+ height: 10px;
+ width: 15px;
+ left: 0px;
}
-.prov-chart.expand-collapse .collapsed{
- display: none;
+.prov-chart.expand-collapse .collapsed {
+ display: none;
}
.prov-chart.expand-collapse .expand-control,
-.prov-chart.expand-collapse .collapse-control{
- font-size: .9em;
- position: absolute;
- top: 100%;
- top: calc(100% + 30px);
- left: 10px;
- width: 90px;
- cursor: pointer;
-}
-.prov-chart.expand-collapse .control .icon{
- margin-left: 5px;
- font-size: 1.5em;
- vertical-align: sub;
-}
-.prov-chart.derivations .connecter{
- width: 17px;
- left: 0px;
- top: 50%;
-}
-.prov-chart.sources .connecter{
- left: 45px; /* fallback */
- left: calc(100% - 15px);
-}
-.prov-chart .prov-pointer{
- width: 15px;
- height: 15px;
- position: absolute;
- top: 50%; /* fallback */
- top: calc(50% - 7px);
+.prov-chart.expand-collapse .collapse-control {
+ font-size: 0.9em;
+ position: absolute;
+ top: 100%;
+ top: calc(100% + 30px);
+ left: 10px;
+ width: 90px;
+ cursor: pointer;
+}
+.prov-chart.expand-collapse .control .icon {
+ margin-left: 5px;
+ font-size: 1.5em;
+ vertical-align: sub;
}
-.prov-chart.sources .prov-pointer{
- right: -15px;
+.prov-chart.derivations .connecter {
+ width: 17px;
+ left: 0px;
+ top: 50%;
}
-.prov-chart.derivations .prov-pointer{
- left: -17px;
+.prov-chart.sources .connecter {
+ left: 45px; /* fallback */
+ left: calc(100% - 15px);
}
-.prov-chart.derivations.one-derivation .connecter{
- margin-left: -17px;
+.prov-chart .prov-pointer {
+ width: 15px;
+ height: 15px;
+ position: absolute;
+ top: 50%; /* fallback */
+ top: calc(50% - 7px);
}
-.prov-chart.derivations.one-derivation .prov-pointer{
- left: 0px;
+.prov-chart.sources .prov-pointer {
+ right: -15px;
}
-.prov-chart > .title{
- font-size: .9em;
- font-weight: normal;
- width: 90px;
- line-height: 1em;
- top: -70px;
- position: absolute;
+.prov-chart.derivations .prov-pointer {
+ left: -17px;
+}
+.prov-chart.derivations.one-derivation .connecter {
+ margin-left: -17px;
+}
+.prov-chart.derivations.one-derivation .prov-pointer {
+ left: 0px;
+}
+.prov-chart > .title {
+ font-size: 0.9em;
+ font-weight: normal;
+ width: 90px;
+ line-height: 1em;
+ top: -70px;
+ position: absolute;
}
-.prov-chart.derivations > .title{
- left: 0px;
- text-align: center;
+.prov-chart.derivations > .title {
+ left: 0px;
+ text-align: center;
}
-.prov-chart .node > i{
- vertical-align: text-top;
+.prov-chart .node > i {
+ vertical-align: text-top;
}
-.prov-chart .metadata.node i{
- margin-left: 5px;
+.prov-chart .metadata.node i {
+ margin-left: 5px;
}
-.provenance-statement a{
- /*Keep the icons and links right by each other */
- display: inline;
- word-break: break-word;
+.provenance-statement a {
+ /*Keep the icons and links right by each other */
+ display: inline;
+ word-break: break-word;
}
-.provenance-statement .currently-viewing{
- font-weight: bold;
+.provenance-statement .currently-viewing {
+ font-weight: bold;
}
-.prov-chart .image.node{
- background-size: cover;
- background-position: 50% 50%;
+.prov-chart .image.node {
+ background-size: cover;
+ background-position: 50% 50%;
}
-.prov-chart .node.editor{
- border-color: #999999;
- border-style: dashed;
- text-align: center;
- line-height: 16px;
- font-size: 14px;
- color:#C0C0C0;
+.prov-chart .node.editor {
+ border-color: #999999;
+ border-style: dashed;
+ text-align: center;
+ line-height: 16px;
+ font-size: 14px;
+ color: #c0c0c0;
}
-.prov-chart i.icon.icon-plus{
- color:#C0C0C0;
+.prov-chart i.icon.icon-plus {
+ color: #c0c0c0;
}
-.prov-chart.editor.empty .prov-pointer{
- -webkit-filter: saturate(0);
- filter: saturate(0);
+.prov-chart.editor.empty .prov-pointer {
+ -webkit-filter: saturate(0);
+ filter: saturate(0);
}
/*
.node .editor.icon {
margin-left: 3px;
}
*/
-.prov-chart .program.node.active{
- stroke: #00eb89;
- stroke-width: 2px;
- transition: stroke 200ms;
+.prov-chart .program.node.active {
+ stroke: #00eb89;
+ stroke-width: 2px;
+ transition: stroke 200ms;
}
-.prov-chart .program-icon{
- font-size: 25px;
+.prov-chart .program-icon {
+ font-size: 25px;
}
.prov-chart .node .pdf.icon {
- margin-left: 5px;
+ margin-left: 5px;
}
/* Program editor node text */
.prov-chart .editor text {
- fill: #C0C0C0;
+ fill: #c0c0c0;
}
-#select-prov-entity{
+#select-prov-entity {
width: 100%;
}
-.prov-entity-select .help-text a{
+.prov-entity-select .help-text a {
display: block;
}
/********* The Prov Chart colors *******/
.prov-chart .node.uniqueNode1,
-.entitydetails.uniqueNode1{
- border-color: #FF0000;
- stroke: #FF0000;
+.entitydetails.uniqueNode1 {
+ border-color: #ff0000;
+ stroke: #ff0000;
}
.prov-chart .uniqueNode1 .icon,
.provenance-statement .node-link.uniqueNode1 .icon,
-.prov-chart .uniqueNode1 + .program-icon{
- color: #FF0000;
- fill: #FF0000;
+.prov-chart .uniqueNode1 + .program-icon {
+ color: #ff0000;
+ fill: #ff0000;
}
.prov-chart .uniqueNode2,
-.entitydetails.uniqueNode2{
- border-color: #878700;
- stroke: #878700;
+.entitydetails.uniqueNode2 {
+ border-color: #878700;
+ stroke: #878700;
}
.prov-chart .uniqueNode2 .icon,
.provenance-statement .node-link.uniqueNode2 .icon,
-.prov-chart .uniqueNode2 + .program-icon{
- color: #878700;
- fill: #878700;
+.prov-chart .uniqueNode2 + .program-icon {
+ color: #878700;
+ fill: #878700;
}
.prov-chart .uniqueNode3,
-.entitydetails.uniqueNode3{
- border-color: #00EAFF;
- stroke: #00EAFF;
+.entitydetails.uniqueNode3 {
+ border-color: #00eaff;
+ stroke: #00eaff;
}
.prov-chart .uniqueNode3 .icon,
.provenance-statement .node-link.uniqueNode3 .icon,
-.prov-chart .uniqueNode3 + .program-icon{
- color: #00EAFF;
- fill: #00EAFF;
+.prov-chart .uniqueNode3 + .program-icon {
+ color: #00eaff;
+ fill: #00eaff;
}
.prov-chart .uniqueNode4,
-.entitydetails.uniqueNode4{
- border-color: #AA00FF;
- stroke: #AA00FF;
+.entitydetails.uniqueNode4 {
+ border-color: #aa00ff;
+ stroke: #aa00ff;
}
.prov-chart .uniqueNode4 .icon,
.provenance-statement .node-link.uniqueNode4 .icon,
-.prov-chart .uniqueNode4 + .program-icon{
- color: #AA00FF;
- fill: #AA00FF;
+.prov-chart .uniqueNode4 + .program-icon {
+ color: #aa00ff;
+ fill: #aa00ff;
}
.prov-chart .node.uniqueNode5,
-.entitydetails.uniqueNode5{
- border-color: #FF7F00;
- stroke: #FF7F00;
+.entitydetails.uniqueNode5 {
+ border-color: #ff7f00;
+ stroke: #ff7f00;
}
.prov-chart .uniqueNode5 .icon,
.provenance-statement .node-link.uniqueNode5 .icon,
-.prov-chart .uniqueNode5 + .program-icon{
- color: #FF7F00;
- fill: #FF7F00;
+.prov-chart .uniqueNode5 + .program-icon {
+ color: #ff7f00;
+ fill: #ff7f00;
}
.prov-chart .node.uniqueNode6,
-.entitydetails.uniqueNode6{
- border-color: #739900;
- stroke: #739900;
+.entitydetails.uniqueNode6 {
+ border-color: #739900;
+ stroke: #739900;
}
.prov-chart .uniqueNode6 .icon,
.provenance-statement .node-link.uniqueNode6 .icon,
-.prov-chart .uniqueNode6 + .program-icon{
- color: #739900;
- fill: #739900;
+.prov-chart .uniqueNode6 + .program-icon {
+ color: #739900;
+ fill: #739900;
}
.prov-chart .node.uniqueNode7,
-.entitydetails.uniqueNode7{
- border-color: #0095FF;
- stroke: #0095FF;
+.entitydetails.uniqueNode7 {
+ border-color: #0095ff;
+ stroke: #0095ff;
}
.prov-chart .uniqueNode7 .icon,
.provenance-statement .node-link.uniqueNode7 .icon,
-.prov-chart .uniqueNode7 + .program-icon{
- color: #0095FF;
- fill: #0096FF;
+.prov-chart .uniqueNode7 + .program-icon {
+ color: #0095ff;
+ fill: #0096ff;
}
.prov-chart .node.uniqueNode8,
-.entitydetails.uniqueNode8{
- border-color: #FF00AA;
- stroke: #FF00AA;
+.entitydetails.uniqueNode8 {
+ border-color: #ff00aa;
+ stroke: #ff00aa;
}
.prov-chart .uniqueNode8 .icon,
.provenance-statement .node-link.uniqueNode8 .icon,
-.prov-chart .uniqueNode8 + .program-icon{
- color: #FF00AA;
- fill: #FF00AA;
+.prov-chart .uniqueNode8 + .program-icon {
+ color: #ff00aa;
+ fill: #ff00aa;
}
.prov-chart .node.uniqueNode9,
-.entitydetails.uniqueNode9{
- border-color: #FFD400;
- stroke: #FFD400;
+.entitydetails.uniqueNode9 {
+ border-color: #ffd400;
+ stroke: #ffd400;
}
.prov-chart .uniqueNode9 .icon,
.provenance-statement .node-link.uniqueNode9 .icon,
-.prov-chart .uniqueNode9 + .program-icon{
- color: #FFD400;
- fill: #FFD400;
+.prov-chart .uniqueNode9 + .program-icon {
+ color: #ffd400;
+ fill: #ffd400;
}
.prov-chart .node.uniqueNode10,
-.entitydetails.uniqueNode10{
- border-color: #6AFF00;
- stroke: #6AFF00;
+.entitydetails.uniqueNode10 {
+ border-color: #6aff00;
+ stroke: #6aff00;
}
.prov-chart .uniqueNode10 .icon,
.provenance-statement .node-link.uniqueNode10 .icon,
-.prov-chart .uniqueNode10 + .program-icon{
- color: #6AFF00;
- fill: #6AFF00;
+.prov-chart .uniqueNode10 + .program-icon {
+ color: #6aff00;
+ fill: #6aff00;
}
.prov-chart .node.uniqueNode11,
-.entitydetails.uniqueNode11{
- border-color: #0040FF;
- stroke: #0040FF;
+.entitydetails.uniqueNode11 {
+ border-color: #0040ff;
+ stroke: #0040ff;
}
.prov-chart .uniqueNode11 .icon,
.provenance-statement .node-link.uniqueNode11 .icon,
-.prov-chart .uniqueNode11 + .program-icon{
- color: #0040FF;
- fill: #0040FF;
+.prov-chart .uniqueNode11 + .program-icon {
+ color: #0040ff;
+ fill: #0040ff;
}
.prov-chart .node.uniqueNode12,
-.entitydetails.uniqueNode12{
- border-color:#EDB9B9;
- stroke:#EDB9B9;
+.entitydetails.uniqueNode12 {
+ border-color: #edb9b9;
+ stroke: #edb9b9;
}
.prov-chart .uniqueNode12 .icon,
.provenance-statement .node-link.uniqueNode12 .icon,
-.prov-chart .uniqueNode12 + .program-icon{
- color: #EDB9B9;
- fill: #ED89B9;
+.prov-chart .uniqueNode12 + .program-icon {
+ color: #edb9b9;
+ fill: #ed89b9;
}
.prov-chart .node.uniqueNode13,
-.entitydetails.uniqueNode13{
- border-color: #B9D7ED;
- stroke: #B9D7ED;
+.entitydetails.uniqueNode13 {
+ border-color: #b9d7ed;
+ stroke: #b9d7ed;
}
.prov-chart .uniqueNode13 .icon,
.provenance-statement .node-link.uniqueNode13 .icon,
-.prov-chart .uniqueNode13 + .program-icon{
- color: #B9D7ED;
- fill: #B9D7ED;
+.prov-chart .uniqueNode13 + .program-icon {
+ color: #b9d7ed;
+ fill: #b9d7ed;
}
.prov-chart .node.uniqueNode14,
-.entitydetails.uniqueNode14{
- border-color:#E7E9B9;
- stroke:#E7E9B9;
+.entitydetails.uniqueNode14 {
+ border-color: #e7e9b9;
+ stroke: #e7e9b9;
}
.prov-chart .uniqueNode14 .icon,
.provenance-statement .node-link.uniqueNode14 .icon,
-.prov-chart .uniqueNode14 + .program-icon{
- color: #E7E9B9;
- fill: #E7E9B9;
+.prov-chart .uniqueNode14 + .program-icon {
+ color: #e7e9b9;
+ fill: #e7e9b9;
}
.prov-chart .node.uniqueNode15,
-.entitydetails.uniqueNode15{
- border-color: #DCB9ED;
- stroke: #DCB9ED;
+.entitydetails.uniqueNode15 {
+ border-color: #dcb9ed;
+ stroke: #dcb9ed;
}
.prov-chart .uniqueNode15 .icon,
.provenance-statement .node-link.uniqueNode15 .icon,
-.prov-chart .uniqueNode15 + .program-icon{
- color: #DCB9ED;
- fill: #DCB9ED;
+.prov-chart .uniqueNode15 + .program-icon {
+ color: #dcb9ed;
+ fill: #dcb9ed;
}
-.prov-chart .node.uniqueNode16{
- border-color:#B9EDE0;
- stroke:#B9EDE0;
+.prov-chart .node.uniqueNode16 {
+ border-color: #b9ede0;
+ stroke: #b9ede0;
}
.prov-chart .uniqueNode16 .icon,
.provenance-statement .node-link.uniqueNode16 .icon,
-.prov-chart .uniqueNode16 + .program-icon{
- color: #B9EDE0;
- fill: #B9EDE0;
+.prov-chart .uniqueNode16 + .program-icon {
+ color: #b9ede0;
+ fill: #b9ede0;
}
.prov-chart .node.uniqueNode17,
-.entitydetails.uniqueNode17{
- border-color:#8F2323;
- stroke:#8F2323;
+.entitydetails.uniqueNode17 {
+ border-color: #8f2323;
+ stroke: #8f2323;
}
.prov-chart .uniqueNode17 .icon,
.provenance-statement .node-link.uniqueNode17 .icon,
-.prov-chart .uniqueNode17 + .program-icon{
- color: #8F2323;
- fill: #8F2323;
+.prov-chart .uniqueNode17 + .program-icon {
+ color: #8f2323;
+ fill: #8f2323;
}
.prov-chart .node.uniqueNode18,
-.entitydetails.uniqueNode18{
- border-color:#23628F;
- stroke:#23628F;
+.entitydetails.uniqueNode18 {
+ border-color: #23628f;
+ stroke: #23628f;
}
.prov-chart .uniqueNode18 .icon,
.provenance-statement .node-link.uniqueNode18 .icon,
-.prov-chart .uniqueNode18 + .program-icon{
- color: #23628F;
- fill: #23628F;
+.prov-chart .uniqueNode18 + .program-icon {
+ color: #23628f;
+ fill: #23628f;
}
.prov-chart .node.uniqueNode19,
-.entitydetails.uniqueNode19{
- border-color:#8F6A23;
- stroke:#8F6A23;
+.entitydetails.uniqueNode19 {
+ border-color: #8f6a23;
+ stroke: #8f6a23;
}
.prov-chart .uniqueNode19 .icon,
.provenance-statement .node-link.uniqueNode19 .icon,
-.prov-chart .uniqueNode19 + .program-icon{
- color: #8F6A23;
- fill: #8F6A23;
+.prov-chart .uniqueNode19 + .program-icon {
+ color: #8f6a23;
+ fill: #8f6a23;
}
.prov-chart .node.uniqueNode20,
-.entitydetails.uniqueNode20{
- border-color:#6B238F;
- stroke:#6B238F;
+.entitydetails.uniqueNode20 {
+ border-color: #6b238f;
+ stroke: #6b238f;
}
.prov-chart .uniqueNode20 .icon,
.provenance-statement .node-link.uniqueNode20 .icon,
-.prov-chart .uniqueNode20 + .program-icon{
- color: #6B238F;
- fill: #6B238F;
+.prov-chart .uniqueNode20 + .program-icon {
+ color: #6b238f;
+ fill: #6b238f;
}
.prov-chart .node.uniqueNode21,
-.entitydetails.uniqueNode21{
- border-color:#4F8F23;
- stroke:#4F8F23;
+.entitydetails.uniqueNode21 {
+ border-color: #4f8f23;
+ stroke: #4f8f23;
}
.prov-chart .uniqueNode21 .icon,
.provenance-statement .node-link.uniqueNode21 .icon,
-.prov-chart .uniqueNode21 + .program-icon{
- color: #4F8F23;
- fill: #4F8F23;
+.prov-chart .uniqueNode21 + .program-icon {
+ color: #4f8f23;
+ fill: #4f8f23;
}
.prov-chart .node.uniqueNode22,
-.entitydetails.uniqueNode22{
- border-color:#000000;
- stroke:#000000;
+.entitydetails.uniqueNode22 {
+ border-color: #000000;
+ stroke: #000000;
}
.prov-chart .uniqueNode22 .icon,
.provenance-statement .node-link.uniqueNode22 .icon,
-.prov-chart .uniqueNode22 + .program-icon{
- color: #000000;
- fill: #000000;
+.prov-chart .uniqueNode22 + .program-icon {
+ color: #000000;
+ fill: #000000;
}
.prov-chart .node.uniqueNode23,
-.entitydetails.uniqueNode23{
- border-color:#737373;
- stroke:#737373;
+.entitydetails.uniqueNode23 {
+ border-color: #737373;
+ stroke: #737373;
}
.prov-chart .uniqueNode23 .icon,
.provenance-statement .node-link.uniqueNode23 .icon,
-.prov-chart .uniqueNode23 + .program-icon{
- color: #737373;
- fill: #737373;
+.prov-chart .uniqueNode23 + .program-icon {
+ color: #737373;
+ fill: #737373;
}
/*The context sections gets narrowed when there is one prov chart next to it*/
-.gutters .entitydetails{
- width: 60%; /* IE fallback */
- width: calc(100% - 215px); /* 100% - prov-chart width - (padding + borders)*/
- display: inline-block;
- margin-left: 105px;
- box-sizing: border-box;
+.gutters .entitydetails {
+ width: 60%; /* IE fallback */
+ width: calc(100% - 215px); /* 100% - prov-chart width - (padding + borders)*/
+ display: inline-block;
+ margin-left: 105px;
+ box-sizing: border-box;
}
-.form-horizontal .controls.entitydetails{
- margin-left: 100px;
+.form-horizontal .controls.entitydetails {
+ margin-left: 100px;
}
-.form-horizontal .controls.entitydetails.hasProvLeft{
- margin-left: 0px;
- width: calc(100% - 305px);
- display: inline-block;
+.form-horizontal .controls.entitydetails.hasProvLeft {
+ margin-left: 0px;
+ width: calc(100% - 305px);
+ display: inline-block;
}
-.form-horizontal .controls.entitydetails.hasProvRight{
- width: calc(100% - 278px);
- display: inline-block;
+.form-horizontal .controls.entitydetails.hasProvRight {
+ width: calc(100% - 278px);
+ display: inline-block;
}
.form-horizontal .controls.entitydetails.hasProgramsRight.hasProvRight {
- width: calc(100% - 336px);
+ width: calc(100% - 336px);
}
-.form-horizontal .controls.entitydetails.hasProgramsRight{
- margin-right: 58px;
+.form-horizontal .controls.entitydetails.hasProgramsRight {
+ margin-right: 58px;
}
.form-horizontal .controls.entitydetails.hasProgramsLeft {
- margin-left: 58px;
+ margin-left: 58px;
}
-.form-horizontal .controls.entitydetails.hasProvLeft .thumbnail.pdf{
- margin-left: 60px;
+.form-horizontal .controls.entitydetails.hasProvLeft .thumbnail.pdf {
+ margin-left: 60px;
}
#article-container.hasProvLeft table.download-contents .name,
-#article-container.hasProvRight table.download-contents .name{
- min-width: 100px;
- max-width: 350px;
+#article-container.hasProvRight table.download-contents .name {
+ min-width: 100px;
+ max-width: 350px;
}
-#article-container.hasProvLeft.hasProvRight table.download-contents .name{
- min-width: 100px;
- max-width: 250px;
+#article-container.hasProvLeft.hasProvRight table.download-contents .name {
+ min-width: 100px;
+ max-width: 250px;
}
-.downloads-this-version{
- font-size: 12px;
+.downloads-this-version {
+ font-size: 12px;
}
/*The metadata gets narrowed even more when there are two prov charts next to it */
.hasProvLeft.hasProvRight,
-.entitydetails.hasProvLeft.hasProvRight{
- margin-left: 0px;
- width: calc(100% - 230px);
+.entitydetails.hasProvLeft.hasProvRight {
+ margin-left: 0px;
+ width: calc(100% - 230px);
}
#article-container.hasProvLeft,
-#article-container.hasProvRight{
- width: 80%;
- width: calc(100% - 110px);
- padding: 20px;
- box-sizing: border-box;
- display: inline-block;
- border-width: 1px;
- border-style: solid;
- border-radius: 4px;
-}
-#article-container.hasProvLeft.hasProvRight{
- width: 70%;
- width: calc(100% - 214px);
+#article-container.hasProvRight {
+ width: 80%;
+ width: calc(100% - 110px);
+ padding: 20px;
+ box-sizing: border-box;
+ display: inline-block;
+ border-width: 1px;
+ border-style: solid;
+ border-radius: 4px;
+}
+#article-container.hasProvLeft.hasProvRight {
+ width: 70%;
+ width: calc(100% - 214px);
}
/* The metadata is narrowed even more when there are programs */
-.form-horizontal .entitydetails.hasPrograms.hasProvLeft{
- margin-left: 57px;
- width: calc(100% - 272px); /* When there is a program column on one side */
+.form-horizontal .entitydetails.hasPrograms.hasProvLeft {
+ margin-left: 57px;
+ width: calc(100% - 272px); /* When there is a program column on one side */
}
-.form-horizontal .entitydetails.hasPrograms.hasProvRight{
- margin-right: 57px;
- width: calc(100% - 362px); /* When there is a program column on one side */
+.form-horizontal .entitydetails.hasPrograms.hasProvRight {
+ margin-right: 57px;
+ width: calc(100% - 362px); /* When there is a program column on one side */
}
-.form-horizontal .entitydetails.hasProvLeft.hasProvRight.hasPrograms{
- width: calc(100% - 334px); /* When there are program columns on both sides */
+.form-horizontal .entitydetails.hasProvLeft.hasProvRight.hasPrograms {
+ width: calc(100% - 334px); /* When there are program columns on both sides */
}
/* MDQ */
-#mdqResult .alert [data-toggle="collapse"]:after
-{
- /*content: "\e072"; "play" icon */
- content: "-";
- float: right;
- color: #b0c5d8;
- font-size: 18px;
- line-height: 22px;
+#mdqResult .alert [data-toggle="collapse"]:after {
+ /*content: "\e072"; "play" icon */
+ content: "-";
+ float: right;
+ color: #b0c5d8;
+ font-size: 18px;
+ line-height: 22px;
}
-#mdqResult .alert [data-toggle="collapse"].collapsed:after
-{
- content: "+";
+#mdqResult .alert [data-toggle="collapse"].collapsed:after {
+ content: "+";
}
.mdq .popover {
- max-width: 50%;
+ max-width: 50%;
}
#mdqResult .progress {
- margin-bottom: 10px;
- margin-top: 0px;
+ margin-bottom: 10px;
+ margin-top: 0px;
}
#mdqResult .progress-bar-success {
- opacity: 0.50;
+ opacity: 0.5;
}
.list-group-item.success .icon-caret-right,
@@ -1342,363 +1346,363 @@ text-shadow: none;
.list-group-item.info .icon-caret-right,
.list-group-item.info .icon-caret-down,
.list-group-item.danger .icon-caret-down,
-.list-group-item.danger .icon-caret-right{
- color: #333;
+.list-group-item.danger .icon-caret-right {
+ color: #333;
}
.list-group-item .icon-caret-right,
-.list-group-item.collapsed .icon-caret-down{
- display: none;
+.list-group-item.collapsed .icon-caret-down {
+ display: none;
}
.list-group-item .icon-caret-down,
-.list-group-item.collapsed .icon-caret-right{
- display: inline;
+.list-group-item.collapsed .icon-caret-right {
+ display: inline;
}
.list-group-item.collapse {
- padding: 0px;
- border: 0px;
- margin-bottom: 0px;
+ padding: 0px;
+ border: 0px;
+ margin-bottom: 0px;
}
-.list-group-item.collapse.in{
- padding: 10px 15px;
- border: 1px solid #DDD;
- margin-bottom: -1px;
+.list-group-item.collapse.in {
+ padding: 10px 15px;
+ border: 1px solid #ddd;
+ margin-bottom: -1px;
}
-.check.fail .icon{
- color: #960303;
+.check.fail .icon {
+ color: #960303;
}
-.fail.check .icon-remove{
- font-size: 1em;
- color: #FFF;
+.fail.check .icon-remove {
+ font-size: 1em;
+ color: #fff;
}
-.check.fail .icon-sign-blank{
- font-size: 1.3em;
+.check.fail .icon-sign-blank {
+ font-size: 1.3em;
}
-.check .icon{
- font-size: 1.3em;
- max-width: 20px;
+.check .icon {
+ font-size: 1.3em;
+ max-width: 20px;
}
-.check .icon-stack{
- max-width: 20px;
+.check .icon-stack {
+ max-width: 20px;
}
.check.warn .icon {
- color: #c09853;
- font-size: 1.6em;
+ color: #c09853;
+ font-size: 1.6em;
}
-.check.warn .icon-exclamation{
- font-size: .9em;
+.check.warn .icon-exclamation {
+ font-size: 0.9em;
}
-.check.info-check .icon{
- color: #3a87ad;
+.check.info-check .icon {
+ color: #3a87ad;
}
-.check.info-check .icon-info{
- font-size: .8em;
+.check.info-check .icon-info {
+ font-size: 0.8em;
}
-.check .icon.subtle{
- color: #999;
- font-size: 1em;
+.check .icon.subtle {
+ color: #999;
+ font-size: 1em;
}
#mdqTypeSummary {
- float: right;
- width: 70%;
+ float: right;
+ width: 70%;
}
#mdqResult #data-chart {
- height: 220px;
- width: 250px;
- margin:0px;
+ height: 220px;
+ width: 250px;
+ margin: 0px;
}
-
/* different colors for each pie slice */
#mdqResult .donut.data > g:nth-child(1) .donut-arc {
- fill: #b3e69f;
+ fill: #b3e69f;
}
#mdqResult .donut.data > g:nth-child(1) .donut-arc-label,
#mdqResult .donut.data > g:nth-child(1) .donut-arc.active {
- fill: #468847;
+ fill: #468847;
}
#mdqResult .donut.data > g:nth-child(2) .donut-arc {
- fill: #fff0a1;
+ fill: #fff0a1;
}
#mdqResult .donut.data > g:nth-child(2) .donut-arc-label,
#mdqResult .donut.data > g:nth-child(2) .donut-arc.active {
- fill: #c09853;
+ fill: #c09853;
}
#mdqResult .donut.data > g:nth-child(3) .donut-arc {
- fill: #fbb0b0;
+ fill: #fbb0b0;
}
#mdqResult .donut.data > g:nth-child(3) .donut-arc-label,
#mdqResult .donut.data > g:nth-child(3) .donut-arc.active {
- fill: #b94a48;
+ fill: #b94a48;
}
-#mdqResult .donut.data > g:nth-child(4) .donut-arc {
- fill: #91d7fb;
+#mdqResult .donut.data > g:nth-child(4) .donut-arc {
+ fill: #91d7fb;
}
#mdqResult .donut.data > g:nth-child(4) .donut-arc-label,
#mdqResult .donut.data > g:nth-child(4) .donut-arc.active {
- fill: #3a87ad;
+ fill: #3a87ad;
}
-#mdqResult .donut-labels{
- opacity: 0.0;
+#mdqResult .donut-labels {
+ opacity: 0;
}
#mdqResult .donut-arc.active + .donut-labels,
-#mdqResult .donut-arc.active + .donut-labels .donut-arc-count.rotated{
- opacity: 1;
- font-weight: bold;
+#mdqResult .donut-arc.active + .donut-labels .donut-arc-count.rotated {
+ opacity: 1;
+ font-weight: bold;
}
/* MDQ boxplot */
-#mdq-score{
+#mdq-score {
margin-left: 0%;
}
#mdq-score-num {
- font-weight: bold;
- font-size: 1.5em;
+ font-weight: bold;
+ font-size: 1.5em;
}
-#mdq-score .icon{
- margin-left: 10px
+#mdq-score .icon {
+ margin-left: 10px;
}
-#mdq-box-plot{
- margin-bottom: 0px;
- margin-top: 0px;
+#mdq-box-plot {
+ margin-bottom: 0px;
+ margin-top: 0px;
}
#mdq-box {
width: 0%;
height: 20px;
margin-left: 0%;
}
-#mdq-repo-score{
+#mdq-repo-score {
margin-left: 0%;
}
-#mdq-repo-score .icon{
- margin-left: 10px
+#mdq-repo-score .icon {
+ margin-left: 10px;
}
-
/* Prov chart popovers */
-.prov-chart .popover{
- min-width: 300px;
+.prov-chart .popover {
+ min-width: 300px;
}
-.prov-chart .popover-content{
- padding: 0px;
+.prov-chart .popover-content {
+ padding: 0px;
}
-.popover-content .provenance-statement.list-group{
- margin: 10px;
+.popover-content .provenance-statement.list-group {
+ margin: 10px;
}
-.popover-content .well.header{
- margin-bottom: 0px;
- border-radius: 0px;
- padding-bottom: 0px;
- padding-top: 0px;
+.popover-content .well.header {
+ margin-bottom: 0px;
+ border-radius: 0px;
+ padding-bottom: 0px;
+ padding-top: 0px;
}
-.prov-chart .popover-content .header > .name{
- font-size: 1.2em;
- word-wrap: break-word;
+.prov-chart .popover-content .header > .name {
+ font-size: 1.2em;
+ word-wrap: break-word;
}
-.prov-chart .popover-content .header > .subtle{
- margin-bottom: 0px;
+.prov-chart .popover-content .header > .subtle {
+ margin-bottom: 0px;
}
-.prov-chart .popover .citation{
- font-size: 1em;
- line-height: 1.7em;
+.prov-chart .popover .citation {
+ font-size: 1em;
+ line-height: 1.7em;
}
-.prov-chart .popover .btn{
- display: block;
- margin-bottom: 10px;
+.prov-chart .popover .btn {
+ display: block;
+ margin-bottom: 10px;
}
-.prov-chart .popover-title{
- font-size: 1em;
+.prov-chart .popover-title {
+ font-size: 1em;
}
-.prov-chart .popover-title i{
- font-size: 1.2em;
+.prov-chart .popover-title i {
+ font-size: 1.2em;
}
-.prov-chart .popover .thumbnail{
- margin-bottom: 20px;
- height: auto;
- width: 100%;
- padding: 0px;
+.prov-chart .popover .thumbnail {
+ margin-bottom: 20px;
+ height: auto;
+ width: 100%;
+ padding: 0px;
}
-.prov-chart .popover h7{
- font-style: italic;
- margin-bottom: 10px;
- display: block;
- color: #888;
+.prov-chart .popover h7 {
+ font-style: italic;
+ margin-bottom: 10px;
+ display: block;
+ color: #888;
}
.popover-content > div > h6 {
margin-bottom: 0px;
margin-top: 0px;
- font-size: .9em;
+ font-size: 0.9em;
}
.popover-content > div > .name {
margin-top: 0px;
font-size: 1em;
}
-#Metadata .popover-content .citation{
- font-size: 1em;
- font-weight: normal;
+#Metadata .popover-content .citation {
+ font-size: 1em;
+ font-weight: normal;
}
/******************************************
** Collapsable Lists (ExpandCollapseView) **
******************************************/
-.expand-collapse .collapsed{
- display: none;
+.expand-collapse .collapsed {
+ display: none;
}
-.expand-collapse .control{
- margin-right: 2px;
- border-bottom: 1px dotted #999;
+.expand-collapse .control {
+ margin-right: 2px;
+ border-bottom: 1px dotted #999;
}
.expand-collapse .control,
-.expand-collapse .expanded{
- display: inline-block;
+.expand-collapse .expanded {
+ display: inline-block;
}
-.expand-collapse .control.collapse{
- overflow: visible;
- margin-left: 5px;
- display: inline;
+.expand-collapse .control.collapse {
+ overflow: visible;
+ margin-left: 5px;
+ display: inline;
}
-.provenance-statement .expand-collapse{
- display: block;
+.provenance-statement .expand-collapse {
+ display: block;
}
-.expand-collapse .control.hidden{
- display: none;
+.expand-collapse .control.hidden {
+ display: none;
}
-.expand-collapse .spacer{
- margin-right: 10px;
+.expand-collapse .spacer {
+ margin-right: 10px;
}
/******************************************
************ Popup Login **************
******************************************/
-.nav.list-group > .list-group-item img.icon{
- width: 32px;
- height: auto;
- padding: 16px;
+.nav.list-group > .list-group-item img.icon {
+ width: 32px;
+ height: auto;
+ padding: 16px;
}
-.scroll-y{
- overflow-y: scroll;
- -webkit-transform: translate3d(0,0,0);
+.scroll-y {
+ overflow-y: scroll;
+ -webkit-transform: translate3d(0, 0, 0);
}
.inline input,
-.inline label{
- float: left;
- display: inline;
- font-size: .9em;
+.inline label {
+ float: left;
+ display: inline;
+ font-size: 0.9em;
}
-.inline input{
- margin-right: 10px;
- margin-left: 30%;
+.inline input {
+ margin-right: 10px;
+ margin-left: 30%;
}
#signInPopup .orcid-signin {
- margin-top: 10%;
+ margin-top: 10%;
}
-.sign-in-ldap select{
- width: 220px;
+.sign-in-ldap select {
+ width: 220px;
}
-.modal.container{
- padding: 20px 20px 40px 20px;
+.modal.container {
+ padding: 20px 20px 40px 20px;
}
-.modal .container{
- width: auto;
- margin-bottom: 20px;
+.modal .container {
+ width: auto;
+ margin-bottom: 20px;
}
.sign-in.modal .close {
- font-size: 2em;
+ font-size: 2em;
}
.sign-in-btns .notification,
-.login .notification{
+.login .notification {
margin-top: 20px;
}
-#Navbar .sign-in-btns .notification{
+#Navbar .sign-in-btns .notification {
display: none;
}
/** The ORCID Sign In button **/
-.orcid-btn{
- border: 1px solid #aacf14;
- border-radius: 4px;
- -moz-border-radius: 4px;
- -webkit-border-radius: 4px;
- background-color: #FFF;
- padding: 15px 20px;
- color: #aacf14;
- text-decoration: none;
- margin-bottom: 10px;
- margin-top: 10px;
- display: inline-block;
- font-weight: bold;
+.orcid-btn {
+ border: 1px solid #aacf14;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ background-color: #fff;
+ padding: 15px 20px;
+ color: #aacf14;
+ text-decoration: none;
+ margin-bottom: 10px;
+ margin-top: 10px;
+ display: inline-block;
+ font-weight: bold;
}
.orcid-btn img {
- height: 1.5em;
- margin-right: 5px;
+ height: 1.5em;
+ margin-right: 5px;
}
-.orcid-btn:hover{
- background-color: #aacf14;
- transition: all 500ms;
- text-decoration: none;
+.orcid-btn:hover {
+ background-color: #aacf14;
+ transition: all 500ms;
+ text-decoration: none;
}
-.orcid-btn:hover > span{
- color: #FFF;
- text-decoration: none;
+.orcid-btn:hover > span {
+ color: #fff;
+ text-decoration: none;
}
/* MAIN CONTENT
--------------------------------- */
-#mainContent{
- padding: 20px;
+#mainContent {
+ padding: 20px;
}
-#mainContent h1{
- font-size: 2em;
+#mainContent h1 {
+ font-size: 2em;
text-align: center;
display: inline-block;
- width: 100%;
- margin-bottom: 20px;
+ width: 100%;
+ margin-bottom: 20px;
}
-#mainContent .form-input{
+#mainContent .form-input {
display: grid;
grid-template-columns: 90% 10%;
width: 100%;
}
-#mainContent input{
- border-radius: 8px 0px 0px 8px;
- -moz-border-radius: 20px 0px 0px 20px;
- font-size: 2em;
- padding-left: 20px;
- width: 100%;
- height: 100%;
+#mainContent input {
+ border-radius: 8px 0px 0px 8px;
+ -moz-border-radius: 20px 0px 0px 20px;
+ font-size: 2em;
+ padding-left: 20px;
+ width: 100%;
+ height: 100%;
}
-#mainContent input::-webkit-input-placeholder{
- padding-top: 7px;
+#mainContent input::-webkit-input-placeholder {
+ padding-top: 7px;
}
-#mainContent input:-moz-placeholder{ /* Firefox 18- */
- padding-top: 7px;
+#mainContent input:-moz-placeholder {
+ /* Firefox 18- */
+ padding-top: 7px;
}
-#mainContent input::-moz-placeholder{ /* Firefox 19+ */
- padding-top: 7px;
+#mainContent input::-moz-placeholder {
+ /* Firefox 19+ */
+ padding-top: 7px;
}
-#mainContent input:-ms-input-placeholder{
- padding-top: 7px;
+#mainContent input:-ms-input-placeholder {
+ padding-top: 7px;
}
-#search_btn_main{
- -webkit-border-radius: 0 8px 8px 0;
- -moz-border-radius: 0 15px 15px 0;
- border-left: 0px;
- -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
- height: 100%;
- padding: 4px;
- align-content: center;
- display: grid;
- font-size: 1.5em;
+#search_btn_main {
+ -webkit-border-radius: 0 8px 8px 0;
+ -moz-border-radius: 0 15px 15px 0;
+ border-left: 0px;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ height: 100%;
+ padding: 4px;
+ align-content: center;
+ display: grid;
+ font-size: 1.5em;
}
-#search_btn_main > i{
- color: #FFF;
+#search_btn_main > i {
+ color: #fff;
}
/******************************************
** The Metadata and MetadataIndex View **
@@ -1724,120 +1728,122 @@ text-shadow: none;
#metadata-index-details .Other .identifier,
#metadata-index-details .Other [class*=" prov_"],
#metadata-index-details .Other [class*=" geohash_"],
-#metadata-index-details .Other [class^="geohash_"]
-{
- display: none;
+#metadata-index-details .Other [class^="geohash_"] {
+ display: none;
}
-#metadata-index-details .entitydetails .size{
- display: inherit;
+#metadata-index-details .entitydetails .size {
+ display: inherit;
}
-#Metadata .citation,
+#Metadata .citation,
#Content.no-stylesheet .citation,
-#Metadata > .container > .row-fluid > .citation,
-#Metadata > .container > .row-fluid > form > .citation{
- font-size: 1.4em;
- line-height: 1.7em;
- margin-bottom: 20px;
- border: 0px;
- box-shadow: none;
- -webkit-box-shadow: none;
- -moz-box-shadow: none;
- padding: 0px;
- background-color: transparent;
- display: block;
- font-weight: lighter;
- width: 100%;
- float: none;
+#Metadata > .container > .row-fluid > .citation,
+#Metadata > .container > .row-fluid > form > .citation {
+ font-size: 1.4em;
+ line-height: 1.7em;
+ margin-bottom: 20px;
+ border: 0px;
+ box-shadow: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ padding: 0px;
+ background-color: transparent;
+ display: block;
+ font-weight: lighter;
+ width: 100%;
+ float: none;
}
#Metadata .citation-container.has-data-source,
#Content.no-stylesheet .citation-container.has-data-source,
-#Metadata > .container > .row-fluid > .citation-container.has-data-source,
-#Metadata > .container > .row-fluid > form > .citation-container.has-data-source{
- width: 83%;
- width: calc(100% - 130px);
- float: left;
- margin-bottom: 0px;
+#Metadata > .container > .row-fluid > .citation-container.has-data-source,
+#Metadata
+ > .container
+ > .row-fluid
+ > form
+ > .citation-container.has-data-source {
+ width: 83%;
+ width: calc(100% - 130px);
+ float: left;
+ margin-bottom: 0px;
}
.citation-container.has-data-source {
- width: calc(100% - 130px);
- float: left;
+ width: calc(100% - 130px);
+ float: left;
}
#Metadata .citation .title,
-#Content.no-stylesheet .citation .title{
- font-weight: bold;
+#Content.no-stylesheet .citation .title {
+ font-weight: bold;
}
-#Metadata{
- padding-left: 0px;
- padding-right: 0px;
- width: 100%;
+#Metadata {
+ padding-left: 0px;
+ padding-right: 0px;
+ width: 100%;
}
-.entitydetails.controls-well > .thumbnail{
- margin-left: 180px;
+.entitydetails.controls-well > .thumbnail {
+ margin-left: 180px;
}
-.citation ~ .inline-input-group > .input-append{
- float: right;
- margin-bottom: 0px;
+.citation ~ .inline-input-group > .input-append {
+ float: right;
+ margin-bottom: 0px;
}
-.input-append.inline input[type='text']{
- margin-right: 0px;
- margin-left: 0px;
+.input-append.inline input[type="text"] {
+ margin-right: 0px;
+ margin-left: 0px;
}
-.metadata-view .controls-container{
- margin-bottom: 20px;
- padding: 10px;
+.metadata-view .controls-container {
+ margin-bottom: 20px;
+ padding: 10px;
}
-.metadata-view .controls-container > .controls{
- display: inline-block;
- float: right;
- margin-top: 0px;
- margin-bottom: 0px;
+.metadata-view .controls-container > .controls {
+ display: inline-block;
+ float: right;
+ margin-top: 0px;
+ margin-bottom: 0px;
}
-.metadata-view .controls-container > .controls .inline-buttons{
- margin-top: 0px;
- margin-bottom: 0px;
-
+.metadata-view .controls-container > .controls .inline-buttons {
+ margin-top: 0px;
+ margin-bottom: 0px;
}
-.controls-well.annotations-container{
+.controls-well.annotations-container {
box-shadow: none;
border: 0px;
}
body > .alert-container {
- position: fixed;
- top: 115px;
- left: 50%;
- min-width: 75%;
- z-index: 1;
- transform: translateX(-50%);
+ position: fixed;
+ top: 115px;
+ left: 50%;
+ min-width: 75%;
+ z-index: 1;
+ transform: translateX(-50%);
}
/* Hide the elements from the view service that we don't need to display */
#Metadata form > .citation,
-#Metadata #downloadPackage{
- display: none;
+#Metadata #downloadPackage {
+ display: none;
}
/* Used in metadata view for metadata format types without stylesheet */
#Content > pre,
#Metadata pre {
- background-color: #EEE;
- color: #333;
- font-family: inherit;
- font-size: 1em;
+ background-color: #eee;
+ color: #333;
+ font-family: inherit;
+ font-size: 1em;
}
-#Metadata table .copy{
- min-width: 50px;
+#Metadata table .copy {
+ min-width: 50px;
}
-#Content.no-stylesheet.container{
- width: 86%;
+#Content.no-stylesheet.container {
+ width: 86%;
}
-#Content.no-stylesheet.container > .breadcrumb{
- width: 100%;
- margin-top: 20px;
+#Content.no-stylesheet.container > .breadcrumb {
+ width: 100%;
+ margin-top: 20px;
}
-#Content.no-stylesheet > .breadcrumb~.citation{
- margin-top: 20px;
+#Content.no-stylesheet > .breadcrumb ~ .citation {
+ margin-top: 20px;
}
/* Padding at the bottom to avoid scrollbar overlap */
-#attributeTabs{
+#attributeTabs {
padding-bottom: 25px;
}
.attributeList > .row-fluid > .span2 {
@@ -1846,126 +1852,129 @@ body > .alert-container {
overflow: scroll;
width: 25%;
}
-.attributeList > .row-fluid > .span10{
+.attributeList > .row-fluid > .span10 {
width: 72%;
}
-.attributeListTable tr td:last-child a{
- white-space: nowrap;
- max-width: 100%;
+.attributeListTable tr td:last-child a {
+ white-space: nowrap;
+ max-width: 100%;
font-weight: 600;
- font-size: .9em;
+ font-size: 0.9em;
}
.attributeListTable {
- table-layout: fixed;
- width: 100%;
+ table-layout: fixed;
+ width: 100%;
}
/* Make the icon column 16px wide */
.attributeListTable tr td:first-child {
- width: 19px;
+ width: 19px;
}
/* The active attribute in the attribute name list */
-.attributeListTable tr.active{
+.attributeListTable tr.active {
background-color: #166194;
}
-.attributeListTable tr.active a{
- color: #FFF;
+.attributeListTable tr.active a {
+ color: #fff;
}
-.controls-well > .party{
- margin-bottom: 30px;
- border-bottom: 1px solid #DDD;
+.controls-well > .party {
+ margin-bottom: 30px;
+ border-bottom: 1px solid #ddd;
}
/* ---- Taxonomic range ---- */
-.taxonomicCoverage .control-group{
- margin-bottom: 20px;
- width: auto;
+.taxonomicCoverage .control-group {
+ margin-bottom: 20px;
+ width: auto;
}
-.taxonomicCoverage > .control-group{
- margin-left: 180px;
+.taxonomicCoverage > .control-group {
+ margin-left: 180px;
}
-.taxonomicCoverage .control-label{
- width: auto;
- padding: 0px 10px;
+.taxonomicCoverage .control-label {
+ width: auto;
+ padding: 0px 10px;
}
-.taxonomicCoverage .controls > .control-group:nth-child(2n){
- margin-left: 107px;
+.taxonomicCoverage .controls > .control-group:nth-child(2n) {
+ margin-left: 107px;
}
-.taxonomicCoverage .control-label{
- color: #999;
+.taxonomicCoverage .control-label {
+ color: #999;
}
.taxonomicCoverage .controls {
- margin-left: 0px;
+ margin-left: 0px;
}
/* ---- Analyze Button ---- */
-div.dropdown.btn.btn-secondary.dropdown-toggle .icon-bar-chart
-{
- margin-right: 10px;
+div.dropdown.btn.btn-secondary.dropdown-toggle .icon-bar-chart {
+ margin-right: 10px;
}
-#metadata-controls-container > div.controls.btn-toolbar > div > button > span.analyze-text {
- margin-right:10px;
- font-weight: 600;
+#metadata-controls-container
+ > div.controls.btn-toolbar
+ > div
+ > button
+ > span.analyze-text {
+ margin-right: 10px;
+ font-weight: 600;
}
-div.analyze.dropdown{
- display: inline-block;
+div.analyze.dropdown {
+ display: inline-block;
}
-div.analyze.dropdown.open button.dropdown.btn.btn-secondary.dropdown-toggle{
- display: inline-block;
- color:white;
- text-shadow: 0;
- background-color: #1C6E84;
- border-width: 0px;
+div.analyze.dropdown.open button.dropdown.btn.btn-secondary.dropdown-toggle {
+ display: inline-block;
+ color: white;
+ text-shadow: 0;
+ background-color: #1c6e84;
+ border-width: 0px;
}
-.controls.btn-toolbar{
- font-size: 1em;
+.controls.btn-toolbar {
+ font-size: 1em;
}
.analyze.dropdown-menu {
- border: none;
- border-radius: 4px;
- padding: 0px;
- margin: 0px;
+ border: none;
+ border-radius: 4px;
+ padding: 0px;
+ margin: 0px;
}
/**----------------------------------------**/
-.metadata-view .citation{
- margin-bottom: 20px;
- display: inline-block;
- font-size: 1.2em;
- line-height: 1.5em;
+.metadata-view .citation {
+ margin-bottom: 20px;
+ display: inline-block;
+ font-size: 1.2em;
+ line-height: 1.5em;
}
-.citation-container + .data-source{
+.citation-container + .data-source {
max-width: 125px;
display: inline-block;
float: right;
margin-bottom: 10px;
}
-.controls-container .info-icons{
- display: inline-block;
- margin-left: 10px;
- margin-right: 20px;
- vertical-align: top;
- position: relative;
- font-size: 30px;
+.controls-container .info-icons {
+ display: inline-block;
+ margin-left: 10px;
+ margin-right: 20px;
+ vertical-align: top;
+ position: relative;
+ font-size: 30px;
}
-.controls-container .info-icons .icon-stack{
- width: 1.25em;
- height: 1.25em;
- vertical-align: top;
+.controls-container .info-icons .icon-stack {
+ width: 1.25em;
+ height: 1.25em;
+ vertical-align: top;
}
.controls-container .info-icons .icon-stack .icon {
- margin-top: -15px;
+ margin-top: -15px;
}
-.controls-container .info-icons .icon-stack .icon-stack-top{
- color: #FFF;
- font-size: .75em;
- margin-top: -15px;
+.controls-container .info-icons .icon-stack .icon-stack-top {
+ color: #fff;
+ font-size: 0.75em;
+ margin-top: -15px;
}
-.controls-container .info-icons .icon-stack .icon-stack-base{
- font-size: 1.25em;
+.controls-container .info-icons .icon-stack .icon-stack-base {
+ font-size: 1.25em;
}
-.metadata-controls-container{
- position: relative;
+.metadata-controls-container {
+ position: relative;
}
/* .metadata-controls-container>.copy-success{
position: absolute;
@@ -1980,149 +1989,148 @@ div.analyze.dropdown.open button.dropdown.btn.btn-secondary.dropdown-toggle{
.citation-container ~ .data-source .KUBI,
.citation-container ~ .data-source .CLOEBIRD,
.citation-container ~ .data-source .EDACGSTORE,
-.citation-container ~ .data-source .NKN{
- max-width: 175px;
+.citation-container ~ .data-source .NKN {
+ max-width: 175px;
}
.citation-container ~ .data-source .KNB,
-.citation-container ~ .data-source .LTER{
- max-height: 90px;
+.citation-container ~ .data-source .LTER {
+ max-height: 90px;
}
-
/******************************************
* Metrics Controller Styling
********************************************/
-.metrics-container{
- display: inline-block;
- margin-top: 0px;
- margin-bottom: 0px;
+.metrics-container {
+ display: inline-block;
+ margin-top: 0px;
+ margin-bottom: 0px;
}
/***********
Defult button styling for Metrics views
***********/
a.btn.metrics {
- min-width: 164px;
- margin-bottom: 0px;
+ min-width: 164px;
+ margin-bottom: 0px;
}
.metric-value > .icon.metric-icon {
- font-size: 18px;
+ font-size: 18px;
}
.metric-value.badge {
- float: right;
- background-color: #333;
- margin-top: 2px;
- margin-left: 10px;
+ float: right;
+ background-color: #333;
+ margin-top: 2px;
+ margin-left: 10px;
}
.metric-icon {
- margin-right: 10px;
- float: left;
- padding-top: 1px;
+ margin-right: 10px;
+ float: left;
+ padding-top: 1px;
}
.metric-name {
- float: left;
+ float: left;
}
.metric-value {
- float: right;
+ float: right;
}
i.icon.icon-cloud-download {
- font-size: 1.2em !important;
+ font-size: 1.2em !important;
}
i.icon.icon-eye-open {
- font-size: 1.2em !important;
+ font-size: 1.2em !important;
}
-.btn-group+.btn-group, .btn-toolbar>.btn+.btn,
-.btn-toolbar>.btn+.btn-group, .btn-toolbar>.btn-group+.btn {
- margin-left: 15px;
+.btn-group + .btn-group,
+.btn-toolbar > .btn + .btn,
+.btn-toolbar > .btn + .btn-group,
+.btn-toolbar > .btn-group + .btn {
+ margin-left: 15px;
}
i.icon.metric-icon.icon-spinner.icon-spin {
- margin-top: 2px;
- margin-right: 15px;
+ margin-top: 2px;
+ margin-right: 15px;
}
/* Style for the toolbars that contains buttons in the well on either side. */
.metric-toolbar {
- float: left;
- width: auto;
+ float: left;
+ width: auto;
}
.controls-container .controls {
- border-left: 1px solid #E3E3E3;
- float: right;
- padding-left: 10px;
+ border-left: 1px solid #e3e3e3;
+ float: right;
+ padding-left: 10px;
}
-.controls .btn{
- margin-bottom: 0px;
+.controls .btn {
+ margin-bottom: 0px;
}
/* Making the publish button consistent size as the downloads all*/
#owner-controls-container > .btn {
- margin-right: 10px;
+ margin-right: 10px;
}
-#owner-controls-container > .btn[disabled]{
+#owner-controls-container > .btn[disabled] {
cursor: not-allowed;
}
.edit-toolbar > #owner-controls-container #publish {
- width: 138px;
+ width: 138px;
}
#metric-modal {
- z-index: 1050;
- top: 15%;
- width: 90%;
- height: 500px;
- left: 5%;
- margin-left: 0px;
+ z-index: 1050;
+ top: 15%;
+ width: 90%;
+ height: 500px;
+ left: 5%;
+ margin-left: 0px;
}
#metric-modal .modal-content {
- width: auto;
+ width: auto;
}
#metric-modal .modal-header {
- height: 25px;
+ height: 25px;
}
#metric-modal .modal-body {
- width: 100%;
- height: 400px;
- max-height: 400px;
- padding: 1em;
- padding-bottom: 100px;
- background-color: #FFFFFF;
- box-sizing: border-box;
- display: grid;
- grid-template-columns: 80fr 20fr;
- column-gap: 40px;
-}
-#metrics-modal #metrics-chart{
+ width: 100%;
+ height: 400px;
+ max-height: 400px;
+ padding: 1em;
+ padding-bottom: 100px;
+ background-color: #ffffff;
+ box-sizing: border-box;
+ display: grid;
+ grid-template-columns: 80fr 20fr;
+ column-gap: 40px;
+}
+#metrics-modal #metrics-chart {
width: 100%;
}
#metric-modal .modal-footer {
- height: 25px;
- padding-top: 18px;
+ height: 25px;
+ padding-top: 18px;
}
#metric-modal .modal-header .icon {
- padding: 0px 9px;
+ padding: 0px 9px;
}
#metric-modal .modal-header .left-header-section {
- float: left;
- width: 90%;
- margin-top: 3px;
+ float: left;
+ width: 90%;
+ margin-top: 3px;
}
#metric-modal .modal-header .right-header-section {
- float: right;
- width: 10%;
+ float: right;
+ width: 10%;
}
-
#metric-modal .modal-body .modal-description p {
- margin: 0 0 25px;
+ margin: 0 0 25px;
}
-
.empty-citation-list {
grid-template-rows: 3em 100fr;
grid-template-columns: 100fr;
@@ -2136,52 +2144,52 @@ i.icon.metric-icon.icon-spinner.icon-spin {
width: calc(120% + 40px); /*Stretch across both columns in grid*/
}
-.register-citation-modal-header{
- height: 25px;
+.register-citation-modal-header {
+ height: 25px;
}
-.register-citation-modal-header > .left-header-section{
- width: 90%;
- float: left;
- margin-top: 3px;
+.register-citation-modal-header > .left-header-section {
+ width: 90%;
+ float: left;
+ margin-top: 3px;
}
-.register-citation-modal-header > .right-header-section{
- width: 10%;
- float: right;
- margin-top: 3px;
+.register-citation-modal-header > .right-header-section {
+ width: 10%;
+ float: right;
+ margin-top: 3px;
}
.register-citation-element {
- display: grid;
- align-items: center;
- justify-content: center;
+ display: grid;
+ align-items: center;
+ justify-content: center;
}
-.register-citation-element .btn{
+.register-citation-element .btn {
justify-self: center;
}
#publication-identifier.register-citation-doi-validation {
- border: 1px solid red;
+ border: 1px solid red;
}
/******************************************
* Search Page / Data Catalog Page Metrics
******************************************/
-.catalog-metrics{
+.catalog-metrics {
float: right;
margin-right: 0px;
}
.catalog-metrics .badge {
- background-color: #dedede;
- color: #666;
+ background-color: #dedede;
+ color: #666;
min-width: 47px;
text-align: center;
- display: block;
+ display: block;
}
i.catalog-metric-icon {
- margin-right: 3px;
+ margin-right: 3px;
}
-.catalog-stat{
- display: inline-block;
+.catalog-stat {
+ display: inline-block;
}
/**********************************************************
@@ -2190,26 +2198,28 @@ i.catalog-metric-icon {
#user-views-chart,
#user-downloads-chart {
- width: 90%;
+ width: 90%;
}
.metric-chart-loading {
- display: flex;
- box-sizing: border-box;
- position: relative;
- max-height: 390px;
- justify-content: center;
+ display: flex;
+ box-sizing: border-box;
+ position: relative;
+ max-height: 390px;
+ justify-content: center;
}
-.metric-chart-loading .message{
- text-align: center;
- padding: rem 1.2rem;
- font-size: 1.1rem;
- box-shadow: 0 1px 3px -1px rgba(0,0,0,0.15), 0 1px 14px -6px rgba(0,0,0,0.28);
+.metric-chart-loading .message {
+ text-align: center;
+ padding: rem 1.2rem;
+ font-size: 1.1rem;
+ box-shadow:
+ 0 1px 3px -1px rgba(0, 0, 0, 0.15),
+ 0 1px 14px -6px rgba(0, 0, 0, 0.28);
}
.metric-chart-loading .message strong {
- display: block;
- font-weight: 600;
- margin-top: 0.3rem;
+ display: block;
+ font-weight: 600;
+ margin-top: 0.3rem;
}
/*
@@ -2219,9 +2229,9 @@ i.catalog-metric-icon {
#metric-modal .metric-chart text,
.views-metrics .metric-chart text,
.downloads-metrics .metric-chart text {
- fill: #565656;
- font-size: 10px;
- font-family: Helvetica, Arial, "sans serif";
+ fill: #565656;
+ font-size: 10px;
+ font-family: Helvetica, Arial, "sans serif";
}
/*
@@ -2231,15 +2241,15 @@ i.catalog-metric-icon {
#metric-modal .metric-chart text.no-data,
.views-metrics .metric-chart text.no-data,
.downloads-metrics .metric-chart text.no-data {
- font-size: 16px;
- font-weight: 100;
- fill:#555555;
+ font-size: 16px;
+ font-weight: 100;
+ fill: #555555;
}
#metric-modal .metric-chart rect.no-data,
.views-metrics .metric-chart rect.no-data,
.downloads-metrics .metric-chart rect.no-data {
- fill: #f5f5f5;
+ fill: #f5f5f5;
}
/*
@@ -2255,46 +2265,45 @@ i.catalog-metric-icon {
#metric-modal .metric-chart .focus .axis,
.views-metrics .metric-chart .focus .axis,
.downloads-metrics .metric-chart .focus .axis {
- shape-rendering: crispEdges;
+ shape-rendering: crispEdges;
}
#metric-modal .metric-chart .focus .x.axis,
.views-metrics .metric-chart .focus .x.axis,
.downloads-metrics .metric-chart .focus .x.axis {
- clip-path: url(#clip);
+ clip-path: url(#clip);
}
#metric-modal .metric-chart .focus .x.axis line,
.views-metrics .metric-chart .focus .x.axis line,
-.downloads-metrics .metric-chart .focus .x.axis line {
- display: none;
+.downloads-metrics .metric-chart .focus .x.axis line {
+ display: none;
}
#metric-modal .metric-chart .focus .x.axis .tick:hover,
.views-metrics .metric-chart .focus .x.axis .tick:hover,
.downloads-metrics .metric-chart .focus .x.axis .tick:hover {
- cursor: default;
+ cursor: default;
}
-
/* --- x axis context --- */
#metric-modal .metric-chart .context .x.axis line,
.views-metrics .metric-chart .context .x.axis line,
.downloads-metrics .metric-chart .context .x.axis line {
- display: none;
+ display: none;
}
#metric-modal .metric-chart .context .x.axis,
.views-metrics .metric-chart .context .x.axis,
.downloads-metrics .metric-chart .context .x.axis {
- clip-path: url(#clip);
+ clip-path: url(#clip);
}
#metric-modal .metric-chart .context .x.axis .domain,
.views-metrics .metric-chart .context .x.axis .domain,
.downloads-metrics .metric-chart .context .x.axis .domain {
- display:none;
+ display: none;
}
/* --- focus y-axis --- */
@@ -2302,23 +2311,23 @@ i.catalog-metric-icon {
#metric-modal .metric-chart .focus .y.axis .domain,
.views-metrics .metric-chart .focus .y.axis .domain,
.downloads-metrics .metric-chart .focus .y.axis .domain {
- display: none;
+ display: none;
}
#metric-modal .metric-chart .y.axis.title,
.views-metrics .metric-chart .y.axis.title,
.downloads-metrics .metric-chart .y.axis.title {
- font-size: 13px;
- font-weight: 100;
+ font-size: 13px;
+ font-weight: 100;
}
/* horizontal focus gridlines */
#metric-modal .metric-chart .y.axis line,
.views-metrics .metric-chart .y.axis line,
.downloads-metrics .metric-chart .y.axis line {
- stroke: #565656;
- stroke-dasharray: 2,2;
- stroke-opacity: 0.3;
+ stroke: #565656;
+ stroke-dasharray: 2, 2;
+ stroke-opacity: 0.3;
}
/*
@@ -2330,7 +2339,7 @@ i.catalog-metric-icon {
#metric-modal .metric-chart .brush .extent,
.views-metrics .metric-chart .brush .extent,
.downloads-metrics .metric-chart .brush .extent {
- fill-opacity: .07;
+ fill-opacity: 0.07;
shape-rendering: crispEdges;
clip-path: url(#clip);
fill: #70706c;
@@ -2341,15 +2350,15 @@ i.catalog-metric-icon {
#metric-modal .metric-chart .resize .handle,
.views-metrics .metric-chart .resize .handle,
.downloads-metrics .metric-chart .resize .handle {
- fill: #555;
+ fill: #555;
}
#metric-modal .metric-chart .resize .handle-mini,
.views-metrics .metric-chart .resize .handle-mini,
.downloads-metrics .metric-chart .resize .handle-mini {
- fill: white;
- stroke-width: 1px;
- stroke: #555;
+ fill: white;
+ stroke-width: 1px;
+ stroke: #555;
}
/*
@@ -2359,68 +2368,64 @@ i.catalog-metric-icon {
#metric-modal .metric-chart .scale_button rect,
.views-metrics .metric-chart .scale_button rect,
.downloads-metrics .metric-chart .scale_button rect {
- fill: #eaeaea;
+ fill: #eaeaea;
}
#metric-modal .metric-chart .scale_button:hover text,
.views-metrics .metric-chart .scale_button:hover text,
-.downloads-metrics .metric-chart .scale_button:hover text {
- fill: white;
- transition: all 0.1s cubic-bezier(.25,.8,.25,1);
+.downloads-metrics .metric-chart .scale_button:hover text {
+ fill: white;
+ transition: all 0.1s cubic-bezier(0.25, 0.8, 0.25, 1);
}
#metric-modal .metric-chart .scale_button:hover rect,
.views-metrics .metric-chart .scale_button:hover rect,
.downloads-metrics .metric-chart .scale_button:hover rect {
- fill: grey; /* change for each theme */
- transition: all 0.1s cubic-bezier(.25,.8,.25,1);
+ fill: grey; /* change for each theme */
+ transition: all 0.1s cubic-bezier(0.25, 0.8, 0.25, 1);
}
-
-
/*
--- EXPLANATORY TEXT (# views/downloads) --- [metric modal / profile chart]
*/
#metric-modal .metric-chart text#totalCount,
.views-metrics .metric-chart text#totalCount,
-.downloads-metrics .metric-chart text#totalCount {
- font-size: 15px;
- font-weight: bold;
+.downloads-metrics .metric-chart text#totalCount {
+ font-size: 15px;
+ font-weight: bold;
}
#metric-modal .metric-chart text#displayDates,
.views-metrics .metric-chart text#displayDates,
-.downloads-metrics .metric-chart text#displayDates {
- font-weight: bold;
+.downloads-metrics .metric-chart text#displayDates {
+ font-weight: bold;
}
/*
--- BARS --- [metric modal / profile chart]
*/
-
#metric-modal .metric-chart .bar,
#metric-modal .metric-chart .bar_context,
.views-metrics .metric-chart .bar,
.views-metrics .metric-chart .bar_context,
.downloads-metrics .metric-chart .bar,
.downloads-metrics .metric-chart .bar_context {
- fill: black; /* change for each theme */
- stroke-width: 0;
- clip-path: url(#clip);
+ fill: black; /* change for each theme */
+ stroke-width: 0;
+ clip-path: url(#clip);
}
#metric-modal .metric-chart .bar,
.views-metrics .metric-chart .bar,
.downloads-metrics .metric-chart .bar {
- stroke: black; /* change for each theme */
- cursor: default;
+ stroke: black; /* change for each theme */
+ cursor: default;
}
/* --- context bars --- */
-
/*
--- PANE CURSORS --- [metric modal / profile chart]
*/
@@ -2428,17 +2433,17 @@ i.catalog-metric-icon {
#metric-modal .metric-chart rect.pane,
.views-metrics .metric-chart rect.pane,
.downloads-metrics .metric-chart rect.pane {
- cursor: move; /* fallback if grab cursor is unsupported */
- cursor: grab;
- fill: #FAFAFA;
- pointer-events: all;
+ cursor: move; /* fallback if grab cursor is unsupported */
+ cursor: grab;
+ fill: #fafafa;
+ pointer-events: all;
}
#metric-modal .metric-chart rect.pane:active,
.views-metrics .metric-chart rect.pane:active,
.downloads-metrics .metric-chart rect.pane:active {
- cursor: move; /* fallback if grab cursor is unsupported */
- cursor: grabbing;
+ cursor: move; /* fallback if grab cursor is unsupported */
+ cursor: grabbing;
}
/*
@@ -2448,18 +2453,19 @@ i.catalog-metric-icon {
#metric-modal div.metric_tooltip,
.views-metrics div.metric_tooltip,
.downloads-metrics div.metric_tooltip {
- position: fixed;
- text-align: center;
- height: 25px;
- padding: 2px;
- font: 10px sans-serif;
- background: white;
- color: #565656;
- border: 0px;
- border-radius: 2px;
- pointer-events: none;
- box-shadow: 0 0px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
-
+ position: fixed;
+ text-align: center;
+ height: 25px;
+ padding: 2px;
+ font: 10px sans-serif;
+ background: white;
+ color: #565656;
+ border: 0px;
+ border-radius: 2px;
+ pointer-events: none;
+ box-shadow:
+ 0 0px 3px rgba(0, 0, 0, 0.12),
+ 0 1px 2px rgba(0, 0, 0, 0.24);
}
.views-metrics div.metric_tooltip,
@@ -2468,19 +2474,18 @@ i.catalog-metric-icon {
}
/* ====== END: Metrics modal / profile chart ====== */
-
.metric-table.table.table-striped.table-condensed {
- width: 100%;
+ width: 100%;
}
.metric-table.table.table-striped.table-condensed td {
- padding: 1em 1em 1em 2em;
+ padding: 1em 1em 1em 2em;
}
.metric-table.table.table-striped.table-condensed td > .citation {
- margin-left: -1em;
+ margin-left: -1em;
}
button.modal-btn.btn.btn-default {
- all: unset;
- cursor: pointer;
+ all: unset;
+ cursor: pointer;
}
/***********
@@ -2489,51 +2494,52 @@ Non-clickable (Disabled) button styling for metrics views
a.btn.metrics-button-disabled {
width: 164px;
color: #777;
- border-color: #CCC;
+ border-color: #ccc;
background-color: #efefef;
cursor: default;
}
-a.btn.metrics-button-disabled:hover{
+a.btn.metrics-button-disabled:hover {
background-color: transparent;
}
-.metrics-button-disabled> .metric-value.badge {
- float: right;
- color: #FFF;
- margin-left: 20px;
- background-color: #777;
- margin-top: 2px;
+.metrics-button-disabled > .metric-value.badge {
+ float: right;
+ color: #fff;
+ margin-left: 20px;
+ background-color: #777;
+ margin-top: 2px;
}
.metrics-button-disabled.metric-icon {
- margin-right: 10px;
- float: left;
+ margin-right: 10px;
+ float: left;
}
.metrics-button-disabled.metric-name {
- float: left;
- margin-right: 10px;
+ float: left;
+ margin-right: 10px;
}
.metrics-button-disabled.metric-value {
- float: right;
+ float: right;
}
-.metrics-button-disabled:hover, .metrics-button-disabled:click {
- color: #888888;
- background-color: #F5F5F5;
- border-color: #C3C3C3;
+.metrics-button-disabled:hover,
+.metrics-button-disabled:click {
+ color: #888888;
+ background-color: #f5f5f5;
+ border-color: #c3c3c3;
}
/******************************************
* Repository + User Profile Metrics
********************************************/
section#user-citations {
- min-height: 300px;
+ min-height: 300px;
}
-#user-summary .charts{
+#user-summary .charts {
display: flex;
}
-#user-summary .summary-container{
+#user-summary .summary-container {
flex: 1;
}
.metrics-title > h3 {
- display: inline-block;
+ display: inline-block;
margin-bottom: 15px;
}
.metric-description {
@@ -2541,268 +2547,273 @@ section#user-citations {
}
.views-metrics .metric-chart,
.downloads-metrics .metric-chart {
- margin: 0 auto;
+ margin: 0 auto;
}
.citations-metrics-list {
- overflow-y: auto;
- max-height: 300px;
- content: " ";
- display: block;
- clear: both;
+ overflow-y: auto;
+ max-height: 300px;
+ content: " ";
+ display: block;
+ clear: both;
}
.citations-metrics-list > .metric-table.table.table-striped.table-condensed {
- width: 90%;
- height: 5%;
- max-height: 240px;
- overflow-y: scroll;
- top: unset;
- margin: 0 auto;
+ width: 90%;
+ height: 5%;
+ max-height: 240px;
+ overflow-y: scroll;
+ top: unset;
+ margin: 0 auto;
}
.citations-metrics-list > .empty-citation-list {
- width: 66%;
- height: 5%;
- top: unset;
- left: 25%;
- margin: 0 auto;
+ width: 66%;
+ height: 5%;
+ top: unset;
+ left: 25%;
+ margin: 0 auto;
}
.citations-metrics-list > .metric-table.table.table-striped.table-condensed td {
- padding: 5px 15px 5px 85px;
- background-color: #f9f9f9;
+ padding: 5px 15px 5px 85px;
+ background-color: #f9f9f9;
}
-.citations-metrics-list > .metric-table.table.table-striped.table-condensed td > .citation {
- margin-left: -70px;
+.citations-metrics-list
+ > .metric-table.table.table-striped.table-condensed
+ td
+ > .citation {
+ margin-left: -70px;
}
/******************************************
* The Data Details box in the Metadata View
********************************************/
-.entitydetails{
- border-width: 1px;
- border-style: solid;
- border-radius: 4px;
- -moz-border-radius: 4px;
- -webkit-border-radius: 4px;
- margin-bottom: 20px;
- margin-left: 0px;
- padding: 20px;
- width: 85%;
- width: calc(100% - 180px);
-}
-.entitydetails tr,
-.entitydetails table,
-.entitydetails tbody,
-.entitydetails td {
- word-wrap: break-word;
- box-sizing: border-box;
-}
-.entitydetails > h4 > *{
- float: left;
- margin-bottom: 20px;
-}
-.entitydetails > h4 > i{
- font-size: 1.5em;
-}
-.entitydetails > h4 > .title{
- width: 80%;
- width: calc(100% - 175px);
- margin-left: 20px;
- word-wrap: break-word;
-}
-.entitydetails > h4 > .btn{
- float: right;
-}
-.entitydetails > .control-group{
- clear: both;
-}
-.entitydetails a.download{
- margin-left: 180px;
- margin-bottom: 20px;
-}
-.form-horizontal .entitydetails > label{
- text-align: left;
- font-weight: bold;
- width: 98%;
- width: calc(100% - 5px);
- background-color: #CCCCCC;
- padding: 5px;
- color: white;
- margin-bottom: 20px;
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
- margin-left: -5px;
- margin-top: -5px;
- padding-left: 10px;
-}
+.entitydetails {
+ border-width: 1px;
+ border-style: solid;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ margin-bottom: 20px;
+ margin-left: 0px;
+ padding: 20px;
+ width: 85%;
+ width: calc(100% - 180px);
+}
+.entitydetails tr,
+.entitydetails table,
+.entitydetails tbody,
+.entitydetails td {
+ word-wrap: break-word;
+ box-sizing: border-box;
+}
+.entitydetails > h4 > * {
+ float: left;
+ margin-bottom: 20px;
+}
+.entitydetails > h4 > i {
+ font-size: 1.5em;
+}
+.entitydetails > h4 > .title {
+ width: 80%;
+ width: calc(100% - 175px);
+ margin-left: 20px;
+ word-wrap: break-word;
+}
+.entitydetails > h4 > .btn {
+ float: right;
+}
+.entitydetails > .control-group {
+ clear: both;
+}
+.entitydetails a.download {
+ margin-left: 180px;
+ margin-bottom: 20px;
+}
+.form-horizontal .entitydetails > label {
+ text-align: left;
+ font-weight: bold;
+ width: 98%;
+ width: calc(100% - 5px);
+ background-color: #cccccc;
+ padding: 5px;
+ color: white;
+ margin-bottom: 20px;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ margin-left: -5px;
+ margin-top: -5px;
+ padding-left: 10px;
+}
/******************************************
* Annotations
********************************************/
-.annotator-adder, .annotator-outer, .annotator-notice {
- z-index: 999;
+.annotator-adder,
+.annotator-outer,
+.annotator-notice {
+ z-index: 999;
}
-.annotation-container{
- margin-left: 180px;
+.annotation-container {
+ margin-left: 180px;
}
-.annotation-container .add-tag{
- margin-left: 10px;
- margin-top: 10px;
- margin-bottom: 20px;
- text-shadow: none;
+.annotation-container .add-tag {
+ margin-left: 10px;
+ margin-top: 10px;
+ margin-bottom: 20px;
+ text-shadow: none;
}
-.annotation-attribute-name{
- font-size: 0px;
+.annotation-attribute-name {
+ font-size: 0px;
}
-.annotation-target{
- height: 0px;
- margin-left: 180px;
+.annotation-target {
+ height: 0px;
+ margin-left: 180px;
}
.annotator-listing > .annotator-checkbox:nth-child(2n),
.annotator-listing > .annotator-checkbox:nth-child(3n) {
- display: none;
+ display: none;
}
#annotator-field-0,
-#annotator-field-5{
- display: none;
+#annotator-field-5 {
+ display: none;
}
-.annotator-widget span{
- padding: 7px;
- display: inline-block;
+.annotator-widget span {
+ padding: 7px;
+ display: inline-block;
}
-.annotator-widget .annotator-resize{
- display: none;
+.annotator-widget .annotator-resize {
+ display: none;
}
-.annotator-filter{
- display: none;
+.annotator-filter {
+ display: none;
}
-.annotator-field p{
- padding: 10px;
+.annotator-field p {
+ padding: 10px;
}
.annotation.tag {
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
- font-style: normal;
- font-weight: 400;
- text-shadow: none;
- box-shadow: none;
- text-align: center;
-}
-.annotation-viewer-container .annotation.tag{
- display: inline-block;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ font-style: normal;
+ font-weight: 400;
+ text-shadow: none;
+ box-shadow: none;
+ text-align: center;
+}
+.annotation-viewer-container .annotation.tag {
+ display: inline-block;
}
.annotator-tags,
.annotator-viewer .annotator-item,
-.annotator-viewer div{
- border-top: 0px;
+.annotator-viewer div {
+ border-top: 0px;
}
.annotator-tags > .annotator-tag,
-.annotator-annotation .annotator-tag{
- display: none;
+.annotator-annotation .annotator-tag {
+ display: none;
}
-.annotator-viewer div:first-of-type{
- padding-top: 0px;
- padding-bottom: 0px;
+.annotator-viewer div:first-of-type {
+ padding-top: 0px;
+ padding-bottom: 0px;
}
.annotator-widget * {
- font-style: normal;
- color: #333;
+ font-style: normal;
+ color: #333;
}
-.annotator-widget .subtle{
- color: #999;
+.annotator-widget .subtle {
+ color: #999;
}
-.annotator-widget .smaller{
- font-size: .9em;
+.annotator-widget .smaller {
+ font-size: 0.9em;
}
-.annotator-widget .tag-date{
- display: block;
- padding-left: 0px;
+.annotator-widget .tag-date {
+ display: block;
+ padding-left: 0px;
}
.annotator-viewer .container {
- padding-top: 10px;
- margin-top: 10px;
+ padding-top: 10px;
+ margin-top: 10px;
}
-.annotator-viewer .annotator-controls{
- display: none;
+.annotator-viewer .annotator-controls {
+ display: none;
}
.annotation-flag.btn,
.annotation-delete.btn {
- border-width: 0px;
- margin-bottom: 0px;
- margin-right: 10px;
- margin-left: -7px;
- border-top-left-radius: 0px;
- border-bottom-left-radius: 0px;
- border: 0px;
- padding: 6px;
- font-size: 1em;
- line-height: 0em;
- margin-top: -3px;
+ border-width: 0px;
+ margin-bottom: 0px;
+ margin-right: 10px;
+ margin-left: -7px;
+ border-top-left-radius: 0px;
+ border-bottom-left-radius: 0px;
+ border: 0px;
+ padding: 6px;
+ font-size: 1em;
+ line-height: 0em;
+ margin-top: -3px;
}
.annotation-flag.btn:hover,
-.annotation-delete.btn:hover{
- border-width: 0px;
+.annotation-delete.btn:hover {
+ border-width: 0px;
}
.annotation-delete.btn-warning,
.annotation-flag.btn-warning {
- background-image: inherit;
- background-color: #da4f49;
- color: white;
+ background-image: inherit;
+ background-color: #da4f49;
+ color: white;
}
.annotation-delete.btn-warning:hover,
-.annotation-flag.btn-warning:hover{
- background-color: #f73f3f;
+.annotation-flag.btn-warning:hover {
+ background-color: #f73f3f;
}
.annotation-flag.btn-success {
- background-color: #5bb75b;
- color: #FFF;
+ background-color: #5bb75b;
+ color: #fff;
}
-.annotation-flag.btn-success:hover{
- background-color: #66ce66;
+.annotation-flag.btn-success:hover {
+ background-color: #66ce66;
}
-.annotation-viewer-container .warning{
- color: #d00000;
+.annotation-viewer-container .warning {
+ color: #d00000;
}
-.annotator-editor .annotator-controls{
- background-image: none;
- background-color: #FFF;
- padding-top: 10px;
+.annotator-editor .annotator-controls {
+ background-image: none;
+ background-color: #fff;
+ padding-top: 10px;
}
.annotator-editor .annotator-checkbox,
.annotator-editor #annotator-field-4,
-.annotator-editor .annotator-item label[for="annotator-field-4"]{
- display: none !important;
+.annotator-editor .annotator-item label[for="annotator-field-4"] {
+ display: none !important;
}
-.ui-autocomplete.annotator{
- z-index: 9999;
+.ui-autocomplete.annotator {
+ z-index: 9999;
}
.popover-title {
- text-transform: capitalize;
+ text-transform: capitalize;
}
.popover-title code {
- text-transform: none;
- margin-left: 20px;
- margin-top: -3px;
+ text-transform: none;
+ margin-left: 20px;
+ margin-top: -3px;
}
/* Don't force capitalization on annotation popover titles */
.annotation > .popover-title {
- text-transform: none;
+ text-transform: none;
}
.annotation {
- cursor: pointer;
+ cursor: pointer;
}
-.annotation-viewer-container .concept a{
- white-space: pre-wrap;
+.annotation-viewer-container .concept a {
+ white-space: pre-wrap;
}
.annotation.tag.warning {
- background-color: #999;
- color: #DDD;
+ background-color: #999;
+ color: #ddd;
}
/* Force hyperlinks in popovers to wrap */
.annotation .popover .popover-content a {
- white-space: normal;
+ white-space: normal;
}
/* Annotation pill stuff */
@@ -2819,38 +2830,38 @@ section#user-citations {
}
/** Annotation icon **/
-.annotation-icon{
+.annotation-icon {
display: inline-block;
height: 1em;
line-height: 1em;
- font-size: .9em;
+ font-size: 0.9em;
}
-.attributeListTable .annotation-icon{
- width: .6em;
- line-height: .9em;
+.attributeListTable .annotation-icon {
+ width: 0.6em;
+ line-height: 0.9em;
margin-left: -5px;
font-size: 2.5em;
}
-.attributeListTable .annotation-icon .icon{
- font-size: .5em;
+.attributeListTable .annotation-icon .icon {
+ font-size: 0.5em;
}
-.attributeListTable .annotation-icon .icon-ok{
- font-size: .25em;
+.attributeListTable .annotation-icon .icon-ok {
+ font-size: 0.25em;
}
-.annotation-icon .icon{
- color: #FFF;
+.annotation-icon .icon {
+ color: #fff;
}
-.annotation-icon .icon.icon-stack-base{
+.annotation-icon .icon.icon-stack-base {
color: #337ab7;
}
.icons.annotations .icon-stack {
- vertical-align: -15%;
+ vertical-align: -15%;
}
.icons.annotations .icon.icon-stack-base {
- font-size: 1.25em;
+ font-size: 1.25em;
}
.icons.annotations .icon.icon-ok {
- font-size: 0.65em;
+ font-size: 0.65em;
}
/** Annotation pills **/
.annotation .annotation-property,
@@ -2859,11 +2870,11 @@ section#user-citations {
}
.annotation .annotation-property {
- background-color: #EEE;
+ background-color: #eee;
border-width: 1px 0 1px 1px;
border-style: solid;
- border-color: #CCC;
- border-radius: 3px 0px 0px 3px;
+ border-color: #ccc;
+ border-radius: 3px 0px 0px 3px;
}
.annotation .annotation-value {
@@ -2879,37 +2890,37 @@ section#user-citations {
}
.annotation .annotation-remove {
- padding: 3px 10px;
+ padding: 3px 10px;
}
.annotation .annotation-remove {
- padding: 3px 10px;
+ padding: 3px 10px;
}
- .annotation-popover-definition {
- padding: 0.5rem 0;
- margin: 0.5rem 0;
+.annotation-popover-definition {
+ padding: 0.5rem 0;
+ margin: 0.5rem 0;
}
.annotation-popover-definition {
- border-top: 1px solid #CCC;
- border-bottom: 1px solid #CCC;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
}
/* MeasurementTypeView */
.measurement-type-browse {
display: grid;
grid-template-columns: 2fr 1fr;
- max-height: 12em;
- overflow-y: scroll;
+ max-height: 12em;
+ overflow-y: scroll;
}
/* MeasurementTypeView */
.measurement-type-browse {
display: grid;
grid-template-columns: 2fr 1fr;
- max-height: 12em;
- overflow-y: scroll;
+ max-height: 12em;
+ overflow-y: scroll;
}
.measurement-type-browse-tree {
@@ -2939,154 +2950,155 @@ section#user-citations {
margin-bottom: 0.25rem;
}
-
/** Hide the search when in the dropdown menu **/
.dropdown-menu .ncboAutocomplete {
- display: none;
+ display: none;
}
#bioportal-tree-annotator {
- max-height: 200px;
- overflow-y: scroll;
+ max-height: 200px;
+ overflow-y: scroll;
}
#bioportal-tree-label .annotation.tag.btn {
- margin-left: 10px;
- margin-top: -20px;
- margin-bottom: 10px;
+ margin-left: 10px;
+ margin-top: -20px;
+ margin-bottom: 10px;
}
-.ncboAutocomplete .ui-menu .ui-menu-item a.ui-state-focus, .ncboAutocomplete .ui-menu .ui-menu-item a.ui-state-hover, .ncboAutocomplete .ui-menu .ui-menu-item a.ui-state-active, .ui-menu-item a {
- text-shadow: none;
- font-size: 14px;
- width: 100%;
+.ncboAutocomplete .ui-menu .ui-menu-item a.ui-state-focus,
+.ncboAutocomplete .ui-menu .ui-menu-item a.ui-state-hover,
+.ncboAutocomplete .ui-menu .ui-menu-item a.ui-state-active,
+.ui-menu-item a {
+ text-shadow: none;
+ font-size: 14px;
+ width: 100%;
}
.ncboAutocomplete .jsonSuggest {
- overflow-x: scroll;
+ overflow-x: scroll;
}
.ncboAutocomplete > .ui-autocomplete .ui-menu-item {
- overflow: visible;
+ overflow: visible;
}
.ncboTree {
- width: auto;
- max-height: 300px;
+ width: auto;
+ max-height: 300px;
}
ul.ncboTree {
- overflow: scroll;
+ overflow: scroll;
}
.ncbo-tree-buttons-container {
- position: absolute;
- top: 0;
- right: 0;
+ position: absolute;
+ top: 0;
+ right: 0;
}
/** hide the tree until we have it where we want it **/
#bioportal-tree {
- display: none;
+ display: none;
}
.dropdown-menu #bioportal-tree {
- display: block;
+ display: block;
}
#bioportal-popover #bioportal-tree {
- display: block;
+ display: block;
}
-#sidebar .input-append {
- font-size: 1em;
+#sidebar .input-append {
+ font-size: 1em;
}
-#bioportal-popover .popover{
- max-width: 500px;
- width: 500px;
-
+#bioportal-popover .popover {
+ max-width: 500px;
+ width: 500px;
}
-#bioportal-popover .popover-content{
- width: 95%;
+#bioportal-popover .popover-content {
+ width: 95%;
}
-#bioportal-popover #bioportal-tree{
- max-height: 400px;
+#bioportal-popover #bioportal-tree {
+ max-height: 400px;
}
#jumpUp {
- float: right;
+ float: right;
}
#resetTree {
- float: right;
+ float: right;
}
/* --- multiselect annotation filter */
.annotation-filter .searchable-select #bioportal-tree {
- display: block;
- margin-top: 0.5rem;
- overflow-x: auto;
+ display: block;
+ margin-top: 0.5rem;
+ overflow-x: auto;
}
.annotation-filter .searchable-select .menu {
- min-height: 10rem;
+ min-height: 10rem;
}
/* We use the input in the semantic UI input interface to search */
/* instead of the NCBO tree interface */
.annotation-filter .searchable-select input.ncboAutocomplete {
- display: none;
+ display: none;
}
/* The contents of the dropdown menu is replaced with the annotation tree */
/* Hide all of the menu items that semantic UI inserets */
.annotation-filter .searchable-select .item,
.annotation-filter .searchable-select .message {
- display: none !important;
+ display: none !important;
}
/******************************************
* Images, Maps, and Media
********************************************/
-.thumbnail{
- height: auto;
- max-width: 100%;
- padding: 20px;
- margin-bottom: 20px;
- box-sizing: border-box;
-}
-.entitydetails .thumbnail{
- margin-left: 180px;
- margin-top: 40px;
- padding: 20px;
- width: 350px;
-}
-.entitydetails .thumbnail.pdf{
- width: 500px;
-}
-.entitydetails .thumbnail.pdf .zoom-in{
- width: 500px;
- display: block;
- height: 2em;
- background-color: rgba(0,0,0,0.5);
- color: white;
- font-weight: normal;
- text-align: center;
- line-height: 2em;
-}
-.thumbnail img{
- min-width: 100%;
- height: auto;
-}
-.thumbnail.map{
- width: auto;
- max-width: 700px;
-}
-.control-group ~ .thumbnail{
- margin-left: 180px;
+.thumbnail {
+ height: auto;
+ max-width: 100%;
+ padding: 20px;
+ margin-bottom: 20px;
+ box-sizing: border-box;
+}
+.entitydetails .thumbnail {
+ margin-left: 180px;
+ margin-top: 40px;
+ padding: 20px;
+ width: 350px;
+}
+.entitydetails .thumbnail.pdf {
+ width: 500px;
+}
+.entitydetails .thumbnail.pdf .zoom-in {
+ width: 500px;
+ display: block;
+ height: 2em;
+ background-color: rgba(0, 0, 0, 0.5);
+ color: white;
+ font-weight: normal;
+ text-align: center;
+ line-height: 2em;
+}
+.thumbnail img {
+ min-width: 100%;
+ height: auto;
+}
+.thumbnail.map {
+ width: auto;
+ max-width: 700px;
+}
+.control-group ~ .thumbnail {
+ margin-left: 180px;
}
img[src*="gstatic.com/"].georegion-map,
-img[src*="googleapis.com/"].georegion-map{
- max-width: 100%;
+img[src*="googleapis.com/"].georegion-map {
+ max-width: 100%;
}
-.figure{
- margin-top: 20px;
- margin-bottom: 20px;
- max-width: 100%;
- margin-right: auto;
- margin-left: auto;
+.figure {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ max-width: 100%;
+ margin-right: auto;
+ margin-left: auto;
}
.media {
margin-top: 15px;
@@ -3101,8 +3113,8 @@ img[src*="googleapis.com/"].georegion-map{
.media-body {
width: 10000px;
}
-.media-body h5 a{
- color: inherit;
+.media-body h5 a {
+ color: inherit;
}
.media-object {
display: block;
@@ -3142,7 +3154,7 @@ img[src*="googleapis.com/"].georegion-map{
/******************************************
* EML Party display
********************************************/
-.eml-party .name{
+.eml-party .name {
font-weight: bold;
}
@@ -3152,136 +3164,136 @@ img[src*="googleapis.com/"].georegion-map{
/* Image upload error formatting (including validation) */
.dropzone .dz-error {
- color: #960303;
+ color: #960303;
}
.dropzone.error {
- border-color: #960303;
+ border-color: #960303;
}
.dropzone.error .dz-message {
- background-color: rgba(255,0,0,0.1);
+ background-color: rgba(255, 0, 0, 0.1);
}
-.dz-image-preview div.dz-error-message{
- top: 0;
- bottom: 0;
- position: absolute;
- color: #960303;
+.dz-image-preview div.dz-error-message {
+ top: 0;
+ bottom: 0;
+ position: absolute;
+ color: #960303;
box-sizing: border-box;
padding: 4px;
width: 100%;
- white-space: normal;
- min-height: 100%;
- max-height: 100%;
- display: flex;
+ white-space: normal;
+ min-height: 100%;
+ max-height: 100%;
+ display: flex;
align-items: center;
- z-index: 0; /* make sure it's below clickable dropzone items */
- line-height: 100%;
+ z-index: 0; /* make sure it's below clickable dropzone items */
+ line-height: 100%;
}
.port-editor-md .dz-image-preview div.dz-error-message {
- align-items: flex-start;
- padding-top: 19px;
- justify-content: center;
+ align-items: flex-start;
+ padding-top: 19px;
+ justify-content: center;
}
-.remove-preview .dz-image-preview div.dz-error-message{
- opacity: 0.2;
+.remove-preview .dz-image-preview div.dz-error-message {
+ opacity: 0.2;
}
/* Image upload drag and drop zone */
.dropzone {
- background: #f6f6f6;
- border-radius: 3px;
- cursor: pointer;
- height:100%;
- width:100%;
+ background: #f6f6f6;
+ border-radius: 3px;
+ cursor: pointer;
+ height: 100%;
+ width: 100%;
text-align: center;
- position: relative;
- box-sizing: border-box;
+ position: relative;
+ box-sizing: border-box;
}
-.dropzone .dz-message{
- position: absolute;
- top: 0;
- bottom: 0;
- align-items: center;
- box-sizing: border-box;
- padding: 8px;
- width: 100%;
- justify-content: center;
- z-index: 2; /* make sure it's above .dz-error message because this element must be clickable */
+.dropzone .dz-message {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ align-items: center;
+ box-sizing: border-box;
+ padding: 8px;
+ width: 100%;
+ justify-content: center;
+ z-index: 2; /* make sure it's above .dz-error message because this element must be clickable */
}
.dz-image-preview {
- white-space: nowrap;
- text-align: center;
- box-sizing: border-box;
- padding-bottom: 3px; /* to account for a 1.5px top border and 1.5px bottom border*/
+ white-space: nowrap;
+ text-align: center;
+ box-sizing: border-box;
+ padding-bottom: 3px; /* to account for a 1.5px top border and 1.5px bottom border*/
}
.vertical-align-image-helper {
- display: inline-block;
- height: 100%;
- vertical-align: middle;
+ display: inline-block;
+ height: 100%;
+ vertical-align: middle;
}
/* when images are displayed as image elements */
-.dz-image-preview img{
- max-height: 100%;
+.dz-image-preview img {
+ max-height: 100%;
}
/* when images are displayed as background of divs */
-.dz-image-preview div{
- background-repeat: no-repeat;
- background-position-x: 50%;
- background-position-y: 50%;
- background-size: cover;
- min-height: 296px; /* 300px - 4px for top and bottom border*/
- color: #FFF;
+.dz-image-preview div {
+ background-repeat: no-repeat;
+ background-position-x: 50%;
+ background-position-y: 50%;
+ background-size: cover;
+ min-height: 296px; /* 300px - 4px for top and bottom border*/
+ color: #fff;
}
/* the remove image button */
.dz-preview i {
- position: absolute;
- bottom: 0;
- right: 0;
- box-sizing: border-box;
- opacity: 0;
- font-size: 17px;
- padding:1px;
- margin: 2px;
- color: #ababab;
- cursor: pointer;
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ box-sizing: border-box;
+ opacity: 0;
+ font-size: 17px;
+ padding: 1px;
+ margin: 2px;
+ color: #ababab;
+ cursor: pointer;
}
.dropzone:hover i {
- opacity:0.8;
+ opacity: 0.8;
}
/* show dashed outline when no image is uploaded, or when image is uploaded and user hovers */
-.dropzone{
+.dropzone {
background-image: url(../img/transp_bg.png);
}
.dropzone.dz-started {
- border-color: #ededed;
- background: transparent;
+ border-color: #ededed;
+ background: transparent;
}
-.dropzone.dz-started .dz-message{
+.dropzone.dz-started .dz-message {
opacity: 0;
}
.dropzone,
.dropzone.dz-started:hover,
.dropzone.dz-started.dz-drag-hover {
- border: dashed 1.5px #999;
+ border: dashed 1.5px #999;
}
-.dz-message{
- transition: all .2s ease;
+.dz-message {
+ transition: all 0.2s ease;
}
-.dropzone{
- transition: all .2s ease;
+.dropzone {
+ transition: all 0.2s ease;
}
/* Hide help text if image is already uploaded, show when user hovers */
@@ -3289,17 +3301,17 @@ img[src*="googleapis.com/"].georegion-map{
.dropzone.dz-started:hover .dz-message,
.dropzone.dz-drag-hover .dz-message {
background-color: rgba(0, 0, 0, 0.5);
- color: #FFF;
+ color: #fff;
opacity: 1;
}
-.portal-display-image .dropzone .dz-message{
+.portal-display-image .dropzone .dz-message {
padding-top: 230px;
}
-.portal-display-image .image-uploader.remove-preview .image-container{
- opacity: .3;
+.portal-display-image .image-uploader.remove-preview .image-container {
+ opacity: 0.3;
}
.portal-display-image p.notification.error {
- text-align: center;
+ text-align: center;
}
.logos-container .dropzone .icon-upload {
@@ -3307,7 +3319,7 @@ img[src*="googleapis.com/"].georegion-map{
margin-bottom: 10px;
margin-top: 10px;
}
-.dropzone .icon-upload{
+.dropzone .icon-upload {
display: block;
font-size: 1.5em;
}
@@ -3324,46 +3336,47 @@ img[src*="googleapis.com/"].georegion-map{
.wk-container {
position: relative;
margin: 5px 0;
- border: 1px solid #e5e5e5;
- border-radius: 2px;
- display: flex;
- flex-direction: column;
+ border: 1px solid #e5e5e5;
+ border-radius: 2px;
+ display: flex;
+ flex-direction: column;
}
/* the markdown editor textarea field */
textarea.markdown {
- width: 100%;
- height: 50vh;
- overflow-y: scroll;
- overflow-x: hidden;
- margin: 5px 0 10px;
- line-height: 1.6em;
- font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
- /* so that the textarea is below any and all editor buttons/controls */
- order: 99;
+ width: 100%;
+ height: 50vh;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ margin: 5px 0 10px;
+ line-height: 1.6em;
+ font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier,
+ monospace;
+ /* so that the textarea is below any and all editor buttons/controls */
+ order: 99;
}
-.wk-container textarea.markdown{
- box-shadow: none;
- border: none;
- border-radius: 2px;
- margin: 0;
+.wk-container textarea.markdown {
+ box-shadow: none;
+ border: none;
+ border-radius: 2px;
+ margin: 0;
}
.wk-hide {
display: none !important;
}
-.wk-commands-divider{
+.wk-commands-divider {
display: inline-block;
width: 1px;
height: 21px;
background-color: #ddd;
margin: 10px 6px -7px 6px;
}
-.wk-commands{
- border-bottom: 1px solid #e5e5e5;
+.wk-commands {
+ border-bottom: 1px solid #e5e5e5;
background: #fafafa;
- padding-left: 0.5rem;
+ padding-left: 0.5rem;
}
/* markdown editor button */
.wk-command {
@@ -3374,19 +3387,19 @@ textarea.markdown {
cursor: pointer;
outline: none;
margin: 2px;
- border-radius: 6px;
- transition: all 0.2s ease-in-out;
+ border-radius: 6px;
+ transition: all 0.2s ease-in-out;
}
.wk-command svg {
- transition: fill 0.2s ease-in-out;
- fill: #333;
+ transition: fill 0.2s ease-in-out;
+ fill: #333;
}
-.wk-command:hover{
+.wk-command:hover {
background-color: white;
color: #2c7e90;
}
.wk-command:hover svg {
- fill: #2c7e90;
+ fill: #2c7e90;
}
/* ---- Markdown editor modal ---- */
/* The modal type dialogs to add image or link */
@@ -3396,8 +3409,8 @@ textarea.markdown {
left: 2%;
right: 2%;
z-index: 1050;
- width: fit-content;
- max-width: 95vw;
+ width: fit-content;
+ max-width: 95vw;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.3);
border-radius: 6px;
@@ -3406,7 +3419,7 @@ textarea.markdown {
color: #555;
padding: 0px;
margin: 0 auto;
- display: table; /* works like width:fit-content in firefox */
+ display: table; /* works like width:fit-content in firefox */
}
/* The close button is an empty link (a.wk-prompt-close),
add font awesome close icon */
@@ -3418,12 +3431,12 @@ add font awesome close icon */
top: 10px;
color: #000;
text-shadow: 0 1px 0 #fff;
- opacity: .2;
+ opacity: 0.2;
}
.wk-prompt-close::before:hover {
- opacity: .3;
+ opacity: 0.3;
}
-.wk-prompt-header{
+.wk-prompt-header {
padding: 9px 15px;
border-bottom: 1px solid #eee;
}
@@ -3439,21 +3452,21 @@ add font awesome close icon */
overflow-y: auto;
color: #999;
}
-.wk-prompt .image-uploader{
+.wk-prompt .image-uploader {
position: relative;
margin: 5px auto 15px;
}
-.wk-prompt .dropzone .dz-message{
+.wk-prompt .dropzone .dz-message {
display: flex;
flex-direction: column;
justify-content: center;
padding-bottom: 0px;
}
-.wk-prompt-input{
+.wk-prompt-input {
width: 100%;
max-width: 292px;
border: 1px solid #ccc;
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
display: inline-block;
height: 20px;
padding: 4px 6px;
@@ -3464,7 +3477,7 @@ add font awesome close icon */
vertical-align: middle;
border-radius: 4px;
}
-.wk-prompt-buttons{
+.wk-prompt-buttons {
padding: 14px 15px 15px;
margin-bottom: 0;
text-align: right;
@@ -3478,7 +3491,7 @@ add font awesome close icon */
}
.wk-prompt-buttons > button {
font-weight: 400;
- border: 1px solid #CCC;
+ border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
line-height: 20px;
@@ -3491,7 +3504,7 @@ add font awesome close icon */
color: white;
margin-left: 10px;
}
-.wk-container .tooltip{
+.wk-container .tooltip {
/* ensure the tooltip is not hidden within markdown editor container */
position: fixed;
}
@@ -3505,8 +3518,8 @@ add font awesome close icon */
}
.table-editor .table {
- /* undo bootstrap margin */
- margin-bottom: 0;
+ /* undo bootstrap margin */
+ margin-bottom: 0;
}
.table-editor td {
@@ -3514,7 +3527,7 @@ add font awesome close icon */
}
.table-editor th {
- background: #fafafa;
+ background: #fafafa;
padding: 0.5rem;
vertical-align: middle;
}
@@ -3532,12 +3545,12 @@ add font awesome close icon */
border: none;
cursor: pointer;
transition: ease 0.4s;
- border-radius: 3px;
+ border-radius: 3px;
}
-.table-editor .dropbtn .icon{
- pointer-events: none;
- margin: 0;
+.table-editor .dropbtn .icon {
+ pointer-events: none;
+ margin: 0;
}
/* Dropdown button on hover & focus */
@@ -3549,8 +3562,8 @@ add font awesome close icon */
/* The container - needed to position the dropdown content */
.table-editor .dropdown {
position: relative;
- float: right;
- width: 1rem;
+ float: right;
+ width: 1rem;
}
/* Dropdown Content (hidden by default) */
@@ -3561,34 +3574,34 @@ add font awesome close icon */
min-width: 7rem;
box-shadow: 0 0.5rem 0.8rem 0 rgba(0, 0, 0, 0.1);
z-index: 1;
- border: 1px solid rgba(0,0,0,.05);
+ border: 1px solid rgba(0, 0, 0, 0.05);
}
.table-editor .dropdown-content .icon {
pointer-events: none;
}
/* position row menus to the right of the row number */
-.table-editor .row-header .dropdown-content{
- left: 1.2rem;
- top: 0;
+.table-editor .row-header .dropdown-content {
+ left: 1.2rem;
+ top: 0;
}
/* So the drop down doesn't go into overeflow space */
.table-editor .column-header:last-of-type .dropdown-content {
- right: 0;
+ right: 0;
}
/* Buttons inside the dropdown */
.table-editor .row-dropdown-option,
.table-editor .col-dropdown-option {
- white-space: nowrap;
- display: block;
- width: 100%;
- text-align: left;
- background-color: transparent;
- color: #2c6f90;
- color: var(--portal-primary-color);
+ white-space: nowrap;
+ display: block;
+ width: 100%;
+ text-align: left;
+ background-color: transparent;
+ color: #2c6f90;
+ color: var(--portal-primary-color);
padding: 0.2rem 0.8rem;
- margin: 0.2rem 0;
+ margin: 0.2rem 0;
text-decoration: none;
border: none;
cursor: pointer;
@@ -3597,8 +3610,8 @@ add font awesome close icon */
.table-editor .row-dropdown-option:hover,
.table-editor .col-dropdown-option:hover {
- background-color: #f7f7f7;
- color: #555;
+ background-color: #f7f7f7;
+ color: #555;
}
/* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
@@ -3607,13 +3620,13 @@ add font awesome close icon */
}
.table-editor .column-header-span {
- width: calc(100% - 1.1rem);
- min-width: 1.5rem;
- float: left;
- display: inline-block;
- margin-left: 0;
- line-height: 120%;
- color: #555;
+ width: calc(100% - 1.1rem);
+ min-width: 1.5rem;
+ float: left;
+ display: inline-block;
+ margin-left: 0;
+ line-height: 120%;
+ color: #555;
}
.table-editor .spreadsheet-controls {
@@ -3623,7 +3636,7 @@ add font awesome close icon */
/******************************************
** AccessPolicy editor view ***
******************************************/
-.access-rule .subject .orcid.icon{
+.access-rule .subject .orcid.icon {
vertical-align: top;
}
@@ -3639,94 +3652,94 @@ add font awesome close icon */
max-height: 1000px;
}
-.access-policy-view:not(.modal) .modal-header{
+.access-policy-view:not(.modal) .modal-header {
border-bottom-width: 0px;
padding: 0px;
}
-.access-policy-view:not(.modal) .modal-body{
+.access-policy-view:not(.modal) .modal-body {
padding: 0px;
max-height: none;
}
-.access-policy-view:not(.modal) .modal-header .close{
+.access-policy-view:not(.modal) .modal-header .close {
display: none;
}
.access-policy-control.disabled,
.replace.disabled {
cursor: not-allowed;
}
-.access-policy-view.modal h4{
+.access-policy-view.modal h4 {
font-size: 1em;
font-weight: bold;
}
-.access-policy-view:not(.modal) h4 .icon{
+.access-policy-view:not(.modal) h4 .icon {
display: none;
}
.access-policy-view .popover li h5 {
display: inline;
}
-.access-policy-view .popover{
+.access-policy-view .popover {
min-width: 50%;
}
.access-rule td {
padding-top: 12px;
padding-bottom: 12px;
}
-.access-rule select{
+.access-rule select {
width: 100%;
min-width: 100px;
margin-bottom: 0px;
}
-.access-rule.new input{
+.access-rule.new input {
width: calc(100% - 16px);
margin-bottom: 0px;
}
-.access-rule.new label{
- font-size: .9em;
+.access-rule.new label {
+ font-size: 0.9em;
}
-.access-rule.new select{
+.access-rule.new select {
margin-top: 25px;
}
-.access-rule .remove.icon{
+.access-rule .remove.icon {
font-size: 2em;
- color: #CCC;
+ color: #ccc;
}
-.access-rule .remove-rule{
+.access-rule .remove-rule {
width: 2em;
}
-.access-policy-view .access-rule .add.icon{
+.access-policy-view .access-rule .add.icon {
margin-top: 25px;
display: block;
font-size: 2em;
cursor: pointer;
}
-.access-rule .add.icon:hover{
+.access-rule .add.icon:hover {
color: #006699;
}
.access-rule .ui-autocomplete .ui-menu-item {
padding-top: 5px;
padding-bottom: 5px;
}
-.access-policy-view .public-toggle-container{
+.access-policy-view .public-toggle-container {
margin-bottom: 20px;
position: relative;
display: inline-block;
}
-.access-policy-view .public-toggle-disabled-text.public{
+.access-policy-view .public-toggle-disabled-text.public {
color: #005300; /*dark green*/
}
-.access-policy-view .public-toggle-disabled-text.private{
- color: #A04A01; /*dark orange*/
+.access-policy-view .public-toggle-disabled-text.private {
+ color: #a04a01; /*dark orange*/
}
-.access-policy-view .can-toggle{
+.access-policy-view .can-toggle {
width: 200px;
}
-.access-policy-view p{
+.access-policy-view p {
margin-bottom: 20px;
}
-.access-policy-view:not(.modal) .modal-footer{
+.access-policy-view:not(.modal) .modal-footer {
display: none;
}
-.access-policy-view.unauthorized .alert{
+.access-policy-view.unauthorized .alert {
margin-top: 20px;
min-height: 100px;
line-height: 100px;
@@ -3736,48 +3749,48 @@ add font awesome close icon */
* Portal Editor
********************************************/
-.Portal.Editor textarea.auto-resize{
- overflow-y: hidden;
+.Portal.Editor textarea.auto-resize {
+ overflow-y: hidden;
}
-.Portal.Editor #Content{
+.Portal.Editor #Content {
padding-top: 0px;
- width: 100%;
- box-sizing: border-box;
- overflow-y: auto;
+ width: 100%;
+ box-sizing: border-box;
+ overflow-y: auto;
}
/* Portal editor login page */
-.portal-login-page{
- display: grid;
- grid-template-columns: 100%;
- grid-template-rows: auto;
- justify-items: center;
- gap: 1rem;
+.portal-login-page {
+ display: grid;
+ grid-template-columns: 100%;
+ grid-template-rows: auto;
+ justify-items: center;
+ gap: 1rem;
}
.portal-login-page .portal-image {
- max-width: 900px;
- margin: auto;
+ max-width: 900px;
+ margin: auto;
}
.portal-login-page .portal-login-title {
- margin-top: 3rem;
- font-weight: 300;
- line-height: 1.4;
+ margin-top: 3rem;
+ font-weight: 300;
+ line-height: 1.4;
}
.portal-login-page .portal-login-title strong {
- font-weight: 600;
+ font-weight: 600;
}
.portal-login-page .portal-login-button {
display: flex;
flex-direction: row;
align-items: center;
- justify-content: center;
- max-width: 230px; /* for firefox */
+ justify-content: center;
+ max-width: 230px; /* for firefox */
}
#portals-list-container .portals-list-title {
- color: #999;
+ color: #999;
}
.portals-list-entry {
@@ -3791,87 +3804,87 @@ add font awesome close icon */
margin-bottom: 0px;
}
.portals-list-entry .portal-edit-link {
-
}
-.portals-list-entry .portal-description{
-
+.portals-list-entry .portal-description {
}
-.portals-list-entry .portal-description p{
+.portals-list-entry .portal-description p {
margin: 0;
color: #888;
}
.portals-list-entry.pager {
- border: 0px;
- margin: 10px 0px;
+ border: 0px;
+ margin: 10px 0px;
}
#portals-list-container .no-results {
- grid-column-start: 2;
- text-align: center;
+ grid-column-start: 2;
+ text-align: center;
}
/* Editor navigation (tabs) */
-.section-links-container{
+.section-links-container {
margin-bottom: 0px;
}
-.section-links-toggle-container{
+.section-links-toggle-container {
padding-left: 40px;
padding-right: 40px;
}
.port-editor-sections .section-link-container {
- border-right: 1px solid #CCC;
+ border-right: 1px solid #ccc;
}
.port-editor-sections .section-link-container .handle {
- color: #FFF;
- float: left;
- height: 100%;
- display: flex;
- flex-direction: row;
- align-items: center;
- padding: 1px 3px 0 10px;
- opacity: 0.7;
- cursor: grab;
+ color: #fff;
+ float: left;
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 1px 3px 0 10px;
+ opacity: 0.7;
+ cursor: grab;
}
.port-editor-sections .section-link-container.sortable-chosen,
.port-editor-sections .section-link-container.sortable-chosen .handle {
- cursor: grabbing;
+ cursor: grabbing;
}
.port-editor-sections .section-link-container.sortable-ghost,
.port-editor-sections .section-link-container.sortable-chosen {
- background-color: #006dcc !important;
+ background-color: #006dcc !important;
}
.port-editor-sections .section-link-container.sortable-ghost > * {
- opacity: 0.3 !important;
+ opacity: 0.3 !important;
}
.port-editor-sections .section-link-container .handle + .portal-section-link {
- padding-left: 0;
+ padding-left: 0;
}
.port-editor-sections .section-link-container .handle i:nth-of-type(2) {
- margin-left: -3px;
+ margin-left: -3px;
}
.port-editor-sections .section-menu-link:hover ~ .menu,
-.port-editor-sections .section-link-container .menu:hover{
+.port-editor-sections .section-link-container .menu:hover {
display: block;
- margin-top: 0px;
- margin-left: -1px;
- box-shadow: inset 0 5px 7px -6px rgba(0,0,0,0.3);
- border-top: 1px solid #CCC;
+ margin-top: 0px;
+ margin-left: -1px;
+ box-shadow: inset 0 5px 7px -6px rgba(0, 0, 0, 0.3);
+ border-top: 1px solid #ccc;
}
-.port-editor-sections .portal-section-link{
+.port-editor-sections .portal-section-link {
display: inline-block;
}
-.port-editor-sections .section-link-container .portal-section-link[contenteditable=true]{
- box-shadow: 0 0 5px #00689d;
- padding: 5px 10px;
- margin: 5px 1px 3px 0px;
- border: 1px solid #00689d;
+.port-editor-sections
+ .section-link-container
+ .portal-section-link[contenteditable="true"] {
+ box-shadow: 0 0 5px #00689d;
+ padding: 5px 10px;
+ margin: 5px 1px 3px 0px;
+ border: 1px solid #00689d;
margin-left: 10px;
margin-right: 10px;
}
@@ -3879,174 +3892,184 @@ add font awesome close icon */
.port-editor-sections .section-link-container.active .portal-section-link,
.port-editor-sections .section-link-container.active .portal-section-link:hover,
.port-editor-sections .section-link-container.active .section-menu-link,
-.port-editor-sections .section-link-container.active .section-menu-link:hover{
+.port-editor-sections .section-link-container.active .section-menu-link:hover {
border-color: transparent;
}
.port-editor-sections .section-link-container .portal-section-link:hover,
-.port-editor-sections .section-link-container .section-menu-link:hover{
+.port-editor-sections .section-link-container .section-menu-link:hover {
background-color: transparent;
}
-.port-editor-sections .section-link-container .dropdown-menu{
+.port-editor-sections .section-link-container .dropdown-menu {
width: 100%;
padding: 0px;
}
-.section-link-container.error .portal-section-link{
+.section-link-container.error .portal-section-link {
color: #960303;
}
-#portal-section-tabs .section-link-container .dropdown-menu>li>a.remove-section.disabled{
- background: #f9f9f9;
- color: #c4c4c4;
+#portal-section-tabs
+ .section-link-container
+ .dropdown-menu
+ > li
+ > a.remove-section.disabled {
+ background: #f9f9f9;
+ color: #c4c4c4;
}
/* 'Add new section' icon */
.portal-section-link > .icon.icon-plus {
- margin: 0 1px 0 1px;
+ margin: 0 1px 0 1px;
}
/* `Settings` link */
-.nav .page-Settings{
+.nav .page-Settings {
float: right;
}
/* The Delete confirmation popover */
-.section-link-container .popover{
+.section-link-container .popover {
min-width: 300px;
}
-.section-link-container .popover .inline-buttons{
+.section-link-container .popover .inline-buttons {
margin-bottom: 0px;
}
-.section-link-container .popover .inline-buttons .btn{
+.section-link-container .popover .inline-buttons .btn {
width: calc(50% - 5px);
}
-.section-link-container .popover-title{
+.section-link-container .popover-title {
color: #960303;
}
/* Portal editor sections */
-.port-editor-section{
+.port-editor-section {
padding: 20px 40px;
}
-.port-editor-section.port-editor-md{
+.port-editor-section.port-editor-md {
padding: 0px;
}
.port-editor-description {
- max-width: 1400px;
- margin-left: 1rem;
+ max-width: 1400px;
+ margin-left: 1rem;
}
.port-editor-subtitle {
- margin-top: 0;
- margin-left: 1rem;
+ margin-top: 0;
+ margin-left: 1rem;
}
/* Portal Editor notifications */
.portal-editor .alert:empty {
- display: none;
+ display: none;
}
/* Portal Editor header (contains logo and title) */
.portal-editor #editor-header {
- display: flex;
- padding-bottom: 20px;
- flex-wrap: wrap;
+ display: flex;
+ padding-bottom: 20px;
+ flex-wrap: wrap;
width: 100%;
- align-items: center;
+ align-items: center;
}
.logo-editor-container {
- margin-right: 20px;
+ margin-right: 20px;
}
-.logo-editor-container .dropzone .dz-message{
- font-size: .87em;
+.logo-editor-container .dropzone .dz-message {
+ font-size: 0.87em;
line-height: 1em;
}
.logo-editor-container p.notification.error {
- width: 100px;
- text-align: center;
- line-height: 102%;
- padding-top: 5px;
- font-size: 0.9em;
+ width: 100px;
+ text-align: center;
+ line-height: 102%;
+ padding-top: 5px;
+ font-size: 0.9em;
}
-.logo-editor-container p.notification.error:empty{
+.logo-editor-container p.notification.error:empty {
display: none;
}
.portal-editor .logo-editor-container > h5,
-.portal-editor .title-container > h5{
+.portal-editor .title-container > h5 {
margin-top: 0px;
}
.portal-editor .title-container {
flex-grow: 2;
}
-.portal-editor textarea.portal-title{
- width: 100%;
+.portal-editor textarea.portal-title {
+ width: 100%;
padding: 0px;
background: transparent;
border: none;
border-radius: 0;
border-bottom: 1px solid #ccc;
box-shadow: none;
- align-self: center;
- font-size: 28px;
- line-height: 40px;
- min-height: 40px;
+ align-self: center;
+ font-size: 28px;
+ line-height: 40px;
+ min-height: 40px;
margin-top: 20px;
}
-.portal-editor textarea.portal-title:focus{
+.portal-editor textarea.portal-title:focus {
padding-left: 10px;
- -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);
- -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);
+ -webkit-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 8px rgba(82, 168, 236, 0.6);
+ -moz-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 8px rgba(82, 168, 236, 0.6);
+ box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 8px rgba(82, 168, 236, 0.6);
}
/* Portal Editor settings */
/* The width of each input element next to the remove button */
-.port-editor-settings-container textarea{
- width: calc(100% - 20px - 1em);
+.port-editor-settings-container textarea {
+ width: calc(100% - 20px - 1em);
}
.port-editor-logos {
- display: flex;
- width: 100%;
- overflow-x: auto;
+ display: flex;
+ width: 100%;
+ overflow-x: auto;
padding-top: 20px;
padding-bottom: 20px;
}
-.image-uploader .toggle-remove-preview{
+.image-uploader .toggle-remove-preview {
display: none;
}
-.image-uploader.remove-preview{
- opacity: .5;
+.image-uploader.remove-preview {
+ opacity: 0.5;
}
-.image-uploader .dropzone .remove-message{
+.image-uploader .dropzone .remove-message {
display: none;
}
-.image-uploader.remove-preview{
+.image-uploader.remove-preview {
opacity: 1;
}
-.image-uploader.remove-preview .dz-image-preview img{
- opacity: .3;
+.image-uploader.remove-preview .dz-image-preview img {
+ opacity: 0.3;
}
-.image-uploader.remove-preview .dropzone .dz-message{
+.image-uploader.remove-preview .dropzone .dz-message {
background-color: transparent;
}
-.image-uploader.remove-preview .dropzone .dz-message > *{
+.image-uploader.remove-preview .dropzone .dz-message > * {
display: none;
}
-.image-uploader.remove-preview .dropzone .remove-message{
+.image-uploader.remove-preview .dropzone .remove-message {
display: block;
font-weight: bold;
font-size: 1.25em;
margin-top: 20px;
color: #333;
}
-.image-uploader .dz-image-preview .remove{
+.image-uploader .dz-image-preview .remove {
top: 0px;
color: #fb5656;
font-size: 1.25em;
height: 2em;
- z-index: 5; /* should be ontop of all other dropzone elements */
+ z-index: 5; /* should be ontop of all other dropzone elements */
}
-.port-editor-logos .edit-image{
+.port-editor-logos .edit-image {
border: 1px solid #dedede;
margin-right: 10px;
padding: 10px;
@@ -4054,7 +4077,7 @@ add font awesome close icon */
position: relative;
background-color: #fafafa;
}
-.port-editor-logos .edit-image .dropzone{
+.port-editor-logos .edit-image .dropzone {
height: 150px;
width: 150px;
margin-left: auto;
@@ -4063,48 +4086,50 @@ add font awesome close icon */
overflow: hidden;
}
.port-editor-logos p.notification.error {
- text-align: center;
- margin: 0;
+ text-align: center;
+ margin: 0;
}
.port-editor-settings {
padding-left: 40px;
padding-right: 40px;
}
-.port-editor-settings .edit-image .remove{
+.port-editor-settings .edit-image .remove {
font-size: 1.5em;
display: block;
}
/* hide the remove message for logo images since we can remove the entire logo instead */
.port-editor-settings .logos-container .edit-image .dropzone .remove-message,
.port-editor-settings .logos-container .edit-image .dropzone .remove {
- display: none;
+ display: none;
}
-.port-editor-settings > .row-fluid{
+.port-editor-settings > .row-fluid {
padding: 20px 40px;
box-sizing: border-box;
}
-.port-editor-settings > .row-fluid:nth-child(odd){
- background-color: #EEE;
+.port-editor-settings > .row-fluid:nth-child(odd) {
+ background-color: #eee;
}
-.port-editor-settings .description-container textarea{
+.port-editor-settings .description-container textarea {
width: 100%;
}
.port-editor-section .editable-subsection {
- box-shadow: 0 1px 6px rgb(0 0 0 / 16%), 0 1px 8px -3px rgb(0 0 0 / 23%);
- padding: 2rem 1.5rem;
- border-radius: 3px;
- border: 2px dashed #419ecc;
- margin-top: 1rem;
- margin-bottom: 2rem;
+ box-shadow:
+ 0 1px 6px rgb(0 0 0 / 16%),
+ 0 1px 8px -3px rgb(0 0 0 / 23%);
+ padding: 2rem 1.5rem;
+ border-radius: 3px;
+ border: 2px dashed #419ecc;
+ margin-top: 1rem;
+ margin-bottom: 2rem;
}
-.port-editor-section.port-editor-data #sidebar > *{
+.port-editor-section.port-editor-data #sidebar > * {
padding-left: 0px;
padding-right: 0px;
box-sizing: border-box;
width: 100%;
}
-.port-editor-section.port-editor-data .result-header{
+.port-editor-section.port-editor-data .result-header {
padding-right: 0px;
box-sizing: border-box;
width: 100%;
@@ -4114,325 +4139,332 @@ add font awesome close icon */
/* custom portal search filter editor modal */
.filter-editor.new {
- display: flex;
- align-self: center;
- padding: 3rem 2rem 1rem 2rem;
+ display: flex;
+ align-self: center;
+ padding: 3rem 2rem 1rem 2rem;
}
-.filter-editor:not(.new){
- margin-bottom: 1rem;
+.filter-editor:not(.new) {
+ margin-bottom: 1rem;
}
.filter-editor .modal {
- width: 100%;
- max-width: 800px;
- margin: auto;
- left: 0;
- right: 0;
+ width: 100%;
+ max-width: 800px;
+ margin: auto;
+ left: 0;
+ right: 0;
}
.filter-editor .btn-filter-editor {
- margin: 0;
- padding: 4px 12px;
+ margin: 0;
+ padding: 4px 12px;
}
.filter-editor .delete-button {
- float: left;
+ float: left;
}
/* hide the delete button for new filters */
.filter-editor.new .delete-button {
- display: none;
+ display: none;
}
.filter-editor .modal-body {
- padding: 2rem;
- max-height: 600px;
+ padding: 2rem;
+ max-height: 600px;
}
.filter-editor .modal-instructions {
- font-weight: 500;
- font-size: 1.1rem;
- margin: 0;
- text-align: left;
+ font-weight: 500;
+ font-size: 1.1rem;
+ margin: 0;
+ text-align: left;
}
.filter-editor .fields-container {
- display: grid;
- grid-template-columns: max-content auto;
- gap: 2rem;
- align-items: center;
- margin-bottom: 2rem;
+ display: grid;
+ grid-template-columns: max-content auto;
+ gap: 2rem;
+ align-items: center;
+ margin-bottom: 2rem;
}
.filter-editor .fields-container .modal-instructions {
- margin-top: 1rem;
+ margin-top: 1rem;
}
/* the list of filter UI types to choose from */
.ui-builder-choices-container {
- display: grid;
- grid-template-columns: max-content auto;
- gap: 2rem;
- align-items: center;
- margin-bottom: 2rem;
+ display: grid;
+ grid-template-columns: max-content auto;
+ gap: 2rem;
+ align-items: center;
+ margin-bottom: 2rem;
}
.ui-builder-choices {
- margin-top: 1rem;
- margin-bottom: 1rem;
+ margin-top: 1rem;
+ margin-bottom: 1rem;
}
.ui-builder-choice {
- margin: 0 .6em;
- display: inline-block;
- cursor: pointer;
+ margin: 0 0.6em;
+ display: inline-block;
+ cursor: pointer;
}
/* the icon representing the interface type */
.ui-builder-choice svg {
- width: 3rem;
- height: 100%;
- max-height: 3.5rem;
- vertical-align: middle;
- fill: #b5c4ca;
+ width: 3rem;
+ height: 100%;
+ max-height: 3.5rem;
+ vertical-align: middle;
+ fill: #b5c4ca;
}
/* the label for the interface type */
.ui-builder-choice-label {
- color: #b5c4ca;
- text-transform: uppercase;
- font-weight: 500;
- font-size: 0.6rem;
- margin: 0;
+ color: #b5c4ca;
+ text-transform: uppercase;
+ font-weight: 500;
+ font-size: 0.6rem;
+ margin: 0;
}
.ui-builder-choice:hover svg {
- fill: #419ecc;
+ fill: #419ecc;
}
-.ui-builder-choice:hover .ui-builder-choice-label{
- color: #419ecc;
+.ui-builder-choice:hover .ui-builder-choice-label {
+ color: #419ecc;
}
.ui-builder-choice.selected svg {
- fill: #2c6f90;
+ fill: #2c6f90;
}
-.ui-builder-choice.selected .ui-builder-choice-label{
- color: #2c6f90;
+.ui-builder-choice.selected .ui-builder-choice-label {
+ color: #2c6f90;
}
.ui-builder-choice.disabled {
- cursor: not-allowed;
+ cursor: not-allowed;
}
.ui-builder-choice.disabled svg {
- fill: #EEF3F8;
+ fill: #eef3f8;
}
-.ui-builder-choice.disabled .ui-builder-choice-label{
- color: #EEF3F8;
+.ui-builder-choice.disabled .ui-builder-choice-label {
+ color: #eef3f8;
}
-
/* Modify the filter styles for editing */
.filter-group .filter.ui-build {
- margin: auto;
+ margin: auto;
}
.filter-group .filter.ui-build:not(.date) label {
- padding-right: 30px;
+ padding-right: 30px;
}
.filter.ui-build .icon-on-left {
- margin: 8px;
+ margin: 8px;
}
-.filter.ui-build input[type='text'],
+.filter.ui-build input[type="text"],
.filter.ui-build select {
- height: 44px;
- box-sizing: border-box;
+ height: 44px;
+ box-sizing: border-box;
}
.filter.ui-build .filter-input-contain {
- /* width: unset; */
- max-width: 350px;
- min-width: 300px;
+ /* width: unset; */
+ max-width: 350px;
+ min-width: 300px;
}
.ui-build.choice .ui-build-input.placeholder {
- max-width: 91%;
+ max-width: 91%;
}
.choices-editor .modal-instructions {
- margin: 2rem 0;
+ margin: 2rem 0;
}
.filter.ui-build .btn {
- height: 44px;
- width: 44px;
+ height: 44px;
+ width: 44px;
}
/* value input in filter editor, where user can select icon, label, placeholder, etc */
.ui-build .ui-build-input {
- display: block;
- font-size: 0.95rem;
- box-sizing: border-box;
- border: 1px dashed #419ecc;
- color: #555555;
- border-radius: 4px;
- line-height: 1;
- padding: 0.42rem;
- padding-left: 0.6rem;
- min-height: 1rem;
- min-width: 1rem;
- width: 100%;
- min-width: 280px;
- max-width: 320px;
- background-color: transparent;
- /* width: 100%; */
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- font-weight: var(--fw-bold,500);
- text-shadow: none;
- box-shadow: 0 3px 5px #77797a17,0 1px 3px #77797a17;
- transition: background-color .2s ease, box-shadow .2s ease;
-}
-.ui-build .ui-build-input::placeholder{
- text-shadow: none;
-}
-.ui-build .ui-build-input:focus{
- background-color: #d1e8f3;
- outline: none;
- box-shadow: 0 6px 13px #77797a24,0 2px 5px #77797a24;
-}
-.ui-build .ui-build-input:hover{
- /* background-color: #d1e8f3; */
- /* outline: none; */
- box-shadow: 0 6px 13px #77797a24,0 2px 5px #77797a24;
+ display: block;
+ font-size: 0.95rem;
+ box-sizing: border-box;
+ border: 1px dashed #419ecc;
+ color: #555555;
+ border-radius: 4px;
+ line-height: 1;
+ padding: 0.42rem;
+ padding-left: 0.6rem;
+ min-height: 1rem;
+ min-width: 1rem;
+ width: 100%;
+ min-width: 280px;
+ max-width: 320px;
+ background-color: transparent;
+ /* width: 100%; */
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-weight: var(--fw-bold, 500);
+ text-shadow: none;
+ box-shadow:
+ 0 3px 5px #77797a17,
+ 0 1px 3px #77797a17;
+ transition:
+ background-color 0.2s ease,
+ box-shadow 0.2s ease;
+}
+.ui-build .ui-build-input::placeholder {
+ text-shadow: none;
+}
+.ui-build .ui-build-input:focus {
+ background-color: #d1e8f3;
+ outline: none;
+ box-shadow:
+ 0 6px 13px #77797a24,
+ 0 2px 5px #77797a24;
+}
+.ui-build .ui-build-input:hover {
+ /* background-color: #d1e8f3; */
+ /* outline: none; */
+ box-shadow:
+ 0 6px 13px #77797a24,
+ 0 2px 5px #77797a24;
}
.ui-build input[disabled],
.ui-build button[disabled],
-.ui-build select[disabled]{
- cursor: default;
+.ui-build select[disabled] {
+ cursor: default;
}
.ui-builder-container-label {
- font-size: 0.8rem;
- margin: 0 0 3px 2px;
+ font-size: 0.8rem;
+ margin: 0 0 3px 2px;
}
/* Position the placeholder element over the input */
.ui-build .ui-build-input.placeholder {
- position: absolute;
- left: .5rem;
- top: 5px;
- min-width: 100px;
- max-width: 265px
+ position: absolute;
+ left: 0.5rem;
+ top: 5px;
+ min-width: 100px;
+ max-width: 265px;
}
/* Choice editor is where use selects a value and label for
elements */
.choice-editor {
- display: grid;
- grid-template-columns: [handle] 0.8rem [label] 1fr [value] 1fr [remove] 1.2rem;
- grid-gap: 0.4rem;
- margin-bottom: 0.3rem;
+ display: grid;
+ grid-template-columns: [handle] 0.8rem [label] 1fr [value] 1fr [remove] 1.2rem;
+ grid-gap: 0.4rem;
+ margin-bottom: 0.3rem;
}
.choice-editor .handle {
- grid-area: handle;
- height: 100%;
- display: flex;
- flex-direction: row;
- align-items: center;
- cursor: grab;
+ grid-area: handle;
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ cursor: grab;
}
.choice-editor.sortable-chosen,
.choice-editor.sortable-chosen .handle {
- cursor: grabbing;
+ cursor: grabbing;
}
.choice-editor .handle {
- color: #b5c4ca;
+ color: #b5c4ca;
}
.choice-editor .handle i:nth-of-type(2) {
- margin-left: 2px;
+ margin-left: 2px;
}
.choice-editor .ui-builder-container-text {
- width: 234px;
- margin-left: 3px;
- margin-bottom: 0;
- font-size: 0.8rem;
+ width: 234px;
+ margin-left: 3px;
+ margin-bottom: 0;
+ font-size: 0.8rem;
}
.choice-editor .choice-label {
- grid-area: label;
+ grid-area: label;
}
.choice-editor .choice-value {
- grid-area: value;
+ grid-area: value;
}
.choice-editor input {
- border-radius: 3px;
- color: rgba(0,0,0,.87);
- border: 1px solid rgba(34,36,38,.15);
- line-height: 1em;
- padding: .4rem .8rem;
+ border-radius: 3px;
+ color: rgba(0, 0, 0, 0.87);
+ border: 1px solid rgba(34, 36, 38, 0.15);
+ line-height: 1em;
+ padding: 0.4rem 0.8rem;
}
.choice-editor input.choice-label {
- font-weight: 560;
+ font-weight: 560;
}
.choice-editor input.choice-value {
- font-weight: 400;
+ font-weight: 400;
}
.choice-editor input:focus {
- border-color: #96c8da;
- outline: none;
+ border-color: #96c8da;
+ outline: none;
}
.choice-editor .remove-choice {
- grid-area: remove;
- font-size: 1.2rem;
- cursor: pointer;
- opacity: 0.2;
- display: grid;
- align-content: center;
- justify-self: flex-end;
+ grid-area: remove;
+ font-size: 1.2rem;
+ cursor: pointer;
+ opacity: 0.2;
+ display: grid;
+ align-content: center;
+ justify-self: flex-end;
}
/* In a date filter where a user selects a minRange and maxRange */
.filter-group .ui-build.filter.date,
.filter-group .ui-build.filter.choice {
- max-width: unset;
+ max-width: unset;
min-width: unset;
- padding-right: 0;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- align-items: center;
+ padding-right: 0;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
}
.ui-build .slider-container {
- display: grid;
- grid-template-columns: 2.5rem minmax(300px, 400px) 2.5rem;
- grid-template-rows: min-content min-content;
- justify-items: center;
- grid-gap: 1rem;
+ display: grid;
+ grid-template-columns: 2.5rem minmax(300px, 400px) 2.5rem;
+ grid-template-rows: min-content min-content;
+ justify-items: center;
+ grid-gap: 1rem;
}
.ui-build .slider-arrow {
- margin-top: 20px;
- fill: #419ecc;
- max-width: 40px;
+ margin-top: 20px;
+ fill: #419ecc;
+ max-width: 40px;
}
-.ui-build .year-slider{
- margin-right: 4px;
+.ui-build .year-slider {
+ margin-right: 4px;
}
.ui-build .range-inputs {
- width: 100%;
- grid-column: 1 / span 3;
- display: flex;
- justify-content: space-between;
+ width: 100%;
+ grid-column: 1 / span 3;
+ display: flex;
+ justify-content: space-between;
}
.ui-build .range-input {
- min-width: unset;
- width: 90px;
- min-height: 2.2rem;
+ min-width: unset;
+ width: 90px;
+ min-height: 2.2rem;
}
.ui-build .range-inputs > * {
- display: flex;
- flex-direction: column;
+ display: flex;
+ flex-direction: column;
}
.ui-build .range-inputs .notification.error {
@@ -4454,31 +4486,31 @@ add font awesome close icon */
/* toggle builder in UI build mode */
.toggle.ui-build .modal-instructions {
- margin: 2rem 0 1rem;
+ margin: 2rem 0 1rem;
}
.filter-editor .can-toggle-switch:before {
- opacity: 0
+ opacity: 0;
}
.filter-editor .can-toggle label .can-toggle-switch:after {
- color: white;
+ color: white;
}
.ui-build .ui-build-input.true-label,
-.ui-build .ui-build-input.false-label{
- font-size: 0.95em;
- box-sizing: border-box;
- position: absolute;
- top: 0;
- text-align: center;
- line-height: 1.9em;
- margin: 0.3em;
- width: calc(100px - 0.6em);
- padding: 0 12px;
- min-width: unset;
- max-width: unset;
+.ui-build .ui-build-input.false-label {
+ font-size: 0.95em;
+ box-sizing: border-box;
+ position: absolute;
+ top: 0;
+ text-align: center;
+ line-height: 1.9em;
+ margin: 0.3em;
+ width: calc(100px - 0.6em);
+ padding: 0 12px;
+ min-width: unset;
+ max-width: unset;
}
.ui-build .ui-build-input.true-label {
- left: 100px;
+ left: 100px;
}
/* Validation errors for Filters in UI Build mode */
@@ -4488,7 +4520,7 @@ add font awesome close icon */
background-color: #fff6f6;
}
-.ui-build .ui-build-input.error::placeholder{
+.ui-build .ui-build-input.error::placeholder {
color: #9f3a38;
}
@@ -4497,100 +4529,100 @@ add font awesome close icon */
*/
.port-editor-section.port-editor-md {
- float: left;
- position: relative;
- width: 100%
+ float: left;
+ position: relative;
+ width: 100%;
}
-.port-editor-section.port-editor-md .dropzone{
- width: calc(100% - 2px);
- margin:1px;
+.port-editor-section.port-editor-md .dropzone {
+ width: calc(100% - 2px);
+ margin: 1px;
}
-.port-editor-section.port-editor-md .dropzone .dz-message{
- flex-direction: column;
- justify-content: flex-end;
- padding-bottom: 0px;
+.port-editor-section.port-editor-md .dropzone .dz-message {
+ flex-direction: column;
+ justify-content: flex-end;
+ padding-bottom: 0px;
}
-.portal-editor .portal-display-image{
+.portal-editor .portal-display-image {
padding-top: 0px;
padding-bottom: 0px;
margin-top: -1px;
}
.portal-editor h2,
.portal-view h2 {
- font-size: 2em;
- font-weight: normal;
+ font-size: 2em;
+ font-weight: normal;
}
-.port-editor-md .portal-display-image .dropzone .icon-upload{
+.port-editor-md .portal-display-image .dropzone .icon-upload {
z-index: 1;
}
.portal-display-image .image-uploader .dz-image-preview .remove {
- font-size: 2em;
- margin-right: 10px;
- margin-top: 10px;
- color: red;
- z-index: 5; /* should be above all other elements in the dropzone area */
+ font-size: 2em;
+ margin-right: 10px;
+ margin-top: 10px;
+ color: red;
+ z-index: 5; /* should be above all other elements in the dropzone area */
}
/* for the cs-message: calc(100% - 1px) */
.Portal.Editor .portal-display-text {
- position: absolute;
- width: calc(100% - 6px); /* so it doesn't overlap the dropzone border */
- top: 0;
- box-sizing: border-box;
- left: 0;
- margin: 60px 0px 0px 3px;
+ position: absolute;
+ width: calc(100% - 6px); /* so it doesn't overlap the dropzone border */
+ top: 0;
+ box-sizing: border-box;
+ left: 0;
+ margin: 60px 0px 0px 3px;
padding: 20px 40px;
z-index: 10; /* so it's above all the dropzone stuff */
}
-.port-editor-md > *{
+.port-editor-md > * {
padding-left: 40px;
padding-right: 40px;
}
-.port-editor-md > .portal-display-image{
+.port-editor-md > .portal-display-image {
padding-left: 0px;
padding-right: 0px;
}
.port-editor-md .title,
.port-editor-md .introduction {
- width: 100%;
- padding: 0px;
- background: transparent;
- color: white;
- border: none;
- border-radius: 0;
- border-bottom: 1.5px solid white;
- box-shadow: none;
+ width: 100%;
+ padding: 0px;
+ background: transparent;
+ color: white;
+ border: none;
+ border-radius: 0;
+ border-bottom: 1.5px solid white;
+ box-shadow: none;
}
.port-editor-md .title {
- font-weight: 300;
- font-size: 31.5px;
- line-height: 44px;
- margin: 15px 0;
- min-height: 44px;
+ font-weight: 300;
+ font-size: 31.5px;
+ line-height: 44px;
+ margin: 15px 0;
+ min-height: 44px;
}
-.port-editor-md .introduction{
- font-weight: 400;
- font-size: 14px;
- line-height: 1.5em;
- margin: 0 0 10px;
- min-height: 1.6em;
+.port-editor-md .introduction {
+ font-weight: 400;
+ font-size: 14px;
+ line-height: 1.5em;
+ margin: 0 0 10px;
+ min-height: 1.6em;
}
.port-editor-md .title::placeholder,
.port-editor-md .introduction::placeholder {
- color: white;
+ color: white;
opacity: 0.4;
}
-.port-editor-md .markdown-help{
- display: flex;
- justify-content: space-around;
- align-items: stretch;
- align-content: stretch;
- flex-wrap: wrap;
+.port-editor-md .markdown-help {
+ display: flex;
+ justify-content: space-around;
+ align-items: stretch;
+ align-content: stretch;
+ flex-wrap: wrap;
}
.port-editor-md .markdown-help table {
@@ -4599,171 +4631,176 @@ add font awesome close icon */
}
.port-editor-md .markdown-help-column {
- box-shadow: 0 0px 4px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: 0 0px 4px 0 rgba(0, 0, 0, 0.15);
padding: 15px;
background-color: #fbfbfb;
- border-radius: 3px;
- margin: 10px
+ border-radius: 3px;
+ margin: 10px;
}
.port-editor-md .markdown-help-column td {
padding: 2px 1px;
- border-top: 1px solid #ececec;
+ border-top: 1px solid #ececec;
}
.port-editor-md .markdown-help-column th {
- padding-bottom: 9px;
- font-weight: 600;
+ padding-bottom: 9px;
+ font-weight: 600;
font-size: 13px;
}
.port-editor-md .markdown-help-column .more-help {
- font-weight: 600;
+ font-weight: 600;
font-size: 13px;
- margin: 5px 0 0 0;
- color: #555555;
-
+ margin: 5px 0 0 0;
+ color: #555555;
}
.port-editor-md .markdown-help-column code,
.port-editor-md .markdown-help-column pre {
- color: #999999;
- background-color: transparent;
+ color: #999999;
+ background-color: transparent;
border: none;
- padding: 0px;
- font-size: 12px;
- margin: 0px;
+ padding: 0px;
+ font-size: 12px;
+ margin: 0px;
}
-.port-editor-md .markdown-help-column h1{
- font-weight: 300;
- font-size: 21px;
- margin: 0;
+.port-editor-md .markdown-help-column h1 {
+ font-weight: 300;
+ font-size: 21px;
+ margin: 0;
}
-.port-editor-md .markdown-help-column h2{
- font-weight: 700;
- font-size: 16px;
- margin: 0;
+.port-editor-md .markdown-help-column h2 {
+ font-weight: 700;
+ font-size: 16px;
+ margin: 0;
}
-.port-editor-md .markdown-help-column blockquote{
- margin: 0;
+.port-editor-md .markdown-help-column blockquote {
+ margin: 0;
}
/*** Portal Editor Data Visualization Sections **/
-.port-editor-section.port-editor-viz{
+.port-editor-section.port-editor-viz {
padding-top: 40px;
}
/* Portal editor metrics page */
-.editor-view.portal-editor div#Metrics svg{
- width: 100%;
- height: 400px;
- margin-top: 40px;
+.editor-view.portal-editor div#Metrics svg {
+ width: 100%;
+ height: 400px;
+ margin-top: 40px;
}
/* Add section options */
-#section-options-container{
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- align-items: stretch;
+#section-options-container {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: stretch;
}
.section-option {
padding: 12px 13px;
background-color: white;
- border: 1px solid #DEDEDE;
+ border: 1px solid #dedede;
border-radius: 4px;
- cursor: pointer;
- width: 175px;
- margin: 15px;
- transition: box-shadow 250ms, transform 250ms;
+ cursor: pointer;
+ width: 175px;
+ margin: 15px;
+ transition:
+ box-shadow 250ms,
+ transform 250ms;
}
-.section-option:hover{
- box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 2px 5px rgba(0,0,0,0.06);
- transform: translate(0.2px,-0.2px);
- transition: box-shadow 200ms, transform 200ms;
+.section-option:hover {
+ box-shadow:
+ 0 1px 3px rgba(0, 0, 0, 0.04),
+ 0 2px 5px rgba(0, 0, 0, 0.06);
+ transform: translate(0.2px, -0.2px);
+ transition:
+ box-shadow 200ms,
+ transform 200ms;
}
-.section-option.disabled{
- background-color: #FAFAFA;
- cursor: not-allowed;
- color: #DCDCDC;
+.section-option.disabled {
+ background-color: #fafafa;
+ cursor: not-allowed;
+ color: #dcdcdc;
}
-.section-option.disabled:hover{
- box-shadow: none;
- transform: none;
- transition: none;
+.section-option.disabled:hover {
+ box-shadow: none;
+ transform: none;
+ transition: none;
}
-.section-option.disabled .subtle{
- color: #DCDCDC;
+.section-option.disabled .subtle {
+ color: #dcdcdc;
}
.section-option p.description {
- font-weight: 300;
- line-height: 1.25;
- margin-block-end: 0;
+ font-weight: 300;
+ line-height: 1.25;
+ margin-block-end: 0;
}
.section-option h5 {
- margin: 11px 0 6px 0;
+ margin: 11px 0 6px 0;
}
-.section-option svg{
- display: block;
- margin: auto;
+.section-option svg {
+ display: block;
+ margin: auto;
}
-.port-editor-section svg .theme-primary-fill{
- fill: #05446A
+.port-editor-section svg .theme-primary-fill {
+ fill: #05446a;
}
-.port-editor-section svg .theme-secondary-fill{
- fill: #118AD5
+.port-editor-section svg .theme-secondary-fill {
+ fill: #118ad5;
}
-.port-editor-section svg .theme-accent-fill{
- fill: #05ab8f
+.port-editor-section svg .theme-accent-fill {
+ fill: #05ab8f;
}
-.port-editor-section svg .skintone-fill{
- fill: #72351c
+.port-editor-section svg .skintone-fill {
+ fill: #72351c;
}
-.port-editor-section svg .skintone2-fill{
- fill: #9f7766
+.port-editor-section svg .skintone2-fill {
+ fill: #9f7766;
}
/* Disabled add section image fill color */
-.port-editor-section .disabled svg .theme-primary-fill{
- fill: #3d3d3d
+.port-editor-section .disabled svg .theme-primary-fill {
+ fill: #3d3d3d;
}
-.port-editor-section .disabled svg .theme-secondary-fill{
- fill: #ebebeb
+.port-editor-section .disabled svg .theme-secondary-fill {
+ fill: #ebebeb;
}
-.port-editor-section .disabled svg .theme-accent-fill{
- fill: #d6d6d6
+.port-editor-section .disabled svg .theme-accent-fill {
+ fill: #d6d6d6;
}
-.port-editor-section .disabled svg .skintone-fill{
- fill: #575757
+.port-editor-section .disabled svg .skintone-fill {
+ fill: #575757;
}
-.port-editor-section .disabled svg .skintone2-fill{
- fill: #828282
+.port-editor-section .disabled svg .skintone2-fill {
+ fill: #828282;
}
/******************************************
* Portals & Portal Editor (styles shared between both)
********************************************/
-#editPortal.btn{
+#editPortal.btn {
height: 2em;
-min-width: 180px;
-text-align: center;
-line-height: 2em;
-font-size: 1.1em;
+ min-width: 180px;
+ text-align: center;
+ line-height: 2em;
+ font-size: 1.1em;
}
-.portal-editor #editor-header{
+.portal-editor #editor-header {
border-bottom: 0px;
padding: 20px 40px;
box-sizing: border-box;
@@ -4774,7 +4811,7 @@ font-size: 1.1em;
/* Top level app navigation in the PortalView and EditorView */
.Portal.Editor #Navbar,
-.PortalView #Navbar{
+.PortalView #Navbar {
position: relative;
width: 100%;
left: 0px;
@@ -4782,12 +4819,12 @@ font-size: 1.1em;
}
.Portal.Editor .navbar,
-.PortalView .navbar{
+.PortalView .navbar {
margin: 0px;
}
.Portal.Editor .navbar-inner,
-.PortalView .navbar-inner{
+.PortalView .navbar-inner {
background-image: none;
box-shadow: none;
background-color: #f2f2f2;
@@ -4797,7 +4834,7 @@ font-size: 1.1em;
}
.Portal.Editor .navbar .nav > li > a.brand,
-.PortalView .navbar .nav > li > a.brand{
+.PortalView .navbar .nav > li > a.brand {
font-size: 25px;
letter-spacing: -2px;
}
@@ -4805,33 +4842,32 @@ font-size: 1.1em;
.Portal.Editor .navbar .nav > li > a,
.Portal.Editor .nav .input > form > label,
.PortalView .navbar .nav > li > a,
-.PortalView .nav .input > form > label{
+.PortalView .nav .input > form > label {
font-size: 12px;
}
-
.Portal.Editor #Navbar a.brand,
.Portal.Editor #Navbar a.brand {
- margin-left: 20px;
+ margin-left: 20px;
}
.Portal.Editor #Navbar .minimal-nav > a,
.Portal.Editor #Navbar .minimal-nav > .icon,
.Portal.Editor #Navbar .minimal-nav > a > .icon,
-.Portal.Editor #Navbar .minimal-nav > a,
+.Portal.Editor #Navbar .minimal-nav > a,
.PortalView #Navbar .minimal-nav > .icon,
-.PortalView #Navbar .minimal-nav > a > .icon{
- color: inherit;
+.PortalView #Navbar .minimal-nav > a > .icon {
+ color: inherit;
}
.Portal.Editor #Navbar .nav > li,
.PortalView #Navbar .nav > li {
- display: none;
+ display: none;
}
.Portal.Editor #Navbar .nav .minimal-nav,
.PortalView #Navbar .nav .minimal-nav {
- display: block;
+ display: block;
}
.Portal.Editor #Navbar .nav .minimal-nav:not(:last-child),
.PortalView #Navbar .nav .minimal-nav:not(:last-child) {
@@ -4839,9 +4875,9 @@ font-size: 1.1em;
}
.Portal.Editor #Navbar .nav .minimal-nav a,
.PortalView #Navbar .nav .minimal-nav a {
- font-size: .9em;
- text-transform: none;
- text-shadow: none;
+ font-size: 0.9em;
+ text-transform: none;
+ text-shadow: none;
}
.Portal.Editor #Navbar .nav .minimal-nav > a:not(.btn),
.PortalView #Navbar .nav .minimal-nav > a:not(.btn) {
@@ -4850,18 +4886,18 @@ font-size: 1.1em;
}
.Portal.Editor #Navbar .nav .minimal-nav.subtle a,
.PortalView #Navbar .nav .minimal-nav.subtle a {
- color: #999;
+ color: #999;
}
.Portal.Editor #Navbar .nav,
.PortalView #Navbar .nav {
- margin-top: 0px;
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
+ margin-top: 0px;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
}
-.PortalView #Navbar #editPortal{
+.PortalView #Navbar #editPortal {
margin-top: 0px;
margin-bottom: 0px;
padding: 7px;
@@ -4872,10 +4908,10 @@ font-size: 1.1em;
/* The TOC is used optionally in both the markdown view and the preview portion
of the markdown editor view. Markdown & markdown editor views are used in the
portal editor and portal views. */
-.toc-view.affix{
+.toc-view.affix {
top: 20px;
}
-.toc-view.affix-bottom{
+.toc-view.affix-bottom {
top: 20px;
position: fixed;
}
@@ -4883,52 +4919,52 @@ font-size: 1.1em;
.toc-view.span3.affix-bottom + .span9 {
margin-left: 26.5%;
}
-.toc-ul{
- position: sticky;
- position: -webkit-sticky;
+.toc-ul {
+ position: sticky;
+ position: -webkit-sticky;
padding: 0px;
}
.toc-ul > li > a {
- text-shadow: none;
+ text-shadow: none;
padding-left: 20px;
padding-right: 20px;
margin: 0px;
}
-.toc-ul a{
+.toc-ul a {
line-height: 2em;
}
-.toc-ul > li > ul{
+.toc-ul > li > ul {
margin-left: 0px;
list-style: none;
}
-.toc-ul > li > ul > li > a{
+.toc-ul > li > ul > li > a {
padding-left: 40px;
}
.toc-ul li,
.toc-ul a {
background-color: transparent;
- color: #FFF;
- border:0px;
+ color: #fff;
+ border: 0px;
border-radius: 0px;
text-shadow: none;
}
.toc-ul a:hover {
- color: #FFF;
+ color: #fff;
background-color: transparent;
}
-.toc .icon{
- color: #069;
+.toc .icon {
+ color: #069;
}
i.toc-sub-item {
- margin-left: 18px;
- visibility: hidden;
+ margin-left: 18px;
+ visibility: hidden;
}
/* hide mobile TOC by default */
.toc .mobile {
- display: none;
+ display: none;
}
-.submenu.hidden{
- display: none;
+.submenu.hidden {
+ display: none;
}
/* end of markdown section table of contents */
@@ -4941,19 +4977,19 @@ i.toc-sub-item {
grid-template-rows: 40px 0px 100fr 0px;
}
-.portal-view{
+.portal-view {
width: calc(100% + 80px);
margin-left: -40px;
}
.portal-view #sidebar {
display: none;
}
-.PortalView #Content{
+.PortalView #Content {
padding-top: 0px;
}
-#portal-header-container{
- margin-top: 30px;
+#portal-header-container {
+ margin-top: 30px;
margin-bottom: 30px;
padding-left: 40px;
box-sizing: border-box;
@@ -4961,31 +4997,31 @@ i.toc-sub-item {
#portal-header-container > .row {
display: flex;
- flex-direction: row;
- flex-wrap: nowrap;
- align-items: center;
- width: 100%;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ align-items: center;
+ width: 100%;
}
-#portal-section-tabs{
+#portal-section-tabs {
margin-bottom: 0px;
}
#portal-section-tabs .portal-section-link {
background-color: transparent;
border-radius: 0px;
text-align: center;
- box-sizing: border-box;
+ box-sizing: border-box;
}
-.section-link-container{
+.section-link-container {
position: relative;
}
-.portal-section-content{
+.portal-section-content {
padding: 20px;
}
.portal-section-content .toc-ul {
- margin-left: -20px;
+ margin-left: -20px;
}
-.portal-display-image{
+.portal-display-image {
background-repeat: no-repeat;
background-position-x: 50%;
background-position-y: 50%;
@@ -4993,85 +5029,85 @@ i.toc-sub-item {
min-height: 200px;
padding-top: 60px;
padding-bottom: 60px;
- color: #FFF;
+ color: #fff;
margin-bottom: 40px;
}
-.portal-display-text{
+.portal-display-text {
box-sizing: border-box;
padding: 20px;
}
.portal-display-image .portal-display-text,
-.portal-editor .portal-display-text{
- background-color: rgba(44, 111, 144, .5);
+.portal-editor .portal-display-text {
+ background-color: rgba(44, 111, 144, 0.5);
background-color: var(--portal-primary-color-transparent);
}
.portal-display-image .portal-display-text *,
-.portal-editor .portal-display-text *{
- color: #FFF;
+.portal-editor .portal-display-text * {
+ color: #fff;
}
-.portal-editor .portal-display-text .notification.error{
+.portal-editor .portal-display-text .notification.error {
color: #a70704;
margin-bottom: 0px;
}
-.portal-editor .portal-display-text textarea{
+.portal-editor .portal-display-text textarea {
padding-left: 10px;
}
-.logo-container{
- height: 100px;
+.logo-container {
+ height: 100px;
white-space: nowrap;
width: 100%;
display: table;
- padding-top:10px;
+ padding-top: 10px;
}
-.logo-image{
- vertical-align: middle;
+.logo-image {
+ vertical-align: middle;
padding: 10px;
display: table-cell;
}
-.portal-view .portal-logo{
- max-height: 80px;
+.portal-view .portal-logo {
+ max-height: 80px;
display: inline-block;
margin-right: 30px;
}
-.portal-view #results-container{
- width: 100%;
- margin-left:0px;
+.portal-view #results-container {
+ width: 100%;
+ margin-left: 0px;
}
.portal-view .mapMode #results-container,
-.portal-view .mapMode #map-container{
- width: 50%;
+.portal-view .mapMode #map-container {
+ width: 50%;
}
.portal-view .data-catalog,
.portal-view .data-catalog #results-container,
.portal-view .data-catalog #map-container,
-.portal-view .data-catalog #map-canvas{
+.portal-view .data-catalog #map-canvas {
min-height: 700px;
}
-.portal-view .data-catalog #clear-all{
- display: none;
+.portal-view .data-catalog #clear-all {
+ display: none;
}
-.portal-view .portal-title{
+.portal-view .portal-title {
line-height: 1.5em;
margin: 0px;
font-size: 2em;
display: inline-block;
flex: 2;
}
-.portal-view .portal-description{
- margin-top: 10px;
- margin-right: 20px;
+.portal-view .portal-description {
+ margin-top: 10px;
+ margin-right: 20px;
color: #666;
}
.portal-view .portal-member-since {
- margin-left: -20px;
- margin-top: 10px;
+ margin-left: -20px;
+ margin-top: 10px;
}
-.portal-logos-view{
+.portal-logos-view {
padding-top: 6em;
padding-left: 20px;
padding-right: 20px;
}
-.portal-logos-view .img-logo{
+.portal-logos-view .img-logo {
margin: auto;
display: flex;
justify-content: center;
@@ -5081,47 +5117,47 @@ i.toc-sub-item {
max-height: 125px;
}
.well.awards-info {
- background: inherit;
- margin: 20px;
+ background: inherit;
+ margin: 20px;
}
.portal-view .portal-section-link {
width: 100%;
}
-.portal-view .logo-row{
- margin-bottom: 40px;
+.portal-view .logo-row {
+ margin-bottom: 40px;
display: flex;
justify-content: center;
align-items: center;
}
.portal-view .profile .profile-title,
-.portal-view .profile .charts-container > p{
+.portal-view .profile .charts-container > p {
padding-left: 20px;
}
-.portal-view .profile .profile-title{
+.portal-view .profile .profile-title {
font-size: 1.5em;
}
.profile h4 {
- margin-top: 2rem;
+ margin-top: 2rem;
}
-.portal-view #Members .row-fluid{
+.portal-view #Members .row-fluid {
padding: 20px;
box-sizing: border-box;
}
.portal-view #Members .row-fluid:nth-child(odd) {
- background-color: #EEE;
+ background-color: #eee;
}
/*** Colors used in the TOC in the portal view & portal editor view ***/
.portal-view .toc .icon,
-.portal-editor .toc .icon{
+.portal-editor .toc .icon {
color: #2c6f90; /* Back-up color for IE */
- color: var(--portal-primary-color);
+ color: var(--portal-primary-color);
}
.portal-view .toc li.active > a,
.portal-editor .toc li.active > a {
background-color: #2c7e90; /* Back-up color for IE */
- background-color: var(--portal-secondary-color);
+ background-color: var(--portal-secondary-color);
}
.portal-editor .toc-ul,
.portal-view .toc-ul {
@@ -5129,38 +5165,38 @@ i.toc-sub-item {
background-color: var(--portal-primary-color);
}
.portal-view .toc-ul li > a:hover,
-.portal-editor .toc-ul li > a:hover{
+.portal-editor .toc-ul li > a:hover {
background-color: #2c7e90; /* Back-up color for IE */
background-color: var(--portal-secondary-color);
- color: white;
+ color: white;
}
.portal-editor .toc-view a,
.portal-view .toc-view a {
- color: #FFF;
+ color: #fff;
}
/*** Colors used in the Portal View ***/
.portal-view a:not(.btn):hover,
-.portal-editor a:not(.btn):hover{
+.portal-editor a:not(.btn):hover {
color: #2c7e90; /* Back-up color for IE */
- color: var(--portal-secondary-color);
+ color: var(--portal-secondary-color);
}
-#portal-section-tabs{
+#portal-section-tabs {
display: flex;
flex-wrap: wrap;
align-items: stretch;
border-bottom: 0px;
}
-.portal-editor #portal-section-tabs{
+.portal-editor #portal-section-tabs {
display: block;
- border-bottom: 1px solid #CCC;
- max-width: 100%;
- box-sizing: border-box;
+ border-bottom: 1px solid #ccc;
+ max-width: 100%;
+ box-sizing: border-box;
}
.portal-editor #portal-section-tabs,
.portal-editor #portal-section-tabs li:not(.active) {
- box-shadow: inset 0 -10px 10px -10px rgba(0,0,0,0.05);
+ box-shadow: inset 0 -10px 10px -10px rgba(0, 0, 0, 0.05);
}
/* hide black border below active tabs */
@@ -5174,7 +5210,7 @@ i.toc-sub-item {
left: 0;
}
-#portal-section-tabs .section-link-container{
+#portal-section-tabs .section-link-container {
background-color: #2c6f90; /* Back-up color for IE */
background-color: var(--portal-primary-color);
border: 0px;
@@ -5184,130 +5220,149 @@ i.toc-sub-item {
/* show toggle for showing/hiding section links on mobile only */
.Editor.Portal .show-sections-toggle,
-.PortalView .show-sections-toggle{
- visibility: hidden;
- display: none;
+.PortalView .show-sections-toggle {
+ visibility: hidden;
+ display: none;
}
.portal-editor #portal-section-tabs * {
- transition: none;
+ transition: none;
}
-.portal-editor #portal-section-tabs .section-link-container{
+.portal-editor #portal-section-tabs .section-link-container {
border-width: 1px 1px 0px 1px;
- border-color: #FFF;
+ border-color: #fff;
border-style: solid;
border-top-right-radius: 4px;
border-top-left-radius: 4px;
- height: 36px;
+ height: 36px;
}
-.portal-editor #portal-section-tabs .section-link-container.hidden-section{
+.portal-editor #portal-section-tabs .section-link-container.hidden-section {
background-color: #999;
border-color: #666;
}
-.portal-editor #portal-section-tabs .section-link-container.hidden-section .hidden-section-icon{
+.portal-editor
+ #portal-section-tabs
+ .section-link-container.hidden-section
+ .hidden-section-icon {
display: inline;
}
-.portal-editor #portal-section-tabs .section-link-container.active{
- background-color: #FFF;
- border-color: #CCC;
+.portal-editor #portal-section-tabs .section-link-container.active {
+ background-color: #fff;
+ border-color: #ccc;
}
-#portal-section-tabs .section-link-container.error{
+#portal-section-tabs .section-link-container.error {
border-color: #b94a48;
background-color: #f2dede;
}
-.portal-editor #portal-section-tabs .section-link-container.error .portal-section-link{
+.portal-editor
+ #portal-section-tabs
+ .section-link-container.error
+ .portal-section-link {
color: #b94a48;
}
.portal-view #portal-section-tabs .section-link-container .portal-section-link,
-.portal-view #portal-section-tabs .section-link-container.active .portal-section-link{
- color: #FFF;
+.portal-view
+ #portal-section-tabs
+ .section-link-container.active
+ .portal-section-link {
+ color: #fff;
}
#portal-section-tabs .portal-section-link,
-#portal-section-tabs .section-menu-link{
- color: #FFF;
+#portal-section-tabs .section-menu-link {
+ color: #fff;
background-color: transparent;
margin-right: 0px;
float: left;
border: 0px;
cursor: pointer;
}
-#portal-section-tabs .section-menu-link{
+#portal-section-tabs .section-menu-link {
display: inline-block;
- padding: 8px 10px;
- box-sizing: border-box;
- max-width: 44px;
+ padding: 8px 10px;
+ box-sizing: border-box;
+ max-width: 44px;
}
-#portal-section-tabs .portal-section-link[contenteditable="true"]{
+#portal-section-tabs .portal-section-link[contenteditable="true"] {
width: calc(100% - 48px);
}
-#portal-section-tabs .section-link-container.page-AddPage{
+#portal-section-tabs .section-link-container.page-AddPage {
flex-grow: unset;
margin-right: 40px;
}
-#portal-section-tabs .section-link-container.page-AddPage > a{
- height: 36px;
+#portal-section-tabs .section-link-container.page-AddPage > a {
+ height: 36px;
}
-#portal-section-tabs .section-link-container.page-AddPage .icon{
+#portal-section-tabs .section-link-container.page-AddPage .icon {
font-size: 1.75em;
}
-#portal-section-tabs .section-link-container.page-AddPage .portal-section-link{
- color: #FFF;
+#portal-section-tabs .section-link-container.page-AddPage .portal-section-link {
+ color: #fff;
width: 100%;
}
#portal-section-tabs .section-link-container:hover .portal-section-link,
#portal-section-tabs .section-link-container.active:hover .section-menu-link,
#portal-section-tabs .section-link-container:hover .section-menu-link,
#portal-section-tabs .section-link-container:hover .handle {
- border-radius: 0;
- color: #FFF !important;
+ border-radius: 0;
+ color: #fff !important;
}
#portal-section-tabs .section-link-container .popover-content p {
- color: #333;
- margin: 10px 0px 0px 0px;
+ color: #333;
+ margin: 10px 0px 0px 0px;
}
-#portal-section-tabs .section-link-container.page-Settings{
+#portal-section-tabs .section-link-container.page-Settings {
flex-grow: unset;
}
-#portal-section-tabs .section-link-container.page-Settings.error{
+#portal-section-tabs .section-link-container.page-Settings.error {
width: 120px;
}
-#portal-section-tabs .section-link-container.page-Settings.error .portal-section-link{
+#portal-section-tabs
+ .section-link-container.page-Settings.error
+ .portal-section-link {
color: #b94a48;
}
-#portal-section-tabs .section-link-container .dropdown-menu>li>a{
+#portal-section-tabs .section-link-container .dropdown-menu > li > a {
color: #2c6f90; /* Back-up color for IE */
- border-bottom: 1px solid #CCC;
+ border-bottom: 1px solid #ccc;
line-height: 25px;
}
-#portal-section-tabs .section-link-container .dropdown-menu>li:last-child>a{
+#portal-section-tabs
+ .section-link-container
+ .dropdown-menu
+ > li:last-child
+ > a {
border-bottom-width: 0px;
border-bottom-left-radius: 6px;
border-botttom-right-radius: 6px;
}
-#portal-section-tabs .section-link-container .dropdown-menu>li>a:hover{
+#portal-section-tabs .section-link-container .dropdown-menu > li > a:hover {
background-image: none;
- background-color: #DDD;
+ background-color: #ddd;
}
/** --------------------------Portal colors -----------------------------***/
-.portal-view #portal-section-tabs .section-link-container.active{
+.portal-view #portal-section-tabs .section-link-container.active {
background-color: #2c7e90; /* Back-up color for IE */
background-color: var(--portal-secondary-color);
}
-#portal-section-tabs .active .section-menu-link{
+#portal-section-tabs .active .section-menu-link {
color: #2c6f90; /* Back-up color for IE */
color: var(--portal-primary-color);
}
-#portal-section-tabs .section-link-container.page-AddPage .portal-section-link:hover,
-#portal-section-tabs .section-link-container.page-Settings .portal-section-link:hover{
+#portal-section-tabs
+ .section-link-container.page-AddPage
+ .portal-section-link:hover,
+#portal-section-tabs
+ .section-link-container.page-Settings
+ .portal-section-link:hover {
background-color: transparent;
color: #2c7e90; /* Back-up color for IE */
color: var(--portal-secondary-color);
}
#portal-section-tabs .section-link-container.active .portal-section-link,
-#portal-section-tabs .section-link-container.active .handle {
+#portal-section-tabs .section-link-container.active .handle {
color: #2c6f90; /* Back-up color for IE */
color: var(--portal-primary-color);
}
@@ -5316,109 +5371,135 @@ i.toc-sub-item {
background-color: #2c7e90; /* Back-up color for IE */
background-color: var(--portal-secondary-color);
}
-#portal-section-tabs .section-link-container .dropdown-menu>li>a{
+#portal-section-tabs .section-link-container .dropdown-menu > li > a {
color: var(--portal-primary-color);
}
-.portal-editor #portal-section-tabs .section-link-container.active:hover::after {
+.portal-editor
+ #portal-section-tabs
+ .section-link-container.active:hover::after {
border-bottom: 1px solid #2c7e90; /* Back-up color for IE */
- border-bottom: 1px solid var(--portal-secondary-color);
+ border-bottom: 1px solid var(--portal-secondary-color);
}
.portal-view .result-row .citation .title,
-.portal-editor .result-row .citation .title{
+.portal-editor .result-row .citation .title {
color: #2c6f90; /* Back-up color for IE */
- color: var(--portal-primary-color);
+ color: var(--portal-primary-color);
}
.portal-view .pagination ul > li > a:hover,
-.portal-editor .pagination ul > li > a:hover{
+.portal-editor .pagination ul > li > a:hover {
background-color: var(--portal-primary-color);
- color: #FFF;
+ color: #fff;
}
.portal-view .applied-filter,
-.portal-editor .applied-filter{
+.portal-editor .applied-filter {
background-color: #2c6f90; /* Back-up color for IE */
- background-color: var(--portal-primary-color);
+ background-color: var(--portal-primary-color);
}
.portal-view .filter-group-link a,
-.portal-editor .filter-group-link a{
+.portal-editor .filter-group-link a {
color: #2c6f90; /* Back-up color for IE */
- color: var(--portal-primary-color);
+ color: var(--portal-primary-color);
}
.portal-view .filter-group-link a:hover,
-.portal-editor .filter-group-link a:hover{
+.portal-editor .filter-group-link a:hover {
color: #2c7e90; /* Back-up color for IE */
- color: var(--portal-secondary-color);
+ color: var(--portal-secondary-color);
}
.portal-view .filter-group-link.active a,
.portal-view .filter-group-link.active a:hover,
.portal-editor .filter-group-link.active a,
-.portal-editor .filter-group-link.active a:hover{
- color: inherit;
+.portal-editor .filter-group-link.active a:hover {
+ color: inherit;
}
-.portal-view .can-toggle input[type="checkbox"]:checked ~ label .can-toggle-switch,
-.portal-view .can-toggle input[type="checkbox"]:checked:focus ~ label .can-toggle-switch,
-.portal-editor .can-toggle input[type="checkbox"]:checked ~ label .can-toggle-switch,
-.portal-editor .can-toggle input[type="checkbox"]:checked:focus ~ label .can-toggle-switch{
+.portal-view
+ .can-toggle
+ input[type="checkbox"]:checked
+ ~ label
+ .can-toggle-switch,
+.portal-view
+ .can-toggle
+ input[type="checkbox"]:checked:focus
+ ~ label
+ .can-toggle-switch,
+.portal-editor
+ .can-toggle
+ input[type="checkbox"]:checked
+ ~ label
+ .can-toggle-switch,
+.portal-editor
+ .can-toggle
+ input[type="checkbox"]:checked:focus
+ ~ label
+ .can-toggle-switch {
background-color: #2c6f90; /* Back-up color for IE */
- background-color: var(--portal-primary-color);
+ background-color: var(--portal-primary-color);
}
-.portal-view .can-toggle input[type="checkbox"]:checked:hover ~ label .can-toggle-switch,
-.portal-editor .can-toggle input[type="checkbox"]:checked:hover ~ label .can-toggle-switch{
+.portal-view
+ .can-toggle
+ input[type="checkbox"]:checked:hover
+ ~ label
+ .can-toggle-switch,
+.portal-editor
+ .can-toggle
+ input[type="checkbox"]:checked:hover
+ ~ label
+ .can-toggle-switch {
background-color: #2c7e90; /* Back-up color for IE */
- background-color: var(--portal-secondary-color);
+ background-color: var(--portal-secondary-color);
}
.portal-view .ui-slider-range,
-.portal-editor .ui-slider-range{
+.portal-editor .ui-slider-range {
background-color: #2c6f90; /* Back-up color for IE */
- background-color: var(--portal-primary-color);
+ background-color: var(--portal-primary-color);
}
.portal-view .result-row .citation a:hover,
.portal-view .result-row .citation a:hover .title,
.portal-view .result-row .citation a:hover .id,
.portal-editor .result-row .citation a:hover,
.portal-editor .result-row .citation a:hover .title,
-.portal-editor .result-row .citation a:hover .id{
+.portal-editor .result-row .citation a:hover .id {
color: #2c7e90; /* Back-up color for IE */
- color: var(--portal-secondary-color);
+ color: var(--portal-secondary-color);
}
.portal-view .result-row .icon,
-.portal-editor .result-row .icon{
- color: #666;
+.portal-editor .result-row .icon {
+ color: #666;
}
.portal-view .profile .quick-stats circle,
-.portal-editor .profile .quick-stats circle{
+.portal-editor .profile .quick-stats circle {
stroke: #2c6f90; /* Back-up color for IE */
- stroke: var(--portal-primary-color);
+ stroke: var(--portal-primary-color);
}
.portal-view svg .packages,
.portal-view .chart-title .packages,
.portal-editor svg .packages,
-.portal-editor .chart-title .packages{
+.portal-editor .chart-title .packages {
color: #2c6f90; /* Back-up color for IE */
fill: #2c6f90; /* Back-up color for IE */
- stroke: #2c6f90; /* Back-up color for IE */
- color: var(--portal-primary-color);
- fill: var(--portal-primary-color);
- stroke: var(--portal-primary-color);
+ stroke: #2c6f90; /* Back-up color for IE */
+ color: var(--portal-primary-color);
+ fill: var(--portal-primary-color);
+ stroke: var(--portal-primary-color);
}
.portal-view svg circle.packages,
-.portal-editor svg circle.packages{
- fill: none;
+.portal-editor svg circle.packages {
+ fill: none;
}
.portal-view svg .metadata,
.portal-view .fallback.metadata,
.portal-editor svg .metadata,
-.portal-editor .fallback.metadata{
+.portal-editor .fallback.metadata {
color: #2c7e90; /* Back-up color for IE */
fill: #2c7e90; /* Back-up color for IE */
stroke: #2c7e90; /* Back-up color for IE */
- color: var(--portal-secondary-color);
- fill: var(--portal-secondary-color);
- stroke: var(--portal-secondary-color);
+ color: var(--portal-secondary-color);
+ fill: var(--portal-secondary-color);
+ stroke: var(--portal-secondary-color);
}
.portal-view svg .data,
.portal-view .fallback.data,
.portal-editor svg .data,
-.portal-editor .fallback.data{
+.portal-editor .fallback.data {
color: var(--portal-accent-color);
fill: var(--portal-accent-color);
stroke: var(--portal-accent-color);
@@ -5435,27 +5516,27 @@ i.toc-sub-item {
.portal-view .downloads-metrics .metric-chart .scale_button:hover rect,
.portal-view .downloads-metrics .metric-chart .bar,
.portal-view .downloads-metrics .metric-chart .bar_context {
- stroke: var(--portal-primary-color);
- fill: var(--portal-primary-color);
+ stroke: var(--portal-primary-color);
+ fill: var(--portal-primary-color);
}
.portal-view .donut-title-count.data,
.portal-view .donut-title-count.metadata,
.portal-view .donut-title-text,
-.portal-view .donut-title-text{
+.portal-view .donut-title-text {
fill: #555;
}
.portal-view .line-chart .line,
-.portal-editor .line-chart .line{
- fill: none;
+.portal-editor .line-chart .line {
+ fill: none;
}
.portal-view .line-chart text.line-chart-label,
-.portal-editor .line-chart text.line-chart-label{
- fill: #FFF;
+.portal-editor .line-chart text.line-chart-label {
+ fill: #fff;
}
.portal-view #portal-members a,
-.portal-editor #portal-members a{
+.portal-editor #portal-members a {
color: #2c6f90; /* Back-up color for IE */
- color: var(--portal-primary-color);
+ color: var(--portal-primary-color);
}
.portal-section-content thead tr {
background-color: var(--portal-primary-color);
@@ -5488,14 +5569,14 @@ i.toc-sub-item {
}
.my-portals-container table td {
- vertical-align: middle;
+ vertical-align: middle;
}
.portal-list-container .logo img {
- min-width: 70px;
- width: 70px;
- height: 70px;
- object-fit: contain;
+ min-width: 70px;
+ width: 70px;
+ height: 70px;
+ object-fit: contain;
}
.portal-list-container .loading {
height: 50px;
@@ -5503,42 +5584,41 @@ i.toc-sub-item {
}
.my-portals-container table td.title > a {
- font-size: 15px;
- line-height: 0.9;
- color: #3a3a3a;
+ font-size: 15px;
+ line-height: 0.9;
+ color: #3a3a3a;
}
.my-portals-container table td.title > a:hover {
- color: #660000;
+ color: #660000;
}
.my-portals-container table td.portal-label {
- color: #a4a4a4;
+ color: #a4a4a4;
}
-.create-btn-container .btn{
+.create-btn-container .btn {
float: right;
margin-bottom: 20px;
}
/** The Portal Visualization View **/
-.portal-viz-section-view{
+.portal-viz-section-view {
background-color: black;
height: 100%;
}
-.portal-viz-section-view iframe{
+.portal-viz-section-view iframe {
box-sizing: border-box;
border: 0px;
}
-
/******************************************
* HTML converted from Markdown
********************************************/
/* images in markdown */
.markdown img.thumbnail {
- display: block;
+ display: block;
}
/* the following two rules overwrite both:
@@ -5546,146 +5626,145 @@ i.toc-sub-item {
ii. individual theme `pre` rules
*/
div.markdown pre {
- color: #383a42;
- background: #fafafa;
- border-radius: 3px;
+ color: #383a42;
+ background: #fafafa;
+ border-radius: 3px;
}
/* markdown citations */
.markdown .inlineCitation {
-
}
.markdown .csl-entry {
- text-indent: -15px;
- margin-left: 15px;
- margin-bottom: 10px;
+ text-indent: -15px;
+ margin-left: 15px;
+ margin-bottom: 10px;
}
.markdown #bibliography {
- margin-top: 40px;
- margin-bottom: 20px;
+ margin-top: 40px;
+ margin-bottom: 20px;
}
/******************************************
* Download / Resource Map contents table
********************************************/
-#downloadContents .controls-well{
- overflow-x: scroll;
- -webkit-transform: translate3d(0,0,0);
+#downloadContents .controls-well {
+ overflow-x: scroll;
+ -webkit-transform: translate3d(0, 0, 0);
}
-.download-contents{
- float: none;
- clear: both;
+.download-contents {
+ float: none;
+ clear: both;
}
.download-contents ~ .download-contents {
- margin-top: 20px;
+ margin-top: 20px;
}
table.download-contents {
- width: 100%;
- margin-bottom: 0px;
+ width: 100%;
+ margin-bottom: 0px;
}
-table.download-contents.nested{
- margin-left: 5%;
- width: 95%;
+table.download-contents.nested {
+ margin-left: 5%;
+ width: 95%;
}
-.download-contents td{
- overflow-wrap: break-word;
- vertical-align: middle; /*Oh tables, good for one thing...*/
+.download-contents td {
+ overflow-wrap: break-word;
+ vertical-align: middle; /*Oh tables, good for one thing...*/
}
-.download-contents th{
- font-weight: normal;
+.download-contents th {
+ font-weight: normal;
}
-.table-header.subtle{
- font-size: .9em;
- line-height: 0.8em;
- border-top: 0px;
+.table-header.subtle {
+ font-size: 0.9em;
+ line-height: 0.8em;
+ border-top: 0px;
}
-.table .table-header{
- border-bottom: 1px solid #8DA2AA;
+.table .table-header {
+ border-bottom: 1px solid #8da2aa;
}
-.table-header-link{
- font-weight: normal;
+.table-header-link {
+ font-weight: normal;
}
-.table tr > th .tooltip{
- font-weight: normal;
+.table tr > th .tooltip {
+ font-weight: normal;
}
.download-contents .btn .icon,
-.download-contents .preview .icon{
- font-size: 1em;
- margin-left: 5px;
+.download-contents .preview .icon {
+ font-size: 1em;
+ margin-left: 5px;
}
-.download-contents a[href="tools/eml"]{
- color: inherit;
+.download-contents a[href="tools/eml"] {
+ color: inherit;
}
-.download .btn i{
- margin-left: 5px;
+.download .btn i {
+ margin-left: 5px;
}
-.download-contents .format-type{
- text-align: left;
+.download-contents .format-type {
+ text-align: left;
}
.download-contents .file-type i {
color: #999;
margin-left: 5px;
}
-.download-contents .ellipsis{
- display: block;
- max-width: 250px;
+.download-contents .ellipsis {
+ display: block;
+ max-width: 250px;
}
-table.download-contents .name{
- min-width: 325px;
- max-width: 325px;
+table.download-contents .name {
+ min-width: 325px;
+ max-width: 325px;
}
table.download-contents .btn-container,
-table.download-contents .btn-container .btn{
- width: 140px;
+table.download-contents .btn-container .btn {
+ width: 140px;
}
-.download-contents.service .btn-container .btn{
- width: 135px;
+.download-contents.service .btn-container .btn {
+ width: 135px;
}
.download-contents.service .btn-container .btn span {
- float: left;
- width: 80%;
- width: calc(100% - 20px);
+ float: left;
+ width: 80%;
+ width: calc(100% - 20px);
}
.download-contents.service .btn-container .btn .icon {
- float: right;
- margin-top: 1em;
+ float: right;
+ margin-top: 1em;
}
-.download-contents th .btn-primary{
- min-width: 138px;
+.download-contents th .btn-primary {
+ min-width: 138px;
}
-.download-contents.nested .table-header.title a{
- color: #FFF;
+.download-contents.nested .table-header.title a {
+ color: #fff;
}
-.parent-link{
- font-size: .9em;
- margin-bottom: 10px;
- display: block;
+.parent-link {
+ font-size: 0.9em;
+ margin-bottom: 10px;
+ display: block;
}
/* The footer / expand-collapse control */
-.table tfoot th{
- text-align: center;
+.table tfoot th {
+ text-align: center;
}
-tfoot .control{
- border-bottom-width: 1px;
- border-bottom-style: dotted;
- cursor: pointer;
+tfoot .control {
+ border-bottom-width: 1px;
+ border-bottom-style: dotted;
+ cursor: pointer;
}
-tfoot .control i{
- margin-left: 5px;
+tfoot .control i {
+ margin-left: 5px;
}
-.download-contents ~ .expand-collapse-control{
- line-height: 2em;
- text-align: center;
- font-weight: bold;
- margin-top: 20px;
- margin-bottom: 20px;
+.download-contents ~ .expand-collapse-control {
+ line-height: 2em;
+ text-align: center;
+ font-weight: bold;
+ margin-top: 20px;
+ margin-bottom: 20px;
}
-.expand-collapse-control > a > .icon{
- margin-right: 5px;
+.expand-collapse-control > a > .icon {
+ margin-right: 5px;
}
/**----------------------------------------**/
@@ -5696,126 +5775,125 @@ tfoot .control i{
* Service Registration table
********************************************/
.service-table {
- margin-top: 2em;
+ margin-top: 2em;
}
.service-table td {
- height: 1em;
- vertical-align: middle;
+ height: 1em;
+ vertical-align: middle;
}
.service-table td.service-name {
- width: 150px;
+ width: 150px;
}
.service-table td.service-description {
- min-width: 200px;
- max-width: 300px;
+ min-width: 200px;
+ max-width: 300px;
}
.service-description p {
- display: inline;
- margin-right: 5px;
-}
+ display: inline;
+ margin-right: 5px;
+}
.service-description .ellipsis {
- width: 80%;
- width: calc(100% - 48px);
- display: inline-block;
- margin: 0px;
+ width: 80%;
+ width: calc(100% - 48px);
+ display: inline-block;
+ margin: 0px;
}
.service-description .expand-collapse {
- vertical-align: top;
+ vertical-align: top;
}
-.service-table thead{
- font-weight: bold;
+.service-table thead {
+ font-weight: bold;
}
.service-table td.service-type {
- width: 100px;
+ width: 100px;
}
.service-table td.service-endpoint {
- width: 200px;
+ width: 200px;
}
.service-table td.service-endpoint input {
- width: 120px;
- margin-bottom: 0px;
- border-top-right-radius: 0px;
- border-bottom-right-radius: 0px;
- border-color: #e6e6e6;
+ width: 120px;
+ margin-bottom: 0px;
+ border-top-right-radius: 0px;
+ border-bottom-right-radius: 0px;
+ border-color: #e6e6e6;
}
.service-table td.service-endpoint button {
- margin-bottom: 0px;
- border-left: 0px;
- padding: 4px 7px;
- border-top-left-radius: 0px;
- border-bottom-left-radius: 0px;
+ margin-bottom: 0px;
+ border-left: 0px;
+ padding: 4px 7px;
+ border-top-left-radius: 0px;
+ border-bottom-left-radius: 0px;
}
.service-table span.copy-success {
- position: absolute;
- margin-top: 4px;
- margin-left: 10px;
+ position: absolute;
+ margin-top: 4px;
+ margin-left: 10px;
}
/**----------------------------------------**/
-
/******************************************
* Citations and Citation Popovers
********************************************/
-.popover .citation .id{
- word-wrap: break-word;
- word-break: break-all;
+.popover .citation .id {
+ word-wrap: break-word;
+ word-break: break-all;
}
-.popover .citation-container.multiple .citation{
- padding-top: 10px;
- padding-bottom: 10px;
- border-bottom: 1px solid #999;
+.popover .citation-container.multiple .citation {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #999;
}
-.popover .citation-container a{
- margin-top: 10px;
- display: block;
+.popover .citation-container a {
+ margin-top: 10px;
+ display: block;
}
-.popover .citation-container a > i{
- margin-left: 5px;
+.popover .citation-container a > i {
+ margin-left: 5px;
}
-.citation .label{
- display: inline;
- font-weight: bolder;
- text-shadow: none;
+.citation .label {
+ display: inline;
+ font-weight: bolder;
+ text-shadow: none;
}
-#results .popover{
- min-width: 300px;
- max-width: 300px;
- font-size: .9em;
- line-height: 1.3em;
+#results .popover {
+ min-width: 300px;
+ max-width: 300px;
+ font-size: 0.9em;
+ line-height: 1.3em;
}
-.popover cite{
- display: block;
- margin-bottom: 20px;
+.popover cite {
+ display: block;
+ margin-bottom: 20px;
}
-.citation .id{
- color: #888;
- font-size: .9em;
+.citation .id {
+ color: #888;
+ font-size: 0.9em;
}
.result-row .citation a {
- color: inherit;
- font-weight: normal;
+ color: inherit;
+ font-weight: normal;
}
-.result-row .citation .title{
- font-weight: bold;
+.result-row .citation .title {
+ font-weight: bold;
}
-.route-to-metadata{
- cursor: pointer;
+.route-to-metadata {
+ cursor: pointer;
}
-.citation.collection .title{
+.citation.collection .title {
display: block;
}
-.citation.collection .id{
+.citation.collection .id {
display: none;
}
@@ -5824,137 +5902,137 @@ tfoot .control i{
******************************************/
.user-view-section > section {
- max-width: 1200px;
- width: 90%;
- margin-left: auto;
- margin-right: auto;
+ max-width: 1200px;
+ width: 90%;
+ margin-left: auto;
+ margin-right: auto;
}
#profile-content {
- float: right;
+ float: right;
}
-#user-profile #data-list{
- padding-bottom: 20px;
- margin-bottom: 20px;
+#user-profile #data-list {
+ padding-bottom: 20px;
+ margin-bottom: 20px;
}
-#user-profile #clear-all{
- display: none;
+#user-profile #clear-all {
+ display: none;
}
-.user-view-section > .section > .span12{
- margin-left: 0px;
+.user-view-section > .section > .span12 {
+ margin-left: 0px;
}
-.user-view-section .hgroup{
- margin-bottom: 20px;
+.user-view-section .hgroup {
+ margin-bottom: 20px;
}
-.user-view-section .hgroup h2 a{
- font-weight: lighter;
- color: inherit;
+.user-view-section .hgroup h2 a {
+ font-weight: lighter;
+ color: inherit;
}
.user-view-section .hgroup h6,
-.user-view-section .hgroup h5{
- font-weight: normal;
+.user-view-section .hgroup h5 {
+ font-weight: normal;
}
-.user-view-section h5{
- margin-bottom: 10px;
- margin-top: 5px;
- padding-bottom: 10px;
+.user-view-section h5 {
+ margin-bottom: 10px;
+ margin-top: 5px;
+ padding-bottom: 10px;
}
-.user-view-section .hgroup .divided{
- margin-top: 20px;
- padding-top: 20px;
+.user-view-section .hgroup .divided {
+ margin-top: 20px;
+ padding-top: 20px;
}
-#token-generator-container .notification.loading{
- min-height: 236px;
+#token-generator-container .notification.loading {
+ min-height: 236px;
}
-textarea.token{
- width: 100%;
- margin-top: 20px;
- font-size: .9em;
+textarea.token {
+ width: 100%;
+ margin-top: 20px;
+ font-size: 0.9em;
}
-.form-group{
- clear: both;
+.form-group {
+ clear: both;
}
.list-group-item .remove-identity-btn {
- float: right;
- line-height: 2.7em;
+ float: right;
+ line-height: 2.7em;
}
-.list-group-item.identity.pending .details{
- margin-left: 52px;
+.list-group-item.identity.pending .details {
+ margin-left: 52px;
}
-.list-group-item.identity .details .orcid-logo{
- margin-left: 0px;
+.list-group-item.identity .details .orcid-logo {
+ margin-left: 0px;
}
.list-group-item .confirm-request-btn,
-.list-group-item .reject-request-btn{
- margin-right: 5px;
+.list-group-item .reject-request-btn {
+ margin-right: 5px;
}
-.icon.is-owner{
- color: #FFD700;
+.icon.is-owner {
+ color: #ffd700;
}
-.member .has-member-controls{
- float: left;
- width: 85%;
+.member .has-member-controls {
+ float: left;
+ width: 85%;
}
-.member .member-controls{
- float: left;
- width: 10%;
- width: calc(15% - 10px);
+.member .member-controls {
+ float: left;
+ width: 10%;
+ width: calc(15% - 10px);
}
-.member .member-controls .remove-member{
- float: right;
- font-size: 1.3em;
- line-height: 2em;
- cursor: pointer;
+.member .member-controls .remove-member {
+ float: right;
+ font-size: 1.3em;
+ line-height: 2em;
+ cursor: pointer;
}
-.add-member label{
- max-width: 100%;
- word-break: break-word;
- white-space: pre-wrap;
+.add-member label {
+ max-width: 100%;
+ word-break: break-word;
+ white-space: pre-wrap;
}
-.add-member .notification{
- margin-top: 10px;
- line-height: 30px;
+.add-member .notification {
+ margin-top: 10px;
+ line-height: 30px;
}
-.list-group-item.add-member input{
- max-width: 75%;
+.list-group-item.add-member input {
+ max-width: 75%;
}
-#data-list > p.center{
- margin-top: 40px;
+#data-list > p.center {
+ margin-top: 40px;
}
-.orcid-logo{
- height: 16px;
- width: 16px;
- vertical-align: middle;
+.orcid-logo {
+ height: 16px;
+ width: 16px;
+ vertical-align: middle;
}
-#user-profile .logo{
- max-width: 100px;
- display: block;
- margin-bottom: 30px;
+#user-profile .logo {
+ max-width: 100px;
+ display: block;
+ margin-bottom: 30px;
}
#identity-request-container label,
-.add-member label{
- font-weight: bold;
+.add-member label {
+ font-weight: bold;
}
-#first-upload-year-container{
- float: left;
- max-width: 115px;
+#first-upload-year-container {
+ float: left;
+ max-width: 115px;
}
-#first-upload-container{
- font-size: .9em;
- width: 50%;
- width: calc(97% - 135px);
+#first-upload-container {
+ font-size: 0.9em;
+ width: 50%;
+ width: calc(97% - 135px);
}
-#total-replicas-wrapper{
- display: none;
+#total-replicas-wrapper {
+ display: none;
}
-address.email-container{
- display: inline;
+address.email-container {
+ display: inline;
}
-address.email-container:after{
- content: attr(data-domain);
+address.email-container:after {
+ content: attr(data-domain);
}
-address.email-container:before{
- content: attr(data-user);
+address.email-container:before {
+ content: attr(data-user);
}
[data-username="ORNLDAAC"] #user-profile .user-info .logo,
[data-username="CDL"] #user-profile .user-info .logo,
@@ -5964,24 +6042,25 @@ address.email-container:before{
[data-username="CLOEBIRD"] #user-profile .user-info .logo,
[data-username="EDACGSTORE"] #user-profile .user-info .logo,
[data-username="NKN"] #user-profile .user-info .logo,
-[data-username="NRDC"] #user-profile .user-info .logo{
- max-width: 200px;
- width: 200px;
+[data-username="NRDC"] #user-profile .user-info .logo {
+ max-width: 200px;
+ width: 200px;
}
-#quality-score, #quality-chart{
- display: none;
+#quality-score,
+#quality-chart {
+ display: none;
}
.quality-report-view #suiteId {
- position: relative;
- width: auto;
- margin-top: 10px;
- margin-bottom: 10px;
+ position: relative;
+ width: auto;
+ margin-top: 10px;
+ margin-bottom: 10px;
}
.quality-report-view .metric-chart-loading {
- margin: 50px auto;
+ margin: 50px auto;
}
-.new-icon{
+.new-icon {
color: #d2a908;
}
@@ -5998,159 +6077,157 @@ address.email-container:before{
.no-activity .message,
.no-activity svg .title,
.no-activity svg .bar-label,
-.profile .no-activity .packages p{
- color: #CCC;
- stroke: #CCC;
- fill: #CCC;
+.profile .no-activity .packages p {
+ color: #ccc;
+ stroke: #ccc;
+ fill: #ccc;
}
.no-activity svg.character {
- display: none;
+ display: none;
}
/*Moving the circle badge at the center of the div*/
.profile .circle-badge {
- display: block;
- margin: auto;
+ display: block;
+ margin: auto;
}
-.profile .circle-badge circle.no-activity{
- stroke: #CCC;
- fill: transparent;
+.profile .circle-badge circle.no-activity {
+ stroke: #ccc;
+ fill: transparent;
}
-.profile .circle-badge text.no-activity{
- fill: #CCC;
+.profile .circle-badge text.no-activity {
+ fill: #ccc;
}
-.profile .charts{
+.profile .charts {
box-sizing: border-box;
display: flex;
justify-content: center;
}
-.profile .chart{
- float: left;
- margin-left: 20px;
+.profile .chart {
+ float: left;
+ margin-left: 20px;
margin-right: 20px;
- margin-top: 30px;
+ margin-top: 30px;
}
-.profile .stripe{
- clear: both;
- min-height: 200px;
+.profile .stripe {
+ clear: both;
+ min-height: 200px;
}
-.profile .chart-title.uploads{
- width: 100%;
+.profile .chart-title.uploads {
+ width: 100%;
}
.profile .downloads .chart-title,
-.profile .uploads .chart-title{
- width: 100%;
- min-width: 250px;
+.profile .uploads .chart-title {
+ width: 100%;
+ min-width: 250px;
}
.profile #downloads-title,
-.profile #uploads-title{
- float: left;
- min-width: 200px;
+.profile #uploads-title {
+ float: left;
+ min-width: 200px;
}
.profile .chart.download-chart,
-.profile .chart.upload-chart{
- width: 1000px;
- float: none;
- clear: both;
+.profile .chart.upload-chart {
+ width: 1000px;
+ float: none;
+ clear: both;
}
-.profile .stripe{
- border-top: 1px dashed #CCC;
- padding: 20px 20px 20px 20px;
+.profile .stripe {
+ border-top: 1px dashed #ccc;
+ padding: 20px 20px 20px 20px;
}
.profile .packages p {
- color: #333;
+ color: #333;
}
-.no-activity .profile .packages p{
- color: inherit;
+.no-activity .profile .packages p {
+ color: inherit;
}
-.profile .circle-badge circle{
- fill: transparent;
+.profile .circle-badge circle {
+ fill: transparent;
}
-.profile .quick-stats .circle-badge .count{
- font-size: 24px;
+.profile .quick-stats .circle-badge .count {
+ font-size: 24px;
}
-.profile .quick-stats .circle-badge .title{
- font-size: 16px;
+.profile .quick-stats .circle-badge .title {
+ font-size: 16px;
}
-.profile .charts + p{
+.profile .charts + p {
margin-top: 20px;
}
/**** Bar Charts ******/
-.bar-chart .bar-label.bg{
- fill: rgba(0,0,0,0.8);
- stroke-width: 0px;
+.bar-chart .bar-label.bg {
+ fill: rgba(0, 0, 0, 0.8);
+ stroke-width: 0px;
}
-.bar-chart .bar-label.bg ~ text{
- fill: #FFF;
+.bar-chart .bar-label.bg ~ text {
+ fill: #fff;
}
-.bar-chart.hide-labels .x.axis .tick:nth-child(even){
- display: none; /* hide every other x-axis tick when there are too many bars */
+.bar-chart.hide-labels .x.axis .tick:nth-child(even) {
+ display: none; /* hide every other x-axis tick when there are too many bars */
}
-.bar-chart.log-scale .tick line{
- stroke: #EEE;
+.bar-chart.log-scale .tick line {
+ stroke: #eee;
}
-.profile .temporal-coverage-chart{
+.profile .temporal-coverage-chart {
width: calc(100% - 40px);
}
-
/**** Default Chart Styles *****/
svg .default,
-.fallback.default{
- fill: #999;
- stroke: #999;
- color: #999;
+.fallback.default {
+ fill: #999;
+ stroke: #999;
+ color: #999;
}
.donut.default .donut-labels,
-.donut.no-activity .donut-labels{
- visibility: hidden;
+.donut.no-activity .donut-labels {
+ visibility: hidden;
}
.donut.default > g .donut-arc,
.donut.data.default > g .donut-arc,
.donut.metadata.default > g .donut-arc,
.donut.default .donut-title-count,
-.donut.default .donut-title-text{
- fill: #999;
+.donut.default .donut-title-text {
+ fill: #999;
}
-.circle-badge.default circle{
- stroke: #999;
+.circle-badge.default circle {
+ stroke: #999;
}
-.circle-badge.default text{
- fill: #999;
+.circle-badge.default text {
+ fill: #999;
}
/**** Static Image Chart Styles *****/
-.profile .stripe img{
- width: 100%;
- max-width: 950px;
- min-width: 600px;
+.profile .stripe img {
+ width: 100%;
+ max-width: 950px;
+ min-width: 600px;
max-height: 350px;
- padding-top: 30px;
- padding-left: 10px;
- overflow-x: scroll;
+ padding-top: 30px;
+ padding-left: 10px;
+ overflow-x: scroll;
}
.profile .stripe .static-img-container {
- overflow-x: auto;
+ overflow-x: auto;
}
/**** No Activity Chart Styles (when Stats View is blank/ no activity found) *****/
svg .no-activity,
-.fallback.no-activity{
- fill: #CCCCCC;
- stroke: #CCCCCC;
- color: #CCCCCC;
+.fallback.no-activity {
+ fill: #cccccc;
+ stroke: #cccccc;
+ color: #cccccc;
}
.donut.default > g .donut-arc,
.donut.data.default > g .donut-arc,
.donut.metadata.default > g .donut-arc,
.donut.default .donut-title-count,
-.donut.default .donut-title-text{
- fill: #CCCCCC;
+.donut.default .donut-title-text {
+ fill: #cccccc;
}
-
/******************************************
** Panels ***
******************************************/
@@ -6159,23 +6236,25 @@ svg .no-activity,
background-color: #fff;
border: 1px solid transparent;
border-radius: 4px;
- -webkit-box-shadow: 0 1px 1px rgba(0,0,0,.05);
- box-shadow: 0 1px 1px rgba(0,0,0,.05);
- transition: box-shadow 2s ease-out;
- -o-transition: box-shadow 2s ease-out;
- -moz-transition: box-shadow 2s ease-out;
- -webkit-transition: box-shadow 2s ease-out;
- -ms-transition: box-shadow 2s ease-out;
- -kthtml-transition: box-shadow 2s ease-out;
- transition: box-shadow 2s ease-out;
-}
-.panel.highlight{
- box-shadow: 0 0 5px rgb(50, 161, 19),0 0 12px rgb(0, 255, 213);
+ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+ transition: box-shadow 2s ease-out;
+ -o-transition: box-shadow 2s ease-out;
+ -moz-transition: box-shadow 2s ease-out;
+ -webkit-transition: box-shadow 2s ease-out;
+ -ms-transition: box-shadow 2s ease-out;
+ -kthtml-transition: box-shadow 2s ease-out;
+ transition: box-shadow 2s ease-out;
+}
+.panel.highlight {
+ box-shadow:
+ 0 0 5px rgb(50, 161, 19),
+ 0 0 12px rgb(0, 255, 213);
}
.panel-default {
border-color: #ddd;
}
-.panel-default>.panel-heading {
+.panel-default > .panel-heading {
background-color: #f5f5f5;
border-color: #ddd;
}
@@ -6191,13 +6270,14 @@ svg .no-activity,
.panel-body {
padding: 15px;
}
-.panel-body .panel{
- margin-top: 30px;
+.panel-body .panel {
+ margin-top: 30px;
}
-.panel-body form{
- margin-bottom: 0px;
+.panel-body form {
+ margin-bottom: 0px;
}
-.panel>.list-group, .panel>.panel-collapse>.list-group {
+.panel > .list-group,
+.panel > .panel-collapse > .list-group {
margin-bottom: 0;
margin-left: 0px;
}
@@ -6207,7 +6287,8 @@ svg .no-activity,
margin-left: 0px;
list-style: none;
}
-.panel>.list-group .list-group-item, .panel>.panel-collapse>.list-group .list-group-item {
+.panel > .list-group .list-group-item,
+.panel > .panel-collapse > .list-group .list-group-item {
border-width: 1px 0;
border-radius: 0;
}
@@ -6223,38 +6304,40 @@ svg .no-activity,
background-color: #fff;
border: 1px solid #ddd;
}
-.list-group-header.list-group-item{
- background-color: #ECF1F5;
- min-width: auto;
+.list-group-header.list-group-item {
+ background-color: #ecf1f5;
+ min-width: auto;
}
#profile-group-list-container > ul > .list-group-header.list-group-item:hover,
-#profile-group-list-container > ul > .list-group-header.list-group-item.active{
- background-color: #208095;
- min-width: auto;
+#profile-group-list-container > ul > .list-group-header.list-group-item.active {
+ background-color: #208095;
+ min-width: auto;
}
-#profile-group-list-container > ul > li.list-group-item.member.current-page:hover{
- background-color: #fff;
+#profile-group-list-container
+ > ul
+ > li.list-group-item.member.current-page:hover {
+ background-color: #fff;
}
#profile-group-list-container > ul > .pager.list-group-item:hover {
- background-color: #fff;
+ background-color: #fff;
}
#profile-group-list-container > ul > .pager > .pager-link:hover {
- background-color: #208095;
- color: #fff;
+ background-color: #208095;
+ color: #fff;
}
-.panel>.list-group .list-group-item.active{
- border-left-width: 5px;
+.panel > .list-group .list-group-item.active {
+ border-left-width: 5px;
}
.list-group-item > .list-group {
margin-bottom: 10px;
margin-top: 10px;
}
-.list-group-item > .list-group > .list-group-item:last-child{
- border-bottom-width: 0px;
+.list-group-item > .list-group > .list-group-item:last-child {
+ border-bottom-width: 0px;
}
-.list-group-item a > .icon{
- margin-right: 5px;
+.list-group-item a > .icon {
+ margin-right: 5px;
}
.list-group-item:first-child {
border-top-left-radius: 4px;
@@ -6269,37 +6352,37 @@ svg .no-activity,
border: 1px solid #ddd;
box-sizing: border-box;
}
-.list-group-item .badge{
- margin-top: 10px;
+.list-group-item .badge {
+ margin-top: 10px;
}
-.panel>.list-identity .list-group-item.active{
- border-left-width: 5px;
+.panel > .list-identity .list-group-item.active {
+ border-left-width: 5px;
}
.list-group-item > .list-identity {
padding-left: 20px;
margin-bottom: 0px;
}
-.list-group-item > .list-identity > .list-identity-item:last-child{
- border-bottom-width: 0px;
+.list-group-item > .list-identity > .list-identity-item:last-child {
+ border-bottom-width: 0px;
}
-.list-group-item a > .icon{
- margin-right: 5px;
+.list-group-item a > .icon {
+ margin-right: 5px;
}
.list-group-item .details {
- font-size: .9em;
- display: block;
- color: #666;
+ font-size: 0.9em;
+ display: block;
+ color: #666;
}
-.list-group-item.member .details{
- margin-left: 18px;
+.list-group-item.member .details {
+ margin-left: 18px;
}
-.list-group-item.create-group a{
- font-weight: normal;
+.list-group-item.create-group a {
+ font-weight: normal;
}
/******************************************
** Searching and Map ***
******************************************/
-#clear-all{
+#clear-all {
width: 95%;
width: calc(100% - 10px);
padding: 2px 5px;
@@ -6308,33 +6391,65 @@ svg .no-activity,
float: none;
cursor: pointer;
font-size: 1em;
- color: #FFF;
+ color: #fff;
font-weight: bold;
- background-color: #006699
-}
-#clear-all .icon{
- color: rgb(131, 131, 0);
- margin: 0px 5px;
-}
-#map-container{
- display: none;
-}
-body.mapMode{
- overflow-y: hidden;
-}
-.mapMode #map-container{
- position: relative;
- display: block;
-}
-.map-toggle-container{
- background-color: #333333;
- background: -moz-linear-gradient(top, rgba(0,0,0,1) 0%, rgba(0,0,0,0.7) 37%, rgba(0,0,0,0.7) 100%); /* FF3.6+ */
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,1)), color-stop(37%,rgba(0,0,0,0.7)), color-stop(100%,rgba(0,0,0,0.7))); /* Chrome,Safari4+ */
- background: -webkit-linear-gradient(top, rgba(0,0,0,1) 0%,rgba(0,0,0,0.7) 37%,rgba(0,0,0,0.7) 100%); /* Chrome10+,Safari5.1+ */
- background: -o-linear-gradient(top, rgba(0,0,0,1) 0%,rgba(0,0,0,0.7) 37%,rgba(0,0,0,0.7) 100%); /* Opera 11.10+ */
- background: -ms-linear-gradient(top, rgba(0,0,0,1) 0%,rgba(0,0,0,0.7) 37%,rgba(0,0,0,0.7) 100%); /* IE10+ */
- background: linear-gradient(to bottom, rgba(0,0,0,1) 0%,rgba(0,0,0,0.7) 37%,rgba(0,0,0,0.7) 100%); /* W3C */
- filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#000000', endColorstr='#b3000000',GradientType=0 ); /* IE6-9 */
+ background-color: #006699;
+}
+#clear-all .icon {
+ color: rgb(131, 131, 0);
+ margin: 0px 5px;
+}
+#map-container {
+ display: none;
+}
+body.mapMode {
+ overflow-y: hidden;
+}
+.mapMode #map-container {
+ position: relative;
+ display: block;
+}
+.map-toggle-container {
+ background-color: #333333;
+ background: -moz-linear-gradient(
+ top,
+ rgba(0, 0, 0, 1) 0%,
+ rgba(0, 0, 0, 0.7) 37%,
+ rgba(0, 0, 0, 0.7) 100%
+ ); /* FF3.6+ */
+ background: -webkit-gradient(
+ linear,
+ left top,
+ left bottom,
+ color-stop(0%, rgba(0, 0, 0, 1)),
+ color-stop(37%, rgba(0, 0, 0, 0.7)),
+ color-stop(100%, rgba(0, 0, 0, 0.7))
+ ); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(
+ top,
+ rgba(0, 0, 0, 1) 0%,
+ rgba(0, 0, 0, 0.7) 37%,
+ rgba(0, 0, 0, 0.7) 100%
+ ); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(
+ top,
+ rgba(0, 0, 0, 1) 0%,
+ rgba(0, 0, 0, 0.7) 37%,
+ rgba(0, 0, 0, 0.7) 100%
+ ); /* Opera 11.10+ */
+ background: -ms-linear-gradient(
+ top,
+ rgba(0, 0, 0, 1) 0%,
+ rgba(0, 0, 0, 0.7) 37%,
+ rgba(0, 0, 0, 0.7) 100%
+ ); /* IE10+ */
+ background: linear-gradient(
+ to bottom,
+ rgba(0, 0, 0, 1) 0%,
+ rgba(0, 0, 0, 0.7) 37%,
+ rgba(0, 0, 0, 0.7) 100%
+ ); /* W3C */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#000000', endColorstr='#b3000000',GradientType=0 ); /* IE6-9 */
padding: 5px;
top: 0px;
height: auto;
@@ -6346,85 +6461,85 @@ body.mapMode{
.mapMode #results-view .map-toggle-container {
display: none;
}
-.map-toggle-container > a{
+.map-toggle-container > a {
font-size: 1em;
line-height: 1.3em;
color: white;
font-weight: normal;
}
-.map-toggle-container > a:hover{
- color: white;
- font-weight: bold;
+.map-toggle-container > a:hover {
+ color: white;
+ font-weight: bold;
}
-.map-toggle-container > a > .icon{
- margin-left: 5px;
- margin-right: 5px;
+.map-toggle-container > a > .icon {
+ margin-left: 5px;
+ margin-right: 5px;
}
-.map-toggle-container > label{
- color: #FFF;
- float: right;
+.map-toggle-container > label {
+ color: #fff;
+ float: right;
}
-.map-toggle-container > input[type="checkbox"]{
- float: right;
- margin-top: 2px;
- height: 20px;
- width: 20px;
+.map-toggle-container > input[type="checkbox"] {
+ float: right;
+ margin-top: 2px;
+ height: 20px;
+ width: 20px;
}
/* For list mode only */
-.list-only #sidebar{
- display: none;
+.list-only #sidebar {
+ display: none;
}
-.list-only #results-container{
- width: 100%;
- overflow-y: visible;
- overflow-x: visible;
+.list-only #results-container {
+ width: 100%;
+ overflow-y: visible;
+ overflow-x: visible;
}
-.list-only .map-toggle-container{
- display: none;
+.list-only .map-toggle-container {
+ display: none;
}
/******************************************
** Filters ***
******************************************/
-.filter-container{
- width: 187px;
-}
-.filter-contain{
- border-bottom: 1px dashed #CCC;
- padding: 10px 0px 5px 0px;
- width: 100%;
- height: auto;
- max-height: 1.4em;
- transition: max-height .5s ease-in-out;
- -webkit-transition: max-height .5s ease-in-out;
- -moz-transition: max-height .5s ease-in-out;
- overflow: hidden;
-}
-.filter-input-contain{
- position: relative;
- width: 100%;
-}
-.filter.btn{
- margin-bottom: 0px;
+.filter-container {
+ width: 187px;
+}
+.filter-contain {
+ border-bottom: 1px dashed #ccc;
+ padding: 10px 0px 5px 0px;
+ width: 100%;
+ height: auto;
+ max-height: 1.4em;
+ transition: max-height 0.5s ease-in-out;
+ -webkit-transition: max-height 0.5s ease-in-out;
+ -moz-transition: max-height 0.5s ease-in-out;
+ overflow: hidden;
+}
+.filter-input-contain {
+ position: relative;
+ width: 100%;
+}
+.filter.btn {
+ margin-bottom: 0px;
}
.filter-groups.vertical .filter-input-contain {
- margin-bottom: 0;
+ margin-bottom: 0;
}
-.filter-contain > label{
- font-size: 13px;
- font-weight: bold;
- display: inline-block;
- height: 2em;
+.filter-contain > label {
+ font-size: 13px;
+ font-weight: bold;
+ display: inline-block;
+ height: 2em;
}
-.current-filters-container{
+.current-filters-container {
margin-bottom: 10px;
display: block;
clear: both;
- border-bottom: 2px solid #DDD;
+ border-bottom: 2px solid #ddd;
padding-left: 10px;
padding-right: 25px;
}
.current-filters > .current-filter,
-.current-filters > .current-filter[class*="span"]{
+.current-filters > .current-filter[class*="span"] {
padding: 0px 5px 0px 5px;
min-height: 1.5em;
line-height: 1.5em;
@@ -6437,185 +6552,185 @@ body.mapMode{
font-size: 1em;
}
.current-filters,
-.current-filters[class*="span"]{
- list-style: none;
- margin-left: 0px;
- min-height: 0px;
-}
-.current-filter > span{
- max-width: 85%;
- max-width: calc(100% - 25px);
- overflow: hidden;
- white-space: normal;
- word-break: break-word;
- display: inline-block;
- vertical-align: middle;
-}
-.current-filter .category{
- font-weight: bold;
- margin-right: 5px;
-}
-.current-filter > .icon{
- margin-left: 5px;
-}
-.current-filters[data-category='resourceMap'] .current-filter .category{
- margin-right: 0px;
-}
-#sidebar{
- width: 225px;
- height: auto;
- overflow-y: visible;
- overflow-x: visible;
- float: left;
- clear: left;
-}
-.mapMode #sidebar{
- width: 215px;
- height: 700px;
- border-right: 1px solid #CCC;
- overflow-x: hidden;
- overflow-y: overlay;
- -webkit-transform: translate3d(0,0,0);
-}
-#sidebar > *{
+.current-filters[class*="span"] {
+ list-style: none;
+ margin-left: 0px;
+ min-height: 0px;
+}
+.current-filter > span {
+ max-width: 85%;
+ max-width: calc(100% - 25px);
+ overflow: hidden;
+ white-space: normal;
+ word-break: break-word;
+ display: inline-block;
+ vertical-align: middle;
+}
+.current-filter .category {
+ font-weight: bold;
+ margin-right: 5px;
+}
+.current-filter > .icon {
+ margin-left: 5px;
+}
+.current-filters[data-category="resourceMap"] .current-filter .category {
+ margin-right: 0px;
+}
+#sidebar {
+ width: 225px;
+ height: auto;
+ overflow-y: visible;
+ overflow-x: visible;
+ float: left;
+ clear: left;
+}
+.mapMode #sidebar {
+ width: 215px;
+ height: 700px;
+ border-right: 1px solid #ccc;
+ overflow-x: hidden;
+ overflow-y: overlay;
+ -webkit-transform: translate3d(0, 0, 0);
+}
+#sidebar > * {
padding-left: 10px;
padding-right: 10px;
width: calc(100% - 30px);
}
-#sidebar > .input-append{
- margin-top: 10px;
- border-bottom: 2px solid #DDD;
- padding-bottom: 16px;
+#sidebar > .input-append {
+ margin-top: 10px;
+ border-bottom: 2px solid #ddd;
+ padding-bottom: 16px;
}
-#sidebar > .input-append > label{
- font-size: 15px;
- font-weight: bold;
+#sidebar > .input-append > label {
+ font-size: 15px;
+ font-weight: bold;
}
#sidebar > .input-append .btn {
- margin-left: -6px;
+ margin-left: -6px;
}
-#sidebar .tooltip-inner{
- font-size: 1.2em;
- font-weight: normal;
- max-width: 181px;
+#sidebar .tooltip-inner {
+ font-size: 1.2em;
+ font-weight: normal;
+ max-width: 181px;
}
#all_input {
- width: 75%;
- width: calc(100% - 60px);
- margin-left: 0px;
- margin-right: -3px;
-}
-.filter-input-contain input.filter{
- margin-right: -3px;
- margin-left: 0px;
- border-radius: 5px 0px 0px 5px;
- -webkit-border-radius: 5px 0px 0px 5px;
- -moz-border-radius: 5px 0px 0px 5px;
- border: 1px solid #AAAAAA;
- border-right: 0px;
- height: 20px;
- width: 75%;
-}
-#filter-year{
- width: 90%;
-}
-#filter-year input[type='text']{
- max-width: 50px;
-}
-#sidebar .ui-helper-hidden-accessible{
- display: none;
-}
-#filter-year .filter-input-contain{
- margin-top: 10px;
-}
-.filter-contain input[type="checkbox"].filter{
- width: 20px;
- float: left;
- margin-right: 5px;
+ width: 75%;
+ width: calc(100% - 60px);
+ margin-left: 0px;
+ margin-right: -3px;
+}
+.filter-input-contain input.filter {
+ margin-right: -3px;
+ margin-left: 0px;
+ border-radius: 5px 0px 0px 5px;
+ -webkit-border-radius: 5px 0px 0px 5px;
+ -moz-border-radius: 5px 0px 0px 5px;
+ border: 1px solid #aaaaaa;
+ border-right: 0px;
+ height: 20px;
+ width: 75%;
+}
+#filter-year {
+ width: 90%;
+}
+#filter-year input[type="text"] {
+ max-width: 50px;
+}
+#sidebar .ui-helper-hidden-accessible {
+ display: none;
+}
+#filter-year .filter-input-contain {
+ margin-top: 10px;
+}
+.filter-contain input[type="checkbox"].filter {
+ width: 20px;
+ float: left;
+ margin-right: 5px;
}
/*-- Pagination and sort --*/
-.sort-by{
- float: right;
+.sort-by {
+ float: right;
}
.sort-by label {
- display: inline;
- margin-right: 5px;
- line-height: 30px;
- vertical-align: top;
+ display: inline;
+ margin-right: 5px;
+ line-height: 30px;
+ vertical-align: top;
}
-.sort-by select{
- width: auto;
+.sort-by select {
+ width: auto;
}
-.pager-view li a{
- min-height: 20px;
- min-width: 12px;
+.pager-view li a {
+ min-height: 20px;
+ min-width: 12px;
}
-.pager-view li a:not([href]){
- pointer-events: none;
- color: #CCC;
+.pager-view li a:not([href]) {
+ pointer-events: none;
+ color: #ccc;
}
-#results-container .pagination{
- box-sizing: border-box;
+#results-container .pagination {
+ box-sizing: border-box;
}
/*-- The filter checkboxes --*/
-#includes-files label{
- margin-right: 4px;
- float: left;
- font-size: 13px;
- width: auto;
+#includes-files label {
+ margin-right: 4px;
+ float: left;
+ font-size: 13px;
+ width: auto;
}
.filter-contain .checkbox-list {
- margin-left: 0px;
- list-style: none;
+ margin-left: 0px;
+ list-style: none;
}
-.checkbox-list > li > input{
- float: left;
- margin-right: 10px;
- margin-left: 3px;
+.checkbox-list > li > input {
+ float: left;
+ margin-right: 10px;
+ margin-left: 3px;
}
-.checkbox-list label{
- font-size: .9em;
+.checkbox-list label {
+ font-size: 0.9em;
}
.checkbox-list .more-link,
-.checkbox-list .less-link{
- font-weight: normal;
- font-size: .9em;
+.checkbox-list .less-link {
+ font-weight: normal;
+ font-size: 0.9em;
}
-.checkbox-list .more-link .icon,
-.checkbox-list .less-link .icon{
- margin-left: 5px;
- font-size: 1.2em;
- vertical-align: text-bottom;
- line-height: .9em;
+.checkbox-list .more-link .icon,
+.checkbox-list .less-link .icon {
+ margin-left: 5px;
+ font-size: 1.2em;
+ vertical-align: text-bottom;
+ line-height: 0.9em;
}
/** Filter icons **/
-.filter-contain > label > .icon{
- color: #888;
- font-size: 17px;
- float: left;
- margin: 0px;
- margin-right: 8px;
-}
-.filter-contain .icon.more-info{
- font-size: 14px;
- line-height: 23px;
- top: 5px;
- float: left;
- cursor: default;
+.filter-contain > label > .icon {
+ color: #888;
+ font-size: 17px;
+ float: left;
+ margin: 0px;
+ margin-right: 8px;
+}
+.filter-contain .icon.more-info {
+ font-size: 14px;
+ line-height: 23px;
+ top: 5px;
+ float: left;
+ cursor: default;
}
.filter-contain .icon.expand,
-.filter-contain .icon.collapse{
- margin-right: 15px;
+.filter-contain .icon.collapse {
+ margin-right: 15px;
}
#filter-year > .icon,
-#includes-files > .icon{
- margin-top: 0px;
+#includes-files > .icon {
+ margin-top: 0px;
}
-.filter-contain > .icon-sitemap{
- font-size: 18px;
+.filter-contain > .icon-sitemap {
+ font-size: 18px;
}
-.filter-contain > .icon-map-marker{
- font-size: 22px;
+.filter-contain > .icon-map-marker {
+ font-size: 22px;
}
.current-filters.active {
background-color: inherit;
@@ -6626,29 +6741,32 @@ body.mapMode{
/** For "My Packages" filter **/
.keyword-search-link,
.current-filters > li.keyword-search-link {
- display: none;
+ display: none;
}
.keyword-search-link.active,
-.current-filters > li.keyword-search-link.active{
- border-color: inherit;
- display: inline-block;
- width: auto;
+.current-filters > li.keyword-search-link.active {
+ border-color: inherit;
+ display: inline-block;
+ width: auto;
}
-.btn-primary[disabled], .btn-primary[disabled]:hover, .btn[disabled], .btn[disabled]:hover {
- border-color: #CCC;
- background-color:#e6e6e6;
- color: #333;
+.btn-primary[disabled],
+.btn-primary[disabled]:hover,
+.btn[disabled],
+.btn[disabled]:hover {
+ border-color: #ccc;
+ background-color: #e6e6e6;
+ color: #333;
}
/********* Filter Views ***********/
.filter-groups .tab-content {
- overflow: inherit;
+ overflow: inherit;
}
-.portal-view .filter-group{
+.portal-view .filter-group {
padding: 0px 20px;
}
-.filter-group .filters-container{
+.filter-group .filters-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
@@ -6669,55 +6787,55 @@ body.mapMode{
padding: 0.65rem 0 1rem;
}
.filter-groups.vertical .filter {
- padding-right: 0;
+ padding-right: 0;
}
-.filter-groups .filter .btn:not(.btn-filter-editor){
- box-shadow: none;
- border-color: #ccc;
+.filter-groups .filter .btn:not(.btn-filter-editor) {
+ box-shadow: none;
+ border-color: #ccc;
color: #999;
padding: 4px 10px;
}
-.filter-groups.vertical .filter .btn{
+.filter-groups.vertical .filter .btn {
margin-left: -5px;
padding: 4px 10px;
}
-.filter-groups .filter .btn .icon{
+.filter-groups .filter .btn .icon {
line-height: 16px;
margin: 0px;
}
-.filter-group .filter input{
- box-shadow: none;
+.filter-group .filter input {
+ box-shadow: none;
}
-.filter-groups .filter label{
- cursor: default;
- display: flex;
- align-items: center;
+.filter-groups .filter label {
+ cursor: default;
+ display: flex;
+ align-items: center;
}
-.filter-groups .filter > label{
- font-weight: bold;
- margin-bottom: 0.5rem;
+.filter-groups .filter > label {
+ font-weight: bold;
+ margin-bottom: 0.5rem;
}
.filter-groups.vertical .filter > label {
- font-size: 0.8rem;
- text-transform: uppercase;
- letter-spacing: 0.015em;
- color: #1d1d1d;
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ letter-spacing: 0.015em;
+ color: #1d1d1d;
}
.filter-groups.vertical .filter > label .icon {
- font-size: 1.1rem;
- margin-right: 0.55rem;
+ font-size: 1.1rem;
+ margin-right: 0.55rem;
}
-.filter-group-links{
- border-top: 1px solid #DDD;
+.filter-group-links {
+ border-top: 1px solid #ddd;
clear: both;
}
.edit-mode .filter-group-links {
- margin-top: 2rem;
- margin-left: 1rem;
+ margin-top: 2rem;
+ margin-left: 1rem;
}
.edit-mode .filters-container {
- margin-top: 1rem;
- margin-left: 1.5rem;
+ margin-top: 1rem;
+ margin-left: 1.5rem;
}
.filter-groups.vertical .filter-group-links {
border: 0px;
@@ -6725,24 +6843,24 @@ body.mapMode{
.filter-group-link a,
.nav-tabs .filter-group-link a,
.nav-tabs .filter-group-link.active a {
- background-color: #eee;
- border: 1px solid #ccc;
- border-bottom: 0px;
- border-top: 0px;
- border-radius: 0px;
- border-top-width: 0px;
- margin-right: 0px;
- border-right-width: 0px;
- text-align: center;
+ background-color: #eee;
+ border: 1px solid #ccc;
+ border-bottom: 0px;
+ border-top: 0px;
+ border-radius: 0px;
+ border-top-width: 0px;
+ margin-right: 0px;
+ border-right-width: 0px;
+ text-align: center;
}
-.nav-tabs .filter-group-link.active a{
- background-color: #FFF;
+.nav-tabs .filter-group-link.active a {
+ background-color: #fff;
}
-.nav-tabs .filter-group-link a:hover{
- border-color: #CCC;
+.nav-tabs .filter-group-link a:hover {
+ border-color: #ccc;
}
-.filter-group-link{
- background-color: #eee;
+.filter-group-link {
+ background-color: #eee;
}
.filter-groups.vertical .filter-group-link {
float: none;
@@ -6750,78 +6868,78 @@ body.mapMode{
.filter-groups.vertical .filter-group-link a,
.filter-groups.vertical .nav-tabs .filter-group-link a,
.filter-groups.vertical .nav-tabs .filter-group-link.active a {
- border: 0px;
- text-align: left;
- padding-left: 0px;
-}
-.filter .ui-slider{
- position: relative;
- height: 6px;
- margin-top: 15px;
- margin-bottom: 17px;
- border-radius: 4px;
- border: 0px;
- background: #CCC;
- width: calc(100% - 10px);
-}
-.filter .ui-slider-handle{
+ border: 0px;
+ text-align: left;
+ padding-left: 0px;
+}
+.filter .ui-slider {
+ position: relative;
+ height: 6px;
+ margin-top: 15px;
+ margin-bottom: 17px;
+ border-radius: 4px;
+ border: 0px;
+ background: #ccc;
+ width: calc(100% - 10px);
+}
+.filter .ui-slider-handle {
width: 7px;
height: 15px;
margin-top: -1px;
}
-.filters-title{
- text-transform: uppercase;
- color: #999;
- font-size: .9em;
- font-weight: normal;
- margin-top: 0px;
+.filters-title {
+ text-transform: uppercase;
+ color: #999;
+ font-size: 0.9em;
+ font-weight: normal;
+ margin-top: 0px;
}
-.catalog h5.result-header-count{
- letter-spacing: 0.015em;
- margin-left: 0.6rem;
- margin-bottom: 1rem;
+.catalog h5.result-header-count {
+ letter-spacing: 0.015em;
+ margin-left: 0.6rem;
+ margin-bottom: 1rem;
}
/* vertical filter groups */
.filter-groups.vertical {
- display: grid;
- width: 100%;
- box-sizing: border-box;
+ display: grid;
+ width: 100%;
+ box-sizing: border-box;
}
.filter-groups.vertical .filters-container {
- display: grid;
- grid-auto-rows: min-content;
- width: 100%;
+ display: grid;
+ grid-auto-rows: min-content;
+ width: 100%;
}
-.filter-groups:not(.vertical) .filters-title{
+.filter-groups:not(.vertical) .filters-title {
display: none;
}
-.filter-groups.vertical .filters-title{
+.filter-groups.vertical .filters-title {
display: none;
- font-size: .85em;
+ font-size: 0.85em;
}
-.filter-groups.vertical .filters-title .clear-all{
+.filter-groups.vertical .filters-title .clear-all {
margin-left: 10px;
}
-.filter-groups.vertical .filters-title .clear-all .icon{
+.filter-groups.vertical .filters-title .clear-all .icon {
float: right;
margin-top: 2px;
font-size: 1.2em;
}
-.filters-title .clear-all{
- margin-left: 10px;
+.filters-title .clear-all {
+ margin-left: 10px;
}
-.filters-header label{
+.filters-header label {
font-weight: bold;
}
-.filters-header .applied-filters{
- min-height: 25px;
+.filters-header .applied-filters {
+ min-height: 25px;
}
-.filters-header .applied-filters .applied-filter{
+.filters-header .applied-filters .applied-filter {
background-color: #166194;
- color: #FFF;
+ color: #fff;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
@@ -6832,124 +6950,128 @@ body.mapMode{
font-weight: normal;
padding: 5px;
margin-right: 10px;
- line-height: 1.1;
+ line-height: 1.1;
}
-.filter-groups.vertical .applied-filters .applied-filter{
+.filter-groups.vertical .applied-filters .applied-filter {
display: block;
position: relative;
}
-.filter-groups.vertical .filters-header .applied-filters .applied-filter > span{
+.filter-groups.vertical
+ .filters-header
+ .applied-filters
+ .applied-filter
+ > span {
width: calc(100% - 20px);
display: inline-block;
white-space: normal;
}
-.filters-header .applied-filters .applied-filter .label{
+.filters-header .applied-filters .applied-filter .label {
font-weight: bold;
margin-right: 10px;
background-color: transparent;
}
-.filter-groups.vertical .applied-filters .applied-filter .remove-filter{
+.filter-groups.vertical .applied-filters .applied-filter .remove-filter {
font-size: 1.3em;
position: absolute;
right: 5px;
}
-.filter-groups.vertical .filters-header .applied-filters{
+.filter-groups.vertical .filters-header .applied-filters {
min-height: 0px;
}
-.filters-header > .filter{
- float: left;
- margin-right: 10px;
+.filters-header > .filter {
+ float: left;
+ margin-right: 10px;
width: auto;
}
-.filter-groups.vertical .filters-header > .filter{
+.filter-groups.vertical .filters-header > .filter {
float: none;
- margin-right:0px;
+ margin-right: 0px;
}
-.filter-groups:not(.vertical) .filters-header > .filter{
+.filter-groups:not(.vertical) .filters-header > .filter {
display: inline-block;
}
-.filter-groups:not(.vertical) .filters-header > .filter input[type='text']{
+.filter-groups:not(.vertical) .filters-header > .filter input[type="text"] {
width: auto;
}
.filter.date .min,
.filter.date .max,
.filter.numeric .min,
-.filter.numeric .max{
- max-width: 30%;
+.filter.numeric .max {
+ max-width: 30%;
}
.filter.date .to,
-.filter.numeric .to{
- width: 30%;
- width: calc(40% - 50px);
- text-align: center;
- display: inline-block;
- vertical-align: middle;
- color: #7c7a7a;
+.filter.numeric .to {
+ width: 30%;
+ width: calc(40% - 50px);
+ text-align: center;
+ display: inline-block;
+ vertical-align: middle;
+ color: #7c7a7a;
}
.filter.date .min,
-.filter.numeric .min{
- float: left;
- margin-right: 0.7rem;
+.filter.numeric .min {
+ float: left;
+ margin-right: 0.7rem;
}
.filter.date .max,
-.filter.numeric .max{
- float: right;
- margin-left: 10px;
+.filter.numeric .max {
+ float: right;
+ margin-left: 10px;
}
-.filter.boolean input{
+.filter.boolean input {
height: auto;
font-size: 3.2em;
margin-right: 10px;
- margin-top: 0;
+ margin-top: 0;
}
-.filter.boolean span{
- display: inline;
+.filter.boolean span {
+ display: inline;
white-space: normal;
}
-.filter.numeric input{
- max-width: 70px;
+.filter.numeric input {
+ max-width: 70px;
}
-.filter.toggle .can-toggle > span{
- margin-bottom: 5px;
- display: block;
+.filter.toggle .can-toggle > span {
+ margin-bottom: 5px;
+ display: block;
}
-.filter input[type='text']{
- width: 80%;
+.filter input[type="text"] {
+ width: 80%;
}
/**** collapsible filter components ****/
/* the button to show/hide the filter */
.collapse-toggle {
- /* make a size variable */
- --size: 1.55rem;
- display: flex;
- margin-left: auto;
- padding: 0;
- height: var(--size);
- width: var(--size);
- border: none;
- background: #ebeff3;
- border-radius: 50%;
- justify-content: center;
- align-items: center;
+ /* make a size variable */
+ --size: 1.55rem;
+ display: flex;
+ margin-left: auto;
+ padding: 0;
+ height: var(--size);
+ width: var(--size);
+ border: none;
+ background: #ebeff3;
+ border-radius: 50%;
+ justify-content: center;
+ align-items: center;
}
.collapse-toggle > .icon {
- font-size: 1.3rem !important;
- margin: 0 !important;
- transition: transform 0.1s ease-in-out;
- height: 100%;
+ font-size: 1.3rem !important;
+ margin: 0 !important;
+ transition: transform 0.1s ease-in-out;
+ height: 100%;
}
.filter.collapsed {
- padding-bottom: 0rem !important;
+ padding-bottom: 0rem !important;
}
.filter.collapsed label ~ div {
- display: none;
+ display: none;
}
.filter.collapsed .collapse-toggle > .icon {
- /* flip it around */
- transform: rotate(180deg);
+ /* flip it around */
+ transform: rotate(180deg);
}
/*
@@ -6958,73 +7080,76 @@ body.mapMode{
the component look nearly identical to other filters (that use native form
elements)
*/
-.filter-groups .filter.annotation-filter .ui.fluid.dropdown>.dropdown.icon {
- top: 25%;
+.filter-groups .filter.annotation-filter .ui.fluid.dropdown > .dropdown.icon {
+ top: 25%;
}
.filter-groups .filter.annotation-filter .dropdown.search {
- padding: 0;
- font-size: 100%;
- box-sizing: border-box;
- border-color: #ccc;
- border-radius: 3px;
+ padding: 0;
+ font-size: 100%;
+ box-sizing: border-box;
+ border-color: #ccc;
+ border-radius: 3px;
}
.filter-groups .filter.annotation-filter .dropdown.multiple.ui {
- padding: 0;
+ padding: 0;
}
-.filter-groups .filter.annotation-filter .ui.multiple.search.dropdown > input.search {
- font-size: inherit;
- line-height: 20px;
- margin: 4px 6px;
+.filter-groups
+ .filter.annotation-filter
+ .ui.multiple.search.dropdown
+ > input.search {
+ font-size: inherit;
+ line-height: 20px;
+ margin: 4px 6px;
}
/* End Overrides for default Bootstrap styling for the searchable select */
-.filter-group .filter select{
+.filter-group .filter select {
width: 100%;
}
-.filter-group > .row-fluid:not(:first-child){
- margin-top: 20px;
+.filter-group > .row-fluid:not(:first-child) {
+ margin-top: 20px;
}
-.applied-filter .icon.remove-filter{
- opacity: .7;
- margin-left: 10px;
- color: inherit;
+.applied-filter .icon.remove-filter {
+ opacity: 0.7;
+ margin-left: 10px;
+ color: inherit;
}
-.applied-filters{
- margin: 0px;
+.applied-filters {
+ margin: 0px;
}
-.filters-header{
- margin: 10px;
+.filters-header {
+ margin: 10px;
}
-.filter-groups.vertical .filters-header{
+.filter-groups.vertical .filters-header {
margin: 0px;
- border-bottom: 1px dashed #CCC;
+ border-bottom: 1px dashed #ccc;
margin-bottom: 1.5rem;
}
-#portal-filters{
- border: 1px solid #CCC;
- box-sizing: border-box;
+#portal-filters {
+ border: 1px solid #ccc;
+ box-sizing: border-box;
}
-#portal-filters > .tab-content{
- padding: 10px 20px;
+#portal-filters > .tab-content {
+ padding: 10px 20px;
}
-#portal-filters > .nav-tabs{
- margin-bottom: 0px;
- border: 1px solid #CCC;
- border-left: 0px;
- border-right: 0px;
+#portal-filters > .nav-tabs {
+ margin-bottom: 0px;
+ border: 1px solid #ccc;
+ border-left: 0px;
+ border-right: 0px;
background-color: #eee;
}
-#portal-filters > .nav-tabs > li:first-child > a{
- border-left: 0px;
+#portal-filters > .nav-tabs > li:first-child > a {
+ border-left: 0px;
}
-#portal-filters > .nav-tabs > li:last-child > a{
- border-right: 0px;
+#portal-filters > .nav-tabs > li:last-child > a {
+ border-right: 0px;
}
/* Hide the Spatial Filter elements until we fully support spatial filters */
-.portal-editor .filter-groups.vertical .applied-filter.label.custom{
+.portal-editor .filter-groups.vertical .applied-filter.label.custom {
display: none;
}
.portal-editor .toggle-map-filter {
@@ -7035,10 +7160,12 @@ body.mapMode{
.can-toggle {
position: relative;
}
-.can-toggle *, .can-toggle *:before, .can-toggle *:after {
+.can-toggle *,
+.can-toggle *:before,
+.can-toggle *:after {
-moz-box-sizing: border-box;
- box-sizing: border-box;
- font-size: 1em;
+ box-sizing: border-box;
+ font-size: 1em;
}
.can-toggle input[type="checkbox"] {
opacity: 0;
@@ -7061,21 +7188,21 @@ body.mapMode{
}
.can-toggle label {
-webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
position: relative;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
- -ms-flex-align: center;
- align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
}
.can-toggle label .can-toggle__label-text {
-webkit-flex: 1;
- -ms-flex: 1;
- flex: 1;
+ -ms-flex: 1;
+ flex: 1;
padding-left: 32px;
}
.can-toggle label .can-toggle-switch {
@@ -7094,9 +7221,9 @@ body.mapMode{
text-align: center;
background: white;
-webkit-border-radius: 4px;
- border-radius: 4px;
+ border-radius: 4px;
-webkit-transform: translate3d(0, 0, 0);
- transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
}
.can-toggle input[type="checkbox"][disabled] ~ label {
color: rgba(119, 119, 119, 0.5);
@@ -7125,18 +7252,24 @@ body.mapMode{
.can-toggle input[type="checkbox"]:checked:hover ~ label .can-toggle-switch {
background-color: #00689d;
}
-.can-toggle input[type="checkbox"]:checked:focus ~ label .can-toggle-switch:after,
-.can-toggle input[type="checkbox"]:checked:hover ~ label .can-toggle-switch:after {
+.can-toggle
+ input[type="checkbox"]:checked:focus
+ ~ label
+ .can-toggle-switch:after,
+.can-toggle
+ input[type="checkbox"]:checked:hover
+ ~ label
+ .can-toggle-switch:after {
color: #00689d;
}
.can-toggle label .can-toggle__label-text {
-webkit-flex: 1;
- -ms-flex: 1;
- flex: 1;
+ -ms-flex: 1;
+ flex: 1;
}
.can-toggle label .can-toggle-switch {
-webkit-transition: background-color 0.3s cubic-bezier(0, 1, 0.5, 1);
- transition: background-color 0.3s cubic-bezier(0, 1, 0.5, 1);
+ transition: background-color 0.3s cubic-bezier(0, 1, 0.5, 1);
background: #848484;
}
.can-toggle label .can-toggle-switch:before {
@@ -7147,14 +7280,22 @@ body.mapMode{
transition: transform 0.3s cubic-bezier(0, 1, 0.5, 1);
color: #777;
}
-.can-toggle input[type="checkbox"]:focus ~ label .can-toggle-switch:after, .can-toggle input[type="checkbox"]:hover ~ label .can-toggle-switch:after {
+.can-toggle input[type="checkbox"]:focus ~ label .can-toggle-switch:after,
+.can-toggle input[type="checkbox"]:hover ~ label .can-toggle-switch:after {
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.4);
}
.can-toggle input[type="checkbox"]:checked ~ label .can-toggle-switch:after {
-webkit-transform: translate3d(65px, 0, 0);
- transform: translate3d(65px, 0, 0);
-}
-.can-toggle input[type="checkbox"]:checked:focus ~ label .can-toggle-switch:after, .can-toggle input[type="checkbox"]:checked:hover ~ label .can-toggle-switch:after {
+ transform: translate3d(65px, 0, 0);
+}
+.can-toggle
+ input[type="checkbox"]:checked:focus
+ ~ label
+ .can-toggle-switch:after,
+.can-toggle
+ input[type="checkbox"]:checked:hover
+ ~ label
+ .can-toggle-switch:after {
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.4);
}
.can-toggle label {
@@ -7163,8 +7304,8 @@ body.mapMode{
.can-toggle label .can-toggle-switch {
height: 1.1em;
-webkit-flex: 0 0 134px;
- -ms-flex: 0 0 134px;
- flex: 0 0 134px;
+ -ms-flex: 0 0 134px;
+ flex: 0 0 134px;
border-radius: 4px;
}
.can-toggle label .can-toggle-switch:before {
@@ -7183,14 +7324,31 @@ body.mapMode{
.can-toggle label .can-toggle-switch:hover:after {
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.4);
}
-.can-toggle.can-toggle-small input[type="checkbox"]:focus ~ label .can-toggle-switch:after, .can-toggle.can-toggle-small input[type="checkbox"]:hover ~ label .can-toggle-switch:after {
+.can-toggle.can-toggle-small
+ input[type="checkbox"]:focus
+ ~ label
+ .can-toggle-switch:after,
+.can-toggle.can-toggle-small
+ input[type="checkbox"]:hover
+ ~ label
+ .can-toggle-switch:after {
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.4);
}
-.can-toggle.can-toggle-small input[type="checkbox"]:checked ~ label .can-toggle-switch:after {
+.can-toggle.can-toggle-small
+ input[type="checkbox"]:checked
+ ~ label
+ .can-toggle-switch:after {
-webkit-transform: translate3d(98px, 0, 0);
- transform: translate3d(98px, 0, 0);
-}
-.can-toggle.can-toggle-small input[type="checkbox"]:checked:focus ~ label .can-toggle-switch:after, .can-toggle.can-toggle-small input[type="checkbox"]:checked:hover ~ label .can-toggle-switch:after {
+ transform: translate3d(98px, 0, 0);
+}
+.can-toggle.can-toggle-small
+ input[type="checkbox"]:checked:focus
+ ~ label
+ .can-toggle-switch:after,
+.can-toggle.can-toggle-small
+ input[type="checkbox"]:checked:hover
+ ~ label
+ .can-toggle-switch:after {
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.4);
}
.can-toggle.can-toggle-small label {
@@ -7200,8 +7358,8 @@ body.mapMode{
.can-toggle.can-toggle-small label .can-toggle-switch {
height: 2.5em;
-webkit-flex: 0 0 200px;
- -ms-flex: 0 0 200px;
- flex: 0 0 200px;
+ -ms-flex: 0 0 200px;
+ flex: 0 0 200px;
border-radius: 4px;
-webkit-border-radius: 4px;
}
@@ -7223,27 +7381,27 @@ body.mapMode{
}
/** Autocompletes **/
-.ui-autocomplete{
+.ui-autocomplete {
width: 246px;
- background-color: #FFFFFF;
+ background-color: #ffffff;
border: 1px solid #999999;
padding: 10px;
list-style: none;
overflow-y: scroll;
- -webkit-transform: translate3d(0,0,0);
+ -webkit-transform: translate3d(0, 0, 0);
max-height: 150px;
z-index: 99999;
overflow-x: hidden;
}
-.ui-autocomplete .ui-menu-item{
+.ui-autocomplete .ui-menu-item {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 0px 5px;
width: 100%;
}
-.ui-autocomplete .ui-menu-item:hover{
- background-color: #DDD;
+.ui-autocomplete .ui-menu-item:hover {
+ background-color: #ddd;
cursor: pointer;
}
/******************************************
@@ -7251,45 +7409,45 @@ body.mapMode{
******************************************/
/* Make the sidebar a bit wider since filters will be used a lot while editing a collection */
-.edit-collection #sidebar{
+.edit-collection #sidebar {
width: 0px;
- border-right: none;
+ border-right: none;
}
.edit-collection .map-toggle-container {
- background: #ededed;
+ background: #ededed;
}
.edit-collection .map-toggle-container > a {
- color: #333;
+ color: #333;
}
-.edit-collection #results-container{
+.edit-collection #results-container {
width: 100%;
- overflow-y: auto !important;
- overflow-x: hidden !important;
- margin-left: 0;
+ overflow-y: auto !important;
+ overflow-x: hidden !important;
+ margin-left: 0;
}
.edit-collection .result-header-count {
- font-size: 1rem;
+ font-size: 1rem;
font-weight: 500;
- margin-bottom: 2rem;
+ margin-bottom: 2rem;
}
-.edit-collection .mapMode #results-container{
+.edit-collection .mapMode #results-container {
width: 49%;
}
-.edit-collection .mapMode #map-container{
- width: 49%;
+.edit-collection .mapMode #map-container {
+ width: 49%;
}
/* The Save and Cancel buttons */
-.collection-controls{
+.collection-controls {
margin-top: 20px;
margin-bottom: 20px;
}
-.collection-controls .save{
+.collection-controls .save {
width: calc(50% - 10px);
box-sizing: border-box;
margin-right: 10px;
}
-.collection-controls .cancel{
+.collection-controls .cancel {
width: calc(50% - 10px);
box-sizing: border-box;
}
@@ -7312,38 +7470,47 @@ form .label,
vertical-align: inherit;
}
.has-error input,
-input.has-error{
+input.has-error {
border-color: #a94442;
- -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.has-error input:focus,
-input.has-error:focus{
+input.has-error:focus {
border-color: #843534;
- -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;
+ -webkit-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #ce8483;
+ box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #ce8483;
}
.has-error label,
-.has-error > div.text-left{ /* div.text-left is for Metacat-specific (2.4.X and before) login markup */
- color: #a94442;
- font-weight: bold;
+.has-error > div.text-left {
+ /* div.text-left is for Metacat-specific (2.4.X and before) login markup */
+ color: #a94442;
+ font-weight: bold;
}
-.has-warning label{
- color: #8a6d3b;
- font-weight: bold;
+.has-warning label {
+ color: #8a6d3b;
+ font-weight: bold;
}
-.has-warning .help-block{
- color: #8a6d3b;
+.has-warning .help-block {
+ color: #8a6d3b;
}
.has-warning .form-control {
border-color: #8a6d3b;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.has-warning .form-control:focus {
border-color: #66512c;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
+ -webkit-box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #c0a16b;
+ box-shadow:
+ inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 6px #c0a16b;
}
.has-warning .input-group-addon {
color: #8a6d3b;
@@ -7360,114 +7527,112 @@ form .text-left > .icon {
.input-append .tooltip-inner {
white-space: normal;
}
-.input-append .alert{
- font-size: 14px;
- margin-top: 10px;
+.input-append .alert {
+ font-size: 14px;
+ margin-top: 10px;
}
-.form-group input.control-group{
- line-height: 30px;
+.form-group input.control-group {
+ line-height: 30px;
}
.form-group input ~ .help-text,
.form-group input ~ .help-text ~ .form-feedback {
- display: block;
- margin-left: 24%;
+ display: block;
+ margin-left: 24%;
}
-.inline-input-group.controls-well{
- margin-bottom: 20px;
- padding: 10px 20px;
- clear: both;
+.inline-input-group.controls-well {
+ margin-bottom: 20px;
+ padding: 10px 20px;
+ clear: both;
}
-.inline-input-group label.inline{
- line-height: 2em;
+.inline-input-group label.inline {
+ line-height: 2em;
}
-.inline-input-group > .input-append{
- position: relative;
+.inline-input-group > .input-append {
+ position: relative;
}
.input-append > .notification {
- position: absolute;
- right: 0;
- top: -25px;
- background-color: #FFF;
-}
-label.inline{
- display: inline;
- margin-right: 10px;
- vertical-align: middle;
-}
-input.subtle{
- width: 50px;
- font-size: .9em;
- -o-transition: width .2s ease-out;
- -moz-transition: width .2s ease-out;
- -webkit-transition: width .2s ease-out;
- -ms-transition: width .2s ease-out;
- -kthtml-transition: width .2s ease-out;
- transition: width .2s ease-out;
-}
-.input-append input.subtle{
- font-size: 13px;
-}
-input.subtle:focus
-{
- width: 200px;
+ position: absolute;
+ right: 0;
+ top: -25px;
+ background-color: #fff;
+}
+label.inline {
+ display: inline;
+ margin-right: 10px;
+ vertical-align: middle;
+}
+input.subtle {
+ width: 50px;
+ font-size: 0.9em;
+ -o-transition: width 0.2s ease-out;
+ -moz-transition: width 0.2s ease-out;
+ -webkit-transition: width 0.2s ease-out;
+ -ms-transition: width 0.2s ease-out;
+ -kthtml-transition: width 0.2s ease-out;
+ transition: width 0.2s ease-out;
+}
+.input-append input.subtle {
+ font-size: 13px;
+}
+input.subtle:focus {
+ width: 200px;
}
.input-append .uneditable-input,
.input-append input,
.input-append select,
.input-prepend .add-on:first-child,
-.input-prepend .btn:first-child{
+.input-prepend .btn:first-child {
-webkit-border-radius: 2px 0 0 2px;
-moz-border-radius: 2px 0 0 2px;
border-radius: 2px 0 0 2px;
}
-.jump-width-input{
- transition: width 1000s;
+.jump-width-input {
+ transition: width 1000s;
}
-.nav
-.input-submit{
- margin-left: 10px;
- height: 1em;
- line-height: 1em;
- border-color: #666;
- color: #666;
+.nav .input-submit {
+ margin-left: 10px;
+ height: 1em;
+ line-height: 1em;
+ border-color: #666;
+ color: #666;
}
-#funding-group .ui-helper-hidden-accessible{
- display: none;
+#funding-group .ui-helper-hidden-accessible {
+ display: none;
}
-#funding-group .ui-autocomplete{
- width: 500px;
- position: absolute;
- top: 10px;
- left: 10px;
+#funding-group .ui-autocomplete {
+ width: 500px;
+ position: absolute;
+ top: 10px;
+ left: 10px;
}
-#funding-group .ui-autocomplete .ui-menu-item{
- width: 500px;
- line-height: 1.4em;
+#funding-group .ui-autocomplete .ui-menu-item {
+ width: 500px;
+ line-height: 1.4em;
}
-#funding-group .ui-autocomplete .ui-menu-item a{
- font-size: 1em;
+#funding-group .ui-autocomplete .ui-menu-item a {
+ font-size: 1em;
}
-.input-help-msg{
- padding: .5em 0;
+.input-help-msg {
+ padding: 0.5em 0;
}
-.cell-icon{
- width: 1em;
+.cell-icon {
+ width: 1em;
}
-.progress{
- margin-top: 2em;
+.progress {
+ margin-top: 2em;
}
-.ui-autocomplete-container.collapse.in{
- overflow: visible;
+.ui-autocomplete-container.collapse.in {
+ overflow: visible;
}
-#collapseParties .popover{
- z-index: 0;
+#collapseParties .popover {
+ z-index: 0;
}
-#RegistryGuide > div{
- font-size: 1.8em;
+#RegistryGuide > div {
+ font-size: 1.8em;
}
.text-container input,
-.text-container textarea{
- width: 100%;
+.text-container textarea {
+ width: 100%;
}
/*******************************************
@@ -7475,122 +7640,124 @@ Query builder & query rule view
******************************************/
.query-builder {
- box-shadow: 0 1px 6px rgba(0,0,0,0.16), 0 1px 8px -3px rgba(0,0,0,0.23);
- padding: 0;
- margin: 2rem 1rem 2.5rem;
- width: 100%;
- max-width: 1400px;
- border-radius: 3px;
- box-sizing: border-box;
+ box-shadow:
+ 0 1px 6px rgba(0, 0, 0, 0.16),
+ 0 1px 8px -3px rgba(0, 0, 0, 0.23);
+ padding: 0;
+ margin: 2rem 1rem 2.5rem;
+ width: 100%;
+ max-width: 1400px;
+ border-radius: 3px;
+ box-sizing: border-box;
}
/* For query builders that are within a query rule row */
-.query-builder.nested{
- box-shadow: none;
- margin: 0 0 0.3rem 0;
+.query-builder.nested {
+ box-shadow: none;
+ margin: 0 0 0.3rem 0;
}
.query-builder-title {
- font-weight: 500;
+ font-weight: 500;
font-size: 1.1rem;
- width: 100%;
- margin: 0 0 1.2rem 0;
- border-bottom: 1px solid #eee;
- padding: 1.4rem 2rem 1.2rem;
- box-sizing: border-box;
+ width: 100%;
+ margin: 0 0 1.2rem 0;
+ border-bottom: 1px solid #eee;
+ padding: 1.4rem 2rem 1.2rem;
+ box-sizing: border-box;
}
-.query-builder.nested .query-builder-title{
- padding: 1rem 1rem 1rem 0.1rem;
+.query-builder.nested .query-builder-title {
+ padding: 1rem 1rem 1rem 0.1rem;
}
.query-builder .exclude-input,
.query-builder .operator-input {
- display: inline-block;
+ display: inline-block;
}
.query-builder .buttons-container {
- margin-left: 2rem;
+ margin-left: 2rem;
}
.query-builder.nested .buttons-container {
- margin-left: 0.1rem;
+ margin-left: 0.1rem;
}
.query-builder .btn.add-rule,
.query-builder .btn.add-rule-group {
- padding: 0.25rem 0.55rem 0.25rem 0.25rem;
- margin-right: 0.5rem;
+ padding: 0.25rem 0.55rem 0.25rem 0.25rem;
+ margin-right: 0.5rem;
}
.rules-container {
- display: grid;
- grid-template-rows: auto;
- grid-template-columns: 100%;
- grid-gap: 1.5rem;
- padding: 1.9rem 2rem;
+ display: grid;
+ grid-template-rows: auto;
+ grid-template-columns: 100%;
+ grid-gap: 1.5rem;
+ padding: 1.9rem 2rem;
}
-.query-builder.nested .rules-container{
- padding: 1.9rem 0 0.9rem 0.3rem;
+.query-builder.nested .rules-container {
+ padding: 1.9rem 0 0.9rem 0.3rem;
}
.query-rule {
- display: grid;
- grid-template-columns: 3.9rem 0.9fr 0.65fr 0.9fr 1.5rem;
- grid-template-rows: 1fr;
- grid-template-areas: "info field operator value remove";
- grid-gap: 1.5rem;
- align-items: top;
+ display: grid;
+ grid-template-columns: 3.9rem 0.9fr 0.65fr 0.9fr 1.5rem;
+ grid-template-rows: 1fr;
+ grid-template-areas: "info field operator value remove";
+ grid-gap: 1.5rem;
+ align-items: top;
}
/* For query rules that contain a query builder */
.query-rule.rule-group {
- grid-template-columns: 3.9rem auto 1.5rem;
- grid-template-areas: "info query-builder remove";
+ grid-template-columns: 3.9rem auto 1.5rem;
+ grid-template-areas: "info query-builder remove";
}
.query-rule .rule-info {
- grid-area: info;
- font-weight: 700;
- font-size: 0.9rem;
- color: #00689d;
- color: var(--rule-color);
- display: flex;
- justify-content: space-between;
+ grid-area: info;
+ font-weight: 700;
+ font-size: 0.9rem;
+ color: #00689d;
+ color: var(--rule-color);
+ display: flex;
+ justify-content: space-between;
}
.query-rule .rule-info:after {
- content:"";
- float:right;
+ content: "";
+ float: right;
background: #00689d;
- background: var(--rule-color);
- width:3px;
+ background: var(--rule-color);
+ width: 3px;
height: calc(100% + 0.2rem);
border-radius: 1.5px;
}
-.query-rule .field{
- grid-area: field;
+.query-rule .field {
+ grid-area: field;
}
-.query-rule .operator{
- grid-area: operator;
+.query-rule .operator {
+ grid-area: operator;
}
-.query-rule .value{
- grid-area: value;
+.query-rule .value {
+ grid-area: value;
}
/* the button used to remove a rule */
.query-rule .remove-rule {
- font-size: 1.5rem;
- grid-area: remove;
- cursor: pointer;
- opacity: 0.2;
- display: grid;
- align-content: center;
- justify-self: flex-end;
+ font-size: 1.5rem;
+ grid-area: remove;
+ cursor: pointer;
+ opacity: 0.2;
+ display: grid;
+ align-content: center;
+ justify-self: flex-end;
}
.query-rule.rule-group > .remove-rule {
- align-content: top;
- height: fit-content;
- margin-top: 4.1rem;
+ align-content: top;
+ height: fit-content;
+ margin-top: 4.1rem;
}
.searchable-select-label {
@@ -7599,32 +7766,32 @@ Query builder & query rule view
}
.search-select-tooltip {
- /* one greater than the .modal z-index */
- z-index: 1051
+ /* one greater than the .modal z-index */
+ z-index: 1051;
}
/* make the number & date filter inputs match the operator and field inputs */
.query-rule .filter input {
- padding: .60em 1em;
- border: 1px solid rgba(34,36,38,.15);
- margin: 0;
+ padding: 0.6em 1em;
+ border: 1px solid rgba(34, 36, 38, 0.15);
+ margin: 0;
}
.query-rule .slider-container {
- text-align: center;
+ text-align: center;
}
.query-rule .filter label {
- display: none;
+ display: none;
}
.query-rule .filter .ui-slider {
- margin: 0.6rem 0 1rem;
+ margin: 0.6rem 0 1rem;
}
.query-rule .filter.date .to,
.query-rule .filter.numeric .to {
- width: 10%;
- text-align: center;
+ width: 10%;
+ text-align: center;
}
.query-rule .filter.date .max,
.query-rule .filter.numeric .max {
@@ -7632,147 +7799,151 @@ Query builder & query rule view
}
/* ---- Icons for the operator options not included in font-awesome ---- */
-.icon-equal, .icon-not-equal, .icon-less-than, .icon-greater-than,
-.icon-less-than-or-eq, .icon-greater-than-or-eq {
- font-family: serif;
- font-weight: 900;
- font-size: larger;
- line-height: 14px;
+.icon-equal,
+.icon-not-equal,
+.icon-less-than,
+.icon-greater-than,
+.icon-less-than-or-eq,
+.icon-greater-than-or-eq {
+ font-family: serif;
+ font-weight: 900;
+ font-size: larger;
+ line-height: 14px;
}
-.icon-equal:before{
- content: "="
+.icon-equal:before {
+ content: "=";
}
-.icon-not-equal:before{
- content: "\2260"
+.icon-not-equal:before {
+ content: "\2260";
}
-.icon-less-than:before{
- content: "<"
+.icon-less-than:before {
+ content: "<";
}
-.icon-greater-than:before{
- content: ">"
+.icon-greater-than:before {
+ content: ">";
}
-.icon-less-than-or-eq:before{
- content: "≤"
+.icon-less-than-or-eq:before {
+ content: "≤";
}
-.icon-greater-than-or-eq:before{
- content: "≥"
+.icon-greater-than-or-eq:before {
+ content: "≥";
}
/*******************************************
Editor View
******************************************/
-.Editor #Footer{
- display: none;
+.Editor #Footer {
+ display: none;
}
-.Editor #Navbar{
- padding-bottom: 0px;
+.Editor #Navbar {
+ padding-bottom: 0px;
}
-.Editor #logo{
- height: 40px;
- margin-top: 5px;
+.Editor #logo {
+ height: 40px;
+ margin-top: 5px;
}
-.Editor .header .nav > li > a{
- font-size: 1em;
+.Editor .header .nav > li > a {
+ font-size: 1em;
}
-.Editor .header .nav li a.btn{
- font-size: .7em;
+.Editor .header .nav li a.btn {
+ font-size: 0.7em;
}
-.Editor .header .border-image{
- height: 15px;
- margin-bottom: 5px;
+.Editor .header .border-image {
+ height: 15px;
+ margin-bottom: 5px;
}
.editor-view {
- margin: 0px;
- width: 100%;
- max-width: 100%;
+ margin: 0px;
+ width: 100%;
+ max-width: 100%;
}
-#editor-body{
- padding-bottom: 100px;
+#editor-body {
+ padding-bottom: 100px;
}
.Editor #Content {
- padding-top: 70px;
- padding-left: 0px;
- padding-right: 0px;
- max-width: 100%;
+ padding-top: 70px;
+ padding-left: 0px;
+ padding-right: 0px;
+ max-width: 100%;
}
#editor-header {
- border-bottom: 1px solid #DDDDDD;
+ border-bottom: 1px solid #dddddd;
}
-#editor-header .citation{
- padding: 1em;
- margin-bottom: 0px;
- font-size: 1.1em;
- line-height: 1.6em;
+#editor-header .citation {
+ padding: 1em;
+ margin-bottom: 0px;
+ font-size: 1.1em;
+ line-height: 1.6em;
}
-#editor-header .editor-controls{
+#editor-header .editor-controls {
min-height: 130px;
align-items: center;
display: flex;
margin-left: 40px;
height: 100%;
}
-#editor-header .editor-controls:empty{
+#editor-header .editor-controls:empty {
display: none;
}
-#editor-header .editor-controls .btn{
+#editor-header .editor-controls .btn {
align-self: center;
margin-top: 14px;
padding: 10px 15px;
}
.side-nav-item a {
- width: 100%;
- display: block;
- height: 3em;
- font-size: 1.3em;
- line-height: 3em;
- padding-left: 20px;
- box-sizing: border-box;
+ width: 100%;
+ display: block;
+ height: 3em;
+ font-size: 1.3em;
+ line-height: 3em;
+ padding-left: 20px;
+ box-sizing: border-box;
}
.side-nav-item a:focus {
- text-decoration: none;
+ text-decoration: none;
}
-.side-nav-item.error a{
- color: #da4e4e;
+.side-nav-item.error a {
+ color: #da4e4e;
}
.side-nav-item.error .active {
- background-color: #da4e4e;
- color: #FFFFFF;
+ background-color: #da4e4e;
+ color: #ffffff;
}
-.side-nav-item a .icon.hidden{
- display: none;
+.side-nav-item a .icon.hidden {
+ display: none;
}
ul.side-nav-items {
- margin-left: 0px;
+ margin-left: 0px;
}
.side-nav-item {
- list-style: none;
- display: block;
+ list-style: none;
+ display: block;
}
.side-nav {
- position: fixed;
- background-color: #EEE;
- height: 100%;
- max-width: 160px;
- min-width: 160px;
- margin-left: 0px;
- border-right: 1px solid #DDD;
-}
-.side-nav + .metadata-container{
- margin-left: 160px;
- padding-left: 20px;
+ position: fixed;
+ background-color: #eee;
+ height: 100%;
+ max-width: 160px;
+ min-width: 160px;
+ margin-left: 0px;
+ border-right: 1px solid #ddd;
+}
+.side-nav + .metadata-container {
+ margin-left: 160px;
+ padding-left: 20px;
}
.Editor #Footer {
- left: 160px;
- width: calc(100% - 160px);
- margin-left: 0px;
- margin-bottom: 0px;
+ left: 160px;
+ width: calc(100% - 160px);
+ margin-left: 0px;
+ margin-bottom: 0px;
}
-.Editor.rendering #Footer{
- margin-left: 0%;
- width: 100%;
+.Editor.rendering #Footer {
+ margin-left: 0%;
+ width: 100%;
}
-#editor-footer{
+#editor-footer {
position: fixed;
display: grid;
box-sizing: border-box;
@@ -7788,111 +7959,111 @@ ul.side-nav-items {
grid-template-areas: "trial-message save-controls";
gap: 0.5rem;
}
-#editor-footer .free-trial-message{
- color: #FFF;
+#editor-footer .free-trial-message {
+ color: #fff;
grid-area: trial-message;
}
-#editor-footer .free-trial-message .icon{
+#editor-footer .free-trial-message .icon {
height: 1.1em;
- fill: #FFF;
+ fill: #fff;
}
-.metadata-view #editor-footer{
+.metadata-view #editor-footer {
display: none;
left: 0px;
}
-#editor-footer a:not(.btn){
- color: #FFF;
+#editor-footer a:not(.btn) {
+ color: #fff;
font-weight: bold;
}
/* The "view last saved version" and "save" button */
-.editor-save-controls{
- justify-self: right;
- display: grid;
- grid-template-columns: auto auto;
- gap: 1rem;
- grid-area: save-controls;
+.editor-save-controls {
+ justify-self: right;
+ display: grid;
+ grid-template-columns: auto auto;
+ gap: 1rem;
+ grid-area: save-controls;
}
-.editor-save-controls > a{
- line-height: 1.5em;
- font-size: 1.1em;
- font-weight: bold;
- margin: 0;
- height: 1.5em;
- align-self: center;
+.editor-save-controls > a {
+ line-height: 1.5em;
+ font-size: 1.1em;
+ font-weight: bold;
+ margin: 0;
+ height: 1.5em;
+ align-self: center;
}
-.editor-save-controls > a:not(.btn){
- color: #FFF;
+.editor-save-controls > a:not(.btn) {
+ color: #fff;
}
-.editor-save-controls .metadata-container textarea{
- width: 100%;
- margin-bottom: 20px;
+.editor-save-controls .metadata-container textarea {
+ width: 100%;
+ margin-bottom: 20px;
}
-#cancel-metadata-prov{
- margin-left: 30px;
+#cancel-metadata-prov {
+ margin-left: 30px;
}
/* Editor package table container */
#data-package-container {
- overflow: auto;
- position: relative;
+ overflow: auto;
+ position: relative;
}
/* Editor package table */
#data-package-table {
- margin-bottom: 0px;
- width: calc(100% - 2px);
- border-collapse: separate;
+ margin-bottom: 0px;
+ width: calc(100% - 2px);
+ border-collapse: separate;
}
/* Editor package table headers */
#data-package-table-share,
#data-package-table-guide-status {
- text-align: center;
+ text-align: center;
}
/* Editor package table row */
.data-package-item td {
- vertical-align: middle;
- white-space: nowrap;
- position: relative;
+ vertical-align: middle;
+ white-space: nowrap;
+ position: relative;
}
.data-package-item td.name div {
- padding: 8px;
- width: fit-content;
- cursor: not-allowed;
+ padding: 8px;
+ width: fit-content;
+ cursor: not-allowed;
}
.data-package-item td.canRename div {
- cursor: text;
+ cursor: text;
}
-.data-package-item td.error > [contenteditable]{
- border: 2px solid red;
+.data-package-item td.error > [contenteditable] {
+ border: 2px solid red;
}
/* Editor package table column 1 */
.data-package-item td:first-of-type {
- padding-left: 20px;
- width: 100px;
+ padding-left: 20px;
+ width: 100px;
}
/* Editor package table file name column */
.data-package-item td.name {
- white-space: normal;
+ white-space: normal;
}
/* Editor package table size column */
.data-package-item td.size {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 100px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 100px;
}
/* Editor package table type column */
.data-package-item td.type {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 100px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 100px;
}
.data-package-item td.public {
@@ -7901,712 +8072,754 @@ ul.side-nav-items {
/* Editor package table sharing column */
.data-package-item td.sharing {
- width: 60px;
+ width: 60px;
}
-.data-package-item td input[type='checkbox']{
- margin-left: 30%;
- height: auto;
+.data-package-item td input[type="checkbox"] {
+ margin-left: 30%;
+ height: auto;
}
-.data-package-item .sharing .disabled{
- opacity: .5;
+.data-package-item .sharing .disabled {
+ opacity: 0.5;
}
/* Editor package table column 5 */
.data-package-item td:last-of-type {
- text-align: right;
- padding-right: 10px;
- padding-left: 0px;
- width: 100px;
+ text-align: right;
+ padding-right: 10px;
+ padding-left: 0px;
+ width: 100px;
}
-
/* Editor package table buttons */
.data-package-item td button {
- text-align: center;
- vertical-align: middle;
+ text-align: center;
+ vertical-align: middle;
}
-.data-package-item td button .icon{
- margin-left: 0px;
+.data-package-item td button .icon {
+ margin-left: 0px;
}
-.data-package-item .icon{
- margin-right: 10px;
- width: 1.4em;
+.data-package-item .icon {
+ margin-right: 10px;
+ width: 1.4em;
}
-.data-package-item .btn.edit{
- width: 85px;
- color: #19B36A;
+.data-package-item .btn.edit {
+ width: 85px;
+ color: #19b36a;
}
-.data-package-item .btn-group .btn{
- background-color: white;
+.data-package-item .btn-group .btn {
+ background-color: white;
}
.data-package-item .btn.warning {
- background-color: #FFF;
- color: #ffbc00;
+ background-color: #fff;
+ color: #ffbc00;
}
.data-package-item .btn.error,
.data-package-item .status .icon.danger,
-.data-package-item .status .icon.error{
- color: #FF0000;
+.data-package-item .status .icon.error {
+ color: #ff0000;
}
-.data-package-item .dropdown-menu{
- border-color: #CCC;
- border-width: 1px;
- border-style: solid;
- margin-top: 6px;
- min-width: 100px;
+.data-package-item .dropdown-menu {
+ border-color: #ccc;
+ border-width: 1px;
+ border-style: solid;
+ margin-top: 6px;
+ min-width: 100px;
}
.data-package-item .dropdown-menu .muted {
- color: #CCC;
+ color: #ccc;
}
-.data-package-item.error-saving .disable-layer{
- width: 100%;
+.data-package-item.error-saving .disable-layer {
+ width: 100%;
}
.data-package-item .btn-group .dropdown-toggle {
- border-bottom-left-radius: 0px;
- border-top-left-radius: 0px;
- width: 25px;
+ border-bottom-left-radius: 0px;
+ border-top-left-radius: 0px;
+ width: 25px;
}
-.data-package-item button{
- border-color: #CCC;
+.data-package-item button {
+ border-color: #ccc;
}
.data-package-item.remove-preview {
- opacity: 1;
+ opacity: 1;
}
.data-package-item.remove-preview .name,
.data-package-item.remove-preview .status,
.data-package-item.remove-preview .size,
.data-package-item.remove-preview .type,
-.data-package-item.remove-preview .type-icon{
- opacity: .3;
+.data-package-item.remove-preview .type-icon {
+ opacity: 0.3;
}
-.data-package-item.message-row td{
- padding-top: 3%;
+.data-package-item.message-row td {
+ padding-top: 3%;
}
-.data-package-item.message-row:hover td{
- background-color: #FFF;
+.data-package-item.message-row:hover td {
+ background-color: #fff;
}
-.data-package-item.message-row h2{
- font-size: 2.1em;
+.data-package-item.message-row h2 {
+ font-size: 2.1em;
}
-.data-package-item.message-row button{
- font-size: 1.2em;
- margin-top: 10px;
- display: block;
+.data-package-item.message-row button {
+ font-size: 1.2em;
+ margin-top: 10px;
+ display: block;
}
/* Editor package table status column */
-.data-package-item .status{
- width: 60px;
+.data-package-item .status {
+ width: 60px;
}
-.data-package-item .progress{
- margin: 0px;
+.data-package-item .progress {
+ margin: 0px;
}
-.data-package-item .progress-striped .bar{
- background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- -webkit-background-size: 40px 40px;
- -moz-background-size: 40px 40px;
- -o-background-size: 40px 40px;
- background-size: 40px 40px;
+.data-package-item .progress-striped .bar {
+ background-image: -webkit-gradient(
+ linear,
+ 0 100%,
+ 100% 0,
+ color-stop(0.25, rgba(255, 255, 255, 0.15)),
+ color-stop(0.25, transparent),
+ color-stop(0.5, transparent),
+ color-stop(0.5, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, rgba(255, 255, 255, 0.15)),
+ color-stop(0.75, transparent),
+ to(transparent)
+ );
+ background-image: -webkit-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -moz-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -o-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ -webkit-background-size: 40px 40px;
+ -moz-background-size: 40px 40px;
+ -o-background-size: 40px 40px;
+ background-size: 40px 40px;
}
-.data-package-item .status .icon{
- display: block;
- margin-left: auto;
- margin-right: auto;
+.data-package-item .status .icon {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
}
-.data-package-item .status{
- text-align: center;
+.data-package-item .status {
+ text-align: center;
}
-.status-tooltip div{
- white-space: normal;
+.status-tooltip div {
+ white-space: normal;
}
-.status-tooltip.error h6{
- color: red;
+.status-tooltip.error h6 {
+ color: red;
}
-.status-tooltip.error div{
- color: white;
+.status-tooltip.error div {
+ color: white;
}
-.data-package-item.loading{
- opacity: .6;
+.data-package-item.loading {
+ opacity: 0.6;
}
-.data-package-item .controls .btn.message{
+.data-package-item .controls .btn.message {
font-size: 12px;
}
-.data-package-item .controls .btn.message .icon{
+.data-package-item .controls .btn.message .icon {
margin-left: -5px;
margin-right: 5px;
}
/* Editor package table button dropdown */
.Editor #data-package-table td:last-of-type ul {
- border-radius: 4px;
- left: 0px;
- width: 107px;
- min-width: 85px;
+ border-radius: 4px;
+ left: 0px;
+ width: 107px;
+ min-width: 85px;
}
-
-#data-package-container + .ui-resizable-handle{
- border: 1px solid #D5D5D5;
- border-right-width: 0px;
- border-left-width: 0px;
- background-color: #FFF;
- height: 10px;
- width: 100%;
- bottom: 1px;
+#data-package-container + .ui-resizable-handle {
+ border: 1px solid #d5d5d5;
+ border-right-width: 0px;
+ border-left-width: 0px;
+ background-color: #fff;
+ height: 10px;
+ width: 100%;
+ bottom: 1px;
}
-#data-package-container + .ui-resizable-handle .icon{
- font-size: 15px;
- color: #D5D5D5;
- z-index: 91;
- margin: -3px auto 0px auto;
- display: block;
- width: 15px;
+#data-package-container + .ui-resizable-handle .icon {
+ font-size: 15px;
+ color: #d5d5d5;
+ z-index: 91;
+ margin: -3px auto 0px auto;
+ display: block;
+ width: 15px;
}
-#data-package-container + .ui-resizable-handle:hover{
- cursor:ns-resize;
+#data-package-container + .ui-resizable-handle:hover {
+ cursor: ns-resize;
}
-
-
-textarea.xlarge{
- min-height: 200px;
+textarea.xlarge {
+ min-height: 200px;
}
-textarea.medium{
- min-height: 50px;
+textarea.medium {
+ min-height: 50px;
}
/* Highlight drop zones with a dashed border */
.droppable {
- border: 2px dashed #1F254F;
-
+ border: 2px dashed #1f254f;
}
-.Editor .disable-layer{
- background-color: rgba(255, 255, 255, 0.6);
- height: 100%;
- width: 100%;
- position: absolute;
- top: 0px;
- z-index: 2;
+.Editor .disable-layer {
+ background-color: rgba(255, 255, 255, 0.6);
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ top: 0px;
+ z-index: 2;
}
.Editor .dropdown-menu li a i {
- vertical-align: middle;
+ vertical-align: middle;
}
#editor-body .alert {
- position: relative;
+ position: relative;
}
-.Editor #Content > .alert-container .alert{
- position: fixed;
+.Editor #Content > .alert-container .alert {
+ position: fixed;
left: 0px;
min-width: 100%;
z-index: 14;
margin-left: 0px;
top: 0px;
- max-width: 100%;
+ max-width: 100%;
}
-.Editor #Content > .alert-container .alert.non-fixed{
- position: relative;
+.Editor #Content > .alert-container .alert.non-fixed {
+ position: relative;
min-width: auto;
z-index: 0;
}
-.Editor #Content > .alert-container .alert{
- padding: 20px;
- font-weight: bold;
+.Editor #Content > .alert-container .alert {
+ padding: 20px;
+ font-weight: bold;
box-sizing: border-box;
- border-radius: 0px;
- -moz-border-radius: 0px;
- -webkit-border-radius: 0px;
+ border-radius: 0px;
+ -moz-border-radius: 0px;
+ -webkit-border-radius: 0px;
z-index: 999;
}
.Editor #Content > .alert-container .alert .container {
- width: auto;
- max-width: 940px;
- text-align: center;
-};
+ width: auto;
+ max-width: 940px;
+ text-align: center;
+}
.Editor input,
-.Editor select{
- /*padding: .5em 1em;*/
- height: 2.4em;
- box-sizing: border-box;
- font-size: 1em;
-}
-.Editor > #Navbar .navbar .navbar-inner .nav .input form input[type="text"]{
- display: inline-block;
- position: relative;
- width: 75px;
- height: 30px;
- margin: 0px 0px 0px 0px;
- padding: 0px 0px 0px 7px;
- border-radius: 4px 0 0 4px;
- font-size: 14px;
-}
-.Editor textarea{
- padding: .5em 1em;
- box-sizing: border-box;
- font-size: 1em;
+.Editor select {
+ /*padding: .5em 1em;*/
+ height: 2.4em;
+ box-sizing: border-box;
+ font-size: 1em;
+}
+.Editor > #Navbar .navbar .navbar-inner .nav .input form input[type="text"] {
+ display: inline-block;
+ position: relative;
+ width: 75px;
+ height: 30px;
+ margin: 0px 0px 0px 0px;
+ padding: 0px 0px 0px 7px;
+ border-radius: 4px 0 0 4px;
+ font-size: 14px;
+}
+.Editor textarea {
+ padding: 0.5em 1em;
+ box-sizing: border-box;
+ font-size: 1em;
border-radius: 3px;
width: 100%;
}
-.Editor .checkbox{
- clear: both;
- margin: 1em auto;
+.Editor .checkbox {
+ clear: both;
+ margin: 1em auto;
}
-.Editor .checkbox input{
- padding: 0px;
- height: auto;
+.Editor .checkbox input {
+ padding: 0px;
+ height: auto;
}
.checkbox .disabled,
-.checkbox .disabled + .text{
- opacity: .5;
+.checkbox .disabled + .text {
+ opacity: 0.5;
}
.Editor input.error,
.Editor select.error,
-.Editor textarea.error{
- border: 1px solid red;
+.Editor textarea.error {
+ border: 1px solid red;
}
-.Editor .input-logo{
- height: 28px;
- width: 28px;
- margin-right: 5px;
- vertical-align: top;
+.Editor .input-logo {
+ height: 28px;
+ width: 28px;
+ margin-right: 5px;
+ vertical-align: top;
}
-.Editor .input-logo + input{
- width: calc(100% - 42px);
+.Editor .input-logo + input {
+ width: calc(100% - 42px);
}
-.Editor .required-icon{
- color: red;
+.Editor .required-icon {
+ color: red;
}
-.Editor .required-icon:after{
- content: "*";
+.Editor .required-icon:after {
+ content: "*";
}
-.metadata-container *{
- box-sizing: border-box;
+.metadata-container * {
+ box-sizing: border-box;
}
-.metadata-container h2{
- margin-bottom: 30px;
+.metadata-container h2 {
+ margin-bottom: 30px;
}
-.metadata-container h5 > .subtle{
- margin-left: 20px;
- font-weight: normal;
+.metadata-container h5 > .subtle {
+ margin-left: 20px;
+ font-weight: normal;
}
.metadata-container .section h2 + .subtle {
- margin-top: -25px;
+ margin-top: -25px;
}
.metadata-container .section h4 + .subtle {
- margin-top: -10px;
+ margin-top: -10px;
}
.metadata-container .section {
- width: 100%;
- clear: both;
+ width: 100%;
+ clear: both;
}
.metadata-container .section > .row-striped:last-child {
- margin-bottom: 0px;
+ margin-bottom: 0px;
}
-.metadata-container .section .header{
- font-weight: bold;
- border-bottom: 1px solid #DDD;
- padding-left: 10px;
- background-color: #EEE;
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
- padding-top: 10px;
+.metadata-container .section .header {
+ font-weight: bold;
+ border-bottom: 1px solid #ddd;
+ padding-left: 10px;
+ background-color: #eee;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ padding-top: 10px;
}
-.metadata-container .section .header h5{
- min-height: 1em;
+.metadata-container .section .header h5 {
+ min-height: 1em;
}
-.Editor .header-dropdown{
- font-size: 1.3em;
- width: 100%;
- height: 2.4em;
+.Editor .header-dropdown {
+ font-size: 1.3em;
+ width: 100%;
+ height: 2.4em;
}
-.metdata-container .checkbox{
- clear: both;
+.metdata-container .checkbox {
+ clear: both;
}
/* ---- Attributes ----*/
.eml-attribute {
- padding-bottom: 20px;
- padding-top: 20px;
+ padding-bottom: 20px;
+ padding-top: 20px;
}
-.eml-attribute.error{
- color: inherit;
+.eml-attribute.error {
+ color: inherit;
}
-.eml-attribute.error .accordion-heading{
- background-color: rgba(255, 0, 0, 0.11);
+.eml-attribute.error .accordion-heading {
+ background-color: rgba(255, 0, 0, 0.11);
}
-.eml-attribute.error .accordion-heading a{
- color: #960303;
- font-weight: bold;
+.eml-attribute.error .accordion-heading a {
+ color: #960303;
+ font-weight: bold;
}
-.eml-attribute .indent{
- margin-left: 20px;
+.eml-attribute .indent {
+ margin-left: 20px;
}
.enumerated-domain .table input {
- margin-bottom: 0px;
-}
-.attribute-menu.side-nav-items{
- overflow-y: overlay;
- transform: translate3d(0,0,0);
- -webkit-transform: translate3d(0,0,0);
- -moz-transform: translate3d(0,0,0);
-
- overflow-y: auto;
- position: fixed;
- height: 400px;
- background-color: #EEE;
- border-right: 1px solid #DDD;
-}
-.attribute-list{
- width: calc(100% - 170px - 60px);
+ margin-bottom: 0px;
+}
+.attribute-menu.side-nav-items {
+ overflow-y: overlay;
+ transform: translate3d(0, 0, 0);
+ -webkit-transform: translate3d(0, 0, 0);
+ -moz-transform: translate3d(0, 0, 0);
+
+ overflow-y: auto;
+ position: fixed;
+ height: 400px;
+ background-color: #eee;
+ border-right: 1px solid #ddd;
+}
+.attribute-list {
+ width: calc(100% - 170px - 60px);
float: right;
padding: 0px 20px;
- overflow: scroll;
+ overflow: scroll;
}
.attribute-menu-container .fill-button-container {
- background-color: #EEE;
- border-right: 1px solid #DDD;
- width: 100%;
+ background-color: #eee;
+ border-right: 1px solid #ddd;
+ width: 100%;
}
.attribute-menu-container .fill-button-container button {
width: calc(100% - 10px);
- margin: 5px;
+ margin: 5px;
}
-.eml-attribute .title{
- margin-bottom: 20px;
+.eml-attribute .title {
+ margin-bottom: 20px;
}
-.attribute-menu{
- width: 170px;
+.attribute-menu {
+ width: 170px;
}
-.attribute-menu-item{
- position: relative;
+.attribute-menu-item {
+ position: relative;
}
-.attribute-menu-item a{
- font-size: 1em;
+.attribute-menu-item a {
+ font-size: 1em;
}
-.attribute-menu-item a.error{
- color: #960303;
+.attribute-menu-item a.error {
+ color: #960303;
}
-.attribute-menu-item.active a{
- background-color: #1E254F;
- color: white;
+.attribute-menu-item.active a {
+ background-color: #1e254f;
+ color: white;
}
-.attribute-menu-item .add{
- display: none;
+.attribute-menu-item .add {
+ display: none;
}
-.attribute-menu-item .remove{
- position: absolute;
- top: 0px;
- right: 0px;
- font-size: 1.3em;
- color: red;
- display: none;
- padding: 13px;
+.attribute-menu-item .remove {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ font-size: 1.3em;
+ color: red;
+ display: none;
+ padding: 13px;
}
-.attribute-menu-item:hover .remove{
- display: block;
+.attribute-menu-item:hover .remove {
+ display: block;
}
.attribute-menu-item.side-nav-item.remove-preview a,
-.attribute-menu-item.side-nav-item.remove-preview a:hover{
- background-color: #f7f7f7;
- color: red;
+.attribute-menu-item.side-nav-item.remove-preview a:hover {
+ background-color: #f7f7f7;
+ color: red;
}
-.attribute-menu-item.side-nav-item.remove-preview .remove:hover{
- background-color: rgba(247, 247, 247, .5);
+.attribute-menu-item.side-nav-item.remove-preview .remove:hover {
+ background-color: rgba(247, 247, 247, 0.5);
}
-.attribute-menu-item.new:hover .remove{
- display: none;
+.attribute-menu-item.new:hover .remove {
+ display: none;
}
-.attributes .attribute-menu-container{
- width: 170px;
+.attributes .attribute-menu-container {
+ width: 170px;
}
-.eml-attribute .non-numeric-domain.error{
- color: inherit;
- border-color: red;
+.eml-attribute .non-numeric-domain.error {
+ color: inherit;
+ border-color: red;
}
-.eml-attribute .category-container.error{
- color: inherit;
- border: 1px solid red;
- padding: 10px;
- width: 100%;
- box-sizing: border-box;
- border-radius: 4px;
- -moz-border-radius: 4px;
- -webkit-border-radius: 4px;
+.eml-attribute .category-container.error {
+ color: inherit;
+ border: 1px solid red;
+ padding: 10px;
+ width: 100%;
+ box-sizing: border-box;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
}
-.code-row.new .remove{
- display: none;
+.code-row.new .remove {
+ display: none;
}
-.code-row .remove{
- width: 20px;
- float: right;
- margin-left: 10px;
- line-height: 2.2em;
+.code-row .remove {
+ width: 20px;
+ float: right;
+ margin-left: 10px;
+ line-height: 2.2em;
}
-.code-row .definition{
- width: calc(100% - 30px);
+.code-row .definition {
+ width: calc(100% - 30px);
}
/* ---- Overview ---- */
-.metadata-container .section.overview > div{
- margin-bottom: 20px;
+.metadata-container .section.overview > div {
+ margin-bottom: 20px;
}
-#data-sensitivity-container input:checked ~ .notification.hidden{
+#data-sensitivity-container input:checked ~ .notification.hidden {
display: block;
}
/* ---- Funding ---- */
-.metadata-container .funding-container{
- padding-left: 0px;
+.metadata-container .funding-container {
+ padding-left: 0px;
}
-.Editor input.funding{
- padding-left: 35px;
+.Editor input.funding {
+ padding-left: 35px;
}
-.metadata-container .funding-row{
- position: relative;
+.metadata-container .funding-row {
+ position: relative;
}
-.Editor .funding-row .ui-autocomplete{
- position: absolute !important;
+.Editor .funding-row .ui-autocomplete {
+ position: absolute !important;
font-size: 1.2em;
}
.Editor .funding-container .ui-autocomplete .ui-menu-item {
- font-size: .7em;
+ font-size: 0.7em;
}
-.Editor .funding-container .ui-helper-hidden-accessible{
- display: none;
+.Editor .funding-container .ui-helper-hidden-accessible {
+ display: none;
}
-.metadata-container .funding-container .autocomplete-container{
- position: relative;
+.metadata-container .funding-container .autocomplete-container {
+ position: relative;
}
-.Editor .funding-container .icon-spinner{
- position: absolute;
- left: 11px;
- top: 5px;
- font-size: 1.5em;
- display: none;
+.Editor .funding-container .icon-spinner {
+ position: absolute;
+ left: 11px;
+ top: 5px;
+ font-size: 1.5em;
+ display: none;
}
-.metadata-container #funding-visible{
- padding-left: 30px;
+.metadata-container #funding-visible {
+ padding-left: 30px;
}
/* ---- People / Parties ---- */
-.Editor .eml-party{
- position: relative;
+.Editor .eml-party {
+ position: relative;
}
-.controls-well > .party{
- margin-bottom: 30px;
- border-bottom: 1px solid #DDD;
+.controls-well > .party {
+ margin-bottom: 30px;
+ border-bottom: 1px solid #ddd;
}
.row-striped > .eml-party {
- padding-top: 10px;
+ padding-top: 10px;
}
-.eml-party .notification{
- height: 40px;
+.eml-party .notification {
+ height: 40px;
}
.metadata-container .section.people h4 {
- margin-top: 2em;
- margin-bottom: 1em;
+ margin-top: 2em;
+ margin-bottom: 1em;
}
-.Editor .eml-party .phone{
- font-size: .8em;
- padding: 5px;
- height: 3em;
+.Editor .eml-party .phone {
+ font-size: 0.8em;
+ padding: 5px;
+ height: 3em;
}
.subtle-btn-group button,
.subtle-btn-group button:hover {
- border-width: 0px;
- box-shadow: none;
- -webkit-box-shadow: none;
- -moz-box-shadow: none;
- background-color: transparent;
+ border-width: 0px;
+ box-shadow: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ background-color: transparent;
}
.subtle-btn-group .icon-ellipsis-vertical {
- font-size: 1.2em;
+ font-size: 1.2em;
}
.subtle-btn-group .dropdown-toggle:hover,
.subtle-btn-group .dropdown-toggle:focus {
- background-color: #DDD;
- border-width: 0px;
+ background-color: #ddd;
+ border-width: 0px;
}
.eml-party .party-menu {
- position: absolute;
- top: 5px;
- right: 10px;
+ position: absolute;
+ top: 5px;
+ right: 10px;
}
-.eml-party.new .party-menu{
- display: none;
+.eml-party.new .party-menu {
+ display: none;
}
.eml-party .party-menu .dropdown-menu {
- margin-left: -142px;
- width: 160px;
- background-color: #FFF;
- border: 1px solid #CCC;
- box-shadow: 8px 6px 24px -1px #ccc;
- -moz-box-shadow: 8px 6px 24px -1px #ccc;
- -webkit-box-shadow: 8px 6px 24px -1px #ccc;
+ margin-left: -142px;
+ width: 160px;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ box-shadow: 8px 6px 24px -1px #ccc;
+ -moz-box-shadow: 8px 6px 24px -1px #ccc;
+ -webkit-box-shadow: 8px 6px 24px -1px #ccc;
}
.eml-party .party-menu .dropdown-menu li {
- border-bottom: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
}
-.eml-party .party-menu .dropdown-menu li:hover{
- background-color: rgba(255, 255, 255, 0.57);
+.eml-party .party-menu .dropdown-menu li:hover {
+ background-color: rgba(255, 255, 255, 0.57);
}
-.eml-party .party-menu .dropdown-menu>li>a:hover {
- color: white;
+.eml-party .party-menu .dropdown-menu > li > a:hover {
+ color: white;
}
.eml-party .party-menu .remove,
.eml-party .party-menu .remove:hover,
.eml-party .party-menu .remove a,
.eml-party .party-menu .remove a:hover {
- font-size: inherit;
- position: relative;
- margin: 0px;
- color: red;
- max-width: 100%;
+ font-size: inherit;
+ position: relative;
+ margin: 0px;
+ color: red;
+ max-width: 100%;
}
/* ---- Keywords ---- */
-.metadata-container .keywords .span2{
- margin-left: 0px;
- width: 16.893617%;
+.metadata-container .keywords .span2 {
+ margin-left: 0px;
+ width: 16.893617%;
}
-.metadata-container .keyword-row{
- padding-top: 10px;
+.metadata-container .keyword-row {
+ padding-top: 10px;
}
/* ---- Usage ---- */
-.metadata-container .usage input{
- float: left;
+.metadata-container .usage input {
+ float: left;
}
-.metadata-container .usage{
- max-width: 500px;
+.metadata-container .usage {
+ max-width: 500px;
}
-.metadata-container .usage .text{
- min-width: 225px;
+.metadata-container .usage .text {
+ min-width: 225px;
}
/* ---- Basic text ---- */
.metadata-container .section .text-container {
- padding-left: 0px;
+ padding-left: 0px;
}
/* ---- Taxon Coverage ---- */
-.root-taxonomic-classification-container .notification{
- padding-left: 68px;
- position: absolute;
- margin-top: -31px;
+.root-taxonomic-classification-container .notification {
+ padding-left: 68px;
+ position: absolute;
+ margin-top: -31px;
}
-.root-taxonomic-classification-container{
- margin-top: 20px;
- position: relative;
+.root-taxonomic-classification-container {
+ margin-top: 20px;
+ position: relative;
}
.row-striped,
-.root-taxonomic-classification-container h6{
- border-top: none;
- border-left: none;
- border-right: none;
+.root-taxonomic-classification-container h6 {
+ border-top: none;
+ border-left: none;
+ border-right: none;
}
.root-taxonomic-classification-container:first-child,
-.taxonomic-coverage > .notification + .row-striped:nth-child(2){
- margin-top: 0px;
+.taxonomic-coverage > .notification + .row-striped:nth-child(2) {
+ margin-top: 0px;
}
-.root-taxonomic-classification{
- margin-bottom: 0px;
- position: relative;
+.root-taxonomic-classification {
+ margin-bottom: 0px;
+ position: relative;
}
.root-taxonomic-classification th > .subtle {
- font-weight: normal;
- margin-bottom: 0px;
+ font-weight: normal;
+ margin-bottom: 0px;
}
-.taxon-rank-value{
- width: calc(100% - 50px);
+.taxon-rank-value {
+ width: calc(100% - 50px);
}
/* quick add box */
-.taxa-quick-add{
- background-color: #ddf3f6;
- display: grid;
- padding: 0.5rem 1rem 1rem 1rem;
- border-radius: 4px;
- border: 1px solid #c4deea;
+.taxa-quick-add {
+ background-color: #ddf3f6;
+ display: grid;
+ padding: 0.5rem 1rem 1rem 1rem;
+ border-radius: 4px;
+ border: 1px solid #c4deea;
}
-.taxa-quick-add__controls{
- display: grid;
- grid-template-columns: auto max-content;
- gap: 1rem;
- align-items: end;
+.taxa-quick-add__controls {
+ display: grid;
+ grid-template-columns: auto max-content;
+ gap: 1rem;
+ align-items: end;
}
-.taxa-quick-add__selects{
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
- gap: 1rem;
+.taxa-quick-add__selects {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: 1rem;
}
-.taxa-quick-add__button{
- height: min-content;
+.taxa-quick-add__button {
+ height: min-content;
}
-.taxa-quick-add__text{
- margin: 0
+.taxa-quick-add__text {
+ margin: 0;
}
/* ---- Methods ---- */
-.metadata-container .sampling{
- margin-top: 20px;
+.metadata-container .sampling {
+ margin-top: 20px;
}
/* ---- Remove icons in the Editor ---- */
-.metadata-container .remove{
- color: #cecece;
+.metadata-container .remove {
+ color: #cecece;
}
-.metadata-container .remove{
- position: absolute;
- margin-left: 20px;
- max-width: 1em;
- font-size: 1.7em;
- margin-top: .2em;
+.metadata-container .remove {
+ position: absolute;
+ margin-left: 20px;
+ max-width: 1em;
+ font-size: 1.7em;
+ margin-top: 0.2em;
}
-.Editor .remove:hover{
- cursor: pointer;
- color: #FF0000;
+.Editor .remove:hover {
+ cursor: pointer;
+ color: #ff0000;
}
-.Editor .dropdown-menu .remove:hover{
- background-image: none;
- background-color: transparent;
+.Editor .dropdown-menu .remove:hover {
+ background-image: none;
+ background-color: transparent;
}
-.taxonomic-coverage-row .remove{
- position: relative;
- line-height: 2.2em;
- font-size: 1.1em;
- float: right;
- margin-right: 14px;
+.taxonomic-coverage-row .remove {
+ position: relative;
+ line-height: 2.2em;
+ font-size: 1.1em;
+ float: right;
+ margin-right: 14px;
}
.taxonomic-coverage-row.new .remove,
-.eml-geocoverage.new .remove{
- visibility: hidden;
+.eml-geocoverage.new .remove {
+ visibility: hidden;
}
-.root-taxonomic-classification-container > .remove{
- right: 20px;
- color: white;
- z-index: 1;
+.root-taxonomic-classification-container > .remove {
+ right: 20px;
+ color: white;
+ z-index: 1;
}
.root-taxonomic-classification > .remove {
- top: 10px;
- right: 18px;
- color: #999999;
+ top: 10px;
+ right: 18px;
+ color: #999999;
}
.remove-preview,
-.locations-table .eml-geocoverage.remove-preview{
- opacity: .3;
+.locations-table .eml-geocoverage.remove-preview {
+ opacity: 0.3;
}
-.method-step.new + .remove{
+.method-step.new + .remove {
display: none;
}
@@ -8615,252 +8828,253 @@ textarea.medium{
.metadata-container .keyword-row,
.metadata-container .funding-row,
.metadata-container .party-details,
-.metadata-container .method-step{
- width: calc(100% - 20px - 1em);
+.metadata-container .method-step {
+ width: calc(100% - 20px - 1em);
min-height: 30px;
}
.taxa input,
.pubDate input,
-.party-details input{
+.party-details input {
min-height: 30px;
}
/** Locations section **/
-.locations .coord{
- width: 5em;
- padding: 5px;
- font-size: .9em;
+.locations .coord {
+ width: 5em;
+ padding: 5px;
+ font-size: 0.9em;
min-height: 30px;
}
-.locations-table{
- border: 1px solid #DDD;
- border-radius: 4px;
- -moz-border-radius: 4px;
- -webkit-border-radius: 4px;
-}
-.locations-table .header{
- font-weight: bold;
- border-bottom: 1px solid #DDD;
- padding-bottom: 10px;
- padding-left: 10px;
- background-color: #EEE;
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
-}
-.locations-table .header .subtle{
- font-size: .9em;
- font-weight: normal;
-}
-.locations-table .header h5{
- margin-bottom: 0px;
-}
-.locations-table .eml-geocoverage{
- padding-top: 10px;
- padding-bottom: 10px;
- padding-left: 10px;
+.locations-table {
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+}
+.locations-table .header {
+ font-weight: bold;
+ border-bottom: 1px solid #ddd;
+ padding-bottom: 10px;
+ padding-left: 10px;
+ background-color: #eee;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+}
+.locations-table .header .subtle {
+ font-size: 0.9em;
+ font-weight: normal;
+}
+.locations-table .header h5 {
+ margin-bottom: 0px;
+}
+.locations-table .eml-geocoverage {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ padding-left: 10px;
}
.locations-table .eml-geocoverage:nth-child(odd) {
- background-color: #EEE;
+ background-color: #eee;
}
.metadata-container .locations .remove {
- position: relative;
- margin-left: 0px;
- margin-top: 50%;
- display: block;
+ position: relative;
+ margin-left: 0px;
+ margin-top: 50%;
+ display: block;
}
-.locations-table .notification{
- min-height: 1em;
- font-size: .9em;
+.locations-table .notification {
+ min-height: 1em;
+ font-size: 0.9em;
}
-.locations-table textarea.error{
- border-color: red;
+.locations-table textarea.error {
+ border-color: red;
}
.eml-geocoverage.error,
.locations-table .eml-geocoverage.error:nth-child(odd) {
- background-color: rgba(251, 188, 188, 0.35);
+ background-color: rgba(251, 188, 188, 0.35);
}
/** Entity section **/
-.eml-entity.modal{
- height: 80%;
- width: 80%;
- left: 40%;
- max-width: 1000px;
- min-width: 300px;
- transform: translate3d(0,0,0);
- -webkit-transform: translate3d(0,0,0);
- -moz-transform: translate3d(0,0,0);
-}
-.eml-entity .modal-body{
- max-height: calc(100% - 110px);
- height: calc(100% - 110px);
- padding: 0px;
- overflow-y: hidden;
-}
-.eml-entity .entity-container > .nav-tabs{
- position: fixed;
- width: 100%;
- background-color: white;
- min-height: 30px;
- margin-bottom: 0px;
+.eml-entity.modal {
+ height: 80%;
+ width: 80%;
+ left: 40%;
+ max-width: 1000px;
+ min-width: 300px;
+ transform: translate3d(0, 0, 0);
+ -webkit-transform: translate3d(0, 0, 0);
+ -moz-transform: translate3d(0, 0, 0);
+}
+.eml-entity .modal-body {
+ max-height: calc(100% - 110px);
+ height: calc(100% - 110px);
+ padding: 0px;
+ overflow-y: hidden;
+}
+.eml-entity .entity-container > .nav-tabs {
+ position: fixed;
+ width: 100%;
+ background-color: white;
+ min-height: 30px;
+ margin-bottom: 0px;
}
.eml-entity .entity-container > .nav-tabs li {
- width: 50%;
+ width: 50%;
}
-.eml-entity .entity-container > .nav-tabs li.active{
- border-top: 0px;
+.eml-entity .entity-container > .nav-tabs li.active {
+ border-top: 0px;
}
-.eml-entity .entity-container > .nav-tabs li a{
- border-radius: 0px;
- border-top: 0px;
- text-align: center;
+.eml-entity .entity-container > .nav-tabs li a {
+ border-radius: 0px;
+ border-top: 0px;
+ text-align: center;
}
-.eml-entity > .entity-container > .tab-content{
- width: 100%;
- box-sizing: border-box;
- margin-top: 40px;
+.eml-entity > .entity-container > .tab-content {
+ width: 100%;
+ box-sizing: border-box;
+ margin-top: 40px;
}
-.eml-entity .overview-container{
- padding: 20px;
- margin-top: 0px;
- width: calc(100% - 40px);
+.eml-entity .overview-container {
+ padding: 20px;
+ margin-top: 0px;
+ width: calc(100% - 40px);
}
-.eml-entity .preview-container{
- box-sizing: border-box;
- float: left;
+.eml-entity .preview-container {
+ box-sizing: border-box;
+ float: left;
}
-.eml-entity .preview-container .thumbnail{
- width: 100%;
- box-sizing: border-box;
+.eml-entity .preview-container .thumbnail {
+ width: 100%;
+ box-sizing: border-box;
}
-.eml-entity .preview-container + .description{
- float: right;
- width: 100%;
+.eml-entity .preview-container + .description {
+ float: right;
+ width: 100%;
}
-.preview-container .thumbnail-square{
- height: 250px;
- width: 250px;
- overflow: hidden;
+.preview-container .thumbnail-square {
+ height: 250px;
+ width: 250px;
+ overflow: hidden;
}
-.preview-container .description textarea{
- min-height: 175px;
+.preview-container .description textarea {
+ min-height: 175px;
}
.eml-attribute input {
- height: auto;
+ height: auto;
}
.eml-missing-value-rows {
- display: grid;
- gap: 0.7rem;
- grid-auto-flow: row;
- margin-bottom: 3rem;
+ display: grid;
+ gap: 0.7rem;
+ grid-auto-flow: row;
+ margin-bottom: 3rem;
}
.eml-missing-value {
- display: grid;
- grid-template-columns: 1fr 4fr 1.5rem;
- gap: 0.5rem;
+ display: grid;
+ grid-template-columns: 1fr 4fr 1.5rem;
+ gap: 0.5rem;
}
.eml-missing-value input {
- margin-bottom: 0;
- width: auto;
+ margin-bottom: 0;
+ width: auto;
}
.eml-missing-value button {
- font-size: 1.1rem;
+ font-size: 1.1rem;
}
-.eml-measurement-scale .options{
- box-sizing: border-box;
- padding-top: 10px;
- margin-top: 20px;
+.eml-measurement-scale .options {
+ box-sizing: border-box;
+ padding-top: 10px;
+ margin-top: 20px;
}
-.eml-measurement-scale .hidden{
- display: none;
+.eml-measurement-scale .hidden {
+ display: none;
}
-.eml-measurement-scale .full-width{
- width: 100%;
+.eml-measurement-scale .full-width {
+ width: 100%;
}
/*******************************************
Pager View
******************************************/
-.pager .pager-link{
- border: 1px solid #CCC;
- padding: 5px;
+.pager .pager-link {
+ border: 1px solid #ccc;
+ padding: 5px;
}
-.pager .current-page.pager-link{
- background-color: #ECF1F5;
+.pager .current-page.pager-link {
+ background-color: #ecf1f5;
}
-.pager .pager-link.first{
- border-top-left-radius: 4px;
- border-bottom-left-radius: 4px;
+.pager .pager-link.first {
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
}
-.pager .pager-link.last{
- border-top-right-radius: 4px;
- border-bottom-right-radius: 4px;
+.pager .pager-link.last {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
}
-.list-group-item.pager{
- margin-top: 0px;
+.list-group-item.pager {
+ margin-top: 0px;
}
/*******************************************
D3 old style charts
*******************************************/
.line-chart-label.text {
- color: white;
- stroke: white;
- fill: white;
+ color: white;
+ stroke: white;
+ fill: white;
}
/*******************************************
Documentation and Help Pages
******************************************/
-.documentation h5{
- display: inline;
+.documentation h5 {
+ display: inline;
}
-.documentation h4{
- margin-top: 20px;
+.documentation h4 {
+ margin-top: 20px;
}
-.documentation > section{
- margin-bottom: 30px;
+.documentation > section {
+ margin-bottom: 30px;
}
.code_example {
- display: block;
- white-space: pre;
- margin-top: 10px;
- margin-bottom: 10px;
- width: auto;
- padding: 5px 20px;
- border: 0px;
- color: #1192DD;
+ display: block;
+ white-space: pre;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ width: auto;
+ padding: 5px 20px;
+ border: 0px;
+ color: #1192dd;
}
-code{
- color: #1192DD;
+code {
+ color: #1192dd;
}
-.example{
- margin-top: 20px;
- margin-bottom: 20px;
+.example {
+ margin-top: 20px;
+ margin-bottom: 20px;
}
-table.api{
- font-size: 1.1em;
+table.api {
+ font-size: 1.1em;
}
table.api th {
- background-color: #90B5CE;
- color: white;
- font-weight: normal;
- font-size: 1.1em;
+ background-color: #90b5ce;
+ color: white;
+ font-weight: normal;
+ font-size: 1.1em;
}
-table.api th, table.api td {
- padding: 10px;
+table.api th,
+table.api td {
+ padding: 10px;
}
table.api tr:nth-child(odd) {
- background-color: #E7EAEA;
+ background-color: #e7eaea;
}
table.api tr td:nth-child(even) {
- background-color: rgba(192, 218, 218, 0.2);
+ background-color: rgba(192, 218, 218, 0.2);
}
-.example h3{
- font-weight: lighter;
+.example h3 {
+ font-weight: lighter;
}
-#slaask-input{
- height: 100px;
- word-break: break-word;
+#slaask-input {
+ height: 100px;
+ word-break: break-word;
}
-.slaask-widget-container{
+.slaask-widget-container {
}
/*******************************************
CSS library fixes
@@ -8868,19 +9082,28 @@ CSS library fixes
/* overflow-y:scroll fix for webkit browsers */
.pre-scrollable, /*Bootstrap */
.fancybox-lock .fancybox-overlay,
-.modal-body{ /* Fancybox */
- transform: translate3d(0,0,0);
- -webkit-transform: translate3d(0,0,0);
- -moz-transform: translate3d(0,0,0);
+.modal-body {
+ /* Fancybox */
+ transform: translate3d(0, 0, 0);
+ -webkit-transform: translate3d(0, 0, 0);
+ -moz-transform: translate3d(0, 0, 0);
}
/* make the modal appear a little faster */
.modal.fade {
- --speed: 0.13s;
- top: -8%;
- -webkit-transition: opacity var(--speed) linear, top var(--speed) ease-out;
- -moz-transition: opacity var(--speed) linear, top var(--speed) ease-out;
- -o-transition: opacity var(--speed) linear, top var(--speed) ease-out;
- transition: opacity var(--speed) linear, top var(--speed) ease-out;
+ --speed: 0.13s;
+ top: -8%;
+ -webkit-transition:
+ opacity var(--speed) linear,
+ top var(--speed) ease-out;
+ -moz-transition:
+ opacity var(--speed) linear,
+ top var(--speed) ease-out;
+ -o-transition:
+ opacity var(--speed) linear,
+ top var(--speed) ease-out;
+ transition:
+ opacity var(--speed) linear,
+ top var(--speed) ease-out;
}
/** Progress Bars from Bootstrap 3.3.6 **/
@@ -8914,8 +9137,8 @@ CSS library fixes
overflow: hidden;
background-color: #f5f5f5;
border-radius: 4px;
- -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
}
.progress-bar {
float: left;
@@ -8926,81 +9149,215 @@ CSS library fixes
color: #fff;
text-align: center;
background-color: #337ab7;
- -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
- box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
- -webkit-transition: width .6s ease;
- -o-transition: width .6s ease;
- transition: width .6s ease;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -webkit-transition: width 0.6s ease;
+ -o-transition: width 0.6s ease;
+ transition: width 0.6s ease;
}
.progress-striped .progress-bar,
.progress-bar-striped {
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -webkit-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -o-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
-webkit-background-size: 40px 40px;
- background-size: 40px 40px;
+ background-size: 40px 40px;
}
.progress.active .progress-bar,
.progress-bar.active {
-webkit-animation: progress-bar-stripes 2s linear infinite;
- -o-animation: progress-bar-stripes 2s linear infinite;
- animation: progress-bar-stripes 2s linear infinite;
+ -o-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite;
}
.progress-bar.progress-bar-success {
background-color: #5cb85c;
}
.progress-striped .progress-bar-success {
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -webkit-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -o-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
}
.progress-bar-info {
background-color: #5bc0de;
}
.progress-striped .progress-bar-info {
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -webkit-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -o-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
}
.progress-bar.progress-bar-warning {
background-color: #f0ad4e;
}
.progress-striped .progress-bar-warning {
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -webkit-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -o-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
}
.progress-bar.progress-bar-danger {
background-color: #d9534f;
}
.progress-striped .progress-bar-danger {
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -webkit-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: -o-linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
+ background-image: linear-gradient(
+ 45deg,
+ rgba(255, 255, 255, 0.15) 25%,
+ transparent 25%,
+ transparent 50%,
+ rgba(255, 255, 255, 0.15) 50%,
+ rgba(255, 255, 255, 0.15) 75%,
+ transparent 75%,
+ transparent
+ );
}
/* Portal Citations Metric Chart */
#user-citations > .citations-metrics-list > .empty-citation-list {
- width: 90%;
- height: 5%;
- top: unset;
- left: 5%;
- margin: 0 auto;
+ width: 90%;
+ height: 5%;
+ top: unset;
+ left: 5%;
+ margin: 0 auto;
}
#user-citations > .citations-metrics-list > .alert-container {
- width: 90%;
- left: 5%;
- margin: 0 auto;
+ width: 90%;
+ left: 5%;
+ margin: 0 auto;
}
/* Center Justify Metadata Assessment charts */
#metadata-assessment-graphic {
- display: flex;
- justify-content: center;
+ display: flex;
+ justify-content: center;
}
-
/* New Summary of Holdings design */
.summary-container {
@@ -9010,215 +9367,238 @@ CSS library fixes
display: grid;
}
.summary-count-container {
- width: 100%;
- display: grid;
- align-self: center;
- ttext-ALIGN: center;
- justify-self: center;
+ width: 100%;
+ display: grid;
+ align-self: center;
+ ttext-align: center;
+ justify-self: center;
}
.summary-container.span3 > div {
- margin-left: 17.02127659574468%;
+ margin-left: 17.02127659574468%;
}
-.quick-stats-count{
- color: #2c6f90; /* Back-up color for IE */
- fill: #2c6f90; /* Back-up color for IE */
- font-size: xx-large;
+.quick-stats-count {
+ color: #2c6f90; /* Back-up color for IE */
+ fill: #2c6f90; /* Back-up color for IE */
+ font-size: xx-large;
}
-.portal-view .quick-stats-count{
- color: #2c6f90; /* Back-up color for IE */
- fill: #2c6f90; /* Back-up color for IE */
- color: var(--portal-primary-color);
- fill: var(--portal-primary-color);
- font-size: xx-large;
+.portal-view .quick-stats-count {
+ color: #2c6f90; /* Back-up color for IE */
+ fill: #2c6f90; /* Back-up color for IE */
+ color: var(--portal-primary-color);
+ fill: var(--portal-primary-color);
+ font-size: xx-large;
}
/******* Loading styles *******/
-.circle-loading{
- border-radius: 50%;
- background-color: var(--loading-background-color);
+.circle-loading {
+ border-radius: 50%;
+ background-color: var(--loading-background-color);
}
/** Browser extension element fixes **/
-body > #extension-is-installed{
+body > #extension-is-installed {
display: none;
}
/* TODO: Move citation CSS into its own file */
.citation.copiable {
- font-size: 12px;
- background-color: #fbfbfb;
- border: 1px solid #e3e3e3;
- padding: 4px 15px;
- font-weight: 100;
- border-radius: 8px;
- /* Make it a 2x2 grid */
- display: grid;
- grid-template-columns: 1fr 84px;
- grid-template-rows: auto 1fr;
- grid-gap: 0px;
- /* vertically center everything*/
- align-items: center;
-
+ font-size: 12px;
+ background-color: #fbfbfb;
+ border: 1px solid #e3e3e3;
+ padding: 4px 15px;
+ font-weight: 100;
+ border-radius: 8px;
+ /* Make it a 2x2 grid */
+ display: grid;
+ grid-template-columns: 1fr 84px;
+ grid-template-rows: auto 1fr;
+ grid-gap: 0px;
+ /* vertically center everything*/
+ align-items: center;
}
.cite-text {
- font-size: 12px;
- font-weight: 900;
- letter-spacing: 0.01em;
- margin-bottom: 3px;
+ font-size: 12px;
+ font-weight: 900;
+ letter-spacing: 0.01em;
+ margin-bottom: 3px;
}
.citation.header .top-info > *:not(.id) {
text-transform: uppercase;
- font-size: 13px;
- letter-spacing: 0.02em;
- line-height: 1;
- margin-top: 20px;
+ font-size: 13px;
+ letter-spacing: 0.02em;
+ line-height: 1;
+ margin-top: 20px;
}
.citation.header .type {
color: #146660;
font-weight: 700;
- letter-spacing: 0.035em;
+ letter-spacing: 0.035em;
}
.citation.header .title {
- margin-top: 20px;
- font-size: 29px;
- color: #333333;
- line-height: 1.26;
+ margin-top: 20px;
+ font-size: 29px;
+ color: #333333;
+ line-height: 1.26;
}
.citation.header .separator {
- color: #e3e3e3;
- padding: 0 5px;
+ color: #e3e3e3;
+ padding: 0 5px;
}
.citation.header .authors {
- color: #202424;
- font-weight: 100;
- font-size: 15px;
-}
-
-.citation.header .show-authors{
- background: none;
- border: 0;
- border-radius: 15px;
- color: #313d90;
- text-decoration: none;
- text-transform: uppercase;
- font-weight: bold;
- line-height: 15px;
- font-size: 13px;
- /* vertical-align: text-bottom; */
- cursor: pointer;
- padding: 0 5px;
- letter-spacing: 0.02em;
-}
-.citation.header .show-authors:hover{
- color: #166760;
- background-color: #e4faf8;
-}
-.citation.header button.show-authors{
- margin-left: 5px;
+ color: #202424;
+ font-weight: 100;
+ font-size: 15px;
+}
+
+.citation.header .show-authors {
+ background: none;
+ border: 0;
+ border-radius: 15px;
+ color: #313d90;
+ text-decoration: none;
+ text-transform: uppercase;
+ font-weight: bold;
+ line-height: 15px;
+ font-size: 13px;
+ /* vertical-align: text-bottom; */
+ cursor: pointer;
+ padding: 0 5px;
+ letter-spacing: 0.02em;
+}
+.citation.header .show-authors:hover {
+ color: #166760;
+ background-color: #e4faf8;
+}
+.citation.header button.show-authors {
+ margin-left: 5px;
}
/* Table Head */
#data-package-table-head {
- position: sticky;
- top: 0;
- z-index: 1;
+ position: sticky;
+ top: 0;
+ z-index: 1;
}
#data-package-table-head > .file-header > th {
- background-color: #FDFDFD;
- border-bottom: 1px solid #E3E3E3;
- height: 32px;
- position: sticky;
- top: 0;
- z-index: -2;
+ background-color: #fdfdfd;
+ border-bottom: 1px solid #e3e3e3;
+ height: 32px;
+ position: sticky;
+ top: 0;
+ z-index: -2;
}
#data-package-table-head > .file-header > th:not(:last-child) {
- border-right: 1px solid #E3E3E3;
- height: 30px;
+ border-right: 1px solid #e3e3e3;
+ height: 30px;
}
/* Table Body */
#data-package-table-body > tr > td:first-child {
- cursor: default;
+ cursor: default;
}
/* Table Container */
.file-table-container {
- height: max-content;
- max-height: 25.4em;
- overflow-y: scroll;
- border: 1px solid #E3E3E3;
- border-radius: 4px;
+ height: max-content;
+ max-height: 25.4em;
+ overflow-y: scroll;
+ border: 1px solid #e3e3e3;
+ border-radius: 4px;
}
.file-table-container::-webkit-scrollbar {
- -webkit-appearance: none;
- width: 10px;
+ -webkit-appearance: none;
+ width: 10px;
}
.file-table-container::-webkit-scrollbar-thumb {
- border-radius: 5px;
- background-color: rgba(0, 0, 0, 0.5);
- -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5);
+ border-radius: 5px;
+ background-color: rgba(0, 0, 0, 0.5);
+ -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5);
}
/* Buttons */
.btn.btn-rounded.action {
- border-radius: 50%;
- height: 15px;
- width: 15px;
+ border-radius: 50%;
+ height: 15px;
+ width: 15px;
}
.btn.btn-rounded.action.downloadAction {
- border: none;
+ border: none;
}
.btn.btn-rounded.action.downloadAction > i {
- margin-left: -4px;
+ margin-left: -4px;
}
/* Data Package Item */
.data-package-item {
- height: 38px;
+ height: 38px;
}
/* File Table Styles */
.data-package-file-table > .data-package-item > .file-actions > .action .btn,
-.data-package-file-table > .data-package-item > .file-actions > .action .btn-primary,
-.data-package-file-table > .data-package-item > .file-actions > .action .btn-large,
-.data-package-file-table > .data-package-item > .file-actions > .action .btn-small,
-.data-package-file-table > .data-package-item > .file-actions > .action .btn-mini {
- font-weight: 400;
- background-image: none;
- background-repeat: no-repeat;
- background-color: transparent;
- border: 1px solid #146660;
- text-shadow: none;
- box-shadow: none;
- color: #146660;
- padding: 0.5em;
-}
-
-.data-package-file-table > .data-package-item > .file-actions > .action .btn:hover,
-.data-package-file-table > .data-package-item > .file-actions > .action .btn:focus,
+.data-package-file-table
+ > .data-package-item
+ > .file-actions
+ > .action
+ .btn-primary,
+.data-package-file-table
+ > .data-package-item
+ > .file-actions
+ > .action
+ .btn-large,
+.data-package-file-table
+ > .data-package-item
+ > .file-actions
+ > .action
+ .btn-small,
+.data-package-file-table
+ > .data-package-item
+ > .file-actions
+ > .action
+ .btn-mini {
+ font-weight: 400;
+ background-image: none;
+ background-repeat: no-repeat;
+ background-color: transparent;
+ border: 1px solid #146660;
+ text-shadow: none;
+ box-shadow: none;
+ color: #146660;
+ padding: 0.5em;
+}
+
+.data-package-file-table
+ > .data-package-item
+ > .file-actions
+ > .action
+ .btn:hover,
+.data-package-file-table
+ > .data-package-item
+ > .file-actions
+ > .action
+ .btn:focus,
.header .nav li .btn:hover,
.header nav li .btn:focus {
- background-color: #146660;
- color: #FFF;
- border: 0;
- box-shadow: none;
- border: 1px solid #146660;
+ background-color: #146660;
+ color: #fff;
+ border: 0;
+ box-shadow: none;
+ border: 1px solid #146660;
}
/* Tooltip */
.data-package-file-table div.tooltip {
- white-space: wrap;
+ white-space: wrap;
}
diff --git a/src/css/metacatui-common.responsive.css b/src/css/metacatui-common.responsive.css
index fd97bf9e9..0e82aace0 100644
--- a/src/css/metacatui-common.responsive.css
+++ b/src/css/metacatui-common.responsive.css
@@ -1,912 +1,918 @@
/* Smartphones (portrait) ----------- */
-@media only screen and (max-width : 768px) {
+@media only screen and (max-width: 768px) {
+ /************** Metadata View ****************/
+ .form-horizontal .control-label {
+ float: none;
+ text-align: left;
+ width: 100%;
+ font-weight: bold; /*Because labels/titles become less apparent when in the mobile design*/
+ }
+ .form-horizontal .controls {
+ margin-left: 0px;
+ }
+ .form-horizontal .controls-well .table td {
+ display: block;
+ padding: 4px;
+ }
+ .attributeList .span2 {
+ width: 40%;
+ overflow: hidden;
+ }
+ .attributeList .tab-content {
+ width: 55%;
+ overflow: visible;
+ }
+ .entitydetails {
+ width: 100%;
+ margin: 0px;
+ box-sizing: border-box;
+ }
+ .entitydetails > h4 > .title {
+ width: 83%;
+ }
+ .entitydetails > h4 > i {
+ width: 10%;
+ }
+ .entitydetails > h4 > .btn {
+ float: none;
+ }
+ .control-group ~ .thumbnail {
+ width: 100%;
+ box-sizing: border-box;
+ margin-left: 0px;
+ }
+ .breadcrumb {
+ margin-top: 20px;
+ margin-left: 10px;
+ width: 95%;
+ width: calc(100% - 50px);
+ }
- /************** Metadata View ****************/
- .form-horizontal .control-label{
- float: none;
- text-align: left;
- width: 100%;
- font-weight: bold; /*Because labels/titles become less apparent when in the mobile design*/
- }
- .form-horizontal .controls{
- margin-left: 0px;
- }
- .form-horizontal .controls-well .table td{
- display: block;
- padding: 4px;
- }
- .attributeList .span2{
- width: 40%;
- overflow: hidden;
- }
- .attributeList .tab-content{
- width: 55%;
- overflow: visible;
- }
- .entitydetails{
- width: 100%;
- margin: 0px;
- box-sizing: border-box;
- }
- .entitydetails > h4 > .title{
- width: 83%;
- }
- .entitydetails > h4 > i{
- width: 10%;
- }
- .entitydetails > h4 > .btn{
- float: none;
- }
- .control-group ~ .thumbnail{
- width: 100%;
- box-sizing: border-box;
- margin-left: 0px;
- }
- .breadcrumb{
- margin-top: 20px;
- margin-left: 10px;
- width: 95%;
- width: calc(100% - 50px);
- }
-
- /************** Footer ****************/
- #Footer{
- overflow-x: hidden;
- max-width: 100vw;
- }
-
- /************** Navbar ****************/
- #Navbar{
- width: 100%;
- position: relative;
- margin: 0px;
- }
- .navbar,
- .navbar-inner{
- padding: 0px;
- margin: 0px;
- }
- .nav{
- width: 100%;
- flex-direction: column;
+ /************** Footer ****************/
+ #Footer {
+ overflow-x: hidden;
+ max-width: 100vw;
+ }
+
+ /************** Navbar ****************/
+ #Navbar {
+ width: 100%;
+ position: relative;
+ margin: 0px;
+ }
+ .navbar,
+ .navbar-inner {
+ padding: 0px;
+ margin: 0px;
+ }
+ .nav {
+ width: 100%;
+ flex-direction: column;
display: flex;
- }
- .navbar li{
- width: 100%;
- text-align: center;
- border-bottom: 1px solid #FFF;
- }
- #Header{
- position: relative;
- }
- .nav .right .dropdown-menu{
- width: 100%;
- }
-
- .navbar .nav {
- margin-right: 0;
- }
-
- .navbar .nav ul.dropdown-menu {
- /* ensure the dropdown menu is covers width of viewport regardless of parent's width */
- position: absolute;
- left: 50%;
- right: 50%;
- margin-left: -50vw;
- margin-right: -50vw;
- width: 100vw;
- }
-
- .navbar .nav ul.dropdown-menu li > a {
- padding: 10px;
- width: auto;
- }
-
- a.dropdown-toggle.user {
+ }
+ .navbar li {
+ width: 100%;
+ text-align: center;
+ border-bottom: 1px solid #fff;
+ }
+ #Header {
+ position: relative;
+ }
+ .nav .right .dropdown-menu {
+ width: 100%;
+ }
+
+ .navbar .nav {
+ margin-right: 0;
+ }
+
+ .navbar .nav ul.dropdown-menu {
+ /* ensure the dropdown menu is covers width of viewport regardless of parent's width */
+ position: absolute;
+ left: 50%;
+ right: 50%;
+ margin-left: -50vw;
+ margin-right: -50vw;
+ width: 100vw;
+ }
+
+ .navbar .nav ul.dropdown-menu li > a {
+ padding: 10px;
+ width: auto;
+ }
+
+ a.dropdown-toggle.user {
margin: 4px 0 9px 0;
- }
+ }
- .dropdown-header {
- padding: 3px 0;
- }
+ .dropdown-header {
+ padding: 3px 0;
+ }
- /* put the drop down menu triangle in the middle */
- .navbar .nav>li>.dropdown-menu:before {
+ /* put the drop down menu triangle in the middle */
+ .navbar .nav > li > .dropdown-menu:before {
left: 50%;
- }
- .navbar .nav>li>.dropdown-menu:after {
+ }
+ .navbar .nav > li > .dropdown-menu:after {
left: calc(50% + 1px);
- }
-
- /* edit portal button */
- .PortalView #Navbar .nav .edit-portal-link-container.minimal-nav{
- display: flex;
- justify-content: center;
- }
+ }
+ /* edit portal button */
+ .PortalView #Navbar .nav .edit-portal-link-container.minimal-nav {
+ display: flex;
+ justify-content: center;
+ }
- /*************** User menu ***********/
+ /*************** User menu ***********/
- #Content > .nav-tabs {
- flex-direction: row;
- }
+ #Content > .nav-tabs {
+ flex-direction: row;
+ }
- #Content .span8.subsection {
- width: 100%;
- margin-left: 0px;
- }
+ #Content .span8.subsection {
+ width: 100%;
+ margin-left: 0px;
+ }
- #Content .panel.panel-default.span3 {
- width: 100%;
- margin-left: 0px;
- }
+ #Content .panel.panel-default.span3 {
+ width: 100%;
+ margin-left: 0px;
+ }
- /*************** Sign In box ***********/
- #signinPopup {
+ /*************** Sign In box ***********/
+ #signinPopup {
width: 100vw;
box-sizing: border-box;
top: 50px;
- position:fixed;
+ position: fixed;
left: 0;
- margin: 0;
- }
-
+ margin: 0;
+ }
- /*************** Portals ***********/
+ /*************** Portals ***********/
- /* portal list in settings */
+ /* portal list in settings */
- .my-portals-container.panel-body {
- padding: 0px 10px;
+ .my-portals-container.panel-body {
+ padding: 0px 10px;
}
- .create-btn-container .btn {
+ .create-btn-container .btn {
float: center;
margin: 10px 10px;
- }
+ }
- /* Hide table headers (but not display: none;, for accessibility) */
+ /* Hide table headers (but not display: none;, for accessibility) */
.my-portals-container table thead {
- position: absolute;
- top: -9999px;
- left: -9999px;
+ position: absolute;
+ top: -9999px;
+ left: -9999px;
}
.my-portals-container table tr {
- border-bottom: 1px solid #dddddd;
- position: relative;
- padding-left: 90px; /* space for logo */
- padding-right: 50px; /* space for button */
- min-height: 90px;
- height: auto;
- display: flex;
- flex-direction: column;
- justify-content: center;
+ border-bottom: 1px solid #dddddd;
+ position: relative;
+ padding-left: 90px; /* space for logo */
+ padding-right: 50px; /* space for button */
+ min-height: 90px;
+ height: auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
}
- .my-portals-container tr:nth-child(odd) {
- background: #f9f9f9;
- }
+ .my-portals-container tr:nth-child(odd) {
+ background: #f9f9f9;
+ }
- .my-portals-container tr:first-child {
- border-top: 1px solid #dddddd;
- }
+ .my-portals-container tr:first-child {
+ border-top: 1px solid #dddddd;
+ }
.my-portals-container table td {
- background-color: inherit !important; /* to overwrite bootstrap's striped table colors */
+ background-color: inherit !important; /* to overwrite bootstrap's striped table colors */
border: 0px;
- width: 100%;
- box-sizing: border-box;
+ width: 100%;
+ box-sizing: border-box;
}
- /* move logos to the left of the row */
- .my-portals-container table td.logo {
- display: flex;
- justify-content: center;
- align-items: center;
- top: 0;
- left: 0;
- width: 90px;
- position: absolute;
- height: 100%;
- display: flex;
- }
+ /* move logos to the left of the row */
+ .my-portals-container table td.logo {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ top: 0;
+ left: 0;
+ width: 90px;
+ position: absolute;
+ height: 100%;
+ display: flex;
+ }
- /* move buttons to the right of the row */
- .my-portals-container table td.controls {
- display: flex;
- align-items: center;
- order: 3;
- padding: 1px 5px 5px 0px;
- top: 0;
- right: 0;
- width: 50px;
- position: absolute;
- height: 100%;
- }
-
- .my-portals-container table td.controls .btn {
- padding: 4px 9px;
- }
-
- .my-portals-container table td.title {
- padding: 8px 5px 1px 0px;
- order: 1;
- /* hide the end of very long titles */
- max-height: 190px;
+ /* move buttons to the right of the row */
+ .my-portals-container table td.controls {
+ display: flex;
+ align-items: center;
+ order: 3;
+ padding: 1px 5px 5px 0px;
+ top: 0;
+ right: 0;
+ width: 50px;
+ position: absolute;
+ height: 100%;
+ }
+
+ .my-portals-container table td.controls .btn {
+ padding: 4px 9px;
+ }
+
+ .my-portals-container table td.title {
+ padding: 8px 5px 1px 0px;
+ order: 1;
+ /* hide the end of very long titles */
+ max-height: 190px;
overflow: hidden;
position: relative;
- }
+ }
- td.title:after {
- content: "...";
- position: absolute;
- top: 169px;
- right: 0;
- background: inherit;
- }
+ td.title:after {
+ content: "...";
+ position: absolute;
+ top: 169px;
+ right: 0;
+ background: inherit;
+ }
- .my-portals-container table td.portal-label {
- padding: 1px 5px 8px 0px;
- order: 2;
- }
+ .my-portals-container table td.portal-label {
+ padding: 1px 5px 8px 0px;
+ order: 2;
+ }
- .my-portals-container table td.portal-label::before {
- content: "ID: ";
- }
+ .my-portals-container table td.portal-label::before {
+ content: "ID: ";
+ }
- /* end portal list */
+ /* end portal list */
- /* .PortalView #Navbar .nav {
+ /* .PortalView #Navbar .nav {
float: none;
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
} */
- .Portal.Editor #Navbar .nav,
- .PortalView #Navbar .nav{
+ .Portal.Editor #Navbar .nav,
+ .PortalView #Navbar .nav {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 5px;
background-color: #f1f1f1;
- justify-content: space-evenly;
- border-top: 1px solid #ebebeb;
- border-bottom: 1px solid #ebebeb;
- }
+ justify-content: space-evenly;
+ border-top: 1px solid #ebebeb;
+ border-bottom: 1px solid #ebebeb;
+ }
- .PortalView #Navbar li.minimal-nav,
- .Portal.Editor #Navbar li.minimal-nav {
+ .PortalView #Navbar li.minimal-nav,
+ .Portal.Editor #Navbar li.minimal-nav {
border-bottom: none;
- width: auto;
- }
+ width: auto;
+ }
- .portal-view {
- width:100%;
- margin-left: 0;
- }
+ .portal-view {
+ width: 100%;
+ margin-left: 0;
+ }
- /* Portal header */
+ /* Portal header */
- .Portal.Editor #editor-header,
- .PortalView .portal-view #portal-header-container {
- padding: 0px;
- }
+ .Portal.Editor #editor-header,
+ .PortalView .portal-view #portal-header-container {
+ padding: 0px;
+ }
- .Portal.Editor #editor-header,
- .PortalView #portal-header-container .row{
- text-align: center;
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 100%;
- box-sizing: border-box;
- margin: 0;
- }
-
- .PortalView #portal-header-container .row{
- padding: 60px 5px 60px 5px;
- }
-
- .Portal.Editor #editor-header{
- padding: 40px 5px 40px 5px;
- }
-
- .Portal.Editor #editor-header .logo-editor-container,
- .PortalView #portal-header-container .row .portal-logo {
- max-width: 80px;
- max-height: 100%;
- margin: 0px 0px 25px 0px;
- }
-
- .Portal.Editor #editor-header .logo-editor-container {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
-
- .PortalView #portal-header-container .portal-title{
- margin: 0px 3px 0px 3px;
- font-size: 35px;
- line-height: 1.2;
- }
-
- .Portal.Editor .title-container {
- width: 100%;
- padding-left: 5px;
- padding-right: 5px;
- box-sizing: border-box;
- }
-
- .Portal.Editor #editor-header h5 {
- margin: 5px 0px;
- text-align: left;
- }
-
- /* Portal & portal editor navigation ('tabs') */
-
- .Editor.Portal .section-links-toggle-container,
- .PortalView .section-links-container {
- width: 100%;
- background: var(--portal-primary-color);
- color: white;
- position: fixed;
- box-shadow: 0px 1px 12px 3px rgba(0,0,0,0.3);
- z-index: 12; /* so it stays above footer and editor dropzone elements*/
- transition: bottom 0.3s;
- }
-
- /* Default position of portal pages nav on mobile. */
- /* This will be changed by PortalEditorView */
- .PortalView .section-links-container,
- .Editor.Portal .section-links-toggle-container {
- bottom: 0;
- padding-left: 0;
- }
-
- .PortalView #portal-section-tabs,
- .Editor.Portal #portal-section-tabs {
- display: flex;
- flex-direction: column;
- flex-wrap: nowrap;
- }
-
- .Editor.Portal .show-sections-toggle,
- .PortalView .show-sections-toggle{
- visibility: visible;
- display: block;
- text-align: center;
- padding: 10px;
- font-size: 14px;
- cursor: pointer;
- }
-
- /* .Editor.Portal #portal-section-tabs .section-links-container > li > a, */
- /* .Editor.Portal #portal-section-tabs .section-links-container > li, */
- .Editor.Portal #portal-section-tabs .section-link-container {
- width: 100%;
- border-top-right-radius: 0px;
- border-top-left-radius: 0px;
- background: white;
- }
-
- .Editor.Portal #portal-section-tabs a.portal-section-link {
- width: calc(100% - 65px);
- text-align: left;
- }
-
- .Editor.Portal #portal-section-tabs .portal-section-link,
- .Editor.Portal #portal-section-tabs .section-menu-link,
- .port-editor-sections .section-link-container .handle {
- color: #555;
- }
-
- .Editor.Portal #portal-section-tabs .section-menu-link {
- padding: 8px 10px;
- }
-
- .Editor.Portal .port-editor-sections .section-link-container .dropdown-menu > li > a {
- border-radius: 6px;
- padding: 8px;
- }
-
- .section-link-container .popover.fade.right.in{
- left: 0 !important;
- right: 0 !important;
- margin-left: auto !important;
- margin-right: auto !important;
- top: -100% !important;
- box-sizing: content-box;
-
- }
- .section-link-container .popover.fade.right.in > .arrow {
- display: none;
- }
- .section-link-container .popover.fade.right.in p{
- color: #555;
- }
-
- .Editor.Portal ul.section-links-container > li.active,
- .Editor.Portal ul.section-links-container > li:hover {
- background: var(--portal-secondary-color) !important;
- }
- .Editor.Portal ul.section-links-container > li:hover > a,
- .Editor.Portal ul.section-links-container > li.active > a {
- font-weight: bolder;
- color: white !important;
- }
-
- #portal-section-tabs .section-link-container.page-AddPage .portal-section-link {
- width: unset;
- margin-left: 0px !important;
- }
-
- #portal-section-tabs .section-link-container.page-AddPage .icon {
- font-size: 1em;
- margin-right: 5px;
- }
-
- #portal-section-tabs .section-link-container.page-AddPage .portal-section-link::after{
- content: " Add a page"
- }
-
- .Editor.Portal #portal-section-tabs .section-menu-link {
- float: right;
- }
-
- /* Portal markdown section */
-
- .portal-display-text {
- padding: 0.5rem;
- text-align: center;
- }
-
- .portal-display-text h2 {
- font-size: 1.5em;
- line-height: 1.4;
- padding: 10px 2px;
- }
-
- .tab-pane.portal-section-view {
+ .Portal.Editor #editor-header,
+ .PortalView #portal-header-container .row {
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ box-sizing: border-box;
+ margin: 0;
+ }
+
+ .PortalView #portal-header-container .row {
+ padding: 60px 5px 60px 5px;
+ }
+
+ .Portal.Editor #editor-header {
+ padding: 40px 5px 40px 5px;
+ }
+
+ .Portal.Editor #editor-header .logo-editor-container,
+ .PortalView #portal-header-container .row .portal-logo {
+ max-width: 80px;
+ max-height: 100%;
+ margin: 0px 0px 25px 0px;
+ }
+
+ .Portal.Editor #editor-header .logo-editor-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .PortalView #portal-header-container .portal-title {
+ margin: 0px 3px 0px 3px;
+ font-size: 35px;
+ line-height: 1.2;
+ }
+
+ .Portal.Editor .title-container {
+ width: 100%;
+ padding-left: 5px;
+ padding-right: 5px;
+ box-sizing: border-box;
+ }
+
+ .Portal.Editor #editor-header h5 {
+ margin: 5px 0px;
+ text-align: left;
+ }
+
+ /* Portal & portal editor navigation ('tabs') */
+
+ .Editor.Portal .section-links-toggle-container,
+ .PortalView .section-links-container {
+ width: 100%;
+ background: var(--portal-primary-color);
+ color: white;
+ position: fixed;
+ box-shadow: 0px 1px 12px 3px rgba(0, 0, 0, 0.3);
+ z-index: 12; /* so it stays above footer and editor dropzone elements*/
+ transition: bottom 0.3s;
+ }
+
+ /* Default position of portal pages nav on mobile. */
+ /* This will be changed by PortalEditorView */
+ .PortalView .section-links-container,
+ .Editor.Portal .section-links-toggle-container {
+ bottom: 0;
+ padding-left: 0;
+ }
+
+ .PortalView #portal-section-tabs,
+ .Editor.Portal #portal-section-tabs {
+ display: flex;
+ flex-direction: column;
+ flex-wrap: nowrap;
+ }
+
+ .Editor.Portal .show-sections-toggle,
+ .PortalView .show-sections-toggle {
+ visibility: visible;
+ display: block;
+ text-align: center;
+ padding: 10px;
+ font-size: 14px;
+ cursor: pointer;
+ }
+
+ /* .Editor.Portal #portal-section-tabs .section-links-container > li > a, */
+ /* .Editor.Portal #portal-section-tabs .section-links-container > li, */
+ .Editor.Portal #portal-section-tabs .section-link-container {
+ width: 100%;
+ border-top-right-radius: 0px;
+ border-top-left-radius: 0px;
+ background: white;
+ }
+
+ .Editor.Portal #portal-section-tabs a.portal-section-link {
+ width: calc(100% - 65px);
+ text-align: left;
+ }
+
+ .Editor.Portal #portal-section-tabs .portal-section-link,
+ .Editor.Portal #portal-section-tabs .section-menu-link,
+ .port-editor-sections .section-link-container .handle {
+ color: #555;
+ }
+
+ .Editor.Portal #portal-section-tabs .section-menu-link {
+ padding: 8px 10px;
+ }
+
+ .Editor.Portal
+ .port-editor-sections
+ .section-link-container
+ .dropdown-menu
+ > li
+ > a {
+ border-radius: 6px;
+ padding: 8px;
+ }
+
+ .section-link-container .popover.fade.right.in {
+ left: 0 !important;
+ right: 0 !important;
+ margin-left: auto !important;
+ margin-right: auto !important;
+ top: -100% !important;
+ box-sizing: content-box;
+ }
+ .section-link-container .popover.fade.right.in > .arrow {
+ display: none;
+ }
+ .section-link-container .popover.fade.right.in p {
+ color: #555;
+ }
+
+ .Editor.Portal ul.section-links-container > li.active,
+ .Editor.Portal ul.section-links-container > li:hover {
+ background: var(--portal-secondary-color) !important;
+ }
+ .Editor.Portal ul.section-links-container > li:hover > a,
+ .Editor.Portal ul.section-links-container > li.active > a {
+ font-weight: bolder;
+ color: white !important;
+ }
+
+ #portal-section-tabs
+ .section-link-container.page-AddPage
+ .portal-section-link {
+ width: unset;
+ margin-left: 0px !important;
+ }
+
+ #portal-section-tabs .section-link-container.page-AddPage .icon {
+ font-size: 1em;
+ margin-right: 5px;
+ }
+
+ #portal-section-tabs
+ .section-link-container.page-AddPage
+ .portal-section-link::after {
+ content: " Add a page";
+ }
+
+ .Editor.Portal #portal-section-tabs .section-menu-link {
+ float: right;
+ }
+
+ /* Portal markdown section */
+
+ .portal-display-text {
+ padding: 0.5rem;
+ text-align: center;
+ }
+
+ .portal-display-text h2 {
+ font-size: 1.5em;
+ line-height: 1.4;
+ padding: 10px 2px;
+ }
+
+ .tab-pane.portal-section-view {
width: 100vw;
- overflow-y: scroll;
- overflow-x: hidden;
- }
- .portal-section-content {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ }
+ .portal-section-content {
display: flex;
flex-direction: column;
width: 100vw;
padding: 0;
- }
+ }
- .toc .mobile {
- display: flex;
- flex-direction: row;
- width: 100vw;
- margin: 0;
- background-color: #2c7e90;
- background-color: var(--portal-secondary-color);
- color: white;
- }
-
- .toc .mobile a,
- .toc .mobile a:hover {
- color: white;
- }
-
- .toc .desktop {
- display: none;
- }
-
- .toc-ul li, .toc-ul a {
- background-color: transparent;
- }
-
- .mobile .toc-ul, .mobile .toc-ul {
+ .toc .mobile {
+ display: flex;
+ flex-direction: row;
+ width: 100vw;
+ margin: 0;
+ background-color: #2c7e90;
+ background-color: var(--portal-secondary-color);
+ color: white;
+ }
+
+ .toc .mobile a,
+ .toc .mobile a:hover {
+ color: white;
+ }
+
+ .toc .desktop {
+ display: none;
+ }
+
+ .toc-ul li,
+ .toc-ul a {
+ background-color: transparent;
+ }
+
+ .mobile .toc-ul,
+ .mobile .toc-ul {
position: sticky;
position: -webkit-sticky;
background-color: transparent;
margin-left: 0;
padding: 0px;
- }
+ }
- .mobile .toc-ul, .mobile .toc-ul a {
- color: #242424;
- }
+ .mobile .toc-ul,
+ .mobile .toc-ul a {
+ color: #242424;
+ }
- .mobile .toc-ul, .mobile .toc-ul li.active > a {
- color: white;
- }
+ .mobile .toc-ul,
+ .mobile .toc-ul li.active > a {
+ color: white;
+ }
- .toc .mobile > * {
- padding: 9px;
- box-sizing: border-box;
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- }
- .toc .mobile > *.hidden {
- display: none;
- }
-
- .toc .mobile > * > a {
- padding-right: 7px;
- }
-
- .toc .mobile > .second-level-items {
- min-width: 48vw;
- }
-
- .toc .mobile > .second-level-items .dropdown-menu {
- right: 0;
+ .toc .mobile > * {
+ padding: 9px;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ }
+ .toc .mobile > *.hidden {
+ display: none;
+ }
+
+ .toc .mobile > * > a {
+ padding-right: 7px;
+ }
+
+ .toc .mobile > .second-level-items {
+ min-width: 48vw;
+ }
+
+ .toc .mobile > .second-level-items .dropdown-menu {
+ right: 0;
left: unset;
min-width: 100%;
margin-right: 4px;
- }
-
- .toc .mobile > .top-level-items .dropdown-menu {
- min-width: 100%;
- }
-
- .toc .mobile > .top-level-items {
- margin: 0;
- min-width: 48vw;
- max-width: 100vw;
- padding-left: 10px;
- }
-
- .toc .mobile > .mobile-toc-divider {
- width: 4vw;
- }
-
- .toc .mobile > .mobile-toc-divider:before{
- font-size: 21px;
- }
-
- .nav.mobile li > a {
- display: list-item;
- }
-
- /* affix the TOC to the top */
- .toc-view.affix,
- .toc-view.affix {
+ }
+
+ .toc .mobile > .top-level-items .dropdown-menu {
+ min-width: 100%;
+ }
+
+ .toc .mobile > .top-level-items {
+ margin: 0;
+ min-width: 48vw;
+ max-width: 100vw;
+ padding-left: 10px;
+ }
+
+ .toc .mobile > .mobile-toc-divider {
+ width: 4vw;
+ }
+
+ .toc .mobile > .mobile-toc-divider:before {
+ font-size: 21px;
+ }
+
+ .nav.mobile li > a {
+ display: list-item;
+ }
+
+ /* affix the TOC to the top */
+ .toc-view.affix,
+ .toc-view.affix {
top: 0px;
- }
+ }
- /* make markdown content take full width */
- .markdown.span9 {
+ /* make markdown content take full width */
+ .markdown.span9 {
padding: 13px 10px;
width: 100vw;
box-sizing: border-box;
- margin: 0;
- overflow-y: scroll; /* so that vw calculation includes space for scroll bar */
- }
+ margin: 0;
+ overflow-y: scroll; /* so that vw calculation includes space for scroll bar */
+ }
- .toc-view.span3.affix + .span9 {
- margin-left: 0;
- }
+ .toc-view.span3.affix + .span9 {
+ margin-left: 0;
+ }
- .markdown code {
- white-space: pre-wrap;
- }
+ .markdown code {
+ white-space: pre-wrap;
+ }
- #portal-sections {
+ #portal-sections {
max-width: 100vw;
overflow-x: hidden;
- }
+ }
- /*************** Portal Editor ***********/
+ /*************** Portal Editor ***********/
- /* Decrease the padding so that content has more room on narrow screens */
- .port-editor-section.port-editor-data {
- padding: 5px;
- margin-right: 30px;
- }
+ /* Decrease the padding so that content has more room on narrow screens */
+ .port-editor-section.port-editor-data {
+ padding: 5px;
+ margin-right: 30px;
+ }
- .port-editor-md textarea.markdown {
- margin: 0;
- width: 100%;
- }
+ .port-editor-md textarea.markdown {
+ margin: 0;
+ width: 100%;
+ }
- .Portal.Editor .port-editor-md textarea.auto-resize {
+ .Portal.Editor .port-editor-md textarea.auto-resize {
max-height: 80px !important;
overflow-y: scroll;
- }
-
- .Portal.Editor .portal-display-text {
- margin-top: 40px;
- }
-
- .port-editor-section.port-editor-md .dropzone .dz-message {
- font-size: 12px;
- }
-
- .port-editor-section > h3 {
- line-height: 1.2;
- margin-bottom: 10px;
- }
-
- .editor-view.portal-editor div#Metrics p,
- .port-editor-section > h3,
- .port-editor-settings,
- .port-editor-settings > .row-fluid,
- .port-editor-md > * {
- padding-left: 9px;
- padding-right: 9px;
- margin-left: 0;
- margin-right: 0;
- width: 100%;
- box-sizing: border-box;
- }
-
- .portal-editor .toc {
- margin-left: -9px !important;
- }
-
- #Metrics metrics-figure-container {
- max-width: 100%;
- }
-
- .row-fluid .span6.pagination-left {
- width: 100%;
- }
- #Content{
- width: 100%;
- padding-left: 0;
- padding-right: 0;
-
- }
-
- #portal-header-container{
+ }
+
+ .Portal.Editor .portal-display-text {
+ margin-top: 40px;
+ }
+
+ .port-editor-section.port-editor-md .dropzone .dz-message {
+ font-size: 12px;
+ }
+
+ .port-editor-section > h3 {
+ line-height: 1.2;
+ margin-bottom: 10px;
+ }
+
+ .editor-view.portal-editor div#Metrics p,
+ .port-editor-section > h3,
+ .port-editor-settings,
+ .port-editor-settings > .row-fluid,
+ .port-editor-md > * {
+ padding-left: 9px;
+ padding-right: 9px;
+ margin-left: 0;
+ margin-right: 0;
+ width: 100%;
+ box-sizing: border-box;
+ }
+
+ .portal-editor .toc {
+ margin-left: -9px !important;
+ }
+
+ #Metrics metrics-figure-container {
+ max-width: 100%;
+ }
+
+ .row-fluid .span6.pagination-left {
+ width: 100%;
+ }
+ #Content {
+ width: 100%;
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ #portal-header-container {
margin-bottom: 0px;
}
- .PortalView #portal-header-container .row{
+ .PortalView #portal-header-container .row {
padding: 0px;
}
- #editPortal.btn{
+ #editPortal.btn {
margin: 10px;
}
- .portal-view .portal-description{
+ .portal-view .portal-description {
padding: 10px;
margin-right: 0px;
}
- /************** Custom search filter editor ****************/
-
- .filter-editor .modal-body {
- padding: 1rem;
- }
- .filter-editor .fields-container,
- .ui-builder-choices-container {
- grid-template-columns: 100%;
- gap: 1rem;
- justify-items: center;
- }
-
- .choice-input,
- .choice-editor .ui-builder-container-text {
- width: unset;
- max-width: 206px;
- }
-
- /************** Search page ****************/
- #sidebar{
- width: 90%;
- }
- #results-container{
- width: 90%;
- margin-left: 10px;
- }
- .filter-contain > .filter-input-contain{
- width: 100%;
- }
- .filter-contain > label{
- font-size: 1.1em;
- }
-
- /***** Stats ********/
- .profile .format-charts .chart{
- width: 100%;
- float: none;
- }
- .profile .format-charts-container{
- height: auto;
- }
- #metadata-chart, #data-chart{
- width: 100%;
- float: none;
- }
- /********* Login popup **********/
- .fancybox-inline .span7 .well{
- max-width: 83%;
- }
- .fancybox-inline > .row-fluid > .span7{
- float: none;
- width: 44%;
- padding-left: 0px;
- margin-left: 0px;
- }
- .fancybox-inline h2{
- display: inline;
- }
-
- /******************************************
+ /************** Custom search filter editor ****************/
+
+ .filter-editor .modal-body {
+ padding: 1rem;
+ }
+ .filter-editor .fields-container,
+ .ui-builder-choices-container {
+ grid-template-columns: 100%;
+ gap: 1rem;
+ justify-items: center;
+ }
+
+ .choice-input,
+ .choice-editor .ui-builder-container-text {
+ width: unset;
+ max-width: 206px;
+ }
+
+ /************** Search page ****************/
+ #sidebar {
+ width: 90%;
+ }
+ #results-container {
+ width: 90%;
+ margin-left: 10px;
+ }
+ .filter-contain > .filter-input-contain {
+ width: 100%;
+ }
+ .filter-contain > label {
+ font-size: 1.1em;
+ }
+
+ /***** Stats ********/
+ .profile .format-charts .chart {
+ width: 100%;
+ float: none;
+ }
+ .profile .format-charts-container {
+ height: auto;
+ }
+ #metadata-chart,
+ #data-chart {
+ width: 100%;
+ float: none;
+ }
+ /********* Login popup **********/
+ .fancybox-inline .span7 .well {
+ max-width: 83%;
+ }
+ .fancybox-inline > .row-fluid > .span7 {
+ float: none;
+ width: 44%;
+ padding-left: 0px;
+ margin-left: 0px;
+ }
+ .fancybox-inline h2 {
+ display: inline;
+ }
+
+ /******************************************
* Metrics Styling
********************************************/
- .charts-container .charts.row-fluid {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
-
- .charts-container .span6.packages.badge-container {
- /* width: 100%; */
- display: flex;
- flex-direction: column;
- }
-
- .charts-container .row-fluid .span6 {
- width: 100%;
- height: 100%;
- display: flex;
- justify-content: center;
- margin: 0;
- box-sizing: border-box;
- }
-
- .charts-container .stripe.quick-stats {
- height: auto;
- }
-
- .charts-container svg.circle-badge {
- width: 220px;
- }
-
- .charts-container .temporal-coverage-chart.chart {
- margin-left: 0;
- }
-
- .temporal-coverage-chart.chart {
- width: 100%;
- }
+ .charts-container .charts.row-fluid {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .charts-container .span6.packages.badge-container {
+ /* width: 100%; */
+ display: flex;
+ flex-direction: column;
+ }
+
+ .charts-container .row-fluid .span6 {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ margin: 0;
+ box-sizing: border-box;
+ }
+
+ .charts-container .stripe.quick-stats {
+ height: auto;
+ }
+
+ .charts-container svg.circle-badge {
+ width: 220px;
+ }
+
+ .charts-container .temporal-coverage-chart.chart {
+ margin-left: 0;
+ }
+
+ .temporal-coverage-chart.chart {
+ width: 100%;
+ }
/************ Access Policy View *************/
- .access-policy-view .public-toggle-container .can-toggle{
+ .access-policy-view .public-toggle-container .can-toggle {
margin: auto;
}
/************ Editor footer (portal editor, metadata editor) *************/
- #editor-footer{
- grid-template-rows: auto auto;
- grid-template-columns: 100%;
- grid-template-areas: "save-controls" "trial-message";
- gap: 0.4rem;
- padding: 0.5rem;
+ #editor-footer {
+ grid-template-rows: auto auto;
+ grid-template-columns: 100%;
+ grid-template-areas: "save-controls" "trial-message";
+ gap: 0.4rem;
+ padding: 0.5rem;
}
- #editor-footer .free-trial-message{
- justify-self: center;
- font-size: 0.87rem;
+ #editor-footer .free-trial-message {
+ justify-self: center;
+ font-size: 0.87rem;
}
- .editor-save-controls{
- justify-self: center;
+ .editor-save-controls {
+ justify-self: center;
}
- .editor-save-controls > a{
+ .editor-save-controls > a {
font-size: 1em;
}
}
-@media only screen and (max-width : 1000px){
- /* Editor styles */
- .temporal-coverage .control-label span{
- display: block;
- }
- .temporal-coverage .control-label {
- height: 4em;
- }
+@media only screen and (max-width: 1000px) {
+ /* Editor styles */
+ .temporal-coverage .control-label span {
+ display: block;
+ }
+ .temporal-coverage .control-label {
+ height: 4em;
+ }
- /******************************************
+ /******************************************
* Metrics Controller Styling
********************************************/
- /* Style for the metric button */
- a.btn.metrics {
- float:none;
- width: 150px;
- margin: 1px;
- padding-right: 0px;
- }
- .btn-group+.btn-group, .btn-toolbar>.btn+.btn,
- .btn-toolbar>.btn+.btn-group, .btn-toolbar>.btn-group+.btn {
- margin-left: 0px;
- }
-
- .metric-well {
- float:none;
- flex-wrap: wrap;
- width: 100%;
- height: auto;
- }
-
- /* Style for the toolbars that contains buttons in the well on either side. */
- .metric-toolbar {
- float:none;
- width: 100%;
- justify-content: center;
- align-items: center;
- margin-top: 0px;
- }
- .edit-toolbar {
- float:none;
- border-left: none;
- margin-top: none;
- justify-content: center;
- align-items: center;
- }
-
- .edit-toolbar >
- #metadata-controls-container >
- .metadata-controls-container {
- margin-left: 0px;
- margin-top: 5px;
- margin-right: 0px;
- height: 38px;
- }
-
- .edit-toolbar >
- #owner-controls-container >
- .authority-controls {
- width: auto;
- height: 38px;
- margin-top: -1px;
- margin-right: 0px;
- margin-left: 0px;
- }
-
- .edit-toolbar >
- #owner-controls-container >
- .authority-controls >
- #editMetadata {
- margin-right: none;
- }
-
- /* Query rules in the query builder */
- .query-rule {
- grid-template-columns: 2.2rem auto 1.3rem;
- grid-template-rows: auto;
- gap: 0.5rem 1rem;
- grid-template-areas:
- "info field remove"
- "info operator remove"
- "info value remove";
- }
-
- .query-rule.rule-group{
- grid-template-columns: 2.2rem auto 1.3rem;
- grid-template-areas:
- "info query-builder remove"
- "info query-builder remove"
- "info query-builder remove";
- }
-
- .rules-container {
- padding: 2rem 0.7rem 2rem 1rem;
- grid-gap: 2rem;
- }
-
- .query-rule .rule-info {
- font-size: 0.7rem;
- }
+ /* Style for the metric button */
+ a.btn.metrics {
+ float: none;
+ width: 150px;
+ margin: 1px;
+ padding-right: 0px;
+ }
+ .btn-group + .btn-group,
+ .btn-toolbar > .btn + .btn,
+ .btn-toolbar > .btn + .btn-group,
+ .btn-toolbar > .btn-group + .btn {
+ margin-left: 0px;
+ }
+ .metric-well {
+ float: none;
+ flex-wrap: wrap;
+ width: 100%;
+ height: auto;
+ }
+
+ /* Style for the toolbars that contains buttons in the well on either side. */
+ .metric-toolbar {
+ float: none;
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+ margin-top: 0px;
+ }
+ .edit-toolbar {
+ float: none;
+ border-left: none;
+ margin-top: none;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .edit-toolbar > #metadata-controls-container > .metadata-controls-container {
+ margin-left: 0px;
+ margin-top: 5px;
+ margin-right: 0px;
+ height: 38px;
+ }
+
+ .edit-toolbar > #owner-controls-container > .authority-controls {
+ width: auto;
+ height: 38px;
+ margin-top: -1px;
+ margin-right: 0px;
+ margin-left: 0px;
+ }
+
+ .edit-toolbar
+ > #owner-controls-container
+ > .authority-controls
+ > #editMetadata {
+ margin-right: none;
+ }
+
+ /* Query rules in the query builder */
+ .query-rule {
+ grid-template-columns: 2.2rem auto 1.3rem;
+ grid-template-rows: auto;
+ gap: 0.5rem 1rem;
+ grid-template-areas:
+ "info field remove"
+ "info operator remove"
+ "info value remove";
+ }
+
+ .query-rule.rule-group {
+ grid-template-columns: 2.2rem auto 1.3rem;
+ grid-template-areas:
+ "info query-builder remove"
+ "info query-builder remove"
+ "info query-builder remove";
+ }
+
+ .rules-container {
+ padding: 2rem 0.7rem 2rem 1rem;
+ grid-gap: 2rem;
+ }
+
+ .query-rule .rule-info {
+ font-size: 0.7rem;
+ }
}
/* Smartphones (landscape) ----------- */
-@media only screen and (max-width : 700px){
- /*************** General Structure ***********/
- .row-fluid .span6.pagination-left {
- width: 100%;
- }
+@media only screen and (max-width: 700px) {
+ /*************** General Structure ***********/
+ .row-fluid .span6.pagination-left {
+ width: 100%;
+ }
/** Metrics **/
#metric-modal .modal-body {
@@ -914,199 +920,179 @@
row-gap: 40px;
column-gap: 0px;
}
- #metric-modal .empty-citation-list{
+ #metric-modal .empty-citation-list {
width: 90%;
}
}
/* Smartphones (portrait) ----------- */
-@media only screen and (max-width : 420px) {
-
+@media only screen and (max-width: 420px) {
.modal,
.access-policy-view-container.modal,
- #metric-modal{
+ #metric-modal {
top: 5%;
width: 90%;
left: 5%;
margin-left: 0px;
}
- .access-policy-view thead{
+ .access-policy-view thead {
display: none;
}
.access-policy-view table,
.access-policy-view tbody,
.access-policy-view tr,
- .access-policy-view td{
+ .access-policy-view td {
display: block;
}
- .access-policy-view td{
+ .access-policy-view td {
border-top-width: 0px;
}
- .access-policy-view .subject{
- font-size: .8em;
+ .access-policy-view .subject {
+ font-size: 0.8em;
color: #999;
padding-top: 0px;
padding-bottom: 0px;
line-height: 1.5em;
}
- .access-policy-view .access{
+ .access-policy-view .access {
width: 80%;
display: inline-block;
box-sizing: border-box;
padding-top: 0px;
}
- .access-policy-view .remove-rule{
+ .access-policy-view .remove-rule {
width: 20%;
display: inline-block;
box-sizing: border-box;
padding-top: 0px;
}
- .access-policy-view .access-rule{
- border-bottom: 1px solid #CCC;
+ .access-policy-view .access-rule {
+ border-bottom: 1px solid #ccc;
}
- .access-policy-view .access-rule.new .access{
+ .access-policy-view .access-rule.new .access {
width: 80%;
display: inline-block;
box-sizing: border-box;
}
.access-policy-view .access-rule.new .access select,
- .access-policy-view .access-rule.new .add-rule .add{
+ .access-policy-view .access-rule.new .add-rule .add {
margin-top: 0px;
}
- .access-policy-view .access-rule.new .add-rule{
+ .access-policy-view .access-rule.new .add-rule {
width: 20%;
display: inline-block;
box-sizing: border-box;
padding-top: 0px;
}
- #mainContent .form-input{
+ #mainContent .form-input {
grid-template-columns: 80% 20%;
}
- #mainContent input{
+ #mainContent input {
font-size: 1.5em;
}
-
}
/**********
* These styles control the mobile navigation
**********/
-@media only screen and (min-width : 900px) and (max-width : 1050px) {
+@media only screen and (min-width: 900px) and (max-width: 1050px) {
/* Collapse the repo title into multiple lines, once we get to a certain window width */
- .navbar .title{
+ .navbar .title {
display: inline-block;
max-width: 140px;
font-size: 1.1em;
line-height: 1em;
}
/* Make the nav links a bit smaller */
- .navbar .nav > li > a{
+ .navbar .nav > li > a {
font-size: 1.1em;
}
}
-@media only screen and (max-width : 900px) {
-
- /* At this window width, show the mobile nav hamburger menu and hide the non-mobile nav links */
- #nav-trigger{
- display: block;
- }
- #main-nav{
- display: none;
- }
- /* Dropdown menus in mobile nav should change from popover-style dropdowns to listed links */
- #main-nav .dropdown-menu {
- display: block;
- position: relative;
- background-color: transparent;
- border: 0px;
- box-shadow: none;
- margin-left: -5%;
- width: auto;
- }
- /* Hide dropdown headers, dividers, borders, and the caret icon */
- #main-nav .dropdown-header,
- #main-nav .divider{
- display: none;
- }
- #Navbar .dropdown-menu:before, #Navbar .dropdown-menu:after{
- content: none;
- }
- #main-nav .dropdown-menu > li{
- border-bottom: 0px;
- }
- /* Dropdown menu links should be white */
- #main-nav .dropdown-menu > li > a{
- color: #FFF;
- text-align: left;
- }
- /* Set a height on input forms in the nav*/
- #main-nav form{
- height: 40px;
- }
+@media only screen and (max-width: 900px) {
+ /* At this window width, show the mobile nav hamburger menu and hide the non-mobile nav links */
+ #nav-trigger {
+ display: block;
+ }
+ #main-nav {
+ display: none;
+ }
+ /* Dropdown menus in mobile nav should change from popover-style dropdowns to listed links */
+ #main-nav .dropdown-menu {
+ display: block;
+ position: relative;
+ background-color: transparent;
+ border: 0px;
+ box-shadow: none;
+ margin-left: -5%;
+ width: auto;
+ }
+ /* Hide dropdown headers, dividers, borders, and the caret icon */
+ #main-nav .dropdown-header,
+ #main-nav .divider {
+ display: none;
+ }
+ #Navbar .dropdown-menu:before,
+ #Navbar .dropdown-menu:after {
+ content: none;
+ }
+ #main-nav .dropdown-menu > li {
+ border-bottom: 0px;
+ }
+ /* Dropdown menu links should be white */
+ #main-nav .dropdown-menu > li > a {
+ color: #fff;
+ text-align: left;
+ }
+ /* Set a height on input forms in the nav*/
+ #main-nav form {
+ height: 40px;
+ }
}
/* iPads (portrait and landscape) ----------- */
-@media only screen
-and (min-width : 768px)
-and (max-width : 1024px) {
-/* STYLES GO HERE */
+@media only screen and (min-width: 768px) and (max-width: 1024px) {
+ /* STYLES GO HERE */
}
/* iPads (landscape) ----------- */
-@media only screen
-and (min-width : 768px)
-and (max-width : 1024px)
-and (orientation : landscape) {
-/* STYLES GO HERE */
+@media only screen and (min-width: 768px) and (max-width: 1024px) and (orientation: landscape) {
+ /* STYLES GO HERE */
}
/* iPads (portrait) ----------- */
-@media only screen
-and (min-width : 768px)
-and (max-width : 1024px)
-and (orientation : portrait) {
-/* STYLES GO HERE */
+@media only screen and (min-width: 768px) and (max-width: 1024px) and (orientation: portrait) {
+ /* STYLES GO HERE */
}
/* Desktops and laptops ----------- */
-@media only screen
-and (min-width : 1224px) {
-/* STYLES GO HERE */
+@media only screen and (min-width: 1224px) {
+ /* STYLES GO HERE */
}
/* Large screens ----------- */
-@media only screen
-and (min-width : 1824px) {
-/* STYLES GO HERE */
+@media only screen and (min-width: 1824px) {
+ /* STYLES GO HERE */
#portals-list-container {
- width: 50%;
- margin: auto;
+ width: 50%;
+ margin: auto;
}
}
/* iPhone 5 (portrait & landscape)----------- */
-@media only screen
-and (min-width : 320px)
-and (max-width : 568px) {
+@media only screen and (min-width: 320px) and (max-width: 568px) {
.access-policy-view-container.modal .modal-body,
- .modal .modal-body{
+ .modal .modal-body {
max-height: 350px;
}
}
/* iPhone 5 (landscape)----------- */
-@media only screen
-and (min-width : 320px)
-and (max-width : 568px)
-and (orientation : landscape) {
-/* STYLES GO HERE */
+@media only screen and (min-width: 320px) and (max-width: 568px) and (orientation: landscape) {
+ /* STYLES GO HERE */
}
/* iPhone 5 (portrait)----------- */
-@media only screen
-and (min-width : 320px)
-and (max-width : 568px)
-and (orientation : portrait) {
-/* STYLES GO HERE */
+@media only screen and (min-width: 320px) and (max-width: 568px) and (orientation: portrait) {
+ /* STYLES GO HERE */
}
diff --git a/src/css/portal-layouts/panels.css b/src/css/portal-layouts/panels.css
index ad6dd94aa..ec7fa8fed 100644
--- a/src/css/portal-layouts/panels.css
+++ b/src/css/portal-layouts/panels.css
@@ -63,7 +63,7 @@
overflow: hidden;
}
-#portal-header-container>.row {
+#portal-header-container > .row {
margin-left: 0;
position: relative;
width: auto;
@@ -78,19 +78,18 @@
.portal-view .portal-logo {
height: 3rem;
margin-right: -3.25rem;
- opacity: .25;
+ opacity: 0.25;
z-index: 1;
}
.portal-view .portal-description {
align-self: center;
- font-size: .75rem;
+ font-size: 0.75rem;
margin: 0;
margin-left: 0.75rem;
position: relative;
}
-
.portal-view .portal-description::before {
/* font awesome info icon */
font-family: FontAwesome;
@@ -106,16 +105,19 @@
}
.portal-view .portal-description p {
- font-size: .8rem;
+ font-size: 0.8rem;
font-weight: 500;
position: absolute;
margin: 0;
- top: -.25rem;
+ top: -0.25rem;
left: 1.5rem;
width: max-content;
- background-color: var(--portal-col-tooltip-background, var(--portal-col-bkg-active__deprecate, white));
+ background-color: var(
+ --portal-col-tooltip-background,
+ var(--portal-col-bkg-active__deprecate, white)
+ );
z-index: 10;
- padding: 0.25rem .5rem;
+ padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
/* hide the description until the info icon is hovered */
display: none;
@@ -133,11 +135,10 @@
}
}
-
/* Title & logo on smaller screens... */
@media only screen and (max-width: 768px) {
-
- .Portal.Editor #editor-header, .PortalView #portal-header-container .row {
+ .Portal.Editor #editor-header,
+ .PortalView #portal-header-container .row {
flex-direction: row;
}
@@ -182,18 +183,28 @@
color: var(--portal-col-text-nav);
}
-.portal-view #portal-section-tabs .section-link-container.active .portal-section-link {
+.portal-view
+ #portal-section-tabs
+ .section-link-container.active
+ .portal-section-link {
color: var(--portal-col-text-nav-highlight, var(--portal-secondary-color));
background: transparent;
}
-#portal-section-tabs .section-link-container.active:hover, #portal-section-tabs .section-link-container:hover {
+#portal-section-tabs .section-link-container.active:hover,
+#portal-section-tabs .section-link-container:hover {
background: transparent;
}
-.portal-view #portal-section-tabs .section-link-container .portal-section-link:hover {
+.portal-view
+ #portal-section-tabs
+ .section-link-container
+ .portal-section-link:hover {
background: transparent;
- color: var(--portal-col-text-nav-hover, var(--portal-secondary-color)) !important;
+ color: var(
+ --portal-col-text-nav-hover,
+ var(--portal-secondary-color)
+ ) !important;
}
.portal-logos-view {
@@ -230,4 +241,4 @@
footer {
height: min-content;
-}
\ No newline at end of file
+}
diff --git a/src/css/portal-themes/dark.css b/src/css/portal-themes/dark.css
index 5e5cfed39..c042821a4 100644
--- a/src/css/portal-themes/dark.css
+++ b/src/css/portal-themes/dark.css
@@ -1,15 +1,16 @@
:root {
/* COLOURS */
--portal-col-bkg__deprecate: #111827;
- --portal-col-bkg-lighter__deprecate: #1F2937;
+ --portal-col-bkg-lighter__deprecate: #1f2937;
--portal-col-bkg-active__deprecate: #374151;
- --portal-col-buttons__deprecate: #4B5563F2;
- --portal-col-text-subtle__deprecate: #9CA3AF;
- --portal-col-text__deprecate: #F9FAFB;
+ --portal-col-buttons__deprecate: #4b5563f2;
+ --portal-col-text-subtle__deprecate: #9ca3af;
+ --portal-col-text__deprecate: #f9fafb;
--portal-col-highlight__deprecate: #269fb9;
--portal-col-highlight-subtle__deprecate: #0c4e66;
/* SHADOWS */
- --map-shadow-md: 0 1px 9px -1px rgba(0, 0, 0, 0.2), 0 1px 2px 0px rgba(0, 0, 0, 0.5);
+ --map-shadow-md: 0 1px 9px -1px rgba(0, 0, 0, 0.2),
+ 0 1px 2px 0px rgba(0, 0, 0, 0.5);
/* Colors used in the 'loading-metrics.html' template, on Metrics page */
--m-chart-bkg: var(--portal-col-bkg-lighter__deprecate);
--m-chart-lines: var(--portal-col-bkg-active__deprecate);
@@ -20,20 +21,29 @@
color: var(--portal-col-text-subtle__deprecate);
}
-.portal-view, .Portal #Content, .PortalView #Content {
+.portal-view,
+.Portal #Content,
+.PortalView #Content {
color: var(--portal-col-text__deprecate);
background-color: var(--portal-col-bkg-lighter__deprecate);
}
-body, #portal-sections {
+body,
+#portal-sections {
background-color: var(--portal-col-bkg__deprecate);
}
-#portal-sections{
+#portal-sections {
box-shadow: inset 0 7px 9px -7px rgb(0 0 0 / 40%);
}
-.portal-view, .portal-view .portal-section-content, .portal-view h2, .portal-view h3, .portal-view h4, .portal-view h5, .portal-view h6 {
+.portal-view,
+.portal-view .portal-section-content,
+.portal-view h2,
+.portal-view h3,
+.portal-view h4,
+.portal-view h5,
+.portal-view h6 {
color: var(--portal-col-text__deprecate);
}
@@ -45,33 +55,45 @@ body, #portal-sections {
line-height: 1.2;
}
-.portal-display-image .portal-display-text, .portal-editor .portal-display-text, .portal-section .portal-display-text {
+.portal-display-image .portal-display-text,
+.portal-editor .portal-display-text,
+.portal-section .portal-display-text {
background-color: rgb(28 39 64 / 82%);
}
-.Portal.Editor #Navbar, .PortalView #Navbar, .Portal.Editor .navbar-inner, .PortalView .navbar-inner, .Portal .d1_nav, .PortalView .d1_nav {
+.Portal.Editor #Navbar,
+.PortalView #Navbar,
+.Portal.Editor .navbar-inner,
+.PortalView .navbar-inner,
+.Portal .d1_nav,
+.PortalView .d1_nav {
color: var(--portal-col-text__deprecate);
background-color: var(--portal-col-bkg-active__deprecate);
}
-.navbar-inner .nav>li>a, #nav-trigger, .header .nav li a, .Portal.Editor #Navbar .brand::before, .PortalView #Navbar .brand::before {
+.navbar-inner .nav > li > a,
+#nav-trigger,
+.header .nav li a,
+.Portal.Editor #Navbar .brand::before,
+.PortalView #Navbar .brand::before {
color: var(--portal-col-text__deprecate);
}
-.navbar-inner .nav>li>a:hover {
- color: var(--portal-col-highlight__deprecate)
+.navbar-inner .nav > li > a:hover {
+ color: var(--portal-col-highlight__deprecate);
}
#Navbar .nav .dropdown-toggle .caret {
border-top-color: var(--portal-col-text__deprecate);
}
-.Portal.Editor #Navbar #logo::after, .PortalView #Navbar #logo::after {
+.Portal.Editor #Navbar #logo::after,
+.PortalView #Navbar #logo::after {
color: var(--portal-col-text-subtle__deprecate);
}
.portal-view .portal-description {
- color: var(--portal-col-text__deprecate)
+ color: var(--portal-col-text__deprecate);
}
/* sign in button */
@@ -81,7 +103,7 @@ body, #portal-sections {
color: var(--portal-col-text-subtle__deprecate);
}
-.header .nav li a.btn.login>.icon {
+.header .nav li a.btn.login > .icon {
opacity: 0.7;
}
@@ -89,19 +111,21 @@ body, #portal-sections {
color: var(--portal-col-text-subtle__deprecate);
}
-footer, #Footer {
+footer,
+#Footer {
color: var(--portal-col-text__deprecate);
background-color: var(--portal-col-bkg__deprecate);
border: none;
}
-footer .footnote, footer .adc-contact .contact-title {
+footer .footnote,
+footer .adc-contact .contact-title {
color: var(--portal-col-text__deprecate);
opacity: 0.7;
}
a {
- color: var(--portal-col-highlight__deprecate)
+ color: var(--portal-col-highlight__deprecate);
}
.thumbnail {
@@ -110,27 +134,30 @@ a {
margin: 1rem 0;
}
-.table-hover tbody tr:hover>td, .table-hover tbody tr:hover>th {
+.table-hover tbody tr:hover > td,
+.table-hover tbody tr:hover > th {
background-color: var(--portal-col-bkg-active__deprecate);
}
-.table td, .table th {
+.table td,
+.table th {
border-color: var(--portal-col-bkg-active__deprecate);
}
.portal-section-content thead tr {
background-color: var(--portal-primary-color-transparent);
- color: var(--portal-col-text__deprecate)
+ color: var(--portal-col-text__deprecate);
}
.portal-section-content thead th {
background-color: var(--portal-primary-color-transparent);
- color: var(--portal-col-text__deprecate)
+ color: var(--portal-col-text__deprecate);
}
/* Table of contents in portal markdown section */
-.portal-editor .toc-ul, .portal-view .toc-ul {
+.portal-editor .toc-ul,
+.portal-view .toc-ul {
background-color: var(--portal-col-bkg-active__deprecate);
border-radius: 0.5rem;
opacity: 0.85;
@@ -139,14 +166,18 @@ a {
box-shadow: 0px 1px 15px -1px rgba(32, 23, 23, 0.9);
}
-.portal-view .toc li.active>a, .portal-editor .toc li.active>a, .portal-view .toc-ul li>a:hover, .portal-editor .toc-ul li>a:hover {
+.portal-view .toc li.active > a,
+.portal-editor .toc li.active > a,
+.portal-view .toc-ul li > a:hover,
+.portal-editor .toc-ul li > a:hover {
border-radius: 0.5rem;
box-shadow: 0px 1px 10px -1px rgba(0, 0, 0, 0.5);
}
/* Metrics page */
-.stripe, .profile .charts-container {
+.stripe,
+.profile .charts-container {
background-color: transparent;
}
@@ -168,7 +199,14 @@ a {
/* SVG charts */
-.donut-title-text, svg .title, svg .tick text, .donut-arc-count, .portal-view .donut-title-count.data, .portal-view .donut-title-count.metadata, .portal-view .donut-title-text, .portal-view .donut-title-text {
+.donut-title-text,
+svg .title,
+svg .tick text,
+.donut-arc-count,
+.portal-view .donut-title-count.data,
+.portal-view .donut-title-count.metadata,
+.portal-view .donut-title-text,
+.portal-view .donut-title-text {
color: var(--portal-col-text-subtle__deprecate);
fill: var(--portal-col-text-subtle__deprecate);
}
@@ -177,11 +215,15 @@ a {
stroke: var(--portal-col-bkg-lighter__deprecate);
}
-#metric-modal .metric-chart rect.no-data, .views-metrics .metric-chart rect.no-data, .downloads-metrics .metric-chart rect.no-data {
+#metric-modal .metric-chart rect.no-data,
+.views-metrics .metric-chart rect.no-data,
+.downloads-metrics .metric-chart rect.no-data {
fill: var(--portal-col-bkg-lighter__deprecate);
}
-#metric-modal .metric-chart text.no-data, .views-metrics .metric-chart text.no-data, .downloads-metrics .metric-chart text.no-data {
+#metric-modal .metric-chart text.no-data,
+.views-metrics .metric-chart text.no-data,
+.downloads-metrics .metric-chart text.no-data {
fill: var(--portal-col-text__deprecate);
}
@@ -189,40 +231,73 @@ a {
background-color: var(--portal-col-bkg-lighter__deprecate);
}
-.no-activity h3, .no-activity h4, .no-activity h5, .no-activity h6, .no-activity p, .no-activity .summary-container p, .no-activity a, .no-activity .message, .no-activity svg .title, .no-activity svg .bar-label, .profile .no-activity .packages p {
+.no-activity h3,
+.no-activity h4,
+.no-activity h5,
+.no-activity h6,
+.no-activity p,
+.no-activity .summary-container p,
+.no-activity a,
+.no-activity .message,
+.no-activity svg .title,
+.no-activity svg .bar-label,
+.profile .no-activity .packages p {
color: var(--portal-col-text-subtle__deprecate);
stroke: var(--portal-col-text-subtle__deprecate);
fill: var(--portal-col-text-subtle__deprecate);
}
-.donut.no-activity>g .donut-arc, .donut.data.no-activity>g .donut-arc, .donut.metadata.no-activity>g .donut-arc, .donut.no-activity .donut-title-count, .donut.no-activity .donut-title-text {
- fill: var(--portal-col-buttons__deprecate)
+.donut.no-activity > g .donut-arc,
+.donut.data.no-activity > g .donut-arc,
+.donut.metadata.no-activity > g .donut-arc,
+.donut.no-activity .donut-title-count,
+.donut.no-activity .donut-title-text {
+ fill: var(--portal-col-buttons__deprecate);
}
-#metric-modal .metric-chart rect.pane, .views-metrics .metric-chart rect.pane, .downloads-metrics .metric-chart rect.pane {
+#metric-modal .metric-chart rect.pane,
+.views-metrics .metric-chart rect.pane,
+.downloads-metrics .metric-chart rect.pane {
fill: var(--portal-col-bkg-lighter__deprecate);
}
-.portal-view #metric-modal .metric-chart .bar, .portal-view .views-metrics .metric-chart .bar, .portal-view .downloads-metrics .metric-chart .bar, .portal-view #metric-modal .metric-chart .scale_button:hover rect, .portal-view #metric-modal .metric-chart .bar, .portal-view #metric-modal .metric-chart .bar_context, .portal-view .views-metrics .metric-chart .scale_button:hover rect, .portal-view .views-metrics .metric-chart .bar, .portal-view .views-metrics .metric-chart .bar_context, .portal-view .downloads-metrics .metric-chart .scale_button:hover rect, .portal-view .downloads-metrics .metric-chart .bar, .portal-view .downloads-metrics .metric-chart .bar_context {
+.portal-view #metric-modal .metric-chart .bar,
+.portal-view .views-metrics .metric-chart .bar,
+.portal-view .downloads-metrics .metric-chart .bar,
+.portal-view #metric-modal .metric-chart .scale_button:hover rect,
+.portal-view #metric-modal .metric-chart .bar,
+.portal-view #metric-modal .metric-chart .bar_context,
+.portal-view .views-metrics .metric-chart .scale_button:hover rect,
+.portal-view .views-metrics .metric-chart .bar,
+.portal-view .views-metrics .metric-chart .bar_context,
+.portal-view .downloads-metrics .metric-chart .scale_button:hover rect,
+.portal-view .downloads-metrics .metric-chart .bar,
+.portal-view .downloads-metrics .metric-chart .bar_context {
filter: brightness(1.2);
}
-#metric-modal .metric-chart text, .views-metrics .metric-chart text, .downloads-metrics .metric-chart text {
- fill: var(--portal-col-text-subtle__deprecate)
+#metric-modal .metric-chart text,
+.views-metrics .metric-chart text,
+.downloads-metrics .metric-chart text {
+ fill: var(--portal-col-text-subtle__deprecate);
}
-#metric-modal .metric-chart .y.axis line, .views-metrics .metric-chart .y.axis line, .downloads-metrics .metric-chart .y.axis line {
- fill: var(--portal-col-text-subtle__deprecate)
+#metric-modal .metric-chart .y.axis line,
+.views-metrics .metric-chart .y.axis line,
+.downloads-metrics .metric-chart .y.axis line {
+ fill: var(--portal-col-text-subtle__deprecate);
}
-#metric-modal .metric-chart .scale_button rect, .views-metrics .metric-chart .scale_button rect, .downloads-metrics .metric-chart .scale_button rect {
- fill: var(--portal-col-bkg-active__deprecate)
+#metric-modal .metric-chart .scale_button rect,
+.views-metrics .metric-chart .scale_button rect,
+.downloads-metrics .metric-chart .scale_button rect {
+ fill: var(--portal-col-bkg-active__deprecate);
}
/* members page */
.portal-view #Members .row-fluid:nth-child(odd) {
- background-color: var(--portal-col-bkg-lighter__deprecate)
+ background-color: var(--portal-col-bkg-lighter__deprecate);
}
/* data page */
@@ -239,15 +314,32 @@ a {
border-top: 1px solid var(--portal-col-bkg-active__deprecate);
}
-.pagination ul>li>a, .pagination ul>li>span {
+.pagination ul > li > a,
+.pagination ul > li > span {
border: 1px solid var(--portal-col-bkg-active__deprecate);
background-color: var(--portal-col-bkg__deprecate);
}
-select, .uneditable-input, input[type=text], input[type=password], input[type=datetime], input[type=datetime-local], input[type=date], input[type=month], input[type=time], input[type=week], input[type=number], input[type=email], input[type=url], input[type=tel], input[type=color], input[type=search], textarea {
+select,
+.uneditable-input,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="tel"],
+input[type="color"],
+input[type="search"],
+textarea {
border: 1px solid var(--portal-col-bkg-active__deprecate);
background-color: var(--portal-col-bkg-lighter__deprecate);
- color: var(--portal-col-text__deprecate)
+ color: var(--portal-col-text__deprecate);
}
.filter-groups .filter .btn:not(.btn-filter-editor) {
@@ -256,7 +348,9 @@ select, .uneditable-input, input[type=text], input[type=password], input[type=da
background-color: var(--portal-col-bkg-active__deprecate);
}
-.filter-group-link a, .nav-tabs .filter-group-link a, .nav-tabs .filter-group-link.active a {
+.filter-group-link a,
+.nav-tabs .filter-group-link a,
+.nav-tabs .filter-group-link.active a {
background-color: var(--portal-col-bkg__deprecate);
}
@@ -264,15 +358,21 @@ select, .uneditable-input, input[type=text], input[type=password], input[type=da
background-color: var(--portal-col-bkg-lighter__deprecate);
}
-.nav-tabs .filter-group-link a:hover, .filter-group-link a, .nav-tabs .filter-group-link a, .nav-tabs .filter-group-link.active a {
+.nav-tabs .filter-group-link a:hover,
+.filter-group-link a,
+.nav-tabs .filter-group-link a,
+.nav-tabs .filter-group-link.active a {
border-color: var(--portal-col-bkg-lighter__deprecate);
}
-.filter-group-link, .nav>li>a:focus, .nav>li>a:hover {
+.filter-group-link,
+.nav > li > a:focus,
+.nav > li > a:hover {
background-color: var(--portal-col-bkg-lighter__deprecate);
}
-.filter-group-links, .nav-tabs {
+.filter-group-links,
+.nav-tabs {
border-color: var(--portal-col-bkg-lighter__deprecate);
}
@@ -282,12 +382,13 @@ select, .uneditable-input, input[type=text], input[type=password], input[type=da
.catalog-metrics .badge {
background-color: var(--portal-col-bkg-active__deprecate);
- color: var(--portal-col-text-subtle__deprecate)
+ color: var(--portal-col-text-subtle__deprecate);
}
/* Various elements in the portal view that need to be a little bit lighter */
-.portal-section-view a, .portal-view .quick-stats-count {
+.portal-section-view a,
+.portal-view .quick-stats-count {
filter: brightness(1.25);
}
@@ -295,23 +396,31 @@ select, .uneditable-input, input[type=text], input[type=password], input[type=da
filter: brightness(1.2);
}
-.Portal.Editor #Navbar .brand, .PortalView #Navbar .brand {
+.Portal.Editor #Navbar .brand,
+.PortalView #Navbar .brand {
filter: brightness(1.6);
}
.citations-metrics-list > .metric-table.table.table-striped.table-condensed td {
- background-color: var(--portal-col-bkg__deprecate)
+ background-color: var(--portal-col-bkg__deprecate);
}
/* nav in dataone theme */
-.PortalView .d1_nav__minimal-nav .d1_menu-item__top-item-name, .Portal .d1_nav__minimal-nav .d1_menu-item__top-item-name, .PortalView .d1_menu-item__icon, .Portal .d1_menu-item__icon, .PortalView .d1_menu-item__dropdown-icon, .Portal .d1_menu-item__dropdown-icon {
+.PortalView .d1_nav__minimal-nav .d1_menu-item__top-item-name,
+.Portal .d1_nav__minimal-nav .d1_menu-item__top-item-name,
+.PortalView .d1_menu-item__icon,
+.Portal .d1_menu-item__icon,
+.PortalView .d1_menu-item__dropdown-icon,
+.Portal .d1_menu-item__dropdown-icon {
filter: brightness(1.6);
}
/* loading */
-.notification.loading p, .notification.loading .icon, .stripe .notification.loading i {
- color: var(--portal-col-text-subtle__deprecate)
+.notification.loading p,
+.notification.loading .icon,
+.stripe .notification.loading i {
+ color: var(--portal-col-text-subtle__deprecate);
}
/* data page search results */
@@ -321,4 +430,4 @@ select, .uneditable-input, input[type=text], input[type=password], input[type=da
}
.map-toggle-container {
background-color: var(--portal-col-bkg-lighter__deprecate);
-}
\ No newline at end of file
+}
diff --git a/src/css/portal-themes/light.css b/src/css/portal-themes/light.css
index cf02f878c..907899843 100644
--- a/src/css/portal-themes/light.css
+++ b/src/css/portal-themes/light.css
@@ -1,25 +1,25 @@
-@import url('https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600;700&display=swap');
+@import url("https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600;700&display=swap");
:root {
/* COLOURS */
- --portal-blue-1: #ECF8FC;
- --portal-blue-2: #CCF0FC;
- --portal-blue-2-50: #CCF0FC80;
- --portal-blue-3: #99E1FA;
- --portal-blue-4: #33C3F5;
- --portal-blue-5: #00A9E4;
- --portal-blue-5-50: #00A9E480;
- --portal-blue-6: #0087B5;
+ --portal-blue-1: #ecf8fc;
+ --portal-blue-2: #ccf0fc;
+ --portal-blue-2-50: #ccf0fc80;
+ --portal-blue-3: #99e1fa;
+ --portal-blue-4: #33c3f5;
+ --portal-blue-5: #00a9e4;
+ --portal-blue-5-50: #00a9e480;
+ --portal-blue-6: #0087b5;
--portal-blue-7: #136682;
- --portal-grey-1: #F8F9FA;
- --portal-grey-2: #F1F3F4;
- --portal-grey-2-50: #F1F3F480;
- --portal-grey-3: #E8EAED;
- --portal-grey-4: #BDC1C6;
- --portal-grey-5: #80868B;
- --portal-grey-6: #5F6368;
- --portal-grey-7: #3C4043;
+ --portal-grey-1: #f8f9fa;
+ --portal-grey-2: #f1f3f4;
+ --portal-grey-2-50: #f1f3f480;
+ --portal-grey-3: #e8eaed;
+ --portal-grey-4: #bdc1c6;
+ --portal-grey-5: #80868b;
+ --portal-grey-6: #5f6368;
+ --portal-grey-7: #3c4043;
--portal-grey-8: #202124;
--portal-col-content-bkg: white;
@@ -69,20 +69,20 @@
--portal-col-opacity-slider-active: var(--portal-blue-5);
--portal-col-opacity-slider-inactive: var(--portal-grey-3);
- --portal-col-input-error-bkg: #FFFCF4;
- --portal-col-input-error-border: #FFDDAA;
- --portal-col-input-error-text: #CA6C0B;
- --portal-col-search-match-highlight: #FEF4D6;
+ --portal-col-input-error-bkg: #fffcf4;
+ --portal-col-input-error-border: #ffddaa;
+ --portal-col-input-error-text: #ca6c0b;
+ --portal-col-search-match-highlight: #fef4d6;
--portal-col-tooltip-background: var(--portal-grey-7);
--portal-col-tooltip-text: white;
--portal-col-bkg__deprecate: #111827;
- --portal-col-bkg-lighter__deprecate: #1F2937;
+ --portal-col-bkg-lighter__deprecate: #1f2937;
--portal-col-bkg-active__deprecate: #374151;
- --portal-col-buttons__deprecate: #4B5563F2;
- --portal-col-text-subtle__deprecate: #9CA3AF;
- --portal-col-text__deprecate: #F9FAFB;
+ --portal-col-buttons__deprecate: #4b5563f2;
+ --portal-col-text-subtle__deprecate: #9ca3af;
+ --portal-col-text__deprecate: #f9fafb;
--portal-col-highlight__deprecate: #269fb9;
--portal-col-highlight-subtle__deprecate: #0c4e66;
@@ -90,7 +90,7 @@
/* SHADOWS */
--portal-shadow-md: 0 2px 4px 0px rgba(0, 0, 0, 0.16);
-
+
/* Colors used in the 'loading-metrics.html' template, on Metrics page */
--m-chart-bkg: var(--portal-col-bkg-lighter__deprecate);
--m-chart-lines: var(--portal-col-bkg-active__deprecate);
@@ -108,20 +108,29 @@ body {
color: var(--portal-col-text-subtle__deprecate);
}
-.portal-view, .Portal #Content, .PortalView #Content {
+.portal-view,
+.Portal #Content,
+.PortalView #Content {
background-color: var(--portal-col-page-bkg);
}
-body, #portal-sections {
+body,
+#portal-sections {
/* TODO: Update to var(--portal-col-content-bkg) when all portal tabs support the color palette from a theme. */
background-color: #111827;
}
-#portal-sections{
+#portal-sections {
box-shadow: inset 0 7px 9px -7px rgb(0 0 0 / 40%);
}
-.portal-view, .portal-view .portal-section-content, .portal-view h2, .portal-view h3, .portal-view h4, .portal-view h5, .portal-view h6 {
+.portal-view,
+.portal-view .portal-section-content,
+.portal-view h2,
+.portal-view h3,
+.portal-view h4,
+.portal-view h5,
+.portal-view h6 {
color: var(--portal-col-text__deprecate);
}
@@ -133,33 +142,45 @@ body, #portal-sections {
line-height: 1.2;
}
-.portal-display-image .portal-display-text, .portal-editor .portal-display-text, .portal-section .portal-display-text {
+.portal-display-image .portal-display-text,
+.portal-editor .portal-display-text,
+.portal-section .portal-display-text {
background-color: rgb(28 39 64 / 82%);
}
-.Portal.Editor #Navbar, .PortalView #Navbar, .Portal.Editor .navbar-inner, .PortalView .navbar-inner, .Portal .d1_nav, .PortalView .d1_nav {
+.Portal.Editor #Navbar,
+.PortalView #Navbar,
+.Portal.Editor .navbar-inner,
+.PortalView .navbar-inner,
+.Portal .d1_nav,
+.PortalView .d1_nav {
color: var(--portal-col-text__deprecate);
background-color: var(--portal-col-bkg-active__deprecate);
}
-.navbar-inner .nav>li>a, #nav-trigger, .header .nav li a, .Portal.Editor #Navbar .brand::before, .PortalView #Navbar .brand::before {
+.navbar-inner .nav > li > a,
+#nav-trigger,
+.header .nav li a,
+.Portal.Editor #Navbar .brand::before,
+.PortalView #Navbar .brand::before {
color: var(--portal-col-text__deprecate);
}
-.navbar-inner .nav>li>a:hover {
- color: var(--portal-col-highlight__deprecate)
+.navbar-inner .nav > li > a:hover {
+ color: var(--portal-col-highlight__deprecate);
}
#Navbar .nav .dropdown-toggle .caret {
border-top-color: var(--portal-col-text__deprecate);
}
-.Portal.Editor #Navbar #logo::after, .PortalView #Navbar #logo::after {
+.Portal.Editor #Navbar #logo::after,
+.PortalView #Navbar #logo::after {
color: var(--portal-col-text-subtle__deprecate);
}
.portal-view .portal-description {
- color: var(--portal-col-text__deprecate)
+ color: var(--portal-col-text__deprecate);
}
/* sign in button */
@@ -169,7 +190,7 @@ body, #portal-sections {
color: var(--portal-col-text-subtle__deprecate);
}
-.header .nav li a.btn.login>.icon {
+.header .nav li a.btn.login > .icon {
opacity: 0.7;
}
@@ -177,19 +198,21 @@ body, #portal-sections {
color: var(--portal-col-text-subtle__deprecate);
}
-footer, #Footer {
+footer,
+#Footer {
color: var(--portal-col-text__deprecate);
background-color: var(--portal-col-bkg__deprecate);
border: none;
}
-footer .footnote, footer .adc-contact .contact-title {
+footer .footnote,
+footer .adc-contact .contact-title {
color: var(--portal-col-text__deprecate);
opacity: 0.7;
}
a {
- color: var(--portal-col-highlight__deprecate)
+ color: var(--portal-col-highlight__deprecate);
}
.thumbnail {
@@ -198,27 +221,30 @@ a {
margin: 1rem 0;
}
-.table-hover tbody tr:hover>td, .table-hover tbody tr:hover>th {
+.table-hover tbody tr:hover > td,
+.table-hover tbody tr:hover > th {
background-color: var(--portal-col-bkg-active__deprecate);
}
-.table td, .table th {
+.table td,
+.table th {
border-color: var(--portal-col-bkg-active__deprecate);
}
.portal-section-content thead tr {
background-color: var(--portal-primary-color-transparent);
- color: var(--portal-col-text__deprecate)
+ color: var(--portal-col-text__deprecate);
}
.portal-section-content thead th {
background-color: var(--portal-primary-color-transparent);
- color: var(--portal-col-text__deprecate)
+ color: var(--portal-col-text__deprecate);
}
/* Table of contents in portal markdown section */
-.portal-editor .toc-ul, .portal-view .toc-ul {
+.portal-editor .toc-ul,
+.portal-view .toc-ul {
background-color: var(--portal-col-bkg-active__deprecate);
border-radius: 0.5rem;
opacity: 0.85;
@@ -227,14 +253,18 @@ a {
box-shadow: 0px 1px 15px -1px rgba(32, 23, 23, 0.9);
}
-.portal-view .toc li.active>a, .portal-editor .toc li.active>a, .portal-view .toc-ul li>a:hover, .portal-editor .toc-ul li>a:hover {
+.portal-view .toc li.active > a,
+.portal-editor .toc li.active > a,
+.portal-view .toc-ul li > a:hover,
+.portal-editor .toc-ul li > a:hover {
border-radius: 0.5rem;
box-shadow: 0px 1px 10px -1px rgba(0, 0, 0, 0.5);
}
/* Metrics page */
-.stripe, .profile .charts-container {
+.stripe,
+.profile .charts-container {
background-color: transparent;
}
@@ -256,7 +286,14 @@ a {
/* SVG charts */
-.donut-title-text, svg .title, svg .tick text, .donut-arc-count, .portal-view .donut-title-count.data, .portal-view .donut-title-count.metadata, .portal-view .donut-title-text, .portal-view .donut-title-text {
+.donut-title-text,
+svg .title,
+svg .tick text,
+.donut-arc-count,
+.portal-view .donut-title-count.data,
+.portal-view .donut-title-count.metadata,
+.portal-view .donut-title-text,
+.portal-view .donut-title-text {
color: var(--portal-col-text-subtle__deprecate);
fill: var(--portal-col-text-subtle__deprecate);
}
@@ -265,11 +302,15 @@ a {
stroke: var(--portal-col-bkg-lighter__deprecate);
}
-#metric-modal .metric-chart rect.no-data, .views-metrics .metric-chart rect.no-data, .downloads-metrics .metric-chart rect.no-data {
+#metric-modal .metric-chart rect.no-data,
+.views-metrics .metric-chart rect.no-data,
+.downloads-metrics .metric-chart rect.no-data {
fill: var(--portal-col-bkg-lighter__deprecate);
}
-#metric-modal .metric-chart text.no-data, .views-metrics .metric-chart text.no-data, .downloads-metrics .metric-chart text.no-data {
+#metric-modal .metric-chart text.no-data,
+.views-metrics .metric-chart text.no-data,
+.downloads-metrics .metric-chart text.no-data {
fill: var(--portal-col-text__deprecate);
}
@@ -277,40 +318,73 @@ a {
background-color: var(--portal-col-bkg-lighter__deprecate);
}
-.no-activity h3, .no-activity h4, .no-activity h5, .no-activity h6, .no-activity p, .no-activity .summary-container p, .no-activity a, .no-activity .message, .no-activity svg .title, .no-activity svg .bar-label, .profile .no-activity .packages p {
+.no-activity h3,
+.no-activity h4,
+.no-activity h5,
+.no-activity h6,
+.no-activity p,
+.no-activity .summary-container p,
+.no-activity a,
+.no-activity .message,
+.no-activity svg .title,
+.no-activity svg .bar-label,
+.profile .no-activity .packages p {
color: var(--portal-col-text-subtle__deprecate);
stroke: var(--portal-col-text-subtle__deprecate);
fill: var(--portal-col-text-subtle__deprecate);
}
-.donut.no-activity>g .donut-arc, .donut.data.no-activity>g .donut-arc, .donut.metadata.no-activity>g .donut-arc, .donut.no-activity .donut-title-count, .donut.no-activity .donut-title-text {
- fill: var(--portal-col-buttons__deprecate)
+.donut.no-activity > g .donut-arc,
+.donut.data.no-activity > g .donut-arc,
+.donut.metadata.no-activity > g .donut-arc,
+.donut.no-activity .donut-title-count,
+.donut.no-activity .donut-title-text {
+ fill: var(--portal-col-buttons__deprecate);
}
-#metric-modal .metric-chart rect.pane, .views-metrics .metric-chart rect.pane, .downloads-metrics .metric-chart rect.pane {
+#metric-modal .metric-chart rect.pane,
+.views-metrics .metric-chart rect.pane,
+.downloads-metrics .metric-chart rect.pane {
fill: var(--portal-col-bkg-lighter__deprecate);
}
-.portal-view #metric-modal .metric-chart .bar, .portal-view .views-metrics .metric-chart .bar, .portal-view .downloads-metrics .metric-chart .bar, .portal-view #metric-modal .metric-chart .scale_button:hover rect, .portal-view #metric-modal .metric-chart .bar, .portal-view #metric-modal .metric-chart .bar_context, .portal-view .views-metrics .metric-chart .scale_button:hover rect, .portal-view .views-metrics .metric-chart .bar, .portal-view .views-metrics .metric-chart .bar_context, .portal-view .downloads-metrics .metric-chart .scale_button:hover rect, .portal-view .downloads-metrics .metric-chart .bar, .portal-view .downloads-metrics .metric-chart .bar_context {
+.portal-view #metric-modal .metric-chart .bar,
+.portal-view .views-metrics .metric-chart .bar,
+.portal-view .downloads-metrics .metric-chart .bar,
+.portal-view #metric-modal .metric-chart .scale_button:hover rect,
+.portal-view #metric-modal .metric-chart .bar,
+.portal-view #metric-modal .metric-chart .bar_context,
+.portal-view .views-metrics .metric-chart .scale_button:hover rect,
+.portal-view .views-metrics .metric-chart .bar,
+.portal-view .views-metrics .metric-chart .bar_context,
+.portal-view .downloads-metrics .metric-chart .scale_button:hover rect,
+.portal-view .downloads-metrics .metric-chart .bar,
+.portal-view .downloads-metrics .metric-chart .bar_context {
filter: brightness(1.2);
}
-#metric-modal .metric-chart text, .views-metrics .metric-chart text, .downloads-metrics .metric-chart text {
- fill: var(--portal-col-text-subtle__deprecate)
+#metric-modal .metric-chart text,
+.views-metrics .metric-chart text,
+.downloads-metrics .metric-chart text {
+ fill: var(--portal-col-text-subtle__deprecate);
}
-#metric-modal .metric-chart .y.axis line, .views-metrics .metric-chart .y.axis line, .downloads-metrics .metric-chart .y.axis line {
- fill: var(--portal-col-text-subtle__deprecate)
+#metric-modal .metric-chart .y.axis line,
+.views-metrics .metric-chart .y.axis line,
+.downloads-metrics .metric-chart .y.axis line {
+ fill: var(--portal-col-text-subtle__deprecate);
}
-#metric-modal .metric-chart .scale_button rect, .views-metrics .metric-chart .scale_button rect, .downloads-metrics .metric-chart .scale_button rect {
- fill: var(--portal-col-bkg-active__deprecate)
+#metric-modal .metric-chart .scale_button rect,
+.views-metrics .metric-chart .scale_button rect,
+.downloads-metrics .metric-chart .scale_button rect {
+ fill: var(--portal-col-bkg-active__deprecate);
}
/* members page */
.portal-view #Members .row-fluid:nth-child(odd) {
- background-color: var(--portal-col-bkg-lighter__deprecate)
+ background-color: var(--portal-col-bkg-lighter__deprecate);
}
/* data page */
@@ -327,15 +401,32 @@ a {
border-top: 1px solid var(--portal-col-bkg-active__deprecate);
}
-.pagination ul>li>a, .pagination ul>li>span {
+.pagination ul > li > a,
+.pagination ul > li > span {
border: 1px solid var(--portal-col-bkg-active__deprecate);
background-color: var(--portal-col-bkg__deprecate);
}
-select, .uneditable-input, input[type=text], input[type=password], input[type=datetime], input[type=datetime-local], input[type=date], input[type=month], input[type=time], input[type=week], input[type=number], input[type=email], input[type=url], input[type=tel], input[type=color], input[type=search], textarea {
+select,
+.uneditable-input,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="tel"],
+input[type="color"],
+input[type="search"],
+textarea {
border: 1px solid var(--portal-col-bkg-active__deprecate);
background-color: var(--portal-col-bkg-lighter__deprecate);
- color: var(--portal-col-text__deprecate)
+ color: var(--portal-col-text__deprecate);
}
.filter-groups .filter .btn:not(.btn-filter-editor) {
@@ -344,7 +435,9 @@ select, .uneditable-input, input[type=text], input[type=password], input[type=da
background-color: var(--portal-col-bkg-active__deprecate);
}
-.filter-group-link a, .nav-tabs .filter-group-link a, .nav-tabs .filter-group-link.active a {
+.filter-group-link a,
+.nav-tabs .filter-group-link a,
+.nav-tabs .filter-group-link.active a {
background-color: var(--portal-col-bkg__deprecate);
}
@@ -352,15 +445,21 @@ select, .uneditable-input, input[type=text], input[type=password], input[type=da
background-color: var(--portal-col-bkg-lighter__deprecate);
}
-.nav-tabs .filter-group-link a:hover, .filter-group-link a, .nav-tabs .filter-group-link a, .nav-tabs .filter-group-link.active a {
+.nav-tabs .filter-group-link a:hover,
+.filter-group-link a,
+.nav-tabs .filter-group-link a,
+.nav-tabs .filter-group-link.active a {
border-color: var(--portal-col-bkg-lighter__deprecate);
}
-.filter-group-link, .nav>li>a:focus, .nav>li>a:hover {
+.filter-group-link,
+.nav > li > a:focus,
+.nav > li > a:hover {
background-color: var(--portal-col-bkg-lighter__deprecate);
}
-.filter-group-links, .nav-tabs {
+.filter-group-links,
+.nav-tabs {
border-color: var(--portal-col-bkg-lighter__deprecate);
}
@@ -370,12 +469,13 @@ select, .uneditable-input, input[type=text], input[type=password], input[type=da
.catalog-metrics .badge {
background-color: var(--portal-col-bkg-active__deprecate);
- color: var(--portal-col-text-subtle__deprecate)
+ color: var(--portal-col-text-subtle__deprecate);
}
/* Various elements in the portal view that need to be a little bit lighter */
-.portal-section-view a, .portal-view .quick-stats-count {
+.portal-section-view a,
+.portal-view .quick-stats-count {
filter: brightness(1.25);
}
@@ -388,23 +488,31 @@ select, .uneditable-input, input[type=text], input[type=password], input[type=da
filter: brightness(1.2);
}
-.Portal.Editor #Navbar .brand, .PortalView #Navbar .brand {
+.Portal.Editor #Navbar .brand,
+.PortalView #Navbar .brand {
filter: brightness(1.6);
}
.citations-metrics-list > .metric-table.table.table-striped.table-condensed td {
- background-color: var(--portal-col-bkg__deprecate)
+ background-color: var(--portal-col-bkg__deprecate);
}
/* nav in dataone theme */
-.PortalView .d1_nav__minimal-nav .d1_menu-item__top-item-name, .Portal .d1_nav__minimal-nav .d1_menu-item__top-item-name, .PortalView .d1_menu-item__icon, .Portal .d1_menu-item__icon, .PortalView .d1_menu-item__dropdown-icon, .Portal .d1_menu-item__dropdown-icon {
+.PortalView .d1_nav__minimal-nav .d1_menu-item__top-item-name,
+.Portal .d1_nav__minimal-nav .d1_menu-item__top-item-name,
+.PortalView .d1_menu-item__icon,
+.Portal .d1_menu-item__icon,
+.PortalView .d1_menu-item__dropdown-icon,
+.Portal .d1_menu-item__dropdown-icon {
filter: brightness(1.6);
}
/* loading */
-.notification.loading p, .notification.loading .icon, .stripe .notification.loading i {
- color: var(--portal-col-text-subtle__deprecate)
+.notification.loading p,
+.notification.loading .icon,
+.stripe .notification.loading i {
+ color: var(--portal-col-text-subtle__deprecate);
}
/* data page search results */
@@ -414,4 +522,4 @@ select, .uneditable-input, input[type=text], input[type=password], input[type=da
}
.map-toggle-container {
background-color: var(--portal-col-bkg-lighter__deprecate);
-}
\ No newline at end of file
+}
diff --git a/src/js/app.js b/src/js/app.js
index a8c96374e..81945ca08 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -1,142 +1,162 @@
/*global require */
/*jshint unused:false */
-'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;
- });
-
+"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);
-
+ define("gmaps", null);
}
-MetacatUI.d3URL = '../components/d3.v3.min';
-
+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/',
+ baseUrl: MetacatUI.root + "/js/",
waitSeconds: 180, //wait 3 minutes before throwing a timeout error
map: MetacatUI.themeMap,
- urlArgs: "v=" + (MetacatUI.AppConfig.cachebuster || MetacatUI.metacatUIVersion),
+ 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],
+ 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 */
+ shim: {
+ /* used for libraries without native AMD support */
underscore: {
- exports: '_',
+ exports: "_",
},
backbone: {
- deps: ['underscore', 'jquery'],
- exports: 'Backbone'
+ deps: ["underscore", "jquery"],
+ exports: "Backbone",
},
bootstrap: {
- deps: ['jquery'],
- exports: 'Bootstrap'
+ deps: ["jquery"],
+ exports: "Bootstrap",
},
annotator: {
- exports: 'Annotator'
+ exports: "Annotator",
},
bioportal: {
- exports: 'Bioportal'
+ exports: "Bioportal",
},
jws: {
- exports: 'JWS',
- deps: ['jsrasign'],
+ exports: "JWS",
+ deps: ["jsrasign"],
+ },
+ nGeohash: {
+ exports: "geohash",
+ },
+ fancybox: {
+ deps: ["jquery"],
},
- nGeohash: {
- exports: "geohash"
- },
- fancybox: {
- deps: ['jquery']
- },
- uuid: {
- exports: 'uuid'
+ uuid: {
+ exports: "uuid",
},
rdflib: {
- exports: 'rdf'
+ exports: "rdf",
},
- xss: {
- exports: 'filterXSS'
- },
- citation: {
- exports: 'citationRequire'
- },
- promise: {
- exports: 'Promise'
- },
- metacatuiConnectors: {
- exports: "FiltersSearchConnector"
- }
- }
+ xss: {
+ exports: "filterXSS",
+ },
+ citation: {
+ exports: "citationRequire",
+ },
+ promise: {
+ exports: "Promise",
+ },
+ metacatuiConnectors: {
+ exports: "FiltersSearchConnector",
+ },
+ },
});
MetacatUI.appModel = MetacatUI.appModel || {};
@@ -145,10 +165,10 @@ 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.
-*/
+ * @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 || {};
@@ -158,180 +178,228 @@ 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
+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
+ 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);
+ MetacatUI.eventDispatcher = _.clone(Backbone.Events);
- //Load the App View now
- MetacatUI.appView = new AppView();
+ //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.
+ // 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");
- });
+ * @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");
+ });
});
diff --git a/src/js/collections/AccessPolicy.js b/src/js/collections/AccessPolicy.js
index bddd8e9b2..b69c8a4fe 100644
--- a/src/js/collections/AccessPolicy.js
+++ b/src/js/collections/AccessPolicy.js
@@ -1,381 +1,355 @@
"use strict";
-define(["jquery", "underscore", "backbone", "models/AccessRule"],
- function($, _, Backbone, AccessRule) {
+define(["jquery", "underscore", "backbone", "models/AccessRule"], function (
+ $,
+ _,
+ Backbone,
+ AccessRule,
+) {
+ /**
+ * @class AccessPolicy
+ * @classdesc An AccessPolicy collection is a collection of AccessRules that specify
+ * the permissions set on a DataONEObject
+ * @classcategory Collections
+ * @extends Backbone.Collection
+ */
+ var AccessPolicy = Backbone.Collection.extend(
+ /** @lends AccessPolicy.prototype */
+ {
+ model: AccessRule,
/**
- * @class AccessPolicy
- * @classdesc An AccessPolicy collection is a collection of AccessRules that specify
- * the permissions set on a DataONEObject
- * @classcategory Collections
- * @extends Backbone.Collection
+ * The DataONEObject that will be saved with this AccessPolicy
+ * @type {DataONEObject}
*/
- var AccessPolicy = Backbone.Collection.extend(
- /** @lends AccessPolicy.prototype */
- {
+ dataONEObject: null,
- model: AccessRule,
+ initialize: function () {
+ //When a model triggers the event "removeMe", remove it from this collection
+ this.on("removeMe", this.removeAccessRule);
+ },
- /**
- * The DataONEObject that will be saved with this AccessPolicy
- * @type {DataONEObject}
- */
- dataONEObject: null,
-
- initialize: function(){
-
- //When a model triggers the event "removeMe", remove it from this collection
- this.on("removeMe", this.removeAccessRule);
-
- },
-
- /**
- * Parses the given access policy XML and creates AccessRule models for
- * each rule in the access policy XML. Adds these models to this collection.
- * @param {Element} The XML DOM that contains a set of
- * access rules.
- */
- parse: function(accessPolicyXML){
-
- var originalLength = this.length,
- newLength = 0;
-
- //Parse each "allow" access rule
- _.each( $(accessPolicyXML).children(), function(accessRuleXML, i){
-
- var accessRuleModel;
-
- //Update the AccessRule models that already exist in the collection, first.
- // This is important to keep listeners thoughout the app intact.
- if( AccessRule.prototype.isPrototypeOf(this.models[i]) ){
- accessRuleModel = this.models[i];
- }
- //Create new AccessRules for all others
- else{
- accessRuleModel = new AccessRule();
- this.add( accessRuleModel );
- }
-
- newLength++;
-
- //Reset all the values first
- accessRuleModel.set( accessRuleModel.defaults() );
- //Parse the AccessRule model and update the model attributes
- accessRuleModel.set( accessRuleModel.parse(accessRuleXML) );
- //Save a reference to the DataONEObbject
- accessRuleModel.set("dataONEObject", this.dataONEObject);
-
- }, this);
-
- //If there are more AccessRules in this collection than were in the
- // system metadata XML, then remove the extras
- if( originalLength > newLength ){
- for(var i=0; i < (originalLength - newLength); i++){
- this.pop();
- }
- }
-
- },
-
- /**
- * Creates AccessRule member models from the `defaultAccessPolicy`
- * setting in the AppModel.
- */
- createDefaultPolicy: function(){
-
- //For each access policy in the AppModel, create an AccessRule model
- _.each(MetacatUI.appModel.get("defaultAccessPolicy"), function(accessRule){
-
- accessRule.dataONEObject = this.dataONEObject;
-
- this.add( new AccessRule(accessRule) );
-
- }, this);
-
- },
-
- /**
- * Copies all the AccessRules from the given AccessPolicy and replaces this AccessPolicy
- * @param {AccessPolicy} otherAccessPolicy
- * @fires Backbone.Collection#reset
- * @since 2.15.0
- */
- copyAccessPolicy: function(otherAccessPolicy){
-
- try{
-
- let accessRules = [];
-
- //For each access policy in the AppModel, create an AccessRule model
- otherAccessPolicy.each(function(accessRule){
-
- //Convert the AccessRule model to JSON and update the reference to the DataONEObject
- let accessRuleJSON = accessRule.toJSON();
- accessRuleJSON.dataONEObject = this.dataONEObject;
- accessRules.push(accessRuleJSON);
-
- }, this);
-
- //Reset the Collection with these AccessRules
- this.reset(accessRules);
+ /**
+ * Parses the given access policy XML and creates AccessRule models for
+ * each rule in the access policy XML. Adds these models to this collection.
+ * @param {Element} The XML DOM that contains a set of
+ * access rules.
+ */
+ parse: function (accessPolicyXML) {
+ var originalLength = this.length,
+ newLength = 0;
+
+ //Parse each "allow" access rule
+ _.each(
+ $(accessPolicyXML).children(),
+ function (accessRuleXML, i) {
+ var accessRuleModel;
+
+ //Update the AccessRule models that already exist in the collection, first.
+ // This is important to keep listeners thoughout the app intact.
+ if (AccessRule.prototype.isPrototypeOf(this.models[i])) {
+ accessRuleModel = this.models[i];
}
- catch(e){
- console.error(e);
+ //Create new AccessRules for all others
+ else {
+ accessRuleModel = new AccessRule();
+ this.add(accessRuleModel);
}
- },
- /**
- * Creates an access policy XML from the values set on the member
- * AccessRule models.
- * @returns {object} A XML object of the access policy or null if empty
- */
- serialize: function() {
- if (this.length === 0) {
- return null;
- }
-
- // Create the access policy node which will contain all the rules
- var accessPolicyElement = document.createElement('accesspolicy');
-
- // Serialize each AccessRule member model and add to the policy DOM
- this.each(function(accessRule) {
- var accessRuleNode = accessRule.serialize();
- if (accessRuleNode) {
- accessPolicyElement.appendChild(accessRuleNode);
- }
- });
-
- return accessPolicyElement;
- },
-
- /**
- * Removes access rules that grant public access and sets an access rule
- * that denies public read.
- */
- makePrivate: function(){
-
- var alreadyPrivate = false;
-
- //Find the public access rules and remove them
- this.each( function(accessRule){
-
- if( typeof accessRule === "undefined" )
- return;
-
- //If the access rule subject is `public` and they are given any kind of access,
- if( accessRule.get("subject") == "public" &&
- (accessRule.get("read") || accessRule.get("write") || accessRule.get("changePermission")) ){
-
- //Remove this AccessRule model from the collection
- this.remove(accessRule);
-
- }
-
- }, this);
+ newLength++;
+ //Reset all the values first
+ accessRuleModel.set(accessRuleModel.defaults());
+ //Parse the AccessRule model and update the model attributes
+ accessRuleModel.set(accessRuleModel.parse(accessRuleXML));
+ //Save a reference to the DataONEObbject
+ accessRuleModel.set("dataONEObject", this.dataONEObject);
},
+ this,
+ );
+
+ //If there are more AccessRules in this collection than were in the
+ // system metadata XML, then remove the extras
+ if (originalLength > newLength) {
+ for (var i = 0; i < originalLength - newLength; i++) {
+ this.pop();
+ }
+ }
+ },
- /**
- * Removes any AccessRule that denies public read and adds an AccessRule
- * that allows public read
- */
- makePublic: function(){
-
- var alreadyPublic = false;
-
- //Find any public read rule and set read=true
- this.each( function(accessRule){
-
- if( typeof accessRule === "undefined" )
- return;
-
- //If the access rule subject is `public` and they are denied read access
- if( accessRule.get("subject") == "public" ){
-
- //Remove this AccessRule model from the collection
- accessRule.set("read", true);
- alreadyPublic = true;
-
- }
-
- }, this);
-
- //If this policy does not already allow the public read access, then add that rule
- if( !alreadyPublic ){
- //Create an access rule that allows public read
- var publicAllow = new AccessRule({
- subject: "public",
- read: true,
- dataONEObject: this.dataONEObject
- });
- //Add this access rule
- this.add(publicAllow);
- }
-
+ /**
+ * Creates AccessRule member models from the `defaultAccessPolicy`
+ * setting in the AppModel.
+ */
+ createDefaultPolicy: function () {
+ //For each access policy in the AppModel, create an AccessRule model
+ _.each(
+ MetacatUI.appModel.get("defaultAccessPolicy"),
+ function (accessRule) {
+ accessRule.dataONEObject = this.dataONEObject;
+
+ this.add(new AccessRule(accessRule));
},
+ this,
+ );
+ },
- /**
- * Returns true if this access policy specifies that it is accessible to
- * the public in any way
- * @return {boolean}
- */
- isPublic: function(){
-
- var isPublic = false;
-
- this.each(function(accessRule){
-
- if( accessRule.get("subject") == "public" &&
- (accessRule.get("read") || accessRule.get("write") || accessRule.get("changePermission")) ){
- isPublic = true;
- }
-
- });
-
- return isPublic;
+ /**
+ * Copies all the AccessRules from the given AccessPolicy and replaces this AccessPolicy
+ * @param {AccessPolicy} otherAccessPolicy
+ * @fires Backbone.Collection#reset
+ * @since 2.15.0
+ */
+ copyAccessPolicy: function (otherAccessPolicy) {
+ try {
+ let accessRules = [];
+
+ //For each access policy in the AppModel, create an AccessRule model
+ otherAccessPolicy.each(function (accessRule) {
+ //Convert the AccessRule model to JSON and update the reference to the DataONEObject
+ let accessRuleJSON = accessRule.toJSON();
+ accessRuleJSON.dataONEObject = this.dataONEObject;
+ accessRules.push(accessRuleJSON);
+ }, this);
+
+ //Reset the Collection with these AccessRules
+ this.reset(accessRules);
+ } catch (e) {
+ console.error(e);
+ }
+ },
- },
+ /**
+ * Creates an access policy XML from the values set on the member
+ * AccessRule models.
+ * @returns {object} A XML object of the access policy or null if empty
+ */
+ serialize: function () {
+ if (this.length === 0) {
+ return null;
+ }
+
+ // Create the access policy node which will contain all the rules
+ var accessPolicyElement = document.createElement("accesspolicy");
+
+ // Serialize each AccessRule member model and add to the policy DOM
+ this.each(function (accessRule) {
+ var accessRuleNode = accessRule.serialize();
+ if (accessRuleNode) {
+ accessPolicyElement.appendChild(accessRuleNode);
+ }
+ });
- /**
- * Checks if the current user is authorized to perform the given action
- * based on the current access rules in this collection
- *
- * @param {string} action - The action to check authorization for. Can
- * be either `read`, `write`, or `changePermission`
- * @return {boolean} - Returns true is the user can perform this action,
- * false if not.
- */
- isAuthorized: function(action){
- if( typeof action == "undefined" || !action )
- return false;
-
- //Get the access rules for the user's subject or groups
- var allSubjects = [];
- if( !MetacatUI.appUserModel.get("loggedIn") )
- allSubjects = "public";
- else{
-
- allSubjects = _.union(MetacatUI.appUserModel.get("identities"),
- _.pluck(MetacatUI.appUserModel.get("isMemberOf"), "groupId"),
- [MetacatUI.appUserModel.get("username")]);
+ return accessPolicyElement;
+ },
+ /**
+ * Removes access rules that grant public access and sets an access rule
+ * that denies public read.
+ */
+ makePrivate: function () {
+ var alreadyPrivate = false;
+
+ //Find the public access rules and remove them
+ this.each(function (accessRule) {
+ if (typeof accessRule === "undefined") return;
+
+ //If the access rule subject is `public` and they are given any kind of access,
+ if (
+ accessRule.get("subject") == "public" &&
+ (accessRule.get("read") ||
+ accessRule.get("write") ||
+ accessRule.get("changePermission"))
+ ) {
+ //Remove this AccessRule model from the collection
+ this.remove(accessRule);
+ }
+ }, this);
+ },
- }
+ /**
+ * Removes any AccessRule that denies public read and adds an AccessRule
+ * that allows public read
+ */
+ makePublic: function () {
+ var alreadyPublic = false;
+
+ //Find any public read rule and set read=true
+ this.each(function (accessRule) {
+ if (typeof accessRule === "undefined") return;
+
+ //If the access rule subject is `public` and they are denied read access
+ if (accessRule.get("subject") == "public") {
+ //Remove this AccessRule model from the collection
+ accessRule.set("read", true);
+ alreadyPublic = true;
+ }
+ }, this);
+
+ //If this policy does not already allow the public read access, then add that rule
+ if (!alreadyPublic) {
+ //Create an access rule that allows public read
+ var publicAllow = new AccessRule({
+ subject: "public",
+ read: true,
+ dataONEObject: this.dataONEObject,
+ });
+ //Add this access rule
+ this.add(publicAllow);
+ }
+ },
- //Find the access rules that match the given action and user subjects
- var applicableRules = this.filter(function(accessRule){
- if( accessRule.get(action) && _.contains(allSubjects, accessRule.get("subject")) ) {
- return true;
- }
- }, this);
+ /**
+ * Returns true if this access policy specifies that it is accessible to
+ * the public in any way
+ * @return {boolean}
+ */
+ isPublic: function () {
+ var isPublic = false;
+
+ this.each(function (accessRule) {
+ if (
+ accessRule.get("subject") == "public" &&
+ (accessRule.get("read") ||
+ accessRule.get("write") ||
+ accessRule.get("changePermission"))
+ ) {
+ isPublic = true;
+ }
+ });
- if( applicableRules.length )
- return true;
- else if( _.contains(allSubjects, this.dataONEObject.get("rightsHolder")) )
- return true;
- else
- return false;
+ return isPublic;
+ },
- },
+ /**
+ * Checks if the current user is authorized to perform the given action
+ * based on the current access rules in this collection
+ *
+ * @param {string} action - The action to check authorization for. Can
+ * be either `read`, `write`, or `changePermission`
+ * @return {boolean} - Returns true is the user can perform this action,
+ * false if not.
+ */
+ isAuthorized: function (action) {
+ if (typeof action == "undefined" || !action) return false;
+
+ //Get the access rules for the user's subject or groups
+ var allSubjects = [];
+ if (!MetacatUI.appUserModel.get("loggedIn")) allSubjects = "public";
+ else {
+ allSubjects = _.union(
+ MetacatUI.appUserModel.get("identities"),
+ _.pluck(MetacatUI.appUserModel.get("isMemberOf"), "groupId"),
+ [MetacatUI.appUserModel.get("username")],
+ );
+ }
+
+ //Find the access rules that match the given action and user subjects
+ var applicableRules = this.filter(function (accessRule) {
+ if (
+ accessRule.get(action) &&
+ _.contains(allSubjects, accessRule.get("subject"))
+ ) {
+ return true;
+ }
+ }, this);
- /**
- * Checks if the user is authorized to update the system metadata.
- * Updates to system metadata will fail if the user doesn't have changePermission permission,
- * *unless* the user is performing an update() at the same time and has `write` permission
- * @returns {boolean}
- * @since 2.15.0
- */
- isAuthorizedUpdateSysMeta: function(){
- try{
- //Yes, if the user has changePermission
- if( this.isAuthorized("changePermission") ){
- return true;
- }
- //Yes, if the user just uploaded this object and is saving it for the first time
- else if( this.isAuthorized("write") && this.dataONEObject.isNew() ){
- return true;
- }
- else{
- return false;
- }
- }
- catch(e){
- console.error("Failed to determing authorization: " , e);
- return false;
- }
- },
+ if (applicableRules.length) return true;
+ else if (
+ _.contains(allSubjects, this.dataONEObject.get("rightsHolder"))
+ )
+ return true;
+ else return false;
+ },
- /**
- * Gets the subject info for all of the subjects in this access policy.
- * Sets the subject info on each corresponding model.
- */
- getSubjectInfo: function(){
+ /**
+ * Checks if the user is authorized to update the system metadata.
+ * Updates to system metadata will fail if the user doesn't have changePermission permission,
+ * *unless* the user is performing an update() at the same time and has `write` permission
+ * @returns {boolean}
+ * @since 2.15.0
+ */
+ isAuthorizedUpdateSysMeta: function () {
+ try {
+ //Yes, if the user has changePermission
+ if (this.isAuthorized("changePermission")) {
+ return true;
+ }
+ //Yes, if the user just uploaded this object and is saving it for the first time
+ else if (this.isAuthorized("write") && this.dataONEObject.isNew()) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (e) {
+ console.error("Failed to determing authorization: ", e);
+ return false;
+ }
+ },
- //If there are more than 5 subjects in the access policy, then get the entire list of subjects in the DataONE/CN system
- /* if( this.length > 5 ){
+ /**
+ * Gets the subject info for all of the subjects in this access policy.
+ * Sets the subject info on each corresponding model.
+ */
+ getSubjectInfo: function () {
+ //If there are more than 5 subjects in the access policy, then get the entire list of subjects in the DataONE/CN system
+ /* if( this.length > 5 ){
//TODO: Get everything from the /accounts endpoint
}
*/
- //If there are less than 5, then send individual requests to get the subject info
- this.invoke("getSubjectInfo");
-
- },
-
- /**
- * Remove the given AccessRule from this AccessPolicy
- * @param {AccessRule} accessRule - The AccessRule model to remove
- */
- removeAccessRule: function(accessRule){
+ //If there are less than 5, then send individual requests to get the subject info
+ this.invoke("getSubjectInfo");
+ },
- this.remove(accessRule);
-
- },
-
- /**
- * Checks if there is at least one AccessRule with changePermission permission
- * in this AccessPolicy.
- * @returns {boolean}
- */
- hasOwner: function(){
- try{
- var owners = this.where({ changePermission: true });
-
- //Check if there are any other subjects with ownership levels
- if( !owners || owners.length == 0 ){
+ /**
+ * Remove the given AccessRule from this AccessPolicy
+ * @param {AccessRule} accessRule - The AccessRule model to remove
+ */
+ removeAccessRule: function (accessRule) {
+ this.remove(accessRule);
+ },
- //If there is a rightsHolder, that counts as an owner
- /* if( this.dataONEObject && this.dataONEObject.get("rightsHolder") ){
+ /**
+ * Checks if there is at least one AccessRule with changePermission permission
+ * in this AccessPolicy.
+ * @returns {boolean}
+ */
+ hasOwner: function () {
+ try {
+ var owners = this.where({ changePermission: true });
+
+ //Check if there are any other subjects with ownership levels
+ if (!owners || owners.length == 0) {
+ //If there is a rightsHolder, that counts as an owner
+ /* if( this.dataONEObject && this.dataONEObject.get("rightsHolder") ){
return true;
}
*/
- return false;
- }
- else{
- return true;
- }
- }
- catch(e){
- console.error("Error getting the owners of this AccessPolicy: ", e);
- }
- },
-
- replaceRightsHolder: function(){
- var owner = this.findWhere({ changePermission: true });
-
- //Make sure the owner model was found
- if( !owner ){
- return;
- }
-
- //Set this other owner as the rightsHolder
- this.dataONEObject.set("rightsHolder", owner.get("subject"));
-
- //Remove them as an AccessRule in the AccessPolicy
- this.remove(owner);
+ return false;
+ } else {
+ return true;
}
-
- });
-
- return AccessPolicy;
-
- });
+ } catch (e) {
+ console.error("Error getting the owners of this AccessPolicy: ", e);
+ }
+ },
+
+ replaceRightsHolder: function () {
+ var owner = this.findWhere({ changePermission: true });
+
+ //Make sure the owner model was found
+ if (!owner) {
+ return;
+ }
+
+ //Set this other owner as the rightsHolder
+ this.dataONEObject.set("rightsHolder", owner.get("subject"));
+
+ //Remove them as an AccessRule in the AccessPolicy
+ this.remove(owner);
+ },
+ },
+ );
+
+ return AccessPolicy;
+});
diff --git a/src/js/collections/Citations.js b/src/js/collections/Citations.js
index bafe747dc..7f96da054 100644
--- a/src/js/collections/Citations.js
+++ b/src/js/collections/Citations.js
@@ -1,40 +1,41 @@
/* global define */
"use strict";
-define(['jquery', 'underscore', 'backbone', 'models/CitationModel'],
- function($, _, Backbone, CitationModel) {
-
- /**
- * @class Citations
- * @classdesc Citations represents the Citations list
- * found at https://app.swaggerhub.com/apis/nenuji/data-metrics/1.0.0.3.
- * For details regarding a single Citation Entity, refer `models/CitationModel`
- * @classcategory Collections
- * @name Citations
- * @extends Backbone.Collection
- * @constructor
- */
- var Citations = Backbone.Collection.extend(
- /** @lends Citations.prototype */{
-
- model: function (attrs, options) {
- // We use the inline require here in addition to the define above to
- // avoid an issue caused by the circular dependency between
- // CitationModel and Citations
- var CitationModel = require('models/CitationModel');
- return new CitationModel(attrs, options)
- },
-
- //The name of this type of collection
- type: "Citations",
-
-
- // Used for sorting the year in the reverse Chronological order
- comparator : function(model) {
- return -model.get("year_of_publishing"); // Note the minus!
- }
-
- });
-
- return Citations;
+define(["jquery", "underscore", "backbone", "models/CitationModel"], function (
+ $,
+ _,
+ Backbone,
+ CitationModel,
+) {
+ /**
+ * @class Citations
+ * @classdesc Citations represents the Citations list
+ * found at https://app.swaggerhub.com/apis/nenuji/data-metrics/1.0.0.3.
+ * For details regarding a single Citation Entity, refer `models/CitationModel`
+ * @classcategory Collections
+ * @name Citations
+ * @extends Backbone.Collection
+ * @constructor
+ */
+ var Citations = Backbone.Collection.extend(
+ /** @lends Citations.prototype */ {
+ model: function (attrs, options) {
+ // We use the inline require here in addition to the define above to
+ // avoid an issue caused by the circular dependency between
+ // CitationModel and Citations
+ var CitationModel = require("models/CitationModel");
+ return new CitationModel(attrs, options);
+ },
+
+ //The name of this type of collection
+ type: "Citations",
+
+ // Used for sorting the year in the reverse Chronological order
+ comparator: function (model) {
+ return -model.get("year_of_publishing"); // Note the minus!
+ },
+ },
+ );
+
+ return Citations;
});
diff --git a/src/js/collections/DataPackage.js b/src/js/collections/DataPackage.js
index df29d62d4..8a0b92cf3 100644
--- a/src/js/collections/DataPackage.js
+++ b/src/js/collections/DataPackage.js
@@ -2,31 +2,31 @@
"use strict";
define([
- "jquery",
- "underscore",
- "backbone",
- "rdflib",
- "uuid",
- "md5",
- "collections/SolrResults",
- "models/filters/Filter",
- "models/DataONEObject",
- "models/metadata/ScienceMetadata",
- "models/metadata/eml211/EML211",
+ "jquery",
+ "underscore",
+ "backbone",
+ "rdflib",
+ "uuid",
+ "md5",
+ "collections/SolrResults",
+ "models/filters/Filter",
+ "models/DataONEObject",
+ "models/metadata/ScienceMetadata",
+ "models/metadata/eml211/EML211",
], function (
- $,
- _,
- Backbone,
- rdf,
- uuid,
- md5,
- SolrResults,
- Filter,
- DataONEObject,
- ScienceMetadata,
- EML211
+ $,
+ _,
+ Backbone,
+ rdf,
+ uuid,
+ md5,
+ SolrResults,
+ Filter,
+ DataONEObject,
+ ScienceMetadata,
+ EML211,
) {
- /**
+ /**
* @class DataPackage
* @classdesc A DataPackage represents a hierarchical collection of
packages, metadata, and data objects, modeling an OAI-ORE RDF graph.
@@ -36,1101 +36,1052 @@ define([
* @extends Backbone.Collection
* @constructor
*/
- var DataPackage = Backbone.Collection.extend(
- /** @lends DataPackage.prototype */ {
- /**
- * The name of this type of collection
- * @type {string}
- */
- type: "DataPackage",
-
- /**
- * The package identifier
- * @type {string}
- */
- id: null,
-
- /**
- * The type of the object (DataPackage, Metadata, Data)
- * Simple queue to enqueue file transfers. Use push() and shift()
- * to add and remove items. If this gets to large/slow, possibly
- * switch to http://code.stephenmorley.org/javascript/queues/
- * @type {DataPackage|Metadata|Data[]}
- */
- transferQueue: [],
-
- /** A flag ued for the package's edit status. Can be
- * set to false to 'lock' the package
- * @type {boolean}
- */
- editable: true,
-
- /**
- * The RDF graph representing this data package
- * @type {RDFGraph}
- */
- dataPackageGraph: null,
-
- /**
- * A DataONEObject representing the resource map itself
- * @type {DataONEObject}
- */
- packageModel: null,
-
- /** The science data identifiers associated with this
- * data package (from cito:documents), mapped to the science metadata
- * identifier that documents it
- * Not to be changed after initial fetch - this is to keep track of the relationships in their original state
- * @type {object}
- */
- originalIsDocBy: {},
-
- /** An array of ids that are aggregated in the resource map on the server.
- * Taken from the original RDF XML that was fetched from the server.
- * Used for comparing the original aggregation with the aggregation of this collection.
- * @type {string[]}
- */
- originalMembers: [],
-
- /**
- * Keep the collection sorted by model "sortOrder". The three model types are ordered as:
- * Metadata: 1
- * Data: 2
- * DataPackage: 3
- * See getMember(). We do this so that Metadata get rendered first, and Data are
- * rendered as DOM siblings of the Metadata rows of the DataPackage table.
- * @type {string}
- */
- comparator: "sortOrder",
-
- /**
- * The nesting level in a data package hierarchy
- * @type {number}
- */
- nodeLevel: 0,
-
- /**
- * The SolrResults collection associated with this DataPackage.
- * This can be used to fetch the package from Solr by passing the 'fromIndex' option
- * to fetch().
- * @type {SolrResults}
- */
- solrResults: new SolrResults(),
-
- /**
- * A Filter model that should filter the Solr index for only the
- * objects aggregated by this package.
- * @type {Filter}
- */
- filterModel: null,
-
- /** Define the namespaces used in the RDF XML
- * @type {object}
- */
- namespaces: {
- RDF: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
- FOAF: "http://xmlns.com/foaf/0.1/",
- OWL: "http://www.w3.org/2002/07/owl#",
- DC: "http://purl.org/dc/elements/1.1/",
- ORE: "http://www.openarchives.org/ore/terms/",
- DCTERMS: "http://purl.org/dc/terms/",
- CITO: "http://purl.org/spar/cito/",
- XSD: "http://www.w3.org/2001/XMLSchema#",
- PROV: "http://www.w3.org/ns/prov#",
- PROVONE: "http://purl.dataone.org/provone/2015/01/15/ontology#",
- },
+ var DataPackage = Backbone.Collection.extend(
+ /** @lends DataPackage.prototype */ {
+ /**
+ * The name of this type of collection
+ * @type {string}
+ */
+ type: "DataPackage",
- sources: [],
- derivations: [],
- provenanceFlag: null,
- sourcePackages: [],
- derivationPackages: [],
- relatedModels: [],
+ /**
+ * The package identifier
+ * @type {string}
+ */
+ id: null,
+
+ /**
+ * The type of the object (DataPackage, Metadata, Data)
+ * Simple queue to enqueue file transfers. Use push() and shift()
+ * to add and remove items. If this gets to large/slow, possibly
+ * switch to http://code.stephenmorley.org/javascript/queues/
+ * @type {DataPackage|Metadata|Data[]}
+ */
+ transferQueue: [],
- /**
- * Contains provenance relationships added or deleted to this DataONEObject.
- * Each entry is [operation ('add' or 'delete'), prov field name, object id], i.e. ['add', 'prov_used', 'urn:uuid:5678']
- */
- provEdits: [],
+ /** A flag ued for the package's edit status. Can be
+ * set to false to 'lock' the package
+ * @type {boolean}
+ */
+ editable: true,
- /**
- * The number of models that have been updated during the current save().
- * This is reset to zero after the current save() is complete.
- */
- numSaves: 0,
+ /**
+ * The RDF graph representing this data package
+ * @type {RDFGraph}
+ */
+ dataPackageGraph: null,
- // Constructor: Initialize a new DataPackage
- initialize: function (models, options) {
- if (typeof options == "undefined") var options = {};
+ /**
+ * A DataONEObject representing the resource map itself
+ * @type {DataONEObject}
+ */
+ packageModel: null,
- // Create an rdflib reference
- this.rdf = rdf;
+ /** The science data identifiers associated with this
+ * data package (from cito:documents), mapped to the science metadata
+ * identifier that documents it
+ * Not to be changed after initial fetch - this is to keep track of the relationships in their original state
+ * @type {object}
+ */
+ originalIsDocBy: {},
- // Create an initial RDF graph
- this.dataPackageGraph = this.rdf.graph();
+ /** An array of ids that are aggregated in the resource map on the server.
+ * Taken from the original RDF XML that was fetched from the server.
+ * Used for comparing the original aggregation with the aggregation of this collection.
+ * @type {string[]}
+ */
+ originalMembers: [],
+
+ /**
+ * Keep the collection sorted by model "sortOrder". The three model types are ordered as:
+ * Metadata: 1
+ * Data: 2
+ * DataPackage: 3
+ * See getMember(). We do this so that Metadata get rendered first, and Data are
+ * rendered as DOM siblings of the Metadata rows of the DataPackage table.
+ * @type {string}
+ */
+ comparator: "sortOrder",
- //Set the id or create a new one
- this.id = options.id || "resource_map_urn:uuid:" + uuid.v4();
+ /**
+ * The nesting level in a data package hierarchy
+ * @type {number}
+ */
+ nodeLevel: 0,
- let packageModelAttrs = options.packageModelAttrs || {};
+ /**
+ * The SolrResults collection associated with this DataPackage.
+ * This can be used to fetch the package from Solr by passing the 'fromIndex' option
+ * to fetch().
+ * @type {SolrResults}
+ */
+ solrResults: new SolrResults(),
- if (typeof options.packageModel !== "undefined") {
- // use the given package model
- this.packageModel = new DataONEObject(options.packageModel);
- } else {
- // Create a DataONEObject to represent this resource map
- this.packageModel = new DataONEObject(
- _.extend(packageModelAttrs, {
- formatType: "RESOURCE",
- type: "DataPackage",
- formatId: "http://www.openarchives.org/ore/terms",
- childPackages: {},
- id: this.id,
- latestVersion: this.id,
- })
- );
- }
+ /**
+ * A Filter model that should filter the Solr index for only the
+ * objects aggregated by this package.
+ * @type {Filter}
+ */
+ filterModel: null,
- this.id = this.packageModel.id;
+ /** Define the namespaces used in the RDF XML
+ * @type {object}
+ */
+ namespaces: {
+ RDF: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+ FOAF: "http://xmlns.com/foaf/0.1/",
+ OWL: "http://www.w3.org/2002/07/owl#",
+ DC: "http://purl.org/dc/elements/1.1/",
+ ORE: "http://www.openarchives.org/ore/terms/",
+ DCTERMS: "http://purl.org/dc/terms/",
+ CITO: "http://purl.org/spar/cito/",
+ XSD: "http://www.w3.org/2001/XMLSchema#",
+ PROV: "http://www.w3.org/ns/prov#",
+ PROVONE: "http://purl.dataone.org/provone/2015/01/15/ontology#",
+ },
+
+ sources: [],
+ derivations: [],
+ provenanceFlag: null,
+ sourcePackages: [],
+ derivationPackages: [],
+ relatedModels: [],
+
+ /**
+ * Contains provenance relationships added or deleted to this DataONEObject.
+ * Each entry is [operation ('add' or 'delete'), prov field name, object id], i.e. ['add', 'prov_used', 'urn:uuid:5678']
+ */
+ provEdits: [],
- //Create a Filter for this DataPackage using the id
- this.filterModel = new Filter({
- fields: ["resourceMap"],
- values: [this.id],
- matchSubstring: false,
- });
- //If the id is ever changed, update the id in the Filter
- this.listenTo(this.packageModel, "change:id", function () {
- this.filterModel.set("values", [
- this.packageModel.get("id"),
- ]);
- });
+ /**
+ * The number of models that have been updated during the current save().
+ * This is reset to zero after the current save() is complete.
+ */
+ numSaves: 0,
+
+ // Constructor: Initialize a new DataPackage
+ initialize: function (models, options) {
+ if (typeof options == "undefined") var options = {};
+
+ // Create an rdflib reference
+ this.rdf = rdf;
+
+ // Create an initial RDF graph
+ this.dataPackageGraph = this.rdf.graph();
+
+ //Set the id or create a new one
+ this.id = options.id || "resource_map_urn:uuid:" + uuid.v4();
+
+ let packageModelAttrs = options.packageModelAttrs || {};
+
+ if (typeof options.packageModel !== "undefined") {
+ // use the given package model
+ this.packageModel = new DataONEObject(options.packageModel);
+ } else {
+ // Create a DataONEObject to represent this resource map
+ this.packageModel = new DataONEObject(
+ _.extend(packageModelAttrs, {
+ formatType: "RESOURCE",
+ type: "DataPackage",
+ formatId: "http://www.openarchives.org/ore/terms",
+ childPackages: {},
+ id: this.id,
+ latestVersion: this.id,
+ }),
+ );
+ }
- this.on("add", this.handleAdd);
- this.on("add", this.triggerComplete);
- this.on("successSaving", this.updateRelationships);
+ this.id = this.packageModel.id;
+
+ //Create a Filter for this DataPackage using the id
+ this.filterModel = new Filter({
+ fields: ["resourceMap"],
+ values: [this.id],
+ matchSubstring: false,
+ });
+ //If the id is ever changed, update the id in the Filter
+ this.listenTo(this.packageModel, "change:id", function () {
+ this.filterModel.set("values", [this.packageModel.get("id")]);
+ });
+
+ this.on("add", this.handleAdd);
+ this.on("add", this.triggerComplete);
+ this.on("successSaving", this.updateRelationships);
+
+ return this;
+ },
+
+ // Build the DataPackage URL based on the MetacatUI.appModel.objectServiceUrl
+ // and id or seriesid
+ url: function (options) {
+ if (options && options.update) {
+ return (
+ MetacatUI.appModel.get("objectServiceUrl") +
+ (encodeURIComponent(this.packageModel.get("oldPid")) ||
+ encodeURIComponent(this.packageModel.get("seriesid")))
+ );
+ } else {
+ //URL encode the id or seriesId
+ var encodedId =
+ encodeURIComponent(this.packageModel.get("id")) ||
+ encodeURIComponent(this.packageModel.get("seriesid"));
+ //Use the object service URL if it is available (when pointing to a MN)
+ if (MetacatUI.appModel.get("objectServiceUrl")) {
+ return MetacatUI.appModel.get("objectServiceUrl") + encodedId;
+ }
+ //Otherwise, use the resolve service URL (when pointing to a CN)
+ else {
+ return MetacatUI.appModel.get("resolveServiceUrl") + encodedId;
+ }
+ }
+ },
- return this;
- },
+ /*
+ * The DataPackage collection stores DataPackages and
+ * DataONEObjects, including Metadata and Data objects.
+ * Return the correct model based on the type
+ */
+ model: function (attrs, options) {
+ switch (attrs.formatid) {
+ case "http://www.openarchives.org/ore/terms":
+ return new DataPackage(null, { packageModel: attrs }); // TODO: is this correct?
- // Build the DataPackage URL based on the MetacatUI.appModel.objectServiceUrl
- // and id or seriesid
- url: function (options) {
- if (options && options.update) {
- return (
- MetacatUI.appModel.get("objectServiceUrl") +
- (encodeURIComponent(this.packageModel.get("oldPid")) ||
- encodeURIComponent(
- this.packageModel.get("seriesid")
- ))
- );
- } else {
- //URL encode the id or seriesId
- var encodedId =
- encodeURIComponent(this.packageModel.get("id")) ||
- encodeURIComponent(this.packageModel.get("seriesid"));
- //Use the object service URL if it is available (when pointing to a MN)
- if (MetacatUI.appModel.get("objectServiceUrl")) {
- return (
- MetacatUI.appModel.get("objectServiceUrl") +
- encodedId
- );
- }
- //Otherwise, use the resolve service URL (when pointing to a CN)
- else {
- return (
- MetacatUI.appModel.get("resolveServiceUrl") +
- encodedId
- );
- }
- }
- },
+ case "eml://ecoinformatics.org/eml-2.0.0":
+ return new EML211(attrs, options);
- /*
- * The DataPackage collection stores DataPackages and
- * DataONEObjects, including Metadata and Data objects.
- * Return the correct model based on the type
- */
- model: function (attrs, options) {
- switch (attrs.formatid) {
- case "http://www.openarchives.org/ore/terms":
- return new DataPackage(null, { packageModel: attrs }); // TODO: is this correct?
+ case "eml://ecoinformatics.org/eml-2.0.1":
+ return new EML211(attrs, options);
- case "eml://ecoinformatics.org/eml-2.0.0":
- return new EML211(attrs, options);
+ case "eml://ecoinformatics.org/eml-2.1.0":
+ return new EML211(attrs, options);
- case "eml://ecoinformatics.org/eml-2.0.1":
- return new EML211(attrs, options);
+ case "eml://ecoinformatics.org/eml-2.1.1":
+ return new EML211(attrs, options);
- case "eml://ecoinformatics.org/eml-2.1.0":
- return new EML211(attrs, options);
+ case "eml://ecoinformatics.org/eml-2.1.1":
+ return new EML211(attrs, options);
- case "eml://ecoinformatics.org/eml-2.1.1":
- return new EML211(attrs, options);
+ case "-//ecoinformatics.org//eml-access-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "eml://ecoinformatics.org/eml-2.1.1":
- return new EML211(attrs, options);
+ case "-//ecoinformatics.org//eml-access-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-access-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-attribute-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-access-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-attribute-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-attribute-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-constraint-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-attribute-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-constraint-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-constraint-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-coverage-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-constraint-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-coverage-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-coverage-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-dataset-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-coverage-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-dataset-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-dataset-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-distribution-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-dataset-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-distribution-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-distribution-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-entity-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-distribution-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-entity-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-entity-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-literature-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-entity-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-literature-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-literature-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-party-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-literature-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-party-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-party-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-physical-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-party-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-physical-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-physical-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-project-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-physical-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-project-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-project-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-protocol-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-project-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-protocol-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-protocol-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-resource-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-protocol-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-resource-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-resource-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-software-2.0.0beta4//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-resource-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "-//ecoinformatics.org//eml-software-2.0.0beta6//EN":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-software-2.0.0beta4//EN":
- return new ScienceMetadata(attrs, options);
+ case "FGDC-STD-001-1998":
+ return new ScienceMetadata(attrs, options);
- case "-//ecoinformatics.org//eml-software-2.0.0beta6//EN":
- return new ScienceMetadata(attrs, options);
+ case "FGDC-STD-001.1-1999":
+ return new ScienceMetadata(attrs, options);
- case "FGDC-STD-001-1998":
- return new ScienceMetadata(attrs, options);
+ case "FGDC-STD-001.2-1999":
+ return new ScienceMetadata(attrs, options);
- case "FGDC-STD-001.1-1999":
- return new ScienceMetadata(attrs, options);
+ case "INCITS-453-2009":
+ return new ScienceMetadata(attrs, options);
- case "FGDC-STD-001.2-1999":
- return new ScienceMetadata(attrs, options);
+ case "ddi:codebook:2_5":
+ return new ScienceMetadata(attrs, options);
- case "INCITS-453-2009":
- return new ScienceMetadata(attrs, options);
+ case "http://datacite.org/schema/kernel-3.0":
+ return new ScienceMetadata(attrs, options);
- case "ddi:codebook:2_5":
- return new ScienceMetadata(attrs, options);
+ case "http://datacite.org/schema/kernel-3.1":
+ return new ScienceMetadata(attrs, options);
- case "http://datacite.org/schema/kernel-3.0":
- return new ScienceMetadata(attrs, options);
+ case "http://datadryad.org/profile/v3.1":
+ return new ScienceMetadata(attrs, options);
- case "http://datacite.org/schema/kernel-3.1":
- return new ScienceMetadata(attrs, options);
+ case "http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd":
+ return new ScienceMetadata(attrs, options);
- case "http://datadryad.org/profile/v3.1":
- return new ScienceMetadata(attrs, options);
+ case "http://ns.dataone.org/metadata/schema/onedcx/v1.0":
+ return new ScienceMetadata(attrs, options);
- case "http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd":
- return new ScienceMetadata(attrs, options);
+ case "http://purl.org/dryad/terms/":
+ return new ScienceMetadata(attrs, options);
- case "http://ns.dataone.org/metadata/schema/onedcx/v1.0":
- return new ScienceMetadata(attrs, options);
+ case "http://purl.org/ornl/schema/mercury/terms/v1.0":
+ return new ScienceMetadata(attrs, options);
- case "http://purl.org/dryad/terms/":
- return new ScienceMetadata(attrs, options);
+ case "http://rs.tdwg.org/dwc/xsd/simpledarwincore/":
+ return new ScienceMetadata(attrs, options);
- case "http://purl.org/ornl/schema/mercury/terms/v1.0":
- return new ScienceMetadata(attrs, options);
+ case "http://www.cuahsi.org/waterML/1.0/":
+ return new ScienceMetadata(attrs, options);
- case "http://rs.tdwg.org/dwc/xsd/simpledarwincore/":
- return new ScienceMetadata(attrs, options);
+ case "http://www.cuahsi.org/waterML/1.1/":
+ return new ScienceMetadata(attrs, options);
- case "http://www.cuahsi.org/waterML/1.0/":
- return new ScienceMetadata(attrs, options);
+ case "http://www.esri.com/metadata/esriprof80.dtd":
+ return new ScienceMetadata(attrs, options);
- case "http://www.cuahsi.org/waterML/1.1/":
- return new ScienceMetadata(attrs, options);
+ case "http://www.icpsr.umich.edu/DDI":
+ return new ScienceMetadata(attrs, options);
- case "http://www.esri.com/metadata/esriprof80.dtd":
- return new ScienceMetadata(attrs, options);
+ case "http://www.isotc211.org/2005/gmd":
+ return new ScienceMetadata(attrs, options);
- case "http://www.icpsr.umich.edu/DDI":
- return new ScienceMetadata(attrs, options);
+ case "http://www.isotc211.org/2005/gmd-noaa":
+ return new ScienceMetadata(attrs, options);
- case "http://www.isotc211.org/2005/gmd":
- return new ScienceMetadata(attrs, options);
+ case "http://www.loc.gov/METS/":
+ return new ScienceMetadata(attrs, options);
- case "http://www.isotc211.org/2005/gmd-noaa":
- return new ScienceMetadata(attrs, options);
+ case "http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2":
+ return new ScienceMetadata(attrs, options);
- case "http://www.loc.gov/METS/":
- return new ScienceMetadata(attrs, options);
+ default:
+ return new DataONEObject(attrs, options);
+ }
+ },
+
+ /**
+ * Overload fetch calls for a DataPackage
+ *
+ * @param {Object} [options] - Optional options for this fetch that get sent with the XHR request
+ * @property {boolean} fetchModels - If false, this fetch will not fetch
+ * each model in the collection. It will only get the resource map object.
+ * @property {boolean} fromIndex - If true, the collection will be fetched from Solr rather than
+ * fetching the system metadata of each model. Useful when you only need to retrieve limited information about
+ * each package member. Set query-specific parameters on the `solrResults` SolrResults set on this collection.
+ */
+ fetch: function (options) {
+ // Fetch the system metadata for this resource map
+ this.packageModel.fetch();
+
+ if (typeof options == "object") {
+ // If the fetchModels property is set to false,
+ if (options.fetchModels === false) {
+ // Save the property to the Collection itself so it is accessible in other functions
+ this.fetchModels = false;
+ // Remove the property from the options Object since we don't want to send it with the XHR
+ delete options.fetchModels;
+ this.once("reset", this.triggerComplete);
+ }
+ // If the fetchFromIndex property is set to true
+ else if (options.fromIndex) {
+ this.fetchFromIndex();
+ return;
+ }
+ }
- case "http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2":
- return new ScienceMetadata(attrs, options);
+ // Set some custom fetch options
+ var fetchOptions = _.extend({ dataType: "text" }, options);
+
+ var thisPackage = this;
+
+ // Function to retry fetching with user login details if the initial fetch fails
+ var retryFetch = function () {
+ // Add the authorization options
+ var authFetchOptions = _.extend(
+ fetchOptions,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
+
+ // Fetch the resource map RDF XML with user login details
+ return Backbone.Collection.prototype.fetch
+ .call(thisPackage, authFetchOptions)
+ .fail(function () {
+ // trigger failure()
+ console.log("Fetch failed");
+
+ thisPackage.trigger("fetchFailed", thisPackage);
+ });
+ };
+
+ // Fetch the resource map RDF XML
+ return Backbone.Collection.prototype.fetch
+ .call(this, fetchOptions)
+ .fail(function () {
+ // If the initial fetch fails, retry with user login details
+ return retryFetch();
+ });
+ },
+
+ /*
+ * Deserialize a Package from OAI-ORE RDF XML
+ */
+ parse: function (response, options) {
+ //Save the raw XML in case it needs to be used later
+ this.objectXML = response;
+
+ var RDF = this.rdf.Namespace(this.namespaces.RDF),
+ FOAF = this.rdf.Namespace(this.namespaces.FOAF),
+ OWL = this.rdf.Namespace(this.namespaces.OWL),
+ DC = this.rdf.Namespace(this.namespaces.DC),
+ ORE = this.rdf.Namespace(this.namespaces.ORE),
+ DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS),
+ CITO = this.rdf.Namespace(this.namespaces.CITO),
+ PROV = this.rdf.Namespace(this.namespaces.PROV),
+ XSD = this.rdf.Namespace(this.namespaces.XSD);
+
+ var memberStatements = [],
+ atLocationStatements = [], // array to store atLocation statements
+ memberURIParts,
+ memberPIDStr,
+ memberPID,
+ memberPIDs = [],
+ memberModel,
+ documentsStatements,
+ objectParts,
+ objectPIDStr,
+ objectPID,
+ objectAtLocationValue,
+ scimetaID, // documentor
+ scidataID, // documentee
+ models = []; // the models returned by parse()
+
+ try {
+ //First, make sure we are only using one CN Base URL in the RDF or the RDF parsing will fail.
+ var cnResolveUrl = MetacatUI.appModel.get("resolveServiceUrl");
+
+ var cnURLs = _.uniq(
+ response.match(
+ /cn\S+\.test\.dataone\.org\/cn\/v\d\/resolve|cn\.dataone\.org\/cn\/v\d\/resolve/g,
+ ),
+ );
+ if (cnURLs.length > 1) {
+ response = response.replace(
+ /cn\S+\.test\.dataone\.org\/cn\/v\d\/resolve|cn\.dataone\.org\/cn\/v\d\/resolve/g,
+ cnResolveUrl.substring(cnResolveUrl.indexOf("https://") + 8),
+ );
+ }
+
+ this.rdf.parse(
+ response,
+ this.dataPackageGraph,
+ this.url(),
+ "application/rdf+xml",
+ );
+
+ // List the package members
+ memberStatements = this.dataPackageGraph.statementsMatching(
+ undefined,
+ ORE("aggregates"),
+ undefined,
+ undefined,
+ );
+
+ // Get system metadata for each member to eval the formatId
+ _.each(
+ memberStatements,
+ function (memberStatement) {
+ memberURIParts = memberStatement.object.value.split("/");
+ memberPIDStr = _.last(memberURIParts);
+ memberPID = decodeURIComponent(memberPIDStr);
+
+ if (memberPID) memberPIDs.push(memberPID);
+
+ //TODO: Test passing merge:true when adding a model and this if statement may not be necessary
+ //Create a DataONEObject model to represent this collection member and add to the collection
+ if (!_.contains(this.pluck("id"), memberPID)) {
+ memberModel = new DataONEObject({
+ id: memberPID,
+ resourceMap: [this.packageModel.get("id")],
+ collections: [this],
+ });
- default:
- return new DataONEObject(attrs, options);
- }
- },
+ models.push(memberModel);
+ }
+ //If the model already exists, add this resource map ID to it's list of resource maps
+ else {
+ memberModel = this.get(memberPID);
+ models.push(memberModel);
- /**
- * Overload fetch calls for a DataPackage
- *
- * @param {Object} [options] - Optional options for this fetch that get sent with the XHR request
- * @property {boolean} fetchModels - If false, this fetch will not fetch
- * each model in the collection. It will only get the resource map object.
- * @property {boolean} fromIndex - If true, the collection will be fetched from Solr rather than
- * fetching the system metadata of each model. Useful when you only need to retrieve limited information about
- * each package member. Set query-specific parameters on the `solrResults` SolrResults set on this collection.
- */
- fetch: function(options) {
- // Fetch the system metadata for this resource map
- this.packageModel.fetch();
-
- if(typeof options == "object") {
- // If the fetchModels property is set to false,
- if(options.fetchModels === false) {
- // Save the property to the Collection itself so it is accessible in other functions
- this.fetchModels = false;
- // Remove the property from the options Object since we don't want to send it with the XHR
- delete options.fetchModels;
- this.once("reset", this.triggerComplete);
- }
- // If the fetchFromIndex property is set to true
- else if(options.fromIndex) {
- this.fetchFromIndex();
- return;
- }
- }
-
- // Set some custom fetch options
- var fetchOptions = _.extend({ dataType: "text" }, options);
-
- var thisPackage = this;
-
- // Function to retry fetching with user login details if the initial fetch fails
- var retryFetch = function() {
- // Add the authorization options
- var authFetchOptions = _.extend(fetchOptions, MetacatUI.appUserModel.createAjaxSettings());
-
- // Fetch the resource map RDF XML with user login details
- return Backbone.Collection.prototype.fetch.call(thisPackage, authFetchOptions)
- .fail(function() {
- // trigger failure()
- console.log("Fetch failed");
-
- thisPackage.trigger("fetchFailed", thisPackage);
- });
- };
-
- // Fetch the resource map RDF XML
- return Backbone.Collection.prototype.fetch.call(this, fetchOptions)
- .fail(function() {
- // If the initial fetch fails, retry with user login details
- return retryFetch();
- });
+ var rMaps = memberModel.get("resourceMap");
+ if (
+ rMaps &&
+ Array.isArray(rMaps) &&
+ !_.contains(rMaps, this.packageModel.get("id"))
+ )
+ rMaps.push(this.packageModel.get("id"));
+ else if (rMaps && !Array.isArray(rMaps))
+ rMaps = [rMaps, this.packageModel.get("id")];
+ else rMaps = [this.packageModel.get("id")];
+ }
},
+ this,
+ );
+
+ //Save the list of original ids
+ this.originalMembers = memberPIDs;
+
+ // Get the isDocumentedBy relationships
+ documentsStatements = this.dataPackageGraph.statementsMatching(
+ undefined,
+ CITO("documents"),
+ undefined,
+ undefined,
+ );
+
+ var sciMetaPids = [];
+
+ _.each(
+ documentsStatements,
+ function (documentsStatement) {
+ // Extract and URI-decode the metadata pid
+ scimetaID = decodeURIComponent(
+ _.last(documentsStatement.subject.value.split("/")),
+ );
+
+ sciMetaPids.push(scimetaID);
+
+ // Extract and URI-decode the data pid
+ scidataID = decodeURIComponent(
+ _.last(documentsStatement.object.value.split("/")),
+ );
+
+ // Store the isDocumentedBy relationship
+ if (typeof this.originalIsDocBy[scidataID] == "undefined")
+ this.originalIsDocBy[scidataID] = [scimetaID];
+ else if (
+ Array.isArray(this.originalIsDocBy[scidataID]) &&
+ !_.contains(this.originalIsDocBy[scidataID], scimetaID)
+ )
+ this.originalIsDocBy[scidataID].push(scimetaID);
+ else
+ this.originalIsDocBy[scidataID] = _.uniq([
+ this.originalIsDocBy[scidataID],
+ scimetaID,
+ ]);
+
+ //Find the model in this collection for this data object
+ //var dataObj = this.get(scidataID);
+ var dataObj = _.find(models, function (m) {
+ return m.get("id") == scidataID;
+ });
+
+ if (dataObj) {
+ //Get the isDocumentedBy field
+ var isDocBy = dataObj.get("isDocumentedBy");
+ if (
+ isDocBy &&
+ Array.isArray(isDocBy) &&
+ !_.contains(isDocBy, scimetaID)
+ )
+ isDocBy.push(scimetaID);
+ else if (isDocBy && !Array.isArray(isDocBy))
+ isDocBy = [isDocBy, scimetaID];
+ else isDocBy = [scimetaID];
- /*
- * Deserialize a Package from OAI-ORE RDF XML
- */
- parse: function (response, options) {
- //Save the raw XML in case it needs to be used later
- this.objectXML = response;
-
- var RDF = this.rdf.Namespace(this.namespaces.RDF),
- FOAF = this.rdf.Namespace(this.namespaces.FOAF),
- OWL = this.rdf.Namespace(this.namespaces.OWL),
- DC = this.rdf.Namespace(this.namespaces.DC),
- ORE = this.rdf.Namespace(this.namespaces.ORE),
- DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS),
- CITO = this.rdf.Namespace(this.namespaces.CITO),
- PROV = this.rdf.Namespace(this.namespaces.PROV),
- XSD = this.rdf.Namespace(this.namespaces.XSD);
-
- var memberStatements = [],
- atLocationStatements = [], // array to store atLocation statements
- memberURIParts,
- memberPIDStr,
- memberPID,
- memberPIDs = [],
- memberModel,
- documentsStatements,
- objectParts,
- objectPIDStr,
- objectPID,
- objectAtLocationValue,
- scimetaID, // documentor
- scidataID, // documentee
- models = []; // the models returned by parse()
-
- try {
- //First, make sure we are only using one CN Base URL in the RDF or the RDF parsing will fail.
- var cnResolveUrl =
- MetacatUI.appModel.get("resolveServiceUrl");
-
- var cnURLs = _.uniq(
- response.match(
- /cn\S+\.test\.dataone\.org\/cn\/v\d\/resolve|cn\.dataone\.org\/cn\/v\d\/resolve/g
- )
- );
- if (cnURLs.length > 1) {
- response = response.replace(
- /cn\S+\.test\.dataone\.org\/cn\/v\d\/resolve|cn\.dataone\.org\/cn\/v\d\/resolve/g,
- cnResolveUrl.substring(
- cnResolveUrl.indexOf("https://") + 8
- )
- );
- }
-
- this.rdf.parse(
- response,
- this.dataPackageGraph,
- this.url(),
- "application/rdf+xml"
- );
-
- // List the package members
- memberStatements = this.dataPackageGraph.statementsMatching(
- undefined,
- ORE("aggregates"),
- undefined,
- undefined
- );
+ //Set the isDocumentedBy field
+ dataObj.set("isDocumentedBy", isDocBy);
+ }
+ },
+ this,
+ );
+
+ //Save the list of science metadata pids
+ this.sciMetaPids = sciMetaPids;
+
+ // Parse atLocation
+ var atLocationObject = {};
+ atLocationStatements = this.dataPackageGraph.statementsMatching(
+ undefined,
+ PROV("atLocation"),
+ undefined,
+ undefined,
+ );
+
+ const ref = this;
+
+ // Get atLocation information for each statement in the resourceMap
+ _.each(
+ atLocationStatements,
+ function (atLocationStatement) {
+ objectParts = atLocationStatement.subject.value.split("/");
+ objectPIDStr = _.last(objectParts);
+ objectPID = decodeURIComponent(objectPIDStr);
+ objectAtLocationValue = atLocationStatement.object.value;
+
+ atLocationObject[objectPID] = ref.getAbsolutePath(
+ objectAtLocationValue,
+ );
+ },
+ this,
+ );
+
+ this.atLocationObject = atLocationObject;
+
+ //Put the science metadata pids first
+ memberPIDs = _.difference(memberPIDs, sciMetaPids);
+ _.each(_.uniq(sciMetaPids), function (id) {
+ memberPIDs.unshift(id);
+ });
+
+ //Don't fetch each member model if the fetchModels property on this Collection is set to false
+ if (this.fetchModels !== false) {
+ //Add the models to the collection now, silently
+ //this.add(models, {silent: true});
+
+ //Retrieve the model for each member
+ _.each(
+ models,
+ function (memberModel) {
+ var collection = this;
- // Get system metadata for each member to eval the formatId
- _.each(
- memberStatements,
- function (memberStatement) {
- memberURIParts =
- memberStatement.object.value.split("/");
- memberPIDStr = _.last(memberURIParts);
- memberPID = decodeURIComponent(memberPIDStr);
-
- if (memberPID) memberPIDs.push(memberPID);
-
- //TODO: Test passing merge:true when adding a model and this if statement may not be necessary
- //Create a DataONEObject model to represent this collection member and add to the collection
- if (!_.contains(this.pluck("id"), memberPID)) {
- memberModel = new DataONEObject({
- id: memberPID,
- resourceMap: [this.packageModel.get("id")],
- collections: [this],
- });
-
- models.push(memberModel);
- }
- //If the model already exists, add this resource map ID to it's list of resource maps
- else {
- memberModel = this.get(memberPID);
- models.push(memberModel);
-
- var rMaps = memberModel.get("resourceMap");
- if (
- rMaps &&
- Array.isArray(rMaps) &&
- !_.contains(
- rMaps,
- this.packageModel.get("id")
- )
- )
- rMaps.push(this.packageModel.get("id"));
- else if (rMaps && !Array.isArray(rMaps))
- rMaps = [
- rMaps,
- this.packageModel.get("id"),
- ];
- else rMaps = [this.packageModel.get("id")];
- }
- },
- this
- );
+ memberModel.fetch();
+ memberModel.once("sync", function (oldModel) {
+ //Get the right model type based on the model values
+ var newModel = collection.getMember(oldModel);
+
+ //If the model type has changed, then mark the model as unsynced, since there may be custom fetch() options for the new model
+ if (oldModel.type != newModel.type) {
+ // DataPackages shouldn't be fetched until we support nested packages better in the UI
+ if (newModel.type == "DataPackage") {
+ //Trigger a replace event so other parts of the app know when a model has been replaced with a different type
+ oldModel.trigger("replace", newModel);
+ } else {
+ newModel.set("synced", false);
- //Save the list of original ids
- this.originalMembers = memberPIDs;
-
- // Get the isDocumentedBy relationships
- documentsStatements =
- this.dataPackageGraph.statementsMatching(
- undefined,
- CITO("documents"),
- undefined,
- undefined
- );
-
- var sciMetaPids = [];
-
- _.each(
- documentsStatements,
- function (documentsStatement) {
- // Extract and URI-decode the metadata pid
- scimetaID = decodeURIComponent(
- _.last(
- documentsStatement.subject.value.split("/")
- )
- );
-
- sciMetaPids.push(scimetaID);
-
- // Extract and URI-decode the data pid
- scidataID = decodeURIComponent(
- _.last(
- documentsStatement.object.value.split("/")
- )
- );
-
- // Store the isDocumentedBy relationship
- if (
- typeof this.originalIsDocBy[scidataID] ==
- "undefined"
- )
- this.originalIsDocBy[scidataID] = [scimetaID];
- else if (
- Array.isArray(
- this.originalIsDocBy[scidataID]
- ) &&
- !_.contains(
- this.originalIsDocBy[scidataID],
- scimetaID
- )
- )
- this.originalIsDocBy[scidataID].push(scimetaID);
- else
- this.originalIsDocBy[scidataID] = _.uniq([
- this.originalIsDocBy[scidataID],
- scimetaID,
- ]);
-
- //Find the model in this collection for this data object
- //var dataObj = this.get(scidataID);
- var dataObj = _.find(models, function (m) {
- return m.get("id") == scidataID;
- });
-
- if (dataObj) {
- //Get the isDocumentedBy field
- var isDocBy = dataObj.get("isDocumentedBy");
- if (
- isDocBy &&
- Array.isArray(isDocBy) &&
- !_.contains(isDocBy, scimetaID)
- )
- isDocBy.push(scimetaID);
- else if (isDocBy && !Array.isArray(isDocBy))
- isDocBy = [isDocBy, scimetaID];
- else isDocBy = [scimetaID];
-
- //Set the isDocumentedBy field
- dataObj.set("isDocumentedBy", isDocBy);
- }
- },
- this
- );
+ newModel.fetch();
+ newModel.once("sync", function (fetchedModel) {
+ fetchedModel.set("synced", true);
- //Save the list of science metadata pids
- this.sciMetaPids = sciMetaPids;
-
- // Parse atLocation
- var atLocationObject = {};
- atLocationStatements =
- this.dataPackageGraph.statementsMatching(
- undefined,
- PROV("atLocation"),
- undefined,
- undefined
- );
-
- const ref = this;
-
- // Get atLocation information for each statement in the resourceMap
- _.each(
- atLocationStatements,
- function (atLocationStatement) {
- objectParts =
- atLocationStatement.subject.value.split("/");
- objectPIDStr = _.last(objectParts);
- objectPID = decodeURIComponent(objectPIDStr);
- objectAtLocationValue =
- atLocationStatement.object.value;
-
- atLocationObject[objectPID] = ref.getAbsolutePath(
- objectAtLocationValue
- );
- },
- this
- );
+ //Remove the model from the collection and add it back
+ collection.remove(oldModel);
+ collection.add(fetchedModel);
- this.atLocationObject = atLocationObject;
+ //Trigger a replace event so other parts of the app know when a model has been replaced with a different type
+ oldModel.trigger("replace", newModel);
- //Put the science metadata pids first
- memberPIDs = _.difference(memberPIDs, sciMetaPids);
- _.each(_.uniq(sciMetaPids), function (id) {
- memberPIDs.unshift(id);
+ if (newModel.type == "EML")
+ collection.trigger("add:EML");
+ });
+ }
+ } else {
+ newModel.set("synced", true);
+ collection.add(newModel, {
+ merge: true,
});
- //Don't fetch each member model if the fetchModels property on this Collection is set to false
- if (this.fetchModels !== false) {
- //Add the models to the collection now, silently
- //this.add(models, {silent: true});
-
- //Retrieve the model for each member
- _.each(
- models,
- function (memberModel) {
- var collection = this;
-
- memberModel.fetch();
- memberModel.once("sync", function (oldModel) {
- //Get the right model type based on the model values
- var newModel =
- collection.getMember(oldModel);
-
- //If the model type has changed, then mark the model as unsynced, since there may be custom fetch() options for the new model
- if (oldModel.type != newModel.type) {
- // DataPackages shouldn't be fetched until we support nested packages better in the UI
- if (newModel.type == "DataPackage") {
- //Trigger a replace event so other parts of the app know when a model has been replaced with a different type
- oldModel.trigger(
- "replace",
- newModel
- );
- } else {
- newModel.set("synced", false);
-
- newModel.fetch();
- newModel.once(
- "sync",
- function (fetchedModel) {
- fetchedModel.set(
- "synced",
- true
- );
-
- //Remove the model from the collection and add it back
- collection.remove(oldModel);
- collection.add(
- fetchedModel
- );
-
- //Trigger a replace event so other parts of the app know when a model has been replaced with a different type
- oldModel.trigger(
- "replace",
- newModel
- );
-
- if (newModel.type == "EML")
- collection.trigger(
- "add:EML"
- );
- }
- );
- }
- } else {
- newModel.set("synced", true);
- collection.add(newModel, {
- merge: true,
- });
-
- if (newModel.type == "EML")
- collection.trigger("add:EML");
- }
- });
- },
- this
- );
- }
- } catch (error) {
- console.log(error);
- }
+ if (newModel.type == "EML") collection.trigger("add:EML");
+ }
+ });
+ },
+ this,
+ );
+ }
+ } catch (error) {
+ console.log(error);
+ }
- // trigger complete if fetchModel is false and this is the only object in the package
- if (this.fetchModels == false && models.length == 1)
- this.triggerComplete();
+ // trigger complete if fetchModel is false and this is the only object in the package
+ if (this.fetchModels == false && models.length == 1)
+ this.triggerComplete();
- return models;
- },
+ return models;
+ },
- /* Parse the provenance relationships from the RDF graph, after all DataPackage members
+ /* Parse the provenance relationships from the RDF graph, after all DataPackage members
have been fetched, as the prov info will be stored in them.
*/
- parseProv: function () {
- try {
- /* Now run the SPARQL queries for the provenance relationships */
- var provQueries = [];
- /* result: pidValue, wasDerivedFromValue (prov_wasDerivedFrom) */
- provQueries["prov_wasDerivedFrom"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_wasDerivedFrom \n" +
- "WHERE { \n" +
- "?derived_data prov:wasDerivedFrom ?primary_data . \n" +
- "?derived_data dcterms:identifier ?pid . \n" +
- "?primary_data dcterms:identifier ?prov_wasDerivedFrom . \n" +
- "} \n" +
- "]]>";
-
- /* result: pidValue, generatedValue (prov_generated) */
- provQueries["prov_generated"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_generated \n" +
- "WHERE { \n" +
- "?result prov:wasGeneratedBy ?activity . \n" +
- "?activity prov:qualifiedAssociation ?association . \n" +
- "?association prov:hadPlan ?program . \n" +
- "?result dcterms:identifier ?prov_generated . \n" +
- "?program dcterms:identifier ?pid . \n" +
- "} \n" +
- "]]>";
-
- /* result: pidValue, wasInformedByValue (prov_wasInformedBy) */
- provQueries["prov_wasInformedBy"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_wasInformedBy \n" +
- "WHERE { \n" +
- "?activity prov:wasInformedBy ?previousActivity . \n" +
- "?activity dcterms:identifier ?pid . \n" +
- "?previousActivity dcterms:identifier ?prov_wasInformedBy . \n" +
- "} \n" +
- "]]> \n";
-
- /* result: pidValue, usedValue (prov_used) */
- provQueries["prov_used"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_used \n" +
- "WHERE { \n" +
- "?activity prov:used ?data . \n" +
- "?activity prov:qualifiedAssociation ?association . \n" +
- "?association prov:hadPlan ?program . \n" +
- "?program dcterms:identifier ?pid . \n" +
- "?data dcterms:identifier ?prov_used . \n" +
- "} \n" +
- "]]> \n";
-
- /* result: pidValue, programPidValue (prov_generatesByProgram) */
- provQueries["prov_generatedByProgram"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_generatedByProgram \n" +
- "WHERE { \n" +
- "?derived_data prov:wasGeneratedBy ?execution . \n" +
- "?execution prov:qualifiedAssociation ?association . \n" +
- "?association prov:hadPlan ?program . \n" +
- "?program dcterms:identifier ?prov_generatedByProgram . \n" +
- "?derived_data dcterms:identifier ?pid . \n" +
- "} \n" +
- "]]> \n";
-
- /* result: pidValue, executionPidValue */
- provQueries["prov_generatedByExecution"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_generatedByExecution \n" +
- "WHERE { \n" +
- "?derived_data prov:wasGeneratedBy ?execution . \n" +
- "?execution dcterms:identifier ?prov_generatedByExecution . \n" +
- "?derived_data dcterms:identifier ?pid . \n" +
- "} \n" +
- "]]> \n";
-
- /* result: pidValue, pid (prov_generatedByProgram) */
- provQueries["prov_generatedByUser"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_generatedByUser \n" +
- "WHERE { \n" +
- "?derived_data prov:wasGeneratedBy ?execution . \n" +
- "?execution prov:qualifiedAssociation ?association . \n" +
- "?association prov:agent ?prov_generatedByUser . \n" +
- "?derived_data dcterms:identifier ?pid . \n" +
- "} \n" +
- "]]> \n";
-
- /* results: pidValue, programPidValue (prov_usedByProgram) */
- provQueries["prov_usedByProgram"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_usedByProgram \n" +
- "WHERE { \n" +
- "?execution prov:used ?primary_data . \n" +
- "?execution prov:qualifiedAssociation ?association . \n" +
- "?association prov:hadPlan ?program . \n" +
- "?program dcterms:identifier ?prov_usedByProgram . \n" +
- "?primary_data dcterms:identifier ?pid . \n" +
- "} \n" +
- "]]> \n";
-
- /* results: pidValue, executionIdValue (prov_usedByExecution) */
- provQueries["prov_usedByExecution"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_usedByExecution \n" +
- "WHERE { \n" +
- "?execution prov:used ?primary_data . \n" +
- "?primary_data dcterms:identifier ?pid . \n" +
- "?execution dcterms:identifier ?prov_usedByExecution . \n" +
- "} \n" +
- "]]> \n";
-
- /* results: pidValue, pid (prov_usedByUser) */
- provQueries["prov_usedByUser"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_usedByUser \n" +
- "WHERE { \n" +
- "?execution prov:used ?primary_data . \n" +
- "?execution prov:qualifiedAssociation ?association . \n" +
- "?association prov:agent ?prov_usedByUser . \n" +
- "?primary_data dcterms:identifier ?pid . \n" +
- "} \n" +
- "]]> \n";
- /* results: pidValue, executionIdValue (prov_wasExecutedByExecution) */
- provQueries["prov_wasExecutedByExecution"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_wasExecutedByExecution \n" +
- "WHERE { \n" +
- "?execution prov:qualifiedAssociation ?association . \n" +
- "?association prov:hadPlan ?program . \n" +
- "?execution dcterms:identifier ?prov_wasExecutedByExecution . \n" +
- "?program dcterms:identifier ?pid . \n" +
- "} \n" +
- "]]> \n";
-
- /* results: pidValue, pid (prov_wasExecutedByUser) */
- provQueries["prov_wasExecutedByUser"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_wasExecutedByUser \n" +
- "WHERE { \n" +
- "?execution prov:qualifiedAssociation ?association . \n" +
- "?association prov:hadPlan ?program . \n" +
- "?association prov:agent ?prov_wasExecutedByUser . \n" +
- "?program dcterms:identifier ?pid . \n" +
- "} \n" +
- "]]> \n";
-
- /* results: pidValue, derivedDataPidValue (prov_hasDerivations) */
- provQueries["prov_hasDerivations"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "PREFIX cito: \n" +
- "SELECT ?pid ?prov_hasDerivations \n" +
- "WHERE { \n" +
- "?derived_data prov:wasDerivedFrom ?source_data . \n" +
- "?source_data dcterms:identifier ?pid . \n" +
- "?derived_data dcterms:identifier ?prov_hasDerivations . \n" +
- "} \n" +
- "]]> \n";
-
- /* results: pidValue, pid (prov_instanceOfClass) */
- provQueries["prov_instanceOfClass"] =
- " \n" +
- "PREFIX rdfs: \n" +
- "PREFIX owl: \n" +
- "PREFIX prov: \n" +
- "PREFIX provone: \n" +
- "PREFIX ore: \n" +
- "PREFIX dcterms: \n" +
- "SELECT ?pid ?prov_instanceOfClass \n" +
- "WHERE { \n" +
- "?subject rdf:type ?prov_instanceOfClass . \n" +
- "?subject dcterms:identifier ?pid . \n" +
- "} \n" +
- "]]> \n";
-
- // These are the provenance fields that are currently searched for in the provenance queries, but
- // not all of these fields are displayed by any view.
- // Note: this list is different than the prov list returned by MetacatUI.appSearchModel.getProvFields()
- this.provFields = [
- "prov_wasDerivedFrom",
- "prov_generated",
- "prov_wasInformedBy",
- "prov_used",
- "prov_generatedByProgram",
- "prov_generatedByExecution",
- "prov_generatedByUser",
- "prov_usedByProgram",
- "prov_usedByExecution",
- "prov_usedByUser",
- "prov_wasExecutedByExecution",
- "prov_wasExecutedByUser",
- "prov_hasDerivations",
- "prov_instanceOfClass",
- ];
-
- // Process each SPARQL query
- var keys = Object.keys(provQueries);
- this.queriesToRun = keys.length;
-
- //Bind the onResult and onDone functions to the model so they can be called out of context
- this.onResult = _.bind(this.onResult, this);
- this.onDone = _.bind(this.onDone, this);
-
- /* Run queries for all provenance fields.
+ parseProv: function () {
+ try {
+ /* Now run the SPARQL queries for the provenance relationships */
+ var provQueries = [];
+ /* result: pidValue, wasDerivedFromValue (prov_wasDerivedFrom) */
+ provQueries["prov_wasDerivedFrom"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_wasDerivedFrom \n" +
+ "WHERE { \n" +
+ "?derived_data prov:wasDerivedFrom ?primary_data . \n" +
+ "?derived_data dcterms:identifier ?pid . \n" +
+ "?primary_data dcterms:identifier ?prov_wasDerivedFrom . \n" +
+ "} \n" +
+ "]]>";
+
+ /* result: pidValue, generatedValue (prov_generated) */
+ provQueries["prov_generated"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_generated \n" +
+ "WHERE { \n" +
+ "?result prov:wasGeneratedBy ?activity . \n" +
+ "?activity prov:qualifiedAssociation ?association . \n" +
+ "?association prov:hadPlan ?program . \n" +
+ "?result dcterms:identifier ?prov_generated . \n" +
+ "?program dcterms:identifier ?pid . \n" +
+ "} \n" +
+ "]]>";
+
+ /* result: pidValue, wasInformedByValue (prov_wasInformedBy) */
+ provQueries["prov_wasInformedBy"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_wasInformedBy \n" +
+ "WHERE { \n" +
+ "?activity prov:wasInformedBy ?previousActivity . \n" +
+ "?activity dcterms:identifier ?pid . \n" +
+ "?previousActivity dcterms:identifier ?prov_wasInformedBy . \n" +
+ "} \n" +
+ "]]> \n";
+
+ /* result: pidValue, usedValue (prov_used) */
+ provQueries["prov_used"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_used \n" +
+ "WHERE { \n" +
+ "?activity prov:used ?data . \n" +
+ "?activity prov:qualifiedAssociation ?association . \n" +
+ "?association prov:hadPlan ?program . \n" +
+ "?program dcterms:identifier ?pid . \n" +
+ "?data dcterms:identifier ?prov_used . \n" +
+ "} \n" +
+ "]]> \n";
+
+ /* result: pidValue, programPidValue (prov_generatesByProgram) */
+ provQueries["prov_generatedByProgram"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_generatedByProgram \n" +
+ "WHERE { \n" +
+ "?derived_data prov:wasGeneratedBy ?execution . \n" +
+ "?execution prov:qualifiedAssociation ?association . \n" +
+ "?association prov:hadPlan ?program . \n" +
+ "?program dcterms:identifier ?prov_generatedByProgram . \n" +
+ "?derived_data dcterms:identifier ?pid . \n" +
+ "} \n" +
+ "]]> \n";
+
+ /* result: pidValue, executionPidValue */
+ provQueries["prov_generatedByExecution"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_generatedByExecution \n" +
+ "WHERE { \n" +
+ "?derived_data prov:wasGeneratedBy ?execution . \n" +
+ "?execution dcterms:identifier ?prov_generatedByExecution . \n" +
+ "?derived_data dcterms:identifier ?pid . \n" +
+ "} \n" +
+ "]]> \n";
+
+ /* result: pidValue, pid (prov_generatedByProgram) */
+ provQueries["prov_generatedByUser"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_generatedByUser \n" +
+ "WHERE { \n" +
+ "?derived_data prov:wasGeneratedBy ?execution . \n" +
+ "?execution prov:qualifiedAssociation ?association . \n" +
+ "?association prov:agent ?prov_generatedByUser . \n" +
+ "?derived_data dcterms:identifier ?pid . \n" +
+ "} \n" +
+ "]]> \n";
+
+ /* results: pidValue, programPidValue (prov_usedByProgram) */
+ provQueries["prov_usedByProgram"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_usedByProgram \n" +
+ "WHERE { \n" +
+ "?execution prov:used ?primary_data . \n" +
+ "?execution prov:qualifiedAssociation ?association . \n" +
+ "?association prov:hadPlan ?program . \n" +
+ "?program dcterms:identifier ?prov_usedByProgram . \n" +
+ "?primary_data dcterms:identifier ?pid . \n" +
+ "} \n" +
+ "]]> \n";
+
+ /* results: pidValue, executionIdValue (prov_usedByExecution) */
+ provQueries["prov_usedByExecution"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_usedByExecution \n" +
+ "WHERE { \n" +
+ "?execution prov:used ?primary_data . \n" +
+ "?primary_data dcterms:identifier ?pid . \n" +
+ "?execution dcterms:identifier ?prov_usedByExecution . \n" +
+ "} \n" +
+ "]]> \n";
+
+ /* results: pidValue, pid (prov_usedByUser) */
+ provQueries["prov_usedByUser"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_usedByUser \n" +
+ "WHERE { \n" +
+ "?execution prov:used ?primary_data . \n" +
+ "?execution prov:qualifiedAssociation ?association . \n" +
+ "?association prov:agent ?prov_usedByUser . \n" +
+ "?primary_data dcterms:identifier ?pid . \n" +
+ "} \n" +
+ "]]> \n";
+ /* results: pidValue, executionIdValue (prov_wasExecutedByExecution) */
+ provQueries["prov_wasExecutedByExecution"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_wasExecutedByExecution \n" +
+ "WHERE { \n" +
+ "?execution prov:qualifiedAssociation ?association . \n" +
+ "?association prov:hadPlan ?program . \n" +
+ "?execution dcterms:identifier ?prov_wasExecutedByExecution . \n" +
+ "?program dcterms:identifier ?pid . \n" +
+ "} \n" +
+ "]]> \n";
+
+ /* results: pidValue, pid (prov_wasExecutedByUser) */
+ provQueries["prov_wasExecutedByUser"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_wasExecutedByUser \n" +
+ "WHERE { \n" +
+ "?execution prov:qualifiedAssociation ?association . \n" +
+ "?association prov:hadPlan ?program . \n" +
+ "?association prov:agent ?prov_wasExecutedByUser . \n" +
+ "?program dcterms:identifier ?pid . \n" +
+ "} \n" +
+ "]]> \n";
+
+ /* results: pidValue, derivedDataPidValue (prov_hasDerivations) */
+ provQueries["prov_hasDerivations"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "PREFIX cito: \n" +
+ "SELECT ?pid ?prov_hasDerivations \n" +
+ "WHERE { \n" +
+ "?derived_data prov:wasDerivedFrom ?source_data . \n" +
+ "?source_data dcterms:identifier ?pid . \n" +
+ "?derived_data dcterms:identifier ?prov_hasDerivations . \n" +
+ "} \n" +
+ "]]> \n";
+
+ /* results: pidValue, pid (prov_instanceOfClass) */
+ provQueries["prov_instanceOfClass"] =
+ " \n" +
+ "PREFIX rdfs: \n" +
+ "PREFIX owl: \n" +
+ "PREFIX prov: \n" +
+ "PREFIX provone: \n" +
+ "PREFIX ore: \n" +
+ "PREFIX dcterms: \n" +
+ "SELECT ?pid ?prov_instanceOfClass \n" +
+ "WHERE { \n" +
+ "?subject rdf:type ?prov_instanceOfClass . \n" +
+ "?subject dcterms:identifier ?pid . \n" +
+ "} \n" +
+ "]]> \n";
+
+ // These are the provenance fields that are currently searched for in the provenance queries, but
+ // not all of these fields are displayed by any view.
+ // Note: this list is different than the prov list returned by MetacatUI.appSearchModel.getProvFields()
+ this.provFields = [
+ "prov_wasDerivedFrom",
+ "prov_generated",
+ "prov_wasInformedBy",
+ "prov_used",
+ "prov_generatedByProgram",
+ "prov_generatedByExecution",
+ "prov_generatedByUser",
+ "prov_usedByProgram",
+ "prov_usedByExecution",
+ "prov_usedByUser",
+ "prov_wasExecutedByExecution",
+ "prov_wasExecutedByUser",
+ "prov_hasDerivations",
+ "prov_instanceOfClass",
+ ];
+
+ // Process each SPARQL query
+ var keys = Object.keys(provQueries);
+ this.queriesToRun = keys.length;
+
+ //Bind the onResult and onDone functions to the model so they can be called out of context
+ this.onResult = _.bind(this.onResult, this);
+ this.onDone = _.bind(this.onDone, this);
+
+ /* Run queries for all provenance fields.
Each query may have multiple solutions and each solution will trigger a callback
to the 'onResult' function. When each query has completed, the 'onDone' function
is called for that query.
*/
- for (var iquery = 0; iquery < keys.length; iquery++) {
- var eq = rdf.SPARQLToQuery(
- provQueries[keys[iquery]],
- false,
- this.dataPackageGraph
- );
- this.dataPackageGraph.query(
- eq,
- this.onResult,
- this.url(),
- this.onDone
- );
- }
- } catch (error) {
- console.log(error);
- }
- },
-
- // The return values have to be extracted from the result.
- getValue: function (result, name) {
- var res = result[name];
- // The result is of type 'NamedNode', just return the string value
- if (res) {
- return res.value;
- } else return " ";
- },
-
- /* This callback is called for every query solution of the SPARQL queries. One
+ for (var iquery = 0; iquery < keys.length; iquery++) {
+ var eq = rdf.SPARQLToQuery(
+ provQueries[keys[iquery]],
+ false,
+ this.dataPackageGraph,
+ );
+ this.dataPackageGraph.query(
+ eq,
+ this.onResult,
+ this.url(),
+ this.onDone,
+ );
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ },
+
+ // The return values have to be extracted from the result.
+ getValue: function (result, name) {
+ var res = result[name];
+ // The result is of type 'NamedNode', just return the string value
+ if (res) {
+ return res.value;
+ } else return " ";
+ },
+
+ /* This callback is called for every query solution of the SPARQL queries. One
query may result in multple queries solutions and calls to this function.
Each query result returns two pids, i.e. pid: 1234 prov_generated: 5678,
which corresponds to the RDF triple '5678 wasGeneratedBy 1234', or the
@@ -1143,2670 +1094,2391 @@ define([
?primary_data : t {termType: "NamedNode", value: "https://cn-stage.test.dataone.org/cn/v2/resolve/urn%3Auuid%3Aaae9d025-a331-4c3a-b399-a8ca0a2826ef"}
?prov_wasDerivedFrom : t {termType: "Literal", value: "urn:uuid:aae9d025-a331-4c3a-b399-a8ca0a2826ef", datatype: t}]
*/
- onResult: function (result) {
- var currentPid = this.getValue(result, "?pid");
- var resval;
- var provFieldResult;
- var provFieldValues;
-
- // If there is a solution for this query, assign the value
- // to the prov field attribute (e.g. "prov_generated") of the package member (a DataONEObject)
- // with id = '?pid'
- if (typeof currentPid !== "undefined" && currentPid !== " ") {
- var currentMember = null;
- var provFieldValues;
- var fieldName = null;
- var vals = [];
- var resultMember = null;
- currentMember = this.find(function (model) {
- return model.get("id") === currentPid;
- });
-
- if (typeof currentMember === "undefined") {
- return;
- }
- // Search for a provenenace field value (i.e. 'prov_wasDerivedFrom') that was
- // returned from the query. The current prov queries all return one prov field each
- // (see this.provFields).
- // Note: dataPackage.provSources and dataPackage.provDerivations are accumulators for
- // the entire DataPackage. member.sources and member.derivations are accumulators for
- // each package member, and are used by functions such as ProvChartView().
- for (var iFld = 0; iFld < this.provFields.length; iFld++) {
- fieldName = this.provFields[iFld];
- resval = "?" + fieldName;
- // The pid corresponding to the object of the RDF triple, with the predicate
- // of 'prov_generated', 'prov_used', etc.
- // getValue returns a string value.
- provFieldResult = this.getValue(result, resval);
- if (provFieldResult != " ") {
- // Find the Datapacakge member for the result 'pid' and add the result
- // prov_* value to it. This is the package member that is the 'subject' of the
- // prov relationship.
- // The 'resultMember' could be in the current package, or could be in another 'related' package.
- resultMember = this.find(function (model) {
- return model.get("id") === provFieldResult;
- });
-
- if (typeof resultMember !== "undefined") {
- // If this prov field is a 'source' field, add it to 'sources'
-
- if (currentMember.isSourceField(fieldName)) {
- // Get the package member that the id of the prov field is associated with
- if (
- _.findWhere(this.sources, {
- id: provFieldResult,
- }) == null
- ) {
- this.sources.push(resultMember);
- }
- // Only add the result member if it has not already been added.
- if (
- _.findWhere(
- currentMember.get("provSources"),
- { id: provFieldResult }
- ) == null
- ) {
- vals = currentMember.get("provSources");
- vals.push(resultMember);
- currentMember.set("provSources", vals);
- }
- } else if (
- currentMember.isDerivationField(fieldName)
- ) {
- // If this prov field is a 'derivation' field, add it to 'derivations'
- if (
- _.findWhere(this.derivations, {
- id: provFieldResult,
- }) == null
- ) {
- this.derivations.push(resultMember);
- }
-
- if (
- _.findWhere(
- currentMember.get(
- "provDerivations"
- ),
- { id: provFieldResult }
- ) == null
- ) {
- vals =
- currentMember.get(
- "provDerivations"
- );
- vals.push(resultMember);
- currentMember.set(
- "provDerivations",
- vals
- );
- }
- }
-
- // Get the existing values for this prov field in the package member
- vals = currentMember.get(fieldName);
-
- // Push this result onto the prov file list if it is not there, i.e.
- if (!_.contains(vals, resultMember)) {
- vals.push(resultMember);
- currentMember.set(fieldName, vals);
- }
-
- //provFieldValues = _.uniq(provFieldValues);
- // Add the current prov valid (a pid) to the current value in the member
- //currentMember.set(fieldName, provFieldValues);
- //this.add(currentMember, { merge: true });
- } else {
- // The query result field is not the identifier of a packge member, so it may be the identifier
- // of another 'related' package, or it may be a string value that is the object of a prov relationship,
- // i.e. for 'prov_instanceOfClass' == 'http://purl.dataone.org/provone/2015/01/15/ontology#Data',
- // so add the value to the current member.
- vals = currentMember.get(fieldName);
- if (!_.contains(vals, provFieldResult)) {
- vals.push(provFieldResult);
- currentMember.set(fieldName, vals);
- }
- }
- }
- }
+ onResult: function (result) {
+ var currentPid = this.getValue(result, "?pid");
+ var resval;
+ var provFieldResult;
+ var provFieldValues;
+
+ // If there is a solution for this query, assign the value
+ // to the prov field attribute (e.g. "prov_generated") of the package member (a DataONEObject)
+ // with id = '?pid'
+ if (typeof currentPid !== "undefined" && currentPid !== " ") {
+ var currentMember = null;
+ var provFieldValues;
+ var fieldName = null;
+ var vals = [];
+ var resultMember = null;
+ currentMember = this.find(function (model) {
+ return model.get("id") === currentPid;
+ });
+
+ if (typeof currentMember === "undefined") {
+ return;
+ }
+ // Search for a provenenace field value (i.e. 'prov_wasDerivedFrom') that was
+ // returned from the query. The current prov queries all return one prov field each
+ // (see this.provFields).
+ // Note: dataPackage.provSources and dataPackage.provDerivations are accumulators for
+ // the entire DataPackage. member.sources and member.derivations are accumulators for
+ // each package member, and are used by functions such as ProvChartView().
+ for (var iFld = 0; iFld < this.provFields.length; iFld++) {
+ fieldName = this.provFields[iFld];
+ resval = "?" + fieldName;
+ // The pid corresponding to the object of the RDF triple, with the predicate
+ // of 'prov_generated', 'prov_used', etc.
+ // getValue returns a string value.
+ provFieldResult = this.getValue(result, resval);
+ if (provFieldResult != " ") {
+ // Find the Datapacakge member for the result 'pid' and add the result
+ // prov_* value to it. This is the package member that is the 'subject' of the
+ // prov relationship.
+ // The 'resultMember' could be in the current package, or could be in another 'related' package.
+ resultMember = this.find(function (model) {
+ return model.get("id") === provFieldResult;
+ });
+
+ if (typeof resultMember !== "undefined") {
+ // If this prov field is a 'source' field, add it to 'sources'
+
+ if (currentMember.isSourceField(fieldName)) {
+ // Get the package member that the id of the prov field is associated with
+ if (
+ _.findWhere(this.sources, {
+ id: provFieldResult,
+ }) == null
+ ) {
+ this.sources.push(resultMember);
+ }
+ // Only add the result member if it has not already been added.
+ if (
+ _.findWhere(currentMember.get("provSources"), {
+ id: provFieldResult,
+ }) == null
+ ) {
+ vals = currentMember.get("provSources");
+ vals.push(resultMember);
+ currentMember.set("provSources", vals);
+ }
+ } else if (currentMember.isDerivationField(fieldName)) {
+ // If this prov field is a 'derivation' field, add it to 'derivations'
+ if (
+ _.findWhere(this.derivations, {
+ id: provFieldResult,
+ }) == null
+ ) {
+ this.derivations.push(resultMember);
+ }
+
+ if (
+ _.findWhere(currentMember.get("provDerivations"), {
+ id: provFieldResult,
+ }) == null
+ ) {
+ vals = currentMember.get("provDerivations");
+ vals.push(resultMember);
+ currentMember.set("provDerivations", vals);
+ }
}
- },
- /* This callback is called when all queries have finished. */
- onDone: function () {
- if (this.queriesToRun > 1) {
- this.queriesToRun--;
- } else {
- // Signal that all prov queries have finished
- this.provenanceFlag = "complete";
- this.trigger("queryComplete");
- }
- },
-
- /*
- * Use the DataONEObject parseSysMeta() function
- */
- parseSysMeta: function () {
- return DataONEObject.parseSysMeta.call(this, arguments[0]);
- },
+ // Get the existing values for this prov field in the package member
+ vals = currentMember.get(fieldName);
- /**
- * Overwrite the Backbone.Collection.sync() function to set custom options
- * @param {Object} [options] - Options for this DataPackage save
- * @param {Boolean} [options.sysMetaOnly] - If true, only the system metadata of this Package will be saved.
- * @param {Boolean} [options.resourceMapOnly] - If true, only the Resource Map/Package object will be saved. Metadata and Data objects aggregated by the package will be skipped.
- */
- save: function (options) {
- if (!options) var options = {};
-
- this.packageModel.set("uploadStatus", "p");
-
- //Get the system metadata first if we haven't retrieved it yet
- if (!this.packageModel.get("sysMetaXML")) {
- var collection = this;
- this.packageModel.fetch({
- success: function () {
- collection.save(options);
- },
- });
- return;
+ // Push this result onto the prov file list if it is not there, i.e.
+ if (!_.contains(vals, resultMember)) {
+ vals.push(resultMember);
+ currentMember.set(fieldName, vals);
}
- //If we want to update the system metadata only,
- // then update via the DataONEObject model and exit
- if (options.sysMetaOnly) {
- this.packageModel.save(null, options);
- return;
- }
-
- if (options.resourceMapOnly !== true) {
- //Sort the models in the collection so the metadata is saved first
- var metadataModels = this.where({ type: "Metadata" });
- var dataModels = _.difference(this.models, metadataModels);
- var sortedModels = _.union(metadataModels, dataModels);
- var modelsInProgress = _.filter(sortedModels, function (m) {
- return (
- m.get("uploadStatus") == "p" ||
- m.get("sysMetaUploadStatus") == "p"
- );
- });
- var modelsToBeSaved = _.filter(sortedModels, function (m) {
- //Models should be saved if they are in the save queue, had an error saving earlier,
- //or they are Science Metadata model that is NOT already in progress
- return (
- (m.get("type") == "Metadata" &&
- m.get("uploadStatus") == "q") ||
- (m.get("type") == "Data" &&
- m.get("hasContentChanges") &&
- m.get("uploadStatus") != "p" &&
- m.get("uploadStatus") != "c" &&
- m.get("uploadStatus") != "e") ||
- (m.get("type") == "Metadata" &&
- m.get("uploadStatus") != "p" &&
- m.get("uploadStatus") != "c" &&
- m.get("uploadStatus") != "e" &&
- m.get("uploadStatus") !== null)
- );
- });
- //Get an array of data objects whose system metadata should be updated.
- var sysMetaToUpdate = _.reject(dataModels, function (m) {
- // Find models that don't have any content changes to save,
- // and whose system metadata is not already saving
- return (
- !m.hasUpdates() ||
- m.get("hasContentChanges") ||
- m.get("sysMetaUploadStatus") == "p" ||
- m.get("sysMetaUploadStatus") == "c" ||
- m.get("sysMetaUploadStatus") == "e"
- );
- });
-
- //First quickly validate all the models before attempting to save any
- var allValid = _.every(modelsToBeSaved, function (m) {
- if (m.isValid()) {
- m.trigger("valid");
- return true;
- } else {
- return false;
- }
- });
-
- // If at least once model to be saved is invalid,
- // or the metadata failed to save, cancel the save.
- if (
- !allValid ||
- _.contains(
- _.map(metadataModels, function (model) {
- return model.get("uploadStatus");
- }),
- "e"
- )
- ) {
- this.packageModel.set("changed", false);
- this.packageModel.set("uploadStatus", "q");
- this.trigger("cancelSave");
- return;
- }
-
- //If we are saving at least one model in this package, then serialize the Resource Map RDF XML
- if (modelsToBeSaved.length) {
- try {
- //Set a new id and keep our old id
- if (!this.packageModel.isNew()) {
- //Update the identifier for this object
- this.packageModel.updateID();
- }
-
- //Create the resource map XML
- var mapXML = this.serialize();
- } catch (serializationException) {
- //If serialization failed, revert back to our old id
- this.packageModel.resetID();
-
- //Cancel the save and show an error message
- this.packageModel.set("changed", false);
- this.packageModel.set("uploadStatus", "q");
- this.trigger(
- "errorSaving",
- "There was a Javascript error during the serialization process: " +
- serializationException
- );
- return;
- }
- }
-
- //First save all the models of the collection, if needed
- _.each(
- modelsToBeSaved,
- function (model) {
- //If the model is saved successfully, start this save function again
- this.stopListening(
- model,
- "successSaving",
- this.save
- );
- this.listenToOnce(
- model,
- "successSaving",
- this.save
- );
-
- //If the model fails to save, start this save function
- this.stopListening(model, "errorSaving", this.save);
- this.listenToOnce(model, "errorSaving", this.save);
-
- //If the model fails to save, start this save function
- this.stopListening(model, "cancelSave", this.save);
- this.listenToOnce(model, "cancelSave", this.save);
-
- //Save the model and watch for fails
- model.save();
-
- //Add it to the list of models in progress
- modelsInProgress.push(model);
-
- this.numSaves++;
- },
- this
- );
-
- //Save the system metadata of all the Data objects
- _.each(
- sysMetaToUpdate,
- function (dataModel) {
- //When the sytem metadata has been saved, save this resource map
- this.listenTo(
- dataModel,
- "change:sysMetaUploadStatus",
- this.save
- );
- //Update the system metadata
- dataModel.updateSysMeta();
- //Add it to the list of models in progress
- modelsInProgress.push(dataModel);
- this.numSaves++;
- },
- this
- );
-
- //If there are still models in progress of uploading, then exit. (We will return when they are synced to upload the resource map)
- if (modelsInProgress.length) return;
- }
- //If we are saving the resource map object only, and there are changes to save, serialize the RDF XML
- else if (this.needsUpdate()) {
- try {
- //Set a new id and keep our old id
- if (!this.packageModel.isNew()) {
- //Update the identifier for this object
- this.packageModel.updateID();
- }
-
- //Create the resource map XML
- var mapXML = this.serialize();
- } catch (serializationException) {
- //If serialization failed, revert back to our old id
- this.packageModel.resetID();
-
- //Cancel the save and show an error message
- this.packageModel.set("changed", false);
- this.packageModel.set("uploadStatus", "q");
- this.trigger(
- "errorSaving",
- "There was a Javascript error during the serialization process: " +
- serializationException
- );
- return;
- }
- }
- //If we are saving the resource map object only, and there are no changes to save, exit the function
- else if (!this.needsUpdate()) {
- return;
- }
-
- //If no models were saved and this package has no changes, we can exit without saving the resource map
- if (this.numSaves < 1 && !this.needsUpdate()) {
- this.numSaves = 0;
- this.packageModel.set(
- "uploadStatus",
- this.packageModel.defaults().uploadStatus
- );
- this.trigger("successSaving", this);
- return;
+ //provFieldValues = _.uniq(provFieldValues);
+ // Add the current prov valid (a pid) to the current value in the member
+ //currentMember.set(fieldName, provFieldValues);
+ //this.add(currentMember, { merge: true });
+ } else {
+ // The query result field is not the identifier of a packge member, so it may be the identifier
+ // of another 'related' package, or it may be a string value that is the object of a prov relationship,
+ // i.e. for 'prov_instanceOfClass' == 'http://purl.dataone.org/provone/2015/01/15/ontology#Data',
+ // so add the value to the current member.
+ vals = currentMember.get(fieldName);
+ if (!_.contains(vals, provFieldResult)) {
+ vals.push(provFieldResult);
+ currentMember.set(fieldName, vals);
}
+ }
+ }
+ }
+ }
+ },
+
+ /* This callback is called when all queries have finished. */
+ onDone: function () {
+ if (this.queriesToRun > 1) {
+ this.queriesToRun--;
+ } else {
+ // Signal that all prov queries have finished
+ this.provenanceFlag = "complete";
+ this.trigger("queryComplete");
+ }
+ },
- //Reset the number of models saved since they should all be completed by now
- this.numSaves = 0;
+ /*
+ * Use the DataONEObject parseSysMeta() function
+ */
+ parseSysMeta: function () {
+ return DataONEObject.parseSysMeta.call(this, arguments[0]);
+ },
+
+ /**
+ * Overwrite the Backbone.Collection.sync() function to set custom options
+ * @param {Object} [options] - Options for this DataPackage save
+ * @param {Boolean} [options.sysMetaOnly] - If true, only the system metadata of this Package will be saved.
+ * @param {Boolean} [options.resourceMapOnly] - If true, only the Resource Map/Package object will be saved. Metadata and Data objects aggregated by the package will be skipped.
+ */
+ save: function (options) {
+ if (!options) var options = {};
- //Determine the HTTP request type
- var requestType;
- if (this.packageModel.isNew()) {
- requestType = "POST";
- } else {
- requestType = "PUT";
- }
+ this.packageModel.set("uploadStatus", "p");
- //Create a FormData object to send data with the XHR
- var formData = new FormData();
+ //Get the system metadata first if we haven't retrieved it yet
+ if (!this.packageModel.get("sysMetaXML")) {
+ var collection = this;
+ this.packageModel.fetch({
+ success: function () {
+ collection.save(options);
+ },
+ });
+ return;
+ }
- //Add the identifier to the XHR data
- if (this.packageModel.isNew()) {
- formData.append("pid", this.packageModel.get("id"));
- } else {
- //Add the ids to the form data
- formData.append("newPid", this.packageModel.get("id"));
- formData.append("pid", this.packageModel.get("oldPid"));
- }
+ //If we want to update the system metadata only,
+ // then update via the DataONEObject model and exit
+ if (options.sysMetaOnly) {
+ this.packageModel.save(null, options);
+ return;
+ }
- //Do a fresh re-serialization of the RDF XML, in case any pids in the package have changed.
- //The hope is that any errors during the serialization process have already been caught during the first serialization above
- try {
- var mapXML = this.serialize();
- } catch (serializationException) {
- //Cancel the save and show an error message
- this.packageModel.set("changed", false);
- this.packageModel.set("uploadStatus", "q");
- this.trigger(
- "errorSaving",
- "There was a Javascript error during the serialization process: " +
- serializationException
- );
- return;
- }
+ if (options.resourceMapOnly !== true) {
+ //Sort the models in the collection so the metadata is saved first
+ var metadataModels = this.where({ type: "Metadata" });
+ var dataModels = _.difference(this.models, metadataModels);
+ var sortedModels = _.union(metadataModels, dataModels);
+ var modelsInProgress = _.filter(sortedModels, function (m) {
+ return (
+ m.get("uploadStatus") == "p" ||
+ m.get("sysMetaUploadStatus") == "p"
+ );
+ });
+ var modelsToBeSaved = _.filter(sortedModels, function (m) {
+ //Models should be saved if they are in the save queue, had an error saving earlier,
+ //or they are Science Metadata model that is NOT already in progress
+ return (
+ (m.get("type") == "Metadata" && m.get("uploadStatus") == "q") ||
+ (m.get("type") == "Data" &&
+ m.get("hasContentChanges") &&
+ m.get("uploadStatus") != "p" &&
+ m.get("uploadStatus") != "c" &&
+ m.get("uploadStatus") != "e") ||
+ (m.get("type") == "Metadata" &&
+ m.get("uploadStatus") != "p" &&
+ m.get("uploadStatus") != "c" &&
+ m.get("uploadStatus") != "e" &&
+ m.get("uploadStatus") !== null)
+ );
+ });
+ //Get an array of data objects whose system metadata should be updated.
+ var sysMetaToUpdate = _.reject(dataModels, function (m) {
+ // Find models that don't have any content changes to save,
+ // and whose system metadata is not already saving
+ return (
+ !m.hasUpdates() ||
+ m.get("hasContentChanges") ||
+ m.get("sysMetaUploadStatus") == "p" ||
+ m.get("sysMetaUploadStatus") == "c" ||
+ m.get("sysMetaUploadStatus") == "e"
+ );
+ });
+
+ //First quickly validate all the models before attempting to save any
+ var allValid = _.every(modelsToBeSaved, function (m) {
+ if (m.isValid()) {
+ m.trigger("valid");
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ // If at least once model to be saved is invalid,
+ // or the metadata failed to save, cancel the save.
+ if (
+ !allValid ||
+ _.contains(
+ _.map(metadataModels, function (model) {
+ return model.get("uploadStatus");
+ }),
+ "e",
+ )
+ ) {
+ this.packageModel.set("changed", false);
+ this.packageModel.set("uploadStatus", "q");
+ this.trigger("cancelSave");
+ return;
+ }
+
+ //If we are saving at least one model in this package, then serialize the Resource Map RDF XML
+ if (modelsToBeSaved.length) {
+ try {
+ //Set a new id and keep our old id
+ if (!this.packageModel.isNew()) {
+ //Update the identifier for this object
+ this.packageModel.updateID();
+ }
- //Make a Blob object from the serialized RDF XML
- var mapBlob = new Blob([mapXML], { type: "application/xml" });
+ //Create the resource map XML
+ var mapXML = this.serialize();
+ } catch (serializationException) {
+ //If serialization failed, revert back to our old id
+ this.packageModel.resetID();
+
+ //Cancel the save and show an error message
+ this.packageModel.set("changed", false);
+ this.packageModel.set("uploadStatus", "q");
+ this.trigger(
+ "errorSaving",
+ "There was a Javascript error during the serialization process: " +
+ serializationException,
+ );
+ return;
+ }
+ }
+
+ //First save all the models of the collection, if needed
+ _.each(
+ modelsToBeSaved,
+ function (model) {
+ //If the model is saved successfully, start this save function again
+ this.stopListening(model, "successSaving", this.save);
+ this.listenToOnce(model, "successSaving", this.save);
+
+ //If the model fails to save, start this save function
+ this.stopListening(model, "errorSaving", this.save);
+ this.listenToOnce(model, "errorSaving", this.save);
+
+ //If the model fails to save, start this save function
+ this.stopListening(model, "cancelSave", this.save);
+ this.listenToOnce(model, "cancelSave", this.save);
+
+ //Save the model and watch for fails
+ model.save();
+
+ //Add it to the list of models in progress
+ modelsInProgress.push(model);
+
+ this.numSaves++;
+ },
+ this,
+ );
+
+ //Save the system metadata of all the Data objects
+ _.each(
+ sysMetaToUpdate,
+ function (dataModel) {
+ //When the sytem metadata has been saved, save this resource map
+ this.listenTo(dataModel, "change:sysMetaUploadStatus", this.save);
+ //Update the system metadata
+ dataModel.updateSysMeta();
+ //Add it to the list of models in progress
+ modelsInProgress.push(dataModel);
+ this.numSaves++;
+ },
+ this,
+ );
- //Get the size of the new resource map
- this.packageModel.set("size", mapBlob.size);
+ //If there are still models in progress of uploading, then exit. (We will return when they are synced to upload the resource map)
+ if (modelsInProgress.length) return;
+ }
+ //If we are saving the resource map object only, and there are changes to save, serialize the RDF XML
+ else if (this.needsUpdate()) {
+ try {
+ //Set a new id and keep our old id
+ if (!this.packageModel.isNew()) {
+ //Update the identifier for this object
+ this.packageModel.updateID();
+ }
+
+ //Create the resource map XML
+ var mapXML = this.serialize();
+ } catch (serializationException) {
+ //If serialization failed, revert back to our old id
+ this.packageModel.resetID();
+
+ //Cancel the save and show an error message
+ this.packageModel.set("changed", false);
+ this.packageModel.set("uploadStatus", "q");
+ this.trigger(
+ "errorSaving",
+ "There was a Javascript error during the serialization process: " +
+ serializationException,
+ );
+ return;
+ }
+ }
+ //If we are saving the resource map object only, and there are no changes to save, exit the function
+ else if (!this.needsUpdate()) {
+ return;
+ }
- //Get the new checksum of the resource map
- var checksum = md5(mapXML);
- this.packageModel.set("checksum", checksum);
- this.packageModel.set("checksumAlgorithm", "MD5");
+ //If no models were saved and this package has no changes, we can exit without saving the resource map
+ if (this.numSaves < 1 && !this.needsUpdate()) {
+ this.numSaves = 0;
+ this.packageModel.set(
+ "uploadStatus",
+ this.packageModel.defaults().uploadStatus,
+ );
+ this.trigger("successSaving", this);
+ return;
+ }
- //Set the file name based on the id
- this.packageModel.set(
- "fileName",
- this.packageModel.get("id").replace(/[^a-zA-Z0-9]/g, "_") +
- ".rdf.xml"
- );
+ //Reset the number of models saved since they should all be completed by now
+ this.numSaves = 0;
- //Create the system metadata
- var sysMetaXML = this.packageModel.serializeSysMeta();
+ //Determine the HTTP request type
+ var requestType;
+ if (this.packageModel.isNew()) {
+ requestType = "POST";
+ } else {
+ requestType = "PUT";
+ }
- //Send the system metadata
- var xmlBlob = new Blob([sysMetaXML], {
- type: "application/xml",
- });
+ //Create a FormData object to send data with the XHR
+ var formData = new FormData();
- //Add the object XML and System Metadata XML to the form data
- //Append the system metadata first, so we can take advantage of Metacat's streaming multipart handler
- formData.append("sysmeta", xmlBlob, "sysmeta");
- formData.append("object", mapBlob);
+ //Add the identifier to the XHR data
+ if (this.packageModel.isNew()) {
+ formData.append("pid", this.packageModel.get("id"));
+ } else {
+ //Add the ids to the form data
+ formData.append("newPid", this.packageModel.get("id"));
+ formData.append("pid", this.packageModel.get("oldPid"));
+ }
- var collection = this;
- var requestSettings = {
- url: this.packageModel.isNew()
- ? this.url()
- : this.url({ update: true }),
- type: requestType,
- cache: false,
- contentType: false,
- processData: false,
- data: formData,
- success: function (response) {
- //Update the object XML
- collection.objectXML = mapXML;
- collection.packageModel.set(
- "sysMetaXML",
- collection.packageModel.serializeSysMeta()
- );
-
- //Reset the upload status for all members
- _.each(
- collection.where({ uploadStatus: "c" }),
- function (m) {
- m.set(
- "uploadStatus",
- m.defaults().uploadStatus
- );
- }
- );
-
- // Reset oldPid to null so we know we need to update the ID
- // in the future
- collection.packageModel.set("oldPid", null);
-
- //Reset the upload status for the package
- collection.packageModel.set(
- "uploadStatus",
- collection.packageModel.defaults().uploadStatus
- );
-
- // Reset the content changes status
- collection.packageModel.set("hasContentChanges", false);
-
- // This package is no longer new, so mark it as such
- collection.packageModel.set("isNew", false);
-
- collection.trigger("successSaving", collection);
-
- collection.packageModel.fetch({ merge: true });
-
- _.each(sysMetaToUpdate, function (dataModel) {
- dataModel.set("sysMetaUploadStatus", "c");
- });
- },
- error: function (data) {
- //Reset the id back to its original state
- collection.packageModel.resetID();
-
- //Reset the upload status for all members
- _.each(
- collection.where({ uploadStatus: "c" }),
- function (m) {
- m.set(
- "uploadStatus",
- m.defaults().uploadStatus
- );
- }
- );
-
- //When there is no network connection (status == 0), there will be no response text
- if (data.status == 408 || data.status == 0) {
- var parsedResponse =
- "There was a network issue that prevented this file from uploading. " +
- "Make sure you are connected to a reliable internet connection.";
- } else {
- var parsedResponse = $(data.responseText)
- .not("style, title")
- .text();
- }
-
- //Save the error message in the model
- collection.packageModel.set(
- "errorMessage",
- parsedResponse
- );
-
- //Reset the upload status for the package
- collection.packageModel.set("uploadStatus", "e");
-
- collection.trigger("errorSaving", parsedResponse);
-
- // Track this error in our analytics
- MetacatUI.analytics?.trackException(
- `DataPackage save error: ${parsedResponse}`,
- collection.packageModel.get("id"),
- true
- );
- },
- };
- $.ajax(
- _.extend(
- requestSettings,
- MetacatUI.appUserModel.createAjaxSettings()
- )
- );
- },
+ //Do a fresh re-serialization of the RDF XML, in case any pids in the package have changed.
+ //The hope is that any errors during the serialization process have already been caught during the first serialization above
+ try {
+ var mapXML = this.serialize();
+ } catch (serializationException) {
+ //Cancel the save and show an error message
+ this.packageModel.set("changed", false);
+ this.packageModel.set("uploadStatus", "q");
+ this.trigger(
+ "errorSaving",
+ "There was a Javascript error during the serialization process: " +
+ serializationException,
+ );
+ return;
+ }
- /*
- * When a data package member updates, we evaluate it for its formatid,
- * and update it appropriately if it is not a data object only
- */
- getMember: function (context, args) {
- var memberModel = {};
-
- switch (context.get("formatId")) {
- case "http://www.openarchives.org/ore/terms":
- context.attributes.id = context.id;
- context.attributes.type = "DataPackage";
- context.attributes.childPackages = {};
- memberModel = new DataPackage(null, {
- packageModel: context.attributes,
- });
- this.packageModel.get("childPackages")[
- memberModel.packageModel.id
- ] = memberModel;
- break;
-
- case "eml://ecoinformatics.org/eml-2.0.0":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new EML211(context.attributes);
- break;
-
- case "eml://ecoinformatics.org/eml-2.0.1":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new EML211(context.attributes);
- break;
-
- case "eml://ecoinformatics.org/eml-2.1.0":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new EML211(context.attributes);
- break;
-
- case "eml://ecoinformatics.org/eml-2.1.1":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new EML211(context.attributes);
- break;
-
- case "https://eml.ecoinformatics.org/eml-2.2.0":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new EML211(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-access-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-access-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-attribute-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-attribute-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-constraint-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-constraint-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-coverage-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-coverage-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-dataset-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-dataset-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-distribution-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-distribution-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-entity-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-entity-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-literature-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-literature-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-party-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-party-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-physical-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-physical-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-project-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-project-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-protocol-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-protocol-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-resource-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-resource-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-software-2.0.0beta4//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "-//ecoinformatics.org//eml-software-2.0.0beta6//EN":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "FGDC-STD-001-1998":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "FGDC-STD-001.1-1999":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "FGDC-STD-001.2-1999":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "INCITS-453-2009":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "ddi:codebook:2_5":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://datacite.org/schema/kernel-3.0":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://datacite.org/schema/kernel-3.1":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://datadryad.org/profile/v3.1":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://ns.dataone.org/metadata/schema/onedcx/v1.0":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://purl.org/dryad/terms/":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://purl.org/ornl/schema/mercury/terms/v1.0":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://rs.tdwg.org/dwc/xsd/simpledarwincore/":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://www.cuahsi.org/waterML/1.0/":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://www.cuahsi.org/waterML/1.1/":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://www.esri.com/metadata/esriprof80.dtd":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://www.icpsr.umich.edu/DDI":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://www.isotc211.org/2005/gmd":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://www.isotc211.org/2005/gmd-noaa":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://www.loc.gov/METS/":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- case "http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2":
- context.set({ type: "Metadata", sortOrder: 1 });
- memberModel = new ScienceMetadata(context.attributes);
- break;
-
- default:
- // For other data formats, keep just the DataONEObject sysmeta
- context.set({ type: "Data", sortOrder: 2 });
- memberModel = context;
- }
+ //Make a Blob object from the serialized RDF XML
+ var mapBlob = new Blob([mapXML], { type: "application/xml" });
+
+ //Get the size of the new resource map
+ this.packageModel.set("size", mapBlob.size);
+
+ //Get the new checksum of the resource map
+ var checksum = md5(mapXML);
+ this.packageModel.set("checksum", checksum);
+ this.packageModel.set("checksumAlgorithm", "MD5");
+
+ //Set the file name based on the id
+ this.packageModel.set(
+ "fileName",
+ this.packageModel.get("id").replace(/[^a-zA-Z0-9]/g, "_") +
+ ".rdf.xml",
+ );
+
+ //Create the system metadata
+ var sysMetaXML = this.packageModel.serializeSysMeta();
+
+ //Send the system metadata
+ var xmlBlob = new Blob([sysMetaXML], {
+ type: "application/xml",
+ });
+
+ //Add the object XML and System Metadata XML to the form data
+ //Append the system metadata first, so we can take advantage of Metacat's streaming multipart handler
+ formData.append("sysmeta", xmlBlob, "sysmeta");
+ formData.append("object", mapBlob);
+
+ var collection = this;
+ var requestSettings = {
+ url: this.packageModel.isNew()
+ ? this.url()
+ : this.url({ update: true }),
+ type: requestType,
+ cache: false,
+ contentType: false,
+ processData: false,
+ data: formData,
+ success: function (response) {
+ //Update the object XML
+ collection.objectXML = mapXML;
+ collection.packageModel.set(
+ "sysMetaXML",
+ collection.packageModel.serializeSysMeta(),
+ );
+
+ //Reset the upload status for all members
+ _.each(collection.where({ uploadStatus: "c" }), function (m) {
+ m.set("uploadStatus", m.defaults().uploadStatus);
+ });
+
+ // Reset oldPid to null so we know we need to update the ID
+ // in the future
+ collection.packageModel.set("oldPid", null);
+
+ //Reset the upload status for the package
+ collection.packageModel.set(
+ "uploadStatus",
+ collection.packageModel.defaults().uploadStatus,
+ );
+
+ // Reset the content changes status
+ collection.packageModel.set("hasContentChanges", false);
+
+ // This package is no longer new, so mark it as such
+ collection.packageModel.set("isNew", false);
+
+ collection.trigger("successSaving", collection);
+
+ collection.packageModel.fetch({ merge: true });
+
+ _.each(sysMetaToUpdate, function (dataModel) {
+ dataModel.set("sysMetaUploadStatus", "c");
+ });
+ },
+ error: function (data) {
+ //Reset the id back to its original state
+ collection.packageModel.resetID();
+
+ //Reset the upload status for all members
+ _.each(collection.where({ uploadStatus: "c" }), function (m) {
+ m.set("uploadStatus", m.defaults().uploadStatus);
+ });
+
+ //When there is no network connection (status == 0), there will be no response text
+ if (data.status == 408 || data.status == 0) {
+ var parsedResponse =
+ "There was a network issue that prevented this file from uploading. " +
+ "Make sure you are connected to a reliable internet connection.";
+ } else {
+ var parsedResponse = $(data.responseText)
+ .not("style, title")
+ .text();
+ }
+
+ //Save the error message in the model
+ collection.packageModel.set("errorMessage", parsedResponse);
+
+ //Reset the upload status for the package
+ collection.packageModel.set("uploadStatus", "e");
+
+ collection.trigger("errorSaving", parsedResponse);
+
+ // Track this error in our analytics
+ MetacatUI.analytics?.trackException(
+ `DataPackage save error: ${parsedResponse}`,
+ collection.packageModel.get("id"),
+ true,
+ );
+ },
+ };
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ /*
+ * When a data package member updates, we evaluate it for its formatid,
+ * and update it appropriately if it is not a data object only
+ */
+ getMember: function (context, args) {
+ var memberModel = {};
+
+ switch (context.get("formatId")) {
+ case "http://www.openarchives.org/ore/terms":
+ context.attributes.id = context.id;
+ context.attributes.type = "DataPackage";
+ context.attributes.childPackages = {};
+ memberModel = new DataPackage(null, {
+ packageModel: context.attributes,
+ });
+ this.packageModel.get("childPackages")[
+ memberModel.packageModel.id
+ ] = memberModel;
+ break;
+
+ case "eml://ecoinformatics.org/eml-2.0.0":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new EML211(context.attributes);
+ break;
+
+ case "eml://ecoinformatics.org/eml-2.0.1":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new EML211(context.attributes);
+ break;
+
+ case "eml://ecoinformatics.org/eml-2.1.0":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new EML211(context.attributes);
+ break;
+
+ case "eml://ecoinformatics.org/eml-2.1.1":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new EML211(context.attributes);
+ break;
+
+ case "https://eml.ecoinformatics.org/eml-2.2.0":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new EML211(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-access-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-access-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-attribute-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-attribute-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-constraint-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-constraint-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-coverage-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-coverage-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-dataset-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-dataset-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-distribution-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-distribution-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-entity-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-entity-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-literature-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-literature-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-party-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-party-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-physical-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-physical-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-project-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-project-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-protocol-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-protocol-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-resource-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-resource-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-software-2.0.0beta4//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "-//ecoinformatics.org//eml-software-2.0.0beta6//EN":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "FGDC-STD-001-1998":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "FGDC-STD-001.1-1999":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "FGDC-STD-001.2-1999":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "INCITS-453-2009":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "ddi:codebook:2_5":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://datacite.org/schema/kernel-3.0":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://datacite.org/schema/kernel-3.1":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://datadryad.org/profile/v3.1":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://ns.dataone.org/metadata/schema/onedcx/v1.0":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://purl.org/dryad/terms/":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://purl.org/ornl/schema/mercury/terms/v1.0":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://rs.tdwg.org/dwc/xsd/simpledarwincore/":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://www.cuahsi.org/waterML/1.0/":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://www.cuahsi.org/waterML/1.1/":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://www.esri.com/metadata/esriprof80.dtd":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://www.icpsr.umich.edu/DDI":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://www.isotc211.org/2005/gmd":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://www.isotc211.org/2005/gmd-noaa":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://www.loc.gov/METS/":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ case "http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2":
+ context.set({ type: "Metadata", sortOrder: 1 });
+ memberModel = new ScienceMetadata(context.attributes);
+ break;
+
+ default:
+ // For other data formats, keep just the DataONEObject sysmeta
+ context.set({ type: "Data", sortOrder: 2 });
+ memberModel = context;
+ }
- if (memberModel.type == "DataPackage") {
- // We have a nested collection
- memberModel.packageModel.set(
- "nodeLevel",
- this.packageModel.get("nodeLevel") + 1
- );
- } else {
- // We have a model
- memberModel.set(
- "nodeLevel",
- this.packageModel.get("nodeLevel")
- ); // same level for all members
- }
+ if (memberModel.type == "DataPackage") {
+ // We have a nested collection
+ memberModel.packageModel.set(
+ "nodeLevel",
+ this.packageModel.get("nodeLevel") + 1,
+ );
+ } else {
+ // We have a model
+ memberModel.set("nodeLevel", this.packageModel.get("nodeLevel")); // same level for all members
+ }
- return memberModel;
- },
+ return memberModel;
+ },
- triggerComplete: function (model) {
- //If the last fetch did not fetch the models of the collection, then mark as complete now.
- if (this.fetchModels === false) {
- // Delete the fetchModels property since it is set only once per fetch.
- delete this.fetchModels;
+ triggerComplete: function (model) {
+ //If the last fetch did not fetch the models of the collection, then mark as complete now.
+ if (this.fetchModels === false) {
+ // Delete the fetchModels property since it is set only once per fetch.
+ delete this.fetchModels;
- this.trigger("complete", this);
+ this.trigger("complete", this);
- return;
- }
+ return;
+ }
- //Check if the collection is done being retrieved
- var notSynced = this.reject(function (m) {
- return m.get("synced") || m.get("id") == model.get("id");
- });
+ //Check if the collection is done being retrieved
+ var notSynced = this.reject(function (m) {
+ return m.get("synced") || m.get("id") == model.get("id");
+ });
- //If there are any models that are not synced yet, the collection is not complete
- if (notSynced.length > 0) {
- return;
- }
+ //If there are any models that are not synced yet, the collection is not complete
+ if (notSynced.length > 0) {
+ return;
+ }
- //If the number of models in this collection does not equal the number of objects referenced in the RDF XML, the collection is not complete
- if (this.originalMembers.length > this.length) return;
+ //If the number of models in this collection does not equal the number of objects referenced in the RDF XML, the collection is not complete
+ if (this.originalMembers.length > this.length) return;
- this.sort();
- this.trigger("complete", this);
- },
+ this.sort();
+ this.trigger("complete", this);
+ },
- /* Accumulate edits that are made to the provenance relationships via the ProvChartView. these
+ /* Accumulate edits that are made to the provenance relationships via the ProvChartView. these
edits are accumulated here so that they are available to any package member or view.
*/
- recordProvEdit: function (operation, subject, predicate, object) {
- if (!this.provEdits.length) {
- this.provEdits = [[operation, subject, predicate, object]];
- } else {
- // First check if the edit already exists in the list. If yes, then
- // don't add it again! This could occur if an edit icon was clicked rapidly
- // before it is dismissed.
- var editFound = _.find(this.provEdits, function (edit) {
- return (
- edit[0] == operation &&
- edit[1] == subject &&
- edit[2] == predicate &&
- edit[3] == object
- );
- });
-
- if (typeof editFound != "undefined") {
- return;
- }
-
- // If this is a delete operation, then check if a matching operation
- // is in the edit list (i.e. the user may have changed their mind, and
- // they just want to cancel an edit). If yes, then just delete the
- // matching add edit request
- var editListSize = this.provEdits.length;
- var oppositeOp = operation == "delete" ? "add" : "delete";
-
- this.provEdits = _.reject(this.provEdits, function (edit) {
- var editOperation = edit[0];
- var editSubjectId = edit[1];
- var editPredicate = edit[2];
- var editObject = edit[3];
- if (
- editOperation == oppositeOp &&
- editSubjectId == subject &&
- editPredicate == predicate &&
- editObject == object
- ) {
- return true;
- }
- });
+ recordProvEdit: function (operation, subject, predicate, object) {
+ if (!this.provEdits.length) {
+ this.provEdits = [[operation, subject, predicate, object]];
+ } else {
+ // First check if the edit already exists in the list. If yes, then
+ // don't add it again! This could occur if an edit icon was clicked rapidly
+ // before it is dismissed.
+ var editFound = _.find(this.provEdits, function (edit) {
+ return (
+ edit[0] == operation &&
+ edit[1] == subject &&
+ edit[2] == predicate &&
+ edit[3] == object
+ );
+ });
+
+ if (typeof editFound != "undefined") {
+ return;
+ }
+
+ // If this is a delete operation, then check if a matching operation
+ // is in the edit list (i.e. the user may have changed their mind, and
+ // they just want to cancel an edit). If yes, then just delete the
+ // matching add edit request
+ var editListSize = this.provEdits.length;
+ var oppositeOp = operation == "delete" ? "add" : "delete";
+
+ this.provEdits = _.reject(this.provEdits, function (edit) {
+ var editOperation = edit[0];
+ var editSubjectId = edit[1];
+ var editPredicate = edit[2];
+ var editObject = edit[3];
+ if (
+ editOperation == oppositeOp &&
+ editSubjectId == subject &&
+ editPredicate == predicate &&
+ editObject == object
+ ) {
+ return true;
+ }
+ });
+
+ // If we cancelled out edit containing inverse of the current edit
+ // then the edit list will now be one edit shorter. Test for this
+ // and only save the current edit if we didn't remove the inverse.
+ if (editListSize >= this.provEdits.length) {
+ this.provEdits.push([operation, subject, predicate, object]);
+ }
+ }
+ },
- // If we cancelled out edit containing inverse of the current edit
- // then the edit list will now be one edit shorter. Test for this
- // and only save the current edit if we didn't remove the inverse.
- if (editListSize >= this.provEdits.length) {
- this.provEdits.push([
- operation,
- subject,
- predicate,
- object,
- ]);
- }
- }
- },
+ // Return true if the prov edits list is not empty
+ provEditsPending: function () {
+ if (this.provEdits.length) return true;
+ return false;
+ },
- // Return true if the prov edits list is not empty
- provEditsPending: function () {
- if (this.provEdits.length) return true;
- return false;
- },
-
- /* If provenance relationships have been modified by the provenance editor (in ProvChartView), then
+ /* If provenance relationships have been modified by the provenance editor (in ProvChartView), then
update the ORE Resource Map and save it to the server.
*/
- saveProv: function () {
- var rdf = this.rdf;
- var graph = this.dataPackageGraph;
+ saveProv: function () {
+ var rdf = this.rdf;
+ var graph = this.dataPackageGraph;
- var provEdits = this.provEdits;
- if (!provEdits.length) {
- return;
- }
- var RDF = rdf.Namespace(this.namespaces.RDF),
- PROV = rdf.Namespace(this.namespaces.PROV),
- PROVONE = rdf.Namespace(this.namespaces.PROVONE),
- DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
- CITO = rdf.Namespace(this.namespaces.CITO),
- XSD = rdf.Namespace(this.namespaces.XSD);
+ var provEdits = this.provEdits;
+ if (!provEdits.length) {
+ return;
+ }
+ var RDF = rdf.Namespace(this.namespaces.RDF),
+ PROV = rdf.Namespace(this.namespaces.PROV),
+ PROVONE = rdf.Namespace(this.namespaces.PROVONE),
+ DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
+ CITO = rdf.Namespace(this.namespaces.CITO),
+ XSD = rdf.Namespace(this.namespaces.XSD);
- var cnResolveUrl = this.getCnURI();
+ var cnResolveUrl = this.getCnURI();
- /* Check if this package member had provenance relationships added
+ /* Check if this package member had provenance relationships added
or deleted by the provenance editor functionality of the ProvChartView
*/
- _.each(
- provEdits,
- function (edit) {
- var operation, subject, predicate, object;
- var provStatements;
- operation = edit[0];
- subject = edit[1];
- predicate = edit[2];
- object = edit[3];
-
- // The predicates of the provenance edits recorded by the ProvChartView
- // indicate which W3C PROV relationship has been recorded.
- // First check if this relationship alread exists in the RDF graph.
- // See DataPackage.parseProv for a description of how relationships from an ORE resource map
- // are parsed and stored in DataONEObjects. Here we are reversing the process, so may need
- // The representation of the PROVONE data model is simplified in the ProvChartView, to aid
- // legibility for users not familiar with the details of the PROVONE model. In this simplification,
- // a provone:Program has direct inputs and outputs. In the actual model, a prov:Execution has
- // inputs and outputs and is connected to a program via a prov:association. We must 'expand' the
- // simplified provenance updates recorded by the editor into the fully detailed representation
- // of the actual model.
- var executionId, executionURI, executionNode;
- var programId, programURI, programNode;
- var dataId, dataURI, dataNode;
- var derivedDataURI, derivedDataNode;
- var lastRef = false;
- //var graph = this.dataPackageGraph;
-
- //Create a node for the subject and object
- var subjectNode = rdf.sym(this.getURIFromRDF(subject)),
- objectNode = rdf.sym(this.getURIFromRDF(object));
-
- switch (predicate) {
- case "prov_wasDerivedFrom":
- derivedDataNode = subjectNode;
- dataNode = objectNode;
- if (operation == "add") {
- this.addToGraph(
- dataNode,
- RDF("type"),
- PROVONE("Data")
- );
- this.addToGraph(
- derivedDataNode,
- RDF("type"),
- PROVONE("Data")
- );
- this.addToGraph(
- derivedDataNode,
- PROV("wasDerivedFrom"),
- dataNode
- );
- } else {
- graph.removeMatches(
- derivedDataNode,
- PROV("wasDerivedFrom"),
- dataNode
- );
- this.removeIfLastProvRef(
- dataNode,
- RDF("type"),
- PROVONE("Data")
- );
- this.removeIfLastProvRef(
- derivedDataNode,
- RDF("type"),
- PROVONE("Data")
- );
- }
- break;
- case "prov_generatedByProgram":
- programId = object;
- dataNode = subjectNode;
- var removed = false;
- if (operation == "add") {
- // 'subject' is the program id, which is a simplification of the PROVONE model for display.
- // In the PROVONE model, execution 'uses' and input, and is associated with a program.
- executionId =
- this.addProgramToGraph(programId);
- //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId));
- executionNode =
- this.getExecutionNode(executionId);
- this.addToGraph(
- dataNode,
- RDF("type"),
- PROVONE("Data")
- );
- this.addToGraph(
- dataNode,
- PROV("wasGeneratedBy"),
- executionNode
- );
- } else {
- executionId =
- this.getExecutionId(programId);
- executionNode =
- this.getExecutionNode(executionId);
-
- graph.removeMatches(
- dataNode,
- PROV("wasGeneratedBy"),
- executionNode
- );
- removed =
- this.removeProgramFromGraph(programId);
- this.removeIfLastProvRef(
- dataNode,
- RDF("type"),
- PROVONE("Data")
- );
- }
- break;
- case "prov_usedByProgram":
- programId = object;
- dataNode = subjectNode;
- if (operation == "add") {
- // 'subject' is the program id, which is a simplification of the PROVONE model for display.
- // In the PROVONE model, execution 'uses' and input, and is associated with a program.
- executionId =
- this.addProgramToGraph(programId);
- //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId));
- executionNode =
- this.getExecutionNode(executionId);
- this.addToGraph(
- dataNode,
- RDF("type"),
- PROVONE("Data")
- );
- this.addToGraph(
- executionNode,
- PROV("used"),
- dataNode
- );
- } else {
- executionId =
- this.getExecutionId(programId);
- executionNode =
- this.getExecutionNode(executionId);
-
- graph.removeMatches(
- executionNode,
- PROV("used"),
- dataNode
- );
- removed =
- this.removeProgramFromGraph(programId);
- this.removeIfLastProvRef(
- dataNode,
- RDF("type"),
- PROVONE("Data")
- );
- }
- break;
- case "prov_hasDerivations":
- dataNode = subjectNode;
- derivedDataNode = objectNode;
- if (operation == "add") {
- this.addToGraph(
- dataNode,
- RDF("type"),
- PROVONE("Data")
- );
- this.addToGraph(
- derivedDataNode,
- RDF("type"),
- PROVONE("Data")
- );
- this.addToGraph(
- derivedDataNode,
- PROV("wasDerivedFrom"),
- dataNode
- );
- } else {
- graph.removeMatches(
- derivedDataNode,
- PROV("wasDerivedFrom"),
- dataNode
- );
- this.removeIfLastProvRef(
- dataNode,
- RDF("type"),
- PROVONE("Data")
- );
- this.removeIfLastProvRef(
- derivedDataNode,
- RDF("type"),
- PROVONE("Data")
- );
- }
- break;
- case "prov_instanceOfClass":
- var entityNode = subjectNode;
- var classNode = PROVONE(object);
- if (operation == "add") {
- this.addToGraph(
- entityNode,
- RDF("type"),
- classNode
- );
- } else {
- // Make sure there are no other references to this
- this.removeIfLastProvRef(
- entityNode,
- RDF("type"),
- classNode
- );
- }
- break;
- default:
- // Print error if predicate for prov edit not found.
- }
- },
- this
- );
-
- // When saving provenance only, we only have to save the Resource Map/Package object.
- // So we will send the resourceMapOnly flag with the save function.
- this.save({
- resourceMapOnly: true,
- });
- },
-
- /* Add the specified relationship to the RDF graph only if it
+ _.each(
+ provEdits,
+ function (edit) {
+ var operation, subject, predicate, object;
+ var provStatements;
+ operation = edit[0];
+ subject = edit[1];
+ predicate = edit[2];
+ object = edit[3];
+
+ // The predicates of the provenance edits recorded by the ProvChartView
+ // indicate which W3C PROV relationship has been recorded.
+ // First check if this relationship alread exists in the RDF graph.
+ // See DataPackage.parseProv for a description of how relationships from an ORE resource map
+ // are parsed and stored in DataONEObjects. Here we are reversing the process, so may need
+ // The representation of the PROVONE data model is simplified in the ProvChartView, to aid
+ // legibility for users not familiar with the details of the PROVONE model. In this simplification,
+ // a provone:Program has direct inputs and outputs. In the actual model, a prov:Execution has
+ // inputs and outputs and is connected to a program via a prov:association. We must 'expand' the
+ // simplified provenance updates recorded by the editor into the fully detailed representation
+ // of the actual model.
+ var executionId, executionURI, executionNode;
+ var programId, programURI, programNode;
+ var dataId, dataURI, dataNode;
+ var derivedDataURI, derivedDataNode;
+ var lastRef = false;
+ //var graph = this.dataPackageGraph;
+
+ //Create a node for the subject and object
+ var subjectNode = rdf.sym(this.getURIFromRDF(subject)),
+ objectNode = rdf.sym(this.getURIFromRDF(object));
+
+ switch (predicate) {
+ case "prov_wasDerivedFrom":
+ derivedDataNode = subjectNode;
+ dataNode = objectNode;
+ if (operation == "add") {
+ this.addToGraph(dataNode, RDF("type"), PROVONE("Data"));
+ this.addToGraph(
+ derivedDataNode,
+ RDF("type"),
+ PROVONE("Data"),
+ );
+ this.addToGraph(
+ derivedDataNode,
+ PROV("wasDerivedFrom"),
+ dataNode,
+ );
+ } else {
+ graph.removeMatches(
+ derivedDataNode,
+ PROV("wasDerivedFrom"),
+ dataNode,
+ );
+ this.removeIfLastProvRef(
+ dataNode,
+ RDF("type"),
+ PROVONE("Data"),
+ );
+ this.removeIfLastProvRef(
+ derivedDataNode,
+ RDF("type"),
+ PROVONE("Data"),
+ );
+ }
+ break;
+ case "prov_generatedByProgram":
+ programId = object;
+ dataNode = subjectNode;
+ var removed = false;
+ if (operation == "add") {
+ // 'subject' is the program id, which is a simplification of the PROVONE model for display.
+ // In the PROVONE model, execution 'uses' and input, and is associated with a program.
+ executionId = this.addProgramToGraph(programId);
+ //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId));
+ executionNode = this.getExecutionNode(executionId);
+ this.addToGraph(dataNode, RDF("type"), PROVONE("Data"));
+ this.addToGraph(
+ dataNode,
+ PROV("wasGeneratedBy"),
+ executionNode,
+ );
+ } else {
+ executionId = this.getExecutionId(programId);
+ executionNode = this.getExecutionNode(executionId);
+
+ graph.removeMatches(
+ dataNode,
+ PROV("wasGeneratedBy"),
+ executionNode,
+ );
+ removed = this.removeProgramFromGraph(programId);
+ this.removeIfLastProvRef(
+ dataNode,
+ RDF("type"),
+ PROVONE("Data"),
+ );
+ }
+ break;
+ case "prov_usedByProgram":
+ programId = object;
+ dataNode = subjectNode;
+ if (operation == "add") {
+ // 'subject' is the program id, which is a simplification of the PROVONE model for display.
+ // In the PROVONE model, execution 'uses' and input, and is associated with a program.
+ executionId = this.addProgramToGraph(programId);
+ //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId));
+ executionNode = this.getExecutionNode(executionId);
+ this.addToGraph(dataNode, RDF("type"), PROVONE("Data"));
+ this.addToGraph(executionNode, PROV("used"), dataNode);
+ } else {
+ executionId = this.getExecutionId(programId);
+ executionNode = this.getExecutionNode(executionId);
+
+ graph.removeMatches(executionNode, PROV("used"), dataNode);
+ removed = this.removeProgramFromGraph(programId);
+ this.removeIfLastProvRef(
+ dataNode,
+ RDF("type"),
+ PROVONE("Data"),
+ );
+ }
+ break;
+ case "prov_hasDerivations":
+ dataNode = subjectNode;
+ derivedDataNode = objectNode;
+ if (operation == "add") {
+ this.addToGraph(dataNode, RDF("type"), PROVONE("Data"));
+ this.addToGraph(
+ derivedDataNode,
+ RDF("type"),
+ PROVONE("Data"),
+ );
+ this.addToGraph(
+ derivedDataNode,
+ PROV("wasDerivedFrom"),
+ dataNode,
+ );
+ } else {
+ graph.removeMatches(
+ derivedDataNode,
+ PROV("wasDerivedFrom"),
+ dataNode,
+ );
+ this.removeIfLastProvRef(
+ dataNode,
+ RDF("type"),
+ PROVONE("Data"),
+ );
+ this.removeIfLastProvRef(
+ derivedDataNode,
+ RDF("type"),
+ PROVONE("Data"),
+ );
+ }
+ break;
+ case "prov_instanceOfClass":
+ var entityNode = subjectNode;
+ var classNode = PROVONE(object);
+ if (operation == "add") {
+ this.addToGraph(entityNode, RDF("type"), classNode);
+ } else {
+ // Make sure there are no other references to this
+ this.removeIfLastProvRef(entityNode, RDF("type"), classNode);
+ }
+ break;
+ default:
+ // Print error if predicate for prov edit not found.
+ }
+ },
+ this,
+ );
+
+ // When saving provenance only, we only have to save the Resource Map/Package object.
+ // So we will send the resourceMapOnly flag with the save function.
+ this.save({
+ resourceMapOnly: true,
+ });
+ },
+
+ /* Add the specified relationship to the RDF graph only if it
has not already been added. */
- addToGraph: function (subject, predicate, object) {
- var graph = this.dataPackageGraph;
- var statements = graph.statementsMatching(
- subject,
- predicate,
- object
- );
+ addToGraph: function (subject, predicate, object) {
+ var graph = this.dataPackageGraph;
+ var statements = graph.statementsMatching(subject, predicate, object);
- if (!statements.length) {
- graph.add(subject, predicate, object);
- }
- },
+ if (!statements.length) {
+ graph.add(subject, predicate, object);
+ }
+ },
- /* Remove the statement fromn the RDF graph only if the subject of this
+ /* Remove the statement fromn the RDF graph only if the subject of this
relationship is not referenced by any other provenance relationship, i.e.
for example, the prov relationship "id rdf:type provone:data" is only
needed if the subject ('id') is referenced in another relationship.
Also don't remove it if the subject is in any other prov statement,
meaning it still references another prov object.
*/
- removeIfLastProvRef: function (
- subjectNode,
- predicateNode,
- objectNode
- ) {
- var graph = this.dataPackageGraph;
- var stillUsed = false;
- var PROV = rdf.Namespace(this.namespaces.PROV);
- var PROVONE = rdf.Namespace(this.namespaces.PROVONE);
- // PROV namespace value, used to identify PROV statements
- var provStr = PROV("").value;
- // PROVONE namespace value, used to identify PROVONE statements
- var provoneStr = PROVONE("").value;
- // Get the statements from the RDF graph that reference the subject of the
- // statement to remove.
- var statements = graph.statementsMatching(
- undefined,
- undefined,
- subjectNode
- );
-
- var found = _.find(
- statements,
- function (statement) {
- if (
- statement.subject == subjectNode &&
- statement.predicate == predicateNode &&
- statement.object == objectNode
- )
- return false;
-
- var pVal = statement.predicate.value;
-
- // Now check if the subject is referenced in a prov statement
- // There is another statement that references the subject of the
- // statement to remove, so it is still being used and don't
- // remove it.
- if (pVal.indexOf(provStr) != -1) return true;
- if (pVal.indexOf(provoneStr) != -1) return true;
- return false;
- },
- this
- );
-
- // IF not found in the first test, keep looking.
- if (typeof found == "undefined") {
- // Get the statements from the RDF where
- var statements = graph.statementsMatching(
- subjectNode,
- undefined,
- undefined
- );
-
- found = _.find(
- statements,
- function (statement) {
- if (
- statement.subject == subjectNode &&
- statement.predicate == predicateNode &&
- statement.object == objectNode
- )
- return false;
- var pVal = statement.predicate.value;
-
- // Now check if the subject is referenced in a prov statement
- if (pVal.indexOf(provStr) != -1) return true;
- if (pVal.indexOf(provoneStr) != -1) return true;
- // There is another statement that references the subject of the
- // statement to remove, so it is still being used and don't
- // remove it.
- return false;
- },
- this
- );
- }
-
- // The specified statement term isn't being used for prov, so remove it.
- if (typeof found == "undefined") {
- graph.removeMatches(
- subjectNode,
- predicateNode,
- objectNode,
- undefined
- );
- }
+ removeIfLastProvRef: function (subjectNode, predicateNode, objectNode) {
+ var graph = this.dataPackageGraph;
+ var stillUsed = false;
+ var PROV = rdf.Namespace(this.namespaces.PROV);
+ var PROVONE = rdf.Namespace(this.namespaces.PROVONE);
+ // PROV namespace value, used to identify PROV statements
+ var provStr = PROV("").value;
+ // PROVONE namespace value, used to identify PROVONE statements
+ var provoneStr = PROVONE("").value;
+ // Get the statements from the RDF graph that reference the subject of the
+ // statement to remove.
+ var statements = graph.statementsMatching(
+ undefined,
+ undefined,
+ subjectNode,
+ );
+
+ var found = _.find(
+ statements,
+ function (statement) {
+ if (
+ statement.subject == subjectNode &&
+ statement.predicate == predicateNode &&
+ statement.object == objectNode
+ )
+ return false;
+
+ var pVal = statement.predicate.value;
+
+ // Now check if the subject is referenced in a prov statement
+ // There is another statement that references the subject of the
+ // statement to remove, so it is still being used and don't
+ // remove it.
+ if (pVal.indexOf(provStr) != -1) return true;
+ if (pVal.indexOf(provoneStr) != -1) return true;
+ return false;
+ },
+ this,
+ );
+
+ // IF not found in the first test, keep looking.
+ if (typeof found == "undefined") {
+ // Get the statements from the RDF where
+ var statements = graph.statementsMatching(
+ subjectNode,
+ undefined,
+ undefined,
+ );
+
+ found = _.find(
+ statements,
+ function (statement) {
+ if (
+ statement.subject == subjectNode &&
+ statement.predicate == predicateNode &&
+ statement.object == objectNode
+ )
+ return false;
+ var pVal = statement.predicate.value;
+
+ // Now check if the subject is referenced in a prov statement
+ if (pVal.indexOf(provStr) != -1) return true;
+ if (pVal.indexOf(provoneStr) != -1) return true;
+ // There is another statement that references the subject of the
+ // statement to remove, so it is still being used and don't
+ // remove it.
+ return false;
},
+ this,
+ );
+ }
- /**
- * Remove orphaned blank nodes from the model's current graph
- *
- * This was put in to support replacing package members who are
- * referenced by provenance statements, specifically members typed as
- * Programs. rdflib.js will throw an error when serializing if any
- * statements in the graph have objects that are blank nodes when no
- * other statements in the graph have subjects for the same blank node.
- * i.e., blank nodes references that aren't defined.
- *
- * Should be called during a call to serialize() and mutates
- * this.dataPackageGraph directly as a side-effect.
- */
- removeOrphanedBlankNodes: function () {
- if (
- !this.dataPackageGraph ||
- !this.dataPackageGraph.statements
- ) {
- return;
- }
-
- // Collect an array of statements to be removed
- var toRemove = [];
-
- _.each(
- this.dataPackageGraph.statements,
- function (statement) {
- if (statement.object.termType !== "BlankNode") {
- return;
- }
-
- // For this statement, look for other statments about it
- var matches = 0;
-
- _.each(
- this.dataPackageGraph.statements,
- function (other) {
- if (
- other.subject.termType === "BlankNode" &&
- other.subject.id === statement.object.id
- ) {
- matches += 1;
- }
- }
- );
-
- // If none are found, add it to our list
- if (matches === 0) {
- toRemove.push(statement);
- }
- },
- this
- );
-
- // Remove collected statements
- _.each(
- toRemove,
- function (statement) {
- this.dataPackageGraph.removeStatement(statement);
- },
- this
- );
- },
+ // The specified statement term isn't being used for prov, so remove it.
+ if (typeof found == "undefined") {
+ graph.removeMatches(
+ subjectNode,
+ predicateNode,
+ objectNode,
+ undefined,
+ );
+ }
+ },
+
+ /**
+ * Remove orphaned blank nodes from the model's current graph
+ *
+ * This was put in to support replacing package members who are
+ * referenced by provenance statements, specifically members typed as
+ * Programs. rdflib.js will throw an error when serializing if any
+ * statements in the graph have objects that are blank nodes when no
+ * other statements in the graph have subjects for the same blank node.
+ * i.e., blank nodes references that aren't defined.
+ *
+ * Should be called during a call to serialize() and mutates
+ * this.dataPackageGraph directly as a side-effect.
+ */
+ removeOrphanedBlankNodes: function () {
+ if (!this.dataPackageGraph || !this.dataPackageGraph.statements) {
+ return;
+ }
- /* Get the execution identifier that is associated with a program id.
+ // Collect an array of statements to be removed
+ var toRemove = [];
+
+ _.each(
+ this.dataPackageGraph.statements,
+ function (statement) {
+ if (statement.object.termType !== "BlankNode") {
+ return;
+ }
+
+ // For this statement, look for other statments about it
+ var matches = 0;
+
+ _.each(this.dataPackageGraph.statements, function (other) {
+ if (
+ other.subject.termType === "BlankNode" &&
+ other.subject.id === statement.object.id
+ ) {
+ matches += 1;
+ }
+ });
+
+ // If none are found, add it to our list
+ if (matches === 0) {
+ toRemove.push(statement);
+ }
+ },
+ this,
+ );
+
+ // Remove collected statements
+ _.each(
+ toRemove,
+ function (statement) {
+ this.dataPackageGraph.removeStatement(statement);
+ },
+ this,
+ );
+ },
+
+ /* Get the execution identifier that is associated with a program id.
This will either be in the 'prov_wasExecutedByExecution' of the package member
for the program script, or available by tracing backward in the RDF graph from
the program node, through the assocation to the related execution.
*/
- getExecutionId: function (programId) {
- var rdf = this.rdf;
- var graph = this.dataPackageGraph;
- var stmts = null;
- var cnResolveUrl = this.getCnURI();
- var RDF = rdf.Namespace(this.namespaces.RDF),
- DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
- PROV = rdf.Namespace(this.namespaces.PROV),
- PROVONE = rdf.Namespace(this.namespaces.PROVONE);
-
- var member = this.get(programId);
- var executionId = member.get("prov_wasExecutedByExecution");
- if (executionId.length > 0) {
- return executionId[0];
- } else {
- var programNode = rdf.sym(this.getURIFromRDF(programId));
- // Get the executionId from the RDF graph
- // There can be only one plan for an association
- stmts = graph.statementsMatching(
- undefined,
- PROV("hadPlan"),
- programNode
- );
- if (typeof stmts == "undefined") return null;
- var associationNode = stmts[0].subject;
- // There should be only one execution for this assocation.
- stmts = graph.statementsMatching(
- undefined,
- PROV("qualifiedAssociation"),
- associationNode
- );
- if (typeof stmts == "undefined") return null;
- return stmts[0].subject;
- }
- },
+ getExecutionId: function (programId) {
+ var rdf = this.rdf;
+ var graph = this.dataPackageGraph;
+ var stmts = null;
+ var cnResolveUrl = this.getCnURI();
+ var RDF = rdf.Namespace(this.namespaces.RDF),
+ DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
+ PROV = rdf.Namespace(this.namespaces.PROV),
+ PROVONE = rdf.Namespace(this.namespaces.PROVONE);
+
+ var member = this.get(programId);
+ var executionId = member.get("prov_wasExecutedByExecution");
+ if (executionId.length > 0) {
+ return executionId[0];
+ } else {
+ var programNode = rdf.sym(this.getURIFromRDF(programId));
+ // Get the executionId from the RDF graph
+ // There can be only one plan for an association
+ stmts = graph.statementsMatching(
+ undefined,
+ PROV("hadPlan"),
+ programNode,
+ );
+ if (typeof stmts == "undefined") return null;
+ var associationNode = stmts[0].subject;
+ // There should be only one execution for this assocation.
+ stmts = graph.statementsMatching(
+ undefined,
+ PROV("qualifiedAssociation"),
+ associationNode,
+ );
+ if (typeof stmts == "undefined") return null;
+ return stmts[0].subject;
+ }
+ },
- /* Get the RDF node for an execution that is associated with the execution identifier.
+ /* Get the RDF node for an execution that is associated with the execution identifier.
The execution may have been created in the resource map as a 'bare' urn:uuid
(no resolveURI), or as a resolve URL, so check for both until the id is
found.
*/
- getExecutionNode: function (executionId) {
- var rdf = this.rdf;
- var graph = this.dataPackageGraph;
- var stmts = null;
- var testNode = null;
- var cnResolveUrl = this.getCnURI();
-
- // First see if the execution exists in the RDF graph as a 'bare' idenfier, i.e.
- // a 'urn:uuid'.
- stmts = graph.statementsMatching(
- rdf.sym(executionId),
- undefined,
- undefined
- );
- if (typeof stmts == "undefined" || !stmts.length) {
- // The execution node as urn was not found, look for fully qualified version.
- testNode = rdf.sym(this.getURIFromRDF(executionId));
- stmts = graph.statementsMatching(
- rdf.sym(executionId),
- undefined,
- undefined
- );
- if (typeof stmts == "undefined") {
- // Couldn't find the execution, return the standard RDF node value
- executionNode = rdf.sym(
- this.getURIFromRDF(executionId)
- );
- return executionNode;
- } else {
- return testNode;
- }
- } else {
- // The executionNode was found in the RDF graph as a urn
- var executionNode = stmts[0].subject;
- return executionNode;
- }
- },
-
- addProgramToGraph: function (programId) {
- var rdf = this.rdf;
- var graph = this.dataPackageGraph;
- var RDF = rdf.Namespace(this.namespaces.RDF),
- DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
- PROV = rdf.Namespace(this.namespaces.PROV),
- PROVONE = rdf.Namespace(this.namespaces.PROVONE),
- XSD = rdf.Namespace(this.namespaces.XSD);
- var member = this.get(programId);
- var executionId = member.get("prov_wasExecutedByExecution");
- var executionNode = null;
- var programNode = null;
- var associationId = null;
- var associationNode = null;
- var cnResolveUrl = this.getCnURI();
-
- if (!executionId.length) {
- // This is a new execution, so create new execution and association ids
- executionId = "urn:uuid:" + uuid.v4();
- member.set("prov_wasExecutedByExecution", [executionId]);
- // Blank node id. RDF validator doesn't like ':' so don't use in the id
- //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId));
- executionNode = this.getExecutionNode(executionId);
- //associationId = "_" + uuid.v4();
- associationNode = graph.bnode();
- } else {
- executionId = executionId[0];
- // Check if an association exists in the RDF graph for this execution id
- //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId));
- executionNode = this.getExecutionNode(executionId);
- // Check if there is an association id for this execution.
- // If this execution is newly created (via the editor (existing would
- // be parsed from the resmap), then create a new association id.
- var stmts = graph.statementsMatching(
- executionNode,
- PROV("qualifiedAssociation"),
- undefined
- );
- // IF an associati on was found, then use it, else geneate a new one
- // (Associations aren't stored in the )
- if (stmts.length) {
- associationNode = stmts[0].object;
- //associationId = stmts[0].object.value;
- } else {
- //associationId = "_" + uuid.v4();
- associationNode = graph.bnode();
- }
- }
- //associationNode = graph.bnode(associationId);
- //associationNode = graph.bnode();
- programNode = rdf.sym(this.getURIFromRDF(programId));
- try {
- this.addToGraph(
- executionNode,
- PROV("qualifiedAssociation"),
- associationNode
- );
- this.addToGraph(
- executionNode,
- RDF("type"),
- PROVONE("Execution")
- );
- this.addToGraph(
- executionNode,
- DCTERMS("identifier"),
- rdf.literal(executionId, undefined, XSD("string"))
- );
- this.addToGraph(
- associationNode,
- PROV("hadPlan"),
- programNode
- );
- this.addToGraph(
- programNode,
- RDF("type"),
- PROVONE("Program")
- );
- } catch (error) {
- console.log(error);
- }
- return executionId;
- },
-
- // Remove a program identifier from the RDF graph and remove associated
- // linkage between the program id and the exection, if the execution is not
- // being used by any other statements.
- removeProgramFromGraph: function (programId) {
- var graph = this.dataPackageGraph;
- var rdf = this.rdf;
- var stmts = null;
- var cnResolveUrl = this.getCnURI();
- var RDF = rdf.Namespace(this.namespaces.RDF),
- DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
- PROV = rdf.Namespace(this.namespaces.PROV),
- PROVONE = rdf.Namespace(this.namespaces.PROVONE),
- XSD = rdf.Namespace(this.namespaces.XSD);
- var associationNode = null;
-
- var executionId = this.getExecutionId(programId);
- if (executionId == null) return false;
-
- //var executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId));
- var executionNode = this.getExecutionNode(executionId);
- var programNode = rdf.sym(this.getURIFromRDF(programId));
-
- // In order to remove this program from the graph, we have to first determine that
- // nothing else is using the execution that is associated with the program (the plan).
- // There may be additional 'used', 'geneated', 'qualifiedGeneration', etc. items that
- // may be pointing to the execution. If yes, then don't delete the execution or the
- // program (the execution's plan).
- try {
- // Is the program in the graph? If the program is not in the graph, then
- // we don't know how to remove the proper execution and assocation.
- stmts = graph.statementsMatching(
- undefined,
- undefined,
- programNode
- );
- if (typeof stmts == "undefined" || !stmts.length)
- return false;
+ getExecutionNode: function (executionId) {
+ var rdf = this.rdf;
+ var graph = this.dataPackageGraph;
+ var stmts = null;
+ var testNode = null;
+ var cnResolveUrl = this.getCnURI();
+
+ // First see if the execution exists in the RDF graph as a 'bare' idenfier, i.e.
+ // a 'urn:uuid'.
+ stmts = graph.statementsMatching(
+ rdf.sym(executionId),
+ undefined,
+ undefined,
+ );
+ if (typeof stmts == "undefined" || !stmts.length) {
+ // The execution node as urn was not found, look for fully qualified version.
+ testNode = rdf.sym(this.getURIFromRDF(executionId));
+ stmts = graph.statementsMatching(
+ rdf.sym(executionId),
+ undefined,
+ undefined,
+ );
+ if (typeof stmts == "undefined") {
+ // Couldn't find the execution, return the standard RDF node value
+ executionNode = rdf.sym(this.getURIFromRDF(executionId));
+ return executionNode;
+ } else {
+ return testNode;
+ }
+ } else {
+ // The executionNode was found in the RDF graph as a urn
+ var executionNode = stmts[0].subject;
+ return executionNode;
+ }
+ },
+
+ addProgramToGraph: function (programId) {
+ var rdf = this.rdf;
+ var graph = this.dataPackageGraph;
+ var RDF = rdf.Namespace(this.namespaces.RDF),
+ DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
+ PROV = rdf.Namespace(this.namespaces.PROV),
+ PROVONE = rdf.Namespace(this.namespaces.PROVONE),
+ XSD = rdf.Namespace(this.namespaces.XSD);
+ var member = this.get(programId);
+ var executionId = member.get("prov_wasExecutedByExecution");
+ var executionNode = null;
+ var programNode = null;
+ var associationId = null;
+ var associationNode = null;
+ var cnResolveUrl = this.getCnURI();
+
+ if (!executionId.length) {
+ // This is a new execution, so create new execution and association ids
+ executionId = "urn:uuid:" + uuid.v4();
+ member.set("prov_wasExecutedByExecution", [executionId]);
+ // Blank node id. RDF validator doesn't like ':' so don't use in the id
+ //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId));
+ executionNode = this.getExecutionNode(executionId);
+ //associationId = "_" + uuid.v4();
+ associationNode = graph.bnode();
+ } else {
+ executionId = executionId[0];
+ // Check if an association exists in the RDF graph for this execution id
+ //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId));
+ executionNode = this.getExecutionNode(executionId);
+ // Check if there is an association id for this execution.
+ // If this execution is newly created (via the editor (existing would
+ // be parsed from the resmap), then create a new association id.
+ var stmts = graph.statementsMatching(
+ executionNode,
+ PROV("qualifiedAssociation"),
+ undefined,
+ );
+ // IF an associati on was found, then use it, else geneate a new one
+ // (Associations aren't stored in the )
+ if (stmts.length) {
+ associationNode = stmts[0].object;
+ //associationId = stmts[0].object.value;
+ } else {
+ //associationId = "_" + uuid.v4();
+ associationNode = graph.bnode();
+ }
+ }
+ //associationNode = graph.bnode(associationId);
+ //associationNode = graph.bnode();
+ programNode = rdf.sym(this.getURIFromRDF(programId));
+ try {
+ this.addToGraph(
+ executionNode,
+ PROV("qualifiedAssociation"),
+ associationNode,
+ );
+ this.addToGraph(executionNode, RDF("type"), PROVONE("Execution"));
+ this.addToGraph(
+ executionNode,
+ DCTERMS("identifier"),
+ rdf.literal(executionId, undefined, XSD("string")),
+ );
+ this.addToGraph(associationNode, PROV("hadPlan"), programNode);
+ this.addToGraph(programNode, RDF("type"), PROVONE("Program"));
+ } catch (error) {
+ console.log(error);
+ }
+ return executionId;
+ },
+
+ // Remove a program identifier from the RDF graph and remove associated
+ // linkage between the program id and the exection, if the execution is not
+ // being used by any other statements.
+ removeProgramFromGraph: function (programId) {
+ var graph = this.dataPackageGraph;
+ var rdf = this.rdf;
+ var stmts = null;
+ var cnResolveUrl = this.getCnURI();
+ var RDF = rdf.Namespace(this.namespaces.RDF),
+ DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
+ PROV = rdf.Namespace(this.namespaces.PROV),
+ PROVONE = rdf.Namespace(this.namespaces.PROVONE),
+ XSD = rdf.Namespace(this.namespaces.XSD);
+ var associationNode = null;
+
+ var executionId = this.getExecutionId(programId);
+ if (executionId == null) return false;
+
+ //var executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId));
+ var executionNode = this.getExecutionNode(executionId);
+ var programNode = rdf.sym(this.getURIFromRDF(programId));
+
+ // In order to remove this program from the graph, we have to first determine that
+ // nothing else is using the execution that is associated with the program (the plan).
+ // There may be additional 'used', 'geneated', 'qualifiedGeneration', etc. items that
+ // may be pointing to the execution. If yes, then don't delete the execution or the
+ // program (the execution's plan).
+ try {
+ // Is the program in the graph? If the program is not in the graph, then
+ // we don't know how to remove the proper execution and assocation.
+ stmts = graph.statementsMatching(undefined, undefined, programNode);
+ if (typeof stmts == "undefined" || !stmts.length) return false;
+
+ // Is anything else linked to this execution?
+ stmts = graph.statementsMatching(executionNode, PROV("used"));
+ if (!typeof stmts == "undefined" || stmts.length) return false;
+ stmts = graph.statementsMatching(
+ undefined,
+ PROV("wasGeneratedBy"),
+ executionNode,
+ );
+ if (!typeof stmts == "undefined" || stmts.length) return false;
+ stmts = graph.statementsMatching(
+ executionNode,
+ PROV("qualifiedGeneration"),
+ undefined,
+ );
+ if (!typeof stmts == "undefined" || stmts.length) return false;
+ stmts = graph.statementsMatching(
+ undefined,
+ PROV("wasInformedBy"),
+ executionNode,
+ );
+ if (!typeof stmts == "undefined" || stmts.length) return false;
+ stmts = graph.statementsMatching(
+ undefined,
+ PROV("wasPartOf"),
+ executionNode,
+ );
+ if (!typeof stmts == "undefined" || stmts.length) return false;
+
+ // get association
+ stmts = graph.statementsMatching(
+ undefined,
+ PROV("hadPlan"),
+ programNode,
+ );
+ associationNode = stmts[0].subject;
+ } catch (error) {
+ console.log(error);
+ }
- // Is anything else linked to this execution?
- stmts = graph.statementsMatching(
- executionNode,
- PROV("used")
- );
- if (!typeof stmts == "undefined" || stmts.length)
- return false;
- stmts = graph.statementsMatching(
- undefined,
- PROV("wasGeneratedBy"),
- executionNode
- );
- if (!typeof stmts == "undefined" || stmts.length)
- return false;
- stmts = graph.statementsMatching(
- executionNode,
- PROV("qualifiedGeneration"),
- undefined
- );
- if (!typeof stmts == "undefined" || stmts.length)
- return false;
- stmts = graph.statementsMatching(
- undefined,
- PROV("wasInformedBy"),
- executionNode
- );
- if (!typeof stmts == "undefined" || stmts.length)
- return false;
- stmts = graph.statementsMatching(
- undefined,
- PROV("wasPartOf"),
- executionNode
- );
- if (!typeof stmts == "undefined" || stmts.length)
- return false;
-
- // get association
- stmts = graph.statementsMatching(
- undefined,
- PROV("hadPlan"),
- programNode
- );
- associationNode = stmts[0].subject;
- } catch (error) {
- console.log(error);
- }
+ // The execution isn't needed any longer, so remove it and the program.
+ try {
+ graph.removeMatches(programNode, RDF("type"), PROVONE("Program"));
+ graph.removeMatches(associationNode, PROV("hadPlan"), programNode);
+ graph.removeMatches(
+ associationNode,
+ RDF("type"),
+ PROV("Association"),
+ );
+ graph.removeMatches(associationNode, PROV("Agent"), undefined);
+ graph.removeMatches(executionNode, RDF("type"), PROVONE("Execution"));
+ graph.removeMatches(
+ executionNode,
+ DCTERMS("identifier"),
+ rdf.literal(executionId, undefined, XSD("string")),
+ );
+ graph.removeMatches(
+ executionNode,
+ PROV("qualifiedAssociation"),
+ associationNode,
+ );
+ } catch (error) {
+ console.log(error);
+ }
+ return true;
+ },
- // The execution isn't needed any longer, so remove it and the program.
- try {
- graph.removeMatches(
- programNode,
- RDF("type"),
- PROVONE("Program")
- );
- graph.removeMatches(
- associationNode,
- PROV("hadPlan"),
- programNode
- );
- graph.removeMatches(
- associationNode,
- RDF("type"),
- PROV("Association")
- );
- graph.removeMatches(
- associationNode,
- PROV("Agent"),
- undefined
- );
- graph.removeMatches(
- executionNode,
- RDF("type"),
- PROVONE("Execution")
- );
- graph.removeMatches(
- executionNode,
- DCTERMS("identifier"),
- rdf.literal(executionId, undefined, XSD("string"))
- );
- graph.removeMatches(
- executionNode,
- PROV("qualifiedAssociation"),
- associationNode
- );
- } catch (error) {
- console.log(error);
- }
- return true;
+ /*
+ * Serialize the DataPackage to OAI-ORE RDF XML
+ */
+ serialize: function () {
+ //Create an RDF serializer
+ var serializer = this.rdf.Serializer(),
+ oldPidVariations,
+ modifiedDate,
+ subjectClone,
+ predicateClone,
+ objectClone;
+
+ serializer.store = this.dataPackageGraph;
+
+ //Define the namespaces
+ var ORE = this.rdf.Namespace(this.namespaces.ORE),
+ CITO = this.rdf.Namespace(this.namespaces.CITO),
+ DC = this.rdf.Namespace(this.namespaces.DC),
+ DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS),
+ FOAF = this.rdf.Namespace(this.namespaces.FOAF),
+ RDF = this.rdf.Namespace(this.namespaces.RDF),
+ XSD = this.rdf.Namespace(this.namespaces.XSD);
+
+ //Get the pid of this package - depends on whether we are updating or creating a resource map
+ var pid = this.packageModel.get("id"),
+ oldPid = this.packageModel.get("oldPid"),
+ cnResolveUrl = this.getCnURI();
+
+ //Get a list of the model pids that should be aggregated by this package
+ var idsFromModel = [];
+ this.each(function (packageMember) {
+ //If this object isn't done uploading, don't aggregate it.
+ //Or if it failed to upload, don't aggregate it.
+ //But if the system metadata failed to update, it can still be aggregated.
+ if (
+ packageMember.get("uploadStatus") !== "p" ||
+ packageMember.get("uploadStatus") !== "e" ||
+ packageMember.get("sysMetaUploadStatus") == "e"
+ ) {
+ idsFromModel.push(packageMember.get("id"));
+ }
+ });
+
+ this.idsToAggregate = idsFromModel;
+
+ //Update the pids in the RDF graph only if we are updating the resource map with a new pid
+ if (!this.packageModel.isNew()) {
+ // Remove all describes/isDescribedBy statements (they'll be rebuilt)
+ this.dataPackageGraph.removeMany(
+ undefined,
+ ORE("describes"),
+ undefined,
+ undefined,
+ undefined,
+ );
+ this.dataPackageGraph.removeMany(
+ undefined,
+ ORE("isDescribedBy"),
+ undefined,
+ undefined,
+ undefined,
+ );
+
+ //Create variations of the resource map ID using the resolve URL so we can always find it in the RDF graph
+ oldPidVariations = [
+ oldPid,
+ encodeURIComponent(oldPid),
+ cnResolveUrl + oldPid,
+ cnResolveUrl + encodeURIComponent(oldPid),
+ this.getURIFromRDF(oldPid),
+ ];
+
+ //Using the isAggregatedBy statements, find all the DataONE object ids in the RDF graph
+ var idsFromXML = [];
+
+ var identifierStatements = this.dataPackageGraph.statementsMatching(
+ undefined,
+ DCTERMS("identifier"),
+ undefined,
+ );
+ _.each(
+ identifierStatements,
+ function (statement) {
+ idsFromXML.push(
+ statement.object.value,
+ encodeURIComponent(statement.object.value),
+ cnResolveUrl + encodeURIComponent(statement.object.value),
+ cnResolveUrl + statement.object.value,
+ );
},
-
- /*
- * Serialize the DataPackage to OAI-ORE RDF XML
- */
- serialize: function () {
- //Create an RDF serializer
- var serializer = this.rdf.Serializer(),
- oldPidVariations,
- modifiedDate,
- subjectClone,
- predicateClone,
- objectClone;
-
- serializer.store = this.dataPackageGraph;
-
- //Define the namespaces
- var ORE = this.rdf.Namespace(this.namespaces.ORE),
- CITO = this.rdf.Namespace(this.namespaces.CITO),
- DC = this.rdf.Namespace(this.namespaces.DC),
- DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS),
- FOAF = this.rdf.Namespace(this.namespaces.FOAF),
- RDF = this.rdf.Namespace(this.namespaces.RDF),
- XSD = this.rdf.Namespace(this.namespaces.XSD);
-
- //Get the pid of this package - depends on whether we are updating or creating a resource map
- var pid = this.packageModel.get("id"),
- oldPid = this.packageModel.get("oldPid"),
- cnResolveUrl = this.getCnURI();
-
- //Get a list of the model pids that should be aggregated by this package
- var idsFromModel = [];
- this.each(function (packageMember) {
- //If this object isn't done uploading, don't aggregate it.
- //Or if it failed to upload, don't aggregate it.
- //But if the system metadata failed to update, it can still be aggregated.
- if (
- packageMember.get("uploadStatus") !== "p" ||
- packageMember.get("uploadStatus") !== "e" ||
- packageMember.get("sysMetaUploadStatus") == "e"
- ) {
- idsFromModel.push(packageMember.get("id"));
- }
- });
-
- this.idsToAggregate = idsFromModel;
-
- //Update the pids in the RDF graph only if we are updating the resource map with a new pid
- if (!this.packageModel.isNew()) {
- // Remove all describes/isDescribedBy statements (they'll be rebuilt)
- this.dataPackageGraph.removeMany(
- undefined,
- ORE("describes"),
- undefined,
- undefined,
- undefined
- );
- this.dataPackageGraph.removeMany(
- undefined,
- ORE("isDescribedBy"),
- undefined,
- undefined,
- undefined
- );
-
- //Create variations of the resource map ID using the resolve URL so we can always find it in the RDF graph
- oldPidVariations = [
- oldPid,
- encodeURIComponent(oldPid),
- cnResolveUrl + oldPid,
- cnResolveUrl + encodeURIComponent(oldPid),
- this.getURIFromRDF(oldPid),
- ];
-
- //Using the isAggregatedBy statements, find all the DataONE object ids in the RDF graph
- var idsFromXML = [];
-
- var identifierStatements =
- this.dataPackageGraph.statementsMatching(
- undefined,
- DCTERMS("identifier"),
- undefined
- );
- _.each(
- identifierStatements,
- function (statement) {
- idsFromXML.push(
- statement.object.value,
- encodeURIComponent(statement.object.value),
- cnResolveUrl +
- encodeURIComponent(statement.object.value),
- cnResolveUrl + statement.object.value
- );
- },
- this
- );
-
- //Get all the child package ids
- var childPackages = this.packageModel.get("childPackages");
- if (typeof childPackages == "object") {
- idsFromModel = _.union(
- idsFromModel,
- Object.keys(childPackages)
- );
- }
-
- //Find the difference between the model IDs and the XML IDs to get a list of added members
- var addedIds = _.without(
- _.difference(idsFromModel, idsFromXML),
- oldPidVariations
- );
-
- //Start an array to track all the member id variations
- var allMemberIds = idsFromModel;
-
- //Add the ids with the CN Resolve URLs
- _.each(idsFromModel, function (id) {
- allMemberIds.push(
- cnResolveUrl + encodeURIComponent(id),
- cnResolveUrl + id,
- encodeURIComponent(id)
- );
- });
-
- //Find the identifier statement in the resource map
- var idNode = this.rdf.lit(oldPid);
- var idStatements = this.dataPackageGraph.statementsMatching(
- undefined,
- undefined,
- idNode
- );
-
- //Change all the resource map identifier literal node in the RDF graph
- if (idStatements.length) {
- var idStatement = idStatements[0];
-
- //Remove the identifier statement
- try {
- this.dataPackageGraph.remove(idStatement);
- } catch (error) {
- console.log(error);
- }
-
- //Replace the id in the subject URI with the new id
- var newRMapURI = "";
- if (idStatement.subject.value.indexOf(oldPid) > -1) {
- newRMapURI = idStatement.subject.value.replace(
- oldPid,
- pid
- );
- } else if (
- idStatement.subject.value.indexOf(
- encodeURIComponent(oldPid)
- ) > -1
- ) {
- newRMapURI = idStatement.subject.value.replace(
- encodeURIComponent(oldPid),
- encodeURIComponent(pid)
- );
- }
-
- //Create resource map nodes for the subject and object
- var rMapNode = this.rdf.sym(newRMapURI),
- rMapIdNode = this.rdf.lit(pid);
- //Add the triple for the resource map id
- this.dataPackageGraph.add(
- rMapNode,
- DCTERMS("identifier"),
- rMapIdNode
- );
- }
-
- //Get all the isAggregatedBy statements
- var aggByStatements = $.extend(
- true,
- [],
- this.dataPackageGraph.statementsMatching(
- undefined,
- ORE("isAggregatedBy")
- )
- );
-
- // Remove any other isAggregatedBy statements that are not listed as members of this model
- _.each(
- aggByStatements,
- function (statement) {
- if (
- !_.contains(
- allMemberIds,
- statement.subject.value
- )
- ) {
- this.removeFromAggregation(
- statement.subject.value
- );
- }
- },
- this
- );
-
- // Change all the statements in the RDF where the aggregation is the subject, to reflect the new resource map ID
- var aggregationNode;
- _.each(
- oldPidVariations,
- function (oldPid) {
- //Create a node for the old aggregation using this pid variation
- aggregationNode = this.rdf.sym(
- oldPid + "#aggregation"
- );
- var aggregationLitNode = this.rdf.lit(
- oldPid + "#aggregation",
- "",
- XSD("anyURI")
- );
-
- //Get all the triples where the old aggregation is the subject
- var aggregationSubjStatements = _.union(
- this.dataPackageGraph.statementsMatching(
- aggregationNode
- ),
- this.dataPackageGraph.statementsMatching(
- aggregationLitNode
- )
- );
-
- if (aggregationSubjStatements.length) {
- _.each(
- aggregationSubjStatements,
- function (statement) {
- //Clone the subject
- subjectClone = this.cloneNode(
- statement.subject
- );
- //Clone the predicate
- predicateClone = this.cloneNode(
- statement.predicate
- );
- //Clone the object
- objectClone = this.cloneNode(
- statement.object
- );
-
- //Set the subject value to the new aggregation id
- subjectClone.value =
- this.getURIFromRDF(pid) +
- "#aggregation";
-
- //Add a new statement with the new aggregation subject but the same predicate and object
- this.dataPackageGraph.add(
- subjectClone,
- predicateClone,
- objectClone
- );
- },
- this
- );
-
- //Remove the old aggregation statements from the graph
- this.dataPackageGraph.removeMany(
- aggregationNode
- );
- }
-
- // Change all the statements in the RDF where the aggregation is the object, to reflect the new resource map ID
- var aggregationObjStatements = _.union(
- this.dataPackageGraph.statementsMatching(
- undefined,
- undefined,
- aggregationNode
- ),
- this.dataPackageGraph.statementsMatching(
- undefined,
- undefined,
- aggregationLitNode
- )
- );
-
- if (aggregationObjStatements.length) {
- _.each(
- aggregationObjStatements,
- function (statement) {
- //Clone the subject, object, and predicate
- subjectClone = this.cloneNode(
- statement.subject
- );
- predicateClone = this.cloneNode(
- statement.predicate
- );
- objectClone = this.cloneNode(
- statement.object
- );
-
- //Set the object to the new aggregation pid
- objectClone.value =
- this.getURIFromRDF(pid) +
- "#aggregation";
-
- //Add the statement with the old subject and predicate but new aggregation object
- this.dataPackageGraph.add(
- subjectClone,
- predicateClone,
- objectClone
- );
- },
- this
- );
-
- //Remove all the old aggregation statements from the graph
- this.dataPackageGraph.removeMany(
- undefined,
- undefined,
- aggregationNode
- );
- }
-
- // Change all the resource map subject nodes in the RDF graph
- var rMapNode = this.rdf.sym(
- this.getURIFromRDF(oldPid)
- );
- var rMapStatements = $.extend(
- true,
- [],
- this.dataPackageGraph.statementsMatching(
- rMapNode
- )
- );
-
- // then repopulate them with correct values
- _.each(
- rMapStatements,
- function (statement) {
- subjectClone = this.cloneNode(
- statement.subject
- );
- predicateClone = this.cloneNode(
- statement.predicate
- );
- objectClone = this.cloneNode(
- statement.object
- );
-
- // In the case of modified date, reset it to now()
- if (
- predicateClone.value === DC("modified")
- ) {
- objectClone.value =
- new Date().toISOString();
- }
-
- //Update the subject to the new pid
- subjectClone.value =
- this.getURIFromRDF(pid);
-
- //Remove the old resource map statement
- this.dataPackageGraph.remove(statement);
-
- //Add the statement with the new subject pid, but the same predicate and object
- this.dataPackageGraph.add(
- subjectClone,
- predicateClone,
- objectClone
- );
- },
- this
- );
- },
- this
- );
-
- // Add the describes/isDescribedBy statements back in
- this.dataPackageGraph.add(
- this.rdf.sym(this.getURIFromRDF(pid)),
- ORE("describes"),
- this.rdf.sym(this.getURIFromRDF(pid) + "#aggregation")
- );
- this.dataPackageGraph.add(
- this.rdf.sym(this.getURIFromRDF(pid) + "#aggregation"),
- ORE("isDescribedBy"),
- this.rdf.sym(this.getURIFromRDF(pid))
- );
-
- //Add nodes for new package members
- _.each(
- addedIds,
- function (id) {
- this.addToAggregation(id);
- },
- this
- );
- } else {
- // Create the OAI-ORE graph from scratch
- this.dataPackageGraph = this.rdf.graph();
- cnResolveUrl = this.getCnURI();
-
- //Create a resource map node
- var rMapNode = this.rdf.sym(
- this.getURIFromRDF(this.packageModel.id)
- );
- //Create an aggregation node
- var aggregationNode = this.rdf.sym(
- this.getURIFromRDF(this.packageModel.id) +
- "#aggregation"
- );
-
- // Describe the resource map with a Creator
- var creatorNode = this.rdf.blankNode();
- var creatorName = this.rdf.lit(
- (MetacatUI.appUserModel.get("firstName") || "") +
- " " +
- (MetacatUI.appUserModel.get("lastName") || ""),
- "",
- XSD("string")
- );
- this.dataPackageGraph.add(
- creatorNode,
- FOAF("name"),
- creatorName
- );
- this.dataPackageGraph.add(
- creatorNode,
- RDF("type"),
- DCTERMS("Agent")
- );
- this.dataPackageGraph.add(
- rMapNode,
- DC("creator"),
- creatorNode
- );
-
- // Set the modified date
- modifiedDate = this.rdf.lit(
- new Date().toISOString(),
- "",
- XSD("dateTime")
- );
+ this,
+ );
+
+ //Get all the child package ids
+ var childPackages = this.packageModel.get("childPackages");
+ if (typeof childPackages == "object") {
+ idsFromModel = _.union(idsFromModel, Object.keys(childPackages));
+ }
+
+ //Find the difference between the model IDs and the XML IDs to get a list of added members
+ var addedIds = _.without(
+ _.difference(idsFromModel, idsFromXML),
+ oldPidVariations,
+ );
+
+ //Start an array to track all the member id variations
+ var allMemberIds = idsFromModel;
+
+ //Add the ids with the CN Resolve URLs
+ _.each(idsFromModel, function (id) {
+ allMemberIds.push(
+ cnResolveUrl + encodeURIComponent(id),
+ cnResolveUrl + id,
+ encodeURIComponent(id),
+ );
+ });
+
+ //Find the identifier statement in the resource map
+ var idNode = this.rdf.lit(oldPid);
+ var idStatements = this.dataPackageGraph.statementsMatching(
+ undefined,
+ undefined,
+ idNode,
+ );
+
+ //Change all the resource map identifier literal node in the RDF graph
+ if (idStatements.length) {
+ var idStatement = idStatements[0];
+
+ //Remove the identifier statement
+ try {
+ this.dataPackageGraph.remove(idStatement);
+ } catch (error) {
+ console.log(error);
+ }
+
+ //Replace the id in the subject URI with the new id
+ var newRMapURI = "";
+ if (idStatement.subject.value.indexOf(oldPid) > -1) {
+ newRMapURI = idStatement.subject.value.replace(oldPid, pid);
+ } else if (
+ idStatement.subject.value.indexOf(encodeURIComponent(oldPid)) > -1
+ ) {
+ newRMapURI = idStatement.subject.value.replace(
+ encodeURIComponent(oldPid),
+ encodeURIComponent(pid),
+ );
+ }
+
+ //Create resource map nodes for the subject and object
+ var rMapNode = this.rdf.sym(newRMapURI),
+ rMapIdNode = this.rdf.lit(pid);
+ //Add the triple for the resource map id
+ this.dataPackageGraph.add(
+ rMapNode,
+ DCTERMS("identifier"),
+ rMapIdNode,
+ );
+ }
+
+ //Get all the isAggregatedBy statements
+ var aggByStatements = $.extend(
+ true,
+ [],
+ this.dataPackageGraph.statementsMatching(
+ undefined,
+ ORE("isAggregatedBy"),
+ ),
+ );
+
+ // Remove any other isAggregatedBy statements that are not listed as members of this model
+ _.each(
+ aggByStatements,
+ function (statement) {
+ if (!_.contains(allMemberIds, statement.subject.value)) {
+ this.removeFromAggregation(statement.subject.value);
+ }
+ },
+ this,
+ );
+
+ // Change all the statements in the RDF where the aggregation is the subject, to reflect the new resource map ID
+ var aggregationNode;
+ _.each(
+ oldPidVariations,
+ function (oldPid) {
+ //Create a node for the old aggregation using this pid variation
+ aggregationNode = this.rdf.sym(oldPid + "#aggregation");
+ var aggregationLitNode = this.rdf.lit(
+ oldPid + "#aggregation",
+ "",
+ XSD("anyURI"),
+ );
+
+ //Get all the triples where the old aggregation is the subject
+ var aggregationSubjStatements = _.union(
+ this.dataPackageGraph.statementsMatching(aggregationNode),
+ this.dataPackageGraph.statementsMatching(aggregationLitNode),
+ );
+
+ if (aggregationSubjStatements.length) {
+ _.each(
+ aggregationSubjStatements,
+ function (statement) {
+ //Clone the subject
+ subjectClone = this.cloneNode(statement.subject);
+ //Clone the predicate
+ predicateClone = this.cloneNode(statement.predicate);
+ //Clone the object
+ objectClone = this.cloneNode(statement.object);
+
+ //Set the subject value to the new aggregation id
+ subjectClone.value =
+ this.getURIFromRDF(pid) + "#aggregation";
+
+ //Add a new statement with the new aggregation subject but the same predicate and object
this.dataPackageGraph.add(
- rMapNode,
- DCTERMS("modified"),
- modifiedDate
+ subjectClone,
+ predicateClone,
+ objectClone,
);
+ },
+ this,
+ );
- this.dataPackageGraph.add(
- rMapNode,
- RDF("type"),
- ORE("ResourceMap")
- );
- this.dataPackageGraph.add(
- rMapNode,
- ORE("describes"),
- aggregationNode
- );
- var idLiteral = this.rdf.lit(
- this.packageModel.id,
- "",
- XSD("string")
- );
- this.dataPackageGraph.add(
- rMapNode,
- DCTERMS("identifier"),
- idLiteral
- );
+ //Remove the old aggregation statements from the graph
+ this.dataPackageGraph.removeMany(aggregationNode);
+ }
- // Describe the aggregation
+ // Change all the statements in the RDF where the aggregation is the object, to reflect the new resource map ID
+ var aggregationObjStatements = _.union(
+ this.dataPackageGraph.statementsMatching(
+ undefined,
+ undefined,
+ aggregationNode,
+ ),
+ this.dataPackageGraph.statementsMatching(
+ undefined,
+ undefined,
+ aggregationLitNode,
+ ),
+ );
+
+ if (aggregationObjStatements.length) {
+ _.each(
+ aggregationObjStatements,
+ function (statement) {
+ //Clone the subject, object, and predicate
+ subjectClone = this.cloneNode(statement.subject);
+ predicateClone = this.cloneNode(statement.predicate);
+ objectClone = this.cloneNode(statement.object);
+
+ //Set the object to the new aggregation pid
+ objectClone.value =
+ this.getURIFromRDF(pid) + "#aggregation";
+
+ //Add the statement with the old subject and predicate but new aggregation object
this.dataPackageGraph.add(
- aggregationNode,
- ORE("isDescribedBy"),
- rMapNode
- );
-
- // Aggregate each package member
- _.each(
- idsFromModel,
- function (id) {
- this.addToAggregation(id);
- },
- this
+ subjectClone,
+ predicateClone,
+ objectClone,
);
- }
-
- // Remove any references to blank nodes not already cleaned up.
- // rdflib.js will fail to serialize an IndexedFormula (graph) with
- // statements whose object is a blank node when the blank node
- // is not the subject of any other statements.
- this.removeOrphanedBlankNodes();
+ },
+ this,
+ );
- var xmlString = serializer.statementsToXML(
- this.dataPackageGraph.statements
+ //Remove all the old aggregation statements from the graph
+ this.dataPackageGraph.removeMany(
+ undefined,
+ undefined,
+ aggregationNode,
);
+ }
- return xmlString;
+ // Change all the resource map subject nodes in the RDF graph
+ var rMapNode = this.rdf.sym(this.getURIFromRDF(oldPid));
+ var rMapStatements = $.extend(
+ true,
+ [],
+ this.dataPackageGraph.statementsMatching(rMapNode),
+ );
+
+ // then repopulate them with correct values
+ _.each(
+ rMapStatements,
+ function (statement) {
+ subjectClone = this.cloneNode(statement.subject);
+ predicateClone = this.cloneNode(statement.predicate);
+ objectClone = this.cloneNode(statement.object);
+
+ // In the case of modified date, reset it to now()
+ if (predicateClone.value === DC("modified")) {
+ objectClone.value = new Date().toISOString();
+ }
+
+ //Update the subject to the new pid
+ subjectClone.value = this.getURIFromRDF(pid);
+
+ //Remove the old resource map statement
+ this.dataPackageGraph.remove(statement);
+
+ //Add the statement with the new subject pid, but the same predicate and object
+ this.dataPackageGraph.add(
+ subjectClone,
+ predicateClone,
+ objectClone,
+ );
+ },
+ this,
+ );
},
-
- // Clone an rdflib.js Node by creaing a new node based on the
- // original node RDF term type and data type.
- cloneNode: function (nodeToClone) {
- switch (nodeToClone.termType) {
- case "NamedNode":
- return this.rdf.sym(nodeToClone.value);
- break;
- case "Literal":
- // Check for the datatype for this literal value, e.g. http://www.w3.org/2001/XMLSchema#string"
- if (typeof nodeToClone.datatype !== "undefined") {
- return this.rdf.literal(
- nodeToClone.value,
- undefined,
- nodeToClone.datatype
- );
- } else {
- return this.rdf.literal(nodeToClone.value);
- }
- break;
- case "BlankNode":
- //Blank nodes don't need to be cloned
- return nodeToClone; //(this.rdf.blankNode(nodeToClone.value));
- break;
- case "Collection":
- // TODO: construct a list of nodes for this term type.
- return this.rdf.list(nodeToClone.value);
- break;
- default:
- console.log(
- "ERROR: unknown node type to clone: " +
- nodeToClone.termType
- );
- }
+ this,
+ );
+
+ // Add the describes/isDescribedBy statements back in
+ this.dataPackageGraph.add(
+ this.rdf.sym(this.getURIFromRDF(pid)),
+ ORE("describes"),
+ this.rdf.sym(this.getURIFromRDF(pid) + "#aggregation"),
+ );
+ this.dataPackageGraph.add(
+ this.rdf.sym(this.getURIFromRDF(pid) + "#aggregation"),
+ ORE("isDescribedBy"),
+ this.rdf.sym(this.getURIFromRDF(pid)),
+ );
+
+ //Add nodes for new package members
+ _.each(
+ addedIds,
+ function (id) {
+ this.addToAggregation(id);
},
+ this,
+ );
+ } else {
+ // Create the OAI-ORE graph from scratch
+ this.dataPackageGraph = this.rdf.graph();
+ cnResolveUrl = this.getCnURI();
+
+ //Create a resource map node
+ var rMapNode = this.rdf.sym(this.getURIFromRDF(this.packageModel.id));
+ //Create an aggregation node
+ var aggregationNode = this.rdf.sym(
+ this.getURIFromRDF(this.packageModel.id) + "#aggregation",
+ );
+
+ // Describe the resource map with a Creator
+ var creatorNode = this.rdf.blankNode();
+ var creatorName = this.rdf.lit(
+ (MetacatUI.appUserModel.get("firstName") || "") +
+ " " +
+ (MetacatUI.appUserModel.get("lastName") || ""),
+ "",
+ XSD("string"),
+ );
+ this.dataPackageGraph.add(creatorNode, FOAF("name"), creatorName);
+ this.dataPackageGraph.add(creatorNode, RDF("type"), DCTERMS("Agent"));
+ this.dataPackageGraph.add(rMapNode, DC("creator"), creatorNode);
+
+ // Set the modified date
+ modifiedDate = this.rdf.lit(
+ new Date().toISOString(),
+ "",
+ XSD("dateTime"),
+ );
+ this.dataPackageGraph.add(
+ rMapNode,
+ DCTERMS("modified"),
+ modifiedDate,
+ );
+
+ this.dataPackageGraph.add(rMapNode, RDF("type"), ORE("ResourceMap"));
+ this.dataPackageGraph.add(
+ rMapNode,
+ ORE("describes"),
+ aggregationNode,
+ );
+ var idLiteral = this.rdf.lit(this.packageModel.id, "", XSD("string"));
+ this.dataPackageGraph.add(rMapNode, DCTERMS("identifier"), idLiteral);
+
+ // Describe the aggregation
+ this.dataPackageGraph.add(
+ aggregationNode,
+ ORE("isDescribedBy"),
+ rMapNode,
+ );
+
+ // Aggregate each package member
+ _.each(
+ idsFromModel,
+ function (id) {
+ this.addToAggregation(id);
+ },
+ this,
+ );
+ }
- // Adds a new object to the resource map RDF graph
- addToAggregation: function (id) {
- // Initialize the namespaces
- var ORE = this.rdf.Namespace(this.namespaces.ORE),
- DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS),
- XSD = this.rdf.Namespace(this.namespaces.XSD),
- CITO = this.rdf.Namespace(this.namespaces.CITO);
-
- // Create a node for this object, the identifier, the resource map, and the aggregation
- var objectNode = this.rdf.sym(this.getURIFromRDF(id)),
- rMapURI = this.getURIFromRDF(this.packageModel.get("id")),
- mapNode = this.rdf.sym(rMapURI),
- aggNode = this.rdf.sym(rMapURI + "#aggregation"),
- idNode = this.rdf.literal(id, undefined, XSD("string")),
- idStatements = [],
- aggStatements = [],
- aggByStatements = [],
- documentsStatements = [],
- isDocumentedByStatements = [];
-
- // Add the statement: this object isAggregatedBy the resource map aggregation
- aggByStatements = this.dataPackageGraph.statementsMatching(
- objectNode,
- ORE("isAggregatedBy"),
- aggNode
- );
- if (aggByStatements.length < 1) {
- this.dataPackageGraph.add(
- objectNode,
- ORE("isAggregatedBy"),
- aggNode
- );
- }
-
- // Add the statement: The resource map aggregation aggregates this object
- aggStatements = this.dataPackageGraph.statementsMatching(
- aggNode,
- ORE("aggregates"),
- objectNode
- );
- if (aggStatements.length < 1) {
- this.dataPackageGraph.add(
- aggNode,
- ORE("aggregates"),
- objectNode
- );
- }
+ // Remove any references to blank nodes not already cleaned up.
+ // rdflib.js will fail to serialize an IndexedFormula (graph) with
+ // statements whose object is a blank node when the blank node
+ // is not the subject of any other statements.
+ this.removeOrphanedBlankNodes();
+
+ var xmlString = serializer.statementsToXML(
+ this.dataPackageGraph.statements,
+ );
+
+ return xmlString;
+ },
+
+ // Clone an rdflib.js Node by creaing a new node based on the
+ // original node RDF term type and data type.
+ cloneNode: function (nodeToClone) {
+ switch (nodeToClone.termType) {
+ case "NamedNode":
+ return this.rdf.sym(nodeToClone.value);
+ break;
+ case "Literal":
+ // Check for the datatype for this literal value, e.g. http://www.w3.org/2001/XMLSchema#string"
+ if (typeof nodeToClone.datatype !== "undefined") {
+ return this.rdf.literal(
+ nodeToClone.value,
+ undefined,
+ nodeToClone.datatype,
+ );
+ } else {
+ return this.rdf.literal(nodeToClone.value);
+ }
+ break;
+ case "BlankNode":
+ //Blank nodes don't need to be cloned
+ return nodeToClone; //(this.rdf.blankNode(nodeToClone.value));
+ break;
+ case "Collection":
+ // TODO: construct a list of nodes for this term type.
+ return this.rdf.list(nodeToClone.value);
+ break;
+ default:
+ console.log(
+ "ERROR: unknown node type to clone: " + nodeToClone.termType,
+ );
+ }
+ },
+
+ // Adds a new object to the resource map RDF graph
+ addToAggregation: function (id) {
+ // Initialize the namespaces
+ var ORE = this.rdf.Namespace(this.namespaces.ORE),
+ DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS),
+ XSD = this.rdf.Namespace(this.namespaces.XSD),
+ CITO = this.rdf.Namespace(this.namespaces.CITO);
+
+ // Create a node for this object, the identifier, the resource map, and the aggregation
+ var objectNode = this.rdf.sym(this.getURIFromRDF(id)),
+ rMapURI = this.getURIFromRDF(this.packageModel.get("id")),
+ mapNode = this.rdf.sym(rMapURI),
+ aggNode = this.rdf.sym(rMapURI + "#aggregation"),
+ idNode = this.rdf.literal(id, undefined, XSD("string")),
+ idStatements = [],
+ aggStatements = [],
+ aggByStatements = [],
+ documentsStatements = [],
+ isDocumentedByStatements = [];
+
+ // Add the statement: this object isAggregatedBy the resource map aggregation
+ aggByStatements = this.dataPackageGraph.statementsMatching(
+ objectNode,
+ ORE("isAggregatedBy"),
+ aggNode,
+ );
+ if (aggByStatements.length < 1) {
+ this.dataPackageGraph.add(objectNode, ORE("isAggregatedBy"), aggNode);
+ }
- // Add the statement: This object has the identifier {id} if it isn't present
- idStatements = this.dataPackageGraph.statementsMatching(
- objectNode,
- DCTERMS("identifier"),
- idNode
- );
- if (idStatements.length < 1) {
- this.dataPackageGraph.add(
- objectNode,
- DCTERMS("identifier"),
- idNode
- );
- }
+ // Add the statement: The resource map aggregation aggregates this object
+ aggStatements = this.dataPackageGraph.statementsMatching(
+ aggNode,
+ ORE("aggregates"),
+ objectNode,
+ );
+ if (aggStatements.length < 1) {
+ this.dataPackageGraph.add(aggNode, ORE("aggregates"), objectNode);
+ }
- // Find the metadata doc that describes this object
- var model = this.findWhere({ id: id }),
- isDocBy = model.get("isDocumentedBy"),
- documents = model.get("documents");
+ // Add the statement: This object has the identifier {id} if it isn't present
+ idStatements = this.dataPackageGraph.statementsMatching(
+ objectNode,
+ DCTERMS("identifier"),
+ idNode,
+ );
+ if (idStatements.length < 1) {
+ this.dataPackageGraph.add(objectNode, DCTERMS("identifier"), idNode);
+ }
- // Deal with Solr indexing bug where metadata-only packages must "document" themselves
- if (isDocBy.length === 0 && documents.length === 0) {
- documents.push(model.get("id"));
- }
+ // Find the metadata doc that describes this object
+ var model = this.findWhere({ id: id }),
+ isDocBy = model.get("isDocumentedBy"),
+ documents = model.get("documents");
- // If this object is documented by any metadata...
- if (isDocBy && isDocBy.length) {
- // Get the ids of all the metadata objects in this package
- var metadataInPackage = _.compact(
- _.map(this.models, function (m) {
- if (m.get("formatType") == "METADATA") return m;
- })
- ),
- metadataInPackageIDs = _.each(
- metadataInPackage,
- function (m) {
- return m.get("id");
- }
- );
-
- // Find the metadata IDs that are in this package that also documents this data object
- var metadataIds = Array.isArray(isDocBy)
- ? _.intersection(metadataInPackageIDs, isDocBy)
- : _.intersection(metadataInPackageIDs, [isDocBy]);
-
- // If this data object is not documented by one of these metadata docs,
- // then we should check if it's documented by an obsoleted pid. If so,
- // we'll want to change that so it's documented by a current metadata.
- if (metadataIds.length == 0) {
- for (var i = 0; i < metadataInPackage.length; i++) {
- //If the previous version of this metadata documents this data,
- if (
- _.contains(
- isDocBy,
- metadataInPackage[i].get("obsoletes")
- )
- ) {
- //Save the metadata id for serialization
- metadataIds = [metadataInPackage[i].get("id")];
-
- //Exit the for loop
- break;
- }
- }
- }
+ // Deal with Solr indexing bug where metadata-only packages must "document" themselves
+ if (isDocBy.length === 0 && documents.length === 0) {
+ documents.push(model.get("id"));
+ }
- // For each metadata that documents this object, add a CITO:isDocumentedBy and CITO:documents statement
- _.each(
- metadataIds,
- function (metaId) {
- //Create the named nodes and statements
- var dataNode = this.rdf.sym(this.getURIFromRDF(id)),
- metadataNode = this.rdf.sym(
- this.getURIFromRDF(metaId)
- ),
- isDocByStatement = this.rdf.st(
- dataNode,
- CITO("isDocumentedBy"),
- metadataNode
- ),
- documentsStatement = this.rdf.st(
- metadataNode,
- CITO("documents"),
- dataNode
- );
-
- // Add the statements
- documentsStatements =
- this.dataPackageGraph.statementsMatching(
- metadataNode,
- CITO("documents"),
- dataNode
- );
- if (documentsStatements.length < 1) {
- this.dataPackageGraph.add(documentsStatement);
- }
- isDocumentedByStatements =
- this.dataPackageGraph.statementsMatching(
- dataNode,
- CITO("isDocumentedBy"),
- metadataNode
- );
- if (isDocumentedByStatements.length < 1) {
- this.dataPackageGraph.add(isDocByStatement);
- }
- },
- this
- );
- }
+ // If this object is documented by any metadata...
+ if (isDocBy && isDocBy.length) {
+ // Get the ids of all the metadata objects in this package
+ var metadataInPackage = _.compact(
+ _.map(this.models, function (m) {
+ if (m.get("formatType") == "METADATA") return m;
+ }),
+ ),
+ metadataInPackageIDs = _.each(metadataInPackage, function (m) {
+ return m.get("id");
+ });
+
+ // Find the metadata IDs that are in this package that also documents this data object
+ var metadataIds = Array.isArray(isDocBy)
+ ? _.intersection(metadataInPackageIDs, isDocBy)
+ : _.intersection(metadataInPackageIDs, [isDocBy]);
+
+ // If this data object is not documented by one of these metadata docs,
+ // then we should check if it's documented by an obsoleted pid. If so,
+ // we'll want to change that so it's documented by a current metadata.
+ if (metadataIds.length == 0) {
+ for (var i = 0; i < metadataInPackage.length; i++) {
+ //If the previous version of this metadata documents this data,
+ if (_.contains(isDocBy, metadataInPackage[i].get("obsoletes"))) {
+ //Save the metadata id for serialization
+ metadataIds = [metadataInPackage[i].get("id")];
+
+ //Exit the for loop
+ break;
+ }
+ }
+ }
+
+ // For each metadata that documents this object, add a CITO:isDocumentedBy and CITO:documents statement
+ _.each(
+ metadataIds,
+ function (metaId) {
+ //Create the named nodes and statements
+ var dataNode = this.rdf.sym(this.getURIFromRDF(id)),
+ metadataNode = this.rdf.sym(this.getURIFromRDF(metaId)),
+ isDocByStatement = this.rdf.st(
+ dataNode,
+ CITO("isDocumentedBy"),
+ metadataNode,
+ ),
+ documentsStatement = this.rdf.st(
+ metadataNode,
+ CITO("documents"),
+ dataNode,
+ );
- // If this object documents a data object
- if (documents && documents.length) {
- // Create a literal node for it
- var metadataNode = this.rdf.sym(this.getURIFromRDF(id));
-
- _.each(
- documents,
- function (dataID) {
- //Make sure the id is one that will be aggregated
- if (_.contains(this.idsToAggregate, dataID)) {
- //Find the identifier statement for this data object
- var dataURI = this.getURIFromRDF(dataID);
-
- //Create a data node using the exact way the identifier URI is written
- var dataNode = this.rdf.sym(dataURI);
-
- //Get the statements for data isDocumentedBy metadata
- isDocumentedByStatements =
- this.dataPackageGraph.statementsMatching(
- dataNode,
- CITO("isDocumentedBy"),
- metadataNode
- );
-
- //If that statement is not in the RDF already...
- if (isDocumentedByStatements.length < 1) {
- // Create a statement: This data is documented by this metadata
- var isDocByStatement = this.rdf.st(
- dataNode,
- CITO("isDocumentedBy"),
- metadataNode
- );
- //Add the "isDocumentedBy" statement
- this.dataPackageGraph.add(isDocByStatement);
- }
-
- //Get the statements for metadata documents data
- documentsStatements =
- this.dataPackageGraph.statementsMatching(
- metadataNode,
- CITO("documents"),
- dataNode
- );
-
- //If that statement is not in the RDF already...
- if (documentsStatements.length < 1) {
- // Create a statement: This metadata documents data
- var documentsStatement = this.rdf.st(
- metadataNode,
- CITO("documents"),
- dataNode
- );
- //Add the "isDocumentedBy" statement
- this.dataPackageGraph.add(
- documentsStatement
- );
- }
- }
- },
- this
- );
- }
+ // Add the statements
+ documentsStatements = this.dataPackageGraph.statementsMatching(
+ metadataNode,
+ CITO("documents"),
+ dataNode,
+ );
+ if (documentsStatements.length < 1) {
+ this.dataPackageGraph.add(documentsStatement);
+ }
+ isDocumentedByStatements =
+ this.dataPackageGraph.statementsMatching(
+ dataNode,
+ CITO("isDocumentedBy"),
+ metadataNode,
+ );
+ if (isDocumentedByStatements.length < 1) {
+ this.dataPackageGraph.add(isDocByStatement);
+ }
},
+ this,
+ );
+ }
- /*
- * Removes an object from the aggregation in the RDF graph
- */
- removeFromAggregation: function (id) {
- if (id.indexOf(this.dataPackageGraph.cnResolveUrl) == -1) {
- id = this.getURIFromRDF(id);
- }
-
- // Create a literal node for the removed object
- var removedObjNode = this.rdf.sym(id),
- // Get the statements from the RDF where the removed object is the subject or object
- statements = $.extend(
- true,
- [],
- _.union(
- this.dataPackageGraph.statementsMatching(
- undefined,
- undefined,
- removedObjNode
- ),
- this.dataPackageGraph.statementsMatching(
- removedObjNode
- )
- )
- );
-
- // Remove all the statements mentioning this object
- try {
- this.dataPackageGraph.remove(statements);
- } catch (error) {
- console.log(error);
+ // If this object documents a data object
+ if (documents && documents.length) {
+ // Create a literal node for it
+ var metadataNode = this.rdf.sym(this.getURIFromRDF(id));
+
+ _.each(
+ documents,
+ function (dataID) {
+ //Make sure the id is one that will be aggregated
+ if (_.contains(this.idsToAggregate, dataID)) {
+ //Find the identifier statement for this data object
+ var dataURI = this.getURIFromRDF(dataID);
+
+ //Create a data node using the exact way the identifier URI is written
+ var dataNode = this.rdf.sym(dataURI);
+
+ //Get the statements for data isDocumentedBy metadata
+ isDocumentedByStatements =
+ this.dataPackageGraph.statementsMatching(
+ dataNode,
+ CITO("isDocumentedBy"),
+ metadataNode,
+ );
+
+ //If that statement is not in the RDF already...
+ if (isDocumentedByStatements.length < 1) {
+ // Create a statement: This data is documented by this metadata
+ var isDocByStatement = this.rdf.st(
+ dataNode,
+ CITO("isDocumentedBy"),
+ metadataNode,
+ );
+ //Add the "isDocumentedBy" statement
+ this.dataPackageGraph.add(isDocByStatement);
}
- },
-
- /**
- * Finds the given identifier in the RDF graph and returns the subject
- * URI of that statement. This is useful when adding additional statements
- * to the RDF graph for an object that already exists in that graph.
- *
- * @param {string} id - The identifier to search for
- * @return {string} - The full URI for the given id as it exists in the RDF.
- */
- getURIFromRDF: function (id) {
- //Exit if no id was given
- if (!id) return "";
-
- //Create a literal node with the identifier as the value
- var XSD = this.rdf.Namespace(this.namespaces.XSD),
- DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS),
- idNode = this.rdf.literal(id, undefined, XSD("string")),
- //Find the identifier statements for the given id
- idStatements = this.dataPackageGraph.statementsMatching(
- undefined,
- DCTERMS("identifier"),
- idNode
- );
- //If this object has an identifier statement,
- if (idStatements.length > 0) {
- //Return the subject of the statement
- return idStatements[0].subject.value;
- } else {
- return this.getCnURI() + encodeURIComponent(id);
- }
- },
+ //Get the statements for metadata documents data
+ documentsStatements = this.dataPackageGraph.statementsMatching(
+ metadataNode,
+ CITO("documents"),
+ dataNode,
+ );
- /**
- * Parses out the CN Resolve URL from the existing statements in the RDF
- * or if not found in the RDF, from the app configuration.
- *
- * @return {string} - The CN resolve URL
- */
- getCnURI: function () {
- //If the CN resolve URL was already found, return it
- if (this.dataPackageGraph.cnResolveUrl) {
- return this.dataPackageGraph.cnResolveUrl;
- } else if (this.packageModel.get("oldPid")) {
- //Find the identifier statement for the resource map in the RDF graph
- var idNode = this.rdf.lit(this.packageModel.get("oldPid")),
- idStatements = this.dataPackageGraph.statementsMatching(
- undefined,
- undefined,
- idNode
- ),
- idStatement = idStatements.length
- ? idStatements[0]
- : null;
-
- if (idStatement) {
- //Parse the CN resolve URL from the statement subject URI
- this.dataPackageGraph.cnResolveUrl =
- idStatement.subject.value.substring(
- 0,
- idStatement.subject.value.indexOf(
- this.packageModel.get("oldPid")
- )
- ) ||
- idStatement.subject.value.substring(
- 0,
- idStatement.subject.value.indexOf(
- encodeURIComponent(
- this.packageModel.get("oldPid")
- )
- )
- );
- } else {
- this.dataPackageGraph.cnResolveUrl =
- MetacatUI.appModel.get("resolveServiceUrl");
- }
- } else {
- this.dataPackageGraph.cnResolveUrl =
- MetacatUI.appModel.get("resolveServiceUrl");
+ //If that statement is not in the RDF already...
+ if (documentsStatements.length < 1) {
+ // Create a statement: This metadata documents data
+ var documentsStatement = this.rdf.st(
+ metadataNode,
+ CITO("documents"),
+ dataNode,
+ );
+ //Add the "isDocumentedBy" statement
+ this.dataPackageGraph.add(documentsStatement);
}
-
- //Return the CN resolve URL
- return this.dataPackageGraph.cnResolveUrl;
+ }
},
+ this,
+ );
+ }
+ },
- /**
- * Checks if this resource map has had any changes that requires an update
- */
- needsUpdate: function () {
- //Check for changes to the list of aggregated members
- var ids = this.pluck("id");
- if (
- this.originalMembers.length != ids.length ||
- _.intersection(this.originalMembers, ids).length !=
- ids.length
- )
- return true;
-
- // If the provenance relationships have been updated, then the resource map
- // needs to be updated.
- if (this.provEdits.length) return true;
- //Check for changes to the isDocumentedBy relationships
- var isDifferent = false,
- i = 0;
-
- //Keep going until we find a difference
- while (!isDifferent && i < this.length) {
- //Get the original isDocBy relationships from the resource map, and the new isDocBy relationships from the models
- var isDocBy = this.models[i].get("isDocumentedBy"),
- id = this.models[i].get("id"),
- origIsDocBy = this.originalIsDocBy[id];
-
- //Make sure they are both formatted as arrays for these checks
- isDocBy = _.uniq(
- _.flatten(
- _.compact(
- Array.isArray(isDocBy) ? isDocBy : [isDocBy]
- )
- )
- );
- origIsDocBy = _.uniq(
- _.flatten(
- _.compact(
- Array.isArray(origIsDocBy)
- ? origIsDocBy
- : [origIsDocBy]
- )
- )
- );
-
- //Remove the id of this object so metadata can not be "isDocumentedBy" itself
- isDocBy = _.without(isDocBy, id);
- origIsDocBy = _.without(origIsDocBy, id);
-
- //Simply check if they are the same
- if (origIsDocBy === isDocBy) {
- i++;
- continue;
- }
- //Are the number of relationships different?
- else if (isDocBy.length != origIsDocBy.length)
- isDifferent = true;
- //Are the arrays the same?
- else if (
- _.intersection(isDocBy, origIsDocBy).length !=
- origIsDocBy.length
- )
- isDifferent = true;
-
- i++;
- }
+ /*
+ * Removes an object from the aggregation in the RDF graph
+ */
+ removeFromAggregation: function (id) {
+ if (id.indexOf(this.dataPackageGraph.cnResolveUrl) == -1) {
+ id = this.getURIFromRDF(id);
+ }
- return isDifferent;
- },
+ // Create a literal node for the removed object
+ var removedObjNode = this.rdf.sym(id),
+ // Get the statements from the RDF where the removed object is the subject or object
+ statements = $.extend(
+ true,
+ [],
+ _.union(
+ this.dataPackageGraph.statementsMatching(
+ undefined,
+ undefined,
+ removedObjNode,
+ ),
+ this.dataPackageGraph.statementsMatching(removedObjNode),
+ ),
+ );
+
+ // Remove all the statements mentioning this object
+ try {
+ this.dataPackageGraph.remove(statements);
+ } catch (error) {
+ console.log(error);
+ }
+ },
+
+ /**
+ * Finds the given identifier in the RDF graph and returns the subject
+ * URI of that statement. This is useful when adding additional statements
+ * to the RDF graph for an object that already exists in that graph.
+ *
+ * @param {string} id - The identifier to search for
+ * @return {string} - The full URI for the given id as it exists in the RDF.
+ */
+ getURIFromRDF: function (id) {
+ //Exit if no id was given
+ if (!id) return "";
+
+ //Create a literal node with the identifier as the value
+ var XSD = this.rdf.Namespace(this.namespaces.XSD),
+ DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS),
+ idNode = this.rdf.literal(id, undefined, XSD("string")),
+ //Find the identifier statements for the given id
+ idStatements = this.dataPackageGraph.statementsMatching(
+ undefined,
+ DCTERMS("identifier"),
+ idNode,
+ );
+
+ //If this object has an identifier statement,
+ if (idStatements.length > 0) {
+ //Return the subject of the statement
+ return idStatements[0].subject.value;
+ } else {
+ return this.getCnURI() + encodeURIComponent(id);
+ }
+ },
- /*
- * Returns an array of the models that are in the queue or in progress of uploading
- */
- getQueue: function () {
- return this.filter(function (m) {
- return (
- m.get("uploadStatus") == "q" ||
- m.get("uploadStatus") == "p"
- );
- });
- },
+ /**
+ * Parses out the CN Resolve URL from the existing statements in the RDF
+ * or if not found in the RDF, from the app configuration.
+ *
+ * @return {string} - The CN resolve URL
+ */
+ getCnURI: function () {
+ //If the CN resolve URL was already found, return it
+ if (this.dataPackageGraph.cnResolveUrl) {
+ return this.dataPackageGraph.cnResolveUrl;
+ } else if (this.packageModel.get("oldPid")) {
+ //Find the identifier statement for the resource map in the RDF graph
+ var idNode = this.rdf.lit(this.packageModel.get("oldPid")),
+ idStatements = this.dataPackageGraph.statementsMatching(
+ undefined,
+ undefined,
+ idNode,
+ ),
+ idStatement = idStatements.length ? idStatements[0] : null;
+
+ if (idStatement) {
+ //Parse the CN resolve URL from the statement subject URI
+ this.dataPackageGraph.cnResolveUrl =
+ idStatement.subject.value.substring(
+ 0,
+ idStatement.subject.value.indexOf(
+ this.packageModel.get("oldPid"),
+ ),
+ ) ||
+ idStatement.subject.value.substring(
+ 0,
+ idStatement.subject.value.indexOf(
+ encodeURIComponent(this.packageModel.get("oldPid")),
+ ),
+ );
+ } else {
+ this.dataPackageGraph.cnResolveUrl =
+ MetacatUI.appModel.get("resolveServiceUrl");
+ }
+ } else {
+ this.dataPackageGraph.cnResolveUrl =
+ MetacatUI.appModel.get("resolveServiceUrl");
+ }
- /*
- * Adds a DataONEObject model to this DataPackage collection
- */
- addNewModel: function (model) {
- //Check that this collection doesn't already contain this model
- if (!this.contains(model)) {
- this.add(model);
-
- //Mark this data package as changed
- this.packageModel.set("changed", true);
- this.packageModel.trigger("change:changed");
- }
- },
+ //Return the CN resolve URL
+ return this.dataPackageGraph.cnResolveUrl;
+ },
- handleAdd: function (dataONEObject) {
- var metadataModel = this.find(function (m) {
- return m.get("type") == "Metadata";
- });
+ /**
+ * Checks if this resource map has had any changes that requires an update
+ */
+ needsUpdate: function () {
+ //Check for changes to the list of aggregated members
+ var ids = this.pluck("id");
+ if (
+ this.originalMembers.length != ids.length ||
+ _.intersection(this.originalMembers, ids).length != ids.length
+ )
+ return true;
+
+ // If the provenance relationships have been updated, then the resource map
+ // needs to be updated.
+ if (this.provEdits.length) return true;
+ //Check for changes to the isDocumentedBy relationships
+ var isDifferent = false,
+ i = 0;
+
+ //Keep going until we find a difference
+ while (!isDifferent && i < this.length) {
+ //Get the original isDocBy relationships from the resource map, and the new isDocBy relationships from the models
+ var isDocBy = this.models[i].get("isDocumentedBy"),
+ id = this.models[i].get("id"),
+ origIsDocBy = this.originalIsDocBy[id];
+
+ //Make sure they are both formatted as arrays for these checks
+ isDocBy = _.uniq(
+ _.flatten(_.compact(Array.isArray(isDocBy) ? isDocBy : [isDocBy])),
+ );
+ origIsDocBy = _.uniq(
+ _.flatten(
+ _.compact(
+ Array.isArray(origIsDocBy) ? origIsDocBy : [origIsDocBy],
+ ),
+ ),
+ );
+
+ //Remove the id of this object so metadata can not be "isDocumentedBy" itself
+ isDocBy = _.without(isDocBy, id);
+ origIsDocBy = _.without(origIsDocBy, id);
+
+ //Simply check if they are the same
+ if (origIsDocBy === isDocBy) {
+ i++;
+ continue;
+ }
+ //Are the number of relationships different?
+ else if (isDocBy.length != origIsDocBy.length) isDifferent = true;
+ //Are the arrays the same?
+ else if (
+ _.intersection(isDocBy, origIsDocBy).length != origIsDocBy.length
+ )
+ isDifferent = true;
+
+ i++;
+ }
- // Append to or create a new documents list
- if (metadataModel) {
- if (!Array.isArray(metadataModel.get("documents"))) {
- metadataModel.set("documents", [dataONEObject.id]);
- } else {
- if (
- !_.contains(
- metadataModel.get("documents"),
- dataONEObject.id
- )
- )
- metadataModel
- .get("documents")
- .push(dataONEObject.id);
- }
+ return isDifferent;
+ },
- // Create an EML Entity for this DataONE Object if there isn't one already
- if (
- metadataModel.type == "EML" &&
- !dataONEObject.get("metadataEntity") &&
- dataONEObject.type != "EML"
- ) {
- metadataModel.createEntity(dataONEObject);
- metadataModel.set("uploadStatus", "q");
- }
- }
+ /*
+ * Returns an array of the models that are in the queue or in progress of uploading
+ */
+ getQueue: function () {
+ return this.filter(function (m) {
+ return m.get("uploadStatus") == "q" || m.get("uploadStatus") == "p";
+ });
+ },
+
+ /*
+ * Adds a DataONEObject model to this DataPackage collection
+ */
+ addNewModel: function (model) {
+ //Check that this collection doesn't already contain this model
+ if (!this.contains(model)) {
+ this.add(model);
+
+ //Mark this data package as changed
+ this.packageModel.set("changed", true);
+ this.packageModel.trigger("change:changed");
+ }
+ },
+
+ handleAdd: function (dataONEObject) {
+ var metadataModel = this.find(function (m) {
+ return m.get("type") == "Metadata";
+ });
+
+ // Append to or create a new documents list
+ if (metadataModel) {
+ if (!Array.isArray(metadataModel.get("documents"))) {
+ metadataModel.set("documents", [dataONEObject.id]);
+ } else {
+ if (!_.contains(metadataModel.get("documents"), dataONEObject.id))
+ metadataModel.get("documents").push(dataONEObject.id);
+ }
+
+ // Create an EML Entity for this DataONE Object if there isn't one already
+ if (
+ metadataModel.type == "EML" &&
+ !dataONEObject.get("metadataEntity") &&
+ dataONEObject.type != "EML"
+ ) {
+ metadataModel.createEntity(dataONEObject);
+ metadataModel.set("uploadStatus", "q");
+ }
+ }
- this.saveReference(dataONEObject);
+ this.saveReference(dataONEObject);
- this.setLoadingFiles(dataONEObject);
+ this.setLoadingFiles(dataONEObject);
- //Save a reference to this DataPackage
- // If the collections attribute is an array
- /* if( Array.isArray(dataONEObject.get("collections")) ){
+ //Save a reference to this DataPackage
+ // If the collections attribute is an array
+ /* if( Array.isArray(dataONEObject.get("collections")) ){
//Add this DataPackage to the collections list if it's not already in the array
if( !_.contains(dataONEObject.get("collections"), this) ){
dataONEObject.get("collections").push(this);
@@ -3831,269 +3503,248 @@ define([
dataONEObject.set("collections", [this]);
}
*/
- },
+ },
- /**
- * Fetches this DataPackage from the Solr index by using a SolrResults collection
- * and merging the models in.
- */
- fetchFromIndex: function () {
- if (
- typeof this.solrResults == "undefined" ||
- !this.solrResults
- ) {
- this.solrResults = new SolrResults();
- }
-
- //If no query is set yet, use the FilterModel associated with this DataPackage
- if (!this.solrResults.currentquery.length) {
- this.solrResults.currentquery = this.filterModel.getQuery();
- }
-
- this.listenToOnce(
- this.solrResults,
- "reset",
- function (solrResults) {
- //Merge the SolrResults into this collection
- this.mergeModels(solrResults.models);
-
- //Trigger the fetch as complete
- this.trigger("complete");
- }
- );
+ /**
+ * Fetches this DataPackage from the Solr index by using a SolrResults collection
+ * and merging the models in.
+ */
+ fetchFromIndex: function () {
+ if (typeof this.solrResults == "undefined" || !this.solrResults) {
+ this.solrResults = new SolrResults();
+ }
- //Query the index for this data package
- this.solrResults.query();
- },
+ //If no query is set yet, use the FilterModel associated with this DataPackage
+ if (!this.solrResults.currentquery.length) {
+ this.solrResults.currentquery = this.filterModel.getQuery();
+ }
- /**
- * Merge the attributes of other models into the corresponding models in this collection.
- * This should be used when merging models of other types (e.g. SolrResult) that represent the same
- * object that the DataONEObject models in the collection represent.
- *
- * @param {Backbone.Model[]} otherModels - the other models to merge with the models in this collection
- * @param {string[]} [fieldsToMerge] - If specified, only these fields will be extracted from the otherModels
- */
- mergeModels: function (otherModels, fieldsToMerge) {
- //If no otherModels are given, exit the function since there is nothing to merge
- if (
- typeof otherModels == "undefined" ||
- !otherModels ||
- !otherModels.length
- ) {
- return false;
- }
+ this.listenToOnce(this.solrResults, "reset", function (solrResults) {
+ //Merge the SolrResults into this collection
+ this.mergeModels(solrResults.models);
+
+ //Trigger the fetch as complete
+ this.trigger("complete");
+ });
+
+ //Query the index for this data package
+ this.solrResults.query();
+ },
+
+ /**
+ * Merge the attributes of other models into the corresponding models in this collection.
+ * This should be used when merging models of other types (e.g. SolrResult) that represent the same
+ * object that the DataONEObject models in the collection represent.
+ *
+ * @param {Backbone.Model[]} otherModels - the other models to merge with the models in this collection
+ * @param {string[]} [fieldsToMerge] - If specified, only these fields will be extracted from the otherModels
+ */
+ mergeModels: function (otherModels, fieldsToMerge) {
+ //If no otherModels are given, exit the function since there is nothing to merge
+ if (
+ typeof otherModels == "undefined" ||
+ !otherModels ||
+ !otherModels.length
+ ) {
+ return false;
+ }
- _.each(
- otherModels,
- function (otherModel) {
- //Get the model from this collection that matches ids with the other model
- var modelInDataPackage = this.findWhere({
- id: otherModel.get("id"),
- });
-
- //If a match is found,
- if (modelInDataPackage) {
- var valuesFromOtherModel;
-
- //If specific fields to merge are given, get the values for those from the other model
- if (fieldsToMerge && fieldsToMerge.length) {
- valuesFromOtherModel = _.pick(
- otherModel.toJSON(),
- fieldsToMerge
- );
- }
- //If no specific fields are given, merge (almost) all others
- else {
- //Get the default values for this model type
- var otherModelDefaults = otherModel.defaults,
- //Get a JSON object of all the attributes on this model
- otherModelAttr = otherModel.toJSON(),
- //Start an array of attributes to omit during the merge
- omitKeys = [];
-
- _.each(otherModelAttr, function (val, key) {
- //If this model's attribute is the default, don't set it on our DataONEObject model
- // because whatever value is in the DataONEObject model is better information than the default
- // value of the other model.
- if (otherModelDefaults[key] === val)
- omitKeys.push(key);
- });
-
- //Remove the properties that are still the default value
- valuesFromOtherModel = _.omit(
- otherModelAttr,
- omitKeys
- );
- }
-
- //Set the values from the other model on the model in this collection
- modelInDataPackage.set(valuesFromOtherModel);
- }
- },
- this
+ _.each(
+ otherModels,
+ function (otherModel) {
+ //Get the model from this collection that matches ids with the other model
+ var modelInDataPackage = this.findWhere({
+ id: otherModel.get("id"),
+ });
+
+ //If a match is found,
+ if (modelInDataPackage) {
+ var valuesFromOtherModel;
+
+ //If specific fields to merge are given, get the values for those from the other model
+ if (fieldsToMerge && fieldsToMerge.length) {
+ valuesFromOtherModel = _.pick(
+ otherModel.toJSON(),
+ fieldsToMerge,
);
- },
-
- /**
- * Update the relationships in this resource map when its been udpated
- */
- updateRelationships: function () {
- //Get the old id
- var oldId = this.packageModel.get("oldPid");
-
- if (!oldId) return;
-
- //Update the resource map list
- this.each(function (m) {
- var updateRMaps = _.without(m.get("resourceMap"), oldId);
- updateRMaps.push(this.packageModel.get("id"));
-
- m.set("resourceMap", updateRMaps);
- }, this);
- },
-
- saveReference: function (model) {
- //Save a reference to this collection in the model
- var currentCollections = model.get("collections");
- if (currentCollections.length > 0) {
- currentCollections.push(this);
- model.set("collections", _.uniq(currentCollections));
- } else model.set("collections", [this]);
- },
+ }
+ //If no specific fields are given, merge (almost) all others
+ else {
+ //Get the default values for this model type
+ var otherModelDefaults = otherModel.defaults,
+ //Get a JSON object of all the attributes on this model
+ otherModelAttr = otherModel.toJSON(),
+ //Start an array of attributes to omit during the merge
+ omitKeys = [];
+
+ _.each(otherModelAttr, function (val, key) {
+ //If this model's attribute is the default, don't set it on our DataONEObject model
+ // because whatever value is in the DataONEObject model is better information than the default
+ // value of the other model.
+ if (otherModelDefaults[key] === val) omitKeys.push(key);
+ });
- /**
- * Broadcast an accessPolicy across members of this package
- *
- * Note: Currently just sets the incoming accessPolicy on this
- * object and doesn't broadcast to other members (such as data).
- * How this works is likely to change in the future.
- *
- * Closely tied to the AccessPolicyView.broadcast property.
- *
- * @param {AccessPolicy} accessPolicy - The accessPolicy to
- * broadcast
- */
- broadcastAccessPolicy: function (accessPolicy) {
- if (!accessPolicy) {
- return;
- }
+ //Remove the properties that are still the default value
+ valuesFromOtherModel = _.omit(otherModelAttr, omitKeys);
+ }
- var policy = _.clone(accessPolicy);
- this.packageModel.set("accessPolicy", policy);
+ //Set the values from the other model on the model in this collection
+ modelInDataPackage.set(valuesFromOtherModel);
+ }
+ },
+ this,
+ );
+ },
- // Stop now if the package is new because we don't want force
- // a save just yet
- if (this.packageModel.isNew()) {
- return;
- }
+ /**
+ * Update the relationships in this resource map when its been udpated
+ */
+ updateRelationships: function () {
+ //Get the old id
+ var oldId = this.packageModel.get("oldPid");
+
+ if (!oldId) return;
+
+ //Update the resource map list
+ this.each(function (m) {
+ var updateRMaps = _.without(m.get("resourceMap"), oldId);
+ updateRMaps.push(this.packageModel.get("id"));
+
+ m.set("resourceMap", updateRMaps);
+ }, this);
+ },
+
+ saveReference: function (model) {
+ //Save a reference to this collection in the model
+ var currentCollections = model.get("collections");
+ if (currentCollections.length > 0) {
+ currentCollections.push(this);
+ model.set("collections", _.uniq(currentCollections));
+ } else model.set("collections", [this]);
+ },
+
+ /**
+ * Broadcast an accessPolicy across members of this package
+ *
+ * Note: Currently just sets the incoming accessPolicy on this
+ * object and doesn't broadcast to other members (such as data).
+ * How this works is likely to change in the future.
+ *
+ * Closely tied to the AccessPolicyView.broadcast property.
+ *
+ * @param {AccessPolicy} accessPolicy - The accessPolicy to
+ * broadcast
+ */
+ broadcastAccessPolicy: function (accessPolicy) {
+ if (!accessPolicy) {
+ return;
+ }
- this.packageModel.on("sysMetaUpdateError", function (e) {
- // Show a generic error. Any errors at this point are things the
- // user can't really recover from. i.e., we've already checked
- // that the user has changePermission perms and we've already
- // re-tried the request a few times
- var message =
- "There was an error sharing your dataset. Not all of your changes were applied.";
-
- // TODO: Is this really the right way to hook into the editor's
- // error notification mechanism?
- MetacatUI.appView.eml211EditorView.saveError(message);
- });
+ var policy = _.clone(accessPolicy);
+ this.packageModel.set("accessPolicy", policy);
- this.packageModel.updateSysMeta();
- },
+ // Stop now if the package is new because we don't want force
+ // a save just yet
+ if (this.packageModel.isNew()) {
+ return;
+ }
- /**
- * Tracks the upload status of DataONEObject models in this collection. If they are
- * `loading` into the DOM or `in progress` of an upload to the server, they will be considered as "loading" files.
- * @param {DataONEObject} [dataONEObject] - A model to begin tracking. Optional. If no DataONEObject is given, then only
- * the number of loading files will be calcualted and set on the packageModel.
- * @since 2.17.1
- */
- setLoadingFiles: function (dataONEObject) {
- //Set the number of loading files and the isLoadingFiles flag
- let numLoadingFiles =
- this.where({ uploadStatus: "l" }).length +
- this.where({ uploadStatus: "p" }).length;
+ this.packageModel.on("sysMetaUpdateError", function (e) {
+ // Show a generic error. Any errors at this point are things the
+ // user can't really recover from. i.e., we've already checked
+ // that the user has changePermission perms and we've already
+ // re-tried the request a few times
+ var message =
+ "There was an error sharing your dataset. Not all of your changes were applied.";
+
+ // TODO: Is this really the right way to hook into the editor's
+ // error notification mechanism?
+ MetacatUI.appView.eml211EditorView.saveError(message);
+ });
+
+ this.packageModel.updateSysMeta();
+ },
+
+ /**
+ * Tracks the upload status of DataONEObject models in this collection. If they are
+ * `loading` into the DOM or `in progress` of an upload to the server, they will be considered as "loading" files.
+ * @param {DataONEObject} [dataONEObject] - A model to begin tracking. Optional. If no DataONEObject is given, then only
+ * the number of loading files will be calcualted and set on the packageModel.
+ * @since 2.17.1
+ */
+ setLoadingFiles: function (dataONEObject) {
+ //Set the number of loading files and the isLoadingFiles flag
+ let numLoadingFiles =
+ this.where({ uploadStatus: "l" }).length +
+ this.where({ uploadStatus: "p" }).length;
+ this.packageModel.set({
+ isLoadingFiles: numLoadingFiles > 0,
+ numLoadingFiles: numLoadingFiles,
+ });
+
+ if (dataONEObject) {
+ //Listen to the upload status to update the flag
+ this.listenTo(dataONEObject, "change:uploadStatus", function () {
+ //If the object is done being successfully saved
+ if (dataONEObject.get("uploadStatus") == "c") {
+ let numLoadingFiles =
+ this.where({ uploadStatus: "l" }).length +
+ this.where({ uploadStatus: "p" }).length;
+
+ //If all models in this DataPackage have finished loading, then mark the loading as complete
+ if (!numLoadingFiles) {
this.packageModel.set({
- isLoadingFiles: numLoadingFiles > 0,
- numLoadingFiles: numLoadingFiles,
+ isLoadingFiles: false,
+ numLoadingFiles: numLoadingFiles,
});
-
- if (dataONEObject) {
- //Listen to the upload status to update the flag
- this.listenTo(
- dataONEObject,
- "change:uploadStatus",
- function () {
- //If the object is done being successfully saved
- if (dataONEObject.get("uploadStatus") == "c") {
- let numLoadingFiles =
- this.where({ uploadStatus: "l" }).length +
- this.where({ uploadStatus: "p" }).length;
-
- //If all models in this DataPackage have finished loading, then mark the loading as complete
- if (!numLoadingFiles) {
- this.packageModel.set({
- isLoadingFiles: false,
- numLoadingFiles: numLoadingFiles,
- });
- } else {
- this.packageModel.set(
- "numLoadingFiles",
- numLoadingFiles
- );
- }
- }
- }
- );
- }
- },
-
- /**
- * Returns atLocation information found in this resourceMap
- * for all the PIDs in this resourceMap
- * @returns object with PIDs as key and atLocation paths as values
- * @since 2.28.0
- */
- getAtLocation: function () {
- return this.atLocationObject;
- },
-
- /**
- * Get the absolute path from a relative path, handling '~', '..', and '.'.
- *
- * @param {string} relativePath - The relative path to be converted to an absolute path.
- * @returns {string} - The absolute path after processing '~', '..', and '.'.
- * If the result is empty, returns '/'.
- * @since 2.28.0
- */
- getAbsolutePath(relativePath) {
- // Replace ~ with an empty space
- const fullPath = relativePath.replace(/^~(?=$|\/|\\)/, "");
-
- // Process '..' and '.'
- const components = fullPath.split("/");
- const resolvedPath = components.reduce(
- (accumulator, component) => {
- if (component === "..") {
- accumulator.pop();
- } else if (component !== "." && component !== "") {
- accumulator.push(component);
- }
- return accumulator;
- },
- []
- );
-
- // Join the resolved path components with '/'
- const result = resolvedPath.join("/");
-
- return result || "/";
- },
+ } else {
+ this.packageModel.set("numLoadingFiles", numLoadingFiles);
+ }
+ }
+ });
}
- );
+ },
- return DataPackage;
+ /**
+ * Returns atLocation information found in this resourceMap
+ * for all the PIDs in this resourceMap
+ * @returns object with PIDs as key and atLocation paths as values
+ * @since 2.28.0
+ */
+ getAtLocation: function () {
+ return this.atLocationObject;
+ },
+
+ /**
+ * Get the absolute path from a relative path, handling '~', '..', and '.'.
+ *
+ * @param {string} relativePath - The relative path to be converted to an absolute path.
+ * @returns {string} - The absolute path after processing '~', '..', and '.'.
+ * If the result is empty, returns '/'.
+ * @since 2.28.0
+ */
+ getAbsolutePath(relativePath) {
+ // Replace ~ with an empty space
+ const fullPath = relativePath.replace(/^~(?=$|\/|\\)/, "");
+
+ // Process '..' and '.'
+ const components = fullPath.split("/");
+ const resolvedPath = components.reduce((accumulator, component) => {
+ if (component === "..") {
+ accumulator.pop();
+ } else if (component !== "." && component !== "") {
+ accumulator.push(component);
+ }
+ return accumulator;
+ }, []);
+
+ // Join the resolved path components with '/'
+ const result = resolvedPath.join("/");
+
+ return result || "/";
+ },
+ },
+ );
+
+ return DataPackage;
});
diff --git a/src/js/collections/Filters.js b/src/js/collections/Filters.js
index 6e1c2911b..83eedfe53 100644
--- a/src/js/collections/Filters.js
+++ b/src/js/collections/Filters.js
@@ -19,7 +19,7 @@ define([
DateFilter,
NumericFilter,
ToggleFilter,
- SpatialFilter
+ SpatialFilter,
) {
"use strict";
@@ -33,7 +33,6 @@ define([
*/
var Filters = Backbone.Collection.extend(
/** @lends Filters.prototype */ {
-
/**
* The name of this type of collection
* @type {string}
@@ -83,7 +82,7 @@ define([
}
} catch (error) {
console.log(
- "Error initializing a Filters collection. Error details: " + error
+ "Error initializing a Filters collection. Error details: " + error,
);
}
},
@@ -279,7 +278,7 @@ define([
});
} catch (error) {
console.log(
- "Error trying to find ID Filters, error details: " + error
+ "Error trying to find ID Filters, error details: " + error,
);
}
},
@@ -296,7 +295,7 @@ define([
return this.difference(this.getIdFilters());
} catch (error) {
console.log(
- "Error trying to find non-ID Filters, error details: " + error
+ "Error trying to find non-ID Filters, error details: " + error,
);
}
},
@@ -336,13 +335,13 @@ define([
groupQueryFragments.push(filterQuery);
}
},
- this
+ this,
);
//Join this group's query fragments with an OR operator
if (groupQueryFragments.length) {
var queryString = groupQueryFragments.join(
- "%20" + operator + "%20"
+ "%20" + operator + "%20",
);
if (groupQueryFragments.length > 1) {
queryString = "(" + queryString + ")";
@@ -355,7 +354,8 @@ define([
}
} catch (e) {
console.log(
- "Error creating a group query, returning a blank string. ", e
+ "Error creating a group query, returning a blank string. ",
+ e,
);
return "";
}
@@ -397,7 +397,7 @@ define([
filterModel.get("values").length &&
_.difference(
filterModel.get("values"),
- filterModel.defaults().values
+ filterModel.defaults().values,
).length) ||
(!Array.isArray(filterModel.get("values")) &&
filterModel.get("values") !== filterModel.defaults().values))
@@ -445,8 +445,8 @@ define([
return field.indexOf("geohash") > -1;
});
}
- })
- )
+ }),
+ ),
);
},
@@ -493,7 +493,7 @@ define([
exclude: true,
matchSubstring: true,
operator: "OR",
- }
+ },
]);
var query = catalogFilters.getGroupQuery(catalogFilters.models, "AND");
return query;
@@ -579,7 +579,7 @@ define([
} catch (e) {
console.log(
"Failed to remove empty Filter models from the Filters collection, error message: " +
- e
+ e,
);
}
},
@@ -598,7 +598,7 @@ define([
} catch (e) {
console.log(
"Failed to remove empty Filter models from the Filters collection, error message: " +
- e
+ e,
);
}
},
@@ -625,7 +625,7 @@ define([
return newModel;
} catch (e) {
console.log(
- "Failed to replace a Filter model in a Filters collection, " + e
+ "Failed to replace a Filter model in a Filters collection, " + e,
);
}
},
@@ -650,11 +650,11 @@ define([
} catch (e) {
console.log(
"Failed to get the index of a Filter within the collection of visible Filters, error message: " +
- e
+ e,
);
}
},
- }
+ },
);
return Filters;
});
diff --git a/src/js/collections/ObjectFormats.js b/src/js/collections/ObjectFormats.js
index d5aac8fce..ae2bea1fe 100644
--- a/src/js/collections/ObjectFormats.js
+++ b/src/js/collections/ObjectFormats.js
@@ -1,65 +1,64 @@
/* global define */
"use strict";
-define(['jquery', 'underscore', 'backbone', 'x2js', 'models/formats/ObjectFormat'],
- function($, _, Backbone, X2JS, ObjectFormat) {
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "x2js",
+ "models/formats/ObjectFormat",
+], function ($, _, Backbone, X2JS, ObjectFormat) {
+ /**
+ * @class ObjectFormats
+ * @classdesc ObjectFormats represents the DataONE object format list
+ * found at https://cn.dataone.org/cn/v2/formats, or
+ * the Coordinating Node environment configured `AppModel.d1CNBaseUrl`
+ * This collection is intended to be used as a formats cache -
+ * retrieved once, and only refreshed later if an object format
+ * isn't present when needed.
+ * @classcategory Collections
+ * @extends Backbone.Collection
+ * @constructor
+ */
+ var ObjectFormats = Backbone.Collection.extend(
+ /** @lends ObjectFormats.prototype */ {
+ model: ObjectFormat,
- /**
- * @class ObjectFormats
- * @classdesc ObjectFormats represents the DataONE object format list
- * found at https://cn.dataone.org/cn/v2/formats, or
- * the Coordinating Node environment configured `AppModel.d1CNBaseUrl`
- * This collection is intended to be used as a formats cache -
- * retrieved once, and only refreshed later if an object format
- * isn't present when needed.
- * @classcategory Collections
- * @extends Backbone.Collection
- * @constructor
- */
- var ObjectFormats = Backbone.Collection.extend(
- /** @lends ObjectFormats.prototype */{
+ /**
+ * The constructed URL of the collection
+ * (/cn/v2/formats)
+ * @returns {string} - The URL to use during fetch
+ */
+ url: function () {
+ // no need for authentication token, just the URL
+ return MetacatUI.appModel.get("formatsServiceUrl");
+ },
- model: ObjectFormat,
+ /**
+ * Retrieve the formats from the Coordinating Node
+ * @extends Backbone.Collection#fetch
+ */
+ fetch: function (options) {
+ var fetchOptions = _.extend({ dataType: "text" }, options);
- /**
- * The constructed URL of the collection
- * (/cn/v2/formats)
- * @returns {string} - The URL to use during fetch
- */
- url: function() {
+ return Backbone.Model.prototype.fetch.call(this, fetchOptions);
+ },
- // no need for authentication token, just the URL
- return MetacatUI.appModel.get("formatsServiceUrl");
+ /**
+ * Parse the XML response from the CN
+ */
+ parse: function (response) {
+ // If the collection is already parsed, just return it
+ if (typeof response === "object") return response;
- },
+ // Otherwise, parse it
+ var x2js = new X2JS();
+ var formats = x2js.xml_str2json(response);
- /**
- * Retrieve the formats from the Coordinating Node
- * @extends Backbone.Collection#fetch
- */
- fetch: function(options) {
- var fetchOptions = _.extend({dataType: "text"}, options);
+ return formats.objectFormatList.objectFormat;
+ },
+ },
+ );
- return Backbone.Model.prototype.fetch.call(this, fetchOptions);
-
- },
-
- /**
- * Parse the XML response from the CN
- */
- parse: function(response) {
-
- // If the collection is already parsed, just return it
- if ( typeof response === "object" ) return response;
-
- // Otherwise, parse it
- var x2js = new X2JS();
- var formats = x2js.xml_str2json(response);
-
- return formats.objectFormatList.objectFormat;
- }
-
- });
-
- return ObjectFormats;
+ return ObjectFormats;
});
diff --git a/src/js/collections/ProjectList.js b/src/js/collections/ProjectList.js
index d2fb153d9..a6a02cc6c 100644
--- a/src/js/collections/ProjectList.js
+++ b/src/js/collections/ProjectList.js
@@ -1,9 +1,12 @@
/* global define */
"use strict";
-define(['jquery', 'backbone', 'models/projects/Project'],
- function ($, Backbone, Project) {
- 'use strict';
- /**
+define(["jquery", "backbone", "models/projects/Project"], function (
+ $,
+ Backbone,
+ Project,
+) {
+ "use strict";
+ /**
@class ProjectList
@classdesc A ProjectList represents a collection of projects. This can be
used for a projects list view populating EML projects. It also supports loading
@@ -15,59 +18,64 @@ define(['jquery', 'backbone', 'models/projects/Project'],
@since 2.22.0
*/
- var ProjectList = Backbone.Collection.extend(
- /** @lends ProjectList.prototype */{
- model: Project,
- type: "ProjectList", //The name of this type of collection
- authToken: undefined,
- urlBase: undefined,
- urlEndpoint: "project/",
+ var ProjectList = Backbone.Collection.extend(
+ /** @lends ProjectList.prototype */ {
+ model: Project,
+ type: "ProjectList", //The name of this type of collection
+ authToken: undefined,
+ urlBase: undefined,
+ urlEndpoint: "project/",
- /** Builds the url from the urlBase **/
- url: function(){
- if(this.urlBase)
- {
- return new URL(new URL(this.urlBase).pathname + this.urlEndpoint, this.urlBase).href
- }
- else {
- return undefined
- }
- },
- /**
- * Override backbone's parse to set the data after the request returns from the server
- */
- parse: function (response, options) {
- // Add any custom data structure code here.
- return response
- },
- /**
- * Override backbone's sync to set the auth token
- */
- sync: function(method, model, options) {
+ /** Builds the url from the urlBase **/
+ url: function () {
+ if (this.urlBase) {
+ return new URL(
+ new URL(this.urlBase).pathname + this.urlEndpoint,
+ this.urlBase,
+ ).href;
+ } else {
+ return undefined;
+ }
+ },
+ /**
+ * Override backbone's parse to set the data after the request returns from the server
+ */
+ parse: function (response, options) {
+ // Add any custom data structure code here.
+ return response;
+ },
+ /**
+ * Override backbone's sync to set the auth token
+ */
+ sync: function (method, model, options) {
+ if (this.authToken) {
+ if (options.headers === undefined) {
+ options.headers = {};
+ }
+ options.headers["Authorization"] = "Bearer " + this.authToken;
+ }
+ if (this.urlBase)
+ return Backbone.Model.prototype.sync.apply(this, [
+ method,
+ model,
+ options,
+ ]);
+ },
- if (this.authToken) {
- if (options.headers === undefined){
- options.headers = {}
- }
- options.headers["Authorization"] = "Bearer " + this.authToken;
- }
- if (this.urlBase)
- return Backbone.Model.prototype.sync.apply(this, [method, model, options]);
- },
+ /**
+ * Initializing the Model objects project variables.
+ */
+ initialize: function (options) {
+ if (MetacatUI && MetacatUI.appModel)
+ this.urlBase = MetacatUI.appModel.get("projectsApiUrl");
+ if (options) {
+ if (options.authToken) this.authToken = options.authToken;
+ if (options.urlBase) this.urlBase = options.urlBase;
+ }
+ Backbone.Collection.prototype.initialize.apply(this, options);
+ },
+ },
+ );
- /**
- * Initializing the Model objects project variables.
- */
- initialize: function (options) {
- if (MetacatUI && MetacatUI.appModel) this.urlBase = MetacatUI.appModel.get("projectsApiUrl");
- if (options) {
- if (options.authToken) this.authToken = options.authToken;
- if (options.urlBase) this.urlBase = options.urlBase;
- }
- Backbone.Collection.prototype.initialize.apply(this, options);
- }
- });
-
- return ProjectList
-
- });
+ return ProjectList;
+});
diff --git a/src/js/collections/QualityReport.js b/src/js/collections/QualityReport.js
index 3d8fd779a..0989bc9e2 100644
--- a/src/js/collections/QualityReport.js
+++ b/src/js/collections/QualityReport.js
@@ -1,10 +1,14 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'rdflib', "uuid", "md5",
- 'models/QualityCheckModel'
- ],
- function ($, _, Backbone, rdf, uuid, md5, QualityCheck) {
-
- /**
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "rdflib",
+ "uuid",
+ "md5",
+ "models/QualityCheckModel",
+], function ($, _, Backbone, rdf, uuid, md5, QualityCheck) {
+ /**
@class QualityReport
@classdesc A DataPackage represents a hierarchical collection of
packages, metadata, and data objects, modeling an OAI-ORE RDF graph.
@@ -13,9 +17,8 @@ define(['jquery', 'underscore', 'backbone', 'rdflib', "uuid", "md5",
@extends Backbone.Collection
@constructor
*/
- var QualityReport = Backbone.Collection.extend(
- /** @lends QualityReport.prototype */{
-
+ var QualityReport = Backbone.Collection.extend(
+ /** @lends QualityReport.prototype */ {
//The name of this type of collection
type: "QualityReport",
runStatus: null,
@@ -23,8 +26,7 @@ define(['jquery', 'underscore', 'backbone', 'rdflib', "uuid", "md5",
timestamp: null,
initialize: function (models, options) {
- if (typeof options == "undefined")
- var options = {};
+ if (typeof options == "undefined") var options = {};
//Set the id or create a new one
this.id = options.pid || "urn:uuid:" + uuid.v4();
@@ -41,7 +43,7 @@ define(['jquery', 'underscore', 'backbone', 'rdflib', "uuid", "md5",
*/
model: QualityCheck,
- parse: function(response, options) {
+ parse: function (response, options) {
// runStatus can be one of "success", "failure", "queued"
this.runStatus = response.runStatus;
this.errorDescription = response.errorDescription;
@@ -49,32 +51,35 @@ define(['jquery', 'underscore', 'backbone', 'rdflib', "uuid", "md5",
return response.result;
},
- fetch: function(options) {
+ fetch: function (options) {
var collectionRef = this;
var fetchOptions = {};
- if((typeof options != "undefined")) {
+ if (typeof options != "undefined") {
fetchOptions = _.extend(options, {
- url: options.url,
- cache: false,
- contentType: false, //"multipart/form-data",
- processData: false,
- type: 'GET',
- //headers: { 'Access-Control-Allow-Origin': 'http://localhost:8081' },
- headers: {
- 'Accept': 'application/json'
- },
- success: function (collection, jqXhr, options) {
- //collectionRef.run = data;
- collectionRef.trigger("fetchComplete");
- },
- error: function (collection, jqXhr, options) {
- console.debug("error fetching quality report.");
- collectionRef.fetchResponse = jqXhr;
- collectionRef.trigger("fetchError");
- }
- });
+ url: options.url,
+ cache: false,
+ contentType: false, //"multipart/form-data",
+ processData: false,
+ type: "GET",
+ //headers: { 'Access-Control-Allow-Origin': 'http://localhost:8081' },
+ headers: {
+ Accept: "application/json",
+ },
+ success: function (collection, jqXhr, options) {
+ //collectionRef.run = data;
+ collectionRef.trigger("fetchComplete");
+ },
+ error: function (collection, jqXhr, options) {
+ console.debug("error fetching quality report.");
+ collectionRef.fetchResponse = jqXhr;
+ collectionRef.trigger("fetchError");
+ },
+ });
//fetchOptions = _.extend(fetchOptions, MetacatUI.appUserModel.createAjaxSettings());
- return Backbone.Collection.prototype.fetch.call(collectionRef, fetchOptions);
+ return Backbone.Collection.prototype.fetch.call(
+ collectionRef,
+ fetchOptions,
+ );
}
},
@@ -86,31 +91,31 @@ define(['jquery', 'underscore', 'backbone', 'rdflib', "uuid", "md5",
var status = result.get("status");
// simple cases
// always blue for info and skip
- if (check.level == 'INFO') {
- color = 'BLUE';
+ if (check.level == "INFO") {
+ color = "BLUE";
return color;
}
- if (status == 'SKIP') {
- color = 'BLUE';
+ if (status == "SKIP") {
+ color = "BLUE";
return color;
}
// always green for success
- if (status == 'SUCCESS') {
- color = 'GREEN';
+ if (status == "SUCCESS") {
+ color = "GREEN";
return color;
}
// handle failures and warnings
- if (status == 'FAILURE') {
- color = 'RED';
- if (check.level == 'OPTIONAL') {
- color = 'ORANGE';
+ if (status == "FAILURE") {
+ color = "RED";
+ if (check.level == "OPTIONAL") {
+ color = "ORANGE";
}
}
- if (status == 'ERROR') {
- color = 'ORANGE';
- if (check.level == 'REQUIRED') {
- color = 'RED';
+ if (status == "ERROR") {
+ color = "ORANGE";
+ if (check.level == "REQUIRED") {
+ color = "RED";
}
}
return color;
@@ -139,7 +144,6 @@ define(['jquery', 'underscore', 'backbone', 'rdflib', "uuid", "md5",
groupByType: function (results) {
var groupedResults = _.groupBy(results, function (result) {
-
var check = result.get("check");
var status = result.get("status");
@@ -152,12 +156,12 @@ define(['jquery', 'underscore', 'backbone', 'rdflib', "uuid", "md5",
return "removeMe";
}
- var type = ""
+ var type = "";
// Convert check type to lower case, so that the checks will be
// grouped correctly, even if one check type has an incorrect capitalization.
- if(check.type != null) {
+ if (check.type != null) {
// Normalize check type by converting entire string to lowercase
- type = check.type.toLowerCase()
+ type = check.type.toLowerCase();
// Now convert to title case
type = type.charAt(0).toUpperCase() + type.slice(1);
}
@@ -169,7 +173,8 @@ define(['jquery', 'underscore', 'backbone', 'rdflib', "uuid", "md5",
delete groupedResults["removeMe"];
return groupedResults;
- }
- });
+ },
+ },
+ );
return QualityReport;
});
diff --git a/src/js/collections/SolrResults.js b/src/js/collections/SolrResults.js
index c12f1a81e..9373ea182 100644
--- a/src/js/collections/SolrResults.js
+++ b/src/js/collections/SolrResults.js
@@ -1,7 +1,12 @@
/*global define */
-define(['jquery', 'underscore', 'backbone', 'models/SolrHeader', 'models/SolrResult'],
- function($, _, Backbone, SolrHeader, SolrResult) {
- 'use strict';
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/SolrHeader",
+ "models/SolrResult",
+], function ($, _, Backbone, SolrHeader, SolrResult) {
+ "use strict";
/**
@class SolrResults
@@ -11,179 +16,185 @@ define(['jquery', 'underscore', 'backbone', 'models/SolrHeader', 'models/SolrRes
@constructor
*/
var SolrResults = Backbone.Collection.extend(
- /** @lends SolrResults.prototype */{
-
- // Reference to this collection's model.
- model: SolrResult,
-
- /**
- * The name of this type of collection.
- * @type {string}
- * @default "SolrResults"
- * @since 2.25.0
- */
- type: "SolrResults",
-
- initialize: function(models, options) {
-
- if( typeof options === "undefined" || !options ){
- var options = {};
- }
-
- this.docsCache = options.docsCache || null;
- this.currentquery = options.query || '*:*';
- this.rows = options.rows || 25;
- this.start = options.start || 0;
- this.sort = options.sort || 'dateUploaded desc';
- this.facet = options.facet || [];
- this.facetCounts = "nothing";
- this.stats = options.stats || false;
- this.minYear = options.minYear || 1900;
- this.maxYear = options.maxYear || new Date().getFullYear();
- this.queryServiceUrl = options.queryServiceUrl || MetacatUI.appModel.get('queryServiceUrl');
-
- if( MetacatUI.appModel.get("defaultSearchFields")?.length )
- this.fields = MetacatUI.appModel.get("defaultSearchFields").join(",");
- else
- this.fields = options.fields || "id,title";
-
-
- //If POST queries are disabled in the whole app, don't use POSTs here
- if( MetacatUI.appModel.get("disableQueryPOSTs") ){
- this.usePOST = false;
- }
- //If this collection was initialized with the usePOST option, use POSTs here
- else if( options.usePOST ){
- this.usePOST = true;
- }
- //Otherwise default to using GET
- else{
- this.usePOST = false;
- }
- },
-
- url: function() {
- //Convert facet keywords to a string
- var facetFields = "";
-
- this.facet = _.uniq(this.facet);
-
- for (var i=0; i 0) {
- facetFields += "&facet.mincount=1"; // only facets meeting the current search
- facetFields += "&facet.limit=-1"; // CAREFUL: -1 means no limit on the number of facets
- }
-
- //Do we need stats?
- if (!this.stats){
- var stats = "";
- }
- else{
- var stats = "&stats=true";
- for(var i=0; i 0 )
- endpoint += "&facet=true&facet.sort=index" + facetFields;
+ url: function () {
+ //Convert facet keywords to a string
+ var facetFields = "";
- endpoint += stats + "&wt=json";
+ this.facet = _.uniq(this.facet);
- return endpoint;
- },
+ for (var i = 0; i < this.facet.length; i++) {
+ facetFields += "&facet.field=" + this.facet[i];
+ }
+ // limit to matches
+ if (this.facet.length > 0) {
+ facetFields += "&facet.mincount=1"; // only facets meeting the current search
+ facetFields += "&facet.limit=-1"; // CAREFUL: -1 means no limit on the number of facets
+ }
- parse: function(solr) {
+ //Do we need stats?
+ if (!this.stats) {
+ var stats = "";
+ } else {
+ var stats = "&stats=true";
+ for (var i = 0; i < this.stats.length; i++) {
+ stats += "&stats.field=" + this.stats[i];
+ }
+ }
- //Is this our latest query? If not, use our last set of docs from the latest query
- if((decodeURIComponent(this.currentquery).replace(/\+/g, " ") != solr.responseHeader.params.q) && this.docsCache)
- return this.docsCache;
+ //create the query url
+ var endpoint =
+ (this.queryServiceUrl || MetatcatUI.appModel.get("queryServiceUrl")) +
+ "q=" +
+ this.currentquery;
+
+ if (this.fields) endpoint += "&fl=" + this.fields;
+ if (this.sort) endpoint += "&sort=" + this.sort;
+ if (
+ typeof this.rows == "number" ||
+ (typeof this.rows == "string" && this.rows.length)
+ )
+ endpoint += "&rows=" + this.rows;
+ if (
+ typeof this.start == "number" ||
+ (typeof this.start == "string" && this.start.length)
+ )
+ endpoint += "&start=" + this.start;
+ if (this.facet.length > 0)
+ endpoint += "&facet=true&facet.sort=index" + facetFields;
+
+ endpoint += stats + "&wt=json";
+
+ return endpoint;
+ },
+
+ parse: function (solr) {
+ //Is this our latest query? If not, use our last set of docs from the latest query
+ if (
+ decodeURIComponent(this.currentquery).replace(/\+/g, " ") !=
+ solr.responseHeader.params.q &&
+ this.docsCache
+ )
+ return this.docsCache;
+
+ if (!solr.response) {
+ if (solr.error && solr.error.msg) {
+ console.log("Solr error: " + solr.error.msg);
+ }
+ return;
+ }
- if(!solr.response){
- if(solr.error && solr.error.msg){
- console.log("Solr error: " + solr.error.msg);
+ //Save some stats
+ this.header = new SolrHeader(solr.responseHeader);
+ this.header.set({ numFound: solr.response.numFound });
+ this.header.set({ start: solr.response.start });
+ this.header.set({ rows: solr.responseHeader.params.rows });
+
+ //Get the facet counts and store them in this model
+ if (solr.facet_counts) {
+ this.facetCounts = solr.facet_counts.facet_fields;
+ } else {
+ this.facetCounts = "nothing";
}
- return
- }
-
- //Save some stats
- this.header = new SolrHeader(solr.responseHeader);
- this.header.set({"numFound" : solr.response.numFound});
- this.header.set({"start" : solr.response.start});
- this.header.set({"rows" : solr.responseHeader.params.rows});
-
- //Get the facet counts and store them in this model
- if (solr.facet_counts) {
- this.facetCounts = solr.facet_counts.facet_fields;
- } else {
- this.facetCounts = "nothing";
- }
- //Cache this set of results
- this.docsCache = solr.response.docs;
+ //Cache this set of results
+ this.docsCache = solr.response.docs;
- return solr.response.docs;
- },
+ return solr.response.docs;
+ },
- /**
- * Fetches the next page of results
- */
- nextpage: function() {
- // Only increment the page if the current page is not the last page
- if (this.start + this.rows < this.header.get("numFound")) {
- this.start += this.rows;
- }
- if (this.header != null) {
- this.header.set({"start" : this.start});
- }
+ /**
+ * Fetches the next page of results
+ */
+ nextpage: function () {
+ // Only increment the page if the current page is not the last page
+ if (this.start + this.rows < this.header.get("numFound")) {
+ this.start += this.rows;
+ }
+ if (this.header != null) {
+ this.header.set({ start: this.start });
+ }
- this.lastUrl = this.url();
+ this.lastUrl = this.url();
- var fetchOptions = this.createFetchOptions();
- this.fetch(fetchOptions);
- },
+ var fetchOptions = this.createFetchOptions();
+ this.fetch(fetchOptions);
+ },
- /**
- * Fetches the previous page of results
- */
- prevpage: function() {
- this.start -= this.rows;
- if (this.start < 0) {
- this.start = 0;
- }
- if (this.header != null) {
- this.header.set({"start" : this.start});
- }
+ /**
+ * Fetches the previous page of results
+ */
+ prevpage: function () {
+ this.start -= this.rows;
+ if (this.start < 0) {
+ this.start = 0;
+ }
+ if (this.header != null) {
+ this.header.set({ start: this.start });
+ }
- this.lastUrl = this.url();
+ this.lastUrl = this.url();
- var fetchOptions = this.createFetchOptions();
- this.fetch(fetchOptions);
- },
+ var fetchOptions = this.createFetchOptions();
+ this.fetch(fetchOptions);
+ },
- /**
- * Fetches the given page of results
- * @param {number} page
- */
- toPage: function(page) {
- // go to the requested page
- var requestedStart = this.rows * page;
+ /**
+ * Fetches the given page of results
+ * @param {number} page
+ */
+ toPage: function (page) {
+ // go to the requested page
+ var requestedStart = this.rows * page;
- /*
+ /*
if (this.header != null) {
if (requestedStart < this.header.get("numFound")) {
this.start = requestedStart;
@@ -191,229 +202,222 @@ define(['jquery', 'underscore', 'backbone', 'models/SolrHeader', 'models/SolrRes
this.header.set({"start" : this.start});
}*/
- this.start = requestedStart;
-
- this.lastUrl = this.url();
-
- var fetchOptions = this.createFetchOptions();
- this.fetch(fetchOptions);
- },
-
- setrows: function(numrows) {
- this.rows = numrows;
- },
-
- query: function(newquery) {
-
- if(typeof newquery != "undefined" && this.currentquery != newquery){
- this.currentquery = newquery;
- this.start = 0;
- }
-
- this.lastUrl = this.url();
-
- var fetchOptions = this.createFetchOptions();
- this.fetch(fetchOptions);
- },
-
- setQuery: function(newquery) {
- if (this.currentquery != newquery) {
- this.currentquery = newquery;
- this.start = 0;
- this.lastQuery = newquery;
- }
- },
-
- /**
- * Returns the last query that was fetched.
- * @returns {string}
- */
- getLastQuery: function(){
- return this.lastQuery;
- },
-
- setfields: function(newfields) {
- this.fields = newfields;
- },
-
- setSort: function(newsort) {
- this.sort = newsort;
- this.trigger("change:sort");
- },
-
- setFacet: function (fields) {
- if (!Array.isArray(fields)) {
- fields = [fields];
- }
- this.facet = fields;
- this.trigger("change:facet");
- },
-
- setStats: function(fields){
- this.stats = fields;
- },
-
- createFetchOptions: function(){
- var options = {
- start : this.start,
- reset: true
- }
+ this.start = requestedStart;
- let usePOST = this.usePOST || (this.currentquery.length > 1500 && !MetacatUI.appModel.get("disableQueryPOSTs"));
+ this.lastUrl = this.url();
- if( usePOST ){
- options.type = "POST";
+ var fetchOptions = this.createFetchOptions();
+ this.fetch(fetchOptions);
+ },
- var queryData = new FormData();
- queryData.append("q", decodeURIComponent(this.currentquery));
- queryData.append("rows", this.rows);
- queryData.append("sort", this.sort.replace("+", " "));
- queryData.append("fl", this.fields);
- queryData.append("start", this.start);
- queryData.append("wt", "json");
+ setrows: function (numrows) {
+ this.rows = numrows;
+ },
- //Add the facet fields to the FormData
- if( this.facet.length ){
-
- queryData.append("facet", "true");
+ query: function (newquery) {
+ if (typeof newquery != "undefined" && this.currentquery != newquery) {
+ this.currentquery = newquery;
+ this.start = 0;
+ }
- for (var i=0; i 1500 &&
+ !MetacatUI.appModel.get("disableQueryPOSTs"));
+
+ if (usePOST) {
+ options.type = "POST";
+
+ var queryData = new FormData();
+ queryData.append("q", decodeURIComponent(this.currentquery));
+ queryData.append("rows", this.rows);
+ queryData.append("sort", this.sort.replace("+", " "));
+ queryData.append("fl", this.fields);
+ queryData.append("start", this.start);
+ queryData.append("wt", "json");
+
+ //Add the facet fields to the FormData
+ if (this.facet.length) {
+ queryData.append("facet", "true");
+
+ for (var i = 0; i < this.facet.length; i++) {
+ queryData.append("facet.field", this.facet[i]);
+ }
+
+ queryData.append("facet.mincount", "1");
+ queryData.append("facet.limit", "-1");
+ queryData.append("facet.sort", "index");
+ }
- //Add stats to the FormData
- if( this.stats.length ){
-
- queryData.append("stats", "true");
+ //Add stats to the FormData
+ if (this.stats.length) {
+ queryData.append("stats", "true");
- for(var i=0; i -1)){
- collection.nameAvailable = true;
- collection.trigger("nameChecked", collection);
- }
- }
- return Backbone.Collection.prototype.fetch.call(this, options);
- },
-
- /*
- * Backbone.js override - parses the XML reponse from DataONE and creates a JSON representation that will
- * be used to create UserModels
- */
- parse: function(response, options){
- if(!response) return;
-
- //This group name is not available/already taken
- this.nameAvailable = false;
- this.trigger("nameChecked", this);
-
- var group = $(response).find("group subject:contains('" + this.groupId + "')").parent("group"),
- people = $(response).find("person"),
- collection = this,
- toAdd = new Array(),
- existing = this.pluck("username");
-
- if(!people.length)
- people = $(group).find("hasMember");
-
- //Make all existing usernames lowercase for string matching
- if(existing.length) existing = _.invoke(existing, "toLowerCase");
-
- this.name = $(group).children("groupName").text();
-
- _.each(people, function(person){
-
- //The tag name is "hasMember" if we retrieved info about this group from the group nodes only
- if(person.tagName == "hasMember"){
- var username = $(person).text();
-
- //If this user is already in the group, skip adding it
- if(_.contains(existing, username.toLowerCase())) return;
-
- var user = new UserModel({ username: username }),
- userAttr = user.toJSON();
-
- toAdd.push(userAttr);
- }
- //The tag name is "person" if we retrieved info about this group through the /accounts service, which includes all nodes about all members
- else{
- //If this user is not listed as a member of this group, skip it
- if($(person).children("isMemberOf:contains('" + collection.groupId + "')").length < 1)
- return;
-
- //Username of this person
- var username = $(person).children("subject").text();
-
- //If this user is already in the group, skip adding it
- if(_.contains(existing, username.toLowerCase())) return;
-
- //User attributes - pass the full response for the UserModel to parse
- var userAttr = new UserModel({username: username}).parseXML(response);
-
- //Add to collection
- toAdd.push(userAttr);
- }
- });
-
- return toAdd;
- },
-
- /*
- * An alternative to Backbone sync
- * - will send a POST request to DataONE CNIdentity.createGroup() to create this collection as a new DataONE group
- * or
- * - will send a PUT request to DataONE CNIdentity.updateGroup() to update this existing DataONE group
- *
- * If this group is marked as pending, then the group is created, otherwise it's updated
- */
- save: function(onSuccess, onError){
- if(this.pending && (this.nameAvailable == false)) return false;
-
- var memberXML = "",
- ownerXML = "",
- collection = this;
-
- //Create the member and owner XML
- this.forEach(function(member){
- //Don't list yourself as an owner or member (implied)
- if(MetacatUI.appUserModel == member) return;
-
- var username = member.get("username") ? member.get("username").trim() : null;
- if(!username) return;
-
- memberXML += "" + username + " ";
-
- if(collection.isOwner(member))
- ownerXML += "" + username + " ";
- });
-
- //Create the group XML
- var groupXML =
- ''
- + ''
- + '' + this.groupId + ' '
- + '' + this.name + ' '
- + memberXML
- + ownerXML
- + ' ';
-
- var xmlBlob = new Blob([groupXML], {type : 'application/xml'});
- var formData = new FormData();
- formData.append("group", xmlBlob, "group");
-
- // AJAX call to update
- $.ajax({
- type: this.pending? "POST" : "PUT",
- cache: false,
- contentType: false,
- processData: false,
- xhrFields: {
- withCredentials: true
- },
- headers: {
- "Authorization": "Bearer " + MetacatUI.appUserModel.get("token")
- },
- url: MetacatUI.appModel.get("groupsUrl"),
- data: formData,
- success: function(data, textStatus, xhr) {
- if(typeof onSuccess != "undefined")
- onSuccess(data);
-
- collection.pending = false;
- collection.nameAvailable = null;
- collection.getGroup();
- },
- error: function(xhr, textStatus, error) {
- if(typeof onError != "undefined")
- onError(xhr);
- }
- });
-
- return true;
- },
-
- /*
- * For pending groups only (those in the creation stage)
- * Will check if the given name/id is available
- */
- checkName: function(name){
- //Only check the name for pending groups
- if(!this.pending) return;
-
- //Reset the name and ID
- this.name = name || this.name;
- this.groupId = null;
- this.nameAvailable = null;
-
- //Get group info/check name availablity
- this.getGroup({ add: false });
- },
-
- /*
- * Retrieves the UserModels that are rightsHolders of this group
- */
- getOwners: function(){
- var groupId = this.groupId;
- return _.filter(this.models, function(user){
- return _.contains(user.get("isOwnerOf"), groupId);
- });
- },
-
- /*
- * Shortcut function - will check if a specified User is an owner of this group
- */
- isOwner: function(model){
- if(typeof model === "undefined") return false;
-
- if(this.pending && (model == MetacatUI.appUserModel)) return true;
-
- var usernames = [];
- _.each(this.getOwners(), function(user){ usernames.push(user.get("username")); });
-
- return _.contains(usernames, model.get("username"));
- }
-
- });
-
- return UserGroup;
-
+define(["jquery", "underscore", "backbone", "models/UserModel"], function (
+ $,
+ _,
+ Backbone,
+ UserModel,
+) {
+ "use strict";
+
+ /**
+ * @class UserGroup
+ * @classdesc The collection of Users that represent a DataONE group
+ * @classcategory Collections
+ * @extends Backbone.Collection
+ */
+ var UserGroup = Backbone.Collection.extend(
+ /** @lends UserGroup.prototype */ {
+ // Reference to this collection's model.
+ model: UserModel,
+
+ //Custom attributes of groups
+ groupId: "",
+ name: "",
+ nameAvailable: null,
+
+ url: function () {
+ return (
+ MetacatUI.appModel.get("accountsUrl") +
+ encodeURIComponent(this.groupId)
+ );
+ },
+
+ comparator: "lastName", //Sort by last name
+
+ initialize: function (models, options) {
+ if (typeof models == "undefined" || !models) var models = [];
+
+ if (typeof options !== "undefined") {
+ //Save our options
+ $.extend(this, options);
+ this.groupId = options.groupId || "";
+ this.name = options.name || "";
+ this.pending =
+ typeof options.pending === "undefined" ? false : options.pending;
+
+ //If raw data is passed, parse it to get a list of users to be added to this group
+ if (options.rawData) {
+ //Get a list of UserModel attributes to add to this collection
+ var toAdd = this.parse(options.rawData);
+
+ //Create a UserModel for each user
+ _.each(toAdd, function (modelAttributes) {
+ //Don't pass the raw data to the UserModel creation because it is redundant-
+ //We already parsed the raw data when we called add() above
+ var rawDataSave = modelAttributes.rawData;
+ modelAttributes.rawData = null;
+
+ //Create the model then add the raw data back
+ var member = new UserModel(modelAttributes);
+ member.set("rawData", rawDataSave);
+
+ models.push(member);
+ });
+ }
+ }
+
+ //Add all our models to this collection
+ this.add(models);
+ },
+
+ /*
+ * Gets the group from the server. Options object uses the BackboneJS options API
+ */
+ getGroup: function (options) {
+ if (!this.groupId && this.name) {
+ this.groupId = "CN=" + this.name + ",DC=dataone,DC=org";
+ }
+
+ this.fetch(options);
+
+ return this;
+ },
+
+ /*
+ * Fetches the group info from the server. Should not be called directly - use getGroup() instead
+ */
+ fetch: function (options) {
+ options = options || { silent: false, reset: false, remove: false };
+ options.dataType = "xml";
+ options.error = function (collection, response, options) {
+ //If this group is not found, then the name is available
+ if (
+ response.status == 404 &&
+ response.responseText.indexOf("No Such Object") > -1
+ ) {
+ collection.nameAvailable = true;
+ collection.trigger("nameChecked", collection);
+ }
+ };
+ return Backbone.Collection.prototype.fetch.call(this, options);
+ },
+
+ /*
+ * Backbone.js override - parses the XML reponse from DataONE and creates a JSON representation that will
+ * be used to create UserModels
+ */
+ parse: function (response, options) {
+ if (!response) return;
+
+ //This group name is not available/already taken
+ this.nameAvailable = false;
+ this.trigger("nameChecked", this);
+
+ var group = $(response)
+ .find("group subject:contains('" + this.groupId + "')")
+ .parent("group"),
+ people = $(response).find("person"),
+ collection = this,
+ toAdd = new Array(),
+ existing = this.pluck("username");
+
+ if (!people.length) people = $(group).find("hasMember");
+
+ //Make all existing usernames lowercase for string matching
+ if (existing.length) existing = _.invoke(existing, "toLowerCase");
+
+ this.name = $(group).children("groupName").text();
+
+ _.each(people, function (person) {
+ //The tag name is "hasMember" if we retrieved info about this group from the group nodes only
+ if (person.tagName == "hasMember") {
+ var username = $(person).text();
+
+ //If this user is already in the group, skip adding it
+ if (_.contains(existing, username.toLowerCase())) return;
+
+ var user = new UserModel({ username: username }),
+ userAttr = user.toJSON();
+
+ toAdd.push(userAttr);
+ }
+ //The tag name is "person" if we retrieved info about this group through the /accounts service, which includes all nodes about all members
+ else {
+ //If this user is not listed as a member of this group, skip it
+ if (
+ $(person).children(
+ "isMemberOf:contains('" + collection.groupId + "')",
+ ).length < 1
+ )
+ return;
+
+ //Username of this person
+ var username = $(person).children("subject").text();
+
+ //If this user is already in the group, skip adding it
+ if (_.contains(existing, username.toLowerCase())) return;
+
+ //User attributes - pass the full response for the UserModel to parse
+ var userAttr = new UserModel({ username: username }).parseXML(
+ response,
+ );
+
+ //Add to collection
+ toAdd.push(userAttr);
+ }
+ });
+
+ return toAdd;
+ },
+
+ /*
+ * An alternative to Backbone sync
+ * - will send a POST request to DataONE CNIdentity.createGroup() to create this collection as a new DataONE group
+ * or
+ * - will send a PUT request to DataONE CNIdentity.updateGroup() to update this existing DataONE group
+ *
+ * If this group is marked as pending, then the group is created, otherwise it's updated
+ */
+ save: function (onSuccess, onError) {
+ if (this.pending && this.nameAvailable == false) return false;
+
+ var memberXML = "",
+ ownerXML = "",
+ collection = this;
+
+ //Create the member and owner XML
+ this.forEach(function (member) {
+ //Don't list yourself as an owner or member (implied)
+ if (MetacatUI.appUserModel == member) return;
+
+ var username = member.get("username")
+ ? member.get("username").trim()
+ : null;
+ if (!username) return;
+
+ memberXML += "" + username + " ";
+
+ if (collection.isOwner(member))
+ ownerXML += "" + username + " ";
+ });
+
+ //Create the group XML
+ var groupXML =
+ '' +
+ '' +
+ "" +
+ this.groupId +
+ " " +
+ "" +
+ this.name +
+ " " +
+ memberXML +
+ ownerXML +
+ " ";
+
+ var xmlBlob = new Blob([groupXML], { type: "application/xml" });
+ var formData = new FormData();
+ formData.append("group", xmlBlob, "group");
+
+ // AJAX call to update
+ $.ajax({
+ type: this.pending ? "POST" : "PUT",
+ cache: false,
+ contentType: false,
+ processData: false,
+ xhrFields: {
+ withCredentials: true,
+ },
+ headers: {
+ Authorization: "Bearer " + MetacatUI.appUserModel.get("token"),
+ },
+ url: MetacatUI.appModel.get("groupsUrl"),
+ data: formData,
+ success: function (data, textStatus, xhr) {
+ if (typeof onSuccess != "undefined") onSuccess(data);
+
+ collection.pending = false;
+ collection.nameAvailable = null;
+ collection.getGroup();
+ },
+ error: function (xhr, textStatus, error) {
+ if (typeof onError != "undefined") onError(xhr);
+ },
+ });
+
+ return true;
+ },
+
+ /*
+ * For pending groups only (those in the creation stage)
+ * Will check if the given name/id is available
+ */
+ checkName: function (name) {
+ //Only check the name for pending groups
+ if (!this.pending) return;
+
+ //Reset the name and ID
+ this.name = name || this.name;
+ this.groupId = null;
+ this.nameAvailable = null;
+
+ //Get group info/check name availablity
+ this.getGroup({ add: false });
+ },
+
+ /*
+ * Retrieves the UserModels that are rightsHolders of this group
+ */
+ getOwners: function () {
+ var groupId = this.groupId;
+ return _.filter(this.models, function (user) {
+ return _.contains(user.get("isOwnerOf"), groupId);
+ });
+ },
+
+ /*
+ * Shortcut function - will check if a specified User is an owner of this group
+ */
+ isOwner: function (model) {
+ if (typeof model === "undefined") return false;
+
+ if (this.pending && model == MetacatUI.appUserModel) return true;
+
+ var usernames = [];
+ _.each(this.getOwners(), function (user) {
+ usernames.push(user.get("username"));
+ });
+
+ return _.contains(usernames, model.get("username"));
+ },
+ },
+ );
+
+ return UserGroup;
});
diff --git a/src/js/collections/bookkeeper/Quotas.js b/src/js/collections/bookkeeper/Quotas.js
index 1a51d3985..4e45b246b 100644
--- a/src/js/collections/bookkeeper/Quotas.js
+++ b/src/js/collections/bookkeeper/Quotas.js
@@ -1,8 +1,11 @@
"use strict";
-define(["jquery", "underscore", "backbone", "models/bookkeeper/Quota"],
- function($, _, Backbone, Quota) {
-
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/bookkeeper/Quota",
+], function ($, _, Backbone, Quota) {
/**
* @class Quotas
* @classdesc Quotas are limits set for a particular DataONE Product, such as the number
@@ -14,88 +17,82 @@ define(["jquery", "underscore", "backbone", "models/bookkeeper/Quota"],
*/
var Quotas = Backbone.Collection.extend(
/** @lends Quotas.prototype */ {
-
- /**
- * The class/model that is contained in this collection.
- * @type {Backbone.Model}
- */
- model: Quota,
-
- /**
- * A list of query parameters that are supported by the Bookkeeper Quotas API. These
- * query parameters can be passed to {@link Quotas#fetch} in the `options` object, and they
- * will be used during the fetch.
- * @type {string[]}
- */
- queryParams: ["quotaType", "subscriber"],
-
- /**
- * Constructs a URL string for fetching this collection and returns it
- * @param {Object} [options]
- * @property {string} options.quotaType The Usage quotaType to fetch
- * @property {string} options.subscriber The user or group subject associated with these Quotas
- * @returns {string} The URL string
- */
- url: function(options){
-
- var url = "";
-
- //Use the attributes from the options object for the URL, if it is passed to this function
- if( typeof options == "object" ){
-
- _.each( this.queryParams, function(name){
- if( typeof options[name] !== "undefined"){
- if( url.length == 0 ){
- url += "?";
- }
- else{
- url += "&";
+ /**
+ * The class/model that is contained in this collection.
+ * @type {Backbone.Model}
+ */
+ model: Quota,
+
+ /**
+ * A list of query parameters that are supported by the Bookkeeper Quotas API. These
+ * query parameters can be passed to {@link Quotas#fetch} in the `options` object, and they
+ * will be used during the fetch.
+ * @type {string[]}
+ */
+ queryParams: ["quotaType", "subscriber"],
+
+ /**
+ * Constructs a URL string for fetching this collection and returns it
+ * @param {Object} [options]
+ * @property {string} options.quotaType The Usage quotaType to fetch
+ * @property {string} options.subscriber The user or group subject associated with these Quotas
+ * @returns {string} The URL string
+ */
+ url: function (options) {
+ var url = "";
+
+ //Use the attributes from the options object for the URL, if it is passed to this function
+ if (typeof options == "object") {
+ _.each(this.queryParams, function (name) {
+ if (typeof options[name] !== "undefined") {
+ if (url.length == 0) {
+ url += "?";
+ } else {
+ url += "&";
+ }
+
+ url += name + "=" + encodeURIComponent(options[name]);
}
-
- url += name + "=" + encodeURIComponent(options[name]);
- }
- });
-
- }
-
- //Prepend the Bookkeeper Usages URL to the url query parameters string
- url = MetacatUI.appModel.get("bookkeeperQuotasUrl") + url;
-
- return url;
-
+ });
+ }
+
+ //Prepend the Bookkeeper Usages URL to the url query parameters string
+ url = MetacatUI.appModel.get("bookkeeperQuotasUrl") + url;
+
+ return url;
+ },
+
+ /**
+ * Fetches a list of Quotas from the DataONE Bookkeeper service, parses them, and
+ * stores them on this collection.
+ * @param {Object} [options]
+ * @property {string} options.quotaType The quotaType to fetch
+ * @property {string} options.subscriber The user or group subject associated with these Quotas
+ */
+ fetch: function (options) {
+ var fetchOptions = {
+ url: this.url(options),
+ };
+
+ fetchOptions = Object.assign(
+ fetchOptions,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
+
+ //Call Backbone.Collection.fetch to retrieve the info
+ return Backbone.Collection.prototype.fetch.call(this, fetchOptions);
+ },
+
+ /**
+ * Parses the fetch() of this collection. Bookkeeper returns JSON already, so there
+ * isn't much parsing to do.
+ * @returns {JSON} The collection data in JSON form
+ */
+ parse: function (response) {
+ return response.quotas;
+ },
},
-
- /**
- * Fetches a list of Quotas from the DataONE Bookkeeper service, parses them, and
- * stores them on this collection.
- * @param {Object} [options]
- * @property {string} options.quotaType The quotaType to fetch
- * @property {string} options.subscriber The user or group subject associated with these Quotas
- */
- fetch: function(options){
-
- var fetchOptions = {
- url: this.url(options)
- }
-
- fetchOptions = Object.assign(fetchOptions, MetacatUI.appUserModel.createAjaxSettings());
-
- //Call Backbone.Collection.fetch to retrieve the info
- return Backbone.Collection.prototype.fetch.call(this, fetchOptions);
-
- },
-
- /**
- * Parses the fetch() of this collection. Bookkeeper returns JSON already, so there
- * isn't much parsing to do.
- * @returns {JSON} The collection data in JSON form
- */
- parse: function(response){
-
- return response.quotas;
- }
-
- });
+ );
return Quotas;
});
diff --git a/src/js/collections/bookkeeper/Usages.js b/src/js/collections/bookkeeper/Usages.js
index 05c36e53d..c7c9f15a9 100644
--- a/src/js/collections/bookkeeper/Usages.js
+++ b/src/js/collections/bookkeeper/Usages.js
@@ -1,8 +1,12 @@
"use strict";
-define(["jquery", "underscore", "backbone", "models/bookkeeper/Usage", "models/bookkeeper/Quota"],
- function($, _, Backbone, Usage, Quota) {
-
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/bookkeeper/Usage",
+ "models/bookkeeper/Quota",
+], function ($, _, Backbone, Usage, Quota) {
/**
* @class Usages
* @classdesc A Usages collection is a collection of Usage Models which track
@@ -15,128 +19,122 @@ define(["jquery", "underscore", "backbone", "models/bookkeeper/Usage", "models/b
*/
var Usages = Backbone.Collection.extend(
/** @lends Usages.prototype */ {
+ /**
+ * The class/model that is contained in this collection.
+ * @type {Backbone.Model}
+ */
+ model: Usage,
+
+ /**
+ * A reference to a Quota model that this collection of Usages is associated with.
+ * @type {Quota}
+ */
+ quota: null,
+
+ /**
+ * A list of query parameters that are supported by the Bookkeeper Usages API. These
+ * query parameters can be passed to {@link Usages#fetch} in the `options` object, and they
+ * will be used during the fetch.
+ * @type {string[]}
+ */
+ queryParams: ["quotaType", "subscriber"],
+
+ /**
+ * Constructs a URL string for fetching this collection and returns it
+ * @param {Object} [options]
+ * @property {string} options.quotaType The Usage quotaType to fetch
+ * @property {string} options.subscriber The user or group subject associated with these Usages
+ * @returns {string} The URL string
+ */
+ url: function (options) {
+ var url = "";
+
+ //Use the attributes from the options object for the URL, if it is passed to this function
+ if (typeof options == "object") {
+ _.each(this.queryParams, function (name) {
+ if (typeof options[name] !== "undefined") {
+ if (url.length == 0) {
+ url += "?";
+ } else {
+ url += "&";
+ }
+
+ url += name + "=" + encodeURIComponent(options[name]);
+ }
+ });
+ }
- /**
- * The class/model that is contained in this collection.
- * @type {Backbone.Model}
- */
- model: Usage,
-
- /**
- * A reference to a Quota model that this collection of Usages is associated with.
- * @type {Quota}
- */
- quota: null,
-
- /**
- * A list of query parameters that are supported by the Bookkeeper Usages API. These
- * query parameters can be passed to {@link Usages#fetch} in the `options` object, and they
- * will be used during the fetch.
- * @type {string[]}
- */
- queryParams: ["quotaType", "subscriber"],
-
- /**
- * Constructs a URL string for fetching this collection and returns it
- * @param {Object} [options]
- * @property {string} options.quotaType The Usage quotaType to fetch
- * @property {string} options.subscriber The user or group subject associated with these Usages
- * @returns {string} The URL string
- */
- url: function(options){
-
- var url = "";
-
- //Use the attributes from the options object for the URL, if it is passed to this function
- if( typeof options == "object" ){
-
- _.each( this.queryParams, function(name){
- if( typeof options[name] !== "undefined"){
- if( url.length == 0 ){
- url += "?";
+ //Prepend the Bookkeeper Usages URL to the url query parameters string
+ url = MetacatUI.appModel.get("bookkeeperUsagesUrl") + url;
+
+ return url;
+ },
+
+ /**
+ * Fetches a list of Usages from the DataONE Bookkeeper service, parses them, and
+ * stores them on this collection.
+ * @param {Object} [options]
+ * @property {string} options.quotaType The Usage quotaType to fetch
+ * @property {string} options.subscriber The user or group subject associated with these Usages
+ */
+ fetch: function (options) {
+ var fetchOptions = {
+ url: this.url(options),
+ };
+
+ fetchOptions = Object.assign(
+ fetchOptions,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
+
+ //Call Backbone.Collection.fetch to retrieve the info
+ return Backbone.Collection.prototype.fetch.call(this, fetchOptions);
+ },
+
+ /**
+ * Parses the fetch() of this collection. Bookkeeper returns JSON already, so there
+ * isn't much parsing to do.
+ * @returns {JSON} The collection data in JSON form
+ */
+ parse: function (response) {
+ return response.usages;
+ },
+
+ /**
+ * Merges another collection of models with this collection by matching instanceId to seriesId/id.
+ * A reference to the model from the otherCollection is stored in the corresponding Usage model.
+ * @type {DataPackage|SolrResults}
+ */
+ mergeCollections: function (otherCollection) {
+ //Iterate over each Usage in this collection
+ this.each(function (usage) {
+ //Find the other model that matches this Usage
+ var match = otherCollection.find(function (otherModel) {
+ //Make a match on the seriesId
+ if (
+ _.contains(otherModel.get("seriesId"), usage.get("instanceId"))
+ ) {
+ return true;
}
- else{
- url += "&";
+ //Make a match on the id
+ else if (
+ _.contains(otherModel.get("id"), usage.get("instanceId"))
+ ) {
+ return true;
+ } else {
+ return false;
}
+ });
- url += name + "=" + encodeURIComponent(options[name]);
+ //If a match is found, store a reference in each model
+ if (match) {
+ usage.set(match.type, match);
+ match.set("usageModel", this);
}
- });
-
- }
-
- //Prepend the Bookkeeper Usages URL to the url query parameters string
- url = MetacatUI.appModel.get("bookkeeperUsagesUrl") + url;
-
- return url;
-
- },
-
- /**
- * Fetches a list of Usages from the DataONE Bookkeeper service, parses them, and
- * stores them on this collection.
- * @param {Object} [options]
- * @property {string} options.quotaType The Usage quotaType to fetch
- * @property {string} options.subscriber The user or group subject associated with these Usages
- */
- fetch: function(options){
-
- var fetchOptions = {
- url: this.url(options)
- }
-
- fetchOptions = Object.assign(fetchOptions, MetacatUI.appUserModel.createAjaxSettings());
-
- //Call Backbone.Collection.fetch to retrieve the info
- return Backbone.Collection.prototype.fetch.call(this, fetchOptions);
-
- },
-
- /**
- * Parses the fetch() of this collection. Bookkeeper returns JSON already, so there
- * isn't much parsing to do.
- * @returns {JSON} The collection data in JSON form
- */
- parse: function(response){
- return response.usages;
+ }, this);
+ },
},
-
- /**
- * Merges another collection of models with this collection by matching instanceId to seriesId/id.
- * A reference to the model from the otherCollection is stored in the corresponding Usage model.
- * @type {DataPackage|SolrResults}
- */
- mergeCollections: function(otherCollection){
-
- //Iterate over each Usage in this collection
- this.each(function(usage){
-
- //Find the other model that matches this Usage
- var match = otherCollection.find(function(otherModel){
- //Make a match on the seriesId
- if( _.contains(otherModel.get("seriesId"), usage.get("instanceId")) ){
- return true;
- }
- //Make a match on the id
- else if( _.contains(otherModel.get("id"), usage.get("instanceId")) ){
- return true;
- }
- else{
- return false;
- }
- });
-
- //If a match is found, store a reference in each model
- if( match ){
- usage.set(match.type, match);
- match.set("usageModel", this);
- }
-
- }, this);
-
- }
-
- });
+ );
return Usages;
});
diff --git a/src/js/collections/maps/AssetCategories.js b/src/js/collections/maps/AssetCategories.js
index c3635de20..ce7dfc4fa 100644
--- a/src/js/collections/maps/AssetCategories.js
+++ b/src/js/collections/maps/AssetCategories.js
@@ -5,12 +5,7 @@ define([
"models/maps/AssetCategory",
"models/maps/Map",
"collections/maps/MapAssets",
-], function (
- Backbone,
- AssetCategory,
- MapModel,
- MapAssets,
-) {
+], function (Backbone, AssetCategory, MapModel, MapAssets) {
/**
* @classdesc AssetCategories collection is a group of AssetCategory models - models
* that provide the information required to render geo-spatial data in categories,
@@ -23,7 +18,6 @@ define([
*/
const AssetCategories = Backbone.Collection.extend(
/** @lends AssetCategories.prototype */ {
-
/** @inheritdoc */
model: AssetCategory,
@@ -35,19 +29,21 @@ define([
* models
*/
setMapModel(mapModel) {
- this.each(assetCategoryModel => assetCategoryModel.setMapModel(mapModel));
+ this.each((assetCategoryModel) =>
+ assetCategoryModel.setMapModel(mapModel),
+ );
},
- /**
+ /**
* Gets an array of MapAssets, one from each AssetCategory model.
* @returns {MapAssets[]}
*/
getMapAssets() {
- return this.map(assetCategory => {
+ return this.map((assetCategory) => {
return assetCategory.get("mapAssets");
});
- }
- }
+ },
+ },
);
return AssetCategories;
diff --git a/src/js/collections/maps/AssetColors.js b/src/js/collections/maps/AssetColors.js
index eb6789a00..ad7fc619f 100644
--- a/src/js/collections/maps/AssetColors.js
+++ b/src/js/collections/maps/AssetColors.js
@@ -1,86 +1,74 @@
-'use strict';
+"use strict";
-define(
- [
- 'jquery',
- 'underscore',
- 'backbone',
- 'models/maps/AssetColor'
- ],
- function (
- $,
- _,
- Backbone,
- AssetColor
- ) {
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/maps/AssetColor",
+], function ($, _, Backbone, AssetColor) {
+ /**
+ * @class AssetColors
+ * @classdesc An AssetColors collection represents the colors used to create a color
+ * scale for an asset in a map. The last color in the collection is treated as a
+ * default.
+ * @class AssetColors
+ * @classcategory Collections/Maps
+ * @extends Backbone.Collection
+ * @since 2.18.0
+ * @constructor
+ */
+ var AssetColors = Backbone.Collection.extend(
+ /** @lends AssetColors.prototype */ {
+ /**
+ * The class/model that this collection contains.
+ * @type {Backbone.Model}
+ */
+ model: AssetColor,
- /**
- * @class AssetColors
- * @classdesc An AssetColors collection represents the colors used to create a color
- * scale for an asset in a map. The last color in the collection is treated as a
- * default.
- * @class AssetColors
- * @classcategory Collections/Maps
- * @extends Backbone.Collection
- * @since 2.18.0
- * @constructor
- */
- var AssetColors = Backbone.Collection.extend(
- /** @lends AssetColors.prototype */ {
-
- /**
- * The class/model that this collection contains.
- * @type {Backbone.Model}
- */
- model: AssetColor,
-
- /**
- * Add custom sort functionality such that values are sorted
- * numerically, but keep the special value key words "min" and "max" at
- * the beginning or end of the collection, respectively.
- * @since 2.25.0
- */
- comparator: function (color) {
- let value = color.get('value');
- if (value === 'min') {
- return -Infinity;
- } else if (value === 'max') {
- return Infinity;
- } else {
- return value
- }
- },
-
- /**
- * Finds the last color model in the collection. If there are no colors in the
- * collection, returns the default color set in a new Asset Color model.
- * @return {AssetColor}
- */
- getDefaultColor: function () {
- let defaultColor = this.at(-1)
- if (!defaultColor) {
- defaultColor = new AssetColor();
- }
- return defaultColor
- },
-
- /**
- * For any attribute that exists in the models in this collection, return an
- * array of the values for that attribute.
- * @param {string} attr - The attribute to get the values for.
- * @return {Array}
- * @since 2.25.0
- */
- getAttr: function(attr) {
- return this.map(function (model) {
- return model.get(attr);
- });
+ /**
+ * Add custom sort functionality such that values are sorted
+ * numerically, but keep the special value key words "min" and "max" at
+ * the beginning or end of the collection, respectively.
+ * @since 2.25.0
+ */
+ comparator: function (color) {
+ let value = color.get("value");
+ if (value === "min") {
+ return -Infinity;
+ } else if (value === "max") {
+ return Infinity;
+ } else {
+ return value;
}
+ },
- }
- );
+ /**
+ * Finds the last color model in the collection. If there are no colors in the
+ * collection, returns the default color set in a new Asset Color model.
+ * @return {AssetColor}
+ */
+ getDefaultColor: function () {
+ let defaultColor = this.at(-1);
+ if (!defaultColor) {
+ defaultColor = new AssetColor();
+ }
+ return defaultColor;
+ },
- return AssetColors;
+ /**
+ * For any attribute that exists in the models in this collection, return an
+ * array of the values for that attribute.
+ * @param {string} attr - The attribute to get the values for.
+ * @return {Array}
+ * @since 2.25.0
+ */
+ getAttr: function (attr) {
+ return this.map(function (model) {
+ return model.get(attr);
+ });
+ },
+ },
+ );
- }
-);
\ No newline at end of file
+ return AssetColors;
+});
diff --git a/src/js/collections/maps/Features.js b/src/js/collections/maps/Features.js
index c49eb33b3..c18ce9ede 100644
--- a/src/js/collections/maps/Features.js
+++ b/src/js/collections/maps/Features.js
@@ -4,7 +4,7 @@ define(["jquery", "underscore", "backbone", "models/maps/Feature"], function (
$,
_,
Backbone,
- Feature
+ Feature,
) {
/**
* @class Features
@@ -73,7 +73,7 @@ define(["jquery", "underscore", "backbone", "models/maps/Feature"], function (
} catch (error) {
console.log(
`Failed to get unique attributes for "${attrName}".`,
- error
+ error,
);
}
},
@@ -93,7 +93,7 @@ define(["jquery", "underscore", "backbone", "models/maps/Feature"], function (
featureObject instanceof Feature
? featureObject.get("featureObject")
: featureObject;
- return this.findWhere({ 'featureObject': featureObject }) ? true : false;
+ return this.findWhere({ featureObject: featureObject }) ? true : false;
},
/**
@@ -107,11 +107,10 @@ define(["jquery", "underscore", "backbone", "models/maps/Feature"], function (
containsFeatures: function (featureObjects) {
if (!featureObjects || !featureObjects.length) return false;
return featureObjects.every((featureObject) =>
- this.containsFeature(featureObject)
+ this.containsFeature(featureObject),
);
},
-
- }
+ },
);
return Features;
diff --git a/src/js/collections/maps/GeoPoints.js b/src/js/collections/maps/GeoPoints.js
index b0eec44f9..cce1e9044 100644
--- a/src/js/collections/maps/GeoPoints.js
+++ b/src/js/collections/maps/GeoPoints.js
@@ -184,7 +184,7 @@ define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) {
toCZMLPoints: function () {
return this.models.map((model) => {
return model.toCZML();
- })
+ });
},
/**
@@ -323,7 +323,7 @@ define(["backbone", "models/maps/GeoPoint"], function (Backbone, GeoPoint) {
return model.get("mapWidgetCoords");
});
},
- }
+ },
);
return GeoPoints;
diff --git a/src/js/collections/maps/Geohashes.js b/src/js/collections/maps/Geohashes.js
index 8c2846f45..541f5a554 100644
--- a/src/js/collections/maps/Geohashes.js
+++ b/src/js/collections/maps/Geohashes.js
@@ -83,7 +83,7 @@ define([
if (fix) return p < min ? min : max;
throw new Error(
`Precision must be a number between ${min} and ${max}` +
- ` (inclusive), but got ${p}`
+ ` (inclusive), but got ${p}`,
);
},
@@ -140,7 +140,7 @@ define([
bounds.forEach(function (b) {
const c = b.getCoords();
hashStrings = hashStrings.concat(
- nGeohash.bboxes(c.south, c.west, c.north, c.east, precision)
+ nGeohash.bboxes(c.south, c.west, c.north, c.east, precision),
);
});
return hashStrings;
@@ -172,7 +172,6 @@ define([
return this.models.map((geohash) => geohash.get(attr));
},
-
/**
* Add geohashes to the collection based on a bounding box.
* @param {GeoBoundingBox} bounds - Bounding box with north, south, east, and west
@@ -202,7 +201,7 @@ define([
maxGeohashes = Infinity,
overwrite = false,
minPrecision = this.MIN_PRECISION,
- maxPrecision = this.MAX_PRECISION
+ maxPrecision = this.MAX_PRECISION,
) {
let hashStrings = [];
if (consolidate) {
@@ -210,7 +209,7 @@ define([
bounds,
minPrecision,
maxPrecision,
- maxGeohashes
+ maxGeohashes,
);
} else {
const area = bounds.getArea();
@@ -218,7 +217,7 @@ define([
area,
maxGeohashes,
minPrecision,
- maxPrecision
+ maxPrecision,
);
hashStrings = this.getHashStringsForBounds(bounds, precision);
}
@@ -258,7 +257,7 @@ define([
*/
getGeohashAreas: function (
minPrecision = this.MIN_PRECISION,
- maxPrecision = this.MAX_PRECISION
+ maxPrecision = this.MAX_PRECISION,
) {
minPrecision = this.validatePrecision(minPrecision);
maxPrecision = this.validatePrecision(maxPrecision);
@@ -290,7 +289,7 @@ define([
area,
maxGeohashes,
absMin = this.MIN_PRECISION,
- absMax = this.MAX_PRECISION
+ absMax = this.MAX_PRECISION,
) {
absMin = this.validatePrecision(absMin);
absMax = this.validatePrecision(absMax);
@@ -315,7 +314,7 @@ define([
console.warn(
`The area is too large to cover with fewer than ${maxGeohashes} ` +
`geohashes at the min precision level (${absMin}). Returning ` +
- `the min precision level, which may result in too many geohashes.`
+ `the min precision level, which may result in too many geohashes.`,
);
}
@@ -336,7 +335,7 @@ define([
getMinPrecision: function (
area,
absMin = this.MIN_PRECISION,
- absMax = this.MAX_PRECISION
+ absMax = this.MAX_PRECISION,
) {
absMin = this.validatePrecision(absMin);
absMax = this.validatePrecision(absMax);
@@ -376,12 +375,12 @@ define([
bounds,
maxGeohashes = Infinity,
absMin = this.MIN_PRECISION,
- absMax = this.MAX_PRECISION
+ absMax = this.MAX_PRECISION,
) {
- if (!bounds.isValid()){
+ if (!bounds.isValid()) {
console.warn(
`Bounds are invalid: ${JSON.stringify(bounds)}. ` +
- `Returning the min and max allowable precision levels.`
+ `Returning the min and max allowable precision levels.`,
);
return [absMin, absMax];
}
@@ -417,7 +416,7 @@ define([
bounds,
minPrecision = this.MIN_PRECISION,
maxPrecision = this.MAX_PRECISION,
- maxGeohashes = Infinity
+ maxGeohashes = Infinity,
) {
// Check the inputs
if (!bounds.isValid()) return [];
@@ -431,7 +430,7 @@ define([
bounds,
maxGeohashes,
minPrecision,
- maxPrecision
+ maxPrecision,
);
// Base32 is the set of characters used to encode geohashes
@@ -452,8 +451,7 @@ define([
for (const b of allBounds) {
if (bounds.boundsAreFullyContained(n, e, s, w)) {
return "inside";
- } else if (
- bounds.boundsAreFullyOutside(n, e, s, w)) {
+ } else if (bounds.boundsAreFullyOutside(n, e, s, w)) {
outside.push(true);
}
}
@@ -527,7 +525,7 @@ define([
if (!bounds || !bounds.isValid()) {
console.warn(
`Bounds are invalid: ${JSON.stringify(bounds)}. ` +
- `Returning an empty Geohashes collection.`
+ `Returning an empty Geohashes collection.`,
);
return new Geohashes();
}
@@ -535,7 +533,7 @@ define([
let hashes = [];
precisions.forEach((precision) => {
hashes = hashes.concat(
- this.getHashStringsForBounds(bounds, precision)
+ this.getHashStringsForBounds(bounds, precision),
);
});
const subsetModels = this.filter((geohash) => {
@@ -717,7 +715,7 @@ define([
});
return geohash;
},
- }
+ },
);
return Geohashes;
diff --git a/src/js/collections/maps/MapAssets.js b/src/js/collections/maps/MapAssets.js
index bfea0be14..1df88a760 100644
--- a/src/js/collections/maps/MapAssets.js
+++ b/src/js/collections/maps/MapAssets.js
@@ -19,7 +19,7 @@ define([
CesiumVectorData,
CesiumImagery,
CesiumTerrain,
- CesiumGeohash
+ CesiumGeohash,
) {
/**
* @class MapAssets
@@ -55,7 +55,11 @@ define([
model: Cesium3DTileset,
},
{
- types: ["GeoJsonDataSource", "CzmlDataSource", "CustomDataSource"],
+ types: [
+ "GeoJsonDataSource",
+ "CzmlDataSource",
+ "CustomDataSource",
+ ],
model: CesiumVectorData,
},
{
@@ -119,13 +123,13 @@ define([
otherModel.set("selected", false);
});
}
- }
+ },
);
} catch (error) {
console.log(
"There was an error initializing a MapAssets collection" +
". Error details: " +
- error
+ error,
);
}
},
@@ -178,7 +182,7 @@ define([
console.log(
"Failed to get all of the MapAssets in a MapAssets collection." +
" Returning all models in the asset collection." +
- e
+ e,
);
return this.models;
}
@@ -238,7 +242,7 @@ define([
return asset?.getFeatureAttributes(feature);
});
},
- }
+ },
);
return MapAssets;
diff --git a/src/js/collections/maps/VectorFilters.js b/src/js/collections/maps/VectorFilters.js
index a4bbb5c2d..c59aeb5f6 100644
--- a/src/js/collections/maps/VectorFilters.js
+++ b/src/js/collections/maps/VectorFilters.js
@@ -1,69 +1,58 @@
-'use strict';
+"use strict";
-define(
- [
- 'jquery',
- 'underscore',
- 'backbone',
- 'models/maps/VectorFilter'
- ],
- function (
- $,
- _,
- Backbone,
- VectorFilter
- ) {
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/maps/VectorFilter",
+], function ($, _, Backbone, VectorFilter) {
+ /**
+ * @class VectorFilters
+ * @classdesc A VectorFilters collection is a set of conditions used to show or hide
+ * features of a vector layer on a map.
+ * @class VectorFilters
+ * @classcategory Collections/Maps
+ * @extends Backbone.Collection
+ * @since 2.18.0
+ * @constructor
+ */
+ var VectorFilters = Backbone.Collection.extend(
+ /** @lends VectorFilters.prototype */ {
+ /**
+ * The class/model that this collection contains.
+ * @type {Backbone.Model}
+ */
+ model: VectorFilter,
- /**
- * @class VectorFilters
- * @classdesc A VectorFilters collection is a set of conditions used to show or hide
- * features of a vector layer on a map.
- * @class VectorFilters
- * @classcategory Collections/Maps
- * @extends Backbone.Collection
- * @since 2.18.0
- * @constructor
- */
- var VectorFilters = Backbone.Collection.extend(
- /** @lends VectorFilters.prototype */ {
-
- /**
- * The class/model that this collection contains.
- * @type {Backbone.Model}
- */
- model: VectorFilter,
-
- /**
- * This function is used to determine if a feature should be shown or hidden based
- * on the current filters.
- * @param {Object} properties The properties of the feature to be filtered.
- * (See the 'properties' attribute of {@link Feature#defaults}.)
- * @returns {boolean} Returns true if the feature passes all of the filters.
- */
- featureIsVisible: function (properties) {
- try {
- if (!properties) {
- properties = {};
- }
- let visible = true;
- this.each(function (filter) {
- visible = visible && filter.featureIsVisible(properties);
- });
- return visible;
- }
- catch (error) {
- console.log(
- 'There was an error filtering a feature in a VectorFilters collection' +
- '. Error details: ' + error + '. The feature will be visible.'
- );
- return true;
+ /**
+ * This function is used to determine if a feature should be shown or hidden based
+ * on the current filters.
+ * @param {Object} properties The properties of the feature to be filtered.
+ * (See the 'properties' attribute of {@link Feature#defaults}.)
+ * @returns {boolean} Returns true if the feature passes all of the filters.
+ */
+ featureIsVisible: function (properties) {
+ try {
+ if (!properties) {
+ properties = {};
}
+ let visible = true;
+ this.each(function (filter) {
+ visible = visible && filter.featureIsVisible(properties);
+ });
+ return visible;
+ } catch (error) {
+ console.log(
+ "There was an error filtering a feature in a VectorFilters collection" +
+ ". Error details: " +
+ error +
+ ". The feature will be visible.",
+ );
+ return true;
}
+ },
+ },
+ );
- }
- );
-
- return VectorFilters;
-
- }
-);
\ No newline at end of file
+ return VectorFilters;
+});
diff --git a/src/js/collections/maps/viewfinder/ZoomPresets.js b/src/js/collections/maps/viewfinder/ZoomPresets.js
index 9e0b02d04..ec27c6071 100644
--- a/src/js/collections/maps/viewfinder/ZoomPresets.js
+++ b/src/js/collections/maps/viewfinder/ZoomPresets.js
@@ -1,70 +1,75 @@
-'use strict';
+"use strict";
-define(
- [
- 'underscore',
- 'backbone',
- 'models/maps/viewfinder/ZoomPresetModel',
- ],
- function (_, Backbone, ZoomPresetModel) {
- /**
- * @class ZoomPresets
- * @classdesc A ZoomPresets collection is a group of ZoomPresetModel models
- * that provide a location and list of layers to make visible when the user
- * selects.
- * @class ZoomPresets
- * @classcategory Collections/Maps
- * @extends Backbone.Collection
- * @since 2.29.0
- * @constructor
- */
- const ZoomPresets = Backbone.Collection.extend(
+define([
+ "underscore",
+ "backbone",
+ "models/maps/viewfinder/ZoomPresetModel",
+], function (_, Backbone, ZoomPresetModel) {
+ /**
+ * @class ZoomPresets
+ * @classdesc A ZoomPresets collection is a group of ZoomPresetModel models
+ * that provide a location and list of layers to make visible when the user
+ * selects.
+ * @class ZoomPresets
+ * @classcategory Collections/Maps
+ * @extends Backbone.Collection
+ * @since 2.29.0
+ * @constructor
+ */
+ const ZoomPresets = Backbone.Collection.extend(
/** @lends ZoomPresets.prototype */ {
- /** @inheritdoc */
- model: ZoomPresetModel,
+ /** @inheritdoc */
+ model: ZoomPresetModel,
- /**
- * @param {Object[]} zoomPresets The raw list of objects that represent
- * the zoom presets, to be converted into ZoomPresetModels.
- * @param {MapAsset[]} allLayers All of the layers available for display
- * in the map.
- */
- parse({ zoomPresetObjects, allLayers }) {
- if (isNonEmptyArray(zoomPresetObjects)) {
- const zoomPresets = zoomPresetObjects.map(zoomPresetObj => {
- const enabledLayerIds = [];
- const enabledLayerLabels = [];
- for (const layer of allLayers) {
- if (zoomPresetObj.layerIds?.find(id => id === layer.get('layerId'))) {
- enabledLayerIds.push(layer.get('layerId'));
- enabledLayerLabels.push(layer.get('label'));
- }
+ /**
+ * @param {Object[]} zoomPresets The raw list of objects that represent
+ * the zoom presets, to be converted into ZoomPresetModels.
+ * @param {MapAsset[]} allLayers All of the layers available for display
+ * in the map.
+ */
+ parse({ zoomPresetObjects, allLayers }) {
+ if (isNonEmptyArray(zoomPresetObjects)) {
+ const zoomPresets = zoomPresetObjects.map((zoomPresetObj) => {
+ const enabledLayerIds = [];
+ const enabledLayerLabels = [];
+ for (const layer of allLayers) {
+ if (
+ zoomPresetObj.layerIds?.find(
+ (id) => id === layer.get("layerId"),
+ )
+ ) {
+ enabledLayerIds.push(layer.get("layerId"));
+ enabledLayerLabels.push(layer.get("label"));
}
+ }
- return new ZoomPresetModel({
+ return new ZoomPresetModel(
+ {
description: zoomPresetObj.description,
enabledLayerLabels,
enabledLayerIds,
position: {
latitude: zoomPresetObj.latitude,
longitude: zoomPresetObj.longitude,
- height: zoomPresetObj.height
+ height: zoomPresetObj.height,
},
title: zoomPresetObj.title,
- }, { parse: true });
- });
+ },
+ { parse: true },
+ );
+ });
- return zoomPresets;
- }
+ return zoomPresets;
+ }
- return [];
- },
- }
- );
+ return [];
+ },
+ },
+ );
- function isNonEmptyArray(a) {
- return a && a.length && Array.isArray(a);
- }
+ function isNonEmptyArray(a) {
+ return a && a.length && Array.isArray(a);
+ }
- return ZoomPresets;
- });
\ No newline at end of file
+ return ZoomPresets;
+});
diff --git a/src/js/collections/metadata/eml/EMLAnnotations.js b/src/js/collections/metadata/eml/EMLAnnotations.js
index 758eb531d..b29c17cfc 100644
--- a/src/js/collections/metadata/eml/EMLAnnotations.js
+++ b/src/js/collections/metadata/eml/EMLAnnotations.js
@@ -1,7 +1,11 @@
"use strict";
-define(["jquery", "underscore", "backbone", "models/metadata/eml211/EMLAnnotation"],
-function($, _, Backbone, EMLAnnotation){
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/metadata/eml211/EMLAnnotation",
+], function ($, _, Backbone, EMLAnnotation) {
/**
* @class EMLAnnotations
* @classdesc A collection of EMLAnnotations.
@@ -12,69 +16,63 @@ function($, _, Backbone, EMLAnnotation){
var EMLAnnotations = Backbone.Collection.extend(
/** @lends EMLAnnotations.prototype */
{
-
/**
- * The reference to the model class that this collection is made of.
- * @type EMLAnnotation
- * @since 2.19.0
- */
+ * The reference to the model class that this collection is made of.
+ * @type EMLAnnotation
+ * @since 2.19.0
+ */
model: EMLAnnotation,
/**
- * Checks if this collection already has an annotation for the same property URI.
- * @param {EMLAnnotation} annotation The EMLAnnotation to compare against the annotations already in this collection.
- * @returns {Boolean} Returns true is this collection already has an annotation for this property.
- * @since 2.19.0
- */
- hasDuplicateOf: function(annotation){
-
- try{
-
+ * Checks if this collection already has an annotation for the same property URI.
+ * @param {EMLAnnotation} annotation The EMLAnnotation to compare against the annotations already in this collection.
+ * @returns {Boolean} Returns true is this collection already has an annotation for this property.
+ * @since 2.19.0
+ */
+ hasDuplicateOf: function (annotation) {
+ try {
//If there is at least one model in this collection and there is a propertyURI set on the given model,
- if( this.length && annotation.get("propertyURI") ){
+ if (this.length && annotation.get("propertyURI")) {
//Return whether or not there is a duplicate
let properties = this.pluck("propertyURI");
return properties.includes(annotation.get("propertyURI"));
}
//If this collection is empty or the propertyURI is falsey, return false
- else{
+ else {
return false;
}
-
- }
- catch(e){
+ } catch (e) {
console.error("Could not check for a duplicate annotation: ", e);
return false;
}
-
},
/**
- * Removes the EMLAnnotation from this collection that has the same propertyURI as the given annotation.
- * Then adds the given annotation to the collection. If no duplicate is found, the given annotation is still added
- * to the collection.
- * @param {EMLAnnotation} annotation
- * @since 2.19.0
- */
- replaceDuplicateWith: function(annotation){
-
- try{
-
- if( this.length && annotation.get("propertyURI") ){
- let duplicates = this.findWhere({ "propertyURI": annotation.get("propertyURI") });
+ * Removes the EMLAnnotation from this collection that has the same propertyURI as the given annotation.
+ * Then adds the given annotation to the collection. If no duplicate is found, the given annotation is still added
+ * to the collection.
+ * @param {EMLAnnotation} annotation
+ * @since 2.19.0
+ */
+ replaceDuplicateWith: function (annotation) {
+ try {
+ if (this.length && annotation.get("propertyURI")) {
+ let duplicates = this.findWhere({
+ propertyURI: annotation.get("propertyURI"),
+ });
this.remove(duplicates);
}
this.add(annotation);
-
- }
- catch(e){
- console.error("Could not replace the EMLAnnotation in the collection: ", e);
+ } catch (e) {
+ console.error(
+ "Could not replace the EMLAnnotation in the collection: ",
+ e,
+ );
}
-
- }
-
- });
+ },
+ },
+ );
return EMLAnnotations;
});
diff --git a/src/js/collections/metadata/eml/EMLMissingValueCodes.js b/src/js/collections/metadata/eml/EMLMissingValueCodes.js
index 80d637954..115280fcb 100644
--- a/src/js/collections/metadata/eml/EMLMissingValueCodes.js
+++ b/src/js/collections/metadata/eml/EMLMissingValueCodes.js
@@ -2,7 +2,7 @@
define(["backbone", "models/metadata/eml211/EMLMissingValueCode"], function (
Backbone,
- EMLMissingValueCode
+ EMLMissingValueCode,
) {
/**
* @class EMLMissingValueCodes
@@ -79,7 +79,7 @@ define(["backbone", "models/metadata/eml211/EMLMissingValueCode"], function (
// For now, if there is at least one error, just return the first one
return errors.length ? errors[0] : null;
},
- }
+ },
);
return EMLMissingValueCodes;
diff --git a/src/js/collections/queryFields/QueryFields.js b/src/js/collections/queryFields/QueryFields.js
index 1f02c1a0a..8b51470c9 100644
--- a/src/js/collections/queryFields/QueryFields.js
+++ b/src/js/collections/queryFields/QueryFields.js
@@ -1,155 +1,170 @@
/* global define */
-define(
- ["jquery", "underscore", "backbone", "x2js", "models/queryFields/QueryField"],
- function($, _, Backbone, X2JS, QueryField) {
- "use strict";
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "x2js",
+ "models/queryFields/QueryField",
+], function ($, _, Backbone, X2JS, QueryField) {
+ "use strict";
- /**
- * @class QueryFields
- * @classdesc The collection of queryable fields supported by the the
- * DataONE Solr index, as provided by the DataONE API
- * CNRead.getQueryEngineDescription() function. For more information, see:
- * https://indexer-documentation.readthedocs.io/en/latest/generated/solr_schema.html
- * https://dataone-architecture-documentation.readthedocs.io/en/latest/design/SearchMetadata.html
- * @classcategory Collections/QueryFields
- * @name QueryFields
- * @extends Backbone.Collection
- * @since 2.14.0
- * @constructor
- */
- var QueryFields = Backbone.Collection.extend(
- /** @lends QueryFields.prototype */
- {
+ /**
+ * @class QueryFields
+ * @classdesc The collection of queryable fields supported by the the
+ * DataONE Solr index, as provided by the DataONE API
+ * CNRead.getQueryEngineDescription() function. For more information, see:
+ * https://indexer-documentation.readthedocs.io/en/latest/generated/solr_schema.html
+ * https://dataone-architecture-documentation.readthedocs.io/en/latest/design/SearchMetadata.html
+ * @classcategory Collections/QueryFields
+ * @name QueryFields
+ * @extends Backbone.Collection
+ * @since 2.14.0
+ * @constructor
+ */
+ var QueryFields = Backbone.Collection.extend(
+ /** @lends QueryFields.prototype */
+ {
+ /**
+ * The type of Backbone model that this collection comprises
+ */
+ model: QueryField,
- /**
- * The type of Backbone model that this collection comprises
- */
- model: QueryField,
-
- /**
- * initialize - Creates a new QueryFields collection
- */
- initialize: function(models, options) {
- try {
- if (typeof options === "undefined") {
- var options = {};
- }
- } catch (e) {
- console.log("Failed to initialize a Query Fields collection, error message: " + e);
+ /**
+ * initialize - Creates a new QueryFields collection
+ */
+ initialize: function (models, options) {
+ try {
+ if (typeof options === "undefined") {
+ var options = {};
}
- },
+ } catch (e) {
+ console.log(
+ "Failed to initialize a Query Fields collection, error message: " +
+ e,
+ );
+ }
+ },
- /**
- * comparator - A sortBy function that returns the order of each Query
- * Filter model based on its position in the categoriesMap object.
- *
- * @param {QueryFilter} model The individual Query Filter model
- * @return {number} A numeric value by which the model should be ordered relative to others.
- */
- comparator: function(model){
- try {
- var categoriesMap = model.categoriesMap();
- var order = _(categoriesMap)
- .chain()
- .pluck("queryFields")
- .flatten()
- .value();
- return order.indexOf(model.get("name"));
- } catch (e) {
- console.log("Failed to sort the Query Fields Collection, error message: " + e);
- return 0
- }
- },
+ /**
+ * comparator - A sortBy function that returns the order of each Query
+ * Filter model based on its position in the categoriesMap object.
+ *
+ * @param {QueryFilter} model The individual Query Filter model
+ * @return {number} A numeric value by which the model should be ordered relative to others.
+ */
+ comparator: function (model) {
+ try {
+ var categoriesMap = model.categoriesMap();
+ var order = _(categoriesMap)
+ .chain()
+ .pluck("queryFields")
+ .flatten()
+ .value();
+ return order.indexOf(model.get("name"));
+ } catch (e) {
+ console.log(
+ "Failed to sort the Query Fields Collection, error message: " + e,
+ );
+ return 0;
+ }
+ },
- /**
- * The constructed URL of the collection
- *
- * @returns {string} - The URL to use during fetch
- */
- url: function() {
- try {
- return MetacatUI.appModel.get("queryServiceUrl").replace(/\/\?$/, "");
- } catch (e) {
- return "https://cn.dataone.org/cn/v2/query/solr"
- }
- },
+ /**
+ * The constructed URL of the collection
+ *
+ * @returns {string} - The URL to use during fetch
+ */
+ url: function () {
+ try {
+ return MetacatUI.appModel.get("queryServiceUrl").replace(/\/\?$/, "");
+ } catch (e) {
+ return "https://cn.dataone.org/cn/v2/query/solr";
+ }
+ },
- /**
- * Retrieve the fields from the Coordinating Node
- * @extends Backbone.Collection#fetch
- */
- fetch: function(options) {
- try {
- var fetchOptions = _.extend({dataType: "text"}, options);
- return Backbone.Model.prototype.fetch.call(this, fetchOptions);
- } catch (e) {
- console.log("Failed to fetch Query Fields, error message: " + e);
- }
- },
+ /**
+ * Retrieve the fields from the Coordinating Node
+ * @extends Backbone.Collection#fetch
+ */
+ fetch: function (options) {
+ try {
+ var fetchOptions = _.extend({ dataType: "text" }, options);
+ return Backbone.Model.prototype.fetch.call(this, fetchOptions);
+ } catch (e) {
+ console.log("Failed to fetch Query Fields, error message: " + e);
+ }
+ },
- /**
- * parse - Parse the XML response from the CN
- *
- * @param {string} response The queryEngineDescription XML as a string
- * @return {Array} the Array of Query Field attributes to be added to the collection.
- */
- parse: function(response) {
- try {
- // If the collection is already parsed, just return it
- if ( typeof response === "object" ){
- return response;
- }
- var x2js = new X2JS();
- var responseJSON = x2js.xml_str2json(response);
- if(responseJSON && responseJSON.queryEngineDescription){
- return responseJSON.queryEngineDescription.queryField;
- }
- } catch (e) {
- console.log("Failed to parse Query Fields response, error message: " + e);
+ /**
+ * parse - Parse the XML response from the CN
+ *
+ * @param {string} response The queryEngineDescription XML as a string
+ * @return {Array} the Array of Query Field attributes to be added to the collection.
+ */
+ parse: function (response) {
+ try {
+ // If the collection is already parsed, just return it
+ if (typeof response === "object") {
+ return response;
}
- },
+ var x2js = new X2JS();
+ var responseJSON = x2js.xml_str2json(response);
+ if (responseJSON && responseJSON.queryEngineDescription) {
+ return responseJSON.queryEngineDescription.queryField;
+ }
+ } catch (e) {
+ console.log(
+ "Failed to parse Query Fields response, error message: " + e,
+ );
+ }
+ },
- /**
- * getRequiredFilterType - Based on an array of query (Solr) field names, get the
- * type of filter model to use with these fields. For example, if the fields are
- * type text, use a regular filter model. If the fields are tdate, use a
- * dateFilter. If the field types are mixed, then returns the filterType default
- * value in QueryField models.
- *
- * @param {string[]} fields The list of Query Field names
- * @return {string} The nodeName of the filter model to use (one of the four types
- * of fields that are set in {@link QueryField#filterTypesMap})
- */
- getRequiredFilterType: function (fields) {
- try {
- var types = [],
- // When fields is empty or are different types
- defaultFilterType = MetacatUI.queryFields.models[0].defaults().filterType;
+ /**
+ * getRequiredFilterType - Based on an array of query (Solr) field names, get the
+ * type of filter model to use with these fields. For example, if the fields are
+ * type text, use a regular filter model. If the fields are tdate, use a
+ * dateFilter. If the field types are mixed, then returns the filterType default
+ * value in QueryField models.
+ *
+ * @param {string[]} fields The list of Query Field names
+ * @return {string} The nodeName of the filter model to use (one of the four types
+ * of fields that are set in {@link QueryField#filterTypesMap})
+ */
+ getRequiredFilterType: function (fields) {
+ try {
+ var types = [],
+ // When fields is empty or are different types
+ defaultFilterType =
+ MetacatUI.queryFields.models[0].defaults().filterType;
- if (!fields || fields.length === 0 || fields[0] === "") {
- return defaultFilterType
- }
+ if (!fields || fields.length === 0 || fields[0] === "") {
+ return defaultFilterType;
+ }
- fields.forEach((newField, i) => {
- var fieldModel = MetacatUI.queryFields.findWhere({ name: newField });
- types.push(fieldModel.get("filterType"));
+ fields.forEach((newField, i) => {
+ var fieldModel = MetacatUI.queryFields.findWhere({
+ name: newField,
});
+ types.push(fieldModel.get("filterType"));
+ });
- // Test of all the fields are of the same type
- var allEqual = types.every((val, i, arr) => val === arr[0]);
+ // Test of all the fields are of the same type
+ var allEqual = types.every((val, i, arr) => val === arr[0]);
- if (allEqual) {
- return types[0]
- } else {
- return defaultFilterType
- }
-
- } catch (e) {
- console.log("Failed to detect the required filter type in a Query Fields" +
- " Collection, error message: " + e);
+ if (allEqual) {
+ return types[0];
+ } else {
+ return defaultFilterType;
}
- },
-
- });
- return QueryFields;
- });
+ } catch (e) {
+ console.log(
+ "Failed to detect the required filter type in a Query Fields" +
+ " Collection, error message: " +
+ e,
+ );
+ }
+ },
+ },
+ );
+ return QueryFields;
+});
diff --git a/src/js/common/IconUtilities.js b/src/js/common/IconUtilities.js
index 072a23e3c..b92be7a43 100644
--- a/src/js/common/IconUtilities.js
+++ b/src/js/common/IconUtilities.js
@@ -2,24 +2,22 @@
define([
"models/portals/PortalImage",
"showdown",
- "showdownXssFilter"
-],
- function(PortalImage, showdown, showdownXss) {
- 'use strict';
+ "showdownXssFilter",
+], function (PortalImage, showdown, showdownXss) {
+ "use strict";
// The start of a base64 encoded SVG string
- const B64_START = 'data:image/svg+xml;base64,';
-
- /**
- * @namespace IconUtilities
- * @description A generic utility object that contains functions used throughout
- * MetacatUI to perform useful functions related to icons, but not used to store or
- * manipulate any state about the application.
- * @type {object}
- * @since 2.28.0
- */
- const IconUtilities = /** @lends IconUtilities.prototype */ {
+ const B64_START = "data:image/svg+xml;base64,";
+ /**
+ * @namespace IconUtilities
+ * @description A generic utility object that contains functions used throughout
+ * MetacatUI to perform useful functions related to icons, but not used to store or
+ * manipulate any state about the application.
+ * @type {object}
+ * @since 2.28.0
+ */
+ const IconUtilities = /** @lends IconUtilities.prototype */ {
/**
* Simple test to see if a string is an SVG
* @param {string} str The string to check
@@ -44,8 +42,8 @@ define([
}).get("imageURL");
return fetch(imageURL)
- .then(response => response.text())
- .then(data => {
+ .then((response) => response.text())
+ .then((data) => {
if (this.isSVG(data)) {
return data;
}
@@ -81,20 +79,26 @@ define([
* @returns {SVGElement|null} - The modified SVG element or null if an error occurs.
* @since 2.29.0
*/
- formatSvgForCesiumBillboard(svgString, strokeWidth = 0, strokeColor = "white") {
+ formatSvgForCesiumBillboard(
+ svgString,
+ strokeWidth = 0,
+ strokeColor = "white",
+ ) {
const svgElement = this.parseSvg(svgString);
if (!svgElement) {
- console.error("No SVG element found in the SVG string or failed to parse.");
+ console.error(
+ "No SVG element found in the SVG string or failed to parse.",
+ );
return null;
}
-
+
this.removeCommentNodes(svgElement);
this.setStrokeProperties(svgElement, strokeWidth, strokeColor);
this.adjustViewBox(svgElement, strokeWidth);
-
+
return svgElement;
},
-
+
/**
* Parses an SVG string and returns the SVG element.
* @param {string} svgString - The SVG markup as a string.
@@ -107,18 +111,21 @@ define([
const svgElement = doc.querySelector("svg");
return svgElement;
},
-
+
/**
* Removes comment nodes from an SVG element.
* @param {SVGElement} svgElement - The SVG element.
* @since 2.29.0
*/
removeCommentNodes(svgElement) {
- while (svgElement.firstChild && svgElement.firstChild.nodeType === Node.COMMENT_NODE) {
+ while (
+ svgElement.firstChild &&
+ svgElement.firstChild.nodeType === Node.COMMENT_NODE
+ ) {
svgElement.removeChild(svgElement.firstChild);
}
},
-
+
/**
* Sets stroke properties on an SVG element.
* @param {SVGElement} svgElement - The SVG element.
@@ -130,7 +137,7 @@ define([
svgElement.setAttribute("stroke-width", strokeWidth);
svgElement.setAttribute("stroke", strokeColor);
},
-
+
/**
* Adjusts the viewBox of an SVG element to accommodate a stroke width.
* @param {SVGElement} svgElement - The SVG element.
@@ -145,9 +152,14 @@ define([
const newY = y - strokeWidth;
const newWidth = width + 2 * strokeWidth;
const newHeight = height + 2 * strokeWidth;
- svgElement.setAttribute("viewBox", `${newX} ${newY} ${newWidth} ${newHeight}`);
+ svgElement.setAttribute(
+ "viewBox",
+ `${newX} ${newY} ${newWidth} ${newHeight}`,
+ );
} else {
- console.warn("SVG element does not have a 'viewBox' attribute; viewBox adjustment skipped.");
+ console.warn(
+ "SVG element does not have a 'viewBox' attribute; viewBox adjustment skipped.",
+ );
}
},
@@ -161,9 +173,8 @@ define([
svgToBase64(svgElement) {
const base64 = btoa(svgElement.outerHTML);
return B64_START + base64;
- }
-
- }
+ },
+ };
return IconUtilities;
});
diff --git a/src/js/common/Utilities.js b/src/js/common/Utilities.js
index 41208b01e..9cb6d4394 100644
--- a/src/js/common/Utilities.js
+++ b/src/js/common/Utilities.js
@@ -1,52 +1,51 @@
/*global define */
-define(['jquery', 'underscore'],
- function($, _) {
- 'use strict';
-
- /**
- * @namespace Utilities
- * @description A generic utility object that contains functions used throughout MetacatUI to perform useful functions,
- * but not used to store or manipulate any state about the application.
- * @type {object}
- * @since 2.14.0
- */
- var Utilities = /** @lends Utilities.prototype */ {
-
+define(["jquery", "underscore"], function ($, _) {
+ "use strict";
+
+ /**
+ * @namespace Utilities
+ * @description A generic utility object that contains functions used throughout MetacatUI to perform useful functions,
+ * but not used to store or manipulate any state about the application.
+ * @type {object}
+ * @since 2.14.0
+ */
+ var Utilities = /** @lends Utilities.prototype */ {
/**
- * HTML-encodes the given string so it can be inserted into an HTML page without running
- * any embedded Javascript.
- * @param {string} s
- * @returns {string}
- */
- encodeHTML: function(s) {
-
- try{
- if( !s || typeof s !== "string" ){
+ * HTML-encodes the given string so it can be inserted into an HTML page without running
+ * any embedded Javascript.
+ * @param {string} s
+ * @returns {string}
+ */
+ encodeHTML: function (s) {
+ try {
+ if (!s || typeof s !== "string") {
return "";
}
- return s.replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/'/g, "'")
- .replace(/\//g, "/")
- .replace(/"/g, '"');
- }
- catch(e){
+ return s
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/'/g, "'")
+ .replace(/\//g, "/")
+ .replace(/"/g, """);
+ } catch (e) {
console.error("Could not encode HTML: ", e);
return "";
}
},
/**
- * Validates that the given string is a valid DOI
- * @param {string} identifier
- * @returns {boolean}
- * @since 2.15.0
- */
- isValidDOI: function(identifier) {
+ * Validates that the given string is a valid DOI
+ * @param {string} identifier
+ * @returns {boolean}
+ * @since 2.15.0
+ */
+ isValidDOI: function (identifier) {
// generate doi regex
- var doiRGEX = new RegExp(/^\s*(http:\/\/|https:\/\/)?(doi.org\/|dx.doi.org\/)?(doi: ?|DOI: ?)?(10\.\d{4,}(\.\d)*)\/(\w+).*$/ig)
+ var doiRGEX = new RegExp(
+ /^\s*(http:\/\/|https:\/\/)?(doi.org\/|dx.doi.org\/)?(doi: ?|DOI: ?)?(10\.\d{4,}(\.\d)*)\/(\w+).*$/gi,
+ );
return doiRGEX.test(identifier);
},
@@ -108,19 +107,18 @@ define(['jquery', 'underscore'],
var names = header_line.split(",");
// Remove surrounding parens and double-quotes
- names = names.map(function(name) {
+ names = names.map(function (name) {
return name.replaceAll(/^["']|["']$/gm, "");
});
// Filter out zero-length values (headers like a,b,c,,,,,)
- names = names.filter(function(name) {
+ names = names.filter(function (name) {
return name.length > 0;
});
return names;
- }
-
- }
+ },
+ };
return Utilities;
});
diff --git a/src/js/models/AccessRule.js b/src/js/models/AccessRule.js
index 823222b3b..92ba83109 100644
--- a/src/js/models/AccessRule.js
+++ b/src/js/models/AccessRule.js
@@ -1,7 +1,6 @@
/*global define */
-define(['jquery', 'underscore', 'backbone'],
- function($, _, Backbone) {
- 'use strict';
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ "use strict";
/**
* @class AccessRule
@@ -9,241 +8,251 @@ define(['jquery', 'underscore', 'backbone'],
* @classcategory Models
* @extends Backbone.Model
*/
- var AccessRule = Backbone.Model.extend(
+ var AccessRule = Backbone.Model.extend(
/** @lends AccessRule */
{
-
- defaults: function(){
- return{
- subject: null,
- read: null,
- write: null,
- changePermission: null,
- name: null,
- dataONEObject: null
- }
- },
-
- initialize: function(){
-
- },
-
- /**
- * Translates the access rule XML DOM into a JSON object to be set on the model.
- * @param {Element} accessRuleXML An DOM element that contains a single access rule
- * @return {JSON} The Access Rule values to be set on this model
- */
- parse: function(accessRuleXML) {
- // If there is no access policy, do not attempt to parse anything
- if (typeof accessRuleXML === "undefined" || !accessRuleXML) {
- return {};
- }
-
- var accessRuleXMLObj = $(accessRuleXML);
-
- // Start an access rule object with the given subject
- var parsedAccessRule = {
- subject: accessRuleXMLObj.find("subject").text()
- };
-
- _.each(accessRuleXMLObj.find("permission"), function(permissionNode, idx) {
- let permissionText = $(permissionNode).text().trim();
-
- // Check if the permission text is not empty
- if (permissionText.length) {
- // Save the parsed permission
- parsedAccessRule[permissionText] = true;
- } else {
- // This is added as a workaround for malformed permission XML
- // introduced by Chromium 120.X
- // See https://github.com/NCEAS/metacatui/issues/2235
-
- // Define the regular expression
- let globalPermRegex = /<\/permission>(.*)/g;
- // Define the regular expression
- let permRegex = /<\/permission>(.*)/;
-
- let accessRoleStr = accessRuleXMLObj.html();
-
- let matches = accessRoleStr.match(globalPermRegex);
-
- // Check if matches exist and have a length
- if (matches && matches.length && idx < matches.length) {
- let permMatch = matches[idx].match(permRegex);
-
- // Check if permMatch exists and has a length
- if (permMatch && permMatch.length) {
- parsedAccessRule[permMatch[1]] = true;
- }
- }
+ defaults: function () {
+ return {
+ subject: null,
+ read: null,
+ write: null,
+ changePermission: null,
+ name: null,
+ dataONEObject: null,
+ };
+ },
+
+ initialize: function () {},
+
+ /**
+ * Translates the access rule XML DOM into a JSON object to be set on the model.
+ * @param {Element} accessRuleXML An DOM element that contains a single access rule
+ * @return {JSON} The Access Rule values to be set on this model
+ */
+ parse: function (accessRuleXML) {
+ // If there is no access policy, do not attempt to parse anything
+ if (typeof accessRuleXML === "undefined" || !accessRuleXML) {
+ return {};
}
- });
- return parsedAccessRule;
- },
+ var accessRuleXMLObj = $(accessRuleXML);
+
+ // Start an access rule object with the given subject
+ var parsedAccessRule = {
+ subject: accessRuleXMLObj.find("subject").text(),
+ };
+
+ _.each(
+ accessRuleXMLObj.find("permission"),
+ function (permissionNode, idx) {
+ let permissionText = $(permissionNode).text().trim();
+
+ // Check if the permission text is not empty
+ if (permissionText.length) {
+ // Save the parsed permission
+ parsedAccessRule[permissionText] = true;
+ } else {
+ // This is added as a workaround for malformed permission XML
+ // introduced by Chromium 120.X
+ // See https://github.com/NCEAS/metacatui/issues/2235
- /**
- * Takes the values set on this model's attributes and creates an XML string
- * to be inserted into a DataONEObject's system metadata access policy.
- * @returns {object} The access rule XML object or null if not created
- */
- serialize: function() {
- // Serialize the allow rules
- if (this.get("read") || this.get("write") || this.get("changePermission")) {
+ // Define the regular expression
+ let globalPermRegex = /<\/permission>(.*)/g;
+ // Define the regular expression
+ let permRegex = /<\/permission>(.*)/;
+
+ let accessRoleStr = accessRuleXMLObj.html();
+
+ let matches = accessRoleStr.match(globalPermRegex);
+
+ // Check if matches exist and have a length
+ if (matches && matches.length && idx < matches.length) {
+ let permMatch = matches[idx].match(permRegex);
+
+ // Check if permMatch exists and has a length
+ if (permMatch && permMatch.length) {
+ parsedAccessRule[permMatch[1]] = true;
+ }
+ }
+ }
+ },
+ );
+
+ return parsedAccessRule;
+ },
+
+ /**
+ * Takes the values set on this model's attributes and creates an XML string
+ * to be inserted into a DataONEObject's system metadata access policy.
+ * @returns {object} The access rule XML object or null if not created
+ */
+ serialize: function () {
+ // Serialize the allow rules
+ if (
+ this.get("read") ||
+ this.get("write") ||
+ this.get("changePermission")
+ ) {
// Create the element
- var allowElement = document.createElement('allow');
+ var allowElement = document.createElement("allow");
// Create the element and set its text content
- var subjectElement = document.createElement('subject');
+ var subjectElement = document.createElement("subject");
subjectElement.textContent = this.get("subject");
// Append the and elements to
allowElement.appendChild(subjectElement);
// Create the elements and set their text content
- var permissions = ['read', 'write', 'changePermission'];
+ var permissions = ["read", "write", "changePermission"];
for (var i = 0; i < permissions.length; i++) {
- if (this.get(permissions[i])) {
- var permissionElement = document.createElement('permission');
- permissionElement.textContent = permissions[i];
- allowElement.appendChild(permissionElement);
- }
+ if (this.get(permissions[i])) {
+ var permissionElement = document.createElement("permission");
+ permissionElement.textContent = permissions[i];
+ allowElement.appendChild(permissionElement);
+ }
}
// Return the element
return allowElement;
- }
-
- // If no access rule is created, return null
- return null;
- },
-
-
- /**
- * Gets and sets the subject info for the subjects in this access policy.
- */
- getSubjectInfo: function(){
-
- //If there is no subject, exit now since there is nothing to retrieve
- if( !this.get("subject") ){
- return;
- }
+ }
- //If the subject is "public", there is no subject info to retrieve
- if( this.get("subject") == "public" ){
- this.set("name", "Anyone");
- return;
- }
+ // If no access rule is created, return null
+ return null;
+ },
- //If this is the current user, we can use the name we already have in the app.
- if( this.get("subject") == MetacatUI.appUserModel.get("username") ){
- if( MetacatUI.appUserModel.get("fullName") ){
- this.set("name", MetacatUI.appUserModel.get("fullName"));
+ /**
+ * Gets and sets the subject info for the subjects in this access policy.
+ */
+ getSubjectInfo: function () {
+ //If there is no subject, exit now since there is nothing to retrieve
+ if (!this.get("subject")) {
return;
}
- }
- var model = this;
-
- var ajaxOptions = {
- url: MetacatUI.appModel.get("accountsUrl") + encodeURIComponent(this.get("subject")),
- type: "GET",
- dataType: "text",
- processData: false,
- parse: false,
- success: function(response) {
+ //If the subject is "public", there is no subject info to retrieve
+ if (this.get("subject") == "public") {
+ this.set("name", "Anyone");
+ return;
+ }
- //If there was no response, exit now
- if(!response){
+ //If this is the current user, we can use the name we already have in the app.
+ if (this.get("subject") == MetacatUI.appUserModel.get("username")) {
+ if (MetacatUI.appUserModel.get("fullName")) {
+ this.set("name", MetacatUI.appUserModel.get("fullName"));
return;
}
+ }
- var xmlDoc;
-
- try{
- xmlDoc = $.parseXML(response);
- }
- catch(e){
- //If the parsing XML failed, exit now
- console.error("The accounts service did not return valid XML.", e);
- return;
- }
+ var model = this;
+
+ var ajaxOptions = {
+ url:
+ MetacatUI.appModel.get("accountsUrl") +
+ encodeURIComponent(this.get("subject")),
+ type: "GET",
+ dataType: "text",
+ processData: false,
+ parse: false,
+ success: function (response) {
+ //If there was no response, exit now
+ if (!response) {
+ return;
+ }
- //If the XML string was not parsed correctly, exit now
- if( !XMLDocument.prototype.isPrototypeOf(xmlDoc) ){
- return;
- }
+ var xmlDoc;
+
+ try {
+ xmlDoc = $.parseXML(response);
+ } catch (e) {
+ //If the parsing XML failed, exit now
+ console.error(
+ "The accounts service did not return valid XML.",
+ e,
+ );
+ return;
+ }
- var subjectNode;
+ //If the XML string was not parsed correctly, exit now
+ if (!XMLDocument.prototype.isPrototypeOf(xmlDoc)) {
+ return;
+ }
- if( model.isGroup() ){
- //Find the subject XML node for this person, by matching the text content with the subject
- subjectNode = $(xmlDoc).find("group subject:contains(" + model.get("subject") + ")");
- }
- else{
- //Find the subject XML node for this person, by matching the text content with the subject
- subjectNode = $(xmlDoc).find("person subject:contains(" + model.get("subject") + ")");
- }
+ var subjectNode;
+
+ if (model.isGroup()) {
+ //Find the subject XML node for this person, by matching the text content with the subject
+ subjectNode = $(xmlDoc).find(
+ "group subject:contains(" + model.get("subject") + ")",
+ );
+ } else {
+ //Find the subject XML node for this person, by matching the text content with the subject
+ subjectNode = $(xmlDoc).find(
+ "person subject:contains(" + model.get("subject") + ")",
+ );
+ }
- //If no subject XML node was found, exit now
- if( !subjectNode || !subjectNode.length ){
- return;
- }
+ //If no subject XML node was found, exit now
+ if (!subjectNode || !subjectNode.length) {
+ return;
+ }
- //If more than one subject was found (should be very unlikely), then find the one with the exact matching subject
- if( subjectNode.length > 1 ){
- _.each(subjectNode, function(subjNode){
- if( $(subjNode).text() == model.get("subject") ){
- subjectNode = $(subjNode);
- }
- });
- }
+ //If more than one subject was found (should be very unlikely), then find the one with the exact matching subject
+ if (subjectNode.length > 1) {
+ _.each(subjectNode, function (subjNode) {
+ if ($(subjNode).text() == model.get("subject")) {
+ subjectNode = $(subjNode);
+ }
+ });
+ }
- var name;
- if( model.isGroup() ){
- //Get the group name
- name = $(subjectNode).siblings("groupName").text();
+ var name;
+ if (model.isGroup()) {
+ //Get the group name
+ name = $(subjectNode).siblings("groupName").text();
- //If there is no group name, then just use the name parsed from the subject
- if( !name ){
- name = model.get("subject").substring(3, model.get("subject").indexOf(",DC=dataone") );
+ //If there is no group name, then just use the name parsed from the subject
+ if (!name) {
+ name = model
+ .get("subject")
+ .substring(3, model.get("subject").indexOf(",DC=dataone"));
+ }
+ } else {
+ //Get the first and last name for this person
+ name =
+ $(subjectNode).siblings("givenName").text() +
+ " " +
+ $(subjectNode).siblings("familyName").text();
}
- }
- else{
- //Get the first and last name for this person
- name = $(subjectNode).siblings("givenName").text() + " " + $(subjectNode).siblings("familyName").text();
- }
-
- //Set the name on the model
- model.set("name", name);
+ //Set the name on the model
+ model.set("name", name);
+ },
+ };
+
+ //Send the XHR
+ $.ajax(ajaxOptions);
+ },
+
+ /**
+ * Returns true if the subbject set on this AccessRule is for a group of people.
+ * @returns {boolean}
+ */
+ isGroup: function () {
+ try {
+ //Check if the subject is a group subject
+ var matches = this.get("subject").match(/CN=.+,DC=dataone,DC=org/);
+ return Array.isArray(matches) && matches.length;
+ } catch (e) {
+ console.error(
+ "Couldn't determine if the subject in this AccessRule is a group: ",
+ e,
+ );
+ return false;
}
- }
-
- //Send the XHR
- $.ajax(ajaxOptions);
+ },
},
+ );
- /**
- * Returns true if the subbject set on this AccessRule is for a group of people.
- * @returns {boolean}
- */
- isGroup: function(){
-
- try{
- //Check if the subject is a group subject
- var matches = this.get("subject").match(/CN=.+,DC=dataone,DC=org/);
- return (Array.isArray(matches) && matches.length);
- }
- catch(e){
- console.error("Couldn't determine if the subject in this AccessRule is a group: ", e);
- return false;
- }
-
- }
-
- });
-
- return AccessRule;
-
+ return AccessRule;
});
diff --git a/src/js/models/AppModel.js b/src/js/models/AppModel.js
index d241d6251..6d36db191 100644
--- a/src/js/models/AppModel.js
+++ b/src/js/models/AppModel.js
@@ -1,305 +1,316 @@
/*global define */
-define(['jquery', 'underscore', 'backbone'],
- function($, _, Backbone) {
- 'use strict';
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ "use strict";
/**
- * @class AppModel
- * @classdesc A utility model that contains top-level configuration and storage for the application
- * @name AppModel
- * @extends Backbone.Model
- * @constructor
- * @classcategory Models
- */
+ * @class AppModel
+ * @classdesc A utility model that contains top-level configuration and storage for the application
+ * @name AppModel
+ * @extends Backbone.Model
+ * @constructor
+ * @classcategory Models
+ */
var AppModel = Backbone.Model.extend(
/** @lends AppModel.prototype */ {
+ defaults: _.extend(
+ /** @lends AppConfig.prototype */ {
+ //TODO: These attributes are stored in the AppModel, but shouldn't be set in the AppConfig,
+ //so we need to add docs for them in a separate place
+ headerType: "default",
+ searchHistory: [],
+ page: 0,
+ previousPid: null,
+ lastPid: null,
+ anchorId: null,
+ profileUsername: null,
+
+ /**
+ * The theme name to use
+ * @type {string}
+ * @default "default"
+ */
+ theme: "default",
+
+ /**
+ * The default page title.
+ * @type {string}
+ */
+ title: MetacatUI.themeTitle || "Metacat Data Catalog",
+
+ /**
+ * The default page description.
+ * @type {string}
+ * @since 2.25.0
+ */
+ description:
+ "A research data catalog and repository that provides access to scientific data, metadata, and more.",
+
+ /**
+ * The name of this repository. This is used throughout the interface in different
+ * messages and page content.
+ * @type {string}
+ * @default "Metacat Data Catalog"
+ * @since 2.11.2
+ */
+ repositoryName: MetacatUI.themeTitle || "Metacat Data Catalog",
+
+ /**
+ * The e-mail address that people should contact when they need help with
+ * submitting datasets, resolving error messages, etc.
+ * @type {string}
+ * @default knb-help@nceas.ucsb.edu
+ */
+ emailContact: "knb-help@nceas.ucsb.edu",
+
+ /**
+ * Your Google Maps API key, which is used to display interactive maps on the search
+ * views and static maps on dataset landing pages.
+ * If a Google Maps API key is not specified, the maps will be omitted from the interface.
+ * The Google Maps API key also controls the showViewfinder feature on a Map
+ * and should have the Geocoding API and Places API enabled in order to
+ * function properly.
+ * Sign up for Google Maps services at https://console.developers.google.com/
+ * @type {string}
+ * @example "AIzaSyCYyHnbIokUEpMx5M61ButwgNGX8fIHUs"
+ * @default null
+ */
+ mapKey: null,
+
+ /**
+ * Your Google Analytics API key, which is used to send page view and custom events
+ * to the Google Analytics service.
+ * This service is optional in MetacatUI.
+ * Sign up for Google Analytics services at https://analytics.google.com/analytics/web/
+ * @type {string}
+ * @example "UA-74622301-1"
+ * @default null
+ */
+ googleAnalyticsKey: null,
+
+ /**
+ * The map to use in the catalog search (both DataCatalogViewWithFilters and
+ * DataCatalog). This can be set to either "google" (the default), or "cesium". To
+ * use Google maps, the {@link AppConfig#googleAnalyticsKey} must be set. To use
+ * Cesium maps, the {@link AppConfig#enableCesium} property must be set to true, and
+ * the {@link AppConfig#cesiumToken} must be set. DEPRECATION NOTE: This configuration
+ * is deprecated along with the {@link DataCatalogView} and {@link DataCatalogViewWithFilters}
+ * views and Google Maps. The {@link CatalogSearchView} will replace these as the primary search view and will only
+ * support Cesium, not Google Maps.
+ * @type {string}
+ * @example "cesium"
+ * @default "google"
+ * @deprecated
+ */
+ dataCatalogMap: "google",
+
+ /**
+ * Set this option to true to display the filtering button for data package table
+ * @type {boolean}
+ * @since 2.28.0
+ */
+ dataPackageFiltering: false,
+
+ /**
+ * Set this option to true to display the sorting button for data package table
+ * @type {boolean}
+ * @since 2.28.0
+ */
+ dataPackageSorting: false,
+
+ /**
+ * The default options for the Cesium map used in the {@link CatalogSearchView} for searching the data
+ * catalog. Add custom layers, a default home position (for example, zoom into your area of research),
+ * and enable/disable map widgets. See {@link MapConfig} for the full suite of options. Keep the `CesiumGeohash`
+ * layer here in order to show the search results in the map as geohash boxes. Use any satellite imagery
+ * layer of your choice, such as a self-hosted imagery layer or hosted on Cesium Ion.
+ * @type {MapConfig}
+ * @since 2.22.0
+ */
+ catalogSearchMapOptions: {
+ showLayerList: false,
+ clickFeatureAction: "zoom",
+ },
- defaults: _.extend(
- /** @lends AppConfig.prototype */{
-
- //TODO: These attributes are stored in the AppModel, but shouldn't be set in the AppConfig,
- //so we need to add docs for them in a separate place
- headerType: 'default',
- searchHistory: [],
- page: 0,
- previousPid: null,
- lastPid: null,
- anchorId: null,
- profileUsername: null,
-
- /**
- * The theme name to use
- * @type {string}
- * @default "default"
- */
- theme: "default",
-
- /**
- * The default page title.
- * @type {string}
- */
- title: MetacatUI.themeTitle || "Metacat Data Catalog",
-
- /**
- * The default page description.
- * @type {string}
- * @since 2.25.0
- */
- description: "A research data catalog and repository that provides access to scientific data, metadata, and more.",
-
- /**
- * The name of this repository. This is used throughout the interface in different
- * messages and page content.
- * @type {string}
- * @default "Metacat Data Catalog"
- * @since 2.11.2
- */
- repositoryName: MetacatUI.themeTitle || "Metacat Data Catalog",
-
- /**
- * The e-mail address that people should contact when they need help with
- * submitting datasets, resolving error messages, etc.
- * @type {string}
- * @default knb-help@nceas.ucsb.edu
- */
- emailContact: "knb-help@nceas.ucsb.edu",
-
- /**
- * Your Google Maps API key, which is used to display interactive maps on the search
- * views and static maps on dataset landing pages.
- * If a Google Maps API key is not specified, the maps will be omitted from the interface.
- * The Google Maps API key also controls the showViewfinder feature on a Map
- * and should have the Geocoding API and Places API enabled in order to
- * function properly.
- * Sign up for Google Maps services at https://console.developers.google.com/
- * @type {string}
- * @example "AIzaSyCYyHnbIokUEpMx5M61ButwgNGX8fIHUs"
- * @default null
- */
- mapKey: null,
-
- /**
- * Your Google Analytics API key, which is used to send page view and custom events
- * to the Google Analytics service.
- * This service is optional in MetacatUI.
- * Sign up for Google Analytics services at https://analytics.google.com/analytics/web/
- * @type {string}
- * @example "UA-74622301-1"
- * @default null
- */
- googleAnalyticsKey: null,
-
- /**
- * The map to use in the catalog search (both DataCatalogViewWithFilters and
- * DataCatalog). This can be set to either "google" (the default), or "cesium". To
- * use Google maps, the {@link AppConfig#googleAnalyticsKey} must be set. To use
- * Cesium maps, the {@link AppConfig#enableCesium} property must be set to true, and
- * the {@link AppConfig#cesiumToken} must be set. DEPRECATION NOTE: This configuration
- * is deprecated along with the {@link DataCatalogView} and {@link DataCatalogViewWithFilters}
- * views and Google Maps. The {@link CatalogSearchView} will replace these as the primary search view and will only
- * support Cesium, not Google Maps.
- * @type {string}
- * @example "cesium"
- * @default "google"
- * @deprecated
- */
- dataCatalogMap: "google",
-
- /**
- * Set this option to true to display the filtering button for data package table
- * @type {boolean}
- * @since 2.28.0
- */
- dataPackageFiltering: false,
-
- /**
- * Set this option to true to display the sorting button for data package table
- * @type {boolean}
- * @since 2.28.0
- */
- dataPackageSorting: false,
-
- /**
- * The default options for the Cesium map used in the {@link CatalogSearchView} for searching the data
- * catalog. Add custom layers, a default home position (for example, zoom into your area of research),
- * and enable/disable map widgets. See {@link MapConfig} for the full suite of options. Keep the `CesiumGeohash`
- * layer here in order to show the search results in the map as geohash boxes. Use any satellite imagery
- * layer of your choice, such as a self-hosted imagery layer or hosted on Cesium Ion.
- * @type {MapConfig}
- * @since 2.22.0
- */
- catalogSearchMapOptions: {
- showLayerList: false,
- clickFeatureAction: "zoom"
- },
-
- /**
- * The node identifier for this repository. This is set dynamically by retrieving the
- * DataONE Coordinating Node document and finding this repository in the Node list.
- * (see https://cn.dataone.org/cn/v2/node).
- * If this repository is not registered with DataONE, then set this node id by copying
- * the node id from your node info at https://your-repo-site.com/metacat/d1/mn/v2/node
- * @type {string}
- * @example "urn:node:METACAT"
- * @default null
- */
- nodeId: null,
-
- /**
- * If true, this MetacatUI instance is pointing to a CN rather than a MN.
- * This attribute is set during the AppModel initialization, based on the other configured attributes.
- * @readonly
- * @type {boolean}
- */
- isCN: false,
-
- /**
- * Enable or disable the user profiles. If enabled, users will see a "My profile" link
- * and can view their datasets, metrics on those datasets, their groups, etc.
- * @type {boolean}
- * @default true
- */
- enableUserProfiles: true,
-
- /**
- * Enable or disable the user settings view. If enabled, users will see a list of
- * changeable settings - name, email, groups, portals, etc.
- * @type {boolean}
- * @default true
- */
- enableUserProfileSettings: true,
-
- /**
- * The maximum dataset .zip file size, in bytes, that a user can download.
- * Datasets whose total size are larger than this maximum will show a disabled
- * "Download All" button, and users will be directed to download files individually.
- * This is useful for preventing the Metacat package service from getting overloaded.
- * @type {number}
- * @default 100000000000
- */
- maxDownloadSize: 100000000000,
-
- /**
- * Add a message that will display during a certain time period. This is useful when
- * displaying a warning message about planned outages/maintenance, or alert users to other
- * important information.
- * If this attribute is left blank, no message will display, even if there is a start and end time specified.
- * If there are is no start or end time specified, this message will display until you remove it here.
- *
- * @type {string}
- * @default null
- * @since 2.11.4
- */
- temporaryMessage: null,
-
- /**
- * If there is a temporaryMessage specified, it will display after this start time.
- * Remember that Dates are in GMT time!
- * @type {Date}
- * @example new Date(1594818000000)
- * @default null
- * @since 2.11.4
- */
- temporaryMessageStartTime: null,
-
- /**
- * If there is a temporaryMessage specified, it will display before this end time.
- * Remember that Dates are in GMT time!
- * @type {Date}
- * @example new Date(1594818000000)
- * @default null
- * @since 2.11.4
- */
- temporaryMessageEndTime: null,
-
- /**
- * Additional HTML classes to give the temporary message element. Use these to style the message.
- * @type {string}
- * @default "warning"
- * @since 2.11.4
- */
- temporaryMessageClasses: "warning",
-
- /**
- * A jQuery selector for the element that the temporary message will be displayed in.
- * @type {string}
- * @default "#Navbar"
- * @since 2.11.4
- */
- temporaryMessageContainer: "#Navbar",
-
- /**
- * If true, the temporary message will include a "Need help? Email us at..." email link
- * at the end of the message. The email address will be set to {@link AppConfig#emailContact}
- * @type {boolean}
- * @default true
- * @since 2.13.3
- */
- temporaryMessageIncludeEmail: true,
-
- /**
- * Show or hide the source repository logo in the search result rows
- * @type {boolean}
- * @default false
- */
- displayRepoLogosInSearchResults: false,
- /**
- * Show or hide the Download button in the search result rows
- * @type {boolean}
- * @default false
- */
- displayDownloadButtonInSearchResults: false,
-
- /**
- * If set to false, some parts of the app will send POST HTTP requests to the
- * Solr search index via the `/query/solr` DataONE API.
- * Set this configuration to true if using Metacat 2.10.2 or earlier
- * @type {boolean}
- */
- disableQueryPOSTs: false,
-
- /** If set to true, some parts of the app will use the Solr Join Query syntax
- * when sending queries to the `/query/solr` DataONE API.
- * If this is not enabled, then some parts of the UI may not work if a query has too
- * many characters or has too many boolean clauses. This impacts the "Metrics" tabs of portals/collections,
- * at least.
- * The Solr Join Query Parser as added in Solr 4.0.0-ALPHA (I believe!): https://archive.apache.org/dist/lucene/solr/4.0.0/changes/Changes.html#4.0.0-alpha.new_features
- * About the Solr Join Query Parser: https://lucene.apache.org/solr/guide/8_5/other-parsers.html#join-query-parser
- * WARNING: At some point, MetacatUI will deprecate this configuration and will REQUIRE Solr Join Queries
- * @type {boolean}
- */
- enableSolrJoins: false,
-
- /**
- * The search filters that will be displayed in the search views. Add or remove
- * filter names from this array to show or hide them. See "example" to see all the
- * filter options.
- * @type {string[]}
- * @default ["all", "attribute", "documents", "creator", "dataYear", "pubYear", "id", "taxon", "spatial", "isPrivate"]
- * @example ["all", "annotation", "attribute", "dataSource", "documents", "creator", "dataYear", "pubYear", "id", "taxon", "spatial"]
- */
- defaultSearchFilters: ["all", "attribute", "documents", "creator", "dataYear", "pubYear", "id", "taxon", "spatial", "isPrivate", "projectText"],
-
- /**
- * Enable to show Whole Tale features
- * @type {Boolean}
- * @default false
- */
- showWholeTaleFeatures: false,
- /**
- * The WholeTale environments that are exposed on the dataset landing pages
- * @type {string[]}
- * @default ["RStudio", "Jupyter Notebook"]
- */
- taleEnvironments: ["RStudio", "Jupyter Notebook"],
- /**
- * The Whole Tale endpoint
- * @type {string}
- * @default 'https://girder.wholetale.org/api/v1/integration/dataone'
- */
- dashboardUrl: 'https://girder.wholetale.org/api/v1/integration/dataone',
-
- /**
+ /**
+ * The node identifier for this repository. This is set dynamically by retrieving the
+ * DataONE Coordinating Node document and finding this repository in the Node list.
+ * (see https://cn.dataone.org/cn/v2/node).
+ * If this repository is not registered with DataONE, then set this node id by copying
+ * the node id from your node info at https://your-repo-site.com/metacat/d1/mn/v2/node
+ * @type {string}
+ * @example "urn:node:METACAT"
+ * @default null
+ */
+ nodeId: null,
+
+ /**
+ * If true, this MetacatUI instance is pointing to a CN rather than a MN.
+ * This attribute is set during the AppModel initialization, based on the other configured attributes.
+ * @readonly
+ * @type {boolean}
+ */
+ isCN: false,
+
+ /**
+ * Enable or disable the user profiles. If enabled, users will see a "My profile" link
+ * and can view their datasets, metrics on those datasets, their groups, etc.
+ * @type {boolean}
+ * @default true
+ */
+ enableUserProfiles: true,
+
+ /**
+ * Enable or disable the user settings view. If enabled, users will see a list of
+ * changeable settings - name, email, groups, portals, etc.
+ * @type {boolean}
+ * @default true
+ */
+ enableUserProfileSettings: true,
+
+ /**
+ * The maximum dataset .zip file size, in bytes, that a user can download.
+ * Datasets whose total size are larger than this maximum will show a disabled
+ * "Download All" button, and users will be directed to download files individually.
+ * This is useful for preventing the Metacat package service from getting overloaded.
+ * @type {number}
+ * @default 100000000000
+ */
+ maxDownloadSize: 100000000000,
+
+ /**
+ * Add a message that will display during a certain time period. This is useful when
+ * displaying a warning message about planned outages/maintenance, or alert users to other
+ * important information.
+ * If this attribute is left blank, no message will display, even if there is a start and end time specified.
+ * If there are is no start or end time specified, this message will display until you remove it here.
+ *
+ * @type {string}
+ * @default null
+ * @since 2.11.4
+ */
+ temporaryMessage: null,
+
+ /**
+ * If there is a temporaryMessage specified, it will display after this start time.
+ * Remember that Dates are in GMT time!
+ * @type {Date}
+ * @example new Date(1594818000000)
+ * @default null
+ * @since 2.11.4
+ */
+ temporaryMessageStartTime: null,
+
+ /**
+ * If there is a temporaryMessage specified, it will display before this end time.
+ * Remember that Dates are in GMT time!
+ * @type {Date}
+ * @example new Date(1594818000000)
+ * @default null
+ * @since 2.11.4
+ */
+ temporaryMessageEndTime: null,
+
+ /**
+ * Additional HTML classes to give the temporary message element. Use these to style the message.
+ * @type {string}
+ * @default "warning"
+ * @since 2.11.4
+ */
+ temporaryMessageClasses: "warning",
+
+ /**
+ * A jQuery selector for the element that the temporary message will be displayed in.
+ * @type {string}
+ * @default "#Navbar"
+ * @since 2.11.4
+ */
+ temporaryMessageContainer: "#Navbar",
+
+ /**
+ * If true, the temporary message will include a "Need help? Email us at..." email link
+ * at the end of the message. The email address will be set to {@link AppConfig#emailContact}
+ * @type {boolean}
+ * @default true
+ * @since 2.13.3
+ */
+ temporaryMessageIncludeEmail: true,
+
+ /**
+ * Show or hide the source repository logo in the search result rows
+ * @type {boolean}
+ * @default false
+ */
+ displayRepoLogosInSearchResults: false,
+ /**
+ * Show or hide the Download button in the search result rows
+ * @type {boolean}
+ * @default false
+ */
+ displayDownloadButtonInSearchResults: false,
+
+ /**
+ * If set to false, some parts of the app will send POST HTTP requests to the
+ * Solr search index via the `/query/solr` DataONE API.
+ * Set this configuration to true if using Metacat 2.10.2 or earlier
+ * @type {boolean}
+ */
+ disableQueryPOSTs: false,
+
+ /** If set to true, some parts of the app will use the Solr Join Query syntax
+ * when sending queries to the `/query/solr` DataONE API.
+ * If this is not enabled, then some parts of the UI may not work if a query has too
+ * many characters or has too many boolean clauses. This impacts the "Metrics" tabs of portals/collections,
+ * at least.
+ * The Solr Join Query Parser as added in Solr 4.0.0-ALPHA (I believe!): https://archive.apache.org/dist/lucene/solr/4.0.0/changes/Changes.html#4.0.0-alpha.new_features
+ * About the Solr Join Query Parser: https://lucene.apache.org/solr/guide/8_5/other-parsers.html#join-query-parser
+ * WARNING: At some point, MetacatUI will deprecate this configuration and will REQUIRE Solr Join Queries
+ * @type {boolean}
+ */
+ enableSolrJoins: false,
+
+ /**
+ * The search filters that will be displayed in the search views. Add or remove
+ * filter names from this array to show or hide them. See "example" to see all the
+ * filter options.
+ * @type {string[]}
+ * @default ["all", "attribute", "documents", "creator", "dataYear", "pubYear", "id", "taxon", "spatial", "isPrivate"]
+ * @example ["all", "annotation", "attribute", "dataSource", "documents", "creator", "dataYear", "pubYear", "id", "taxon", "spatial"]
+ */
+ defaultSearchFilters: [
+ "all",
+ "attribute",
+ "documents",
+ "creator",
+ "dataYear",
+ "pubYear",
+ "id",
+ "taxon",
+ "spatial",
+ "isPrivate",
+ "projectText",
+ ],
+
+ /**
+ * Enable to show Whole Tale features
+ * @type {Boolean}
+ * @default false
+ */
+ showWholeTaleFeatures: false,
+ /**
+ * The WholeTale environments that are exposed on the dataset landing pages
+ * @type {string[]}
+ * @default ["RStudio", "Jupyter Notebook"]
+ */
+ taleEnvironments: ["RStudio", "Jupyter Notebook"],
+ /**
+ * The Whole Tale endpoint
+ * @type {string}
+ * @default 'https://girder.wholetale.org/api/v1/integration/dataone'
+ */
+ dashboardUrl:
+ "https://girder.wholetale.org/api/v1/integration/dataone",
+
+ /**
* A list of all the required fields in the EML Editor.
* Any field set to `true` will prevent the user from saving the Editor until a value has been given
* Any EML field not supported in this list cannot be required.
@@ -343,1987 +354,2300 @@ define(['jquery', 'underscore', 'backbone'],
* generalTaxonomicCoverage: false,
* taxonCoverage: false,
* geoCoverage: false,
- * intellectualRights: true,
- * keywordSets: false,
- * methods: false,
- * samplingDescription: false,
- * studyExtentDescription: false,
- * temporalCoverage: false,
- * title: true,
- * contact: true,
- * principalInvestigator: true
- * }
- */
- emlEditorRequiredFields: {
- abstract: true,
- alternateIdentifier: false,
- dataSensitivity: false,
- funding: false,
- generalTaxonomicCoverage: false,
- taxonCoverage: false,
- geoCoverage: false,
- intellectualRights: true,
- keywordSets: false,
- methods: false,
- samplingDescription: false,
- studyExtentDescription: false,
- temporalCoverage: false,
- title: true,
- creator: true,
- contact: true
- },
-
- /**
- * A list of required fields for each EMLParty (People) in the dataset editor.
- * This is a literal object where the keys are the EML Party type (e.g. creator, principalInvestigator) {@link see EMLParty.partytypes}
- * and the values are arrays of field names.
- * By default, EMLPartys are *always* required to have an individual's name, position name, or organization name.
- * @type {object}
- * @since 2.21.0
- * @example
- * {
- * contact: ["email"],
- * creator: ["email", "address", "phone"]
- * principalInvestigator: ["organizationName"]
- * }
- * @default
- * {
- * }
- */
- emlEditorRequiredFields_EMLParty: {
-
- },
-
- /**
- * An array of science metadata format IDs that are editable in MetacatUI.
- * Metadata documents with these format IDs will have an Edit button and will be
- * editable in the Editor Views.
- * This should only be changed if you have extended MetacatUI to edit a new format,
- * or if you want to disable editing of a specific format ID.
- * @type {string[]}
- * @default [
- "eml://ecoinformatics.org/eml-2.1.1",
- "https://eml.ecoinformatics.org/eml-2.2.0"
- ]
- * @example
- * [
- * "eml://ecoinformatics.org/eml-2.1.1",
- * "https://eml.ecoinformatics.org/eml-2.2.0"
- * ]
- * @readonly
- */
- editableFormats: [
- "eml://ecoinformatics.org/eml-2.1.1",
- "https://eml.ecoinformatics.org/eml-2.2.0"
- ],
-
- /**
- * The format ID the dataset editor serializes new EML as
- * @type {string}
- * @default "https://eml.ecoinformatics.org/eml-2.2.0"
- * @readonly
- * @since 2.13.0
- */
- editorSerializationFormat: "https://eml.ecoinformatics.org/eml-2.2.0",
-
- /**
- * The XML schema location the dataset editor will use when creating new EML. This should
- * correspond with {@link AppConfig#editorSerializationFormat}
- * @type {string}
- * @default "https://eml.ecoinformatics.org/eml-2.2.0 https://eml.ecoinformatics.org/eml-2.2.0/eml.xsd"
- * @readonly
- * @since 2.13.0
- */
- editorSchemaLocation: "https://eml.ecoinformatics.org/eml-2.2.0 https://eml.ecoinformatics.org/eml-2.2.0/eml.xsd",
-
- /**
- * The text to use for the eml system attribute. The system attribute
- * indicates the data management system within which an identifier is in
- * scope and therefore unique. This is typically a URL (Uniform Resource
- * Locator) that indicates a data management system. All identifiers that
- * share a system must be unique. In other words, if the same identifier
- * is used in two locations with identical systems, then by definition the
- * objects at which they point are in fact the same object.
- * @type {string}
- * @since 2.26.0
- * @link https://eml.ecoinformatics.org/schema/eml-resource_xsd#SystemType
- * @link https://eml.ecoinformatics.org/schema/eml_xsd
- */
- emlSystem: "knb",
-
- /**
- * This error message is displayed when the Editor encounters an error saving
- * @type {string}
- */
- editorSaveErrorMsg: "Not all of your changes could be submitted.",
- /**
- * This error message is displayed when the Editor encounters an error saving, and a plain-text draft is saved instead
- * @type {string}
- */
- editorSaveErrorMsgWithDraft: "Not all of your changes could be submitted, but a draft " +
- "has been saved which can be accessed by our support team. Please contact us.",
- /**
- * The text of the Save button in the dataset editor.
- * @type {string}
- * @default "Save dataset"
- * @since 2.13.3
- */
- editorSaveButtonText: "Save dataset",
-
- /**
- * A list of keyword thesauri options for the user to choose from in the EML Editor.
- * A "None" option will also always display.
- * @type {object[]}
- * @property {string} label - A readable and short label for the keyword thesaurus that is displayed in the UI
- * @property {string} thesaurus - The exact keyword thesaurus name that will be saved in the EML
- * @since 2.10.0
- * @default [{
- label: "GCMD",
- thesaurus: "NASA Global Change Master Directory (GCMD)"
- }]
- * @example
- * [{
- * label: "GCMD",
- * thesaurus: "NASA Global Change Master Directory (GCMD)"
- * }]
- */
- emlKeywordThesauri: [{
- label: "GCMD",
- thesaurus: "NASA Global Change Master Directory (GCMD)"
- }],
-
-
- /**
- * If true, questions related to Data Sensitivity will be shown in the EML Editor.
- * @type {boolean}
- * @default true
- * @since 2.19.0
- */
- enableDataSensitivityInEditor: true,
-
-
- /**
- * The URL of a webpage that shows more information about Data Sensitivity and DataTags. This will be used
- * for links in help text throughout the app, such as next to Data Sensitivity questions in the dataset editor.
- *
- * @type {string}
- * @default "http://datatags.org"
- * @since 2.19.0
- */
- dataSensitivityInfoURL: "http://datatags.org",
-
- /**
- * In the editor, sometimes it is useful to have guided questions for the Methods section
- * in addition to the generic numbered method steps. These custom methods are defined here
- * as an array of literal objects that define each custom Methods question. Custom methods
- * are serialized to the EML as regular method steps, but with an unchangeable title, defined here,
- * in order to identify them.
- *
- * @typedef {object} CustomEMLMethod
- * @property {string[]} titleOptions One or more titles that may exist in an EML Method Step that identify that Method Step as a custom method type. THe first title in the array is serialized to the EML XML.
- * @property {string} id A unique identifier for this custom method type.
- * @property {boolean} required If true, this custom method will be a required field for submission in the EML editor.
- * @example [{
- "titleOptions": ["Ethical Research Procedures"],
- "id": "ethical-research-procedures",
- "required": false
- }]
- * @since 2.19.0
- */
- /**
- * In the editor, sometimes it is useful to have guided questions for the Methods section
- * in addition to the generic numbered method steps. These custom methods are defined here
- * as an array of literal objects that define each custom Methods question. Custom methods
- * are serialized to the EML as regular method steps, but with an unchangeable title, defined here,
- * in order to identify them.
- * @type {CustomEMLMethod}
- * @since 2.19.0
- */
- customEMLMethods: [],
-
- /**
- * Configuration options for a drop down list of taxa.
- * @typedef {object} AppConfig#quickAddTaxaList
- * @type {Object}
- * @property {string} label - The label for the dropdown menu
- * @property {string} placeholder - The placeholder text for the input field
- * @property {EMLTaxonCoverage#taxonomicClassification[]} taxa - The list of taxa to show in the dropdown menu
- * @example
- * {
- * label: "Primates",
- * placeholder: "Select one or more primates",
- * taxa: [
- * {
- * commonName: "Bonobo",
- * taxonRankName: "Species",
- * taxonRankValue: "Pan paniscus",
- * taxonId: {
- * provider: "ncbi",
- * value: "9597"
- * }
- * },
- * {
- * commonName: "Chimpanzee",
- * ...
- * },
- * ...
- * }
- * @since 2.24.0
- */
-
- /**
- * A list of taxa to show in the Taxa Quick Add section of the EML editor.
- * This can be used to expedite entry of taxa that are common in the
- * repository's domain. The quickAddTaxa is a list of objects, each
- * defining a separate dropdown interface. This way, common taxa can
- * be grouped together.
- * Alternative, provide a SID for a JSON data object that is stored in the
- * repository. The JSON must be in the same format as required for this
- * configuration option.
- * @since 2.24.0
- * @type {AppConfig#quickAddTaxaList[] | string}
- * @example
- * [
- * {
- * label: "Bats"
- * placeholder: "Select one or more bats",
- * taxa: [ ... ]
- * },
- * {
- * label: "Birds"
- * placeholder: "Select one or more birds",
- * taxa: [ ... ]
- * }
- * ]
- */
- quickAddTaxa: [],
-
- /**
- * The base URL for the repository. This only needs to be changed if the repository
- * is hosted at a different origin than the MetacatUI origin. This URL is used to contruct all
- * of the DataONE REST API URLs. If you are testing MetacatUI against a development repository
- * at an external location, this is where you would set that external repository URL.
- * @type {string}
- * @default window.location.origin || (window.location.protocol + "//" + window.location.host)
- */
- baseUrl: window.location.origin || (window.location.protocol + "//" + window.location.host),
-
- /**
- * The directory that metacat is installed in at the `baseUrl`. For example, if you
- * have metacat installed in the tomcat webapps directory as `metacat`, then this should be set
- * to "/metacat". Or if you renamed the metacat webapp to `catalog`, then it should be `/catalog`.
- * @type {string}
- * @default "/metacat"
- */
- context: MetacatUI.AppConfig.metacatContext || '/metacat',
-
- /**
- * The URL fragment for the DataONE Member Node (MN) API.
- * @type {string}
- * @default '/d1/mn/v2'
- */
- d1Service: '/d1/mn/v2',
- /**
- * The base URL of the DataONE Coordinating Node (CN). CHange this if you
- * are testing a deployment in a development environment.
- * @type {string}
- * @default "https://cn.dataone.org"
- * @example "https://cn-stage.test.dataone.org"
- */
- d1CNBaseUrl: "https://cn.dataone.org",
- /**
- * The URL fragment for the DataONE Coordinating Node (CN) API.
- * @type {string}
- * @default '/cn/v2'
- */
- d1CNService: "/cn/v2",
- /**
- * The URL for the DataONE Search MetacatUI. This only needs to be changed
- * if you want to point to a development environment.
- * @type {string}
- * @default "https://search.dataone.org"
- * @readonly
- * @since 2.13.0
- */
- dataoneSearchUrl: "https://search.dataone.org",
- /**
- * The URL for the DataONE listNodes() API. This URL is contructed dynamically when the
- * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNCore.listNodes)
- * @type {string}
- */
- nodeServiceUrl: null,
- /**
- * The URL for the DataONE View API. This URL is contructed dynamically when the
- * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#module-MNView)
- * @type {string}
- */
- viewServiceUrl: null,
- /**
- * The URL for the DataONE getPackage() API. This URL is contructed dynamically when the
- * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNPackage.getPackage)
- *
- * @type {string}
- */
- packageServiceUrl: null,
- /**
- * The URL for the Metacat Publish service. This URL is contructed dynamically when the
- * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
- * @type {string}
- */
- publishServiceUrl: null,
- /**
- * The URL for the DataONE isAuthorized() API. This URL is contructed dynamically when the
- * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNAuthorization.isAuthorized)
- * @type {string}
- */
- authServiceUrl: null,
- /**
- * The URL for the DataONE query API. This URL is contructed dynamically when the
- * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNQuery.query)
- * @type {string}
- */
- queryServiceUrl: null,
- /**
- * The URL for the DataONE reserveIdentifier() API. This URL is contructed dynamically when the
- * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNCore.reserveIdentifier)
- * @type {string}
- */
- reserveServiceUrl: null,
- /**
- * The URL for the DataONE system metadata API. This URL is contructed dynamically when the
- * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNRead.getSystemMetadata
- * and https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNStorage.updateSystemMetadata)
- * @type {string}
- */
- metaServiceUrl: null,
- /**
- * The URL for the DataONE system metadata API. This URL is contructed dynamically when the
- * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNRead.getSystemMetadata
- * and https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNStorage.updateSystemMetadata)
- * @type {string}
- */
- objectServiceUrl: null,
- /**
- * The URL for the DataONE Formats API. This URL is contructed dynamically when the
- * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNCore.listFormats)
- * @type {string}
- */
- formatsServiceUrl: null,
- /**
- * The URL fragment for the DataONE Formats API. This is combined with the AppConfig#formatsServiceUrl
- * @type {string}
- * @default "/formats"
- */
- formatsUrl: "/formats",
-
- /**
- * If true, parts of the UI (most notably, "funding" field in the dataset editor)
- * may look up NSF Award information
- * @type {boolean}
- * @default false
- */
- useNSFAwardAPI: false,
- /**
- * The URL for the NSF Award API, which can be used by the {@link LookupModel}
- * to look up award information for the dataset editor or other views. The
- * URL must point to a proxy that can make requests to the NSF Award API,
- * since it does not support CORS.
- * @type {string}
- * @default "/research.gov/awardapi-service/v1/awards.json"
- */
- grantsUrl: "/research.gov/awardapi-service/v1/awards.json",
-
- /**
- * The base URL for the ORCID REST services
- * @type {string}
- * @default "https:/orcid.org"
- */
- orcidBaseUrl: "https:/orcid.org",
-
- /**
- * The URL for the ORCID search API, which can be used to search for information
- * about people using their ORCID, email, name, etc.
- * This URL is constructed dynamically once the {@link AppModel} is initialized.
- * @type {string}
- */
- orcidSearchUrl: null,
-
- /**
- * The URL for the Metacat API. The Metacat API has been deprecated and is kept here
- * for compatability with Metacat repositories that are using the old x509 certificate
- * authentication mechanism. This is deprecated since authentication is now done via
- * the DataONE Portal service using auth tokens. (Using the {@link AppConfig#tokenUrl})
- * This URL is contructed dynamically when the AppModel is initialized.
- * Only override this if you are an advanced user and have a reason to!
- * @type {string}
- */
- metacatServiceUrl: null,
-
- /**
- * If false, the /monitor/status (the service that returns the status of various DataONE services) will not be used.
- * @type {boolean}
- * @default true
- * @since 2.9.0
- */
- enableMonitorStatus: true,
-
- /**
- * The URL for the service that returns the status of various DataONE services.
- * The only supported status so far is the search index queue -- the number of
- * objects that are waiting to be indexed in the Solr search index.
- * This URL is contructed dynamically when the
- * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
- * @type {string}
- * @since 2.9.0
- */
- monitorStatusUrl: "",
-
- /**
- * If true, users will see a page with sign-in troubleshooting tips
- * @type {boolean}
- * @default true
- * @since 2.13.3
- */
- showSignInHelp: true,
- /**
- * If true, users can sign in using CILogon as the identity provider.
- * ORCID is the only recommended identity provider. CILogon may be deprecated
- * in the future.
- * @type {boolean}
- * @default false
- */
- enableCILogonSignIn: false,
- /**
- * The URL for the DataONE Sign In API using CILogon as the identity provider
- * This URL is constructed dynamically once the {@link AppModel} is initialized.
- * @type {string}
- */
- signInUrl: null,
- /**
- * The URL for the DataONE Sign Out API
- * This URL is constructed dynamically once the {@link AppModel} is initialized.
- * @type {string}
- */
- signOutUrl: null,
- /**
- * The URL for the DataONE Sign In API using ORCID as the identity provider
- * This URL is constructed dynamically once the {@link AppModel} is initialized.
- * @type {string}
- */
- signInUrlOrcid: null,
-
- /**
- * Enable DataONE LDAP authentication. If true, users can sign in from an LDAP account that is in the DataONE CN LDAP directory.
- * This is not recommended, as DataONE is moving towards supporting only ORCID logins for users.
- * This LDAP authentication is separate from the File-based authentication for the Metacat Admin interface.
- * @type {boolean}
- * @default false
- * @since 2.11.0
- */
- enableLdapSignIn: false,
- /**
- * The URL for the DataONE Sign In API using LDAP as the identity provider
- * This URL is constructed dynamically once the {@link AppModel} is initialized.
- * @type {string}
- */
- signInUrlLdap: null,
-
- /**
- * The URL for the DataONE Token API using ORCID as the identity provider
- * This URL is constructed dynamically once the {@link AppModel} is initialized.
- * @type {string}
- */
- tokenUrl: null,
- /**
- * The URL for the DataONE echoCredentials() API
- * This URL is constructed dynamically once the {@link AppModel} is initialized.
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNDiagnostic.echoCredentials)
- * @type {string}
- */
- checkTokenUrl: null,
- /**
- * The URL for the DataONE Identity API
- * This URL is constructed dynamically once the {@link AppModel} is initialized.
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#module-CNIdentity)
- * @type {string}
- */
- accountsUrl: null,
- /**
- * The URL for the DataONE Pending Maps API
- * This URL is constructed dynamically once the {@link AppModel} is initialized.
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNIdentity.getPendingMapIdentity)
- * @type {string}
- */
- pendingMapsUrl: null,
- /**
- * The URL for the DataONE mapIdentity() API
- * This URL is constructed dynamically once the {@link AppModel} is initialized.
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNIdentity.mapIdentity)
- * @type {string}
- */
- accountsMapsUrl: null,
- /**
- * The URL for the DataONE Groups API
- * This URL is constructed dynamically once the {@link AppModel} is initialized.
- * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNIdentity.createGroup)
- * @type {string}
- */
- groupsUrl: null,
- /**
- * The URL for the DataONE metadata assessment service
- * @type {string}
- * @default "https://api.dataone.org/quality"
- */
- mdqBaseUrl: "https://api.dataone.org/quality",
- /**
- * Metadata Assessment Suite IDs for the dataset assessment reports.
- * @type {string[]}
- * @default ["FAIR-suite-0.4.0"]
- */
- mdqSuiteIds: ["FAIR-suite-0.4.0"],
- /**
- * Metadata Assessment Suite labels for the dataset assessment reports
- * @type {string[]}
- * @default ["FAIR Suite v0.4.0"]
- */
- mdqSuiteLabels: ["FAIR Suite v0.4.0"],
- /**
- * Metadata Assessment Suite IDs for the aggregated assessment charts
- * @type {string[]}
- * @default ["FAIR-suite-0.4.0"]
- */
- mdqAggregatedSuiteIds: ["FAIR-suite-0.4.0"],
- /**
- * Metadata Assessment Suite labels for the aggregated assessment charts
- * @type {string[]}
- * @default ["FAIR Suite v0.4.0"]
- */
- mdqAggregatedSuiteLabels: ["FAIR Suite v0.4.0"],
- /**
- * The metadata formats for which to display metadata assessment reports
- * @type {string[]}
- * @default ["eml*", "https://eml*", "*isotc211*"]
- */
- mdqFormatIds:["eml*", "https://eml*", "*isotc211*"],
-
- /**
- * Metrics endpoint url
- * @type {string}
- */
- metricsUrl: 'https://logproc-stage-ucsb-1.test.dataone.org/metrics',
-
- /**
- * Forwards collection Query to Metrics Service if enabled
- * @type {boolean}
- * @default true
- */
- metricsForwardCollectionQuery: true,
-
- /**
- * DataONE Citation reporting endpoint url
- * @type {string}
- */
- dataoneCitationsUrl: 'https://logproc-stage-ucsb-1.test.dataone.org/citations',
-
- /**
- * Hide or show the report Citation button in the dataset landing page.
- * @type {boolean}
- * @default true
- */
- hideReportCitationButton: false,
-
- /**
- * Hide or show the aggregated citations chart in the StatsView.
- * These charts are only available for DataONE Plus members or Hosted Repositories.
- * (see https://dataone.org)
- * @type {boolean}
- * @default true
- * @since 2.9.0
- */
- hideSummaryCitationsChart: true,
- /**
- * Hide or show the aggregated downloads chart in the StatsView
- * These charts are only available for DataONE Plus members or Hosted Repositories.
- * (see https://dataone.org)
- * @type {boolean}
- * @default true
- * @since 2.9.0
- */
- hideSummaryDownloadsChart: true,
- /**
- * Hide or show the aggregated metadata assessment chart in the StatsView
- * These charts are only available for DataONE Plus members or Hosted Repositories.
- * (see https://dataone.org)
- * @type {boolean}
- * @default true
- * @since 2.9.0
- */
- hideSummaryMetadataAssessment: true,
- /**
- * Hide or show the aggregated views chart in the StatsView
- * These charts are only available for DataONE Plus members or Hosted Repositories.
- * (see https://dataone.org)
- * @type {boolean}
- * @default true
- * @since 2.9.0
- */
- hideSummaryViewsChart: true,
-
- /**
- * Metrics flag for the Dataset Landing Page
- * Enable this flag to enable metrics display
- * @type {boolean}
- * @default true
- */
- displayDatasetMetrics: true,
-
- /**
- * If true, displays the dataset metrics tooltips on the metrics buttons.
- * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
- * @type {boolean}
- * @default true
- */
- displayDatasetMetricsTooltip: true,
- /**
- * If true, displays the datasets metric modal windows on the dataset landing page
- * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
- * @type {boolean}
- * @default true
- */
- displayMetricModals: true,
- /**
- * If true, displays the dataset citation metrics on the dataset landing page
- * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
- * @type {boolean}
- * @default true
- */
- displayDatasetCitationMetric: true,
- /**
- * If true, displays the dataset download metrics on the dataset landing page
- * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
- * @type {boolean}
- * @default true
- */
- displayDatasetDownloadMetric: true,
- /**
- * If true, displays the dataset view metrics on the dataset landing page
- * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
- * @type {boolean}
- * @default true
- */
- displayDatasetViewMetric: true,
- /**
- * If true, displays the citation registration tool on the dataset landing page
- * @type {boolean}
- * @default true
- * @since 2.15.0
- */
- displayRegisterCitationTool: true,
- /**
- * If true, displays the "Edit" button on the dataset landing page
- * @type {boolean}
- * @default true
- */
- displayDatasetEditButton: true,
- /**
- * If true, displays the metadata assessment metrics on the dataset landing page
- * @type {boolean}
- * @default false
- */
- displayDatasetQualityMetric: false,
- /**
- * If true, displays the WholeTale "Analyze" button on the dataset landing page
- * @type {boolean}
- * @default false
- */
- displayDatasetAnalyzeButton: false,
- /**
- * If true, displays various buttons on the dataset landing page for dataset owners
- * @type {boolean}
- * @default false
- */
- displayDatasetControls: true,
- /** Hide metrics display for SolrResult models that match the given properties.
- * Properties can be functions, which are given the SolrResult model value as a parameter.
- * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
- * @type {object}
- * @example
- * {
- * formatId: "eml://ecoinformatics.org/eml-2.1.1",
- * isPublic: true,
- * dateUploaded: function(date){
- * return new Date(date) < new Date('1995-12-17T03:24:00');
- * }
- * }
- * // This example would hide metrics for any objects that are:
- * // EML 2.1.1 OR public OR were uploaded before 12/17/1995.
- */
- hideMetricsWhen: null,
-
- /**
- * The bounding box path color to use in the Google Static Map images on the dataset landing pages.
- * Specify the color either as a 24-bit (example: color=0xFFFFCC) or 32-bit hexadecimal value
- * (example: color=0xFFFFCCFF), or from the set: black, brown, green, purple, yellow, blue, gray, orange, red, white.
- * For more information, see the Google Statis Maps API docs: https://developers.google.com/maps/documentation/maps-static/start#PathStyles
- * @type {string}
- * @default "0xDA4D3Aff" (red)
- * @since 2.13.0
- */
- datasetMapPathColor: "0xDA4D3Aff",
-
- /**
- * The bounding box fill color to use in the Google Static Map images on the dataset landing pages.
- * If you don't want to fill in the bounding boxes with a color, set this to null or undefined.
- * Specify the color either as a 24-bit (example: color=0xFFFFCC) or 32-bit hexadecimal value
- * (example: color=0xFFFFCCFF), or from the set: black, brown, green, purple, yellow, blue, gray, orange, red, white.
- * For more information, see the Google Statis Maps API docs: https://developers.google.com/maps/documentation/maps-static/start#PathStyles
- * @type {string}
- * @default "0xFFFF0033" (light yellow)
- * @since 2.13.0
- */
- datasetMapFillColor: "0xFFFF0033",
-
- /**
- * The hue/color of the tiles drawn on the map when searching for data.
- * This should be a three-digit hue degree between 0 and 360. (Try https://hslpicker.com)
- * This is set on the {@link Map} model when it is initialized.
- * @type {string}
- * @default "192" (blue)
- * @since 2.13.3
- */
- searchMapTileHue: "192",
-
- /**
- * If true, the dataset landing pages will generate Schema.org-compliant JSONLD
- * and insert it into the page.
- * @type {boolean}
- * @default true
- */
- isJSONLDEnabled: true,
-
- /**
- * If true, users can see a "Publish" button in the MetadataView, which makes the metadata
- * document public and gives it a DOI identifier.
- * If false, the button will be hidden completely.
- * @type {boolean}
- * @default true
- */
- enablePublishDOI: true,
-
- /**
- * A list of users or groups who exclusively will be able to see and use the "Publish" button,
- * which makes the metadata document public and gives it a DOI identifier.
- * Anyone not in this list will not be able to see the Publish button.
- * `enablePublishDOI` must be set to `true` for this to take effect.
- * @type {string[]}
- */
- enablePublishDOIForSubjects: [],
-
- /**
- * If true, users can change the AccessPolicy for any of their objects.
- * This is equivalent to setting {@link AppConfig#allowAccessPolicyChangesPortals} and
- * {@link AppConfig#allowAccessPolicyChangesDatasets} to `true`.
- * @type {boolean}
- * @default true
- * @since 2.9.0
- */
- allowAccessPolicyChanges: true,
-
- /**
- * If true, users can change the AccessPolicy for their portals only.
- * @type {boolean}
- * @default true
- * @since 2.15.0
- */
- allowAccessPolicyChangesPortals: true,
-
- /**
- * Limit portal Access policy editing to only a defined list of people or groups.
- * To let everyone edit access policies for their own objects, keep this as an empty array
- * and make sure {@link AppConfig#allowAccessPolicyChangesPortals} is set to `true`
- * @type {boolean}
- * @default []
- * @since 2.15.0
- */
- allowAccessPolicyChangesPortalsForSubjects: [],
-
- /**
- * If true, users can change the AccessPolicy for their datasets only.
- * @type {boolean}
- * @default true
- * @since 2.15.0
- */
- allowAccessPolicyChangesDatasets: true,
-
- /**
- * Limit dataset Access policy editing to only a defined list of people or groups.
- * To let everyone edit access policies for their own objects, keep this as an empty array
- * and make sure {@link AppConfig#allowAccessPolicyChangesDatasets} is set to `true`
- * @type {boolean}
- * @default true
- * @since 2.15.0
- */
- allowAccessPolicyChangesDatasetsForSubjects: [],
-
- /**
- * The default {@link AccessPolicy} set on new objects uploaded to the repository.
- * Each literal object here gets set directly on an {@link AccessRule} model.
- * See the {@link AccessRule} list of default attributes for options on what to set here.
- * @see {@link AccessRule}
- * @type {object[]}
- * @since 2.9.0
- * @default [{
- subject: "public",
- read: true
- }]
- * @example
- * [{
- * subject: "public",
- * read: true
- * }]
- * // This example would assign public access to all new objects created in MetacatUI.
- */
- defaultAccessPolicy: [{
- subject: "public",
- read: true
- }],
-
- /**
- * When new data objects are added to a {@link DataPackage}, they can either inherit the {@link AccessPolicy} from the
- * parent metadata object, or default to the {@link AppConfig#defaultAccessPolicy}. To inherit the {@link AccessPolicy}
- * from the parent metadata object, set this config to `true`.
- * @type {boolean}
- * @default true
- * @since 2.15.0
- */
- inheritAccessPolicy: true,
-
- /**
- * The user-facing name for editing the Access Policy. This is displayed as the header of the AccessPolicyView, for example
- * @type {string}
- * @since 2.9.0
- * @default "Sharing options"
- */
- accessPolicyName: "Sharing options",
-
- /**
- * @type {object}
- * @property {boolean} accessRuleOptions.read - If true, users will be able to give others read access to their DataONE objects
- * @property {boolean} accessRuleOptions.write - If true, users will be able to give others write access to their DataONE objects
- * @property {boolean} accessRuleOptions.changePermission - If true, users will be able to give others changePermission access to their DataONE objects
- * @since 2.9.0
- * @default {
- read: true,
- write: true,
- changePermission: true
- }
- * @example
- * {
- * read: true,
- * write: true,
- * changePermission: false
- * }
- * // This example would enable users to edit the read and write access to files,
- * // but not change ownership, in the Access Policy View.
- */
- accessRuleOptions: {
- read: true,
- write: true,
- changePermission: true
- },
-
- /**
- * @type {object}
- * @property {boolean} accessRuleOptionNames.read - The user-facing name of the "read" access in Access Rules
- * @property {boolean} accessRuleOptionNames.write - The user-facing name of the "write" access in Access Rules
- * @property {boolean} accessRuleOptionNames.changePermission - The user-facing name of the "changePermission" access in Access Rules
- * @since 2.9.0
- * @example
- * {
- * read: "Can view",
- * write: "Can edit",
- * changePermission: "Is owner"
- * }
- */
- accessRuleOptionNames: {
- read: "Can view",
- write: "Can edit",
- changePermission: "Is owner"
- },
-
- /**
- * If false, the rightsHolder of a resource will not be displayed in the AccessPolicyView.
- * @type {boolean}
- * @default true
- * @since 2.9.0
- */
- displayRightsHolderInAccessPolicy: true,
-
- /**
- * If false, users will not be able to change the rightsHolder of a resource in the AccessPolicyView
- * @type {boolean}
- * @default true
- * @since 2.9.0
- */
- allowChangeRightsHolder: true,
-
- /**
- * A list of group subjects that will be hidden in the AccessPolicy view to
- * everyone except those in the group. This is useful for preventing users from
- * removing repository administrative groups from access policies.
- * @type {string[]}
- * @since 2.9.0
- * @example ["CN=data-admin-group,DC=dataone,DC=org"]
- */
- hiddenSubjectsInAccessPolicy: [],
-
- /**
- * The format ID the portal editor serializes a new portal document as
- * @type {string}
- * @default "https://purl.dataone.org/portals-1.1.0"
- * @readonly
- * @since 2.17.0
- */
- portalEditorSerializationFormat: "https://purl.dataone.org/portals-1.1.0",
-
- /**
- * If true, the public/private toggle will be displayed in the Sharing Options for portals.
- * @type {boolean}
- * @default true
- * @since 2.9.0
- */
- showPortalPublicToggle: true,
-
- /**
- * The public/private toggle will be displayed in the Sharing Options for portals for only
- * the given users or groups. To display the public/private toggle for everyone,
- * set `showPortalPublicToggle` to true and keep this array empty.
- * @type {string[]}
- * @since 2.9.0
- */
- showPortalPublicToggleForSubjects: [],
-
- /**
- * If true, the public/private toggle will be displayed in the Sharing Options for datasets.
- * @type {boolean}
- * @default true
- * @since 2.9.0
- */
- showDatasetPublicToggle: true,
-
- /**
- * The public/private toggle will be displayed in the Sharing Options for datasets for only
- * the given users or groups. To display the public/private toggle for everyone,
- * set `showDatasetPublicToggle` to true and keep this array empty.
- * @type {string[]}
- * @since 2.15.0
- */
- showDatasetPublicToggleForSubjects: [],
-
- /**
- * Set to false to hide the display of "My Portals", which shows the user's current portals
- * @type {boolean}
- * @default true
- */
- showMyPortals: true,
- /**
- * The user-facing term for portals in lower-case and in singular form.
- * e.g. "portal"
- * @type {string}
- * @default "portal"
- */
- portalTermSingular: "portal",
- /**
- * The user-facing term for portals in lower-case and in plural form.
- * e.g. "portals". This allows for portal terms with irregular plurals.
- * @type {string}
- * @default "portals"
- */
- portalTermPlural: "portals",
- /**
- * A URL of a webpage for people to learn more about portals. If no URL is provided,
- * links to more info about portals will be omitted.
- * @since 2.14.0
- * @type {string}
- * @example "https://dataone.org/plus"
- * @default null
- */
- portalInfoURL: null,
- /**
- * The URL for a webpage where people can learn more about custom portal search
- * filters. If no URL is provided, links to more info about portals will be omitted.
- * @since 2.17.0
- * @type {string}
- * @example "https://dataone.org/custom-search"
- * @default null
- */
- portalSearchFiltersInfoURL: null,
- /**
- * Set to false to prevent ANYONE from creating a new portal.
- * @type {boolean}
- * @default true
- */
- enableCreatePortals: true,
- /**
- * Limits only the following people or groups to create new portals. If this is left as an empty array,
- * then any logged-in user can create a portal.
- * @type {string[]}
- */
- limitPortalsToSubjects: [],
-
- /**
- * This message will display when a user tries to create a new Portal in the PortalEditor
- * when they are not associated with a whitelisted subject in the `limitPortalsToSubjects` list
- * @type {string}
- */
- portalEditNotAuthCreateMessage: "You have not been authorized to create new portals. Please contact us with any questions.",
-
- /**
- * This message will display when a user tries to access the Portal Editor for a portal
- * for which they do not have write permission.
- * @type {string}
- */
- portalEditNotAuthEditMessage: "The portal owner has not granted you permission to edit this portal. Please contact the owner to be given edit permission.",
-
- /**
- * This message will display when a user tries to create a new portal when they have exceeded their DataONE portal quota
- * @type {string}
- */
- portalEditNoQuotaMessage: "You have already reached the maximum number of portals for your membership level.",
-
- /**
- * This message will display when there is any non-specific error during the save process of the PortalEditor.
- * @type {string}
- */
- portalEditSaveErrorMsg: "Something went wrong while attempting to save your changes.",
-
- /**
- * The list of fields that should be required in the portal editor.
- * Set individual properties to `true` to require them in the portal editor.
- * @type {object}
- * @property {boolean} label - Default: true
- * @property {boolean} name - Default: true
- * @property {boolean} description - Default: false
- * @property {boolean} sectionTitle - Default: true
- * @property {boolean} sectionIntroduction - Default: false
- * @property {boolean} logo - Default: false
- */
- portalEditorRequiredFields: {
- label: true,
- name: true,
- description: false,
- sectionTitle: true,
- sectionIntroduction: false,
- logo: false,
- //The following fields are not yet supported as required fields in the portal editor
- //TODO: Add support for requiring the below fields
- sectionImage: false,
- acknowledgments: false,
- acknowledgmentsLogos: false,
- awards: false,
- associatedParties: false
- },
-
- /**
- * A list of portals labels that no one should be able to create portals with
- * @type {string[]}
- * @readonly
- * @since 2.11.3
- */
- portalLabelBlockList: [
- "Dataone",
- 'urn:node:CN', 'CN', 'cn',
- 'urn:node:CNUNM1', 'CNUNM1', 'cn-unm-1',
- 'urn:node:CNUCSB1', 'CNUCSB1', 'cn-ucsb-1',
- 'urn:node:CNORC1', 'CNORC1', 'cn-orc-1',
- 'urn:node:KNB', 'KNB', 'KNB Data Repository',
- 'urn:node:ESA', 'ESA', 'ESA Data Registry',
- 'urn:node:SANPARKS', 'SANPARKS', 'SANParks Data Repository',
- 'urn:node:ORNLDAAC', 'ORNLDAAC', 'ORNL DAAC',
- 'urn:node:LTER', 'LTER', 'U.S. LTER Network',
- 'urn:node:CDL', 'CDL', 'UC3 Merritt',
- 'urn:node:PISCO', 'PISCO', 'PISCO MN',
- 'urn:node:ONEShare', 'ONEShare', 'ONEShare DataONE Member Node',
- 'urn:node:mnORC1', 'mnORC1', 'DataONE ORC Dedicated Replica Server',
- 'urn:node:mnUNM1', 'mnUNM1', 'DataONE UNM Dedicated Replica Server',
- 'urn:node:mnUCSB1', 'mnUCSB1', 'DataONE UCSB Dedicated Replica Server',
- 'urn:node:TFRI', 'TFRI', 'TFRI Data Catalog',
- 'urn:node:USANPN', 'USANPN', 'USA National Phenology Network',
- 'urn:node:SEAD', 'SEAD', 'SEAD Virtual Archive',
- 'urn:node:GOA', 'GOA', 'Gulf of Alaska Data Portal',
- 'urn:node:KUBI', 'KUBI', 'University of Kansas - Biodiversity Institute',
- 'urn:node:LTER_EUROPE', 'LTER_EUROPE', 'LTER Europe Member Node',
- 'urn:node:DRYAD', 'DRYAD', 'Dryad Digital Repository',
- 'urn:node:CLOEBIRD', 'CLOEBIRD', 'Cornell Lab of Ornithology - eBird',
- 'urn:node:EDACGSTORE', 'EDACGSTORE', 'EDAC Gstore Repository',
- 'urn:node:IOE', 'IOE', 'Montana IoE Data Repository',
- 'urn:node:US_MPC', 'US_MPC', 'Minnesota Population Center',
- 'urn:node:EDORA', 'EDORA', 'Environmental Data for the Oak Ridge Area (EDORA)',
- 'urn:node:RGD', 'RGD', 'Regional and Global biogeochemical dynamics Data (RGD)',
- 'urn:node:GLEON', 'GLEON', 'GLEON Data Repository',
- 'urn:node:IARC', 'IARC', 'IARC Data Archive',
- 'urn:node:NMEPSCOR', 'NMEPSCOR', 'NM EPSCoR Tier 4 Node',
- 'urn:node:TERN', 'TERN', 'TERN Australia',
- 'urn:node:NKN', 'NKN', 'Northwest Knowledge Network',
- 'urn:node:USGS_SDC', 'USGS_SDC', 'USGS Science Data Catalog',
- 'urn:node:NRDC', 'NRDC', 'NRDC DataONE member node',
- 'urn:node:NCEI', 'NCEI', 'NOAA NCEI Environmental Data Archive',
- 'urn:node:PPBIO', 'PPBIO', 'PPBio',
- 'urn:node:NEON', 'NEON', 'NEON Member Node',
- 'urn:node:TDAR', 'TDAR', 'The Digital Archaeological Record',
- 'urn:node:ARCTIC', 'ARCTIC', 'Arctic Data Center',
- 'urn:node:BCODMO', 'BCODMO', 'Biological and Chemical Oceanography Data Management Office (BCO-DMO) ',
- 'urn:node:GRIIDC', 'GRIIDC', 'Gulf of Mexico Research Initiative Information and Data Cooperative (GRIIDC)',
- 'urn:node:R2R', 'R2R', 'Rolling Deck to Repository (R2R)',
- 'urn:node:EDI', 'EDI', 'Environmental Data Initiative',
- 'urn:node:UIC', 'UIC', 'A Member Node for University of Illinois at Chicago.',
- 'urn:node:RW', 'RW', 'Research Workspace',
- 'urn:node:FEMC', 'FEMC', 'Forest Ecosystem Monitoring Cooperative Member Node',
- 'urn:node:OTS_NDC', 'OTS_NDC', 'Organization for Tropical Studies - Neotropical Data Center',
- 'urn:node:PANGAEA', 'PANGAEA', 'PANGAEA',
- 'urn:node:ESS_DIVE', 'ESS_DIVE', 'ESS-DIVE: Deep Insight for Earth Science Data',
- 'urn:node:CAS_CERN', 'CAS_CERN', 'Chinese Ecosystem Research Network (CERN)',
- 'urn:node:FIGSHARE_CARY', 'FIGSHARE_CARY', 'Cary Institute of Ecosystem Studies (powered by Figshare)',
- 'urn:node:IEDA_EARTHCHEM', 'IEDA_EARTHCHEM', 'IEDA EARTHCHEM',
- 'urn:node:IEDA_USAP', 'IEDA_USAP', 'IEDA USAP',
- 'urn:node:IEDA_MGDL', 'IEDA_MGDL', 'IEDA MGDL',
- 'urn:node:METAGRIL', 'METAGRIL', 'metaGRIL',
- 'urn:node:ARM', 'ARM', 'ARM - Atmospheric Radiation Measurement Research Facility',
- "urn:node:CA_OPC", "CA_OPC", "OPC",
- "urn:node:TNC_DANGERMOND", "dangermond", "TNC_DANGERMOND", "dangermondpreserve"
- ],
-
- /**
- * Limit users to a certain number of portals. This limit will be ignored if {@link AppConfig#enableBookkeeperServices}
- * is set to true, because the limit will be enforced by Bookkeeper Quotas instead.
- * @type {number}
- * @default 100
- * @since 2.14.0
- */
- portalLimit: 100,
-
- /**
- * The default values to use in portals. Default sections are applied when a portal is new.
- * Default images are used in new freeform pages in the portal builder.
- * The default colors are used when colors haven't been saved to the portal document.
- * Colors can be hex codes, rgb codes, or any other form supported by browsers in CSS
- * @type {object}
- * @property {object[]} sections The default sections for a new portal. Each object within the section array can have a title property and a label property
- * @property {string} label The name of the section that will appear in the tab
- * @property {string} title A longer title for the section that will appear in the section header
- * @property {string} newPortalActiveSectionLabel When a user start the portal builder for a brand new portal, the label for the section that the builder should start on. Can be set to "Data", "Metrics", "Settings", or one of the labels from the default sections described above.
- * @property {string[]} sectionImageIdentifiers A list of image pids to use as default images for new markdown sections
- * @property {string} primaryColor The color that is used most frequently in the portal view
- * @property {string} secondaryColor The color that is used second-most frequently in the portal view
- * @property {string} accentColor The color that is rarely used in portal views as an accent color
- * @property {string} primaryColorTransparent An rgba() version of the primaryColor that is semi-transparent
- * @property {string} secondaryColorTransparent An rgba() version of the secondaryColor that is semi-transparent
- * @property {string} accentColorTransparent An rgba() version of the accentColor that is semi-transparent
- * @example {
- * sections: [
- * { label: "About",
- * title: "About our project"
- * },
- * { label: "Publications",
- * title: "Selected publications by our lab group"
- * }
- * ],
- * newPortalActiveSectionLabel: "About",
- * sectionImageIdentifiers: ["urn:uuid:d2f31a83-debf-4d78-bef7-6abe20962581", "urn:uuid:6ad37acd-d0ac-4142-9f42-e5f05ff55564", "urn:uuid:0b6be09f-2e6f-4e7b-a83c-2823495f9608", "urn:uuid:5b4e0347-07ed-4580-b039-6c4df57ed801", "urn:uuid:0cf62da9-a099-440e-9c1e-595a55c0d60d"],
- * primaryColor: "#16acc0",
- * primaryColorTransparent: "rgba(22, 172, 192, .7)",
- * secondaryColor: "#EED268",
- * secondaryColorTransparent: "rgba(238, 210, 104, .7)",
- * accentColor: "#0f5058",
- * accentColorTransparent: "rgba(15, 80, 88, .7)"
- * }
- * @since 2.14.0
- */
- portalDefaults: {
- },
-
- /**
- * Add an API service URL that retrieves projects data. This is an optional
- * configuration in case the memberNode have a third-party service that provides
- * their projects information.
- *
- * If the configuration is not set, set the default projects list in the views using it.
- *
- * @type {string}
- * @private
- * @since 2.20.0 #TODO Update version here.
- */
- projectsApiUrl: undefined,
- /**
- * Enable or disable the use of Fluid Earth Viewer visualizations in portals.
- * This config option is marked as `private` since this is an experimental feature.
- * @type {boolean}
- * @private
- * @since 2.13.4
- */
- enableFeverVisualizations: false,
- /**
- * The relative path to the location where the Fluid Earth Viewer (FEVer) is deployed. This should be
- * deployed at the same origin as MetacatUI, since your web server configuration and many browsers
- * may block iframes from different origins.
- * This config option is marked as `private` since this is an experimental feature.
- * @type {string}
- * @private
- * @since 2.13.4
- */
- feverPath: "/fever",
- /**
- * The full URL to the location where the Fluid Earth Viewer (FEVer) is deployed.
- * This URL is constructed during {@link AppModel#initialize} using the {@link AppConfig#baseUrl}
- * and {@link AppConfig#feverPath}.
- * This config option is marked as `private` since this is an experimental feature.
- * @type {string}
- * @readonly
- * @private
- * @since 2.13.4
- */
- feverUrl: "",
-
- /** If true, then archived content is available in the search index.
- * Set to false if this MetacatUI is using a Metacat version before 2.10.0
- * @type {boolean}
- * @default true
- */
- archivedContentIsIndexed: true,
-
- /**
- * The metadata fields to hide when a user is creating a collection definition using
- * the Query Builder View displayed in the portal builder on the data page, or
- * anywhere else the EditCollectionView is displayed. Strings listed here should
- * exactly match the 'name' for each field provided by the DataONE search index API
- * (i.e. should match the Solr field).
- * @example ["sem_annotated_by", "mediaType"]
- * @type {string[]}
- */
- collectionQueryExcludeFields: [
- "sem_annotated_by", "sem_annotates", "sem_comment", "pubDate",
- "namedLocation", "contactOrganization", "investigator", "originator",
- "originatorText", "serviceInput",
- "authorGivenName", "authorSurName", "topic", "webUrl", "_root_",
- "collectionQuery", "geohash_1", "geohash_2", "geohash_3", "geohash_4",
- "geohash_5", "geohash_6", "geohash_7", "geohash_8", "geohash_9", "label",
- "LTERSite", "_version_", "checksumAlgorithm", "keywords",
- "parameterText", "project", "topicText", "dataUrl", "fileID",
- "isDocumentedBy", "logo", "obsoletes", "origin", "funding", "formatType",
- "obsoletedBy", "presentationCat", "mediaType", "mediaTypeProperty",
- "relatedOrganizations", "noBoundingBox", "decade", "hasPart", "sensorText",
- "sourceText", "termText", "titlestr", "site", "id", "updateDate",
- "edition", "gcmdKeyword", "isSpatial", "keyConcept", "ogcUrl", "parameter",
- "sensor", "source", "term", "investigatorText", "sku", "_text_",
- // Fields that have been made into a special combination field
- "beginDate", "endDate", "awardNumber",
- // Provenance fields (keep only "prov_hasSources" and "prov_hasDerivations"),
- // since they are the only ones indexed on metadata objects
- "prov_wasGeneratedBy", "prov_generated", "prov_generatedByExecution",
- "prov_generatedByProgram", "prov_generatedByUser", "prov_instanceOfClass",
- "prov_used", "prov_usedByExecution", "prov_usedByProgram", "prov_usedByUser",
- "prov_wasDerivedFrom", "prov_wasExecutedByExecution", "prov_wasExecutedByUser",
- "prov_wasInformedBy"
- ],
-
- /**
- * A special field is one that does not exist in the query service index (i.e.
- * Solr). It can be a combination of fields that are presented to the user as a
- * single field, but which are added to the model as multiple fields. It can also be
- * a duplicate of a field that does exist, but presented with a different label (and
- * even with different {@link operatorOptions operator options} or
- * {@link valueSelectUImap value input} if needed).
- *
- * @typedef {Object} SpecialField
- * @property {string} name - A unique ID to represent this field. It must not match
- * the name of any other query fields.
- * @property {string[]} fields - The list of real query fields that this abstracted
- * field should represent. The query fields listed must exactly match the names of
- * the query fields that are retrieved from the query service.
- * @property {string} label - A user-facing label to display.
- * @property {string} description - A description for this field.
- * @property {string} category - The name of the category under which to place this
- * field. It must match one of the category names for an existing query field set in
- * {@link QueryField#categoriesMap}.
- * @property {string[]} [values] - An optional list of filter values. If set, this
- * is used to determine whether a pre-existing Query Rule should be displayed as one
- * of these special fields, or as a field from the query API. Setting values means
- * that the values set on the Query Rule model must exactly match the values set.
- *
- * @since 2.15.0
- */
-
- /**
- * A list of additional fields which are not retrieved from the query API (i.e. are
- * not Solr fields), but which should be added to the list of options the user can
- * select from when building a query in the EditCollectionView. This can be used to
- * add abstracted fields which are a combination of multiple query fields, or to add
- * a duplicate field that has a different label.
- *
- * @type {SpecialField[]}
- *
- * @since 2.15.0
- */
- collectionQuerySpecialFields: [
- {
- name: "documents-special-field",
- fields: ["documents"],
- label: "Contains Data Files",
- description: "Limit results to packages that include data files. Without" +
- " this rule, results may include packages with metadata but no data.",
- category: "General",
- values: ["*"]
- },
- {
- name: "year-data-collection",
- fields: ["beginDate", "endDate"],
- label: "Year of Data Collection",
- description: "The temporal range of content described by the metadata",
- category: "Dates"
- },
- {
- name: "funding-text-award-number",
- fields: ["fundingText", "awardNumber"],
- label: "Award Number",
- description: "The award number for funding associated with a dataset or the " +
- "description of funding source",
- category: "Awards & funding"
- }
- ],
-
- /**
- * The names of the query fields that use an object identifier as a value. Filter
- * models that use one of these fields are handled specially when building query
- * strings - they are OR'ed at the end of queries. They are also given an "OR"
- * operator and fieldsOperator attribute when parsed.
- * @type {string[]}
- *
- * @since 2.17.0
- */
- queryIdentifierFields: ["id", "identifier", "seriesId", "isPartOf"],
-
- /**
- * The name of the query fields that specify latitude. Filter models that these
- * fields are handled specially, since they must be a float value and have a
- * pre-determined minRange and maxRange (-90 to 90).
- */
- queryLatitudeFields: ["northBoundCoord", "southBoundCoord"],
-
- /**
- * The name of the query fields that specify longitude. Filter models that these
- * fields are handled specially, since they must be a float value and have a
- * pre-determined minRange and maxRange (-180 to 180).
- */
- queryLongitudeFields: ["eastBoundCoord", "westBoundCoord"],
-
- /**
- * The names of the query fields that may require special treatment in the
- * UI. For example, upgrade the view for a Filter from a FilterView to
- * a SemanticFilterView or to block certain UIBuilders in FilterEditorView
- * that don't make sense for a semantic field.
- *
- * @type {string[]}
- * @since 2.22.0
- */
- querySemanticFields: ["sem_annotation"],
-
- /**
- * The isPartOf filter is added to all new portals built in the Portal
- * Builder automatically. It is required for dataset owners to include
- * their dataset in a specific portal collection. By default, this filter
- * is hidden. Set to false to make this filter visible.
- * @type {boolean}
+ * intellectualRights: true,
+ * keywordSets: false,
+ * methods: false,
+ * samplingDescription: false,
+ * studyExtentDescription: false,
+ * temporalCoverage: false,
+ * title: true,
+ * contact: true,
+ * principalInvestigator: true
+ * }
*/
- hideIsPartOfFilter: true,
+ emlEditorRequiredFields: {
+ abstract: true,
+ alternateIdentifier: false,
+ dataSensitivity: false,
+ funding: false,
+ generalTaxonomicCoverage: false,
+ taxonCoverage: false,
+ geoCoverage: false,
+ intellectualRights: true,
+ keywordSets: false,
+ methods: false,
+ samplingDescription: false,
+ studyExtentDescription: false,
+ temporalCoverage: false,
+ title: true,
+ creator: true,
+ contact: true,
+ },
- /**
- * The default {@link FilterGroup}s to use in the data catalog search ({@link CatalogSearchView}).
- * This is an array of literal objects that will be directly set on the {@link FilterGroup} models. Refer to the {@link FilterGroup#defaults} for
- * options.
- * @type {FilterGroup#defaults[]}
+ /**
+ * A list of required fields for each EMLParty (People) in the dataset editor.
+ * This is a literal object where the keys are the EML Party type (e.g. creator, principalInvestigator) {@link see EMLParty.partytypes}
+ * and the values are arrays of field names.
+ * By default, EMLPartys are *always* required to have an individual's name, position name, or organization name.
+ * @type {object}
+ * @since 2.21.0
+ * @example
+ * {
+ * contact: ["email"],
+ * creator: ["email", "address", "phone"]
+ * principalInvestigator: ["organizationName"]
+ * }
+ * @default
+ * {
+ * }
+ */
+ emlEditorRequiredFields_EMLParty: {},
+
+ /**
+ * An array of science metadata format IDs that are editable in MetacatUI.
+ * Metadata documents with these format IDs will have an Edit button and will be
+ * editable in the Editor Views.
+ * This should only be changed if you have extended MetacatUI to edit a new format,
+ * or if you want to disable editing of a specific format ID.
+ * @type {string[]}
+ * @default [
+ "eml://ecoinformatics.org/eml-2.1.1",
+ "https://eml.ecoinformatics.org/eml-2.2.0"
+ ]
+ * @example
+ * [
+ * "eml://ecoinformatics.org/eml-2.1.1",
+ * "https://eml.ecoinformatics.org/eml-2.2.0"
+ * ]
+ * @readonly
+ */
+ editableFormats: [
+ "eml://ecoinformatics.org/eml-2.1.1",
+ "https://eml.ecoinformatics.org/eml-2.2.0",
+ ],
+
+ /**
+ * The format ID the dataset editor serializes new EML as
+ * @type {string}
+ * @default "https://eml.ecoinformatics.org/eml-2.2.0"
+ * @readonly
+ * @since 2.13.0
+ */
+ editorSerializationFormat: "https://eml.ecoinformatics.org/eml-2.2.0",
+
+ /**
+ * The XML schema location the dataset editor will use when creating new EML. This should
+ * correspond with {@link AppConfig#editorSerializationFormat}
+ * @type {string}
+ * @default "https://eml.ecoinformatics.org/eml-2.2.0 https://eml.ecoinformatics.org/eml-2.2.0/eml.xsd"
+ * @readonly
+ * @since 2.13.0
+ */
+ editorSchemaLocation:
+ "https://eml.ecoinformatics.org/eml-2.2.0 https://eml.ecoinformatics.org/eml-2.2.0/eml.xsd",
+
+ /**
+ * The text to use for the eml system attribute. The system attribute
+ * indicates the data management system within which an identifier is in
+ * scope and therefore unique. This is typically a URL (Uniform Resource
+ * Locator) that indicates a data management system. All identifiers that
+ * share a system must be unique. In other words, if the same identifier
+ * is used in two locations with identical systems, then by definition the
+ * objects at which they point are in fact the same object.
+ * @type {string}
+ * @since 2.26.0
+ * @link https://eml.ecoinformatics.org/schema/eml-resource_xsd#SystemType
+ * @link https://eml.ecoinformatics.org/schema/eml_xsd
+ */
+ emlSystem: "knb",
+
+ /**
+ * This error message is displayed when the Editor encounters an error saving
+ * @type {string}
+ */
+ editorSaveErrorMsg: "Not all of your changes could be submitted.",
+ /**
+ * This error message is displayed when the Editor encounters an error saving, and a plain-text draft is saved instead
+ * @type {string}
+ */
+ editorSaveErrorMsgWithDraft:
+ "Not all of your changes could be submitted, but a draft " +
+ "has been saved which can be accessed by our support team. Please contact us.",
+ /**
+ * The text of the Save button in the dataset editor.
+ * @type {string}
+ * @default "Save dataset"
+ * @since 2.13.3
+ */
+ editorSaveButtonText: "Save dataset",
+
+ /**
+ * A list of keyword thesauri options for the user to choose from in the EML Editor.
+ * A "None" option will also always display.
+ * @type {object[]}
+ * @property {string} label - A readable and short label for the keyword thesaurus that is displayed in the UI
+ * @property {string} thesaurus - The exact keyword thesaurus name that will be saved in the EML
+ * @since 2.10.0
+ * @default [{
+ label: "GCMD",
+ thesaurus: "NASA Global Change Master Directory (GCMD)"
+ }]
+ * @example
+ * [{
+ * label: "GCMD",
+ * thesaurus: "NASA Global Change Master Directory (GCMD)"
+ * }]
*/
- defaultFilterGroups: [
- {
- label: "",
- filters: [
+ emlKeywordThesauri: [
{
- fields: ["attribute"],
- label: "Data attribute",
- placeholder: "density, length, etc.",
- icon: "table",
- description: "Measurement type, e.g. density, temperature, species"
+ label: "GCMD",
+ thesaurus: "NASA Global Change Master Directory (GCMD)",
},
+ ],
+
+ /**
+ * If true, questions related to Data Sensitivity will be shown in the EML Editor.
+ * @type {boolean}
+ * @default true
+ * @since 2.19.0
+ */
+ enableDataSensitivityInEditor: true,
+
+ /**
+ * The URL of a webpage that shows more information about Data Sensitivity and DataTags. This will be used
+ * for links in help text throughout the app, such as next to Data Sensitivity questions in the dataset editor.
+ *
+ * @type {string}
+ * @default "http://datatags.org"
+ * @since 2.19.0
+ */
+ dataSensitivityInfoURL: "http://datatags.org",
+
+ /**
+ * In the editor, sometimes it is useful to have guided questions for the Methods section
+ * in addition to the generic numbered method steps. These custom methods are defined here
+ * as an array of literal objects that define each custom Methods question. Custom methods
+ * are serialized to the EML as regular method steps, but with an unchangeable title, defined here,
+ * in order to identify them.
+ *
+ * @typedef {object} CustomEMLMethod
+ * @property {string[]} titleOptions One or more titles that may exist in an EML Method Step that identify that Method Step as a custom method type. THe first title in the array is serialized to the EML XML.
+ * @property {string} id A unique identifier for this custom method type.
+ * @property {boolean} required If true, this custom method will be a required field for submission in the EML editor.
+ * @example [{
+ "titleOptions": ["Ethical Research Procedures"],
+ "id": "ethical-research-procedures",
+ "required": false
+ }]
+ * @since 2.19.0
+ */
+ /**
+ * In the editor, sometimes it is useful to have guided questions for the Methods section
+ * in addition to the generic numbered method steps. These custom methods are defined here
+ * as an array of literal objects that define each custom Methods question. Custom methods
+ * are serialized to the EML as regular method steps, but with an unchangeable title, defined here,
+ * in order to identify them.
+ * @type {CustomEMLMethod}
+ * @since 2.19.0
+ */
+ customEMLMethods: [],
+
+ /**
+ * Configuration options for a drop down list of taxa.
+ * @typedef {object} AppConfig#quickAddTaxaList
+ * @type {Object}
+ * @property {string} label - The label for the dropdown menu
+ * @property {string} placeholder - The placeholder text for the input field
+ * @property {EMLTaxonCoverage#taxonomicClassification[]} taxa - The list of taxa to show in the dropdown menu
+ * @example
+ * {
+ * label: "Primates",
+ * placeholder: "Select one or more primates",
+ * taxa: [
+ * {
+ * commonName: "Bonobo",
+ * taxonRankName: "Species",
+ * taxonRankValue: "Pan paniscus",
+ * taxonId: {
+ * provider: "ncbi",
+ * value: "9597"
+ * }
+ * },
+ * {
+ * commonName: "Chimpanzee",
+ * ...
+ * },
+ * ...
+ * }
+ * @since 2.24.0
+ */
+
+ /**
+ * A list of taxa to show in the Taxa Quick Add section of the EML editor.
+ * This can be used to expedite entry of taxa that are common in the
+ * repository's domain. The quickAddTaxa is a list of objects, each
+ * defining a separate dropdown interface. This way, common taxa can
+ * be grouped together.
+ * Alternative, provide a SID for a JSON data object that is stored in the
+ * repository. The JSON must be in the same format as required for this
+ * configuration option.
+ * @since 2.24.0
+ * @type {AppConfig#quickAddTaxaList[] | string}
+ * @example
+ * [
+ * {
+ * label: "Bats"
+ * placeholder: "Select one or more bats",
+ * taxa: [ ... ]
+ * },
+ * {
+ * label: "Birds"
+ * placeholder: "Select one or more birds",
+ * taxa: [ ... ]
+ * }
+ * ]
+ */
+ quickAddTaxa: [],
+
+ /**
+ * The base URL for the repository. This only needs to be changed if the repository
+ * is hosted at a different origin than the MetacatUI origin. This URL is used to contruct all
+ * of the DataONE REST API URLs. If you are testing MetacatUI against a development repository
+ * at an external location, this is where you would set that external repository URL.
+ * @type {string}
+ * @default window.location.origin || (window.location.protocol + "//" + window.location.host)
+ */
+ baseUrl:
+ window.location.origin ||
+ window.location.protocol + "//" + window.location.host,
+
+ /**
+ * The directory that metacat is installed in at the `baseUrl`. For example, if you
+ * have metacat installed in the tomcat webapps directory as `metacat`, then this should be set
+ * to "/metacat". Or if you renamed the metacat webapp to `catalog`, then it should be `/catalog`.
+ * @type {string}
+ * @default "/metacat"
+ */
+ context: MetacatUI.AppConfig.metacatContext || "/metacat",
+
+ /**
+ * The URL fragment for the DataONE Member Node (MN) API.
+ * @type {string}
+ * @default '/d1/mn/v2'
+ */
+ d1Service: "/d1/mn/v2",
+ /**
+ * The base URL of the DataONE Coordinating Node (CN). CHange this if you
+ * are testing a deployment in a development environment.
+ * @type {string}
+ * @default "https://cn.dataone.org"
+ * @example "https://cn-stage.test.dataone.org"
+ */
+ d1CNBaseUrl: "https://cn.dataone.org",
+ /**
+ * The URL fragment for the DataONE Coordinating Node (CN) API.
+ * @type {string}
+ * @default '/cn/v2'
+ */
+ d1CNService: "/cn/v2",
+ /**
+ * The URL for the DataONE Search MetacatUI. This only needs to be changed
+ * if you want to point to a development environment.
+ * @type {string}
+ * @default "https://search.dataone.org"
+ * @readonly
+ * @since 2.13.0
+ */
+ dataoneSearchUrl: "https://search.dataone.org",
+ /**
+ * The URL for the DataONE listNodes() API. This URL is contructed dynamically when the
+ * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNCore.listNodes)
+ * @type {string}
+ */
+ nodeServiceUrl: null,
+ /**
+ * The URL for the DataONE View API. This URL is contructed dynamically when the
+ * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#module-MNView)
+ * @type {string}
+ */
+ viewServiceUrl: null,
+ /**
+ * The URL for the DataONE getPackage() API. This URL is contructed dynamically when the
+ * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNPackage.getPackage)
+ *
+ * @type {string}
+ */
+ packageServiceUrl: null,
+ /**
+ * The URL for the Metacat Publish service. This URL is contructed dynamically when the
+ * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
+ * @type {string}
+ */
+ publishServiceUrl: null,
+ /**
+ * The URL for the DataONE isAuthorized() API. This URL is contructed dynamically when the
+ * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNAuthorization.isAuthorized)
+ * @type {string}
+ */
+ authServiceUrl: null,
+ /**
+ * The URL for the DataONE query API. This URL is contructed dynamically when the
+ * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNQuery.query)
+ * @type {string}
+ */
+ queryServiceUrl: null,
+ /**
+ * The URL for the DataONE reserveIdentifier() API. This URL is contructed dynamically when the
+ * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNCore.reserveIdentifier)
+ * @type {string}
+ */
+ reserveServiceUrl: null,
+ /**
+ * The URL for the DataONE system metadata API. This URL is contructed dynamically when the
+ * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNRead.getSystemMetadata
+ * and https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNStorage.updateSystemMetadata)
+ * @type {string}
+ */
+ metaServiceUrl: null,
+ /**
+ * The URL for the DataONE system metadata API. This URL is contructed dynamically when the
+ * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNRead.getSystemMetadata
+ * and https://releases.dataone.org/online/api-documentation-v2.0/apis/MN_APIs.html#MNStorage.updateSystemMetadata)
+ * @type {string}
+ */
+ objectServiceUrl: null,
+ /**
+ * The URL for the DataONE Formats API. This URL is contructed dynamically when the
+ * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNCore.listFormats)
+ * @type {string}
+ */
+ formatsServiceUrl: null,
+ /**
+ * The URL fragment for the DataONE Formats API. This is combined with the AppConfig#formatsServiceUrl
+ * @type {string}
+ * @default "/formats"
+ */
+ formatsUrl: "/formats",
+
+ /**
+ * If true, parts of the UI (most notably, "funding" field in the dataset editor)
+ * may look up NSF Award information
+ * @type {boolean}
+ * @default false
+ */
+ useNSFAwardAPI: false,
+ /**
+ * The URL for the NSF Award API, which can be used by the {@link LookupModel}
+ * to look up award information for the dataset editor or other views. The
+ * URL must point to a proxy that can make requests to the NSF Award API,
+ * since it does not support CORS.
+ * @type {string}
+ * @default "/research.gov/awardapi-service/v1/awards.json"
+ */
+ grantsUrl: "/research.gov/awardapi-service/v1/awards.json",
+
+ /**
+ * The base URL for the ORCID REST services
+ * @type {string}
+ * @default "https:/orcid.org"
+ */
+ orcidBaseUrl: "https:/orcid.org",
+
+ /**
+ * The URL for the ORCID search API, which can be used to search for information
+ * about people using their ORCID, email, name, etc.
+ * This URL is constructed dynamically once the {@link AppModel} is initialized.
+ * @type {string}
+ */
+ orcidSearchUrl: null,
+
+ /**
+ * The URL for the Metacat API. The Metacat API has been deprecated and is kept here
+ * for compatability with Metacat repositories that are using the old x509 certificate
+ * authentication mechanism. This is deprecated since authentication is now done via
+ * the DataONE Portal service using auth tokens. (Using the {@link AppConfig#tokenUrl})
+ * This URL is contructed dynamically when the AppModel is initialized.
+ * Only override this if you are an advanced user and have a reason to!
+ * @type {string}
+ */
+ metacatServiceUrl: null,
+
+ /**
+ * If false, the /monitor/status (the service that returns the status of various DataONE services) will not be used.
+ * @type {boolean}
+ * @default true
+ * @since 2.9.0
+ */
+ enableMonitorStatus: true,
+
+ /**
+ * The URL for the service that returns the status of various DataONE services.
+ * The only supported status so far is the search index queue -- the number of
+ * objects that are waiting to be indexed in the Solr search index.
+ * This URL is contructed dynamically when the
+ * AppModel is initialized. Only override this if you are an advanced user and have a reason to!
+ * @type {string}
+ * @since 2.9.0
+ */
+ monitorStatusUrl: "",
+
+ /**
+ * If true, users will see a page with sign-in troubleshooting tips
+ * @type {boolean}
+ * @default true
+ * @since 2.13.3
+ */
+ showSignInHelp: true,
+ /**
+ * If true, users can sign in using CILogon as the identity provider.
+ * ORCID is the only recommended identity provider. CILogon may be deprecated
+ * in the future.
+ * @type {boolean}
+ * @default false
+ */
+ enableCILogonSignIn: false,
+ /**
+ * The URL for the DataONE Sign In API using CILogon as the identity provider
+ * This URL is constructed dynamically once the {@link AppModel} is initialized.
+ * @type {string}
+ */
+ signInUrl: null,
+ /**
+ * The URL for the DataONE Sign Out API
+ * This URL is constructed dynamically once the {@link AppModel} is initialized.
+ * @type {string}
+ */
+ signOutUrl: null,
+ /**
+ * The URL for the DataONE Sign In API using ORCID as the identity provider
+ * This URL is constructed dynamically once the {@link AppModel} is initialized.
+ * @type {string}
+ */
+ signInUrlOrcid: null,
+
+ /**
+ * Enable DataONE LDAP authentication. If true, users can sign in from an LDAP account that is in the DataONE CN LDAP directory.
+ * This is not recommended, as DataONE is moving towards supporting only ORCID logins for users.
+ * This LDAP authentication is separate from the File-based authentication for the Metacat Admin interface.
+ * @type {boolean}
+ * @default false
+ * @since 2.11.0
+ */
+ enableLdapSignIn: false,
+ /**
+ * The URL for the DataONE Sign In API using LDAP as the identity provider
+ * This URL is constructed dynamically once the {@link AppModel} is initialized.
+ * @type {string}
+ */
+ signInUrlLdap: null,
+
+ /**
+ * The URL for the DataONE Token API using ORCID as the identity provider
+ * This URL is constructed dynamically once the {@link AppModel} is initialized.
+ * @type {string}
+ */
+ tokenUrl: null,
+ /**
+ * The URL for the DataONE echoCredentials() API
+ * This URL is constructed dynamically once the {@link AppModel} is initialized.
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNDiagnostic.echoCredentials)
+ * @type {string}
+ */
+ checkTokenUrl: null,
+ /**
+ * The URL for the DataONE Identity API
+ * This URL is constructed dynamically once the {@link AppModel} is initialized.
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#module-CNIdentity)
+ * @type {string}
+ */
+ accountsUrl: null,
+ /**
+ * The URL for the DataONE Pending Maps API
+ * This URL is constructed dynamically once the {@link AppModel} is initialized.
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNIdentity.getPendingMapIdentity)
+ * @type {string}
+ */
+ pendingMapsUrl: null,
+ /**
+ * The URL for the DataONE mapIdentity() API
+ * This URL is constructed dynamically once the {@link AppModel} is initialized.
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNIdentity.mapIdentity)
+ * @type {string}
+ */
+ accountsMapsUrl: null,
+ /**
+ * The URL for the DataONE Groups API
+ * This URL is constructed dynamically once the {@link AppModel} is initialized.
+ * (see https://releases.dataone.org/online/api-documentation-v2.0/apis/CN_APIs.html#CNIdentity.createGroup)
+ * @type {string}
+ */
+ groupsUrl: null,
+ /**
+ * The URL for the DataONE metadata assessment service
+ * @type {string}
+ * @default "https://api.dataone.org/quality"
+ */
+ mdqBaseUrl: "https://api.dataone.org/quality",
+ /**
+ * Metadata Assessment Suite IDs for the dataset assessment reports.
+ * @type {string[]}
+ * @default ["FAIR-suite-0.4.0"]
+ */
+ mdqSuiteIds: ["FAIR-suite-0.4.0"],
+ /**
+ * Metadata Assessment Suite labels for the dataset assessment reports
+ * @type {string[]}
+ * @default ["FAIR Suite v0.4.0"]
+ */
+ mdqSuiteLabels: ["FAIR Suite v0.4.0"],
+ /**
+ * Metadata Assessment Suite IDs for the aggregated assessment charts
+ * @type {string[]}
+ * @default ["FAIR-suite-0.4.0"]
+ */
+ mdqAggregatedSuiteIds: ["FAIR-suite-0.4.0"],
+ /**
+ * Metadata Assessment Suite labels for the aggregated assessment charts
+ * @type {string[]}
+ * @default ["FAIR Suite v0.4.0"]
+ */
+ mdqAggregatedSuiteLabels: ["FAIR Suite v0.4.0"],
+ /**
+ * The metadata formats for which to display metadata assessment reports
+ * @type {string[]}
+ * @default ["eml*", "https://eml*", "*isotc211*"]
+ */
+ mdqFormatIds: ["eml*", "https://eml*", "*isotc211*"],
+
+ /**
+ * Metrics endpoint url
+ * @type {string}
+ */
+ metricsUrl: "https://logproc-stage-ucsb-1.test.dataone.org/metrics",
+
+ /**
+ * Forwards collection Query to Metrics Service if enabled
+ * @type {boolean}
+ * @default true
+ */
+ metricsForwardCollectionQuery: true,
+
+ /**
+ * DataONE Citation reporting endpoint url
+ * @type {string}
+ */
+ dataoneCitationsUrl:
+ "https://logproc-stage-ucsb-1.test.dataone.org/citations",
+
+ /**
+ * Hide or show the report Citation button in the dataset landing page.
+ * @type {boolean}
+ * @default true
+ */
+ hideReportCitationButton: false,
+
+ /**
+ * Hide or show the aggregated citations chart in the StatsView.
+ * These charts are only available for DataONE Plus members or Hosted Repositories.
+ * (see https://dataone.org)
+ * @type {boolean}
+ * @default true
+ * @since 2.9.0
+ */
+ hideSummaryCitationsChart: true,
+ /**
+ * Hide or show the aggregated downloads chart in the StatsView
+ * These charts are only available for DataONE Plus members or Hosted Repositories.
+ * (see https://dataone.org)
+ * @type {boolean}
+ * @default true
+ * @since 2.9.0
+ */
+ hideSummaryDownloadsChart: true,
+ /**
+ * Hide or show the aggregated metadata assessment chart in the StatsView
+ * These charts are only available for DataONE Plus members or Hosted Repositories.
+ * (see https://dataone.org)
+ * @type {boolean}
+ * @default true
+ * @since 2.9.0
+ */
+ hideSummaryMetadataAssessment: true,
+ /**
+ * Hide or show the aggregated views chart in the StatsView
+ * These charts are only available for DataONE Plus members or Hosted Repositories.
+ * (see https://dataone.org)
+ * @type {boolean}
+ * @default true
+ * @since 2.9.0
+ */
+ hideSummaryViewsChart: true,
+
+ /**
+ * Metrics flag for the Dataset Landing Page
+ * Enable this flag to enable metrics display
+ * @type {boolean}
+ * @default true
+ */
+ displayDatasetMetrics: true,
+
+ /**
+ * If true, displays the dataset metrics tooltips on the metrics buttons.
+ * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
+ * @type {boolean}
+ * @default true
+ */
+ displayDatasetMetricsTooltip: true,
+ /**
+ * If true, displays the datasets metric modal windows on the dataset landing page
+ * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
+ * @type {boolean}
+ * @default true
+ */
+ displayMetricModals: true,
+ /**
+ * If true, displays the dataset citation metrics on the dataset landing page
+ * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
+ * @type {boolean}
+ * @default true
+ */
+ displayDatasetCitationMetric: true,
+ /**
+ * If true, displays the dataset download metrics on the dataset landing page
+ * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
+ * @type {boolean}
+ * @default true
+ */
+ displayDatasetDownloadMetric: true,
+ /**
+ * If true, displays the dataset view metrics on the dataset landing page
+ * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
+ * @type {boolean}
+ * @default true
+ */
+ displayDatasetViewMetric: true,
+ /**
+ * If true, displays the citation registration tool on the dataset landing page
+ * @type {boolean}
+ * @default true
+ * @since 2.15.0
+ */
+ displayRegisterCitationTool: true,
+ /**
+ * If true, displays the "Edit" button on the dataset landing page
+ * @type {boolean}
+ * @default true
+ */
+ displayDatasetEditButton: true,
+ /**
+ * If true, displays the metadata assessment metrics on the dataset landing page
+ * @type {boolean}
+ * @default false
+ */
+ displayDatasetQualityMetric: false,
+ /**
+ * If true, displays the WholeTale "Analyze" button on the dataset landing page
+ * @type {boolean}
+ * @default false
+ */
+ displayDatasetAnalyzeButton: false,
+ /**
+ * If true, displays various buttons on the dataset landing page for dataset owners
+ * @type {boolean}
+ * @default false
+ */
+ displayDatasetControls: true,
+ /** Hide metrics display for SolrResult models that match the given properties.
+ * Properties can be functions, which are given the SolrResult model value as a parameter.
+ * Turn off all dataset metrics displays using the {@link AppConfig#displayDatasetMetrics}
+ * @type {object}
+ * @example
+ * {
+ * formatId: "eml://ecoinformatics.org/eml-2.1.1",
+ * isPublic: true,
+ * dateUploaded: function(date){
+ * return new Date(date) < new Date('1995-12-17T03:24:00');
+ * }
+ * }
+ * // This example would hide metrics for any objects that are:
+ * // EML 2.1.1 OR public OR were uploaded before 12/17/1995.
+ */
+ hideMetricsWhen: null,
+
+ /**
+ * The bounding box path color to use in the Google Static Map images on the dataset landing pages.
+ * Specify the color either as a 24-bit (example: color=0xFFFFCC) or 32-bit hexadecimal value
+ * (example: color=0xFFFFCCFF), or from the set: black, brown, green, purple, yellow, blue, gray, orange, red, white.
+ * For more information, see the Google Statis Maps API docs: https://developers.google.com/maps/documentation/maps-static/start#PathStyles
+ * @type {string}
+ * @default "0xDA4D3Aff" (red)
+ * @since 2.13.0
+ */
+ datasetMapPathColor: "0xDA4D3Aff",
+
+ /**
+ * The bounding box fill color to use in the Google Static Map images on the dataset landing pages.
+ * If you don't want to fill in the bounding boxes with a color, set this to null or undefined.
+ * Specify the color either as a 24-bit (example: color=0xFFFFCC) or 32-bit hexadecimal value
+ * (example: color=0xFFFFCCFF), or from the set: black, brown, green, purple, yellow, blue, gray, orange, red, white.
+ * For more information, see the Google Statis Maps API docs: https://developers.google.com/maps/documentation/maps-static/start#PathStyles
+ * @type {string}
+ * @default "0xFFFF0033" (light yellow)
+ * @since 2.13.0
+ */
+ datasetMapFillColor: "0xFFFF0033",
+
+ /**
+ * The hue/color of the tiles drawn on the map when searching for data.
+ * This should be a three-digit hue degree between 0 and 360. (Try https://hslpicker.com)
+ * This is set on the {@link Map} model when it is initialized.
+ * @type {string}
+ * @default "192" (blue)
+ * @since 2.13.3
+ */
+ searchMapTileHue: "192",
+
+ /**
+ * If true, the dataset landing pages will generate Schema.org-compliant JSONLD
+ * and insert it into the page.
+ * @type {boolean}
+ * @default true
+ */
+ isJSONLDEnabled: true,
+
+ /**
+ * If true, users can see a "Publish" button in the MetadataView, which makes the metadata
+ * document public and gives it a DOI identifier.
+ * If false, the button will be hidden completely.
+ * @type {boolean}
+ * @default true
+ */
+ enablePublishDOI: true,
+
+ /**
+ * A list of users or groups who exclusively will be able to see and use the "Publish" button,
+ * which makes the metadata document public and gives it a DOI identifier.
+ * Anyone not in this list will not be able to see the Publish button.
+ * `enablePublishDOI` must be set to `true` for this to take effect.
+ * @type {string[]}
+ */
+ enablePublishDOIForSubjects: [],
+
+ /**
+ * If true, users can change the AccessPolicy for any of their objects.
+ * This is equivalent to setting {@link AppConfig#allowAccessPolicyChangesPortals} and
+ * {@link AppConfig#allowAccessPolicyChangesDatasets} to `true`.
+ * @type {boolean}
+ * @default true
+ * @since 2.9.0
+ */
+ allowAccessPolicyChanges: true,
+
+ /**
+ * If true, users can change the AccessPolicy for their portals only.
+ * @type {boolean}
+ * @default true
+ * @since 2.15.0
+ */
+ allowAccessPolicyChangesPortals: true,
+
+ /**
+ * Limit portal Access policy editing to only a defined list of people or groups.
+ * To let everyone edit access policies for their own objects, keep this as an empty array
+ * and make sure {@link AppConfig#allowAccessPolicyChangesPortals} is set to `true`
+ * @type {boolean}
+ * @default []
+ * @since 2.15.0
+ */
+ allowAccessPolicyChangesPortalsForSubjects: [],
+
+ /**
+ * If true, users can change the AccessPolicy for their datasets only.
+ * @type {boolean}
+ * @default true
+ * @since 2.15.0
+ */
+ allowAccessPolicyChangesDatasets: true,
+
+ /**
+ * Limit dataset Access policy editing to only a defined list of people or groups.
+ * To let everyone edit access policies for their own objects, keep this as an empty array
+ * and make sure {@link AppConfig#allowAccessPolicyChangesDatasets} is set to `true`
+ * @type {boolean}
+ * @default true
+ * @since 2.15.0
+ */
+ allowAccessPolicyChangesDatasetsForSubjects: [],
+
+ /**
+ * The default {@link AccessPolicy} set on new objects uploaded to the repository.
+ * Each literal object here gets set directly on an {@link AccessRule} model.
+ * See the {@link AccessRule} list of default attributes for options on what to set here.
+ * @see {@link AccessRule}
+ * @type {object[]}
+ * @since 2.9.0
+ * @default [{
+ subject: "public",
+ read: true
+ }]
+ * @example
+ * [{
+ * subject: "public",
+ * read: true
+ * }]
+ * // This example would assign public access to all new objects created in MetacatUI.
+ */
+ defaultAccessPolicy: [
{
- fields: ["sem_annotation"],
- label: "Annotation",
- placeholder: "Search for class...",
- icon: "tag",
- description: "Semantic annotations"
+ subject: "public",
+ read: true,
},
+ ],
+
+ /**
+ * When new data objects are added to a {@link DataPackage}, they can either inherit the {@link AccessPolicy} from the
+ * parent metadata object, or default to the {@link AppConfig#defaultAccessPolicy}. To inherit the {@link AccessPolicy}
+ * from the parent metadata object, set this config to `true`.
+ * @type {boolean}
+ * @default true
+ * @since 2.15.0
+ */
+ inheritAccessPolicy: true,
+
+ /**
+ * The user-facing name for editing the Access Policy. This is displayed as the header of the AccessPolicyView, for example
+ * @type {string}
+ * @since 2.9.0
+ * @default "Sharing options"
+ */
+ accessPolicyName: "Sharing options",
+
+ /**
+ * @type {object}
+ * @property {boolean} accessRuleOptions.read - If true, users will be able to give others read access to their DataONE objects
+ * @property {boolean} accessRuleOptions.write - If true, users will be able to give others write access to their DataONE objects
+ * @property {boolean} accessRuleOptions.changePermission - If true, users will be able to give others changePermission access to their DataONE objects
+ * @since 2.9.0
+ * @default {
+ read: true,
+ write: true,
+ changePermission: true
+ }
+ * @example
+ * {
+ * read: true,
+ * write: true,
+ * changePermission: false
+ * }
+ * // This example would enable users to edit the read and write access to files,
+ * // but not change ownership, in the Access Policy View.
+ */
+ accessRuleOptions: {
+ read: true,
+ write: true,
+ changePermission: true,
+ },
+
+ /**
+ * @type {object}
+ * @property {boolean} accessRuleOptionNames.read - The user-facing name of the "read" access in Access Rules
+ * @property {boolean} accessRuleOptionNames.write - The user-facing name of the "write" access in Access Rules
+ * @property {boolean} accessRuleOptionNames.changePermission - The user-facing name of the "changePermission" access in Access Rules
+ * @since 2.9.0
+ * @example
+ * {
+ * read: "Can view",
+ * write: "Can edit",
+ * changePermission: "Is owner"
+ * }
+ */
+ accessRuleOptionNames: {
+ read: "Can view",
+ write: "Can edit",
+ changePermission: "Is owner",
+ },
+
+ /**
+ * If false, the rightsHolder of a resource will not be displayed in the AccessPolicyView.
+ * @type {boolean}
+ * @default true
+ * @since 2.9.0
+ */
+ displayRightsHolderInAccessPolicy: true,
+
+ /**
+ * If false, users will not be able to change the rightsHolder of a resource in the AccessPolicyView
+ * @type {boolean}
+ * @default true
+ * @since 2.9.0
+ */
+ allowChangeRightsHolder: true,
+
+ /**
+ * A list of group subjects that will be hidden in the AccessPolicy view to
+ * everyone except those in the group. This is useful for preventing users from
+ * removing repository administrative groups from access policies.
+ * @type {string[]}
+ * @since 2.9.0
+ * @example ["CN=data-admin-group,DC=dataone,DC=org"]
+ */
+ hiddenSubjectsInAccessPolicy: [],
+
+ /**
+ * The format ID the portal editor serializes a new portal document as
+ * @type {string}
+ * @default "https://purl.dataone.org/portals-1.1.0"
+ * @readonly
+ * @since 2.17.0
+ */
+ portalEditorSerializationFormat:
+ "https://purl.dataone.org/portals-1.1.0",
+
+ /**
+ * If true, the public/private toggle will be displayed in the Sharing Options for portals.
+ * @type {boolean}
+ * @default true
+ * @since 2.9.0
+ */
+ showPortalPublicToggle: true,
+
+ /**
+ * The public/private toggle will be displayed in the Sharing Options for portals for only
+ * the given users or groups. To display the public/private toggle for everyone,
+ * set `showPortalPublicToggle` to true and keep this array empty.
+ * @type {string[]}
+ * @since 2.9.0
+ */
+ showPortalPublicToggleForSubjects: [],
+
+ /**
+ * If true, the public/private toggle will be displayed in the Sharing Options for datasets.
+ * @type {boolean}
+ * @default true
+ * @since 2.9.0
+ */
+ showDatasetPublicToggle: true,
+
+ /**
+ * The public/private toggle will be displayed in the Sharing Options for datasets for only
+ * the given users or groups. To display the public/private toggle for everyone,
+ * set `showDatasetPublicToggle` to true and keep this array empty.
+ * @type {string[]}
+ * @since 2.15.0
+ */
+ showDatasetPublicToggleForSubjects: [],
+
+ /**
+ * Set to false to hide the display of "My Portals", which shows the user's current portals
+ * @type {boolean}
+ * @default true
+ */
+ showMyPortals: true,
+ /**
+ * The user-facing term for portals in lower-case and in singular form.
+ * e.g. "portal"
+ * @type {string}
+ * @default "portal"
+ */
+ portalTermSingular: "portal",
+ /**
+ * The user-facing term for portals in lower-case and in plural form.
+ * e.g. "portals". This allows for portal terms with irregular plurals.
+ * @type {string}
+ * @default "portals"
+ */
+ portalTermPlural: "portals",
+ /**
+ * A URL of a webpage for people to learn more about portals. If no URL is provided,
+ * links to more info about portals will be omitted.
+ * @since 2.14.0
+ * @type {string}
+ * @example "https://dataone.org/plus"
+ * @default null
+ */
+ portalInfoURL: null,
+ /**
+ * The URL for a webpage where people can learn more about custom portal search
+ * filters. If no URL is provided, links to more info about portals will be omitted.
+ * @since 2.17.0
+ * @type {string}
+ * @example "https://dataone.org/custom-search"
+ * @default null
+ */
+ portalSearchFiltersInfoURL: null,
+ /**
+ * Set to false to prevent ANYONE from creating a new portal.
+ * @type {boolean}
+ * @default true
+ */
+ enableCreatePortals: true,
+ /**
+ * Limits only the following people or groups to create new portals. If this is left as an empty array,
+ * then any logged-in user can create a portal.
+ * @type {string[]}
+ */
+ limitPortalsToSubjects: [],
+
+ /**
+ * This message will display when a user tries to create a new Portal in the PortalEditor
+ * when they are not associated with a whitelisted subject in the `limitPortalsToSubjects` list
+ * @type {string}
+ */
+ portalEditNotAuthCreateMessage:
+ "You have not been authorized to create new portals. Please contact us with any questions.",
+
+ /**
+ * This message will display when a user tries to access the Portal Editor for a portal
+ * for which they do not have write permission.
+ * @type {string}
+ */
+ portalEditNotAuthEditMessage:
+ "The portal owner has not granted you permission to edit this portal. Please contact the owner to be given edit permission.",
+
+ /**
+ * This message will display when a user tries to create a new portal when they have exceeded their DataONE portal quota
+ * @type {string}
+ */
+ portalEditNoQuotaMessage:
+ "You have already reached the maximum number of portals for your membership level.",
+
+ /**
+ * This message will display when there is any non-specific error during the save process of the PortalEditor.
+ * @type {string}
+ */
+ portalEditSaveErrorMsg:
+ "Something went wrong while attempting to save your changes.",
+
+ /**
+ * The list of fields that should be required in the portal editor.
+ * Set individual properties to `true` to require them in the portal editor.
+ * @type {object}
+ * @property {boolean} label - Default: true
+ * @property {boolean} name - Default: true
+ * @property {boolean} description - Default: false
+ * @property {boolean} sectionTitle - Default: true
+ * @property {boolean} sectionIntroduction - Default: false
+ * @property {boolean} logo - Default: false
+ */
+ portalEditorRequiredFields: {
+ label: true,
+ name: true,
+ description: false,
+ sectionTitle: true,
+ sectionIntroduction: false,
+ logo: false,
+ //The following fields are not yet supported as required fields in the portal editor
+ //TODO: Add support for requiring the below fields
+ sectionImage: false,
+ acknowledgments: false,
+ acknowledgmentsLogos: false,
+ awards: false,
+ associatedParties: false,
+ },
+
+ /**
+ * A list of portals labels that no one should be able to create portals with
+ * @type {string[]}
+ * @readonly
+ * @since 2.11.3
+ */
+ portalLabelBlockList: [
+ "Dataone",
+ "urn:node:CN",
+ "CN",
+ "cn",
+ "urn:node:CNUNM1",
+ "CNUNM1",
+ "cn-unm-1",
+ "urn:node:CNUCSB1",
+ "CNUCSB1",
+ "cn-ucsb-1",
+ "urn:node:CNORC1",
+ "CNORC1",
+ "cn-orc-1",
+ "urn:node:KNB",
+ "KNB",
+ "KNB Data Repository",
+ "urn:node:ESA",
+ "ESA",
+ "ESA Data Registry",
+ "urn:node:SANPARKS",
+ "SANPARKS",
+ "SANParks Data Repository",
+ "urn:node:ORNLDAAC",
+ "ORNLDAAC",
+ "ORNL DAAC",
+ "urn:node:LTER",
+ "LTER",
+ "U.S. LTER Network",
+ "urn:node:CDL",
+ "CDL",
+ "UC3 Merritt",
+ "urn:node:PISCO",
+ "PISCO",
+ "PISCO MN",
+ "urn:node:ONEShare",
+ "ONEShare",
+ "ONEShare DataONE Member Node",
+ "urn:node:mnORC1",
+ "mnORC1",
+ "DataONE ORC Dedicated Replica Server",
+ "urn:node:mnUNM1",
+ "mnUNM1",
+ "DataONE UNM Dedicated Replica Server",
+ "urn:node:mnUCSB1",
+ "mnUCSB1",
+ "DataONE UCSB Dedicated Replica Server",
+ "urn:node:TFRI",
+ "TFRI",
+ "TFRI Data Catalog",
+ "urn:node:USANPN",
+ "USANPN",
+ "USA National Phenology Network",
+ "urn:node:SEAD",
+ "SEAD",
+ "SEAD Virtual Archive",
+ "urn:node:GOA",
+ "GOA",
+ "Gulf of Alaska Data Portal",
+ "urn:node:KUBI",
+ "KUBI",
+ "University of Kansas - Biodiversity Institute",
+ "urn:node:LTER_EUROPE",
+ "LTER_EUROPE",
+ "LTER Europe Member Node",
+ "urn:node:DRYAD",
+ "DRYAD",
+ "Dryad Digital Repository",
+ "urn:node:CLOEBIRD",
+ "CLOEBIRD",
+ "Cornell Lab of Ornithology - eBird",
+ "urn:node:EDACGSTORE",
+ "EDACGSTORE",
+ "EDAC Gstore Repository",
+ "urn:node:IOE",
+ "IOE",
+ "Montana IoE Data Repository",
+ "urn:node:US_MPC",
+ "US_MPC",
+ "Minnesota Population Center",
+ "urn:node:EDORA",
+ "EDORA",
+ "Environmental Data for the Oak Ridge Area (EDORA)",
+ "urn:node:RGD",
+ "RGD",
+ "Regional and Global biogeochemical dynamics Data (RGD)",
+ "urn:node:GLEON",
+ "GLEON",
+ "GLEON Data Repository",
+ "urn:node:IARC",
+ "IARC",
+ "IARC Data Archive",
+ "urn:node:NMEPSCOR",
+ "NMEPSCOR",
+ "NM EPSCoR Tier 4 Node",
+ "urn:node:TERN",
+ "TERN",
+ "TERN Australia",
+ "urn:node:NKN",
+ "NKN",
+ "Northwest Knowledge Network",
+ "urn:node:USGS_SDC",
+ "USGS_SDC",
+ "USGS Science Data Catalog",
+ "urn:node:NRDC",
+ "NRDC",
+ "NRDC DataONE member node",
+ "urn:node:NCEI",
+ "NCEI",
+ "NOAA NCEI Environmental Data Archive",
+ "urn:node:PPBIO",
+ "PPBIO",
+ "PPBio",
+ "urn:node:NEON",
+ "NEON",
+ "NEON Member Node",
+ "urn:node:TDAR",
+ "TDAR",
+ "The Digital Archaeological Record",
+ "urn:node:ARCTIC",
+ "ARCTIC",
+ "Arctic Data Center",
+ "urn:node:BCODMO",
+ "BCODMO",
+ "Biological and Chemical Oceanography Data Management Office (BCO-DMO) ",
+ "urn:node:GRIIDC",
+ "GRIIDC",
+ "Gulf of Mexico Research Initiative Information and Data Cooperative (GRIIDC)",
+ "urn:node:R2R",
+ "R2R",
+ "Rolling Deck to Repository (R2R)",
+ "urn:node:EDI",
+ "EDI",
+ "Environmental Data Initiative",
+ "urn:node:UIC",
+ "UIC",
+ "A Member Node for University of Illinois at Chicago.",
+ "urn:node:RW",
+ "RW",
+ "Research Workspace",
+ "urn:node:FEMC",
+ "FEMC",
+ "Forest Ecosystem Monitoring Cooperative Member Node",
+ "urn:node:OTS_NDC",
+ "OTS_NDC",
+ "Organization for Tropical Studies - Neotropical Data Center",
+ "urn:node:PANGAEA",
+ "PANGAEA",
+ "PANGAEA",
+ "urn:node:ESS_DIVE",
+ "ESS_DIVE",
+ "ESS-DIVE: Deep Insight for Earth Science Data",
+ "urn:node:CAS_CERN",
+ "CAS_CERN",
+ "Chinese Ecosystem Research Network (CERN)",
+ "urn:node:FIGSHARE_CARY",
+ "FIGSHARE_CARY",
+ "Cary Institute of Ecosystem Studies (powered by Figshare)",
+ "urn:node:IEDA_EARTHCHEM",
+ "IEDA_EARTHCHEM",
+ "IEDA EARTHCHEM",
+ "urn:node:IEDA_USAP",
+ "IEDA_USAP",
+ "IEDA USAP",
+ "urn:node:IEDA_MGDL",
+ "IEDA_MGDL",
+ "IEDA MGDL",
+ "urn:node:METAGRIL",
+ "METAGRIL",
+ "metaGRIL",
+ "urn:node:ARM",
+ "ARM",
+ "ARM - Atmospheric Radiation Measurement Research Facility",
+ "urn:node:CA_OPC",
+ "CA_OPC",
+ "OPC",
+ "urn:node:TNC_DANGERMOND",
+ "dangermond",
+ "TNC_DANGERMOND",
+ "dangermondpreserve",
+ ],
+
+ /**
+ * Limit users to a certain number of portals. This limit will be ignored if {@link AppConfig#enableBookkeeperServices}
+ * is set to true, because the limit will be enforced by Bookkeeper Quotas instead.
+ * @type {number}
+ * @default 100
+ * @since 2.14.0
+ */
+ portalLimit: 100,
+
+ /**
+ * The default values to use in portals. Default sections are applied when a portal is new.
+ * Default images are used in new freeform pages in the portal builder.
+ * The default colors are used when colors haven't been saved to the portal document.
+ * Colors can be hex codes, rgb codes, or any other form supported by browsers in CSS
+ * @type {object}
+ * @property {object[]} sections The default sections for a new portal. Each object within the section array can have a title property and a label property
+ * @property {string} label The name of the section that will appear in the tab
+ * @property {string} title A longer title for the section that will appear in the section header
+ * @property {string} newPortalActiveSectionLabel When a user start the portal builder for a brand new portal, the label for the section that the builder should start on. Can be set to "Data", "Metrics", "Settings", or one of the labels from the default sections described above.
+ * @property {string[]} sectionImageIdentifiers A list of image pids to use as default images for new markdown sections
+ * @property {string} primaryColor The color that is used most frequently in the portal view
+ * @property {string} secondaryColor The color that is used second-most frequently in the portal view
+ * @property {string} accentColor The color that is rarely used in portal views as an accent color
+ * @property {string} primaryColorTransparent An rgba() version of the primaryColor that is semi-transparent
+ * @property {string} secondaryColorTransparent An rgba() version of the secondaryColor that is semi-transparent
+ * @property {string} accentColorTransparent An rgba() version of the accentColor that is semi-transparent
+ * @example {
+ * sections: [
+ * { label: "About",
+ * title: "About our project"
+ * },
+ * { label: "Publications",
+ * title: "Selected publications by our lab group"
+ * }
+ * ],
+ * newPortalActiveSectionLabel: "About",
+ * sectionImageIdentifiers: ["urn:uuid:d2f31a83-debf-4d78-bef7-6abe20962581", "urn:uuid:6ad37acd-d0ac-4142-9f42-e5f05ff55564", "urn:uuid:0b6be09f-2e6f-4e7b-a83c-2823495f9608", "urn:uuid:5b4e0347-07ed-4580-b039-6c4df57ed801", "urn:uuid:0cf62da9-a099-440e-9c1e-595a55c0d60d"],
+ * primaryColor: "#16acc0",
+ * primaryColorTransparent: "rgba(22, 172, 192, .7)",
+ * secondaryColor: "#EED268",
+ * secondaryColorTransparent: "rgba(238, 210, 104, .7)",
+ * accentColor: "#0f5058",
+ * accentColorTransparent: "rgba(15, 80, 88, .7)"
+ * }
+ * @since 2.14.0
+ */
+ portalDefaults: {},
+
+ /**
+ * Add an API service URL that retrieves projects data. This is an optional
+ * configuration in case the memberNode have a third-party service that provides
+ * their projects information.
+ *
+ * If the configuration is not set, set the default projects list in the views using it.
+ *
+ * @type {string}
+ * @private
+ * @since 2.20.0 #TODO Update version here.
+ */
+ projectsApiUrl: undefined,
+ /**
+ * Enable or disable the use of Fluid Earth Viewer visualizations in portals.
+ * This config option is marked as `private` since this is an experimental feature.
+ * @type {boolean}
+ * @private
+ * @since 2.13.4
+ */
+ enableFeverVisualizations: false,
+ /**
+ * The relative path to the location where the Fluid Earth Viewer (FEVer) is deployed. This should be
+ * deployed at the same origin as MetacatUI, since your web server configuration and many browsers
+ * may block iframes from different origins.
+ * This config option is marked as `private` since this is an experimental feature.
+ * @type {string}
+ * @private
+ * @since 2.13.4
+ */
+ feverPath: "/fever",
+ /**
+ * The full URL to the location where the Fluid Earth Viewer (FEVer) is deployed.
+ * This URL is constructed during {@link AppModel#initialize} using the {@link AppConfig#baseUrl}
+ * and {@link AppConfig#feverPath}.
+ * This config option is marked as `private` since this is an experimental feature.
+ * @type {string}
+ * @readonly
+ * @private
+ * @since 2.13.4
+ */
+ feverUrl: "",
+
+ /** If true, then archived content is available in the search index.
+ * Set to false if this MetacatUI is using a Metacat version before 2.10.0
+ * @type {boolean}
+ * @default true
+ */
+ archivedContentIsIndexed: true,
+
+ /**
+ * The metadata fields to hide when a user is creating a collection definition using
+ * the Query Builder View displayed in the portal builder on the data page, or
+ * anywhere else the EditCollectionView is displayed. Strings listed here should
+ * exactly match the 'name' for each field provided by the DataONE search index API
+ * (i.e. should match the Solr field).
+ * @example ["sem_annotated_by", "mediaType"]
+ * @type {string[]}
+ */
+ collectionQueryExcludeFields: [
+ "sem_annotated_by",
+ "sem_annotates",
+ "sem_comment",
+ "pubDate",
+ "namedLocation",
+ "contactOrganization",
+ "investigator",
+ "originator",
+ "originatorText",
+ "serviceInput",
+ "authorGivenName",
+ "authorSurName",
+ "topic",
+ "webUrl",
+ "_root_",
+ "collectionQuery",
+ "geohash_1",
+ "geohash_2",
+ "geohash_3",
+ "geohash_4",
+ "geohash_5",
+ "geohash_6",
+ "geohash_7",
+ "geohash_8",
+ "geohash_9",
+ "label",
+ "LTERSite",
+ "_version_",
+ "checksumAlgorithm",
+ "keywords",
+ "parameterText",
+ "project",
+ "topicText",
+ "dataUrl",
+ "fileID",
+ "isDocumentedBy",
+ "logo",
+ "obsoletes",
+ "origin",
+ "funding",
+ "formatType",
+ "obsoletedBy",
+ "presentationCat",
+ "mediaType",
+ "mediaTypeProperty",
+ "relatedOrganizations",
+ "noBoundingBox",
+ "decade",
+ "hasPart",
+ "sensorText",
+ "sourceText",
+ "termText",
+ "titlestr",
+ "site",
+ "id",
+ "updateDate",
+ "edition",
+ "gcmdKeyword",
+ "isSpatial",
+ "keyConcept",
+ "ogcUrl",
+ "parameter",
+ "sensor",
+ "source",
+ "term",
+ "investigatorText",
+ "sku",
+ "_text_",
+ // Fields that have been made into a special combination field
+ "beginDate",
+ "endDate",
+ "awardNumber",
+ // Provenance fields (keep only "prov_hasSources" and "prov_hasDerivations"),
+ // since they are the only ones indexed on metadata objects
+ "prov_wasGeneratedBy",
+ "prov_generated",
+ "prov_generatedByExecution",
+ "prov_generatedByProgram",
+ "prov_generatedByUser",
+ "prov_instanceOfClass",
+ "prov_used",
+ "prov_usedByExecution",
+ "prov_usedByProgram",
+ "prov_usedByUser",
+ "prov_wasDerivedFrom",
+ "prov_wasExecutedByExecution",
+ "prov_wasExecutedByUser",
+ "prov_wasInformedBy",
+ ],
+
+ /**
+ * A special field is one that does not exist in the query service index (i.e.
+ * Solr). It can be a combination of fields that are presented to the user as a
+ * single field, but which are added to the model as multiple fields. It can also be
+ * a duplicate of a field that does exist, but presented with a different label (and
+ * even with different {@link operatorOptions operator options} or
+ * {@link valueSelectUImap value input} if needed).
+ *
+ * @typedef {Object} SpecialField
+ * @property {string} name - A unique ID to represent this field. It must not match
+ * the name of any other query fields.
+ * @property {string[]} fields - The list of real query fields that this abstracted
+ * field should represent. The query fields listed must exactly match the names of
+ * the query fields that are retrieved from the query service.
+ * @property {string} label - A user-facing label to display.
+ * @property {string} description - A description for this field.
+ * @property {string} category - The name of the category under which to place this
+ * field. It must match one of the category names for an existing query field set in
+ * {@link QueryField#categoriesMap}.
+ * @property {string[]} [values] - An optional list of filter values. If set, this
+ * is used to determine whether a pre-existing Query Rule should be displayed as one
+ * of these special fields, or as a field from the query API. Setting values means
+ * that the values set on the Query Rule model must exactly match the values set.
+ *
+ * @since 2.15.0
+ */
+
+ /**
+ * A list of additional fields which are not retrieved from the query API (i.e. are
+ * not Solr fields), but which should be added to the list of options the user can
+ * select from when building a query in the EditCollectionView. This can be used to
+ * add abstracted fields which are a combination of multiple query fields, or to add
+ * a duplicate field that has a different label.
+ *
+ * @type {SpecialField[]}
+ *
+ * @since 2.15.0
+ */
+ collectionQuerySpecialFields: [
{
- filterType: "ToggleFilter",
+ name: "documents-special-field",
fields: ["documents"],
label: "Contains Data Files",
- placeholder: "Only results with data",
- trueLabel: "Required",
- falseLabel: null,
- trueValue: "*",
- matchSubstring: false,
- icon: "table",
- description: "Checking this option will only return packages that include data files. Leaving this unchecked may return packages that only include metadata."
- },
- {
- fields: ["originText"],
- label: "Creator",
- placeholder: "Name",
- icon: "user",
- description: "The name of the creator or originator of a dataset"
- },
- {
- filterType: "DateFilter",
- fields: ["datePublished", "dateUploaded"],
- label: "Publish Year",
- rangeMin: 1800,
- icon: "calendar",
- description: "Only show results that were published within the year range"
- },
- {
- filterType: "DateFilter",
- fields: ["beginDate"],
- label: "Year of data coverage",
- rangeMin: 1800,
- icon: "calendar",
- description: "Only show results with data collected within the year range"
+ description:
+ "Limit results to packages that include data files. Without" +
+ " this rule, results may include packages with metadata but no data.",
+ category: "General",
+ values: ["*"],
},
{
- fields: ["identifier", "documents", "resourceMap", "seriesId"],
- label: "Identifier",
- placeholder: "DOI or ID",
- icon: "bullseye",
- description: "Find datasets if you have all or part of its DOI or ID",
- operator: "OR",
- fieldsOperator: "OR"
+ name: "year-data-collection",
+ fields: ["beginDate", "endDate"],
+ label: "Year of Data Collection",
+ description:
+ "The temporal range of content described by the metadata",
+ category: "Dates",
},
{
- fields: ["kingdom", "phylum", "class", "order", "family", "genus", "species"],
- label: "Taxon",
- placeholder: "Class, family, etc.",
- icon: "sitemap",
- description: "Find data about any taxonomic rank",
- matchSubstring: true,
- fieldsOperator: "OR"
+ name: "funding-text-award-number",
+ fields: ["fundingText", "awardNumber"],
+ label: "Award Number",
+ description:
+ "The award number for funding associated with a dataset or the " +
+ "description of funding source",
+ category: "Awards & funding",
},
+ ],
+
+ /**
+ * The names of the query fields that use an object identifier as a value. Filter
+ * models that use one of these fields are handled specially when building query
+ * strings - they are OR'ed at the end of queries. They are also given an "OR"
+ * operator and fieldsOperator attribute when parsed.
+ * @type {string[]}
+ *
+ * @since 2.17.0
+ */
+ queryIdentifierFields: ["id", "identifier", "seriesId", "isPartOf"],
+
+ /**
+ * The name of the query fields that specify latitude. Filter models that these
+ * fields are handled specially, since they must be a float value and have a
+ * pre-determined minRange and maxRange (-90 to 90).
+ */
+ queryLatitudeFields: ["northBoundCoord", "southBoundCoord"],
+
+ /**
+ * The name of the query fields that specify longitude. Filter models that these
+ * fields are handled specially, since they must be a float value and have a
+ * pre-determined minRange and maxRange (-180 to 180).
+ */
+ queryLongitudeFields: ["eastBoundCoord", "westBoundCoord"],
+
+ /**
+ * The names of the query fields that may require special treatment in the
+ * UI. For example, upgrade the view for a Filter from a FilterView to
+ * a SemanticFilterView or to block certain UIBuilders in FilterEditorView
+ * that don't make sense for a semantic field.
+ *
+ * @type {string[]}
+ * @since 2.22.0
+ */
+ querySemanticFields: ["sem_annotation"],
+
+ /**
+ * The isPartOf filter is added to all new portals built in the Portal
+ * Builder automatically. It is required for dataset owners to include
+ * their dataset in a specific portal collection. By default, this filter
+ * is hidden. Set to false to make this filter visible.
+ * @type {boolean}
+ */
+ hideIsPartOfFilter: true,
+
+ /**
+ * The default {@link FilterGroup}s to use in the data catalog search ({@link CatalogSearchView}).
+ * This is an array of literal objects that will be directly set on the {@link FilterGroup} models. Refer to the {@link FilterGroup#defaults} for
+ * options.
+ * @type {FilterGroup#defaults[]}
+ */
+ defaultFilterGroups: [
{
- fields: ["siteText"],
- label: "Location",
- placeholder: "Geographic region",
- icon: "globe",
- description: "The geographic region or study site, as described by the submitter"
+ label: "",
+ filters: [
+ {
+ fields: ["attribute"],
+ label: "Data attribute",
+ placeholder: "density, length, etc.",
+ icon: "table",
+ description:
+ "Measurement type, e.g. density, temperature, species",
+ },
+ {
+ fields: ["sem_annotation"],
+ label: "Annotation",
+ placeholder: "Search for class...",
+ icon: "tag",
+ description: "Semantic annotations",
+ },
+ {
+ filterType: "ToggleFilter",
+ fields: ["documents"],
+ label: "Contains Data Files",
+ placeholder: "Only results with data",
+ trueLabel: "Required",
+ falseLabel: null,
+ trueValue: "*",
+ matchSubstring: false,
+ icon: "table",
+ description:
+ "Checking this option will only return packages that include data files. Leaving this unchecked may return packages that only include metadata.",
+ },
+ {
+ fields: ["originText"],
+ label: "Creator",
+ placeholder: "Name",
+ icon: "user",
+ description:
+ "The name of the creator or originator of a dataset",
+ },
+ {
+ filterType: "DateFilter",
+ fields: ["datePublished", "dateUploaded"],
+ label: "Publish Year",
+ rangeMin: 1800,
+ icon: "calendar",
+ description:
+ "Only show results that were published within the year range",
+ },
+ {
+ filterType: "DateFilter",
+ fields: ["beginDate"],
+ label: "Year of data coverage",
+ rangeMin: 1800,
+ icon: "calendar",
+ description:
+ "Only show results with data collected within the year range",
+ },
+ {
+ fields: [
+ "identifier",
+ "documents",
+ "resourceMap",
+ "seriesId",
+ ],
+ label: "Identifier",
+ placeholder: "DOI or ID",
+ icon: "bullseye",
+ description:
+ "Find datasets if you have all or part of its DOI or ID",
+ operator: "OR",
+ fieldsOperator: "OR",
+ },
+ {
+ fields: [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ ],
+ label: "Taxon",
+ placeholder: "Class, family, etc.",
+ icon: "sitemap",
+ description: "Find data about any taxonomic rank",
+ matchSubstring: true,
+ fieldsOperator: "OR",
+ },
+ {
+ fields: ["siteText"],
+ label: "Location",
+ placeholder: "Geographic region",
+ icon: "globe",
+ description:
+ "The geographic region or study site, as described by the submitter",
+ },
+ ],
},
- ]
- }
- ],
-
- /**
- * The document fields to return when conducting a search. This is the list of fields returned by the main catalog search view.
- * @type {string[]}
- * @since 2.22.0
- * @example ["id", "title", "obsoletedBy"]
- */
- defaultSearchFields: ["id", "seriesId", "title", "origin", "pubDate","dateUploaded","abstract","resourceMap","beginDate","endDate","read_count_i","geohash_9","datasource","isPublic","project","documents","label","logo","formatId","northBoundCoord","southBoundCoord","eastBoundCoord","westBoundCoord"],
-
- /**
- * Semantic annotation configuration
- * Include your Bioportal api key to show ontology information for metadata annotations
- * see: http://bioportal.bioontology.org/account
- * @type {string}
- */
- bioportalAPIKey: "",
- /**
- * The Bioportal REST API URL, which is set dynamically only if a bioportalAPIKey is configured
- * @type {string}
- * @default "https://data.bioontology.org/search"
- */
- bioportalSearchUrl: "https://data.bioontology.org/search",
- /**
- * This attribute stores cache of ontology information that is looked up in Bioportal, so that duplicate REST calls don't need to be made.
- * @type {object}
- */
- bioportalLookupCache: {},
- /**
- * Set this option to true to display the annotation icon in search result rows when a dataset has an annotation
- * @type {boolean}
- */
- showAnnotationIndicator: false,
-
- /**
- * A list of unsupported User-Agent regular expressions for browsers that will not work well with MetacatUI.
- * A warning message will display on the page for anyone using one of these browsers.
- * @type {RegExp[]}
- * @since 2.10.0
- * @default [/(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:)(\d+)/]
- * @example [/(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:)(\d+)/]
- */
- unsupportedBrowsers: [/(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:)(\d+)/],
-
- /**
- * A list of alternate repositories to use for fetching and saving DataONEObjects.
- * In the AppConfig, this is an array of {@link NodeModel#members} attributes, in JSON form.
- * These are the same attributes retireved from the Node Info document, via the d1/mn/v2/node API.
- * The only required attributes are name, identifier, and baseURL.
- * @type {object[]}
- * @example [{
- * name: "Metacat MN",
- * identifier: "urn:node:METACAT",
- * baseURL: "https://my-metacat.org/metacat/d1/mn"
- * }]
- *
- * @since 2.14.0
- */
- alternateRepositories: [],
-
- /**
- * The node identifier of the alternate repository that is used for fetching and saving DataONEObjects.
- * this attribute is dynamically set by MetacatUI to keep track of the currently active alt repo.
- * To specify a repository that should be active by default, set {@link AppConfig#defaultAlternateRepositoryId}
- * @type {string}
- * @example "urn:node:METACAT"
- * @since 2.14.0
- * @readonly
- */
- activeAlternateRepositoryId: null,
-
- /**
- * The node identifier of the alternate repository that should be used for fetching and saving DataONEObjects.
- * Since there can be multiple alternate repositories configured, this attribute can be used to specify which
- * one is actively in use.
- * @type {string}
- * @example "urn:node:METACAT"
- * @since 2.14.0
- */
- defaultAlternateRepositoryId: null,
-
- /**
- * Enable or disable the DataONE Bookkeeper services. If enabled, Portal Views will use the DataONE Plus
- * paid features for active subscriptions. If disabled, the Portal Views will assume
- * all portals are in inactive/free, and will only render free features.
- * @type {boolean}
- * @since 2.14.0
- */
- enableBookkeeperServices: false,
- /**
- * The base URL for the DataONE Bookkeeper services, which manage the DataONE membership plans, such as
- * Hosted Repositories and Plus.
- * See https://github.com/DataONEorg/bookkeeper for more info on this service.
- * @type {string}
- * @since 2.14.0
- */
- bookkeeperBaseUrl: "https://api.test.dataone.org:30443/bookkeeper/v1",
- /**
- * The URL for the DataONE Bookkeeper Quota API, e.g. listQuotas(), getQuota(), createQuota(), etc.
- * This full URL is contructed using {@link AppModel#bookkeeperBaseUrl} when the AppModel is initialized.
- * @readonly
- * @type {string}
- * @since 2.14.0
- */
- bookkeeperQuotasUrl: null,
- /**
- * The URL for the DataONE Bookkeeper Usages API, e.g. listUsages(), getUsage(), createUsage(), etc.
- * This full URL is contructed using {@link AppModel#bookkeeperBaseUrl} when the AppModel is initialized.
- * @readonly
- * @type {string}
- * @since 2.14.0
- */
- bookkeeperUsagesUrl: null,
- /**
- * The URL for the DataONE Bookkeeper Subscriptions API, e.g. listSubscriptions(), fetchSubscription(), createSubscription(), etc.
- * This full URL is contructed using {@link AppModel#bookkeeperBaseUrl} when the AppModel is initialized.
- * @readonly
- * @type {string}
- * @since 2.14.0
- */
- bookkeeperSubscriptionsUrl: null,
- /**
- * The URL for the DataONE Bookkeeper Customers API, e.g. listCustomers(), getCustomer(), createCustomer(), etc.
- * This full URL is contructed using {@link AppModel#bookkeeperBaseUrl} when the AppModel is initialized.
- * @readonly
- * @type {string}
- * @since 2.14.0
- */
- bookkeeperCustomersUrl: null,
-
- /**
- * The name of the DataONE Plus membership plan, which is used in messaging throughout the UI.
- * This is only used if the enableBookkeeperServices setting is set to true.
- * @type {string}
- * @default "DataONE Plus"
- */
- dataonePlusName: "DataONE Plus",
-
- //These two DataONE Plus Preview attributes are for a special DataONE Plus tag of MetacatUI
- // and won't be released in an offical MetacatUI version, since they will be replaced by bookkeeper
- dataonePlusPreviewMode: false,
- dataonePlusPreviewPortals: [],
- /*
- * List of Repositories that are DataONE Hosted Repos.
- * DataONE Hosted Repo features are displayed only for these members.
- * @type {string[]}
- * @readonly
- * @since 2.13.0
- * ------------------------------------
- * This config will not be displayed in the JSDoc documentation since it is
- * temporary and only useful for internal DataONE purposes. This functionality will be replaced
- * with the DataONE Bookkeeper service, eventually.
- */
- dataoneHostedRepos: ["urn:node:KNB", "urn:node:ARCTIC", "urn:node:CA_OPC", "urn:node:ESS_DIVE", "urn:node:CERP_SFWMD"],
-
- /**
- * The length of random portal label generated during preview/trial mode of DataONE Plus
- * @readonly
- * @type {number}
- * @default 7
- * @since 2.14.0
- */
- randomLabelNumericLength: 7,
-
- /**
- * If enabled (by setting to true), Cesium maps will be used in the interface.
- * If a {@link AppConfig#cesiumToken} is not provided, Cesium features will be disabled.
- * @type {boolean}
- * @default false
- * @since 2.18.0
- */
- enableCesium: true,
-
- /**
- * Your Access Token for the Cesium API, which can be retrieved from
- * {@link https://cesium.com/ion/tokens}.
- * @type {string}
- * @since 2.18.0
- * @example eyJhbGciOiJIUzI1R5cCI6IkpXVCJ9.eyJqdGkiOiJmYzUwYjI0ZC0yN2Y4LTRiZjItOdCI6MTYwODIyNDg5MH0.KwCI2-4cHjFYXrR6-mUrwkhh1UdNARK7NxFLpFftjeg
- */
- cesiumToken: "",
-
- /**
- * Your Access Token for the Bing Maps Imagery API, which can be retrieved from
- * https://www.bingmapsportal.com/. Required if any Cesium layers use imagery
- * directly from Bing.
- * @type {string}
- * @since 2.18.0
- * @example AtZjkdlajkl_jklcCAO_1JYafsvAjU1nkd9jdD6CDnHyamndlasdt5CB7xs
- */
- bingMapsKey: "",
-
- /**
- * Enable or disable showing the MeasurementTypeView in the Editor's
- * attribute modal dialog. The {@link AppModel#bioportalAPIKey} must be set to a valid Bioportal
- * API key for the ontology tree widget to work.
- * @type {boolean}
- * @since 2.17.0
- * @default false
- */
- enableMeasurementTypeView: false,
-
- /**
- * As of 2.22.0, the {@link DataCatalogView} is being soft-deprecated and replaced with the new {@link CatalogSearchView}.
- * To give MetacatUI operators time to transition to the new {@link CatalogSearchView}, this configuration option can be
- * enabled (by setting to `true`) and will tell MetacatUI to use the legacy {@link DataCatalogView}. It is highly suggested
- * that MetacatUI operators switch to supporting the new {@link CatalogSearchView} as soon as possible as the legacy {@link DataCatalogView}
- * will be fully deprecated and removed in the future.
- * @since 2.22.0
- * @type {boolean}
- * @default false
- */
- useDeprecatedDataCatalogView: true,
-
- /**
- * The following configuration options are deprecated or experimental and should only be changed by advanced users
- */
- /**
- * The URL for the DataONE log service. This service has been replaced with the DataONE metrics service
- * (which has not been publicly released), so this configuration will be deprecated in the future.
- * This URL is constructed dynamically upon AppModel intialization.
- * @type {string}
- * @deprecated
- */
- d1LogServiceUrl: null,
-
- /**
- * This configuration option is deprecated. This is only used by the {@link DataCatalogView} and {@link DataCatalogViewWithFilters},
- * both of which have been replaced by the {@link CatalogSearchView}. The search mode is now controlled directly on the {@link CatalogSearchView}
- * instead of controlled at the global level here.
- * @deprecated
- */
- searchMode: MetacatUI.mapKey ? 'map' : 'list',
-
- /**
- * This Bioportal REST API URL is used by the experimental and unsupported AnnotatorView to get multiple ontology class info at once.
- * @deprecated
- */
- //bioportalBatchUrl: "https://data.bioontology.org/batch"
-
- /**
- * The packageFormat is the identifier for the version of bagit used when downloading data packages. The format should
- * not contain any additional characters after, for example a backslash.
- * For hierarchical dowloads, use application%2Fbagit-1.0
- * @type {string}
- * @default "application%2Fbagit-1.0"
- * @example application%2Fbagit-097
- */
- packageFormat: 'application%2Fbagit-1.0'
- }, MetacatUI.AppConfig),
-
- defaultView: "data",
-
- initialize: function() {
-
- //If no base URL is specified, then user the DataONE CN base URL
- if(!this.get("baseUrl")){
- this.set("baseUrl", this.get("d1CNBaseUrl"));
- this.set("d1Service", this.get("d1CNService"));
- }
-
- //Set the DataONE MN API URLs
- this.set( this.getDataONEMNAPIs() );
-
- //Determine if this instance of MetacatUI is pointing to a CN, rather than a MN
- this.set("isCN", (this.get("d1Service").indexOf("cn/v2") > 0));
-
- this.set('metacatServiceUrl', this.get('baseUrl') + this.get('context') + '/metacat');
-
- // Metadata quality report services
- this.set('mdqSuitesServiceUrl', this.get("mdqBaseUrl") + "/suites/");
- this.set('mdqRunsServiceUrl', this.get('mdqBaseUrl') + "/runs/");
+ ],
+
+ /**
+ * The document fields to return when conducting a search. This is the list of fields returned by the main catalog search view.
+ * @type {string[]}
+ * @since 2.22.0
+ * @example ["id", "title", "obsoletedBy"]
+ */
+ defaultSearchFields: [
+ "id",
+ "seriesId",
+ "title",
+ "origin",
+ "pubDate",
+ "dateUploaded",
+ "abstract",
+ "resourceMap",
+ "beginDate",
+ "endDate",
+ "read_count_i",
+ "geohash_9",
+ "datasource",
+ "isPublic",
+ "project",
+ "documents",
+ "label",
+ "logo",
+ "formatId",
+ "northBoundCoord",
+ "southBoundCoord",
+ "eastBoundCoord",
+ "westBoundCoord",
+ ],
+
+ /**
+ * Semantic annotation configuration
+ * Include your Bioportal api key to show ontology information for metadata annotations
+ * see: http://bioportal.bioontology.org/account
+ * @type {string}
+ */
+ bioportalAPIKey: "",
+ /**
+ * The Bioportal REST API URL, which is set dynamically only if a bioportalAPIKey is configured
+ * @type {string}
+ * @default "https://data.bioontology.org/search"
+ */
+ bioportalSearchUrl: "https://data.bioontology.org/search",
+ /**
+ * This attribute stores cache of ontology information that is looked up in Bioportal, so that duplicate REST calls don't need to be made.
+ * @type {object}
+ */
+ bioportalLookupCache: {},
+ /**
+ * Set this option to true to display the annotation icon in search result rows when a dataset has an annotation
+ * @type {boolean}
+ */
+ showAnnotationIndicator: false,
+
+ /**
+ * A list of unsupported User-Agent regular expressions for browsers that will not work well with MetacatUI.
+ * A warning message will display on the page for anyone using one of these browsers.
+ * @type {RegExp[]}
+ * @since 2.10.0
+ * @default [/(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:)(\d+)/]
+ * @example [/(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:)(\d+)/]
+ */
+ unsupportedBrowsers: [
+ /(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:)(\d+)/,
+ ],
+
+ /**
+ * A list of alternate repositories to use for fetching and saving DataONEObjects.
+ * In the AppConfig, this is an array of {@link NodeModel#members} attributes, in JSON form.
+ * These are the same attributes retireved from the Node Info document, via the d1/mn/v2/node API.
+ * The only required attributes are name, identifier, and baseURL.
+ * @type {object[]}
+ * @example [{
+ * name: "Metacat MN",
+ * identifier: "urn:node:METACAT",
+ * baseURL: "https://my-metacat.org/metacat/d1/mn"
+ * }]
+ *
+ * @since 2.14.0
+ */
+ alternateRepositories: [],
+
+ /**
+ * The node identifier of the alternate repository that is used for fetching and saving DataONEObjects.
+ * this attribute is dynamically set by MetacatUI to keep track of the currently active alt repo.
+ * To specify a repository that should be active by default, set {@link AppConfig#defaultAlternateRepositoryId}
+ * @type {string}
+ * @example "urn:node:METACAT"
+ * @since 2.14.0
+ * @readonly
+ */
+ activeAlternateRepositoryId: null,
+
+ /**
+ * The node identifier of the alternate repository that should be used for fetching and saving DataONEObjects.
+ * Since there can be multiple alternate repositories configured, this attribute can be used to specify which
+ * one is actively in use.
+ * @type {string}
+ * @example "urn:node:METACAT"
+ * @since 2.14.0
+ */
+ defaultAlternateRepositoryId: null,
+
+ /**
+ * Enable or disable the DataONE Bookkeeper services. If enabled, Portal Views will use the DataONE Plus
+ * paid features for active subscriptions. If disabled, the Portal Views will assume
+ * all portals are in inactive/free, and will only render free features.
+ * @type {boolean}
+ * @since 2.14.0
+ */
+ enableBookkeeperServices: false,
+ /**
+ * The base URL for the DataONE Bookkeeper services, which manage the DataONE membership plans, such as
+ * Hosted Repositories and Plus.
+ * See https://github.com/DataONEorg/bookkeeper for more info on this service.
+ * @type {string}
+ * @since 2.14.0
+ */
+ bookkeeperBaseUrl: "https://api.test.dataone.org:30443/bookkeeper/v1",
+ /**
+ * The URL for the DataONE Bookkeeper Quota API, e.g. listQuotas(), getQuota(), createQuota(), etc.
+ * This full URL is contructed using {@link AppModel#bookkeeperBaseUrl} when the AppModel is initialized.
+ * @readonly
+ * @type {string}
+ * @since 2.14.0
+ */
+ bookkeeperQuotasUrl: null,
+ /**
+ * The URL for the DataONE Bookkeeper Usages API, e.g. listUsages(), getUsage(), createUsage(), etc.
+ * This full URL is contructed using {@link AppModel#bookkeeperBaseUrl} when the AppModel is initialized.
+ * @readonly
+ * @type {string}
+ * @since 2.14.0
+ */
+ bookkeeperUsagesUrl: null,
+ /**
+ * The URL for the DataONE Bookkeeper Subscriptions API, e.g. listSubscriptions(), fetchSubscription(), createSubscription(), etc.
+ * This full URL is contructed using {@link AppModel#bookkeeperBaseUrl} when the AppModel is initialized.
+ * @readonly
+ * @type {string}
+ * @since 2.14.0
+ */
+ bookkeeperSubscriptionsUrl: null,
+ /**
+ * The URL for the DataONE Bookkeeper Customers API, e.g. listCustomers(), getCustomer(), createCustomer(), etc.
+ * This full URL is contructed using {@link AppModel#bookkeeperBaseUrl} when the AppModel is initialized.
+ * @readonly
+ * @type {string}
+ * @since 2.14.0
+ */
+ bookkeeperCustomersUrl: null,
+
+ /**
+ * The name of the DataONE Plus membership plan, which is used in messaging throughout the UI.
+ * This is only used if the enableBookkeeperServices setting is set to true.
+ * @type {string}
+ * @default "DataONE Plus"
+ */
+ dataonePlusName: "DataONE Plus",
+
+ //These two DataONE Plus Preview attributes are for a special DataONE Plus tag of MetacatUI
+ // and won't be released in an offical MetacatUI version, since they will be replaced by bookkeeper
+ dataonePlusPreviewMode: false,
+ dataonePlusPreviewPortals: [],
+ /*
+ * List of Repositories that are DataONE Hosted Repos.
+ * DataONE Hosted Repo features are displayed only for these members.
+ * @type {string[]}
+ * @readonly
+ * @since 2.13.0
+ * ------------------------------------
+ * This config will not be displayed in the JSDoc documentation since it is
+ * temporary and only useful for internal DataONE purposes. This functionality will be replaced
+ * with the DataONE Bookkeeper service, eventually.
+ */
+ dataoneHostedRepos: [
+ "urn:node:KNB",
+ "urn:node:ARCTIC",
+ "urn:node:CA_OPC",
+ "urn:node:ESS_DIVE",
+ "urn:node:CERP_SFWMD",
+ ],
+
+ /**
+ * The length of random portal label generated during preview/trial mode of DataONE Plus
+ * @readonly
+ * @type {number}
+ * @default 7
+ * @since 2.14.0
+ */
+ randomLabelNumericLength: 7,
+
+ /**
+ * If enabled (by setting to true), Cesium maps will be used in the interface.
+ * If a {@link AppConfig#cesiumToken} is not provided, Cesium features will be disabled.
+ * @type {boolean}
+ * @default false
+ * @since 2.18.0
+ */
+ enableCesium: true,
+
+ /**
+ * Your Access Token for the Cesium API, which can be retrieved from
+ * {@link https://cesium.com/ion/tokens}.
+ * @type {string}
+ * @since 2.18.0
+ * @example eyJhbGciOiJIUzI1R5cCI6IkpXVCJ9.eyJqdGkiOiJmYzUwYjI0ZC0yN2Y4LTRiZjItOdCI6MTYwODIyNDg5MH0.KwCI2-4cHjFYXrR6-mUrwkhh1UdNARK7NxFLpFftjeg
+ */
+ cesiumToken: "",
+
+ /**
+ * Your Access Token for the Bing Maps Imagery API, which can be retrieved from
+ * https://www.bingmapsportal.com/. Required if any Cesium layers use imagery
+ * directly from Bing.
+ * @type {string}
+ * @since 2.18.0
+ * @example AtZjkdlajkl_jklcCAO_1JYafsvAjU1nkd9jdD6CDnHyamndlasdt5CB7xs
+ */
+ bingMapsKey: "",
+
+ /**
+ * Enable or disable showing the MeasurementTypeView in the Editor's
+ * attribute modal dialog. The {@link AppModel#bioportalAPIKey} must be set to a valid Bioportal
+ * API key for the ontology tree widget to work.
+ * @type {boolean}
+ * @since 2.17.0
+ * @default false
+ */
+ enableMeasurementTypeView: false,
+
+ /**
+ * As of 2.22.0, the {@link DataCatalogView} is being soft-deprecated and replaced with the new {@link CatalogSearchView}.
+ * To give MetacatUI operators time to transition to the new {@link CatalogSearchView}, this configuration option can be
+ * enabled (by setting to `true`) and will tell MetacatUI to use the legacy {@link DataCatalogView}. It is highly suggested
+ * that MetacatUI operators switch to supporting the new {@link CatalogSearchView} as soon as possible as the legacy {@link DataCatalogView}
+ * will be fully deprecated and removed in the future.
+ * @since 2.22.0
+ * @type {boolean}
+ * @default false
+ */
+ useDeprecatedDataCatalogView: true,
+
+ /**
+ * The following configuration options are deprecated or experimental and should only be changed by advanced users
+ */
+ /**
+ * The URL for the DataONE log service. This service has been replaced with the DataONE metrics service
+ * (which has not been publicly released), so this configuration will be deprecated in the future.
+ * This URL is constructed dynamically upon AppModel intialization.
+ * @type {string}
+ * @deprecated
+ */
+ d1LogServiceUrl: null,
+
+ /**
+ * This configuration option is deprecated. This is only used by the {@link DataCatalogView} and {@link DataCatalogViewWithFilters},
+ * both of which have been replaced by the {@link CatalogSearchView}. The search mode is now controlled directly on the {@link CatalogSearchView}
+ * instead of controlled at the global level here.
+ * @deprecated
+ */
+ searchMode: MetacatUI.mapKey ? "map" : "list",
+
+ /**
+ * This Bioportal REST API URL is used by the experimental and unsupported AnnotatorView to get multiple ontology class info at once.
+ * @deprecated
+ */
+ //bioportalBatchUrl: "https://data.bioontology.org/batch"
+
+ /**
+ * The packageFormat is the identifier for the version of bagit used when downloading data packages. The format should
+ * not contain any additional characters after, for example a backslash.
+ * For hierarchical dowloads, use application%2Fbagit-1.0
+ * @type {string}
+ * @default "application%2Fbagit-1.0"
+ * @example application%2Fbagit-097
+ */
+ packageFormat: "application%2Fbagit-1.0",
+ },
+ MetacatUI.AppConfig,
+ ),
- //DataONE CN API
- if(this.get("d1CNBaseUrl")){
+ defaultView: "data",
- //Add a forward slash to the end of the base URL if there isn't one
- var d1CNBaseUrl = this.get("d1CNBaseUrl");
- if( d1CNBaseUrl.charAt( d1CNBaseUrl.length-1 ) == "/" ){
- d1CNBaseUrl = d1CNBaseUrl.substring(0, d1CNBaseUrl.length-1);
- this.set("d1CNBaseUrl", d1CNBaseUrl);
+ initialize: function () {
+ //If no base URL is specified, then user the DataONE CN base URL
+ if (!this.get("baseUrl")) {
+ this.set("baseUrl", this.get("d1CNBaseUrl"));
+ this.set("d1Service", this.get("d1CNService"));
}
- //Account services
- if(typeof this.get("accountsUrl") != "undefined"){
- this.set("accountsUrl", d1CNBaseUrl + this.get("d1CNService") + "/accounts/");
+ //Set the DataONE MN API URLs
+ this.set(this.getDataONEMNAPIs());
- if(typeof this.get("pendingMapsUrl") != "undefined")
- this.set("pendingMapsUrl", this.get("accountsUrl") + "pendingmap/");
+ //Determine if this instance of MetacatUI is pointing to a CN, rather than a MN
+ this.set("isCN", this.get("d1Service").indexOf("cn/v2") > 0);
- if(typeof this.get("accountsMapsUrl") != "undefined")
- this.set("accountsMapsUrl", this.get("accountsUrl") + "map/");
+ this.set(
+ "metacatServiceUrl",
+ this.get("baseUrl") + this.get("context") + "/metacat",
+ );
- if(typeof this.get("groupsUrl") != "undefined")
- this.set("groupsUrl", d1CNBaseUrl + this.get("d1CNService") + "/groups/");
- }
+ // Metadata quality report services
+ this.set("mdqSuitesServiceUrl", this.get("mdqBaseUrl") + "/suites/");
+ this.set("mdqRunsServiceUrl", this.get("mdqBaseUrl") + "/runs/");
- if(typeof this.get("d1LogServiceUrl") != "undefined")
- this.set('d1LogServiceUrl', d1CNBaseUrl + this.get('d1CNService') + '/query/logsolr/?');
+ //DataONE CN API
+ if (this.get("d1CNBaseUrl")) {
+ //Add a forward slash to the end of the base URL if there isn't one
+ var d1CNBaseUrl = this.get("d1CNBaseUrl");
+ if (d1CNBaseUrl.charAt(d1CNBaseUrl.length - 1) == "/") {
+ d1CNBaseUrl = d1CNBaseUrl.substring(0, d1CNBaseUrl.length - 1);
+ this.set("d1CNBaseUrl", d1CNBaseUrl);
+ }
- this.set("nodeServiceUrl", d1CNBaseUrl + this.get("d1CNService") + "/node/");
- this.set('resolveServiceUrl', d1CNBaseUrl + this.get('d1CNService') + '/resolve/');
- this.set("reserveServiceUrl", d1CNBaseUrl + this.get("d1CNService") + "/reserve");
+ //Account services
+ if (typeof this.get("accountsUrl") != "undefined") {
+ this.set(
+ "accountsUrl",
+ d1CNBaseUrl + this.get("d1CNService") + "/accounts/",
+ );
+
+ if (typeof this.get("pendingMapsUrl") != "undefined")
+ this.set(
+ "pendingMapsUrl",
+ this.get("accountsUrl") + "pendingmap/",
+ );
+
+ if (typeof this.get("accountsMapsUrl") != "undefined")
+ this.set("accountsMapsUrl", this.get("accountsUrl") + "map/");
+
+ if (typeof this.get("groupsUrl") != "undefined")
+ this.set(
+ "groupsUrl",
+ d1CNBaseUrl + this.get("d1CNService") + "/groups/",
+ );
+ }
- //Token URLs
- if(typeof this.get("tokenUrl") != "undefined"){
- this.set("tokenUrl", d1CNBaseUrl + "/portal/" + "token");
+ if (typeof this.get("d1LogServiceUrl") != "undefined")
+ this.set(
+ "d1LogServiceUrl",
+ d1CNBaseUrl + this.get("d1CNService") + "/query/logsolr/?",
+ );
- this.set("checkTokenUrl", d1CNBaseUrl + this.get("d1CNService") + "/diag/subject");
+ this.set(
+ "nodeServiceUrl",
+ d1CNBaseUrl + this.get("d1CNService") + "/node/",
+ );
+ this.set(
+ "resolveServiceUrl",
+ d1CNBaseUrl + this.get("d1CNService") + "/resolve/",
+ );
+ this.set(
+ "reserveServiceUrl",
+ d1CNBaseUrl + this.get("d1CNService") + "/reserve",
+ );
- //The sign-in and out URLs - allow these to be turned off by removing them in the defaults above (hence the check for undefined)
- if(this.get("enableCILogonSignIn") || typeof this.get("signInUrl") !== "undefined")
- this.set("signInUrl", d1CNBaseUrl + "/portal/" + "startRequest?target=");
- if(typeof this.get("signInUrlOrcid") !== "undefined")
- this.set("signInUrlOrcid", d1CNBaseUrl + "/portal/" + "oauth?action=start&target=");
+ //Token URLs
+ if (typeof this.get("tokenUrl") != "undefined") {
+ this.set("tokenUrl", d1CNBaseUrl + "/portal/" + "token");
+
+ this.set(
+ "checkTokenUrl",
+ d1CNBaseUrl + this.get("d1CNService") + "/diag/subject",
+ );
+
+ //The sign-in and out URLs - allow these to be turned off by removing them in the defaults above (hence the check for undefined)
+ if (
+ this.get("enableCILogonSignIn") ||
+ typeof this.get("signInUrl") !== "undefined"
+ )
+ this.set(
+ "signInUrl",
+ d1CNBaseUrl + "/portal/" + "startRequest?target=",
+ );
+ if (typeof this.get("signInUrlOrcid") !== "undefined")
+ this.set(
+ "signInUrlOrcid",
+ d1CNBaseUrl + "/portal/" + "oauth?action=start&target=",
+ );
+
+ if (this.get("enableLdapSignIn") && !this.get("signInUrlLdap")) {
+ this.set(
+ "signInUrlLdap",
+ d1CNBaseUrl + "/portal/" + "ldap?target=",
+ );
+ }
- if(this.get("enableLdapSignIn") && !this.get("signInUrlLdap")){
- this.set("signInUrlLdap", d1CNBaseUrl + "/portal/" + "ldap?target=");
+ if (this.get("orcidBaseUrl"))
+ this.set(
+ "orcidSearchUrl",
+ this.get("orcidBaseUrl") + "/v1.1/search/orcid-bio?q=",
+ );
+
+ if (
+ typeof this.get("signInUrl") !== "undefined" ||
+ typeof this.get("signInUrlOrcid") !== "undefined"
+ )
+ this.set("signOutUrl", d1CNBaseUrl + "/portal/" + "logout");
}
+ // Object format list
+ if (typeof this.get("formatsUrl") != "undefined") {
+ this.set(
+ "formatsServiceUrl",
+ d1CNBaseUrl + this.get("d1CNService") + this.get("formatsUrl"),
+ );
+ }
- if(this.get('orcidBaseUrl'))
- this.set('orcidSearchUrl', this.get('orcidBaseUrl') + '/v1.1/search/orcid-bio?q=');
+ //ORCID search
+ if (typeof this.get("orcidBaseUrl") != "undefined")
+ this.set(
+ "orcidSearchUrl",
+ this.get("orcidBaseUrl") + "/search/orcid-bio?q=",
+ );
+ }
- if((typeof this.get("signInUrl") !== "undefined") || (typeof this.get("signInUrlOrcid") !== "undefined"))
- this.set("signOutUrl", d1CNBaseUrl + "/portal/" + "logout");
+ // Metadata quality report services
+ this.set("mdqSuitesServiceUrl", this.get("mdqBaseUrl") + "/suites/");
+ this.set("mdqRunsServiceUrl", this.get("mdqBaseUrl") + "/runs/");
+ this.set("mdqScoresServiceUrl", this.get("mdqBaseUrl") + "/scores/");
+ //Construct the DataONE Bookkeeper service API URLs
+ if (this.get("enableBookkeeperServices")) {
+ this.set(
+ "bookkeeperSubscriptionsUrl",
+ this.get("bookkeeperBaseUrl") + "/subscriptions",
+ );
+ this.set(
+ "bookkeeperCustomersUrl",
+ this.get("bookkeeperBaseUrl") + "/customers",
+ );
+ this.set(
+ "bookkeeperQuotasUrl",
+ this.get("bookkeeperBaseUrl") + "/quotas",
+ );
+ this.set(
+ "bookkeeperUsagesUrl",
+ this.get("bookkeeperBaseUrl") + "/usages",
+ );
}
- // Object format list
- if ( typeof this.get("formatsUrl") != "undefined" ) {
- this.set("formatsServiceUrl",
- d1CNBaseUrl + this.get("d1CNService") + this.get("formatsUrl"));
+ //Construct the Fluid Earth Fever URL
+ if (
+ this.get("enableFeverVisualizations") &&
+ this.get("feverPath") &&
+ !this.get("feverUrl")
+ ) {
+ this.set("feverUrl", this.get("baseUrl") + this.get("feverPath"));
}
- //ORCID search
- if(typeof this.get("orcidBaseUrl") != "undefined")
- this.set('orcidSearchUrl', this.get('orcidBaseUrl') + '/search/orcid-bio?q=');
-
- }
-
- // Metadata quality report services
- this.set('mdqSuitesServiceUrl', this.get("mdqBaseUrl") + "/suites/");
- this.set('mdqRunsServiceUrl', this.get('mdqBaseUrl') + "/runs/");
- this.set('mdqScoresServiceUrl', this.get('mdqBaseUrl') + "/scores/");
-
- //Construct the DataONE Bookkeeper service API URLs
- if( this.get("enableBookkeeperServices") ){
- this.set("bookkeeperSubscriptionsUrl", this.get("bookkeeperBaseUrl") + "/subscriptions");
- this.set("bookkeeperCustomersUrl", this.get("bookkeeperBaseUrl") + "/customers");
- this.set("bookkeeperQuotasUrl", this.get("bookkeeperBaseUrl") + "/quotas");
- this.set("bookkeeperUsagesUrl", this.get("bookkeeperBaseUrl") + "/usages");
- }
+ this.on("change:pid", this.changePid);
- //Construct the Fluid Earth Fever URL
- if( this.get("enableFeverVisualizations") && this.get("feverPath") && !this.get("feverUrl") ){
- this.set("feverUrl", this.get("baseUrl") + this.get("feverPath"));
- }
+ //For backward-compatbility, set the theme and themeTitle variables using the
+ // attributes set on this model, which are taken from the AppConfig
+ MetacatUI.theme = this.get("theme");
+ MetacatUI.themeTitle = this.get("repositoryName");
- this.on("change:pid", this.changePid);
-
- //For backward-compatbility, set the theme and themeTitle variables using the
- // attributes set on this model, which are taken from the AppConfig
- MetacatUI.theme = this.get("theme");
- MetacatUI.themeTitle = this.get("repositoryName");
-
- //Set up the alternative repositories
- _.map(this.get("alternateRepositories"), function(repo){
- repo = _.extend(repo, this.getDataONEMNAPIs(repo.baseURL));
- }, this);
-
- },
+ //Set up the alternative repositories
+ _.map(
+ this.get("alternateRepositories"),
+ function (repo) {
+ repo = _.extend(repo, this.getDataONEMNAPIs(repo.baseURL));
+ },
+ this,
+ );
+ },
- /**
- * Constructs the DataONE API URLs for the given baseUrl
- * @param {string} [baseUrl] - The baseUrl to use in the URLs. If not specified, it uses the AppModel attributes.
- * @returns {object}
- */
- getDataONEMNAPIs: function(baseUrl){
+ /**
+ * Constructs the DataONE API URLs for the given baseUrl
+ * @param {string} [baseUrl] - The baseUrl to use in the URLs. If not specified, it uses the AppModel attributes.
+ * @returns {object}
+ */
+ getDataONEMNAPIs: function (baseUrl) {
+ var urls = {};
- var urls = {};
+ //Get the baseUrl from this model if one isn't given
+ if (typeof baseUrl == "undefined") {
+ var baseUrl = this.get("baseUrl");
+ }
- //Get the baseUrl from this model if one isn't given
- if( typeof baseUrl == "undefined" ){
- var baseUrl = this.get("baseUrl");
- }
+ //Remove a forward slash to the end of the base URL if there is one
+ if (baseUrl.charAt(baseUrl.length - 1) == "/") {
+ baseUrl = baseUrl.substring(0, baseUrl.length - 1);
+ }
- //Remove a forward slash to the end of the base URL if there is one
- if( baseUrl.charAt( baseUrl.length-1 ) == "/" ){
- baseUrl = baseUrl.substring(0, baseUrl.length-1);
- }
+ //If the baseUrl doesn't have the full DataONE MN API structure, then construct it
+ if (baseUrl.indexOf("/d1/mn") == -1) {
+ //Get the Dataone API fragment, which is either "/d1/mn/v2" or "/cn/v2"
+ var d1Service = this.get("d1Service");
+ if (typeof d1Service != "string" || !d1Service.length) {
+ d1Service = "/d1/mn/v2";
+ } else if (d1Service.charAt(0) != "/") {
+ d1Service = "/" + d1Service;
+ }
- //If the baseUrl doesn't have the full DataONE MN API structure, then construct it
- if( baseUrl.indexOf("/d1/mn") == -1 ){
+ //Get the Metacat context, and make sure it starts with a forward slash
+ var context = this.get("context");
+ if (typeof context != "string" || !context.length) {
+ context = "";
+ } else if (context.charAt(0) != "/") {
+ context = "/" + context;
+ }
- //Get the Dataone API fragment, which is either "/d1/mn/v2" or "/cn/v2"
- var d1Service = this.get('d1Service');
- if( typeof d1Service != "string" || !d1Service.length ){
- d1Service = "/d1/mn/v2";
+ //Construct the base URL
+ baseUrl = baseUrl + context + d1Service;
}
- else if( d1Service.charAt(0) != "/" ){
- d1Service = "/" + d1Service;
+ //Otherwise, just make sure the API version is appended to the base URL
+ else if (baseUrl.substring(baseUrl.length - 3) != "/v2") {
+ d1Service = "/d1/mn";
+ baseUrl = baseUrl + "/v2";
}
- //Get the Metacat context, and make sure it starts with a forward slash
- var context = this.get("context");
- if( typeof context != "string" || !context.length ){
- context = "";
- }
- else if( context.charAt(0) != "/" ){
- context = "/" + context;
+ // these are pretty standard, but can be customized if needed
+ urls.viewServiceUrl = baseUrl + "/views/metacatui/";
+ urls.publishServiceUrl = baseUrl + "/publish/";
+ urls.authServiceUrl = baseUrl + "/isAuthorized/";
+ urls.queryServiceUrl = baseUrl + "/query/solr/?";
+ urls.metaServiceUrl = baseUrl + "/meta/";
+ urls.packageServiceUrl =
+ baseUrl + "/packages/" + this.get("packageFormat") + "/";
+
+ if (d1Service.indexOf("mn") > 0) {
+ urls.objectServiceUrl = baseUrl + "/object/";
}
- //Construct the base URL
- baseUrl = baseUrl + context + d1Service;
- }
- //Otherwise, just make sure the API version is appended to the base URL
- else if( baseUrl.substring( baseUrl.length-3 ) != "/v2" ){
- d1Service = "/d1/mn";
- baseUrl = baseUrl + "/v2";
- }
-
- // these are pretty standard, but can be customized if needed
- urls.viewServiceUrl = baseUrl + '/views/metacatui/';
- urls.publishServiceUrl = baseUrl + '/publish/';
- urls.authServiceUrl = baseUrl + '/isAuthorized/';
- urls.queryServiceUrl = baseUrl + '/query/solr/?';
- urls.metaServiceUrl = baseUrl + '/meta/';
- urls.packageServiceUrl = baseUrl + '/packages/'+this.get('packageFormat')+'/';
-
- if( d1Service.indexOf("mn") > 0 ){
- urls.objectServiceUrl = baseUrl + '/object/';
- }
-
- if( this.get("enableMonitorStatus") ){
- urls.monitorStatusUrl = baseUrl + "/monitor/status";
- }
-
- return urls;
+ if (this.get("enableMonitorStatus")) {
+ urls.monitorStatusUrl = baseUrl + "/monitor/status";
+ }
- },
+ return urls;
+ },
- changePid: function(model, name){
- this.set("previousPid", model.previous("pid"));
- },
+ changePid: function (model, name) {
+ this.set("previousPid", model.previous("pid"));
+ },
- /**
- * Gets the currently-active alternative repository that is configured in this AppModel.
- * @returns {object}
- */
- getActiveAltRepo: function(){
- //Get the alternative repositories to use for uploading objects
- var altRepos = this.get("alternateRepositories"),
+ /**
+ * Gets the currently-active alternative repository that is configured in this AppModel.
+ * @returns {object}
+ */
+ getActiveAltRepo: function () {
+ //Get the alternative repositories to use for uploading objects
+ var altRepos = this.get("alternateRepositories"),
activeAltRepo;
- //Get the active alt repo
- if( altRepos.length && this.get("activeAlternateRepositoryId") ){
- activeAltRepo = _.findWhere(altRepos, {identifier: this.get("activeAlternateRepositoryId") });
+ //Get the active alt repo
+ if (altRepos.length && this.get("activeAlternateRepositoryId")) {
+ activeAltRepo = _.findWhere(altRepos, {
+ identifier: this.get("activeAlternateRepositoryId"),
+ });
- return activeAltRepo || null;
- }
- else{
- return null;
- }
- },
+ return activeAltRepo || null;
+ } else {
+ return null;
+ }
+ },
- /**
- * Gets the default alternate repository and sets it as the active alternate repository.
- * If a default alt repo ({@link AppConfig#defaultAlternateRepositoryId}) isn't configured,
- * the first alt repo in the {@link AppConfig#alternateRepositories} list is used.
- * @fires AppModel#change:activeAlternateRepositoryId
- */
- setActiveAltRepo: function(){
- //Get the alternative repositories to use for uploading objects
- var altRepos = this.get("alternateRepositories"),
+ /**
+ * Gets the default alternate repository and sets it as the active alternate repository.
+ * If a default alt repo ({@link AppConfig#defaultAlternateRepositoryId}) isn't configured,
+ * the first alt repo in the {@link AppConfig#alternateRepositories} list is used.
+ * @fires AppModel#change:activeAlternateRepositoryId
+ */
+ setActiveAltRepo: function () {
+ //Get the alternative repositories to use for uploading objects
+ var altRepos = this.get("alternateRepositories"),
defaultAltRepo;
- if( !altRepos.length ){
- return;
- }
+ if (!altRepos.length) {
+ return;
+ }
- //If a default alt repo is configured, set that as the active alt repo
- if( this.get("defaultAlternateRepositoryId") ){
- defaultAltRepo = _.findWhere(altRepos, {identifier: this.get("defaultAlternateRepositoryId") });
- if( defaultAltRepo ){
- this.set("activeAlternateRepositoryId", defaultAltRepo.identifier);
+ //If a default alt repo is configured, set that as the active alt repo
+ if (this.get("defaultAlternateRepositoryId")) {
+ defaultAltRepo = _.findWhere(altRepos, {
+ identifier: this.get("defaultAlternateRepositoryId"),
+ });
+ if (defaultAltRepo) {
+ this.set("activeAlternateRepositoryId", defaultAltRepo.identifier);
+ }
}
- }
- //Otherwise, use the first alt repo in the list
- if( !defaultAltRepo ){
- this.set("activeAlternateRepositoryId", altRepos[0].identifier);
- }
+ //Otherwise, use the first alt repo in the list
+ if (!defaultAltRepo) {
+ this.set("activeAlternateRepositoryId", altRepos[0].identifier);
+ }
},
/**
@@ -2343,13 +2667,13 @@ define(['jquery', 'underscore', 'backbone'],
"Content-Type": "application/json",
},
})
- .then((response) => response.json())
- .then((data) => {
- this.set("quickAddTaxa", data);
- })
- .catch((error) => {
- console.log("Error fetching taxa", error);
- });
+ .then((response) => response.json())
+ .then((data) => {
+ this.set("quickAddTaxa", data);
+ })
+ .catch((error) => {
+ console.log("Error fetching taxa", error);
+ });
},
/**
@@ -2366,21 +2690,21 @@ define(['jquery', 'underscore', 'backbone'],
*/
addCSS: function (css, id) {
try {
- if (!MetacatUI.loadedCSS) {
- MetacatUI.loadedCSS = []
- }
- if (!MetacatUI.loadedCSS.includes(id)) {
- MetacatUI.loadedCSS.push(id);
- var style = document.createElement('style');
+ if (!MetacatUI.loadedCSS) {
+ MetacatUI.loadedCSS = [];
+ }
+ if (!MetacatUI.loadedCSS.includes(id)) {
+ MetacatUI.loadedCSS.push(id);
+ var style = document.createElement("style");
style.id = id;
- style.appendChild(document.createTextNode(css));
- document.querySelector("head").appendChild(style);
+ style.appendChild(document.createTextNode(css));
+ document.querySelector("head").appendChild(style);
}
- }
- catch (error) {
+ } catch (error) {
console.log(
- 'There was an error adding CSS to the app' +
- '. Error details: ' + error
+ "There was an error adding CSS to the app" +
+ ". Error details: " +
+ error,
);
}
},
@@ -2395,20 +2719,20 @@ define(['jquery', 'underscore', 'backbone'],
removeCSS: function (id) {
try {
if (!MetacatUI.loadedCSS) {
- MetacatUI.loadedCSS = []
+ MetacatUI.loadedCSS = [];
}
if (MetacatUI.loadedCSS.includes(id)) {
- MetacatUI.loadedCSS = MetacatUI.loadedCSS.filter(e => e !== id);
- var sheet = document.querySelector("head #" + id)
+ MetacatUI.loadedCSS = MetacatUI.loadedCSS.filter((e) => e !== id);
+ var sheet = document.querySelector("head #" + id);
if (sheet) {
- sheet.remove()
+ sheet.remove();
}
}
- }
- catch (error) {
+ } catch (error) {
console.log(
- 'There was an error removing CSS from the app' +
- '. Error details: ' + error
+ "There was an error removing CSS from the app" +
+ ". Error details: " +
+ error,
);
}
},
@@ -2498,7 +2822,7 @@ define(['jquery', 'underscore', 'backbone'],
if (doiURLMatch) return "doi:" + doiURLMatch[3];
return "";
},
-
- });
+ },
+ );
return AppModel;
});
diff --git a/src/js/models/CitationModel.js b/src/js/models/CitationModel.js
index 817f44cc4..78209e129 100644
--- a/src/js/models/CitationModel.js
+++ b/src/js/models/CitationModel.js
@@ -5,7 +5,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
$,
_,
Backbone,
- Citations
+ Citations,
) {
/**
* @class CitationModel
@@ -167,7 +167,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
delete item.origin;
// Format the authors in the origin array
item.originArray = item.originArray.map((author) =>
- this.formatAuthor(author)
+ this.formatAuthor(author),
);
// Get the publish year
const date =
@@ -195,7 +195,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
} catch (error) {
console.log(
"Error parsing a CitationModel. Returning response as-is.",
- error
+ error,
);
return response;
}
@@ -290,7 +290,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
this.listenTo(
attrs.citationMetadata,
"update",
- this.trigger.bind(this, "change")
+ this.trigger.bind(this, "change"),
);
}
}
@@ -302,7 +302,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
"Error in custom set() method on CitationModel. Will attempt to set" +
" using with Backbone set(). Attributes and error stack trace:",
{ key, val, options },
- error
+ error,
);
Backbone.Model.prototype.set.call(this, key, val, options);
}
@@ -350,7 +350,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
Backbone.Model.prototype.set.call(
this,
"sourceModel",
- newSourceModel
+ newSourceModel,
);
this.populateFromModel(newSourceModel);
} catch (error) {
@@ -394,7 +394,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
"Error populating a CitationModel from the model: ",
newSourceModel,
" Error: ",
- error
+ error,
);
}
},
@@ -418,7 +418,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
console.log(
"Error getting year from the sourceModel. Model and error:",
sourceModel,
- error
+ error,
);
return this.defaults().year_of_publishing;
}
@@ -453,7 +453,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
console.log(
"Error getting title from the sourceModel. Model and error:",
sourceModel,
- error
+ error,
);
return this.defaults().title;
}
@@ -492,7 +492,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
console.log(
"Error getting journal from the sourceModel. Model and error:",
sourceModel,
- error
+ error,
);
return this.defaults().journal;
}
@@ -536,7 +536,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
console.log(
"Error getting originArray from the sourceModel. Model and error:",
sourceModel,
- error
+ error,
);
return this.defaults().originArray;
}
@@ -557,7 +557,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
console.log(
"Error getting the pid from the sourceModel. Model and error:",
sourceModel,
- error
+ error,
);
return this.defaults().pid;
}
@@ -578,7 +578,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
console.log(
"Error getting the seriesId from the sourceModel. Model and error:",
sourceModel,
- error
+ error,
);
return this.defaults().seriesId;
}
@@ -605,7 +605,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
console.log(
"Error getting the viewUrl from the sourceModel. Model and error:",
sourceModel,
- error
+ error,
);
return this.defaults().viewUrl;
}
@@ -635,7 +635,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
console.log(
"There was an error formatting an author, returning " +
"the author input as is.",
- error
+ error,
);
return author;
}
@@ -722,7 +722,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
// Any remaining lowercase words are assumed to be non-dropping particles
const nonDroppingParticles = parts.filter((part) =>
- part.match(/^[a-z]+$/)
+ part.match(/^[a-z]+$/),
);
if (nonDroppingParticles.length > 0) {
name["non-dropping-particle"] = nonDroppingParticles.join(" ");
@@ -764,7 +764,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
} catch (error) {
console.log(
"There was an error getting the year from the date, returning null.",
- error
+ error,
);
return null;
}
@@ -780,7 +780,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
try {
if (!orcid) return false;
const regex = new RegExp(
- "^https?:\\/\\/orcid.org\\/(\\d{4}-){3}(\\d{3}[0-9X])$"
+ "^https?:\\/\\/orcid.org\\/(\\d{4}-){3}(\\d{3}[0-9X])$",
);
return regex.test(orcid);
} catch {
@@ -822,7 +822,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
} catch (error) {
console.log(
"There was an error getting the name from the orcid.",
- error
+ error,
);
}
},
@@ -848,7 +848,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
console.log(
`There was an error checking if the citation is from node ${node}.` +
`Returning false.`,
- error
+ error,
);
return false;
}
@@ -872,7 +872,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
} catch (error) {
console.log(
"There was an error converting the origin string to an array.",
- error
+ error,
);
return this.defaults().originArray;
}
@@ -908,7 +908,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
} catch (error) {
console.log(
"There was an error converting the origin array to a string.",
- error
+ error,
);
return this.defaults().origin;
}
@@ -1024,7 +1024,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
} catch (error) {
console.log(
"There was an error finding the DOI for the citation. Returning null",
- error
+ error,
);
return null;
}
@@ -1084,7 +1084,7 @@ define(["jquery", "underscore", "backbone", "collections/Citations"], function (
}
return "";
},
- }
+ },
);
return Citation;
diff --git a/src/js/models/CollectionModel.js b/src/js/models/CollectionModel.js
index bc311928e..75842a78c 100644
--- a/src/js/models/CollectionModel.js
+++ b/src/js/models/CollectionModel.js
@@ -1,710 +1,740 @@
/* global define */
-define(["jquery",
- "underscore",
- "backbone",
- "uuid",
- "collections/Filters",
- "collections/SolrResults",
- "models/DataONEObject",
- "models/filters/Filter",
- "models/filters/FilterGroup",
- "models/Search",
- ],
- function($, _, Backbone, uuid, Filters, SolrResults, DataONEObject, Filter, FilterGroup, Search) {
-
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "uuid",
+ "collections/Filters",
+ "collections/SolrResults",
+ "models/DataONEObject",
+ "models/filters/Filter",
+ "models/filters/FilterGroup",
+ "models/Search",
+], function (
+ $,
+ _,
+ Backbone,
+ uuid,
+ Filters,
+ SolrResults,
+ DataONEObject,
+ Filter,
+ FilterGroup,
+ Search,
+) {
/**
- * @class CollectionModel
- * @classdesc A collection of datasets, defined by one or more search filters
- * @classcategory Models
- * @name CollectionModel
- * @extends DataONEObject
- * @constructor
- */
- var CollectionModel = DataONEObject.extend(
- /** @lends CollectionModel.prototype */{
-
- /**
- * The name of this Model
- * @type {string}
- * @readonly
- */
- type: "Collection",
-
- /**
- * Default attributes for CollectionModels
- * @type {Object}
- * @property {string[]} ignoreQueryGroups - Deprecated
- * @property {FilterGroup} definition - The parent-level Filter Group model that represents the collection definition.
- * @property {Filters} definitionFilters - A Filters collection that stores definition filters that have been serialized to the Collection. The same filters that are stored in the definition.
- * @property {Search} searchModel - A Search model with a Filters collection that contains the filters associated with this collection
- * @property {SolrResults} searchResults - A SolrResults collection that contains the filtered search results of datasets in this collection
- * @property {SolrResults} allSearchResults - A SolrResults collection that contains the unfiltered search results of all datasets in this collection
- */
- defaults: function(){
- return _.extend(DataONEObject.prototype.defaults(), {
- name: null,
- label: null,
- originalLabel: null,
- labelBlockList: ["new"],
- description: null,
- formatId: "https://purl.dataone.org/collections-1.1.0",
- formatType: "METADATA",
- type: "collection",
- definition: null,
- definitionFilters: null,
- searchModel: null,
- searchResults: new SolrResults(),
- allSearchResults: null
- });
- },
-
- /**
- * The default Backbone.Model.initialize() function
- */
- initialize: function(options){
-
- //Call the super class initialize function
- DataONEObject.prototype.initialize.call(this, options);
-
- this.listenToOnce(this.get("searchResults"), "sync", this.cacheSearchResults);
-
- //If the searchResults collection is replaced at any time, reset the listener
- this.off("change:searchResults")
- this.on("change:searchResults", function(searchResults){
- this.listenToOnce(this.get("searchResults"), "sync", this.cacheSearchResults);
- });
-
- // Update the search model when the definition filters are updated.
- // Definition filters may be updated by the user in the Query Builder,
- // or they may be updated automatically by this model (e.g. when adding
- // an isPartOf filter).
- this.off("change:definition");
- this.on("change:definition", function(){
- this.stopListening(this.get("definition"), "update change");
- this.listenTo(
- this.get("definition"),
- "update change",
- this.updateSearchModel
+ * @class CollectionModel
+ * @classdesc A collection of datasets, defined by one or more search filters
+ * @classcategory Models
+ * @name CollectionModel
+ * @extends DataONEObject
+ * @constructor
+ */
+ var CollectionModel = DataONEObject.extend(
+ /** @lends CollectionModel.prototype */ {
+ /**
+ * The name of this Model
+ * @type {string}
+ * @readonly
+ */
+ type: "Collection",
+
+ /**
+ * Default attributes for CollectionModels
+ * @type {Object}
+ * @property {string[]} ignoreQueryGroups - Deprecated
+ * @property {FilterGroup} definition - The parent-level Filter Group model that represents the collection definition.
+ * @property {Filters} definitionFilters - A Filters collection that stores definition filters that have been serialized to the Collection. The same filters that are stored in the definition.
+ * @property {Search} searchModel - A Search model with a Filters collection that contains the filters associated with this collection
+ * @property {SolrResults} searchResults - A SolrResults collection that contains the filtered search results of datasets in this collection
+ * @property {SolrResults} allSearchResults - A SolrResults collection that contains the unfiltered search results of all datasets in this collection
+ */
+ defaults: function () {
+ return _.extend(DataONEObject.prototype.defaults(), {
+ name: null,
+ label: null,
+ originalLabel: null,
+ labelBlockList: ["new"],
+ description: null,
+ formatId: "https://purl.dataone.org/collections-1.1.0",
+ formatType: "METADATA",
+ type: "collection",
+ definition: null,
+ definitionFilters: null,
+ searchModel: null,
+ searchResults: new SolrResults(),
+ allSearchResults: null,
+ });
+ },
+
+ /**
+ * The default Backbone.Model.initialize() function
+ */
+ initialize: function (options) {
+ //Call the super class initialize function
+ DataONEObject.prototype.initialize.call(this, options);
+
+ this.listenToOnce(
+ this.get("searchResults"),
+ "sync",
+ this.cacheSearchResults,
);
- }, this);
- //Create a search model
- this.set("searchModel", this.createSearchModel());
+ //If the searchResults collection is replaced at any time, reset the listener
+ this.off("change:searchResults");
+ this.on("change:searchResults", function (searchResults) {
+ this.listenToOnce(
+ this.get("searchResults"),
+ "sync",
+ this.cacheSearchResults,
+ );
+ });
- // Create a Filters collection to store the definition filters. Add the catalog
- // search query fragment to the definition Filter Group model.
- this.set("definition", new FilterGroup({ catalogSearch: true, nodeName: "definition" }));
- this.set("definitionFilters", this.get("definition").get("filters"));
+ // Update the search model when the definition filters are updated.
+ // Definition filters may be updated by the user in the Query Builder,
+ // or they may be updated automatically by this model (e.g. when adding
+ // an isPartOf filter).
+ this.off("change:definition");
+ this.on(
+ "change:definition",
+ function () {
+ this.stopListening(this.get("definition"), "update change");
+ this.listenTo(
+ this.get("definition"),
+ "update change",
+ this.updateSearchModel,
+ );
+ },
+ this,
+ );
- // Update the blocklist with the node/repository labels
- var nodeBlockList = MetacatUI.appModel.get("portalLabelBlockList");
- if (nodeBlockList !== undefined && Array.isArray(nodeBlockList)) {
- this.set("labelBlockList", this.get("labelBlockList").concat(nodeBlockList));
- }
+ //Create a search model
+ this.set("searchModel", this.createSearchModel());
- },
-
- /**
- * updateSearchModel - This function is called when any changes are made to
- * the definition filters. The function adds, removes, or updates models
- * in the Search Model filters when models are changed in the collection
- * Definition Filters.
- *
- * @param {Filter|Filters} model The model or collection that has been
- * changed (filter models) or updated (filters collection). This is ignored.
- * @param {object} record The data passed by backbone that indicates
- * which models have been added, removed, or updated. If the only change was
- * to a pre-existing model attribute, then the object will be empty.
- */
- updateSearchModel: function(model, record){
-
- try {
- var model = this;
-
- // Merge the updated definition Filter Group model with the Search Model collection.
- this.get("searchModel").get("filters").add(
- model.get("definition"),
- { merge: true }
+ // Create a Filters collection to store the definition filters. Add the catalog
+ // search query fragment to the definition Filter Group model.
+ this.set(
+ "definition",
+ new FilterGroup({ catalogSearch: true, nodeName: "definition" }),
);
+ this.set("definitionFilters", this.get("definition").get("filters"));
+
+ // Update the blocklist with the node/repository labels
+ var nodeBlockList = MetacatUI.appModel.get("portalLabelBlockList");
+ if (nodeBlockList !== undefined && Array.isArray(nodeBlockList)) {
+ this.set(
+ "labelBlockList",
+ this.get("labelBlockList").concat(nodeBlockList),
+ );
+ }
+ },
+
+ /**
+ * updateSearchModel - This function is called when any changes are made to
+ * the definition filters. The function adds, removes, or updates models
+ * in the Search Model filters when models are changed in the collection
+ * Definition Filters.
+ *
+ * @param {Filter|Filters} model The model or collection that has been
+ * changed (filter models) or updated (filters collection). This is ignored.
+ * @param {object} record The data passed by backbone that indicates
+ * which models have been added, removed, or updated. If the only change was
+ * to a pre-existing model attribute, then the object will be empty.
+ */
+ updateSearchModel: function (model, record) {
+ try {
+ var model = this;
+
+ // Merge the updated definition Filter Group model with the Search Model collection.
+ this.get("searchModel")
+ .get("filters")
+ .add(model.get("definition"), { merge: true });
+ } catch (e) {
+ console.log(
+ "Failed to update the Search Model collection when the " +
+ "Definition Model collection changed, error message: " +
+ e,
+ );
+ }
+ },
+
+ /**
+ *
+ *
+ */
+ url: function () {
+ return (
+ MetacatUI.appModel.get("objectServiceUrl") +
+ encodeURIComponent(this.get("id"))
+ );
+ },
+
+ /**
+ * Overrides the default Backbone.Model.fetch() function to provide some custom
+ * fetch options
+ *
+ */
+ fetch: function () {
+ var model = this;
- } catch (e) {
- console.log("Failed to update the Search Model collection when the " +
- "Definition Model collection changed, error message: " + e);
- }
- },
-
- /**
- *
- *
- */
- url: function(){
- return MetacatUI.appModel.get("objectServiceUrl") + encodeURIComponent(this.get("id"));
- },
+ var requestSettings = {
+ dataType: "xml",
+ error: function () {
+ model.trigger("error");
+ },
+ };
- /**
- * Overrides the default Backbone.Model.fetch() function to provide some custom
- * fetch options
- *
- */
- fetch: function(){
- var model = this;
-
- var requestSettings = {
- dataType: "xml",
- error: function(){
- model.trigger("error");
+ //Add the authorization header and other AJAX settings
+ requestSettings = _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
+ return Backbone.Model.prototype.fetch.call(this, requestSettings);
+ },
+
+ /**
+ * Sends an AJAX request to fetch the system metadata for this object.
+ * Will not trigger a sync event since it does not use Backbone.Model.fetch
+ */
+ fetchSystemMetadata: function (options) {
+ if (!options) var options = {};
+ else options = _.clone(options);
+
+ //Get the active alternative repository, if one is configured
+ var activeAltRepo = MetacatUI.appModel.getActiveAltRepo();
+
+ if (activeAltRepo) {
+ baseUrl = activeAltRepo.metaServiceUrl;
+ } else {
+ baseUrl = MetacatUI.appModel.get("metaServiceUrl");
}
- }
- //Add the authorization header and other AJAX settings
- requestSettings = _.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings());
- return Backbone.Model.prototype.fetch.call(this, requestSettings);
- },
+ //Exit if no base URL was found
+ if (!baseUrl) {
+ return;
+ }
- /**
- * Sends an AJAX request to fetch the system metadata for this object.
- * Will not trigger a sync event since it does not use Backbone.Model.fetch
- */
- fetchSystemMetadata: function(options){
-
- if(!options) var options = {};
- else options = _.clone(options);
-
- //Get the active alternative repository, if one is configured
- var activeAltRepo = MetacatUI.appModel.getActiveAltRepo();
-
- if( activeAltRepo ){
- baseUrl = activeAltRepo.metaServiceUrl;
- }
- else{
- baseUrl = MetacatUI.appModel.get("metaServiceUrl");
- }
-
- //Exit if no base URL was found
- if( !baseUrl ){
- return;
- }
-
- var model = this,
- fetchOptions = _.extend({
- url: baseUrl + encodeURIComponent(this.get("id") || this.get("seriesId")),
- dataType: "text",
- success: function(response){
- model.set(DataONEObject.prototype.parse.call(model, response));
- model.trigger("systemMetadataSync");
- },
- error: function(){
- model.trigger('error');
- }
- }, options);
+ var model = this,
+ fetchOptions = _.extend(
+ {
+ url:
+ baseUrl +
+ encodeURIComponent(this.get("id") || this.get("seriesId")),
+ dataType: "text",
+ success: function (response) {
+ model.set(DataONEObject.prototype.parse.call(model, response));
+ model.trigger("systemMetadataSync");
+ },
+ error: function () {
+ model.trigger("error");
+ },
+ },
+ options,
+ );
//Add the authorization header and other AJAX settings
_.extend(fetchOptions, MetacatUI.appUserModel.createAjaxSettings());
$.ajax(fetchOptions);
- },
-
- /**
- * Overrides the default Backbone.Model.parse() function to parse the custom
- * collection XML document
- *
- * @param {XMLDocument} response - The XMLDocument returned from the fetch() AJAX call
- * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
- */
- parse: function(json){
-
- //Start the empty JSON object
- var modelJSON = {},
+ },
+
+ /**
+ * Overrides the default Backbone.Model.parse() function to parse the custom
+ * collection XML document
+ *
+ * @param {XMLDocument} response - The XMLDocument returned from the fetch() AJAX call
+ * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
+ */
+ parse: function (json) {
+ //Start the empty JSON object
+ var modelJSON = {},
collectionNode;
- //Iterate over each root XML node to find the collection node
- $(response).children().each(function(i, el){
- if( el.tagName.indexOf("collection") > -1 ){
- collectionNode = el;
- return false;
+ //Iterate over each root XML node to find the collection node
+ $(response)
+ .children()
+ .each(function (i, el) {
+ if (el.tagName.indexOf("collection") > -1) {
+ collectionNode = el;
+ return false;
+ }
+ });
+
+ //If a collection XML node wasn't found, return an empty JSON object
+ if (typeof collectionNode == "undefined" || !collectionNode) return {};
+
+ //Parse the collection XML and return it
+ return this.parseCollectionXML(collectionNode);
+ },
+
+ /**
+ * Parses the collection XML into a JSON object
+ *
+ * @param {Element} rootNode - The XML Element that contains all the collection nodes
+ * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
+ */
+ parseCollectionXML: function (rootNode) {
+ // Get and save the namespace version number. It should be 1.0.0 or 1.1.0. Version
+ // 1.0.0 portals will be updated to 1.1.0 on save. We need to know which version
+ // while parsing to keep rendering of the old versions of collections/portals
+ // consistent with how they were rendered before MetacatUI was updated to handle
+ // 1.1.0 collections/portals - e.g. the fieldsOperator attribute in filters.
+ var namespace = rootNode.namespaceURI,
+ versionRegex = /\d\.\d\.\d$/g,
+ version = namespace.match(versionRegex);
+ if (version && version.length && version[0] != "") {
+ this.set("originalVersion", version[0]);
}
- });
- //If a collection XML node wasn't found, return an empty JSON object
- if( typeof collectionNode == "undefined" || !collectionNode )
- return {};
+ var modelJSON = {};
- //Parse the collection XML and return it
- return this.parseCollectionXML(collectionNode);
+ //Parse the simple text nodes
+ modelJSON.name = this.parseTextNode(rootNode, "name");
+ modelJSON.label = this.parseTextNode(rootNode, "label");
+ modelJSON.description = this.parseTextNode(rootNode, "description");
- },
+ //Create a Filters collection to contain the collection definition Filters
+ var definitionXML = rootNode.getElementsByTagName("definition")[0];
+ // Add the catalog search query fragment to the definition Filter Group model
+ modelJSON.definition = new FilterGroup({
+ objectDOM: definitionXML,
+ catalogSearch: true,
+ });
+ modelJSON.definitionFilters = modelJSON.definition.get("filters");
- /**
- * Parses the collection XML into a JSON object
- *
- * @param {Element} rootNode - The XML Element that contains all the collection nodes
- * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
- */
- parseCollectionXML: function( rootNode ){
-
- // Get and save the namespace version number. It should be 1.0.0 or 1.1.0. Version
- // 1.0.0 portals will be updated to 1.1.0 on save. We need to know which version
- // while parsing to keep rendering of the old versions of collections/portals
- // consistent with how they were rendered before MetacatUI was updated to handle
- // 1.1.0 collections/portals - e.g. the fieldsOperator attribute in filters.
- var namespace = rootNode.namespaceURI,
- versionRegex = /\d\.\d\.\d$/g,
- version = namespace.match(versionRegex);
- if(version && version.length && version[0] != ""){
- this.set("originalVersion", version[0])
- }
-
- var modelJSON = {};
-
- //Parse the simple text nodes
- modelJSON.name = this.parseTextNode(rootNode, "name");
- modelJSON.label = this.parseTextNode(rootNode, "label");
- modelJSON.description = this.parseTextNode(rootNode, "description");
-
- //Create a Filters collection to contain the collection definition Filters
- var definitionXML = rootNode.getElementsByTagName("definition")[0]
- // Add the catalog search query fragment to the definition Filter Group model
- modelJSON.definition = new FilterGroup({ objectDOM: definitionXML, catalogSearch: true });
- modelJSON.definitionFilters = modelJSON.definition.get("filters")
-
- //Create a Search model for this collection's filters
- modelJSON.searchModel = this.createSearchModel();
- // Add all the filters from the Collection definition to the search model as a single
- // Filter Group model.
- modelJSON.searchModel.get("filters").add(modelJSON.definition);
-
- // If we are parsing the first version of a collection or portal
- if(this.get("originalVersion") === "1.0.0"){
- modelJSON = this.updateTo110(modelJSON);
- }
-
- return modelJSON
- },
-
- /**
- * Takes parsed Collections 1.0.0 XML in JSON format and makes any changes required so
- * that collections are still represented in MetacatUI as they were before MetacatUI
- * was updated to support 1.1.0 Collections.
- * @param {JSON} modelJSON Parsed 1.0.0 Collections XML, in JSON
- * @return {JSON} The updated JSON, compatible with 1.1.0 changes
- */
- updateTo110: function(modelJSON){
- try {
- // For version 1.0.0 filters, MetacatUI used the "operator" attribute to set the
- // operator between both fields and values. In 1.1.0, we now have the
- // "fieldsOperator" attribute. (Since "AND" was the default, only the "OR"
- // operator is ever serialized). Therefore, if a version 1.0.0 filter has "OR" as
- // the operator, we should also set the "fieldOperator" to "OR".
- modelJSON.definitionFilters.each(function(filterModel){
- if(filterModel.get("operator") === "OR"){
- filterModel.set("fieldsOperator", "OR")
- }
- }, this);
- return modelJSON
- } catch (error) {
- console.log("Error trying to update a 1.0.0 Collection to 1.1.0 " +
- "returning the JSON unchanged. Error details: " + error );
- return modelJSON
- }
- },
+ //Create a Search model for this collection's filters
+ modelJSON.searchModel = this.createSearchModel();
+ // Add all the filters from the Collection definition to the search model as a single
+ // Filter Group model.
+ modelJSON.searchModel.get("filters").add(modelJSON.definition);
- /**
- * Generate a UUID, reserve it using the DataOne API, and set it on the model
- */
- reserveSeriesId: function(){
-
- // Create a new series ID
- var seriesId = "urn:uuid:" + uuid.v4();
-
- // Set the seriesId on the portal model right away, since reserving takes
- // time. This will be updated in the rare case that the first seriesId was
- // already taken.
- this.set("seriesId", seriesId);
-
- // Reserve a series ID for the new portal
- var model = this;
- var options = {
- url: MetacatUI.appModel.get("reserveServiceUrl"),
- type: "POST",
- data: { pid: seriesId },
- tryCount : 0,
- // If a generated seriesId is already reserved, how many times to retry
- retryLimit : 5,
- success: function(xhr){
- // If the first seriesId was taken, then update the model with the
- // successfully reserved seriesId.
- if(this.tryCount > 0) {
- model.set("seriesId", $(xhr).find("identifier").text());
- }
- },
- error : function(xhr) {
- // If the seriesId was already reserved, try again
- if (xhr.status == 409) {
+ // If we are parsing the first version of a collection or portal
+ if (this.get("originalVersion") === "1.0.0") {
+ modelJSON = this.updateTo110(modelJSON);
+ }
+
+ return modelJSON;
+ },
+
+ /**
+ * Takes parsed Collections 1.0.0 XML in JSON format and makes any changes required so
+ * that collections are still represented in MetacatUI as they were before MetacatUI
+ * was updated to support 1.1.0 Collections.
+ * @param {JSON} modelJSON Parsed 1.0.0 Collections XML, in JSON
+ * @return {JSON} The updated JSON, compatible with 1.1.0 changes
+ */
+ updateTo110: function (modelJSON) {
+ try {
+ // For version 1.0.0 filters, MetacatUI used the "operator" attribute to set the
+ // operator between both fields and values. In 1.1.0, we now have the
+ // "fieldsOperator" attribute. (Since "AND" was the default, only the "OR"
+ // operator is ever serialized). Therefore, if a version 1.0.0 filter has "OR" as
+ // the operator, we should also set the "fieldOperator" to "OR".
+ modelJSON.definitionFilters.each(function (filterModel) {
+ if (filterModel.get("operator") === "OR") {
+ filterModel.set("fieldsOperator", "OR");
+ }
+ }, this);
+ return modelJSON;
+ } catch (error) {
+ console.log(
+ "Error trying to update a 1.0.0 Collection to 1.1.0 " +
+ "returning the JSON unchanged. Error details: " +
+ error,
+ );
+ return modelJSON;
+ }
+ },
+
+ /**
+ * Generate a UUID, reserve it using the DataOne API, and set it on the model
+ */
+ reserveSeriesId: function () {
+ // Create a new series ID
+ var seriesId = "urn:uuid:" + uuid.v4();
+
+ // Set the seriesId on the portal model right away, since reserving takes
+ // time. This will be updated in the rare case that the first seriesId was
+ // already taken.
+ this.set("seriesId", seriesId);
+
+ // Reserve a series ID for the new portal
+ var model = this;
+ var options = {
+ url: MetacatUI.appModel.get("reserveServiceUrl"),
+ type: "POST",
+ data: { pid: seriesId },
+ tryCount: 0,
+ // If a generated seriesId is already reserved, how many times to retry
+ retryLimit: 5,
+ success: function (xhr) {
+ // If the first seriesId was taken, then update the model with the
+ // successfully reserved seriesId.
+ if (this.tryCount > 0) {
+ model.set("seriesId", $(xhr).find("identifier").text());
+ }
+ },
+ error: function (xhr) {
+ // If the seriesId was already reserved, try again
+ if (xhr.status == 409) {
this.tryCount++;
if (this.tryCount <= this.retryLimit) {
- // Generate another seriesId
- this.data = { pid:"urn:uuid:" + uuid.v4() };
- // Send the reserve request again
- $.ajax(this);
- return;
+ // Generate another seriesId
+ this.data = { pid: "urn:uuid:" + uuid.v4() };
+ // Send the reserve request again
+ $.ajax(this);
+ return;
}
return;
- // If the user isn't logged in, or doesn't have write access
- } else if (xhr.status = 401 ){
- model.set("isAuthorized", false);
- } else {
- parsedResponse = $(xhr.responseText).not("style, title").text();
- model.set("errorMessage", parsedResponse);
+ // If the user isn't logged in, or doesn't have write access
+ } else if ((xhr.status = 401)) {
+ model.set("isAuthorized", false);
+ } else {
+ parsedResponse = $(xhr.responseText).not("style, title").text();
+ model.set("errorMessage", parsedResponse);
+ }
+ },
+ };
+ _.extend(options, MetacatUI.appUserModel.createAjaxSettings());
+ $.ajax(options);
+ },
+
+ /**
+ * Creates a FilterModel with isPartOf as the field and this collection's
+ * seriesId as the value, then adds it to the definitionFilters collection.
+ * (which will also add it to the searchFilters collection)
+ * @param {string} [seriesId] - the seriesId of the collection or portal
+ * @return {Filter} The new isPartOf filter that was created
+ */
+ addIsPartOfFilter: function (seriesId) {
+ try {
+ // If no seriesId is given
+ if (!seriesId) {
+ // Use the seriesId set on the model
+ if (this.get("seriesId")) {
+ seriesId = this.get("seriesId");
+ // If there's no seriesId on the model, make and reserve one
+ } else {
+ //Create and reserve a new seriesId
+ this.reserveSeriesId();
+ seriesId = this.get("seriesId");
+
+ // Set a listener to create an isPartOf filter using the seriesId once
+ // the series Id is set. Just in case the first seriesId generated was
+ // already reserved, update the isPartOf filters on the subsequent
+ // attempts to create and resere an ID.
+ this.on("change:seriesId", function (seriesId) {
+ this.addIsPartOfFilter();
+ });
+ }
}
- }
- }
- _.extend(options, MetacatUI.appUserModel.createAjaxSettings());
- $.ajax(options);
- },
- /**
- * Creates a FilterModel with isPartOf as the field and this collection's
- * seriesId as the value, then adds it to the definitionFilters collection.
- * (which will also add it to the searchFilters collection)
- * @param {string} [seriesId] - the seriesId of the collection or portal
- * @return {Filter} The new isPartOf filter that was created
- */
- addIsPartOfFilter: function(seriesId){
-
- try {
- // If no seriesId is given
- if(!seriesId){
- // Use the seriesId set on the model
- if(this.get("seriesId")){
- seriesId = this.get("seriesId");
- // If there's no seriesId on the model, make and reserve one
+ // Create the new isPartOf filter attributes object
+ // NOTE: All other attributes are set in Filter.initialize();
+ var isPartOfAttributes = {
+ fields: ["isPartOf"],
+ values: [seriesId],
+ matchSubstring: false,
+ operator: "OR",
+ };
+
+ // Remove any existing isPartOf filters, and add the new isPartOf filter
+
+ // NOTE:
+ // 1. Changes to the definition filters will automatically update the
+ // Search Model filters because of the listener set in initialize().
+ // 2. Add the new Filter model by passing a list of attributes to the
+ // Filters collection, instead of by passing a new Filter, in order
+ // to trigger 'update' and 'change' events for other models and views.
+
+ this.get("definitionFilters").removeFiltersByField("isPartOf");
+ var filterModel =
+ this.get("definitionFilters").add(isPartOfAttributes);
+
+ return filterModel;
+ } catch (e) {
+ console.log(
+ "Failed to create and add a new isPartOf Filter, error message: " +
+ e,
+ );
+ }
+ },
+
+ /**
+ * Gets the text content of the XML node matching the given node name
+ *
+ * @param {Element} parentNode - The parent node to select from
+ * @param {string} nodeName - The name of the XML node to parse
+ * @param {boolean} isMultiple - If true, parses the nodes into an array
+ * @return {(string|Array)} - Returns a string or array of strings of the text content
+ */
+ parseTextNode: function (parentNode, nodeName, isMultiple) {
+ var node = $(parentNode).children(nodeName);
+
+ //If no matching nodes were found, return falsey values
+ if (!node || !node.length) {
+ //Return an empty array if the isMultiple flag is true
+ if (isMultiple) return [];
+ //Return null if the isMultiple flag is false
+ else return null;
+ }
+ //If exactly one node is found and we are only expecting one, return the text content
+ else if (node.length == 1 && !isMultiple) {
+ return node[0].textContent.trim();
+ }
+ //If more than one node is found, parse into an array
+ else {
+ return _.map(node, function (node) {
+ return node.textContent.trim() || null;
+ });
+ }
+ },
+
+ /**
+ * Updates collection XML with values in the collection model
+ *
+ * @param {XMLDocument} objectDOM the XML element to be updated
+ * @return {XMLElement} An updated XML element
+ */
+ updateCollectionDOM: function (objectDOM) {
+ // Get or make objectDOM
+ if (!objectDOM) {
+ if (this.get("objectDOM")) {
+ var objectDOM = this.get("objectDOM").cloneNode(true);
+ $(objectDOM).empty();
} else {
- //Create and reserve a new seriesId
- this.reserveSeriesId();
- seriesId = this.get("seriesId");
-
- // Set a listener to create an isPartOf filter using the seriesId once
- // the series Id is set. Just in case the first seriesId generated was
- // already reserved, update the isPartOf filters on the subsequent
- // attempts to create and resere an ID.
- this.on("change:seriesId", function(seriesId){
- this.addIsPartOfFilter();
- });
-
+ // create an XML collection element from scratch
+ var objectDOM = $(this.createXML()).children()[0];
}
}
- // Create the new isPartOf filter attributes object
- // NOTE: All other attributes are set in Filter.initialize();
- var isPartOfAttributes = {
- fields: ["isPartOf"],
- values: [seriesId],
- matchSubstring: false,
- operator: "OR"
- };
-
- // Remove any existing isPartOf filters, and add the new isPartOf filter
-
- // NOTE:
- // 1. Changes to the definition filters will automatically update the
- // Search Model filters because of the listener set in initialize().
- // 2. Add the new Filter model by passing a list of attributes to the
- // Filters collection, instead of by passing a new Filter, in order
- // to trigger 'update' and 'change' events for other models and views.
-
- this.get("definitionFilters").removeFiltersByField("isPartOf");
- var filterModel = this.get("definitionFilters").add(isPartOfAttributes);
-
- return filterModel
- } catch (e) {
- console.log("Failed to create and add a new isPartOf Filter, error message: " + e);
- }
-
- },
+ // Set schema version. May need to be updated from 1.0.0 to 1.1.0.
+ // The formatId is the same as the namespace URI.
+ var currentNamespace = this.defaults().formatId;
+
+ // The NS attribute name could be xmlns:por or xmlns:col
+ objectDOM.attributes.forEach(function (attr) {
+ if (attr.name.match(/^xmlns/)) {
+ if (attr.value !== currentNamespace) {
+ var newObjectDOM = this.createXML().documentElement;
+ while (objectDOM.firstChild) {
+ newObjectDOM.appendChild(objectDOM.firstChild);
+ }
+ objectDOM = newObjectDOM;
+ }
+ }
+ }, this);
- /**
- * Gets the text content of the XML node matching the given node name
- *
- * @param {Element} parentNode - The parent node to select from
- * @param {string} nodeName - The name of the XML node to parse
- * @param {boolean} isMultiple - If true, parses the nodes into an array
- * @return {(string|Array)} - Returns a string or array of strings of the text content
- */
- parseTextNode: function( parentNode, nodeName, isMultiple ){
- var node = $(parentNode).children(nodeName);
-
- //If no matching nodes were found, return falsey values
- if( !node || !node.length ){
-
- //Return an empty array if the isMultiple flag is true
- if( isMultiple )
- return [];
- //Return null if the isMultiple flag is false
- else
- return null;
- }
- //If exactly one node is found and we are only expecting one, return the text content
- else if( node.length == 1 && !isMultiple ){
- return node[0].textContent.trim();
- }
- //If more than one node is found, parse into an array
- else{
-
- return _.map(node, function(node){
- return node.textContent.trim() || null;
- });
+ // Remove definition node if it exists in XML already
+ $(objectDOM).find("definition").remove();
- }
- },
+ // Get the filters that are currently applied to the search.
+ var definitionSerialized = this.get("definition").updateDOM();
+ objectDOM.ownerDocument.adoptNode(definitionSerialized);
- /**
- * Updates collection XML with values in the collection model
- *
- * @param {XMLDocument} objectDOM the XML element to be updated
- * @return {XMLElement} An updated XML element
- */
- updateCollectionDOM: function(objectDOM){
-
- // Get or make objectDOM
- if(!objectDOM){
- if (this.get("objectDOM")) {
- var objectDOM = this.get("objectDOM").cloneNode(true);
- $(objectDOM).empty();
- } else {
- // create an XML collection element from scratch
- var objectDOM = $(this.createXML()).children()[0];
- }
- }
-
- // Set schema version. May need to be updated from 1.0.0 to 1.1.0.
- // The formatId is the same as the namespace URI.
- var currentNamespace = this.defaults().formatId;
-
- // The NS attribute name could be xmlns:por or xmlns:col
- objectDOM.attributes.forEach(function(attr){
- if(attr.name.match(/^xmlns/)){
- if(attr.value !== currentNamespace){
- var newObjectDOM = this.createXML().documentElement;
- while (objectDOM.firstChild) {
- newObjectDOM.appendChild(objectDOM.firstChild);
- }
- objectDOM = newObjectDOM
- }
- }
- }, this);
-
- // Remove definition node if it exists in XML already
- $(objectDOM).find("definition").remove();
-
- // Get the filters that are currently applied to the search.
- var definitionSerialized = this.get("definition").updateDOM();
- objectDOM.ownerDocument.adoptNode(definitionSerialized);
-
- //If at least one filter was serialized, add the node
- if (definitionSerialized.childNodes.length) {
- $(objectDOM).prepend(definitionSerialized);
- }
-
- // Get and update the simple text strings (everything but definition)
- // in reverse order because we prepend them consecutively to objectDOM
- var collectionTextData = {
- description: this.get("description"),
- name: this.get("name"),
- label: this.get("label")
- }
-
- _.map(collectionTextData, function(value, nodeName){
-
- // Remove the node if it exists
- // Use children() and not find() as there are sub-children named label
- $(objectDOM).children(nodeName).remove();
-
- // Don't serialize falsey values
- if(value && (typeof value == "string" && value.trim().length)){
- // Make new sub-node
- var collectionSubnodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
- $(collectionSubnodeSerialized).text(value);
- // Append new sub-node to the start of the objectDOM
- $(objectDOM).prepend(collectionSubnodeSerialized);
+ //If at least one filter was serialized, add the node
+ if (definitionSerialized.childNodes.length) {
+ $(objectDOM).prepend(definitionSerialized);
}
- });
-
- return objectDOM;
+ // Get and update the simple text strings (everything but definition)
+ // in reverse order because we prepend them consecutively to objectDOM
+ var collectionTextData = {
+ description: this.get("description"),
+ name: this.get("name"),
+ label: this.get("label"),
+ };
- },
+ _.map(collectionTextData, function (value, nodeName) {
+ // Remove the node if it exists
+ // Use children() and not find() as there are sub-children named label
+ $(objectDOM).children(nodeName).remove();
+
+ // Don't serialize falsey values
+ if (value && typeof value == "string" && value.trim().length) {
+ // Make new sub-node
+ var collectionSubnodeSerialized =
+ objectDOM.ownerDocument.createElement(nodeName);
+ $(collectionSubnodeSerialized).text(value);
+ // Append new sub-node to the start of the objectDOM
+ $(objectDOM).prepend(collectionSubnodeSerialized);
+ }
+ });
- /**
- * Initialize the object XML for a brand spankin' new collection
- * @return {Element}
- */
- createXML: function() {
+ return objectDOM;
+ },
- var xmlString = " ",
+ /**
+ * Initialize the object XML for a brand spankin' new collection
+ * @return {Element}
+ */
+ createXML: function () {
+ var xmlString =
+ ' ',
xmlNew = $.parseXML(xmlString),
colNode = xmlNew.getElementsByTagName("col:collections")[0];
- // set attributes
- colNode.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
- colNode.setAttribute("xsi:schemaLocation", "https://purl.dataone.org/collections-1.1.0");
-
- this.set("ownerDocument", colNode.ownerDocument);
-
- return(xmlNew);
- },
-
- /**
- * Creates a new instance of a Search model with a Filters collection.
- * The Search model is created and returned and NOT set directly on the model in
- * this function, because this function is called during parse(), when we're not ready
- * to set attributes directly on the model yet.
- * @return {Search}
- */
- createSearchModel: function(){
- var search = new Search();
- // Do not set "catalogSearch" to true, even though the search model is specifically
- // created in order to do a catalog search. Instead, we set the definition
- // filterGroup model catalogSearch = true. This allows us to append the query
- // fragment with ID fields AFTER the catalog query fragment.
- search.set("filters", new Filters());
- return search;
- },
-
- /**
- * This is a shortcut function that returns the query for the datasets in this portal,
- * using the Search model for this portal. This is the full query that includes the filters not
- * serialized to the portal XML, such as the filters used for the DataCatalogView.
- *
- */
- getQuery: function(){
-
- return this.get("definition").getQuery();
-
- },
-
- /**
- * Creates a copy of the SolrResults collection and saves it in this
- * model so that there is always access to the unfiltered list of datasets
- *
- * @param {SolrResults} searchResults - The SolrResults collection to cache
- */
- cacheSearchResults: function(searchResults){
-
- //Save a copy of the SolrResults so that we always have a copy of
- // the unfiltered list of datasets
- this.set("allSearchResults", searchResults.clone());
-
- //Make a copy of the facetCounts object
- var allSearchResults = this.get("allSearchResults");
- allSearchResults.facetCounts = Object.assign({}, searchResults.facetCounts);
-
- },
+ // set attributes
+ colNode.setAttribute(
+ "xmlns:xsi",
+ "http://www.w3.org/2001/XMLSchema-instance",
+ );
+ colNode.setAttribute(
+ "xsi:schemaLocation",
+ "https://purl.dataone.org/collections-1.1.0",
+ );
- /**
- * Overrides the default Backbone.Model.validate.function() to
- * check if this portal model has all the required values necessary
- * to save to the server.
- *
- * @param {Object} [attrs] - A literal object of model attributes to validate.
- * @param {Object} [options] - A literal object of options for this validation process
- * @return {Object} If there are errors, an object comprising error
- * messages. If no errors, returns nothing.
- */
- validate: function(attrs, options) {
-
- try{
-
- var errors = {};
-
- // Validate label
- var labelError = this.validateLabel(this.get("label"));
- if( labelError ){
- errors.label = labelError;
- }
+ this.set("ownerDocument", colNode.ownerDocument);
+
+ return xmlNew;
+ },
+
+ /**
+ * Creates a new instance of a Search model with a Filters collection.
+ * The Search model is created and returned and NOT set directly on the model in
+ * this function, because this function is called during parse(), when we're not ready
+ * to set attributes directly on the model yet.
+ * @return {Search}
+ */
+ createSearchModel: function () {
+ var search = new Search();
+ // Do not set "catalogSearch" to true, even though the search model is specifically
+ // created in order to do a catalog search. Instead, we set the definition
+ // filterGroup model catalogSearch = true. This allows us to append the query
+ // fragment with ID fields AFTER the catalog query fragment.
+ search.set("filters", new Filters());
+ return search;
+ },
+
+ /**
+ * This is a shortcut function that returns the query for the datasets in this portal,
+ * using the Search model for this portal. This is the full query that includes the filters not
+ * serialized to the portal XML, such as the filters used for the DataCatalogView.
+ *
+ */
+ getQuery: function () {
+ return this.get("definition").getQuery();
+ },
+
+ /**
+ * Creates a copy of the SolrResults collection and saves it in this
+ * model so that there is always access to the unfiltered list of datasets
+ *
+ * @param {SolrResults} searchResults - The SolrResults collection to cache
+ */
+ cacheSearchResults: function (searchResults) {
+ //Save a copy of the SolrResults so that we always have a copy of
+ // the unfiltered list of datasets
+ this.set("allSearchResults", searchResults.clone());
+
+ //Make a copy of the facetCounts object
+ var allSearchResults = this.get("allSearchResults");
+ allSearchResults.facetCounts = Object.assign(
+ {},
+ searchResults.facetCounts,
+ );
+ },
+
+ /**
+ * Overrides the default Backbone.Model.validate.function() to
+ * check if this portal model has all the required values necessary
+ * to save to the server.
+ *
+ * @param {Object} [attrs] - A literal object of model attributes to validate.
+ * @param {Object} [options] - A literal object of options for this validation process
+ * @return {Object} If there are errors, an object comprising error
+ * messages. If no errors, returns nothing.
+ */
+ validate: function (attrs, options) {
+ try {
+ var errors = {};
+
+ // Validate label
+ var labelError = this.validateLabel(this.get("label"));
+ if (labelError) {
+ errors.label = labelError;
+ }
- // Validate the definition
- var definitionError = this.get("definition").validate(attrs, options);
+ // Validate the definition
+ var definitionError = this.get("definition").validate(attrs, options);
+
+ if (definitionError) {
+ if (definitionError.noFilters) {
+ type = this.type.toLowerCase();
+ errors.definition =
+ "Your dataset collection hasn't been created. Add at " +
+ "least one query rule below to find datasets for this " +
+ type +
+ ". For example, to create a " +
+ type +
+ " for datasets from a specific " +
+ "research project, try using the project name field.";
+ } else {
+ // Just show the first error for now.
+ errors.definition = Object.values(definitionError)[0];
+ }
+ }
- if(definitionError){
- if(definitionError.noFilters){
- type = this.type.toLowerCase();
- errors.definition = "Your dataset collection hasn't been created. Add at " +
- "least one query rule below to find datasets for this " + type +
- ". For example, to create a " + type + " for datasets from a specific " +
- "research project, try using the project name field.";
+ if (Object.keys(errors).length) {
+ console.log(errors);
+ return errors;
} else {
- // Just show the first error for now.
- errors.definition = Object.values(definitionError)[0]
+ return;
}
+ } catch (e) {
+ console.error(e);
}
-
- if( Object.keys(errors).length ) {
- console.log(errors);
- return errors;
- } else {
- return;
- }
-
- }
- catch(e){
- console.error(e);
- }
-
- },
-
- /**
- * Checks that a label does not equal a restricted value
- * (e.g. new temporary name), and that it's encoded properly
- * for use as part of a url
- *
- * @param {string} label - The label to be validated
- * @return {string} - If the label is invalid, an error message string is returned
- */
- validateLabel: function(label){
-
- try{
-
- //Validate the label set on the model if one isn't given
- if(typeof label != "string" ){
- var label = this.get("label");
- }
-
- //If the label is not a string or is an empty string
- if( typeof label != "string" || !label.trim().length ){
- //Convert numbers to strings
- if(typeof label == "number"){
- label = label.toString();
+ },
+
+ /**
+ * Checks that a label does not equal a restricted value
+ * (e.g. new temporary name), and that it's encoded properly
+ * for use as part of a url
+ *
+ * @param {string} label - The label to be validated
+ * @return {string} - If the label is invalid, an error message string is returned
+ */
+ validateLabel: function (label) {
+ try {
+ //Validate the label set on the model if one isn't given
+ if (typeof label != "string") {
+ var label = this.get("label");
}
- else{
- var type = this.type.toLowerCase();
- return "Please choose a name for this " + type + " to use in the URL.";
+
+ //If the label is not a string or is an empty string
+ if (typeof label != "string" || !label.trim().length) {
+ //Convert numbers to strings
+ if (typeof label == "number") {
+ label = label.toString();
+ } else {
+ var type = this.type.toLowerCase();
+ return (
+ "Please choose a name for this " + type + " to use in the URL."
+ );
+ }
}
- }
- // If the label is a restricted string
- var blockList = this.get("labelBlockList");
- if( blockList && Array.isArray(blockList) ){
- if(blockList.includes(label)){
- return "This URL is already taken, please try something else";
+ // If the label is a restricted string
+ var blockList = this.get("labelBlockList");
+ if (blockList && Array.isArray(blockList)) {
+ if (blockList.includes(label)) {
+ return "This URL is already taken, please try something else";
+ }
}
- }
- // If the label includes illegal characters
- // (Only allow letters, numbers, underscores and dashes)
- if(label.match(/[^A-Za-z0-9_-]/g)){
- return "URLs may only contain letters, numbers, underscores (_), and dashes (-).";
+ // If the label includes illegal characters
+ // (Only allow letters, numbers, underscores and dashes)
+ if (label.match(/[^A-Za-z0-9_-]/g)) {
+ return "URLs may only contain letters, numbers, underscores (_), and dashes (-).";
+ }
+ } catch (e) {
+ //Trigger an error event
+ this.trigger("errorValidatingLabel");
+ console.error(e);
}
+ },
+ },
+ );
- }
- catch(e){
- //Trigger an error event
- this.trigger("errorValidatingLabel");
- console.error(e);
- }
-
- }
-
- });
-
- return CollectionModel;
+ return CollectionModel;
});
diff --git a/src/js/models/DataONEObject.js b/src/js/models/DataONEObject.js
index 668f4b4f3..dcc5134b1 100644
--- a/src/js/models/DataONEObject.js
+++ b/src/js/models/DataONEObject.js
@@ -1,8 +1,15 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'uuid', 'he', 'collections/AccessPolicy', 'collections/ObjectFormats', 'md5'],
- function($, _, Backbone, uuid, he, AccessPolicy, ObjectFormats, md5){
-
- /**
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "uuid",
+ "he",
+ "collections/AccessPolicy",
+ "collections/ObjectFormats",
+ "md5",
+], function ($, _, Backbone, uuid, he, AccessPolicy, ObjectFormats, md5) {
+ /**
* @class DataONEObject
* @classdesc A DataONEObject represents a DataONE object, such as a data file,
a science metadata object, or a resource map. It stores the system
@@ -13,2416 +20,2623 @@ define(['jquery', 'underscore', 'backbone', 'uuid', 'he', 'collections/AccessPol
* @classcategory Models
* @augments Backbone.Model
*/
- var DataONEObject = Backbone.Model.extend(
- /** @lends DataONEObject.prototype */{
-
- type: "DataONEObject",
- selectedInEditor: false, // Has this package member been selected and displayed in the provenance editor?
- PROV: "http://www.w3.org/ns/prov#",
- PROVONE: "http://purl.dataone.org/provone/2015/01/15/ontology#",
-
- defaults: function(){
- return{
- // System Metadata attributes
- serialVersion: null,
- identifier: null,
- formatId: null,
- size: null,
- checksum: null,
- originalChecksum: null,
- checksumAlgorithm: "MD5",
- submitter: null,
- rightsHolder : null,
- accessPolicy: [], //An array of accessPolicy literal JS objects
- replicationAllowed: null,
- replicationPolicy: [],
- obsoletes: null,
- obsoletedBy: null,
- archived: null,
- dateUploaded: null,
- dateSysMetadataModified: null,
- originMemberNode: null,
- authoritativeMemberNode: null,
- replica: [],
- seriesId: null, // uuid.v4(), (decide if we want to auto-set this)
- mediaType: null,
- fileName: null,
- // Non-system metadata attributes:
- isNew: null,
- datasource: null,
- insert_count_i: null,
- read_count_i: null,
- changePermission: null,
- writePermission: null,
- readPermission: null,
- isPublic: null,
- dateModified: null,
- id: "urn:uuid:" + uuid.v4(),
- sizeStr: null,
- type: "", // Data, Metadata, or DataPackage
- formatType: "",
- metadataEntity: null, // A model that represents the metadata for this file, e.g. an EMLEntity model
- latestVersion: null,
- isDocumentedBy: null,
- documents: [],
- members: [],
- resourceMap: [],
- nodeLevel: 0, // Indicates hierarchy level in the view for indentation
- sortOrder: 2, // Metadata: 1, Data: 2, DataPackage: 3
- synced: false, // True if the full model has been synced
- uploadStatus: null, //c=complete, p=in progress, q=queued, e=error, w=warning, no upload status=not in queue
- uploadProgress: null,
- sysMetaUploadStatus: null, //c=complete, p=in progress, q=queued, e=error, l=loading, no upload status=not in queue
- percentLoaded: 0, // Percent the file is read before caclculating the md5 sum
- uploadFile: null, // The file reference to be uploaded (JS object: File)
- errorMessage: null,
- sysMetaErrorCode: null, // The status code given when there is an error updating the system metadata
- numSaveAttempts: 0,
- notFound: false, //Whether or not this object was found in the system
- originalAttrs: [], // An array of original attributes in a DataONEObject
- changed: false, // If any attributes have been changed, including attrs in nested objects
- hasContentChanges: false, // If attributes outside of originalAttrs have been changed
- sysMetaXML: null, // A cached original version of the fetched system metadata document
- objectXML: null, // A cached version of the object fetched from the server
- isAuthorized: null, // If the stated permission is authorized by the user
- isAuthorized_read: null, //If the user has permission to read
- isAuthorized_write: null, //If the user has permission to write
- isAuthorized_changePermission: null, //If the user has permission to changePermission
- createSeriesId: false, //If true, a seriesId will be created when this object is saved.
- collections: [], //References to collections that this model is in
- possibleAuthMNs: [], //A list of possible authoritative MNs of this object
- useAltRepo: false,
- isLoadingFiles: false, //Only relevant to Resource Map objects. Is true if there is at least one file still loading into the package.
- numLoadingFiles: 0, //Only relevant to Resource Map objects. The number of files still loading into the package.
- provSources: [],
- provDerivations: [],
- prov_generated: [],
- prov_generatedByExecution: [],
- prov_generatedByProgram: [],
- prov_generatedByUser: [],
- prov_hasDerivations: [],
- prov_hasSources: [],
- prov_instanceOfClass: [],
- prov_used: [],
- prov_usedByExecution: [],
- prov_usedByProgram: [],
- prov_usedByUser: [],
- prov_wasDerivedFrom: [],
- prov_wasExecutedByExecution: [],
- prov_wasExecutedByUser: [],
- prov_wasInformedBy: []
- }
- },
-
- initialize: function(attrs, options) {
- if(typeof attrs == "undefined") var attrs = {};
+ var DataONEObject = Backbone.Model.extend(
+ /** @lends DataONEObject.prototype */ {
+ type: "DataONEObject",
+ selectedInEditor: false, // Has this package member been selected and displayed in the provenance editor?
+ PROV: "http://www.w3.org/ns/prov#",
+ PROVONE: "http://purl.dataone.org/provone/2015/01/15/ontology#",
+
+ defaults: function () {
+ return {
+ // System Metadata attributes
+ serialVersion: null,
+ identifier: null,
+ formatId: null,
+ size: null,
+ checksum: null,
+ originalChecksum: null,
+ checksumAlgorithm: "MD5",
+ submitter: null,
+ rightsHolder: null,
+ accessPolicy: [], //An array of accessPolicy literal JS objects
+ replicationAllowed: null,
+ replicationPolicy: [],
+ obsoletes: null,
+ obsoletedBy: null,
+ archived: null,
+ dateUploaded: null,
+ dateSysMetadataModified: null,
+ originMemberNode: null,
+ authoritativeMemberNode: null,
+ replica: [],
+ seriesId: null, // uuid.v4(), (decide if we want to auto-set this)
+ mediaType: null,
+ fileName: null,
+ // Non-system metadata attributes:
+ isNew: null,
+ datasource: null,
+ insert_count_i: null,
+ read_count_i: null,
+ changePermission: null,
+ writePermission: null,
+ readPermission: null,
+ isPublic: null,
+ dateModified: null,
+ id: "urn:uuid:" + uuid.v4(),
+ sizeStr: null,
+ type: "", // Data, Metadata, or DataPackage
+ formatType: "",
+ metadataEntity: null, // A model that represents the metadata for this file, e.g. an EMLEntity model
+ latestVersion: null,
+ isDocumentedBy: null,
+ documents: [],
+ members: [],
+ resourceMap: [],
+ nodeLevel: 0, // Indicates hierarchy level in the view for indentation
+ sortOrder: 2, // Metadata: 1, Data: 2, DataPackage: 3
+ synced: false, // True if the full model has been synced
+ uploadStatus: null, //c=complete, p=in progress, q=queued, e=error, w=warning, no upload status=not in queue
+ uploadProgress: null,
+ sysMetaUploadStatus: null, //c=complete, p=in progress, q=queued, e=error, l=loading, no upload status=not in queue
+ percentLoaded: 0, // Percent the file is read before caclculating the md5 sum
+ uploadFile: null, // The file reference to be uploaded (JS object: File)
+ errorMessage: null,
+ sysMetaErrorCode: null, // The status code given when there is an error updating the system metadata
+ numSaveAttempts: 0,
+ notFound: false, //Whether or not this object was found in the system
+ originalAttrs: [], // An array of original attributes in a DataONEObject
+ changed: false, // If any attributes have been changed, including attrs in nested objects
+ hasContentChanges: false, // If attributes outside of originalAttrs have been changed
+ sysMetaXML: null, // A cached original version of the fetched system metadata document
+ objectXML: null, // A cached version of the object fetched from the server
+ isAuthorized: null, // If the stated permission is authorized by the user
+ isAuthorized_read: null, //If the user has permission to read
+ isAuthorized_write: null, //If the user has permission to write
+ isAuthorized_changePermission: null, //If the user has permission to changePermission
+ createSeriesId: false, //If true, a seriesId will be created when this object is saved.
+ collections: [], //References to collections that this model is in
+ possibleAuthMNs: [], //A list of possible authoritative MNs of this object
+ useAltRepo: false,
+ isLoadingFiles: false, //Only relevant to Resource Map objects. Is true if there is at least one file still loading into the package.
+ numLoadingFiles: 0, //Only relevant to Resource Map objects. The number of files still loading into the package.
+ provSources: [],
+ provDerivations: [],
+ prov_generated: [],
+ prov_generatedByExecution: [],
+ prov_generatedByProgram: [],
+ prov_generatedByUser: [],
+ prov_hasDerivations: [],
+ prov_hasSources: [],
+ prov_instanceOfClass: [],
+ prov_used: [],
+ prov_usedByExecution: [],
+ prov_usedByProgram: [],
+ prov_usedByUser: [],
+ prov_wasDerivedFrom: [],
+ prov_wasExecutedByExecution: [],
+ prov_wasExecutedByUser: [],
+ prov_wasInformedBy: [],
+ };
+ },
+
+ initialize: function (attrs, options) {
+ if (typeof attrs == "undefined") var attrs = {};
+
+ this.set("accessPolicy", this.createAccessPolicy());
+
+ this.on("change:size", this.bytesToSize);
+ if (attrs.size) this.bytesToSize();
+
+ // Cache an array of original attribute names to help in handleChange()
+ if (this.type == "DataONEObject")
+ this.set("originalAttrs", Object.keys(this.attributes));
+ else
+ this.set(
+ "originalAttrs",
+ Object.keys(DataONEObject.prototype.defaults()),
+ );
+
+ this.on("successSaving", this.updateRelationships);
+
+ //Save a reference to this DataONEObject model in the metadataEntity model
+ //whenever the metadataEntity is set
+ this.on("change:metadataEntity", function () {
+ var entityMetadataModel = this.get("metadataEntity");
+
+ if (entityMetadataModel)
+ entityMetadataModel.set("dataONEObject", this);
+ });
+
+ this.on("sync", function () {
+ this.set("synced", true);
+ });
+
+ //Find Member Node object that might be the authoritative MN
+ //This is helpful when MetacatUI may be displaying content from multiple MNs
+ this.setPossibleAuthMNs();
+ },
- this.set("accessPolicy", this.createAccessPolicy());
-
- this.on("change:size", this.bytesToSize);
- if(attrs.size)
- this.bytesToSize();
+ /**
+ * Maps the lower-case sys meta node names (valid in HTML DOM) to the
+ * camel-cased sys meta node names (valid in DataONE).
+ * Used during parse() and serialize()
+ */
+ nodeNameMap: function () {
+ return {
+ accesspolicy: "accessPolicy",
+ accessrule: "accessRule",
+ authoritativemembernode: "authoritativeMemberNode",
+ checksumalgorithm: "checksumAlgorithm",
+ dateuploaded: "dateUploaded",
+ datesysmetadatamodified: "dateSysMetadataModified",
+ formatid: "formatId",
+ filename: "fileName",
+ nodereference: "nodeReference",
+ numberreplicas: "numberReplicas",
+ obsoletedby: "obsoletedBy",
+ originmembernode: "originMemberNode",
+ replicamembernode: "replicaMemberNode",
+ replicationallowed: "replicationAllowed",
+ replicationpolicy: "replicationPolicy",
+ replicationstatus: "replicationStatus",
+ replicaverified: "replicaVerified",
+ rightsholder: "rightsHolder",
+ serialversion: "serialVersion",
+ seriesid: "seriesId",
+ };
+ },
- // Cache an array of original attribute names to help in handleChange()
- if(this.type == "DataONEObject")
- this.set("originalAttrs", Object.keys(this.attributes));
- else
- this.set("originalAttrs", Object.keys(DataONEObject.prototype.defaults()));
+ /**
+ * Returns the URL string where this DataONEObject can be fetched from or saved to
+ * @returns {string}
+ */
+ url: function () {
+ // With no id, we can't do anything
+ if (!this.get("id") && !this.get("seriesid")) return "";
+
+ //Get the active alternative repository, if one is configured
+ var activeAltRepo = MetacatUI.appModel.getActiveAltRepo();
+
+ //Start the base URL string
+ var baseUrl = "";
+
+ // Determine if we're updating a new/existing object,
+ // or just its system metadata
+ // New uploads use the object service URL
+ if (this.isNew()) {
+ //Use the object service URL from the alt repo
+ if (this.get("useAltRepo") && activeAltRepo) {
+ baseUrl = activeAltRepo.objectServiceUrl;
+ }
+ //If this MetacatUI deployment is pointing to a MN, use the object service URL from the AppModel
+ else {
+ baseUrl = MetacatUI.appModel.get("objectServiceUrl");
+ }
- this.on("successSaving", this.updateRelationships);
+ //Return the full URL
+ return baseUrl;
+ } else {
+ if (this.hasUpdates()) {
+ if (this.get("hasContentChanges")) {
+ //Use the object service URL from the alt repo
+ if (this.get("useAltRepo") && activeAltRepo) {
+ baseUrl = activeAltRepo.objectServiceUrl;
+ } else {
+ baseUrl = MetacatUI.appModel.get("objectServiceUrl");
+ }
- //Save a reference to this DataONEObject model in the metadataEntity model
- //whenever the metadataEntity is set
- this.on("change:metadataEntity", function(){
- var entityMetadataModel = this.get("metadataEntity");
+ // Exists on the server, use MN.update()
+ return baseUrl + encodeURIComponent(this.get("oldPid"));
+ } else {
+ //Use the meta service URL from the alt repo
+ if (this.get("useAltRepo") && activeAltRepo) {
+ baseUrl = activeAltRepo.metaServiceUrl;
+ } else {
+ baseUrl = MetacatUI.appModel.get("metaServiceUrl");
+ }
- if( entityMetadataModel )
- entityMetadataModel.set("dataONEObject", this);
+ // Exists on the server, use MN.updateSystemMetadata()
+ return baseUrl + encodeURIComponent(this.get("id"));
+ }
+ } else {
+ //Use the meta service URL from the alt repo
+ if (this.get("useAltRepo") && activeAltRepo) {
+ baseUrl = activeAltRepo.metaServiceUrl;
+ } else {
+ baseUrl = MetacatUI.appModel.get("metaServiceUrl");
+ }
- });
+ // Use MN.getSystemMetadata()
+ return (
+ baseUrl +
+ (encodeURIComponent(this.get("id")) ||
+ encodeURIComponent(this.get("seriesid")))
+ );
+ }
+ }
+ },
- this.on("sync", function(){
- this.set("synced", true);
+ /**
+ * Create the URL string that is used to download this package
+ * @returns PackageURL string for this DataONE Object
+ * @since 2.28.0
+ */
+ getPackageURL: function () {
+ var url = null;
+
+ // With no id, we can't do anything
+ if (!this.get("id") && !this.get("seriesid")) return url;
+
+ //If we haven't set a packageServiceURL upon app initialization and we are querying a CN, then the packageServiceURL is dependent on the MN this package is from
+ if (
+ MetacatUI.appModel.get("d1Service").toLowerCase().indexOf("cn/") >
+ -1 &&
+ MetacatUI.nodeModel.get("members").length
+ ) {
+ var source = this.get("datasource"),
+ node = _.find(MetacatUI.nodeModel.get("members"), {
+ identifier: source,
});
- //Find Member Node object that might be the authoritative MN
- //This is helpful when MetacatUI may be displaying content from multiple MNs
- this.setPossibleAuthMNs();
-
- },
-
- /**
- * Maps the lower-case sys meta node names (valid in HTML DOM) to the
- * camel-cased sys meta node names (valid in DataONE).
- * Used during parse() and serialize()
- */
- nodeNameMap: function(){
- return{
- accesspolicy: "accessPolicy",
- accessrule: "accessRule",
- authoritativemembernode: "authoritativeMemberNode",
- checksumalgorithm: "checksumAlgorithm",
- dateuploaded: "dateUploaded",
- datesysmetadatamodified: "dateSysMetadataModified",
- formatid: "formatId",
- filename: "fileName",
- nodereference: "nodeReference",
- numberreplicas: "numberReplicas",
- obsoletedby: "obsoletedBy",
- originmembernode: "originMemberNode",
- replicamembernode: "replicaMemberNode",
- replicationallowed: "replicationAllowed",
- replicationpolicy: "replicationPolicy",
- replicationstatus: "replicationStatus",
- replicaverified: "replicaVerified",
- rightsholder: "rightsHolder",
- serialversion: "serialVersion",
- seriesid: "seriesId"
- };
- },
-
- /**
- * Returns the URL string where this DataONEObject can be fetched from or saved to
- * @returns {string}
- */
- url: function(){
-
- // With no id, we can't do anything
- if( !this.get("id") && !this.get("seriesid") )
- return "";
+ //If this node has MNRead v2 services...
+ if (node && node.readv2)
+ url =
+ node.baseURL +
+ "/v2/packages/application%2Fbagit-097/" +
+ encodeURIComponent(this.get("id"));
+ } else if (MetacatUI.appModel.get("packageServiceUrl"))
+ url =
+ MetacatUI.appModel.get("packageServiceUrl") +
+ encodeURIComponent(this.get("id"));
- //Get the active alternative repository, if one is configured
- var activeAltRepo = MetacatUI.appModel.getActiveAltRepo();
+ return url;
+ },
- //Start the base URL string
- var baseUrl = "";
-
- // Determine if we're updating a new/existing object,
- // or just its system metadata
- // New uploads use the object service URL
- if ( this.isNew() ) {
+ /**
+ * Overload Backbone.Model.fetch, so that we can set custom options for each fetch() request
+ */
+ fetch: function (options) {
+ if (!options) var options = {};
+ else var options = _.clone(options);
+
+ options.url = this.url();
+
+ //If we are using the Solr service to retrieve info about this object, then construct a query
+ if (typeof options != "undefined" && options.solrService) {
+ //Get basic information
+ var query = "";
+
+ //Do not search for seriesId when it is not configured in this model/app
+ if (typeof this.get("seriesid") === "undefined")
+ query += 'id:"' + encodeURIComponent(this.get("id")) + '"';
+ //If there is no seriesid set, then search for pid or sid
+ else if (!this.get("seriesid"))
+ query +=
+ '(id:"' +
+ encodeURIComponent(this.get("id")) +
+ '" OR seriesId:"' +
+ encodeURIComponent(this.get("id")) +
+ '")';
+ //If a seriesId is specified, then search for that
+ else if (this.get("seriesid") && this.get("id").length > 0)
+ query +=
+ '(seriesId:"' +
+ encodeURIComponent(this.get("seriesid")) +
+ '" AND id:"' +
+ encodeURIComponent(this.get("id")) +
+ '")';
+ //If only a seriesId is specified, then just search for the most recent version
+ else if (this.get("seriesid") && !this.get("id"))
+ query +=
+ 'seriesId:"' +
+ encodeURIComponent(this.get("id")) +
+ '" -obsoletedBy:*';
+
+ //The fields to return
+ var fl = "formatId,formatType,documents,isDocumentedBy,id,seriesId";
+
+ //Use the Solr query URL
+ var solrOptions = {
+ url:
+ MetacatUI.appModel.get("queryServiceUrl") +
+ "q=" +
+ query +
+ "&fl=" +
+ fl +
+ "&wt=json",
+ };
+
+ //Merge with the options passed to this function
+ var fetchOptions = _.extend(options, solrOptions);
+ } else if (typeof options != "undefined") {
+ //Use custom options for retreiving XML
+ //Merge with the options passed to this function
+ var fetchOptions = _.extend(
+ {
+ dataType: "text",
+ },
+ options,
+ );
+ } else {
+ //Use custom options for retreiving XML
+ var fetchOptions = _.extend({
+ dataType: "text",
+ });
+ }
- //Use the object service URL from the alt repo
- if( this.get("useAltRepo") && activeAltRepo ){
- baseUrl = activeAltRepo.objectServiceUrl;
- }
- //If this MetacatUI deployment is pointing to a MN, use the object service URL from the AppModel
- else{
- baseUrl = MetacatUI.appModel.get("objectServiceUrl");
- }
+ //Add the authorization options
+ fetchOptions = _.extend(
+ fetchOptions,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
- //Return the full URL
- return baseUrl;
+ //Call Backbone.Model.fetch to retrieve the info
+ return Backbone.Model.prototype.fetch.call(this, fetchOptions);
+ },
+ /**
+ * This function is called by Backbone.Model.fetch.
+ * It deserializes the incoming XML from the /meta REST endpoint and converts it into JSON.
+ */
+ parse: function (response) {
+ // If the response is XML
+ if (typeof response == "string" && response.indexOf("<") == 0) {
+ var responseDoc = $.parseHTML(response),
+ systemMetadata;
+
+ //Save the raw XML in case it needs to be used later
+ this.set("sysMetaXML", response);
+
+ //Find the XML node for the system metadata
+ for (var i = 0; i < responseDoc.length; i++) {
+ if (
+ responseDoc[i].nodeType == 1 &&
+ responseDoc[i].localName.indexOf("systemmetadata") > -1
+ ) {
+ systemMetadata = responseDoc[i];
+ break;
}
- else {
- if ( this.hasUpdates() ) {
- if ( this.get("hasContentChanges") ) {
+ }
- //Use the object service URL from the alt repo
- if( this.get("useAltRepo") && activeAltRepo ){
- baseUrl = activeAltRepo.objectServiceUrl;
- }
- else{
- baseUrl = MetacatUI.appModel.get("objectServiceUrl");
- }
+ //Parse the XML to JSON
+ var sysMetaValues = this.toJson(systemMetadata);
+
+ //Convert the JSON to a camel-cased version, which matches Solr and is easier to work with in code
+ _.each(
+ Object.keys(sysMetaValues),
+ function (key) {
+ var camelCasedKey = this.nodeNameMap()[key];
+ if (camelCasedKey) {
+ sysMetaValues[camelCasedKey] = sysMetaValues[key];
+ delete sysMetaValues[key];
+ }
+ },
+ this,
+ );
+
+ //Save the checksum from the system metadata in a separate attribute on the model
+ sysMetaValues.originalChecksum = sysMetaValues.checksum;
+ sysMetaValues.checksum = this.defaults().checksum;
+
+ //Save the identifier as the id attribute
+ sysMetaValues.id = sysMetaValues.identifier;
+
+ //Parse the Access Policy
+ if (
+ this.get("accessPolicy") &&
+ AccessPolicy.prototype.isPrototypeOf(this.get("accessPolicy"))
+ ) {
+ this.get("accessPolicy").parse(
+ $(systemMetadata).find("accesspolicy"),
+ );
+ sysMetaValues.accessPolicy = this.get("accessPolicy");
+ } else {
+ //Create a new AccessPolicy collection, if there isn't one already.
+ sysMetaValues.accessPolicy = this.createAccessPolicy(
+ $(systemMetadata).find("accesspolicy"),
+ );
+ }
- // Exists on the server, use MN.update()
- return baseUrl + (encodeURIComponent(this.get("oldPid")));
+ return sysMetaValues;
+
+ // If the response is a list of Solr docs
+ } else if (
+ typeof response === "object" &&
+ response.response &&
+ response.response.docs
+ ) {
+ //If no objects were found in the index, mark as notFound and exit
+ if (!response.response.docs.length) {
+ this.set("notFound", true);
+ this.trigger("notFound");
+ return;
+ }
- } else {
+ //Get the Solr document (there should be only one)
+ var doc = response.response.docs[0];
- //Use the meta service URL from the alt repo
- if( this.get("useAltRepo") && activeAltRepo ){
- baseUrl = activeAltRepo.metaServiceUrl;
- }
- else{
- baseUrl = MetacatUI.appModel.get("metaServiceUrl");
- }
+ //Take out any empty values
+ _.each(Object.keys(doc), function (field) {
+ if (!doc[field] && doc[field] !== 0) delete doc[field];
+ });
- // Exists on the server, use MN.updateSystemMetadata()
- return baseUrl + (encodeURIComponent(this.get("id")));
+ //Remove any erroneous white space from fields
+ this.removeWhiteSpaceFromSolrFields(doc);
- }
- } else {
- //Use the meta service URL from the alt repo
- if( this.get("useAltRepo") && activeAltRepo ){
- baseUrl = activeAltRepo.metaServiceUrl;
- }
- else{
- baseUrl = MetacatUI.appModel.get("metaServiceUrl");
- }
+ return doc;
+ }
+ // Default to returning the raw response
+ else return response;
+ },
- // Use MN.getSystemMetadata()
- return baseUrl +
- (encodeURIComponent(this.get("id")) ||
- encodeURIComponent(this.get("seriesid")));
- }
- }
- },
+ /** A utility function for converting XML to JSON */
+ toJson: function (xml) {
+ // Create the return object
+ var obj = {};
- /**
- * Create the URL string that is used to download this package
- * @returns PackageURL string for this DataONE Object
- * @since 2.28.0
- */
- getPackageURL: function(){
- var url = null;
+ // do children
+ if (xml.hasChildNodes()) {
+ for (var i = 0; i < xml.childNodes.length; i++) {
+ var item = xml.childNodes.item(i);
- // With no id, we can't do anything
- if( !this.get("id") && !this.get("seriesid") )
- return url;
+ //If it's an empty text node, skip it
+ if (item.nodeType == 3 && !item.nodeValue.trim()) continue;
- //If we haven't set a packageServiceURL upon app initialization and we are querying a CN, then the packageServiceURL is dependent on the MN this package is from
- if((MetacatUI.appModel.get("d1Service").toLowerCase().indexOf("cn/") > -1) && MetacatUI.nodeModel.get("members").length){
- var source = this.get("datasource"),
- node = _.find(MetacatUI.nodeModel.get("members"), {identifier: source});
+ //Get the node name
+ var nodeName = item.localName;
- //If this node has MNRead v2 services...
- if(node && node.readv2)
- url = node.baseURL + "/v2/packages/application%2Fbagit-097/" + encodeURIComponent(this.get("id"));
+ //If it's a new container node, convert it to JSON and add as a new object attribute
+ if (typeof obj[nodeName] == "undefined" && item.nodeType == 1) {
+ obj[nodeName] = this.toJson(item);
}
- else if(MetacatUI.appModel.get("packageServiceUrl"))
- url = MetacatUI.appModel.get("packageServiceUrl") + encodeURIComponent(this.get("id"));
-
- return url;
- },
-
- /**
- * Overload Backbone.Model.fetch, so that we can set custom options for each fetch() request
- */
- fetch: function(options){
-
- if ( ! options ) var options = {};
- else var options = _.clone(options);
-
- options.url = this.url();
-
- //If we are using the Solr service to retrieve info about this object, then construct a query
- if((typeof options != "undefined") && options.solrService){
-
- //Get basic information
- var query = "";
-
- //Do not search for seriesId when it is not configured in this model/app
- if(typeof this.get("seriesid") === "undefined")
- query += 'id:"' + encodeURIComponent(this.get("id")) + '"';
- //If there is no seriesid set, then search for pid or sid
- else if(!this.get("seriesid"))
- query += '(id:"' + encodeURIComponent(this.get("id")) + '" OR seriesId:"' + encodeURIComponent(this.get("id")) + '")';
- //If a seriesId is specified, then search for that
- else if(this.get("seriesid") && (this.get("id").length > 0))
- query += '(seriesId:"' + encodeURIComponent(this.get("seriesid")) + '" AND id:"' + encodeURIComponent(this.get("id")) + '")';
- //If only a seriesId is specified, then just search for the most recent version
- else if(this.get("seriesid") && !this.get("id"))
- query += 'seriesId:"' + encodeURIComponent(this.get("id")) + '" -obsoletedBy:*';
-
- //The fields to return
- var fl = "formatId,formatType,documents,isDocumentedBy,id,seriesId";
-
- //Use the Solr query URL
- var solrOptions = {
- url: MetacatUI.appModel.get("queryServiceUrl") + 'q=' + query + "&fl=" + fl + "&wt=json"
+ //If it's a new text node, just store the text value and add as a new object attribute
+ else if (
+ typeof obj[nodeName] == "undefined" &&
+ item.nodeType == 3
+ ) {
+ obj =
+ item.nodeValue == "false"
+ ? false
+ : item.nodeValue == "true"
+ ? true
+ : item.nodeValue;
+ }
+ //If this node name is already stored as an object attribute...
+ else if (typeof obj[nodeName] != "undefined") {
+ //Cache what we have now
+ var old = obj[nodeName];
+ if (!Array.isArray(old)) old = [old];
+
+ //Create a new object to store this node info
+ var newNode = {};
+
+ //Add the new node info to the existing array we have now
+ if (item.nodeType == 1) {
+ newNode = this.toJson(item);
+ var newArray = old.concat(newNode);
+ } else if (item.nodeType == 3) {
+ newNode = item.nodeValue;
+ var newArray = old.concat(newNode);
}
- //Merge with the options passed to this function
- var fetchOptions = _.extend(options, solrOptions);
- }
- else if(typeof options != "undefined"){
- //Use custom options for retreiving XML
- //Merge with the options passed to this function
- var fetchOptions = _.extend({
- dataType: "text"
- }, options);
- }
- else{
- //Use custom options for retreiving XML
- var fetchOptions = _.extend({
- dataType: "text"
+ //Store the attributes for this node
+ _.each(item.attributes, function (attr) {
+ newNode[attr.localName] = attr.nodeValue;
});
+
+ //Replace the old array with the updated one
+ obj[nodeName] = newArray;
+
+ //Exit
+ continue;
}
- //Add the authorization options
- fetchOptions = _.extend(fetchOptions, MetacatUI.appUserModel.createAjaxSettings());
+ //Store the attributes for this node
+ /*_.each(item.attributes, function(attr){
+ obj[nodeName][attr.localName] = attr.nodeValue;
+ });*/
+ }
+ }
+ return obj;
+ },
- //Call Backbone.Model.fetch to retrieve the info
- return Backbone.Model.prototype.fetch.call(this, fetchOptions);
+ /**
+ Serialize the DataONE object JSON to XML
+ @param {object} json - the JSON object to convert to XML
+ @param {Element} containerNode - an HTML element to insertt the resulting XML into
+ @returns {Element} The updated HTML Element
+ */
+ toXML: function (json, containerNode) {
+ if (typeof json == "string") {
+ containerNode.textContent = json;
+ return containerNode;
+ }
- },
+ for (var i = 0; i < Object.keys(json).length; i++) {
+ var key = Object.keys(json)[i],
+ contents = json[key] || json[key];
- /**
- * This function is called by Backbone.Model.fetch.
- * It deserializes the incoming XML from the /meta REST endpoint and converts it into JSON.
- */
- parse: function(response){
+ var node = document.createElement(key);
- // If the response is XML
- if( (typeof response == "string") && response.indexOf("<") == 0 ) {
+ //Skip this attribute if it is not populated
+ if (!contents || (Array.isArray(contents) && !contents.length))
+ continue;
- var responseDoc = $.parseHTML(response),
- systemMetadata;
+ //If it's a simple text node
+ if (typeof contents == "string") {
+ containerNode.textContent = contents;
+ return containerNode;
+ } else if (Array.isArray(contents)) {
+ var allNewNodes = [];
- //Save the raw XML in case it needs to be used later
- this.set("sysMetaXML", response);
+ for (var ii = 0; ii < contents.length; ii++) {
+ allNewNodes.push(this.toXML(contents[ii], $(node).clone()[0]));
+ }
- //Find the XML node for the system metadata
- for(var i=0; i -1)){
- systemMetadata = responseDoc[i];
- break;
- }
- }
+ if (allNewNodes.length) node = allNewNodes;
+ } else if (typeof contents == "object") {
+ $(node).append(this.toXML(contents, node));
+ var attributeNames = _.without(Object.keys(json[key]), "content");
+ }
- //Parse the XML to JSON
- var sysMetaValues = this.toJson(systemMetadata);
+ $(containerNode).append(node);
+ }
- //Convert the JSON to a camel-cased version, which matches Solr and is easier to work with in code
- _.each(Object.keys(sysMetaValues), function(key){
- var camelCasedKey = this.nodeNameMap()[key];
- if(camelCasedKey){
- sysMetaValues[camelCasedKey] = sysMetaValues[key];
- delete sysMetaValues[key];
- }
- }, this);
+ return containerNode;
+ },
- //Save the checksum from the system metadata in a separate attribute on the model
- sysMetaValues.originalChecksum = sysMetaValues.checksum;
- sysMetaValues.checksum = this.defaults().checksum;
+ /**
+ * Saves the DataONEObject System Metadata to the server
+ */
+ save: function (attributes, options) {
+ // Set missing file names before saving
+ if (!this.get("fileName")) {
+ this.setMissingFileName();
+ } else {
+ //Replace all non-alphanumeric characters with underscores
+ var fileNameWithoutExt = this.get("fileName").substring(
+ 0,
+ this.get("fileName").lastIndexOf("."),
+ ),
+ extension = this.get("fileName").substring(
+ this.get("fileName").lastIndexOf("."),
+ this.get("fileName").length,
+ );
+ this.set(
+ "fileName",
+ fileNameWithoutExt.replace(/[^a-zA-Z0-9]/g, "_") + extension,
+ );
+ }
- //Save the identifier as the id attribute
- sysMetaValues.id = sysMetaValues.identifier;
+ if (!this.hasUpdates()) {
+ this.set("uploadStatus", null);
+ return;
+ }
- //Parse the Access Policy
- if( this.get("accessPolicy") && AccessPolicy.prototype.isPrototypeOf(this.get("accessPolicy")) ){
- this.get("accessPolicy").parse($(systemMetadata).find("accesspolicy"));
- sysMetaValues.accessPolicy = this.get("accessPolicy");
- }
- else{
- //Create a new AccessPolicy collection, if there isn't one already.
- sysMetaValues.accessPolicy = this.createAccessPolicy($(systemMetadata).find("accesspolicy"));
- }
+ //Set the upload transfer as in progress
+ this.set("uploadProgress", 2);
+ this.set("uploadStatus", "p");
+
+ //Check if the checksum has been calculated yet.
+ if (!this.get("checksum")) {
+ //When it is calculated, restart this function
+ this.on("checksumCalculated", this.save);
+ //Calculate the checksum for this file
+ this.calculateChecksum();
+ //Exit this function until the checksum is done
+ return;
+ }
- return sysMetaValues;
+ //Create a FormData object to send data with our XHR
+ var formData = new FormData();
+
+ //If this is not a new object, update the id. New DataONEObjects will have an id
+ // created during initialize.
+ if (!this.isNew()) {
+ this.updateID();
+ formData.append("pid", this.get("oldPid"));
+ formData.append("newPid", this.get("id"));
+ } else {
+ //Create an ID if there isn't one
+ if (!this.get("id")) {
+ this.set("id", "urn:uuid:" + uuid.v4());
+ }
- // If the response is a list of Solr docs
- } else if (( typeof response === "object") && (response.response && response.response.docs)){
+ //Add the identifier to the XHR data
+ formData.append("pid", this.get("id"));
+ }
- //If no objects were found in the index, mark as notFound and exit
- if(!response.response.docs.length){
- this.set("notFound", true);
- this.trigger("notFound");
- return;
- }
+ //Create the system metadata XML
+ var sysMetaXML = this.serializeSysMeta();
- //Get the Solr document (there should be only one)
- var doc = response.response.docs[0];
+ //Send the system metadata as a Blob
+ var xmlBlob = new Blob([sysMetaXML], { type: "application/xml" });
+ //Add the system metadata XML to the XHR data
+ formData.append("sysmeta", xmlBlob, "sysmeta.xml");
- //Take out any empty values
- _.each(Object.keys(doc), function(field){
- if( !doc[field] && doc[field] !== 0 )
- delete doc[field];
- });
+ // Create the new object (MN.create())
+ formData.append("object", this.get("uploadFile"), this.get("fileName"));
- //Remove any erroneous white space from fields
- this.removeWhiteSpaceFromSolrFields(doc);
+ var model = this;
- return doc;
+ // On create(), add to the package and the metadata
+ // Note: This should be added to the parent collection
+ // but for now we are using the root collection
+ _.each(
+ this.get("collections"),
+ function (collection) {
+ if (collection.type == "DataPackage") {
+ this.off("successSaving", collection.addNewModel);
+ this.once("successSaving", collection.addNewModel, collection);
}
- else
- // Default to returning the raw response
- return response;
},
+ this,
+ );
+
+ //Put together the AJAX and Backbone.save() options
+ var requestSettings = {
+ url: this.url(),
+ cache: false,
+ contentType: false,
+ dataType: "text",
+ processData: false,
+ data: formData,
+ parse: false,
+ xhr: function () {
+ var xhr = new window.XMLHttpRequest();
+
+ //Upload progress
+ xhr.upload.addEventListener(
+ "progress",
+ function (evt) {
+ if (evt.lengthComputable) {
+ var percentComplete = (evt.loaded / evt.total) * 100;
+
+ model.set("uploadProgress", percentComplete);
+ }
+ },
+ false,
+ );
- /** A utility function for converting XML to JSON */
- toJson: function(xml) {
-
- // Create the return object
- var obj = {};
-
- // do children
- if (xml.hasChildNodes()) {
+ return xhr;
+ },
+ success: this.onSuccessfulSave,
+ error: function (model, response, xhr) {
+ //Reset the identifier changes
+ model.resetID();
+ //Reset the checksum, if this is a model that needs to be serialized with each save.
+ if (model.serialize) {
+ model.set("checksum", model.defaults().checksum);
+ }
- for(var i = 0; i < xml.childNodes.length; i++) {
- var item = xml.childNodes.item(i);
+ model.set("numSaveAttempts", model.get("numSaveAttempts") + 1);
+ var numSaveAttempts = model.get("numSaveAttempts");
+
+ if (
+ numSaveAttempts < 3 &&
+ (response.status == 408 || response.status == 0)
+ ) {
+ //Try saving again in 10, 40, and 90 seconds
+ setTimeout(
+ function () {
+ model.save.call(model);
+ },
+ numSaveAttempts * numSaveAttempts * 10000,
+ );
+ } else {
+ model.set("numSaveAttempts", 0);
- //If it's an empty text node, skip it
- if((item.nodeType == 3) && (!item.nodeValue.trim()))
- continue;
+ var parsedResponse = $(response.responseText)
+ .not("style, title")
+ .text();
- //Get the node name
- var nodeName = item.localName;
+ //When there is no network connection (status == 0), there will be no response text
+ if (!parsedResponse)
+ parsedResponse =
+ "There was a network issue that prevented this file from uploading. " +
+ "Make sure you are connected to a reliable internet connection.";
- //If it's a new container node, convert it to JSON and add as a new object attribute
- if((typeof(obj[nodeName]) == "undefined") && (item.nodeType == 1)) {
- obj[nodeName] = this.toJson(item);
- }
- //If it's a new text node, just store the text value and add as a new object attribute
- else if((typeof(obj[nodeName]) == "undefined") && (item.nodeType == 3)){
- obj = item.nodeValue == "false" ? false : item.nodeValue == "true" ? true : item.nodeValue;
- }
- //If this node name is already stored as an object attribute...
- else if(typeof(obj[nodeName]) != "undefined"){
-
- //Cache what we have now
- var old = obj[nodeName];
- if(!Array.isArray(old))
- old = [old];
-
- //Create a new object to store this node info
- var newNode = {};
-
- //Add the new node info to the existing array we have now
- if(item.nodeType == 1){
- newNode = this.toJson(item);
- var newArray = old.concat(newNode);
- }
- else if(item.nodeType == 3){
- newNode = item.nodeValue;
- var newArray = old.concat(newNode);
- }
-
- //Store the attributes for this node
- _.each(item.attributes, function(attr){
- newNode[attr.localName] = attr.nodeValue;
- });
-
- //Replace the old array with the updated one
- obj[nodeName] = newArray;
-
- //Exit
- continue;
- }
+ model.set("errorMessage", parsedResponse);
- //Store the attributes for this node
- /*_.each(item.attributes, function(attr){
- obj[nodeName][attr.localName] = attr.nodeValue;
- });*/
+ //Set the model status as e for error
+ model.set("uploadStatus", "e");
- }
+ //Trigger a custom event for the model save error
+ model.trigger("errorSaving", parsedResponse);
+ // Track this error in our analytics
+ MetacatUI.analytics?.trackException(
+ `DataONEObject save error: ${parsedResponse}`,
+ model.get("id"),
+ true,
+ );
}
- return obj;
},
+ };
- /**
- Serialize the DataONE object JSON to XML
- @param {object} json - the JSON object to convert to XML
- @param {Element} containerNode - an HTML element to insertt the resulting XML into
- @returns {Element} The updated HTML Element
- */
- toXML: function(json, containerNode){
-
- if(typeof json == "string"){
- containerNode.textContent = json;
- return containerNode;
- }
+ //Add the user settings
+ requestSettings = _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
- for(var i=0; i", "g");
+ xmlString = xmlString.replace(regEx, nodeNameMap[name] + ">");
- // Track this error in our analytics
- MetacatUI.analytics?.trackException(
- `DataONEObject update system metadata ` +
- `error: ${parsedResponse}`,
- model.get("id"),
- true
- );
- }
- }
+ //If node names haven't been changed, then find an attribute
+ if (xmlString == originalXMLString) {
+ var regEx = new RegExp(" " + name + "=", "g");
+ xmlString = xmlString.replace(
+ regEx,
+ " " + nodeNameMap[name] + "=",
+ );
}
-
- //Add the user settings
- requestSettings = _.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings());
-
- //Send the XHR
- $.ajax(requestSettings);
},
+ this,
+ );
- /**
- * Check if the current user is authorized to perform an action on this object. This function doesn't return
- * the result of the check, but it sends an XHR, updates this model, and triggers a change event.
- * @param {string} [action=changePermission] - The action (read, write, or changePermission) to check
- * if the current user has authorization to perform. By default checks for the highest level of permission.
- * @param {object} [options] Additional options for this function. See the properties below.
- * @property {function} options.onSuccess - A function to execute when the checkAuthority API is successfully completed
- * @property {function} options.onError - A function to execute when the checkAuthority API returns an error, or when no PID or SID can be found for this object.
- * @return {boolean}
- */
- checkAuthority: function(action = "changePermission", options){
-
- try{
- // return false - if neither PID nor SID is present to check the authority
- if ( (this.get("id") == null) && (this.get("seriesId") == null) ) {
- return false;
- }
+ xmlString = xmlString.replace(/systemmetadata/g, "systemMetadata");
- if( typeof options == "undefined" ){
- var options = {};
- }
+ return xmlString;
+ },
- // If onError or onSuccess options were provided by the user,
- // check that they are functions first, so we don't try to use
- // some other type of variable as a function later on.
- ["onError", "onSuccess"].forEach(function(userFunction){
- if(typeof options[userFunction] !== "function"){
- options[userFunction] = null;
- }
- });
+ /**
+ * Get the object format identifier for this object
+ */
+ getFormatId: function () {
+ var formatId = "application/octet-stream", // default to untyped data
+ objectFormats = {
+ mediaTypes: [], // The list of potential formatIds based on mediaType matches
+ extensions: [], // The list of possible formatIds based onextension matches
+ },
+ fileName = this.get("fileName"), // the fileName for this object
+ ext; // The extension of the filename for this object
+
+ objectFormats["mediaTypes"] = MetacatUI.objectFormats.where({
+ formatId: this.get("mediaType"),
+ });
+ if (
+ typeof fileName !== "undefined" &&
+ fileName !== null &&
+ fileName.length > 1
+ ) {
+ ext = fileName.substring(
+ fileName.lastIndexOf(".") + 1,
+ fileName.length,
+ );
+ objectFormats["extensions"] = MetacatUI.objectFormats.where({
+ extension: ext,
+ });
+ }
- // If PID is not present - check authority with seriesId
- var identifier = this.get("id");
- if ( (identifier == null) ) {
- identifier = this.get("seriesId");
- }
+ if (
+ objectFormats["mediaTypes"].length > 0 &&
+ objectFormats["extensions"].length > 0
+ ) {
+ var firstMediaType = objectFormats["mediaTypes"][0].get("formatId");
+ var firstExtension = objectFormats["extensions"][0].get("formatId");
+ // Check if they're equal
+ if (firstMediaType === firstExtension) {
+ formatId = firstMediaType;
+ return formatId;
+ }
+ // Handle mismatched mediaType and extension cases - additional cases can be added below
+ if (
+ firstMediaType === "application/vnd.ms-excel" &&
+ firstExtension === "text/csv"
+ ) {
+ formatId = firstExtension;
+ return formatId;
+ }
+ }
- //If there are alt repositories configured, find the possible authoritative
- // Member Node for this DataONEObject.
- if( MetacatUI.appModel.get("alternateRepositories").length ){
+ if (objectFormats["mediaTypes"].length > 0) {
+ formatId = objectFormats["mediaTypes"][0].get("formatId");
+ console.log("returning default mediaType");
+ console.log(formatId);
+ return formatId;
+ }
- //Get the array of possible authoritative MNs
- var possibleAuthMNs = this.get("possibleAuthMNs");
+ if (objectFormats["extensions"].length > 0) {
+ //If this is a "nc" file, assume it is a netCDF-3 file.
+ if (ext == "nc") {
+ formatId = "netCDF-3";
+ } else {
+ formatId = objectFormats["extensions"][0].get("formatId");
+ }
+ return formatId;
+ }
- //If there are no possible authoritative MNs, use the auth service URL from the AppModel
- if( !possibleAuthMNs.length ){
- baseUrl = MetacatUI.appModel.get("authServiceUrl");
- }
- else{
- //Use the auth service URL from the top possible auth MN
- baseUrl = possibleAuthMNs[0].authServiceUrl;
- }
+ return formatId;
+ },
- }
- else{
- //Get the auth service URL from the AppModel
- baseUrl = MetacatUI.appModel.get("authServiceUrl");
- }
+ /**
+ * Looks up human readable format of the DataONE Object
+ * @returns format String
+ * @since 2.28.0
+ */
+ getFormat: function () {
+ var formatMap = {
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
+ "Microsoft Excel OpenXML",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
+ "Microsoft Word OpenXML",
+ "application/vnd.ms-excel.sheet.binary.macroEnabled.12":
+ "Microsoft Office Excel 2007 binary workbooks",
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation":
+ "Microsoft Office OpenXML Presentation",
+ "application/vnd.ms-excel": "Microsoft Excel",
+ "application/msword": "Microsoft Word",
+ "application/vnd.ms-powerpoint": "Microsoft Powerpoint",
+ "text/html": "HTML",
+ "text/plain": "plain text (.txt)",
+ "video/avi": "Microsoft AVI file",
+ "video/x-ms-wmv": "Windows Media Video (.wmv)",
+ "audio/x-ms-wma": "Windows Media Audio (.wma)",
+ "application/vnd.google-earth.kml xml":
+ "Google Earth Keyhole Markup Language (KML)",
+ "http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html":
+ "annotation",
+ "application/mathematica": "Mathematica Notebook",
+ "application/postscript": "Postscript",
+ "application/rtf": "Rich Text Format (RTF)",
+ "application/xml": "XML Application",
+ "text/xml": "XML",
+ "application/x-fasta": "FASTA sequence file",
+ "nexus/1997": "NEXUS File Format for Systematic Information",
+ "anvl/erc-v02":
+ "Kernel Metadata and Electronic Resource Citations (ERCs), 2010.05.13",
+ "http://purl.org/dryad/terms/":
+ "Dryad Metadata Application Profile Version 3.0",
+ "http://datadryad.org/profile/v3.1":
+ "Dryad Metadata Application Profile Version 3.1",
+ "application/pdf": "PDF",
+ "application/zip": "ZIP file",
+ "http://www.w3.org/TR/rdf-syntax-grammar": "RDF/XML",
+ "http://www.w3.org/TR/rdfa-syntax": "RDFa",
+ "application/rdf xml": "RDF",
+ "text/turtle": "TURTLE",
+ "text/n3": "N3",
+ "application/x-gzip": "GZIP Format",
+ "application/x-python": "Python script",
+ "http://www.w3.org/2005/Atom": "ATOM-1.0",
+ "application/octet-stream": "octet stream (application file)",
+ "http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd":
+ "Darwin Core, v2.0",
+ "http://rs.tdwg.org/dwc/xsd/simpledarwincore/": "Simple Darwin Core",
+ "eml://ecoinformatics.org/eml-2.1.0": "EML v2.1.0",
+ "eml://ecoinformatics.org/eml-2.1.1": "EML v2.1.1",
+ "eml://ecoinformatics.org/eml-2.0.1": "EML v2.0.1",
+ "eml://ecoinformatics.org/eml-2.0.0": "EML v2.0.0",
+ "https://eml.ecoinformatics.org/eml-2.2.0": "EML v2.2.0",
+ };
+
+ return formatMap[this.get("formatId")] || this.get("formatId");
+ },
- if( !baseUrl ){
- return false;
- }
+ /**
+ * Build a fresh system metadata document for this object when it is new
+ * Return it as a DOM object
+ */
+ createSysMeta: function () {
+ var sysmetaDOM, // The DOM
+ sysmetaXML = []; // The document as a string array
+
+ sysmetaXML.push(
+ //'',
+ "',
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ );
+
+ sysmetaDOM = $($.parseHTML(sysmetaXML.join("")));
+ return sysmetaDOM;
+ },
- var onSuccess = options.onSuccess || function(data, textStatus, xhr) {
- model.set("isAuthorized_" + action, true);
- model.set("isAuthorized", true);
- model.trigger("change:isAuthorized");
- },
- onError = options.onError || function(xhr, textStatus, errorThrown){
- if(errorThrown == 404){
- var possibleAuthMNs = model.get("possibleAuthMNs");
- if( possibleAuthMNs.length ){
- //Remove the first MN from the array, since it didn't contain the object, so it's not the auth MN
- possibleAuthMNs.shift();
- }
-
- //If there are no other possible auth MNs to check, trigger this model as Not Found.
- if( possibleAuthMNs.length == 0 || !possibleAuthMNs ){
- model.set("notFound", true);
- model.trigger("notFound");
- }
- //If there's more MNs to check, try again
- else{
- model.checkAuthority(action, options);
- }
- }
- else{
- model.set("isAuthorized_" + action, false);
- model.set("isAuthorized", false);
- }
- };
-
- var model = this;
- var requestSettings = {
- url: baseUrl + encodeURIComponent(identifier) + "?action=" + action,
- type: "GET",
- success: onSuccess,
- error: onError
+ /**
+ * Create an access policy for this DataONEObject using the default access
+ * policy set in the AppModel.
+ *
+ * @param {Element} [accessPolicyXML] - An XML node
+ * that contains a list of access rules.
+ * @return {AccessPolicy} - an AccessPolicy collection that represents the
+ * given XML or the default policy set in the AppModel.
+ */
+ createAccessPolicy: function (accessPolicyXML) {
+ //Create a new AccessPolicy collection
+ var accessPolicy = new AccessPolicy();
+
+ accessPolicy.dataONEObject = this;
+
+ //If there is no access policy XML sent,
+ if (this.isNew() && !accessPolicyXML) {
+ try {
+ //If the app is configured to inherit the access policy from the parent metadata,
+ // then get the parent metadata and copy it's AccessPolicy
+ let scienceMetadata = this.get("isDocumentedByModels");
+ if (
+ MetacatUI.appModel.get("inheritAccessPolicy") &&
+ scienceMetadata &&
+ scienceMetadata.length
+ ) {
+ let sciMetaAccessPolicy = scienceMetadata[0].get("accessPolicy");
+
+ if (sciMetaAccessPolicy) {
+ accessPolicy.copyAccessPolicy(sciMetaAccessPolicy);
+ } else {
+ accessPolicy.createDefaultPolicy();
}
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
}
- catch(e){
- //Log an error to the console
- console.error("Couldn't check the authority for this user: ", e);
+ //Otherwise, set the default access policy using the AppModel configuration
+ else {
+ accessPolicy.createDefaultPolicy();
+ }
+ } catch (e) {
+ console.error(
+ "Could create access policy, so defaulting to default",
+ e,
+ );
+ accessPolicy.createDefaultPolicy();
+ }
+ } else {
+ //Parse the access policy XML to create AccessRule models from the XML
+ accessPolicy.parse(accessPolicyXML);
+ }
- // Track this error in our analytics
- const name = MetacatUI.appModel.get('username')
- MetacatUI.analytics?.trackException(
- `Couldn't check the authority for the user ${name}: ${e}`,
- this.get("id"),
- true
- );
+ //Listen to changes on the collection and trigger a change on this model
+ var self = this;
+ this.listenTo(accessPolicy, "change update", function () {
+ self.trigger("change");
+ this.addToUploadQueue();
+ });
- //Set the user as unauthorized
- model.set("isAuthorized_" + action, false);
- model.set("isAuthorized", false);
- return false;
+ return accessPolicy;
+ },
- }
+ /**
+ * Update identifiers for this object
+ *
+ * @param {string} id - Optional identifier to update with. Generated
+ * automatically when not given.
+ *
+ * Note that this method caches the objects attributes prior to
+ * updating so this.resetID() can be called in case of a failure
+ * state.
+ *
+ * Also note that this method won't run if theh oldPid attribute is
+ * set. This enables knowing before this.save is called what the next
+ * PID will be such as the case where we want to update a matching
+ * EML entity when replacing files.
+ */
+ updateID: function (id) {
+ // Only run once until oldPid is reset
+ if (this.get("oldPid")) {
+ return;
+ }
- },
+ //Save the attributes so we can reset the ID later
+ this.attributeCache = this.toJSON();
- /**
- * Using the attributes set on this DataONEObject model, serializes the system metadata XML
- * @returns {string}
- */
- serializeSysMeta: function(){
- //Get the system metadata XML that currently exists in the system
- var sysMetaXML = this.get("sysMetaXML"), // sysmeta as string
- xml, // sysmeta as DOM object
- accessPolicyXML, // The generated access policy XML
- previousSiblingNode, // A DOM node indicating any previous sibling
- rightsHolderNode, // A DOM node for the rights holder field
- accessPolicyNode, // A DOM node for the access policy
- replicationPolicyNode, // A DOM node for the replication policy
- obsoletesNode, // A DOM node for the obsoletes field
- obsoletedByNode, // A DOM node for the obsoletedBy field
- fileNameNode, // A DOM node for the file name
- xmlString, // The system metadata document as a string
- nodeNameMap, // The map of camelCase to lowercase attributes
- extension; // the file name extension for this object
-
- if ( typeof sysMetaXML === "undefined" || sysMetaXML === null ) {
- xml = this.createSysMeta();
- } else {
- xml = $($.parseHTML(sysMetaXML));
- }
+ //Set the old identifier
+ var oldPid = this.get("id"),
+ selfDocuments,
+ selfDocumentedBy,
+ documentedModels,
+ documentedModel,
+ index;
- //Update the system metadata values
- xml.find("serialversion").text(this.get("serialVersion") || "0");
- xml.find("identifier").text((this.get("newPid") || this.get("id")));
- xml.find("submitter").text(this.get("submitter") || MetacatUI.appUserModel.get("username"));
- xml.find("formatid").text(this.get("formatId") || this.getFormatId());
-
- //If there is a seriesId, add it
- if( this.get("seriesId") ){
- //Get the seriesId XML node
- var seriesIdNode = xml.find("seriesId");
-
- //If it doesn't exist, create one
- if( !seriesIdNode.length ){
- seriesIdNode = $(document.createElement("seriesid"));
- xml.find("identifier").before(seriesIdNode);
- }
+ //Save the current id as the old pid
+ this.set("oldPid", oldPid);
- //Add the seriesId string to the XML node
- seriesIdNode.text( this.get("seriesId") );
- }
+ //Create a new seriesId, if there isn't one, and if this model specifies that one is required
+ if (!this.get("seriesId") && this.get("createSeriesId")) {
+ this.set("seriesId", "urn:uuid:" + uuid.v4());
+ }
- //If there is no size, get it
- if( !this.get("size") && this.get("uploadFile")){
- this.set("size", this.get("uploadFile").size);
- }
+ // Check to see if the old pid documents or is documented by itself
+ selfDocuments = _.contains(this.get("documents"), oldPid);
+ selfDocumentedBy = _.contains(this.get("isDocumentedBy"), oldPid);
+
+ //Set the new identifier
+ if (id) {
+ this.set("id", id);
+ } else {
+ if (this.get("type") == "DataPackage") {
+ this.set("id", "resource_map_urn:uuid:" + uuid.v4());
+ } else {
+ this.set("id", "urn:uuid:" + uuid.v4());
+ }
+ }
- //Get the size of the file, if there is one
- if( this.get("uploadFile") ){
- xml.find("size").text( this.get("uploadFile").size );
- }
- //Otherwise, use the last known size
- else{
- xml.find("size").text(this.get("size"));
- }
+ // Remove the old pid from the documents list if present
+ if (selfDocuments) {
+ index = this.get("documents").indexOf(oldPid);
+ if (index > -1) {
+ this.get("documents").splice(index, 1);
+ }
+ // And add the new pid in
+ this.get("documents").push(this.get("id"));
+ }
- //Save the original checksum
- if( !this.get("checksum") && this.get("originalChecksum") ){
- xml.find("checksum").text(this.get("originalChecksum"));
- }
- //Update the checksum and checksum algorithm
- else{
- xml.find("checksum").text(this.get("checksum"));
- xml.find("checksum").attr("algorithm", this.get("checksumAlgorithm"));
- }
-
- //Update the rightsholder
- xml.find("rightsholder").text(this.get("rightsHolder") || MetacatUI.appUserModel.get("username"));
-
- //Write the access policy
- accessPolicyXML = this.get("accessPolicy").serialize();
-
- // Get the access policy node, if it exists
- accessPolicyNode = xml.find("accesspolicy");
-
- previousSiblingNode = xml.find("rightsholder");
-
- // Create an access policy node if needed
- if ( (! accessPolicyNode.length) && accessPolicyXML ) {
- accessPolicyNode = $(document.createElement("accesspolicy"));
- previousSiblingNode.after(accessPolicyNode);
-
- }
-
- //Replace the old access policy with the new one if it exists
- if ( accessPolicyXML ) {
- accessPolicyNode.replaceWith(accessPolicyXML);
- } else {
- // Remove the node if it is empty
- accessPolicyNode.remove();
- }
-
- // Set the obsoletes node after replPolicy or accessPolicy, or rightsHolder
- replicationPolicyNode = xml.find("replicationpolicy");
- accessPolicyNode = xml.find("accesspolicy");
- rightsHolderNode = xml.find("rightsholder");
-
- if ( replicationPolicyNode.length ) {
- previousSiblingNode = replicationPolicyNode;
- } else if ( accessPolicyNode.length ) {
- previousSiblingNode = accessPolicyNode;
- } else {
- previousSiblingNode = rightsHolderNode;
- }
-
- obsoletesNode = xml.find("obsoletes");
-
- if( this.get("obsoletes") ){
- if( obsoletesNode.length ) {
- obsoletesNode.text(this.get("obsoletes"));
- }
- else {
- obsoletesNode = $(document.createElement("obsoletes")).text(this.get("obsoletes"));
- previousSiblingNode.after(obsoletesNode);
- }
- }
- else {
- if ( obsoletesNode ) {
- obsoletesNode.remove();
- }
- }
-
- if ( obsoletesNode ) {
- previousSiblingNode = obsoletesNode;
- }
-
- obsoletedByNode = xml.find("obsoletedby");
-
- //remove the obsoletedBy node if it exists
- // TODO: Verify this is what we want to do
- if ( obsoletedByNode ) {
- obsoletedByNode.remove();
- }
-
- xml.find("archived").text(this.get("archived") || "false");
- xml.find("dateuploaded").text(this.get("dateUploaded") || new Date().toISOString());
-
- //Get the filename node
- fileNameNode = xml.find("filename");
-
- //If the filename node doesn't exist, then create one
- if( ! fileNameNode.length ){
- fileNameNode = $(document.createElement("filename"));
- xml.find("dateuploaded").after(fileNameNode);
- }
-
- //Set the object file name
- $(fileNameNode).text(this.get("fileName"));
-
- xmlString = $(document.createElement("div")).append(xml.clone()).html();
-
- //Now camel case the nodes
- nodeNameMap = this.nodeNameMap();
-
- _.each(Object.keys(nodeNameMap), function(name, i){
- var originalXMLString = xmlString;
-
- //Camel case node names
- var regEx = new RegExp("<" + name, "g");
- xmlString = xmlString.replace(regEx, "<" + nodeNameMap[name]);
- var regEx = new RegExp(name + ">", "g");
- xmlString = xmlString.replace(regEx, nodeNameMap[name] + ">");
-
- //If node names haven't been changed, then find an attribute
- if(xmlString == originalXMLString){
- var regEx = new RegExp(" " + name + "=", "g");
- xmlString = xmlString.replace(regEx, " " + nodeNameMap[name] + "=");
- }
- }, this);
-
- xmlString = xmlString.replace(/systemmetadata/g, "systemMetadata");
-
- return xmlString;
- },
-
- /**
- * Get the object format identifier for this object
- */
- getFormatId: function() {
- var formatId = "application/octet-stream", // default to untyped data
- objectFormats = {
- "mediaTypes": [], // The list of potential formatIds based on mediaType matches
- "extensions": [] // The list of possible formatIds based onextension matches
- },
- fileName = this.get("fileName"), // the fileName for this object
- ext; // The extension of the filename for this object
-
- objectFormats["mediaTypes"] = MetacatUI.objectFormats.where({formatId: this.get("mediaType")});
- if ( typeof fileName !== "undefined" && fileName !== null && fileName.length > 1) {
- ext = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length);
- objectFormats["extensions"] = MetacatUI.objectFormats.where({extension: ext});
- }
-
- if (objectFormats["mediaTypes"].length > 0 && objectFormats["extensions"].length > 0) {
- var firstMediaType = objectFormats["mediaTypes"][0].get("formatId");
- var firstExtension = objectFormats["extensions"][0].get("formatId");
- // Check if they're equal
- if (firstMediaType === firstExtension) {
- formatId = firstMediaType;
- return formatId;
- }
- // Handle mismatched mediaType and extension cases - additional cases can be added below
- if (firstMediaType === 'application/vnd.ms-excel' && firstExtension === 'text/csv') {
- formatId = firstExtension;
- return formatId;
- }
- }
-
- if (objectFormats["mediaTypes"].length > 0) {
- formatId = objectFormats["mediaTypes"][0].get("formatId");
- console.log('returning default mediaType');
- console.log(formatId);
- return formatId;
- }
-
- if (objectFormats["extensions"].length > 0 ) {
- //If this is a "nc" file, assume it is a netCDF-3 file.
- if (ext == "nc") {
- formatId = "netCDF-3";
- } else {
- formatId = objectFormats["extensions"][0].get("formatId");
- }
- return formatId;
- }
-
- return formatId;
-
- },
+ // Remove the old pid from the isDocumentedBy list if present
+ if (selfDocumentedBy) {
+ index = this.get("isDocumentedBy").indexOf(oldPid);
+ if (index > -1) {
+ this.get("isDocumentedBy").splice(index, 1);
+ }
+ // And add the new pid in
+ this.get("isDocumentedBy").push(this.get("id"));
+ }
- /**
- * Looks up human readable format of the DataONE Object
- * @returns format String
- * @since 2.28.0
- */
- getFormat: function(){
- var formatMap = {
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" : "Microsoft Excel OpenXML",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document" : "Microsoft Word OpenXML",
- "application/vnd.ms-excel.sheet.binary.macroEnabled.12" : "Microsoft Office Excel 2007 binary workbooks",
- "application/vnd.openxmlformats-officedocument.presentationml.presentation" : "Microsoft Office OpenXML Presentation",
- "application/vnd.ms-excel" : "Microsoft Excel",
- "application/msword" : "Microsoft Word",
- "application/vnd.ms-powerpoint" : "Microsoft Powerpoint",
- "text/html" : "HTML",
- "text/plain": "plain text (.txt)",
- "video/avi" : "Microsoft AVI file",
- "video/x-ms-wmv" : "Windows Media Video (.wmv)",
- "audio/x-ms-wma" : "Windows Media Audio (.wma)",
- "application/vnd.google-earth.kml xml" : "Google Earth Keyhole Markup Language (KML)",
- "http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html" : "annotation",
- "application/mathematica" : "Mathematica Notebook",
- "application/postscript" : "Postscript",
- "application/rtf" : "Rich Text Format (RTF)",
- "application/xml" : "XML Application",
- "text/xml" : "XML",
- "application/x-fasta" : "FASTA sequence file",
- "nexus/1997" : "NEXUS File Format for Systematic Information",
- "anvl/erc-v02" : "Kernel Metadata and Electronic Resource Citations (ERCs), 2010.05.13",
- "http://purl.org/dryad/terms/" : "Dryad Metadata Application Profile Version 3.0",
- "http://datadryad.org/profile/v3.1" : "Dryad Metadata Application Profile Version 3.1",
- "application/pdf" : "PDF",
- "application/zip" : "ZIP file",
- "http://www.w3.org/TR/rdf-syntax-grammar" : "RDF/XML",
- "http://www.w3.org/TR/rdfa-syntax" : "RDFa",
- "application/rdf xml" : "RDF",
- "text/turtle" : "TURTLE",
- "text/n3" : "N3",
- "application/x-gzip" : "GZIP Format",
- "application/x-python" : "Python script",
- "http://www.w3.org/2005/Atom" : "ATOM-1.0",
- "application/octet-stream" : "octet stream (application file)",
- "http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd" : "Darwin Core, v2.0",
- "http://rs.tdwg.org/dwc/xsd/simpledarwincore/" : "Simple Darwin Core",
- "eml://ecoinformatics.org/eml-2.1.0" : "EML v2.1.0",
- "eml://ecoinformatics.org/eml-2.1.1" : "EML v2.1.1",
- "eml://ecoinformatics.org/eml-2.0.1" : "EML v2.0.1",
- "eml://ecoinformatics.org/eml-2.0.0" : "EML v2.0.0",
- "https://eml.ecoinformatics.org/eml-2.2.0" : "EML v2.2.0",
+ // Update all models documented by this pid with the new id
+ _.each(
+ this.get("documents"),
+ function (id) {
+ (documentedModels = MetacatUI.rootDataPackage.where({ id: id })),
+ documentedModel;
+ if (documentedModels.length > 0) {
+ documentedModel = documentedModels[0];
}
-
- return formatMap[this.get("formatId")] || this.get("formatId");
- },
-
- /**
- * Build a fresh system metadata document for this object when it is new
- * Return it as a DOM object
- */
- createSysMeta: function() {
- var sysmetaDOM, // The DOM
- sysmetaXML = []; // The document as a string array
-
- sysmetaXML.push(
- //'',
- '',
- ' ',
- ' ',
- ' ',
- ' ',
- ' ',
- ' ',
- ' ',
- ' ',
- ' '
- );
-
- sysmetaDOM = $($.parseHTML(sysmetaXML.join("")));
- return sysmetaDOM;
- },
-
- /**
- * Create an access policy for this DataONEObject using the default access
- * policy set in the AppModel.
- *
- * @param {Element} [accessPolicyXML] - An XML node
- * that contains a list of access rules.
- * @return {AccessPolicy} - an AccessPolicy collection that represents the
- * given XML or the default policy set in the AppModel.
- */
- createAccessPolicy: function(accessPolicyXML){
- //Create a new AccessPolicy collection
- var accessPolicy = new AccessPolicy();
-
- accessPolicy.dataONEObject = this;
-
- //If there is no access policy XML sent,
- if( this.isNew() && !accessPolicyXML ){
-
- try{
- //If the app is configured to inherit the access policy from the parent metadata,
- // then get the parent metadata and copy it's AccessPolicy
- let scienceMetadata = this.get("isDocumentedByModels");
- if( MetacatUI.appModel.get("inheritAccessPolicy") && scienceMetadata && scienceMetadata.length ){
- let sciMetaAccessPolicy = scienceMetadata[0].get("accessPolicy");
-
- if( sciMetaAccessPolicy ){
- accessPolicy.copyAccessPolicy(sciMetaAccessPolicy);
- }
- else{
- accessPolicy.createDefaultPolicy();
- }
+ if (typeof documentedModel !== "undefined") {
+ // Find the oldPid in the array
+ if (Array.isArray(documentedModel.get("isDocumentedBy"))) {
+ index = documentedModel.get("isDocumentedBy").indexOf("oldPid");
+
+ if (index > -1) {
+ // Remove it
+ documentedModel.get("isDocumentedBy").splice(index, 1);
}
- //Otherwise, set the default access policy using the AppModel configuration
- else{
- accessPolicy.createDefaultPolicy();
- }
- }
- catch(e){
- console.error("Could create access policy, so defaulting to default", e);
- accessPolicy.createDefaultPolicy();
+ // And add the new pid in
+ documentedModel.get("isDocumentedBy").push(this.get("id"));
}
}
- else{
- //Parse the access policy XML to create AccessRule models from the XML
- accessPolicy.parse(accessPolicyXML);
- }
-
- //Listen to changes on the collection and trigger a change on this model
- var self = this;
- this.listenTo(accessPolicy, "change update", function(){
- self.trigger("change");
- this.addToUploadQueue();
-
- });
-
- return accessPolicy;
},
+ this,
+ );
- /**
- * Update identifiers for this object
- *
- * @param {string} id - Optional identifier to update with. Generated
- * automatically when not given.
- *
- * Note that this method caches the objects attributes prior to
- * updating so this.resetID() can be called in case of a failure
- * state.
- *
- * Also note that this method won't run if theh oldPid attribute is
- * set. This enables knowing before this.save is called what the next
- * PID will be such as the case where we want to update a matching
- * EML entity when replacing files.
- */
- updateID: function(id){
- // Only run once until oldPid is reset
- if (this.get("oldPid")) {
- return;
- }
+ this.trigger("change:id");
- //Save the attributes so we can reset the ID later
- this.attributeCache = this.toJSON();
+ //Update the obsoletes and obsoletedBy
+ this.set("obsoletes", oldPid);
+ this.set("obsoletedBy", null);
- //Set the old identifier
- var oldPid = this.get("id"),
- selfDocuments,
- selfDocumentedBy,
- documentedModels,
- documentedModel,
- index;
+ // Update the latest version of this object
+ this.set("latestVersion", this.get("id"));
- //Save the current id as the old pid
- this.set("oldPid", oldPid);
+ //Set the archived option to false
+ this.set("archived", false);
+ },
- //Create a new seriesId, if there isn't one, and if this model specifies that one is required
- if( !this.get("seriesId") && this.get("createSeriesId") ){
- this.set("seriesId", "urn:uuid:" + uuid.v4());
- }
-
- // Check to see if the old pid documents or is documented by itself
- selfDocuments = _.contains(this.get("documents"), oldPid);
- selfDocumentedBy = _.contains(this.get("isDocumentedBy"), oldPid);
-
- //Set the new identifier
- if( id ) {
- this.set("id", id);
-
- } else {
- if( this.get("type") == "DataPackage" ){
- this.set("id", "resource_map_urn:uuid:" + uuid.v4());
- }
- else{
- this.set("id", "urn:uuid:" + uuid.v4());
- }
- }
-
- // Remove the old pid from the documents list if present
- if ( selfDocuments ) {
- index = this.get("documents").indexOf(oldPid);
- if ( index > -1 ) {
- this.get("documents").splice(index, 1);
-
- }
- // And add the new pid in
- this.get("documents").push(this.get("id"));
-
- }
-
- // Remove the old pid from the isDocumentedBy list if present
- if ( selfDocumentedBy ) {
- index = this.get("isDocumentedBy").indexOf(oldPid);
- if ( index > -1 ) {
- this.get("isDocumentedBy").splice(index, 1);
-
- }
- // And add the new pid in
- this.get("isDocumentedBy").push(this.get("id"));
-
- }
-
- // Update all models documented by this pid with the new id
- _.each(this.get("documents"), function(id) {
- documentedModels = MetacatUI.rootDataPackage.where({id: id}),
- documentedModel;
-
- if ( documentedModels.length > 0 ) {
- documentedModel = documentedModels[0];
- }
- if ( typeof documentedModel !== "undefined" ) {
- // Find the oldPid in the array
- if( Array.isArray(documentedModel.get("isDocumentedBy")) ){
- index = documentedModel.get("isDocumentedBy").indexOf("oldPid");
-
- if ( index > -1 ) {
- // Remove it
- documentedModel.get("isDocumentedBy").splice(index, 1);
-
- }
- // And add the new pid in
- documentedModel.get("isDocumentedBy").push(this.get("id"));
- }
- }
- }, this);
-
- this.trigger("change:id")
-
- //Update the obsoletes and obsoletedBy
- this.set("obsoletes", oldPid);
- this.set("obsoletedBy", null);
-
- // Update the latest version of this object
- this.set("latestVersion", this.get("id"));
-
- //Set the archived option to false
- this.set("archived", false);
- },
-
- /**
- * Resets the identifier for this model. This undos all of the changes made in {DataONEObject#updateID}
- */
- resetID: function(){
- if(!this.attributeCache) return false;
-
- this.set("oldPid", this.attributeCache.oldPid, {silent:true});
- this.set("id", this.attributeCache.id, {silent: true});
- this.set("obsoletes", this.attributeCache.obsoletes, {silent: true});
- this.set("obsoletedBy", this.attributeCache.obsoletedBy, {silent: true});
- this.set("archived", this.attributeCache.archived, {silent: true});
- this.set("latestVersion", this.attributeCache.latestVersion, {silent: true});
-
- //Reset the attribute cache
- this.attributeCache = {};
- },
+ /**
+ * Resets the identifier for this model. This undos all of the changes made in {DataONEObject#updateID}
+ */
+ resetID: function () {
+ if (!this.attributeCache) return false;
+
+ this.set("oldPid", this.attributeCache.oldPid, { silent: true });
+ this.set("id", this.attributeCache.id, { silent: true });
+ this.set("obsoletes", this.attributeCache.obsoletes, { silent: true });
+ this.set("obsoletedBy", this.attributeCache.obsoletedBy, {
+ silent: true,
+ });
+ this.set("archived", this.attributeCache.archived, { silent: true });
+ this.set("latestVersion", this.attributeCache.latestVersion, {
+ silent: true,
+ });
+
+ //Reset the attribute cache
+ this.attributeCache = {};
+ },
- /**
- * Checks if this system metadata XML has updates that need to be synced with the server.
- * @returns {boolean}
- */
- hasUpdates: function(){
- if(this.isNew()) return true;
+ /**
+ * Checks if this system metadata XML has updates that need to be synced with the server.
+ * @returns {boolean}
+ */
+ hasUpdates: function () {
+ if (this.isNew()) return true;
- // Compare the new system metadata XML to the old system metadata XML
+ // Compare the new system metadata XML to the old system metadata XML
- //Check if there is system metadata first
- if( !this.get("sysMetaXML") ){
- return false;
- }
+ //Check if there is system metadata first
+ if (!this.get("sysMetaXML")) {
+ return false;
+ }
- var D1ObjectClone = this.clone(),
- // Make sure we are using the parse function in the DataONEObject model.
- // Sometimes hasUpdates is called from extensions of the D1Object model,
- // (e.g. from the portal model), and the parse function is overwritten
- oldSysMetaAttrs = new DataONEObject().parse(D1ObjectClone.get("sysMetaXML"));
+ var D1ObjectClone = this.clone(),
+ // Make sure we are using the parse function in the DataONEObject model.
+ // Sometimes hasUpdates is called from extensions of the D1Object model,
+ // (e.g. from the portal model), and the parse function is overwritten
+ oldSysMetaAttrs = new DataONEObject().parse(
+ D1ObjectClone.get("sysMetaXML"),
+ );
- D1ObjectClone.set(oldSysMetaAttrs);
+ D1ObjectClone.set(oldSysMetaAttrs);
- var oldSysMeta = D1ObjectClone.serializeSysMeta();
- var newSysMeta = this.serializeSysMeta();
+ var oldSysMeta = D1ObjectClone.serializeSysMeta();
+ var newSysMeta = this.serializeSysMeta();
- if ( oldSysMeta === "" ) return false;
+ if (oldSysMeta === "") return false;
- return !(newSysMeta == oldSysMeta);
- },
+ return !(newSysMeta == oldSysMeta);
+ },
- /**
+ /**
Set the changed flag on any system metadata or content attribute changes,
and set the hasContentChanges flag on content changes only
@param {DataONEObject} [model]
@param {object} options Furhter options for this function
@property {boolean} options.force If true, a change will be handled regardless if the attribute actually changed
*/
- handleChange: function(model, options) {
- if(!model) var model = this;
-
- var sysMetaAttrs = ["serialVersion", "identifier", "formatId", "formatType", "size", "checksum",
- "checksumAlgorithm", "submitter", "rightsHolder", "accessPolicy", "replicationAllowed",
- "replicationPolicy", "obsoletes", "obsoletedBy", "archived", "dateUploaded", "dateSysMetadataModified",
- "originMemberNode", "authoritativeMemberNode", "replica", "seriesId", "mediaType", "fileName"],
- nonSysMetaNonContentAttrs = _.difference(model.get("originalAttrs"), sysMetaAttrs),
- allChangedAttrs = Object.keys(model.changedAttributes()),
- changedSysMetaOrContentAttrs = [], //sysmeta or content attributes that have changed
- changedContentAttrs = []; // attributes from sub classes like ScienceMetadata or EML211 ...
-
- // Get a list of all changed sysmeta and content attributes
- changedSysMetaOrContentAttrs = _.difference(allChangedAttrs, nonSysMetaNonContentAttrs);
- if ( changedSysMetaOrContentAttrs.length > 0 ) {
- // For any sysmeta or content change, set the package dirty flag
- if ( MetacatUI.rootDataPackage &&
- MetacatUI.rootDataPackage.packageModel &&
- ! MetacatUI.rootDataPackage.packageModel.get("changed") &&
- model.get("synced") ) {
-
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- }
- }
-
- // And get a list of all changed content attributes
- changedContentAttrs = _.difference(changedSysMetaOrContentAttrs, sysMetaAttrs);
-
- if ( (changedContentAttrs.length > 0 && !this.get("hasContentChanges") && model.get("synced")) ||
- (options && options.force)) {
- this.set("hasContentChanges", true);
- this.addToUploadQueue();
- }
-
- },
-
- /**
- * Returns true if this DataONE object is new. A DataONE object is new
- * if there is no upload date and it's been synced (i.e. been fetched)
- * @return {boolean}
- */
- isNew: function(){
-
- //If the model is explicitly marked as not new, return false
- if( this.get("isNew") === false ){
- return false;
- }
- //If the model is explicitly marked as new, return true
- else if( this.get("isNew") === true ){
- return true;
- }
-
- //Check if there is an upload date that was retrieved from the server
- return ( this.get("dateUploaded") === this.defaults().dateUploaded &&
- this.get("synced") );
- },
-
- /**
- * Updates the upload status attribute on this model and marks the collection as changed
- */
- addToUploadQueue: function(){
-
- if( !this.get("synced") ){
- return;
- }
-
- //Add this item to the queue
- if((this.get("uploadStatus") == "c") || (this.get("uploadStatus") == "e") || !this.get("uploadStatus")){
- this.set("uploadStatus", "q");
-
- //Mark each DataPackage collection this model is in as changed
- _.each(this.get("collections"), function(collection){
- if(collection.packageModel)
- collection.packageModel.set("changed", true);
- }, this);
- }
- },
+ handleChange: function (model, options) {
+ if (!model) var model = this;
+
+ var sysMetaAttrs = [
+ "serialVersion",
+ "identifier",
+ "formatId",
+ "formatType",
+ "size",
+ "checksum",
+ "checksumAlgorithm",
+ "submitter",
+ "rightsHolder",
+ "accessPolicy",
+ "replicationAllowed",
+ "replicationPolicy",
+ "obsoletes",
+ "obsoletedBy",
+ "archived",
+ "dateUploaded",
+ "dateSysMetadataModified",
+ "originMemberNode",
+ "authoritativeMemberNode",
+ "replica",
+ "seriesId",
+ "mediaType",
+ "fileName",
+ ],
+ nonSysMetaNonContentAttrs = _.difference(
+ model.get("originalAttrs"),
+ sysMetaAttrs,
+ ),
+ allChangedAttrs = Object.keys(model.changedAttributes()),
+ changedSysMetaOrContentAttrs = [], //sysmeta or content attributes that have changed
+ changedContentAttrs = []; // attributes from sub classes like ScienceMetadata or EML211 ...
+
+ // Get a list of all changed sysmeta and content attributes
+ changedSysMetaOrContentAttrs = _.difference(
+ allChangedAttrs,
+ nonSysMetaNonContentAttrs,
+ );
+ if (changedSysMetaOrContentAttrs.length > 0) {
+ // For any sysmeta or content change, set the package dirty flag
+ if (
+ MetacatUI.rootDataPackage &&
+ MetacatUI.rootDataPackage.packageModel &&
+ !MetacatUI.rootDataPackage.packageModel.get("changed") &&
+ model.get("synced")
+ ) {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ }
+ }
- /**
- * Updates the progress percentage when the model is getting uploaded
- * @param {ProgressEvent} e - The ProgressEvent when this file is being uploaded
- */
- updateProgress: function(e){
- if(e.lengthComputable){
- var max = e.total;
- var current = e.loaded;
+ // And get a list of all changed content attributes
+ changedContentAttrs = _.difference(
+ changedSysMetaOrContentAttrs,
+ sysMetaAttrs,
+ );
+
+ if (
+ (changedContentAttrs.length > 0 &&
+ !this.get("hasContentChanges") &&
+ model.get("synced")) ||
+ (options && options.force)
+ ) {
+ this.set("hasContentChanges", true);
+ this.addToUploadQueue();
+ }
+ },
- var Percentage = (current * 100)/max;
+ /**
+ * Returns true if this DataONE object is new. A DataONE object is new
+ * if there is no upload date and it's been synced (i.e. been fetched)
+ * @return {boolean}
+ */
+ isNew: function () {
+ //If the model is explicitly marked as not new, return false
+ if (this.get("isNew") === false) {
+ return false;
+ }
+ //If the model is explicitly marked as new, return true
+ else if (this.get("isNew") === true) {
+ return true;
+ }
+ //Check if there is an upload date that was retrieved from the server
+ return (
+ this.get("dateUploaded") === this.defaults().dateUploaded &&
+ this.get("synced")
+ );
+ },
- if(Percentage >= 100)
- {
- // process completed
- }
- }
- },
+ /**
+ * Updates the upload status attribute on this model and marks the collection as changed
+ */
+ addToUploadQueue: function () {
+ if (!this.get("synced")) {
+ return;
+ }
- /**
- * Updates the relationships with other models when this model has been updated
- */
- updateRelationships: function(){
- _.each(this.get("collections"), function(collection){
- //Get the old id for this model
- var oldId = this.get("oldPid");
+ //Add this item to the queue
+ if (
+ this.get("uploadStatus") == "c" ||
+ this.get("uploadStatus") == "e" ||
+ !this.get("uploadStatus")
+ ) {
+ this.set("uploadStatus", "q");
+
+ //Mark each DataPackage collection this model is in as changed
+ _.each(
+ this.get("collections"),
+ function (collection) {
+ if (collection.packageModel)
+ collection.packageModel.set("changed", true);
+ },
+ this,
+ );
+ }
+ },
- if(!oldId) return;
+ /**
+ * Updates the progress percentage when the model is getting uploaded
+ * @param {ProgressEvent} e - The ProgressEvent when this file is being uploaded
+ */
+ updateProgress: function (e) {
+ if (e.lengthComputable) {
+ var max = e.total;
+ var current = e.loaded;
+
+ var Percentage = (current * 100) / max;
+
+ if (Percentage >= 100) {
+ // process completed
+ }
+ }
+ },
- //Find references to the old id in the documents relationship
- var outdatedModels = collection.filter(function(m){
- return _.contains(m.get("documents"), oldId);
- });
+ /**
+ * Updates the relationships with other models when this model has been updated
+ */
+ updateRelationships: function () {
+ _.each(
+ this.get("collections"),
+ function (collection) {
+ //Get the old id for this model
+ var oldId = this.get("oldPid");
+
+ if (!oldId) return;
+
+ //Find references to the old id in the documents relationship
+ var outdatedModels = collection.filter(function (m) {
+ return _.contains(m.get("documents"), oldId);
+ });
- //Update the documents array in each model
- _.each(outdatedModels, function(model){
+ //Update the documents array in each model
+ _.each(
+ outdatedModels,
+ function (model) {
var updatedDocuments = _.without(model.get("documents"), oldId);
updatedDocuments.push(this.get("id"));
model.set("documents", updatedDocuments);
- }, this);
-
- }, this);
+ },
+ this,
+ );
},
+ this,
+ );
+ },
- /**
+ /**
* Finds the latest version of this object by travesing the obsolescence chain
* @param {string} [latestVersion] - The identifier of the latest known object in the version chain.
If not supplied, this model's `id` will be used.
* @param {string} [possiblyNewer] - The identifier of the object that obsoletes the latestVersion. It's "possibly" newer, because it may be private/inaccessible
*/
- findLatestVersion: function(latestVersion, possiblyNewer){
- var baseUrl = "",
- activeAltRepo = MetacatUI.appModel.getActiveAltRepo();
- //Use the meta service URL from the alt repo
- if( activeAltRepo ){
- baseUrl = activeAltRepo.metaServiceUrl;
- }
- //If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel
- else{
- baseUrl = MetacatUI.appModel.get("metaServiceUrl");
- }
-
- if( !baseUrl ){
- return;
- }
-
- //If there is no system metadata, then retrieve it first
- if(!this.get("sysMetaXML")){
- this.once("sync", this.findLatestVersion);
- this.once("systemMetadataSync", this.findLatestVersion);
- this.fetch({
- url: baseUrl + encodeURIComponent(this.get("id")),
- dataType: "text",
- systemMetadataOnly: true
- });
- return;
- }
+ findLatestVersion: function (latestVersion, possiblyNewer) {
+ var baseUrl = "",
+ activeAltRepo = MetacatUI.appModel.getActiveAltRepo();
+ //Use the meta service URL from the alt repo
+ if (activeAltRepo) {
+ baseUrl = activeAltRepo.metaServiceUrl;
+ }
+ //If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel
+ else {
+ baseUrl = MetacatUI.appModel.get("metaServiceUrl");
+ }
- //If no pid was supplied, use this model's id
- if(!latestVersion || typeof latestVersion != "string"){
- var latestVersion = this.get("id");
- var possiblyNewer = this.get("obsoletedBy");
- }
+ if (!baseUrl) {
+ return;
+ }
- //If this isn't obsoleted by anything, then there is no newer version
- if(!possiblyNewer || typeof latestVersion != "string"){
- this.set("latestVersion", latestVersion);
+ //If there is no system metadata, then retrieve it first
+ if (!this.get("sysMetaXML")) {
+ this.once("sync", this.findLatestVersion);
+ this.once("systemMetadataSync", this.findLatestVersion);
+ this.fetch({
+ url: baseUrl + encodeURIComponent(this.get("id")),
+ dataType: "text",
+ systemMetadataOnly: true,
+ });
+ return;
+ }
- //Trigger an event that will fire whether or not the latestVersion
- // attribute was actually changed
- this.trigger("latestVersionFound", this);
+ //If no pid was supplied, use this model's id
+ if (!latestVersion || typeof latestVersion != "string") {
+ var latestVersion = this.get("id");
+ var possiblyNewer = this.get("obsoletedBy");
+ }
- //Remove the listeners now that we found the latest version
- this.stopListening("sync", this.findLatestVersion);
- this.stopListening("systemMetadataSync", this.findLatestVersion);
+ //If this isn't obsoleted by anything, then there is no newer version
+ if (!possiblyNewer || typeof latestVersion != "string") {
+ this.set("latestVersion", latestVersion);
- return;
- }
+ //Trigger an event that will fire whether or not the latestVersion
+ // attribute was actually changed
+ this.trigger("latestVersionFound", this);
- var model = this;
+ //Remove the listeners now that we found the latest version
+ this.stopListening("sync", this.findLatestVersion);
+ this.stopListening("systemMetadataSync", this.findLatestVersion);
- //Get the system metadata for the possibly newer version
- var requestSettings = {
- url: baseUrl + encodeURIComponent(possiblyNewer),
- type: "GET",
- success: function(data) {
+ return;
+ }
- // the response may have an obsoletedBy element
- var obsoletedBy = $(data).find("obsoletedBy").text();
+ var model = this;
- //If there is an even newer version, then get it and rerun this function
- if(obsoletedBy){
- model.findLatestVersion(possiblyNewer, obsoletedBy);
- }
- //If there isn't a newer version, then this is it
- else{
- model.set("latestVersion", possiblyNewer);
- model.trigger("latestVersionFound", model);
-
- //Remove the listeners now that we found the latest version
- model.stopListening("sync", model.findLatestVersion);
- model.stopListening("systemMetadataSync", model.findLatestVersion);
- }
+ //Get the system metadata for the possibly newer version
+ var requestSettings = {
+ url: baseUrl + encodeURIComponent(possiblyNewer),
+ type: "GET",
+ success: function (data) {
+ // the response may have an obsoletedBy element
+ var obsoletedBy = $(data).find("obsoletedBy").text();
- },
- error: function(xhr){
- //If this newer version isn't accessible, link to the latest version that is
- if(xhr.status == "401"){
- model.set("latestVersion", latestVersion);
- model.trigger("latestVersionFound", model);
- }
- }
+ //If there is an even newer version, then get it and rerun this function
+ if (obsoletedBy) {
+ model.findLatestVersion(possiblyNewer, obsoletedBy);
}
+ //If there isn't a newer version, then this is it
+ else {
+ model.set("latestVersion", possiblyNewer);
+ model.trigger("latestVersionFound", model);
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
+ //Remove the listeners now that we found the latest version
+ model.stopListening("sync", model.findLatestVersion);
+ model.stopListening(
+ "systemMetadataSync",
+ model.findLatestVersion,
+ );
+ }
},
-
- /**
- * A utility function that will format an XML string or XML nodes by camel-casing the node names, as necessary
- * @param {string|Element} xml - The XML to format
- * @returns {string} The formatted XML string
- */
- formatXML: function(xml){
- var nodeNameMap = this.nodeNameMap(),
- xmlString = "";
-
- //XML must be provided for this function
- if(!xml)
- return "";
- //Support XML strings
- else if(typeof xml == "string")
- xmlString = xml;
- //Support DOMs
- else if(typeof xml == "object" && xml.nodeType){
- //XML comments should be formatted with start and end carets
- if(xml.nodeType == 8)
- xmlString = "<" + xml.nodeValue + ">";
- //XML nodes have the entire XML string available in the outerHTML attribute
- else if(xml.nodeType == 1)
- xmlString = xml.outerHTML;
- //Text node types are left as-is
- else if(xml.nodeType == 3)
- return xml.nodeValue;
+ error: function (xhr) {
+ //If this newer version isn't accessible, link to the latest version that is
+ if (xhr.status == "401") {
+ model.set("latestVersion", latestVersion);
+ model.trigger("latestVersionFound", model);
}
-
- //Return empty strings if something went wrong
- if(!xmlString)
- return "";
-
- _.each(Object.keys(nodeNameMap), function(name, i){
- var originalXMLString = xmlString;
-
- //Check for this node name whe it's an opening XML node, e.g. ``
- var regEx = new RegExp("<" + name + ">", "g");
- xmlString = xmlString.replace(regEx, "<" + nodeNameMap[name] + ">");
-
- //Check for this node name when it's an opening XML node, e.g. ``
- regEx = new RegExp(":" + name + ">", "g");
- xmlString = xmlString.replace(regEx, ":" + nodeNameMap[name] + ">");
-
- //Check for this node name when it's a closing XML tag, e.g. ` `
- regEx = new RegExp("" + name + ">", "g");
- xmlString = xmlString.replace(regEx, "" + nodeNameMap[name] + ">");
-
- //If node names haven't been changed, then find an attribute, e.g. ` name=`
- if(xmlString == originalXMLString){
- regEx = new RegExp(" " + name + "=", "g");
- xmlString = xmlString.replace(regEx, " " + nodeNameMap[name] + "=");
- }
-
- }, this);
-
- //Take each XML node text value and decode any XML entities
- var regEx = new RegExp("\&[0-9a-zA-Z]+\;", "g");
- xmlString = xmlString.replace(regEx, function(match){ return he.encode(he.decode(match)); });
-
- return xmlString;
},
+ };
- /**
- * Converts the number of bytes into a human readable format and
- * updates the `sizeStr` attribute
- * @returns: None
- *
- */
- bytesToSize: function(){
- var kibibyte = 1024;
- var mebibyte = kibibyte * 1024;
- var gibibyte = mebibyte * 1024;
- var tebibyte = gibibyte * 1024;
- var precision = 0;
-
- var bytes = this.get("size");
-
- if ((bytes >= 0) && (bytes < kibibyte)) {
- this.set("sizeStr", bytes + ' B');
-
- } else if ((bytes >= kibibyte) && (bytes < mebibyte)) {
- this.set("sizeStr", (bytes / kibibyte).toFixed(precision) + ' KiB');
-
- } else if ((bytes >= mebibyte) && (bytes < gibibyte)) {
- precision = 2;
- this.set("sizeStr", (bytes / mebibyte).toFixed(precision) + ' MiB');
-
- } else if ((bytes >= gibibyte) && (bytes < tebibyte)) {
- precision = 2;
- this.set("sizeStr", (bytes / gibibyte).toFixed(precision) + ' GiB');
-
- } else if (bytes >= tebibyte) {
- precision = 2;
- this.set("sizeStr", (bytes / tebibyte).toFixed(precision) + ' TiB');
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
- } else {
- this.set("sizeStr", bytes + ' B');
+ /**
+ * A utility function that will format an XML string or XML nodes by camel-casing the node names, as necessary
+ * @param {string|Element} xml - The XML to format
+ * @returns {string} The formatted XML string
+ */
+ formatXML: function (xml) {
+ var nodeNameMap = this.nodeNameMap(),
+ xmlString = "";
+
+ //XML must be provided for this function
+ if (!xml) return "";
+ //Support XML strings
+ else if (typeof xml == "string") xmlString = xml;
+ //Support DOMs
+ else if (typeof xml == "object" && xml.nodeType) {
+ //XML comments should be formatted with start and end carets
+ if (xml.nodeType == 8) xmlString = "<" + xml.nodeValue + ">";
+ //XML nodes have the entire XML string available in the outerHTML attribute
+ else if (xml.nodeType == 1) xmlString = xml.outerHTML;
+ //Text node types are left as-is
+ else if (xml.nodeType == 3) return xml.nodeValue;
+ }
- }
+ //Return empty strings if something went wrong
+ if (!xmlString) return "";
+
+ _.each(
+ Object.keys(nodeNameMap),
+ function (name, i) {
+ var originalXMLString = xmlString;
+
+ //Check for this node name whe it's an opening XML node, e.g. ``
+ var regEx = new RegExp("<" + name + ">", "g");
+ xmlString = xmlString.replace(regEx, "<" + nodeNameMap[name] + ">");
+
+ //Check for this node name when it's an opening XML node, e.g. ``
+ regEx = new RegExp(":" + name + ">", "g");
+ xmlString = xmlString.replace(regEx, ":" + nodeNameMap[name] + ">");
+
+ //Check for this node name when it's a closing XML tag, e.g. ` `
+ regEx = new RegExp("" + name + ">", "g");
+ xmlString = xmlString.replace(
+ regEx,
+ "" + nodeNameMap[name] + ">",
+ );
+
+ //If node names haven't been changed, then find an attribute, e.g. ` name=`
+ if (xmlString == originalXMLString) {
+ regEx = new RegExp(" " + name + "=", "g");
+ xmlString = xmlString.replace(
+ regEx,
+ " " + nodeNameMap[name] + "=",
+ );
+ }
},
+ this,
+ );
- /**
- * This method will download this object while
- * sending the user's auth token in the request.
- * @returns None
- * @since: 2.28.0
- */
- downloadWithCredentials: function(){
- //if(this.get("isPublic")) return;
-
- //Get info about this object
- var url = this.get("url"),
- model = this;
-
- //Create an XHR
- var xhr = new XMLHttpRequest();
+ //Take each XML node text value and decode any XML entities
+ var regEx = new RegExp("&[0-9a-zA-Z]+;", "g");
+ xmlString = xmlString.replace(regEx, function (match) {
+ return he.encode(he.decode(match));
+ });
- //Open and send the request with the user's auth token
- xhr.open('GET', url);
+ return xmlString;
+ },
- if(MetacatUI.appUserModel.get("loggedIn"))
- xhr.withCredentials = true;
+ /**
+ * Converts the number of bytes into a human readable format and
+ * updates the `sizeStr` attribute
+ * @returns: None
+ *
+ */
+ bytesToSize: function () {
+ var kibibyte = 1024;
+ var mebibyte = kibibyte * 1024;
+ var gibibyte = mebibyte * 1024;
+ var tebibyte = gibibyte * 1024;
+ var precision = 0;
+
+ var bytes = this.get("size");
+
+ if (bytes >= 0 && bytes < kibibyte) {
+ this.set("sizeStr", bytes + " B");
+ } else if (bytes >= kibibyte && bytes < mebibyte) {
+ this.set("sizeStr", (bytes / kibibyte).toFixed(precision) + " KiB");
+ } else if (bytes >= mebibyte && bytes < gibibyte) {
+ precision = 2;
+ this.set("sizeStr", (bytes / mebibyte).toFixed(precision) + " MiB");
+ } else if (bytes >= gibibyte && bytes < tebibyte) {
+ precision = 2;
+ this.set("sizeStr", (bytes / gibibyte).toFixed(precision) + " GiB");
+ } else if (bytes >= tebibyte) {
+ precision = 2;
+ this.set("sizeStr", (bytes / tebibyte).toFixed(precision) + " TiB");
+ } else {
+ this.set("sizeStr", bytes + " B");
+ }
+ },
- //When the XHR is ready, create a link with the raw data (Blob) and click the link to download
- xhr.onload = function(){
+ /**
+ * This method will download this object while
+ * sending the user's auth token in the request.
+ * @returns None
+ * @since: 2.28.0
+ */
+ downloadWithCredentials: function () {
+ //if(this.get("isPublic")) return;
+
+ //Get info about this object
+ var url = this.get("url"),
+ model = this;
+
+ //Create an XHR
+ var xhr = new XMLHttpRequest();
+
+ //Open and send the request with the user's auth token
+ xhr.open("GET", url);
+
+ if (MetacatUI.appUserModel.get("loggedIn")) xhr.withCredentials = true;
+
+ //When the XHR is ready, create a link with the raw data (Blob) and click the link to download
+ xhr.onload = function () {
+ if (this.status == 404) {
+ this.onerror.call(this);
+ return;
+ }
- if( this.status == 404 ){
- this.onerror.call(this);
- return;
- }
+ //Get the file name to save this file as
+ var filename = xhr.getResponseHeader("Content-Disposition");
+
+ if (!filename) {
+ filename =
+ model.get("fileName") ||
+ model.get("title") ||
+ model.get("id") ||
+ "download";
+ } else
+ filename = filename
+ .substring(filename.indexOf("filename=") + 9)
+ .replace(/"/g, "");
+
+ //Replace any whitespaces
+ filename = filename.trim().replace(/ /g, "_");
+
+ //For IE, we need to use the navigator API
+ if (navigator && navigator.msSaveOrOpenBlob) {
+ navigator.msSaveOrOpenBlob(xhr.response, filename);
+ }
+ //Other browsers can download it via a link
+ else {
+ var a = document.createElement("a");
+ a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob
+
+ // Set the file name.
+ a.download = filename;
+
+ a.style.display = "none";
+ document.body.appendChild(a);
+ a.click();
+ a.remove();
+ }
- //Get the file name to save this file as
- var filename = xhr.getResponseHeader('Content-Disposition');
+ model.trigger("downloadComplete");
+
+ // Track this event
+ MetacatUI.analytics?.trackEvent(
+ "download",
+ "Download DataONEObject",
+ model.get("id"),
+ );
+ };
+
+ xhr.onerror = function (e) {
+ model.trigger("downloadError");
+
+ // Track the error
+ MetacatUI.analytics?.trackException(
+ `Download DataONEObject error: ${e || ""}`,
+ model.get("id"),
+ true,
+ );
+ };
+
+ xhr.onprogress = function (e) {
+ if (e.lengthComputable) {
+ var percent = (e.loaded / e.total) * 100;
+ model.set("downloadPercent", percent);
+ }
+ };
- if(!filename){
- filename = model.get("fileName") || model.get("title") || model.get("id") || "download";
- }
- else
- filename = filename.substring(filename.indexOf("filename=")+9).replace(/"/g, "");
+ xhr.responseType = "blob";
- //Replace any whitespaces
- filename = filename.trim().replace(/ /g, "_");
+ if (MetacatUI.appUserModel.get("loggedIn"))
+ xhr.setRequestHeader(
+ "Authorization",
+ "Bearer " + MetacatUI.appUserModel.get("token"),
+ );
- //For IE, we need to use the navigator API
- if (navigator && navigator.msSaveOrOpenBlob) {
- navigator.msSaveOrOpenBlob(xhr.response, filename);
- }
- //Other browsers can download it via a link
- else{
- var a = document.createElement('a');
- a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob
-
- // Set the file name.
- a.download = filename
-
- a.style.display = 'none';
- document.body.appendChild(a);
- a.click();
- a.remove();
- }
+ xhr.send();
+ },
- model.trigger("downloadComplete");
+ /**
+ * Creates a file name for this DataONEObject and updates the `fileName` attribute
+ */
+ setMissingFileName: function () {
+ var objectFormats, filename, extension;
+
+ objectFormats = MetacatUI.objectFormats.where({
+ formatId: this.get("formatId"),
+ });
+ if (objectFormats.length > 0) {
+ extension = objectFormats[0].get("extension");
+ }
- // Track this event
- MetacatUI.analytics?.trackEvent(
- "download",
- "Download DataONEObject",
- model.get("id")
- );
- };
+ //Science metadata file names will use the title
+ if (this.get("type") == "Metadata") {
+ filename =
+ Array.isArray(this.get("title")) && this.get("title").length
+ ? this.get("title")[0]
+ : this.get("id");
+ }
+ //Resource maps will use a "resource_map_" prefix
+ else if (this.get("type") == "DataPackage") {
+ filename = "resource_map_" + this.get("id");
+ extension = ".rdf.xml";
+ }
+ //All other object types will just use the id
+ else {
+ filename = this.get("id");
+ }
- xhr.onerror = function(e){
- model.trigger("downloadError");
+ //Replace all non-alphanumeric characters with underscores
+ filename = filename.replace(/[^a-zA-Z0-9]/g, "_");
- // Track the error
- MetacatUI.analytics?.trackException(
- `Download DataONEObject error: ${e || ""}`, model.get("id"), true
- );
- };
+ if (typeof extension !== "undefined") {
+ filename = filename + "." + extension;
+ }
- xhr.onprogress = function(e){
- if (e.lengthComputable){
- var percent = (e.loaded / e.total) * 100;
- model.set("downloadPercent", percent);
- }
- };
+ this.set("fileName", filename);
+ },
- xhr.responseType = "blob";
+ /**
+ * Creates a URL for viewing more information about this object
+ * @return {string}
+ */
+ createViewURL: function () {
+ return (
+ MetacatUI.root +
+ "/view/" +
+ encodeURIComponent(this.get("seriesId") || this.get("id"))
+ );
+ },
- if(MetacatUI.appUserModel.get("loggedIn"))
- xhr.setRequestHeader("Authorization", "Bearer " + MetacatUI.appUserModel.get("token"));
+ /**
+ * Check if the seriesID or PID matches a DOI regex, and if so, return
+ * a canonical IRI for the DOI.
+ * @return {string|null} - The canonical IRI for the DOI, or null if
+ * neither the seriesId nor the PID match a DOI regex.
+ * @since 2.26.0
+ */
+ getCanonicalDOIIRI: function () {
+ const id = this.get("id");
+ const seriesId = this.get("seriesId");
+ let DOI = null;
+ if (this.isDOI(seriesId)) DOI = seriesId;
+ else if (this.isDOI(id)) DOI = id;
+ return MetacatUI.appModel.DOItoURL(DOI);
+ },
- xhr.send();
- },
+ /**
+ * Converts the identifier string to a string safe to use in an XML id attribute
+ * @param {string} [id] - The ID string
+ * @return {string} - The XML-safe string
+ */
+ getXMLSafeID: function (id) {
+ if (typeof id == "undefined") {
+ var id = this.get("id");
+ }
- /**
- * Creates a file name for this DataONEObject and updates the `fileName` attribute
- */
- setMissingFileName: function() {
- var objectFormats, filename, extension;
+ //Replace XML id attribute invalid characters and patterns in the identifier
+ id = id
+ .replace(/ 0 ) {
- extension = objectFormats[0].get("extension");
- }
+ return id;
+ },
- //Science metadata file names will use the title
- if( this.get("type") == "Metadata" ){
- filename = (Array.isArray(this.get("title")) && this.get("title").length)? this.get("title")[0] : this.get("id");
- }
- //Resource maps will use a "resource_map_" prefix
- else if( this.get("type") == "DataPackage" ){
- filename = "resource_map_" + this.get("id");
- extension = ".rdf.xml";
- }
- //All other object types will just use the id
- else{
- filename = this.get("id");
- }
+ /**** Provenance-related functions ****/
+ /**
+ * Returns true if this provenance field points to a source of this data or metadata object
+ * @param {string} field
+ * @returns {boolean}
+ */
+ isSourceField: function (field) {
+ if (typeof field == "undefined" || !field) return false;
+ // Is the field we are checking a prov field?
+ if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field))
+ return false;
+
+ if (
+ field == "prov_generatedByExecution" ||
+ field == "prov_generatedByProgram" ||
+ field == "prov_used" ||
+ field == "prov_wasDerivedFrom" ||
+ field == "prov_wasInformedBy"
+ )
+ return true;
+ else return false;
+ },
- //Replace all non-alphanumeric characters with underscores
- filename = filename.replace(/[^a-zA-Z0-9]/g, "_");
+ /**
+ * Returns true if this provenance field points to a derivation of this data or metadata object
+ * @param {string} field
+ * @returns {boolean}
+ */
+ isDerivationField: function (field) {
+ if (typeof field == "undefined" || !field) return false;
+ if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field))
+ return false;
+
+ if (
+ field == "prov_usedByExecution" ||
+ field == "prov_usedByProgram" ||
+ field == "prov_hasDerivations" ||
+ field == "prov_generated"
+ )
+ return true;
+ else return false;
+ },
- if ( typeof extension !== "undefined" ) {
- filename = filename + "." + extension;
- }
+ /**
+ * Returns a plain-english version of the general format - either image, program, metadata, PDF, annotation or data
+ */
+ getType: function () {
+ //The list of formatIds that are images
+
+ //The list of formatIds that are images
+ var pdfIds = ["application/pdf"];
+ var annotationIds = [
+ "http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html",
+ ];
+
+ // Type has already been set, use that.
+ if (this.get("type").toLowerCase() == "metadata") return "metadata";
+
+ //Determine the type via provONE
+ var instanceOfClass = this.get("prov_instanceOfClass");
+ if (
+ typeof instanceOfClass !== "undefined" &&
+ Array.isArray(instanceOfClass) &&
+ instanceOfClass.length
+ ) {
+ var programClass = _.filter(instanceOfClass, function (className) {
+ return className.indexOf("#Program") > -1;
+ });
+ if (typeof programClass !== "undefined" && programClass.length)
+ return "program";
+ } else {
+ if (this.get("prov_generated").length || this.get("prov_used").length)
+ return "program";
+ }
- this.set("fileName", filename);
- },
+ //Determine the type via file format
+ if (this.isSoftware()) return "program";
+ if (this.isData()) return "data";
- /**
- * Creates a URL for viewing more information about this object
- * @return {string}
- */
- createViewURL: function(){
- return MetacatUI.root + "/view/" + encodeURIComponent((this.get("seriesId") || this.get("id")));
- },
-
- /**
- * Check if the seriesID or PID matches a DOI regex, and if so, return
- * a canonical IRI for the DOI.
- * @return {string|null} - The canonical IRI for the DOI, or null if
- * neither the seriesId nor the PID match a DOI regex.
- * @since 2.26.0
- */
- getCanonicalDOIIRI: function () {
- const id = this.get("id");
- const seriesId = this.get("seriesId");
- let DOI = null;
- if (this.isDOI(seriesId)) DOI = seriesId;
- else if (this.isDOI(id)) DOI = id;
- return MetacatUI.appModel.DOItoURL(DOI);
- },
+ if (this.get("type").toLowerCase() == "metadata") return "metadata";
+ if (this.isImage()) return "image";
+ if (_.contains(pdfIds, this.get("formatId"))) return "PDF";
+ if (_.contains(annotationIds, this.get("formatId")))
+ return "annotation";
+ else return "data";
+ },
- /**
- * Converts the identifier string to a string safe to use in an XML id attribute
- * @param {string} [id] - The ID string
- * @return {string} - The XML-safe string
- */
- getXMLSafeID: function(id){
+ /**
+ * Checks the formatId of this model and determines if it is an image.
+ * @returns {boolean} true if this data object is an image, false if it is other
+ */
+ isImage: function () {
+ //The list of formatIds that are images
+ var imageIds = ["image/gif", "image/jp2", "image/jpeg", "image/png"];
+
+ //Does this data object match one of these IDs?
+ if (_.indexOf(imageIds, this.get("formatId")) == -1) return false;
+ else return true;
+ },
- if(typeof id == "undefined"){
- var id = this.get("id");
- }
+ /**
+ * Checks the formatId of this model and determines if it is a data file.
+ * This determination is mostly used for display and the provenance editor. In the
+ * DataONE API, many formatIds are considered `DATA` formatTypes, but they are categorized
+ * as images {@link DataONEObject#isImage} or software {@link DataONEObject#isSoftware}.
+ * @returns {boolean} true if this data object is a data file, false if it is other
+ */
+ isData: function () {
+ var dataIds = [
+ "application/atom+xml",
+ "application/mathematica",
+ "application/msword",
+ "application/netcdf",
+ "application/octet-stream",
+ "application/pdf",
+ "application/postscript",
+ "application/rdf+xml",
+ "application/rtf",
+ "application/vnd.google-earth.kml+xml",
+ "application/vnd.ms-excel",
+ "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
+ "application/vnd.ms-powerpoint",
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "application/x-bzip2",
+ "application/x-fasta",
+ "application/x-gzip",
+ "application/x-rar-compressed",
+ "application/x-tar",
+ "application/xhtml+xml",
+ "application/xml",
+ "application/zip",
+ "audio/mpeg",
+ "audio/x-ms-wma",
+ "audio/x-wav",
+ "image/svg xml",
+ "image/svg+xml",
+ "image/bmp",
+ "image/tiff",
+ "text/anvl",
+ "text/csv",
+ "text/html",
+ "text/n3",
+ "text/plain",
+ "text/tab-separated-values",
+ "text/turtle",
+ "text/xml",
+ "video/avi",
+ "video/mp4",
+ "video/mpeg",
+ "video/quicktime",
+ "video/x-ms-wmv",
+ ];
+
+ //Does this data object match one of these IDs?
+ if (_.indexOf(dataIds, this.get("formatId")) == -1) return false;
+ else return true;
+ },
- //Replace XML id attribute invalid characters and patterns in the identifier
- id = id.replace(/ -1);
- });
- if((typeof programClass !== "undefined") && programClass.length)
- return "program";
+ /**
+ * Calculate a checksum for the object
+ * @param {string} [algorithm] The algorithm to use, defaults to MD5
+ * @return {string} A checksum plain JS object with value and algorithm attributes
+ */
+ calculateChecksum: function (algorithm) {
+ var algorithm = algorithm || "MD5";
+ var checksum = { algorithm: undefined, value: undefined };
+ var hash; // The checksum hash
+ var file; // The file to be read by slicing
+ var reader; // The FileReader used to read each slice
+ var offset = 0; // Byte offset for reading slices
+ var sliceSize = Math.pow(2, 20); // 1MB slices
+ var model = this;
+
+ // Do we have a file?
+ if (this.get("uploadFile") instanceof Blob) {
+ file = this.get("uploadFile");
+ reader = new FileReader();
+ /* Handle load errors */
+ reader.onerror = function (event) {
+ console.log("Error reading: " + event);
+ };
+ /* Show progress */
+ reader.onprogress = function (event) {};
+ /* Handle load finish */
+ reader.onloadend = function (event) {
+ if (event.target.readyState == FileReader.DONE) {
+ hash.update(event.target.result);
}
- else{
- if(this.get("prov_generated").length || this.get("prov_used").length)
- return "program";
+ offset += sliceSize;
+ if (_seek()) {
+ model.set("checksum", hash.hex());
+ model.set("checksumAlgorithm", checksum.algorithm);
+ model.trigger("checksumCalculated", model.attributes);
}
+ };
+ } else {
+ message = "The given object is not a blob or a file object.";
+ throw new Error(message);
+ }
- //Determine the type via file format
- if(this.isSoftware()) return "program";
- if(this.isData()) return "data";
-
- if(this.get("type").toLowerCase() == "metadata") return "metadata";
- if(this.isImage()) return "image";
- if(_.contains(pdfIds, this.get("formatId"))) return "PDF";
- if(_.contains(annotationIds, this.get("formatId"))) return "annotation";
-
- else return "data";
- },
-
- /**
- * Checks the formatId of this model and determines if it is an image.
- * @returns {boolean} true if this data object is an image, false if it is other
- */
- isImage: function(){
- //The list of formatIds that are images
- var imageIds = ["image/gif",
- "image/jp2",
- "image/jpeg",
- "image/png"];
-
- //Does this data object match one of these IDs?
- if(_.indexOf(imageIds, this.get('formatId')) == -1) return false;
- else return true;
-
- },
-
- /**
- * Checks the formatId of this model and determines if it is a data file.
- * This determination is mostly used for display and the provenance editor. In the
- * DataONE API, many formatIds are considered `DATA` formatTypes, but they are categorized
- * as images {@link DataONEObject#isImage} or software {@link DataONEObject#isSoftware}.
- * @returns {boolean} true if this data object is a data file, false if it is other
- */
- isData: function() {
- var dataIds = ["application/atom+xml",
- "application/mathematica",
- "application/msword",
- "application/netcdf",
- "application/octet-stream",
- "application/pdf",
- "application/postscript",
- "application/rdf+xml",
- "application/rtf",
- "application/vnd.google-earth.kml+xml",
- "application/vnd.ms-excel",
- "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
- "application/vnd.ms-powerpoint",
- "application/vnd.openxmlformats-officedocument.presentationml.presentation",
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- "application/x-bzip2",
- "application/x-fasta",
- "application/x-gzip",
- "application/x-rar-compressed",
- "application/x-tar",
- "application/xhtml+xml",
- "application/xml",
- "application/zip",
- "audio/mpeg",
- "audio/x-ms-wma",
- "audio/x-wav",
- "image/svg xml",
- "image/svg+xml",
- "image/bmp",
- "image/tiff",
- "text/anvl",
- "text/csv",
- "text/html",
- "text/n3",
- "text/plain",
- "text/tab-separated-values",
- "text/turtle",
- "text/xml",
- "video/avi",
- "video/mp4",
- "video/mpeg",
- "video/quicktime",
- "video/x-ms-wmv"];
-
- //Does this data object match one of these IDs?
- if(_.indexOf(dataIds, this.get('formatId')) == -1) return false;
- else return true;
- },
-
- /**
- * Checks the formatId of this model and determines if it is a software file.
- * This determination is mostly used for display and the provenance editor. In the
- * DataONE API, many formatIds are considered `DATA` formatTypes, but they are categorized
- * as images {@link DataONEObject#isImage} for display purposes.
- * @returns {boolean} true if this data object is a software file, false if it is other
- */
- isSoftware: function(){
- //The list of formatIds that are programs
- var softwareIds = ["text/x-python",
- "text/x-rsrc",
- "text/x-matlab",
- "text/x-sas",
- "application/R",
- "application/x-ipynb+json"];
- //Does this data object match one of these IDs?
- if(_.indexOf(softwareIds, this.get('formatId')) == -1) return false;
- else return true;
- },
-
- /**
- * Checks the formatId of this model and determines if it a PDF.
- * @returns {boolean} true if this data object is a pdf, false if it is other
- */
- isPDF: function(){
- //The list of formatIds that are images
- var ids = ["application/pdf"];
-
- //Does this data object match one of these IDs?
- if(_.indexOf(ids, this.get('formatId')) == -1) return false;
- else return true;
- },
+ switch (algorithm) {
+ case "MD5":
+ checksum.algorithm = algorithm;
+ hash = md5.create();
+ _seek();
+ break;
+ case "SHA-1":
+ // TODO: Support SHA-1
+ // break;
+ default:
+ message =
+ "The given algorithm: " + algorithm + " is not supported.";
+ throw new Error(message);
+ }
- /**
- * Set the DataONE ProvONE provenance class
- * param className - the shortened form of the actual classname value. The
- * shortname will be appened to the ProvONE namespace, for example,
- * the className "program" will result in the final class name
- * "http://purl.dataone.org/provone/2015/01/15/ontology#Program"
- * see https://github.com/DataONEorg/sem-prov-ontologies/blob/master/provenance/ProvONE/v1/provone.html
- * @param {string} className
- */
- setProvClass: function(className) {
- className = className.toLowerCase();
- className = className.charAt(0).toUpperCase() + className.slice(1)
-
- /* This function is intended to be used for the ProvONE classes that are
- * typically represented in DataONEObjects: "Data", "Program", and hopefully
- * someday "Execution", as we don't allow the user to set the namespace
- * e.g. to "PROV", so therefor we check for the currently known ProvONE classes.
- */
- if (_.contains(['Program', 'Data', 'Visualization', 'Document', 'Execution', 'User'], className)) {
- this.set("prov_instanceOfClass", [this.PROVONE + className]);
- } else if (_.contains(['Entity', 'Usage', 'Generation', 'Association'], className)) {
- this.set("prov_instanceOfClass", [this.PROV + className]);
- } else {
- message = "The given class name: " + className + " is not in the known ProvONE or PROV classes."
- throw new Error(message);
- }
- },
+ /*
+ * A helper function internal to calculateChecksum() used to slice
+ * the file at the next offset by slice size
+ */
+ function _seek() {
+ var calculated = false;
+ var slice;
+ // Digest the checksum when we're done calculating
+ if (offset >= file.size) {
+ hash.digest();
+ calculated = true;
+ return calculated;
+ }
+ // slice the file and read the slice
+ slice = file.slice(offset, offset + sliceSize);
+ reader.readAsArrayBuffer(slice);
+ return calculated;
+ }
+ },
- /**
- * Calculate a checksum for the object
- * @param {string} [algorithm] The algorithm to use, defaults to MD5
- * @return {string} A checksum plain JS object with value and algorithm attributes
- */
- calculateChecksum: function(algorithm) {
- var algorithm = algorithm || "MD5";
- var checksum = {algorithm: undefined, value: undefined};
- var hash; // The checksum hash
- var file; // The file to be read by slicing
- var reader; // The FileReader used to read each slice
- var offset = 0; // Byte offset for reading slices
- var sliceSize = Math.pow(2,20) // 1MB slices
- var model = this;
-
- // Do we have a file?
- if (this.get("uploadFile") instanceof Blob) {
- file = this.get("uploadFile");
- reader = new FileReader();
- /* Handle load errors */
- reader.onerror = function(event) {
- console.log("Error reading: " + event);
- };
- /* Show progress */
- reader.onprogress = function(event) {
- };
- /* Handle load finish */
- reader.onloadend = function(event) {
- if (event.target.readyState == FileReader.DONE) {
- hash.update(event.target.result);
- }
- offset += sliceSize;
- if ( _seek() ) {
- model.set("checksum", hash.hex());
- model.set("checksumAlgorithm", checksum.algorithm);
- model.trigger("checksumCalculated", model.attributes);
- };
- };
- } else {
- message = "The given object is not a blob or a file object."
- throw new Error(message);
- }
+ /**
+ * Checks if the pid or sid or given string is a DOI
+ *
+ * @param {string} customString - Optional. An identifier string to check instead of the id and seriesId attributes on the model
+ * @returns {boolean} True if it is a DOI
+ */
+ isDOI: function (customString) {
+ return (
+ isDOI(customString) ||
+ isDOI(this.get("id")) ||
+ isDOI(this.get("seriesId"))
+ );
+ },
- switch ( algorithm ) {
- case "MD5":
- checksum.algorithm = algorithm;
- hash = md5.create();
- _seek();
- break;
- case "SHA-1":
- // TODO: Support SHA-1
- // break;
- default:
- message = "The given algorithm: " + algorithm + " is not supported."
- throw new Error(message);
+ /**
+ * Creates an array of objects that represent Member Nodes that could possibly be this
+ * object's authoritative MN. This function updates the `possibleAuthMNs` attribute on this model.
+ */
+ setPossibleAuthMNs: function () {
+ //Only do this for Coordinating Node MetacatUIs.
+ if (MetacatUI.appModel.get("alternateRepositories").length) {
+ //Set the possibleAuthMNs attribute
+ var possibleAuthMNs = [];
+
+ //If a datasource is already found for this Portal, move that to the top of the list of auth MNs
+ var datasource = this.get("datasource") || "";
+ if (datasource) {
+ //Find the MN object that matches the datasource node ID
+ var datasourceMN = _.findWhere(
+ MetacatUI.appModel.get("alternateRepositories"),
+ { identifier: datasource },
+ );
+ if (datasourceMN) {
+ //Clone the MN object and add it to the array
+ var clonedDatasourceMN = Object.assign({}, datasourceMN);
+ possibleAuthMNs.push(clonedDatasourceMN);
}
+ }
- /*
- * A helper function internal to calculateChecksum() used to slice
- * the file at the next offset by slice size
- */
- function _seek() {
- var calculated = false;
- var slice;
- // Digest the checksum when we're done calculating
- if (offset >= file.size) {
- hash.digest();
- calculated = true;
- return calculated;
- }
- // slice the file and read the slice
- slice = file.slice(offset, offset + sliceSize);
- reader.readAsArrayBuffer(slice);
- return calculated;
-
+ //If there is an active alternate repo, move that to the top of the list of auth MNs
+ var activeAltRepo =
+ MetacatUI.appModel.get("activeAlternateRepositoryId") || "";
+ if (activeAltRepo) {
+ var activeAltRepoMN = _.findWhere(
+ MetacatUI.appModel.get("alternateRepositories"),
+ { identifier: activeAltRepo },
+ );
+ if (activeAltRepoMN) {
+ //Clone the MN object and add it to the array
+ var clonedActiveAltRepoMN = Object.assign({}, activeAltRepoMN);
+ possibleAuthMNs.push(clonedActiveAltRepoMN);
}
- },
+ }
- /**
- * Checks if the pid or sid or given string is a DOI
- *
- * @param {string} customString - Optional. An identifier string to check instead of the id and seriesId attributes on the model
- * @returns {boolean} True if it is a DOI
- */
- isDOI: function(customString) {
- return isDOI(customString) ||
- isDOI(this.get("id")) ||
- isDOI(this.get("seriesId"));
- },
+ //Add all the other alternate repositories to the list of auth MNs
+ var otherPossibleAuthMNs = _.reject(
+ MetacatUI.appModel.get("alternateRepositories"),
+ function (mn) {
+ return (
+ mn.identifier == datasource || mn.identifier == activeAltRepo
+ );
+ },
+ );
+ //Clone each MN object and add to the array
+ _.each(otherPossibleAuthMNs, function (mn) {
+ var clonedMN = Object.assign({}, mn);
+ possibleAuthMNs.push(clonedMN);
+ });
- /**
- * Creates an array of objects that represent Member Nodes that could possibly be this
- * object's authoritative MN. This function updates the `possibleAuthMNs` attribute on this model.
- */
- setPossibleAuthMNs: function(){
-
- //Only do this for Coordinating Node MetacatUIs.
- if( MetacatUI.appModel.get("alternateRepositories").length ){
- //Set the possibleAuthMNs attribute
- var possibleAuthMNs = [];
-
- //If a datasource is already found for this Portal, move that to the top of the list of auth MNs
- var datasource = this.get("datasource") || "";
- if( datasource ){
- //Find the MN object that matches the datasource node ID
- var datasourceMN = _.findWhere(MetacatUI.appModel.get("alternateRepositories"), { identifier: datasource });
- if( datasourceMN ){
- //Clone the MN object and add it to the array
- var clonedDatasourceMN = Object.assign({}, datasourceMN);
- possibleAuthMNs.push(clonedDatasourceMN);
- }
- }
+ //Update this model
+ this.set("possibleAuthMNs", possibleAuthMNs);
+ }
+ },
- //If there is an active alternate repo, move that to the top of the list of auth MNs
- var activeAltRepo = MetacatUI.appModel.get("activeAlternateRepositoryId") || "";
- if( activeAltRepo ){
- var activeAltRepoMN = _.findWhere(MetacatUI.appModel.get("alternateRepositories"), { identifier: activeAltRepo });
- if( activeAltRepoMN ){
- //Clone the MN object and add it to the array
- var clonedActiveAltRepoMN = Object.assign({}, activeAltRepoMN);
- possibleAuthMNs.push(clonedActiveAltRepoMN);
- }
+ /**
+ * Removes white space from string values returned by Solr when the white space causes issues.
+ * For now this only effects the `resourceMap` field, which will index new line characters and spaces
+ * when the RDF XML has those in the `identifier` XML element content. This was causing bugs where DataONEObject
+ * models were created with `id`s with new line and white space characters (e.g. `\n urn:uuid:1234...`)
+ * @param {object} json - The Solr document as a JS Object, which will be directly altered
+ */
+ removeWhiteSpaceFromSolrFields: function (json) {
+ if (typeof json.resourceMap == "string") {
+ json.resourceMap = json.resourceMap.trim();
+ } else if (Array.isArray(json.resourceMap)) {
+ let newResourceMapIds = [];
+ _.each(json.resourceMap, function (rMapId) {
+ if (typeof rMapId == "string") {
+ newResourceMapIds.push(rMapId.trim());
}
-
- //Add all the other alternate repositories to the list of auth MNs
- var otherPossibleAuthMNs = _.reject(MetacatUI.appModel.get("alternateRepositories"), function(mn){
- return (mn.identifier == datasource || mn.identifier == activeAltRepo);
- });
- //Clone each MN object and add to the array
- _.each(otherPossibleAuthMNs, function(mn){
- var clonedMN = Object.assign({}, mn);
- possibleAuthMNs.push(clonedMN);
- });
-
- //Update this model
- this.set("possibleAuthMNs", possibleAuthMNs);
-
- }
- },
-
- /**
- * Removes white space from string values returned by Solr when the white space causes issues.
- * For now this only effects the `resourceMap` field, which will index new line characters and spaces
- * when the RDF XML has those in the `identifier` XML element content. This was causing bugs where DataONEObject
- * models were created with `id`s with new line and white space characters (e.g. `\n urn:uuid:1234...`)
- * @param {object} json - The Solr document as a JS Object, which will be directly altered
- */
- removeWhiteSpaceFromSolrFields: function(json){
- if( typeof json.resourceMap == "string" ){
- json.resourceMap = json.resourceMap.trim();
- }
- else if( Array.isArray(json.resourceMap) ){
- let newResourceMapIds = [];
- _.each(json.resourceMap, function(rMapId){
- if( typeof rMapId == "string" ){
- newResourceMapIds.push(rMapId.trim());
- }
- });
- json.resourceMap = newResourceMapIds;
- }
+ });
+ json.resourceMap = newResourceMapIds;
}
+ },
},
/** @lends DataONEObject.prototype */
{
/**
- * Generate a unique identifier to be used as an XML id attribute
- * @returns {string} The identifier string that was generated
- */
- generateId: function() {
- var idStr = ''; // the id to return
+ * Generate a unique identifier to be used as an XML id attribute
+ * @returns {string} The identifier string that was generated
+ */
+ generateId: function () {
+ var idStr = ""; // the id to return
var length = 30; // the length of the generated string
- var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'.split('');
+ var chars =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".split(
+ "",
+ );
for (var i = 0; i < length; i++) {
idStr += chars[Math.floor(Math.random() * chars.length)];
}
return idStr;
- }
- });
+ },
+ },
+ );
- return DataONEObject;
+ return DataONEObject;
});
diff --git a/src/js/models/LogsSearch.js b/src/js/models/LogsSearch.js
index 7798f0ada..bcfcb3699 100644
--- a/src/js/models/LogsSearch.js
+++ b/src/js/models/LogsSearch.js
@@ -1,194 +1,245 @@
/*global define */
-define(['jquery', 'underscore', 'backbone', 'models/Search'],
- function($, _, Backbone, SearchModel) {
- 'use strict';
-
- /**
- * @class LogsSearch
- * @classdesc Searches the DataONE aggregated event logs. The DataONE Metrics Service has replaced
- * the DataONE event log service.
- * @deprecated
- * @classcategory Deprecated
- */
- var LogsSearch = SearchModel.extend(
- /** @lends LogsSearch.prototype */{
- // This model contains all of the search/filter terms
- /*
- * Search filters can be either plain text or a filter object with the following options:
- * filterLabel - text that will be displayed in the filter element in the UI
- * label - text that will be displayed in the autocomplete list
- * value - the value that will be included in the query
- * description - a longer text description of the filter value
- * @classcategory Models
- */
- defaults: function(){
- return {
- all: [],
- dateLogged: [],
- nodeId: MetacatUI.appModel.get("nodeId") || null,
- id: [],
- pid: [],
- event: [],
- userAgent: [],
- dateAggregated: [],
- inPartialRobotList: "false",
- isRepeatVisit: "false",
- isPublic: [],
- entryId: [],
- city: [],
- region: [],
- country: [],
- location: [],
- geohashes: [],
- geohashLevel: 9,
- geohashGroups: {},
- username: [],
- size: [],
- formatId: [],
- formatType: [],
- exclude: [{
- field: null,
- value: null
- }],
- facets: [],
- facetRanges: [],
- facetRangeStart: function(){
- var twentyYrsAgo = new Date();
- twentyYrsAgo.setFullYear( twentyYrsAgo.getFullYear() - 20 );
- return twentyYrsAgo.toISOString();
- }(),
- facetRangeEnd: function(){
- var now = new Date();
- return now.toISOString();
- }(),
- facetRangeGap: "%2B1MONTH",
- facetMinCount: "1"
- }
- },
-
- initialize: function(){
- this.listenTo(this, "change:geohashes", this.groupGeohashes);
- },
-
- //Map the filter names to their index field names
- fieldNameMap: {
- all: "",
- dateLogged: "dateLogged",
- datasource: "nodeId",
- nodeId: "nodeId",
- id: "id",
- pid: "pid",
- event: "event",
- userAgent: "userAgent",
- dateAggregated: "dateAggregated",
- isPublic: "isPublic",
- entryId: "entryId",
- city: "city",
- region: "region",
- country: "country",
- location: "location",
- size: "size",
- username: "rightsHolder",
- formatId: "formatId",
- formatType: "formatType",
- inPartialRobotList : "inPartialRobotList",
- inFullRobotList : "inFullRobotList",
- isRepeatVisit : "isRepeatVisit"
- },
-
- setNodeId: function(){
- if(MetacatUI.nodeModel.get("currentMemberNode"))
- this.set("nodeId", MetacatUI.nodeModel.get("currentMemberNode"));
- },
-
- /*
- * Get the query string based on the attributes set in this model
- */
- getQuery: function(){
- var query = "",
- model = this;
-
- var otherFilters = ["event", "formatType", "formatId", "id", "pid", "userAgent", "inPartialRobotList", "inFullRobotList", "isRepeatVisit", "dateAggregated", "dateLogged", "entryId", "city", "region", "location", "size", "username"];
-
- //-------nodeId--------
- //Update the Node Id
- if(!this.get("nodeId"))
- this.setNodeId();
-
- if(this.filterIsAvailable("nodeId") && this.get("nodeId")){
- var value = this.get("nodeId");
-
- //Don't filter by nodeId when it is set to a CN
- if((typeof value == "string") && (value.substr(value.lastIndexOf(":")+1, 2).toLowerCase() != "cn")){
- //For multiple values
- if(Array.isArray(value) && value.length){
- query += "+" + model.getGroupedQuery(model.fieldNameMap["nodeId"], value, { operator: "OR", subtext: false });
- }
- else if(value && value.length){
- // Does this need to be wrapped in quotes?
- if(model.needsQuotes(value)) value = "%22" + encodeURIComponent(value) + "%22";
- else value = model.escapeSpecialChar(encodeURIComponent(value));
-
- query += "+" + model.fieldNameMap["nodeId"] + ":" + value;
- }
- }
- else if(Array.isArray(value)){
- query += "+" + model.getGroupedQuery(model.fieldNameMap["nodeId"], value, { operator: "OR", subtext: false });
- }
- }
-
- //-----Other Filters/Basic Filters-----
- _.each(otherFilters, function(filterName){
- if(model.filterIsAvailable(filterName)){
- var filterValue = null;
- var filterValues = model.get(filterName);
-
- //Check that this filter is set
- if((typeof filterValues == "undefined") || !filterValues) return;
-
- //For multiple values
- if(Array.isArray(filterValues) && filterValues.length){
- query += "+" + model.getGroupedQuery(model.fieldNameMap[filterName], filterValues, { operator: "OR", subtext: false });
- }
- else if(filterValues && filterValues.length){
- // Does this need to be wrapped in quotes?
- if(model.needsQuotes(filterValues)) filterValues = "%22" + encodeURIComponent(filterValues) + "%22";
- else filterValues = model.escapeSpecialChar(encodeURIComponent(filterValues));
-
- query += "+" + model.fieldNameMap[filterName] + ":" + filterValues;
- }
- }
- });
-
- return query;
- },
-
- getFacetQuery: function(){
- var query = "&facet=true&facet.limit=-1&facet.mincount=" + this.get("facetMinCount"),
- model = this;
-
- if(typeof this.get("facets") == "string")
- this.set("facets", [this.get("facets")]);
- _.each(this.get("facets"), function(facetField, i, list){
-
- if(model.filterIsAvailable(facetField)){
- query += "&facet.field=" + facetField;
- }
- });
-
- _.each(this.get("facetRanges"), function(facetField, i, list){
- if(model.filterIsAvailable(facetField)){
- query += "&facet.range=" + facetField +
- "&facet.range.start=" + model.get("facetRangeStart") +
- "&facet.range.end=" + model.get("facetRangeEnd") +
- "&facet.range.gap=" + model.get("facetRangeGap");
- return;
- }
- });
-
- return query;
- }
-
- });
- return LogsSearch;
+define(["jquery", "underscore", "backbone", "models/Search"], function (
+ $,
+ _,
+ Backbone,
+ SearchModel,
+) {
+ "use strict";
+
+ /**
+ * @class LogsSearch
+ * @classdesc Searches the DataONE aggregated event logs. The DataONE Metrics Service has replaced
+ * the DataONE event log service.
+ * @deprecated
+ * @classcategory Deprecated
+ */
+ var LogsSearch = SearchModel.extend(
+ /** @lends LogsSearch.prototype */ {
+ // This model contains all of the search/filter terms
+ /*
+ * Search filters can be either plain text or a filter object with the following options:
+ * filterLabel - text that will be displayed in the filter element in the UI
+ * label - text that will be displayed in the autocomplete list
+ * value - the value that will be included in the query
+ * description - a longer text description of the filter value
+ * @classcategory Models
+ */
+ defaults: function () {
+ return {
+ all: [],
+ dateLogged: [],
+ nodeId: MetacatUI.appModel.get("nodeId") || null,
+ id: [],
+ pid: [],
+ event: [],
+ userAgent: [],
+ dateAggregated: [],
+ inPartialRobotList: "false",
+ isRepeatVisit: "false",
+ isPublic: [],
+ entryId: [],
+ city: [],
+ region: [],
+ country: [],
+ location: [],
+ geohashes: [],
+ geohashLevel: 9,
+ geohashGroups: {},
+ username: [],
+ size: [],
+ formatId: [],
+ formatType: [],
+ exclude: [
+ {
+ field: null,
+ value: null,
+ },
+ ],
+ facets: [],
+ facetRanges: [],
+ facetRangeStart: (function () {
+ var twentyYrsAgo = new Date();
+ twentyYrsAgo.setFullYear(twentyYrsAgo.getFullYear() - 20);
+ return twentyYrsAgo.toISOString();
+ })(),
+ facetRangeEnd: (function () {
+ var now = new Date();
+ return now.toISOString();
+ })(),
+ facetRangeGap: "%2B1MONTH",
+ facetMinCount: "1",
+ };
+ },
+
+ initialize: function () {
+ this.listenTo(this, "change:geohashes", this.groupGeohashes);
+ },
+
+ //Map the filter names to their index field names
+ fieldNameMap: {
+ all: "",
+ dateLogged: "dateLogged",
+ datasource: "nodeId",
+ nodeId: "nodeId",
+ id: "id",
+ pid: "pid",
+ event: "event",
+ userAgent: "userAgent",
+ dateAggregated: "dateAggregated",
+ isPublic: "isPublic",
+ entryId: "entryId",
+ city: "city",
+ region: "region",
+ country: "country",
+ location: "location",
+ size: "size",
+ username: "rightsHolder",
+ formatId: "formatId",
+ formatType: "formatType",
+ inPartialRobotList: "inPartialRobotList",
+ inFullRobotList: "inFullRobotList",
+ isRepeatVisit: "isRepeatVisit",
+ },
+
+ setNodeId: function () {
+ if (MetacatUI.nodeModel.get("currentMemberNode"))
+ this.set("nodeId", MetacatUI.nodeModel.get("currentMemberNode"));
+ },
+
+ /*
+ * Get the query string based on the attributes set in this model
+ */
+ getQuery: function () {
+ var query = "",
+ model = this;
+
+ var otherFilters = [
+ "event",
+ "formatType",
+ "formatId",
+ "id",
+ "pid",
+ "userAgent",
+ "inPartialRobotList",
+ "inFullRobotList",
+ "isRepeatVisit",
+ "dateAggregated",
+ "dateLogged",
+ "entryId",
+ "city",
+ "region",
+ "location",
+ "size",
+ "username",
+ ];
+
+ //-------nodeId--------
+ //Update the Node Id
+ if (!this.get("nodeId")) this.setNodeId();
+
+ if (this.filterIsAvailable("nodeId") && this.get("nodeId")) {
+ var value = this.get("nodeId");
+
+ //Don't filter by nodeId when it is set to a CN
+ if (
+ typeof value == "string" &&
+ value.substr(value.lastIndexOf(":") + 1, 2).toLowerCase() != "cn"
+ ) {
+ //For multiple values
+ if (Array.isArray(value) && value.length) {
+ query +=
+ "+" +
+ model.getGroupedQuery(model.fieldNameMap["nodeId"], value, {
+ operator: "OR",
+ subtext: false,
+ });
+ } else if (value && value.length) {
+ // Does this need to be wrapped in quotes?
+ if (model.needsQuotes(value))
+ value = "%22" + encodeURIComponent(value) + "%22";
+ else value = model.escapeSpecialChar(encodeURIComponent(value));
+
+ query += "+" + model.fieldNameMap["nodeId"] + ":" + value;
+ }
+ } else if (Array.isArray(value)) {
+ query +=
+ "+" +
+ model.getGroupedQuery(model.fieldNameMap["nodeId"], value, {
+ operator: "OR",
+ subtext: false,
+ });
+ }
+ }
+
+ //-----Other Filters/Basic Filters-----
+ _.each(otherFilters, function (filterName) {
+ if (model.filterIsAvailable(filterName)) {
+ var filterValue = null;
+ var filterValues = model.get(filterName);
+
+ //Check that this filter is set
+ if (typeof filterValues == "undefined" || !filterValues) return;
+
+ //For multiple values
+ if (Array.isArray(filterValues) && filterValues.length) {
+ query +=
+ "+" +
+ model.getGroupedQuery(
+ model.fieldNameMap[filterName],
+ filterValues,
+ { operator: "OR", subtext: false },
+ );
+ } else if (filterValues && filterValues.length) {
+ // Does this need to be wrapped in quotes?
+ if (model.needsQuotes(filterValues))
+ filterValues = "%22" + encodeURIComponent(filterValues) + "%22";
+ else
+ filterValues = model.escapeSpecialChar(
+ encodeURIComponent(filterValues),
+ );
+
+ query +=
+ "+" + model.fieldNameMap[filterName] + ":" + filterValues;
+ }
+ }
+ });
+
+ return query;
+ },
+
+ getFacetQuery: function () {
+ var query =
+ "&facet=true&facet.limit=-1&facet.mincount=" +
+ this.get("facetMinCount"),
+ model = this;
+
+ if (typeof this.get("facets") == "string")
+ this.set("facets", [this.get("facets")]);
+ _.each(this.get("facets"), function (facetField, i, list) {
+ if (model.filterIsAvailable(facetField)) {
+ query += "&facet.field=" + facetField;
+ }
+ });
+
+ _.each(this.get("facetRanges"), function (facetField, i, list) {
+ if (model.filterIsAvailable(facetField)) {
+ query +=
+ "&facet.range=" +
+ facetField +
+ "&facet.range.start=" +
+ model.get("facetRangeStart") +
+ "&facet.range.end=" +
+ model.get("facetRangeEnd") +
+ "&facet.range.gap=" +
+ model.get("facetRangeGap");
+ return;
+ }
+ });
+
+ return query;
+ },
+ },
+ );
+ return LogsSearch;
});
diff --git a/src/js/models/LookupModel.js b/src/js/models/LookupModel.js
index 805203a67..e0eb4c5a6 100644
--- a/src/js/models/LookupModel.js
+++ b/src/js/models/LookupModel.js
@@ -3,7 +3,7 @@ define(["jquery", "jqueryui", "underscore", "backbone"], function (
$,
$ui,
_,
- Backbone
+ Backbone,
) {
"use strict";
@@ -216,7 +216,7 @@ define(["jquery", "jqueryui", "underscore", "backbone"], function (
item["class"] = uri;
item["ontology"] = "http://data.bioontology.org/ontologies/ECSO";
batchData["http://www.w3.org/2002/07/owl#Class"]["collection"].push(
- item
+ item,
);
});
@@ -390,7 +390,7 @@ define(["jquery", "jqueryui", "underscore", "backbone"], function (
request,
response,
beforeRequest,
- afterRequest
+ afterRequest,
) {
// Handle errors in this function or in the findGrants function
function handleError(error) {
@@ -500,7 +500,7 @@ define(["jquery", "jqueryui", "underscore", "backbone"], function (
//If the parsing XML failed, exit now
console.error(
"The accounts service did not return valid XML.",
- e
+ e,
);
return;
}
@@ -541,14 +541,14 @@ define(["jquery", "jqueryui", "underscore", "backbone"], function (
")",
type: type,
});
- }
+ },
);
var term = $.ui.autocomplete.escapeRegex(request.term),
startsWithMatcher = new RegExp("^" + term, "i"),
startsWith = $.grep(list, function (value) {
return startsWithMatcher.test(
- value.label || value.value || value
+ value.label || value.value || value,
);
}),
containsMatcher = new RegExp(term, "i"),
@@ -600,7 +600,7 @@ define(["jquery", "jqueryui", "underscore", "backbone"], function (
},
success: function (data) {
var sizeOfQueue = parseInt(
- $(data).find("status > index > sizeOfQueue").text()
+ $(data).find("status > index > sizeOfQueue").text(),
);
if (sizeOfQueue > 0 || sizeOfQueue == 0) {
@@ -621,8 +621,8 @@ define(["jquery", "jqueryui", "underscore", "backbone"], function (
$.ajax(
_.extend(
requestSettings,
- MetacatUI.appUserModel.createAjaxSettings()
- )
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
);
} catch (e) {
console.error(e);
@@ -632,7 +632,7 @@ define(["jquery", "jqueryui", "underscore", "backbone"], function (
}
}
},
- }
+ },
);
return LookupModel;
});
diff --git a/src/js/models/Map.js b/src/js/models/Map.js
index 78aacc691..ea9323257 100644
--- a/src/js/models/Map.js
+++ b/src/js/models/Map.js
@@ -1,198 +1,247 @@
/*global define */
-define(['jquery', 'underscore', 'backbone', 'gmaps'],
- function($, _, Backbone, gmaps) {
- 'use strict';
+define(["jquery", "underscore", "backbone", "gmaps"], function (
+ $,
+ _,
+ Backbone,
+ gmaps,
+) {
+ "use strict";
/**
- * @class Map
- * @classdesc The Map Model represents all of the settings and options for a Google Map.
- * @classcategory Models
- * @extends Backbone.Model
- */
- var Map = Backbone.Model.extend(
- /** @lends Map.prototype */{
- // This model contains all of the map settings used for searching datasets
- defaults: function(){
- var model = this;
- return {
- map: null,
-
- //The options for the map using the Google Maps API MapOptions syntax
- mapOptions:
- (gmaps)?
- { zoom: 3,
- minZoom: 3,
- maxZoom: 16,
- center: new google.maps.LatLng(44, -103),
- disableDefaultUI: true,
- zoomControl: true,
- zoomControlOptions: {
- style: google.maps.ZoomControlStyle.LARGE,
- position: google.maps.ControlPosition.LEFT_CENTER
- },
- panControl: false,
- scaleControl: true,
- streetViewControl: false,
- mapTypeControl: true,
- mapTypeControlOptions:{
- style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
- mapTypeIds: [google.maps.MapTypeId.SATELLITE, google.maps.MapTypeId.TERRAIN],
- position: google.maps.ControlPosition.LEFT_BOTTOM
- },
- mapTypeId: google.maps.MapTypeId.TERRAIN,
- styles: [{"featureType":"water","stylers":[{"visibility":"on"},{"color":"#b5cbe4"}]},{"featureType":"landscape","stylers":[{"color":"#efefef"}]},{"featureType":"road.highway","elementType":"geometry","stylers":[{"color":"#83a5b0"}]},{"featureType":"road.arterial","elementType":"geometry","stylers":[{"color":"#bdcdd3"}]},{"featureType":"road.local","elementType":"geometry","stylers":[{"color":"#ffffff"}]},{"featureType":"poi.park","elementType":"geometry","stylers":[{"color":"#e3eed3"}]},{"featureType":"administrative","stylers":[{"visibility":"on"},{"lightness":33}]},{"featureType":"road"},{"featureType":"poi.park","elementType":"labels","stylers":[{"visibility":"on"},{"lightness":20}]},{},{"featureType":"road","stylers":[{"lightness":20}]}]
- }
- : null,
-
- //Set to true to draw markers where tile counts are equal to 1. If set to false, a tile with the count "1" will be drawn instead.
- drawMarkers: false,
-
- //If this theme doesn't have an image in this location, Google maps will use their default marker image
- markerImage: "./js/themes/" + MetacatUI.theme + "/img/map-marker.png",
-
- //Keep track of the geohash level used to draw tiles on this map
- tileGeohashLevel: 1,
-
- ///****** MAP TILE OPTIONS **********//
- //The options for the tiles. Using Google Maps Web API
- tileOptions: {
- fillOpacity: 0.2,
- strokeWeight: 1,
- strokePosition: (typeof google != "undefined")? google.maps.StrokePosition.INSIDE : "",
- strokeOpacity: 1
- },
-
- //The options for the tiles when they are hovered on. Using Google Maps Web API
- tileOnHover: {
- fillOpacity: 0.8,
- strokeColor: "#FFFF00",
- strokePosition: (typeof google != "undefined")? google.maps.StrokePosition.INSIDE : "",
- strokeWeight: 1,
- strokeOpacity: 1,
- fillColor: "#FFFF66"
- },
-
- //The options for the tile text
- tileLabelColorOnHover: '#000000',
- tileLabelColor: '#444444',
-
- //The tile hue - the number of the hue that will be used to color tiles
- //Tile lightness - percent range of lightness/brightness of this tile hue
- tileHue: MetacatUI.appModel.get("searchMapTileHue") || "192",
- tileLightnessMax: 100,
- tileLightnessMin: 30
- }
- },
-
- initialize: function(options){
-
- },
-
- isMaxZoom: function(map){
- var zoom = map.getZoom(),
- type = map.getMapTypeId();
-
- if(zoom >= this.get("mapOptions").maxZoom) return true;
- else return false;
- },
-
- /**
- * This function will return the appropriate geohash level to use for mapping geohash tiles on the map at the specified zoom level.
- */
- determineGeohashLevel: function(zoom){
- var geohashLevel;
-
- switch(zoom){
- case 0: // The whole world zoom level
- geohashLevel = 2;
- break;
- case 1:
- geohashLevel = 2;
- break;
- case 2:
- geohashLevel = 2;
- break;
- case 3:
- geohashLevel = 2;
- break;
- case 4:
- geohashLevel = 2;
- break;
- case 5:
- geohashLevel = 3;
- break;
- case 6:
- geohashLevel = 3;
- break;
- case 7:
- geohashLevel = 4;
- break;
- case 8:
- geohashLevel = 4;
- break;
- case 9:
- geohashLevel = 4;
- break;
- case 10:
- geohashLevel = 5;
- break;
- case 11:
- geohashLevel = 5;
- break;
- case 12:
- geohashLevel = 6;
- break;
- case 13:
- geohashLevel = 6;
- break;
- case 14:
- geohashLevel = 7;
- break;
- case 15:
- geohashLevel = 7;
- break;
- case 16:
- geohashLevel = 7;
- break;
- case 17:
- geohashLevel = 8;
- break;
- case 18:
- geohashLevel = 9;
- break;
- case 19:
- geohashLevel = 9;
- break;
- case 20:
- geohashLevel = 9;
- break;
- default: //Anything over (Gmaps goes up to 19)
- geohashLevel = 9;
- }
-
- return geohashLevel;
- },
-
- getSearchPrecision: function(zoom){
- if(zoom <= 5) return 2;
- else if(zoom <= 7) return 3;
- else if (zoom <= 11) return 4;
- else if (zoom <= 13) return 5;
- else if (zoom <= 15) return 6;
- else return 7;
- },
-
- /*
- * Creates a LatLng Google Maps object based on the given latitude and longitude
- */
- createLatLng: function(lat, long){
- return new google.maps.LatLng(parseFloat(lat), parseFloat(long));
- },
-
- clear: function() {
- return this.set(_.clone(this.defaults()));
- }
-
- });
- return Map;
+ * @class Map
+ * @classdesc The Map Model represents all of the settings and options for a Google Map.
+ * @classcategory Models
+ * @extends Backbone.Model
+ */
+ var Map = Backbone.Model.extend(
+ /** @lends Map.prototype */ {
+ // This model contains all of the map settings used for searching datasets
+ defaults: function () {
+ var model = this;
+ return {
+ map: null,
+
+ //The options for the map using the Google Maps API MapOptions syntax
+ mapOptions: gmaps
+ ? {
+ zoom: 3,
+ minZoom: 3,
+ maxZoom: 16,
+ center: new google.maps.LatLng(44, -103),
+ disableDefaultUI: true,
+ zoomControl: true,
+ zoomControlOptions: {
+ style: google.maps.ZoomControlStyle.LARGE,
+ position: google.maps.ControlPosition.LEFT_CENTER,
+ },
+ panControl: false,
+ scaleControl: true,
+ streetViewControl: false,
+ mapTypeControl: true,
+ mapTypeControlOptions: {
+ style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
+ mapTypeIds: [
+ google.maps.MapTypeId.SATELLITE,
+ google.maps.MapTypeId.TERRAIN,
+ ],
+ position: google.maps.ControlPosition.LEFT_BOTTOM,
+ },
+ mapTypeId: google.maps.MapTypeId.TERRAIN,
+ styles: [
+ {
+ featureType: "water",
+ stylers: [{ visibility: "on" }, { color: "#b5cbe4" }],
+ },
+ { featureType: "landscape", stylers: [{ color: "#efefef" }] },
+ {
+ featureType: "road.highway",
+ elementType: "geometry",
+ stylers: [{ color: "#83a5b0" }],
+ },
+ {
+ featureType: "road.arterial",
+ elementType: "geometry",
+ stylers: [{ color: "#bdcdd3" }],
+ },
+ {
+ featureType: "road.local",
+ elementType: "geometry",
+ stylers: [{ color: "#ffffff" }],
+ },
+ {
+ featureType: "poi.park",
+ elementType: "geometry",
+ stylers: [{ color: "#e3eed3" }],
+ },
+ {
+ featureType: "administrative",
+ stylers: [{ visibility: "on" }, { lightness: 33 }],
+ },
+ { featureType: "road" },
+ {
+ featureType: "poi.park",
+ elementType: "labels",
+ stylers: [{ visibility: "on" }, { lightness: 20 }],
+ },
+ {},
+ { featureType: "road", stylers: [{ lightness: 20 }] },
+ ],
+ }
+ : null,
+
+ //Set to true to draw markers where tile counts are equal to 1. If set to false, a tile with the count "1" will be drawn instead.
+ drawMarkers: false,
+
+ //If this theme doesn't have an image in this location, Google maps will use their default marker image
+ markerImage: "./js/themes/" + MetacatUI.theme + "/img/map-marker.png",
+
+ //Keep track of the geohash level used to draw tiles on this map
+ tileGeohashLevel: 1,
+
+ ///****** MAP TILE OPTIONS **********//
+ //The options for the tiles. Using Google Maps Web API
+ tileOptions: {
+ fillOpacity: 0.2,
+ strokeWeight: 1,
+ strokePosition:
+ typeof google != "undefined"
+ ? google.maps.StrokePosition.INSIDE
+ : "",
+ strokeOpacity: 1,
+ },
+
+ //The options for the tiles when they are hovered on. Using Google Maps Web API
+ tileOnHover: {
+ fillOpacity: 0.8,
+ strokeColor: "#FFFF00",
+ strokePosition:
+ typeof google != "undefined"
+ ? google.maps.StrokePosition.INSIDE
+ : "",
+ strokeWeight: 1,
+ strokeOpacity: 1,
+ fillColor: "#FFFF66",
+ },
+
+ //The options for the tile text
+ tileLabelColorOnHover: "#000000",
+ tileLabelColor: "#444444",
+
+ //The tile hue - the number of the hue that will be used to color tiles
+ //Tile lightness - percent range of lightness/brightness of this tile hue
+ tileHue: MetacatUI.appModel.get("searchMapTileHue") || "192",
+ tileLightnessMax: 100,
+ tileLightnessMin: 30,
+ };
+ },
+
+ initialize: function (options) {},
+
+ isMaxZoom: function (map) {
+ var zoom = map.getZoom(),
+ type = map.getMapTypeId();
+
+ if (zoom >= this.get("mapOptions").maxZoom) return true;
+ else return false;
+ },
+
+ /**
+ * This function will return the appropriate geohash level to use for mapping geohash tiles on the map at the specified zoom level.
+ */
+ determineGeohashLevel: function (zoom) {
+ var geohashLevel;
+
+ switch (zoom) {
+ case 0: // The whole world zoom level
+ geohashLevel = 2;
+ break;
+ case 1:
+ geohashLevel = 2;
+ break;
+ case 2:
+ geohashLevel = 2;
+ break;
+ case 3:
+ geohashLevel = 2;
+ break;
+ case 4:
+ geohashLevel = 2;
+ break;
+ case 5:
+ geohashLevel = 3;
+ break;
+ case 6:
+ geohashLevel = 3;
+ break;
+ case 7:
+ geohashLevel = 4;
+ break;
+ case 8:
+ geohashLevel = 4;
+ break;
+ case 9:
+ geohashLevel = 4;
+ break;
+ case 10:
+ geohashLevel = 5;
+ break;
+ case 11:
+ geohashLevel = 5;
+ break;
+ case 12:
+ geohashLevel = 6;
+ break;
+ case 13:
+ geohashLevel = 6;
+ break;
+ case 14:
+ geohashLevel = 7;
+ break;
+ case 15:
+ geohashLevel = 7;
+ break;
+ case 16:
+ geohashLevel = 7;
+ break;
+ case 17:
+ geohashLevel = 8;
+ break;
+ case 18:
+ geohashLevel = 9;
+ break;
+ case 19:
+ geohashLevel = 9;
+ break;
+ case 20:
+ geohashLevel = 9;
+ break;
+ default: //Anything over (Gmaps goes up to 19)
+ geohashLevel = 9;
+ }
+
+ return geohashLevel;
+ },
+
+ getSearchPrecision: function (zoom) {
+ if (zoom <= 5) return 2;
+ else if (zoom <= 7) return 3;
+ else if (zoom <= 11) return 4;
+ else if (zoom <= 13) return 5;
+ else if (zoom <= 15) return 6;
+ else return 7;
+ },
+
+ /*
+ * Creates a LatLng Google Maps object based on the given latitude and longitude
+ */
+ createLatLng: function (lat, long) {
+ return new google.maps.LatLng(parseFloat(lat), parseFloat(long));
+ },
+
+ clear: function () {
+ return this.set(_.clone(this.defaults()));
+ },
+ },
+ );
+ return Map;
});
diff --git a/src/js/models/MetricsModel.js b/src/js/models/MetricsModel.js
index b5dd6465e..c120475db 100644
--- a/src/js/models/MetricsModel.js
+++ b/src/js/models/MetricsModel.js
@@ -1,289 +1,298 @@
/*global define */
-define(['jquery', 'underscore', 'backbone'],
- function($, _, Backbone) {
- 'use strict';
-
- /**
- * @class Metrics
- * @classdesc A single result from the DataONE Metrics Service
- * @classcategory Models
- * @extends Backbone.Model
- * @constructor
- */
- var Metrics = Backbone.Model.extend(
- /** @lends Metrics.prototype */{
-
- /**
- * The name of this Model
- * @type {string}
- */
- type: "Metrics",
-
- defaults: {
- metricRequest: null,
- startDate: null,
- endDate: null,
- results: null,
- resultDetails: null,
- pid_list: null,
- url: null,
- filterType: null,
-
- // metrics and metric Facets returned as response from the user
- // datatype: array
- citations: null,
- views: null,
- downloads: null,
- months: null,
- country: null,
- years: null,
- repository: null,
- award: null,
- datasets: null,
-
-
- // Total counts for metrics
- totalCitations: null,
- totalViews: null,
- totalDownloads: null,
-
- // flag to send POST request to the metrics service
- useMetricsPost: false,
-
- // colelctionQuery for Portal Objects
- filterQueryObject: null,
- forwardCollectionQuery: false,
-
- metricsRequiredFields: {
- metricName: true,
- pid_list: true
- }
-
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ "use strict";
+
+ /**
+ * @class Metrics
+ * @classdesc A single result from the DataONE Metrics Service
+ * @classcategory Models
+ * @extends Backbone.Model
+ * @constructor
+ */
+ var Metrics = Backbone.Model.extend(
+ /** @lends Metrics.prototype */ {
+ /**
+ * The name of this Model
+ * @type {string}
+ */
+ type: "Metrics",
+
+ defaults: {
+ metricRequest: null,
+ startDate: null,
+ endDate: null,
+ results: null,
+ resultDetails: null,
+ pid_list: null,
+ url: null,
+ filterType: null,
+
+ // metrics and metric Facets returned as response from the user
+ // datatype: array
+ citations: null,
+ views: null,
+ downloads: null,
+ months: null,
+ country: null,
+ years: null,
+ repository: null,
+ award: null,
+ datasets: null,
+
+ // Total counts for metrics
+ totalCitations: null,
+ totalViews: null,
+ totalDownloads: null,
+
+ // flag to send POST request to the metrics service
+ useMetricsPost: false,
+
+ // colelctionQuery for Portal Objects
+ filterQueryObject: null,
+ forwardCollectionQuery: false,
+
+ metricsRequiredFields: {
+ metricName: true,
+ pid_list: true,
},
+ },
- metricRequest: {
- "metricsPage": {
- "total": 0,
- "start": 0,
- "count": 0
- },
- "metrics": [
- "citations",
- "downloads",
- "views"
- ],
- "filterBy": [
- {
- "filterType": "",
- "values": [],
- "interpretAs": "list"
- },
- {
- "filterType": "month",
- "values": [],
- "interpretAs": "range"
- }
- ],
- "groupBy": [
- "month"
- ]
+ metricRequest: {
+ metricsPage: {
+ total: 0,
+ start: 0,
+ count: 0,
},
-
- /**
- * Initializing the Model objects pid and the metricName variables.
- * @param {object} options
- */
- initialize: function(options) {
- if((options) && options.pid_list !== 'undefined') {
- this.set("pid_list", options.pid_list);
- this.set("filterType", options.type);
- }
- this.set("startDate", "01/01/2012");
-
- // overwrite forwardCollectionQuery flag
- this.set("forwardCollectionQuery", MetacatUI.appModel.get("metricsForwardCollectionQuery"));
-
- // url for the model that is used to for the fetch() call
- this.url = MetacatUI.appModel.get("metricsUrl");
- },
-
- /**
- * Overriding the Model's fetch function.
- */
- fetch: function(){
- var fetchOptions = {};
- this.metricRequest.filterBy[0].filterType = this.get("filterType");
- this.metricRequest.filterBy[0].values = this.get("pid_list");
-
- // TODO: Set the startDate and endDate based on the datePublished and current date
- // respctively.
- this.metricRequest.filterBy[1].values = [];
- this.metricRequest.filterBy[1].values.push(this.get("startDate"));
- this.metricRequest.filterBy[1].values.push(this.getCurrentDate());
-
- // set custom request settings if we're forwarding a CollectionQuery for a portal
- if ( this.get("forwardCollectionQuery") && this.get("filterType") === "portal"
- && this.get("filterQueryObject") != undefined
- && typeof this.get("filterQueryObject") === "object") {
- var filterQueryObject = this.get("filterQueryObject");
- if (filterQueryObject.filterType != undefined
- && filterQueryObject.filterType == "query") {
- if (Array.isArray(filterQueryObject.values)
- && filterQueryObject.values.length > 0 ) {
-
- // check if query object exists
- var queryInserted = this.metricRequest.filterBy.indexOf(filterQueryObject);
-
- // Performing HTTP POST?
- if(this.get("useMetricsPost")) {
- // insert query object
- if (queryInserted < 0) {
- this.metricRequest.filterBy.push(filterQueryObject);
- }
-
- fetchOptions = _.extend({
- data:JSON.stringify(this.metricRequest),
- type:"POST",
- timeout:300000
- });
- }
- else {
- // insert query object
- var collectionQuery = filterQueryObject.values[0];
- if (collectionQuery.length < 1000 && queryInserted < 0) {
- this.metricRequest.filterBy.push(filterQueryObject);
- }
-
- // set the fetch options for
- var model = this;
- fetchOptions = _.extend({
- data:"metricsRequest="+JSON.stringify(this.metricRequest),
- timeout:300000,
- // on error recursively call fetch, but this time use POST
- error: function(response){
- model.set("useMetricsPost", "true");
- model.fetch();
- }
- });
- }
+ metrics: ["citations", "downloads", "views"],
+ filterBy: [
+ {
+ filterType: "",
+ values: [],
+ interpretAs: "list",
+ },
+ {
+ filterType: "month",
+ values: [],
+ interpretAs: "range",
+ },
+ ],
+ groupBy: ["month"],
+ },
+
+ /**
+ * Initializing the Model objects pid and the metricName variables.
+ * @param {object} options
+ */
+ initialize: function (options) {
+ if (options && options.pid_list !== "undefined") {
+ this.set("pid_list", options.pid_list);
+ this.set("filterType", options.type);
+ }
+ this.set("startDate", "01/01/2012");
+
+ // overwrite forwardCollectionQuery flag
+ this.set(
+ "forwardCollectionQuery",
+ MetacatUI.appModel.get("metricsForwardCollectionQuery"),
+ );
+
+ // url for the model that is used to for the fetch() call
+ this.url = MetacatUI.appModel.get("metricsUrl");
+ },
+
+ /**
+ * Overriding the Model's fetch function.
+ */
+ fetch: function () {
+ var fetchOptions = {};
+ this.metricRequest.filterBy[0].filterType = this.get("filterType");
+ this.metricRequest.filterBy[0].values = this.get("pid_list");
+
+ // TODO: Set the startDate and endDate based on the datePublished and current date
+ // respctively.
+ this.metricRequest.filterBy[1].values = [];
+ this.metricRequest.filterBy[1].values.push(this.get("startDate"));
+ this.metricRequest.filterBy[1].values.push(this.getCurrentDate());
+
+ // set custom request settings if we're forwarding a CollectionQuery for a portal
+ if (
+ this.get("forwardCollectionQuery") &&
+ this.get("filterType") === "portal" &&
+ this.get("filterQueryObject") != undefined &&
+ typeof this.get("filterQueryObject") === "object"
+ ) {
+ var filterQueryObject = this.get("filterQueryObject");
+ if (
+ filterQueryObject.filterType != undefined &&
+ filterQueryObject.filterType == "query"
+ ) {
+ if (
+ Array.isArray(filterQueryObject.values) &&
+ filterQueryObject.values.length > 0
+ ) {
+ // check if query object exists
+ var queryInserted =
+ this.metricRequest.filterBy.indexOf(filterQueryObject);
+
+ // Performing HTTP POST?
+ if (this.get("useMetricsPost")) {
+ // insert query object
+ if (queryInserted < 0) {
+ this.metricRequest.filterBy.push(filterQueryObject);
}
- }
- }
- // check if we need to set fetchOptions
- if (Object.keys(fetchOptions).length === 0) {
- if ( this.get("useMetricsPost") ) {
fetchOptions = _.extend({
- data:JSON.stringify(this.metricRequest),
- type:"POST",
- timeout:300000
+ data: JSON.stringify(this.metricRequest),
+ type: "POST",
+ timeout: 300000,
});
- }
- else {
+ } else {
+ // insert query object
+ var collectionQuery = filterQueryObject.values[0];
+ if (collectionQuery.length < 1000 && queryInserted < 0) {
+ this.metricRequest.filterBy.push(filterQueryObject);
+ }
+
+ // set the fetch options for
+ var model = this;
fetchOptions = _.extend({
- data:"metricsRequest="+JSON.stringify(this.metricRequest),
- timeout:300000,
+ data: "metricsRequest=" + JSON.stringify(this.metricRequest),
+ timeout: 300000,
+ // on error recursively call fetch, but this time use POST
+ error: function (response) {
+ model.set("useMetricsPost", "true");
+ model.fetch();
+ },
});
+ }
}
}
-
- //This calls the Backbone fetch() function but with our custom fetch options.
- return Backbone.Model.prototype.fetch.call(this, fetchOptions);
- },
-
- /**
- * Get's a string version of today's date
- * @return {string}
- */
- getCurrentDate: function() {
- var today = new Date();
- var dd = today.getDate();
- var mm = today.getMonth()+1; //January is 0!
-
- var yyyy = today.getFullYear();
- if(dd<10){
- dd='0'+dd;
- }
- if(mm<10){
- mm='0'+mm;
- }
- var today = mm+'/'+dd+'/'+yyyy;
- return today;
- },
-
- /**
- * Parsing the response for setting the Model's member variables.
- */
- parse: function(response){
- var metricsObject = {
- "metricRequest": response.metricsRequest,
- "citations": response.results.citations,
- "views": response.results.views,
- "downloads": response.results.downloads,
- "months": response.results.months,
- "country": response.results.country,
- "resultDetails": response.resultDetails,
- "datasets": response.results.datasets
- }
-
- if (response.results.citations != null) {
- metricsObject["totalCitations"] = response.results.citations.reduce(function(acc, val) { return acc + val; }, 0)
- }
- else {
- metricsObject["totalCitations"] = 0
- }
-
- if (response.results.downloads != null) {
- metricsObject["totalDownloads"] = response.results.downloads.reduce(function(acc, val) { return acc + val; }, 0)
- }
- else {
- metricsObject["totalDownloads"] = 0
- }
-
- if (response.results.views != null) {
- metricsObject["totalViews"] = response.results.views.reduce(function(acc, val) { return acc + val; }, 0)
- }
- else {
- metricsObject["totalViews"] = 0
- }
-
- //trim off the leading zeros and their corresponding months
- if (response.results.months != null) {
-
- // iterate all the metrics objects and remove the entry if the counts are 0
- for (var i = 0 ; i < metricsObject["months"].length; i++) {
-
- if ( metricsObject["citations"] != null &&
- metricsObject["views"] != null &&
- metricsObject["downloads"] != null ) {
-
- if (( metricsObject["citations"][i] == 0 ) &&
- ( metricsObject["views"][i] == 0 ) &&
- ( metricsObject["downloads"][i] == 0 )) {
-
- metricsObject["months"].splice(i,1);
- metricsObject["citations"].splice(i,1);
- metricsObject["views"].splice(i,1);
- metricsObject["downloads"].splice(i,1);
-
- // if country facet was part of the request; update object;
- if ( metricsObject["country"] != null) {
- metricsObject["country"].splice(i,1)
- }
-
- // modified array size; decrement the counter;
- i--;
- }
- else {
- break;
- }
- }
+ }
+
+ // check if we need to set fetchOptions
+ if (Object.keys(fetchOptions).length === 0) {
+ if (this.get("useMetricsPost")) {
+ fetchOptions = _.extend({
+ data: JSON.stringify(this.metricRequest),
+ type: "POST",
+ timeout: 300000,
+ });
+ } else {
+ fetchOptions = _.extend({
+ data: "metricsRequest=" + JSON.stringify(this.metricRequest),
+ timeout: 300000,
+ });
+ }
+ }
+
+ //This calls the Backbone fetch() function but with our custom fetch options.
+ return Backbone.Model.prototype.fetch.call(this, fetchOptions);
+ },
+
+ /**
+ * Get's a string version of today's date
+ * @return {string}
+ */
+ getCurrentDate: function () {
+ var today = new Date();
+ var dd = today.getDate();
+ var mm = today.getMonth() + 1; //January is 0!
+
+ var yyyy = today.getFullYear();
+ if (dd < 10) {
+ dd = "0" + dd;
+ }
+ if (mm < 10) {
+ mm = "0" + mm;
+ }
+ var today = mm + "/" + dd + "/" + yyyy;
+ return today;
+ },
+
+ /**
+ * Parsing the response for setting the Model's member variables.
+ */
+ parse: function (response) {
+ var metricsObject = {
+ metricRequest: response.metricsRequest,
+ citations: response.results.citations,
+ views: response.results.views,
+ downloads: response.results.downloads,
+ months: response.results.months,
+ country: response.results.country,
+ resultDetails: response.resultDetails,
+ datasets: response.results.datasets,
+ };
+
+ if (response.results.citations != null) {
+ metricsObject["totalCitations"] = response.results.citations.reduce(
+ function (acc, val) {
+ return acc + val;
+ },
+ 0,
+ );
+ } else {
+ metricsObject["totalCitations"] = 0;
+ }
+
+ if (response.results.downloads != null) {
+ metricsObject["totalDownloads"] = response.results.downloads.reduce(
+ function (acc, val) {
+ return acc + val;
+ },
+ 0,
+ );
+ } else {
+ metricsObject["totalDownloads"] = 0;
+ }
+
+ if (response.results.views != null) {
+ metricsObject["totalViews"] = response.results.views.reduce(function (
+ acc,
+ val,
+ ) {
+ return acc + val;
+ }, 0);
+ } else {
+ metricsObject["totalViews"] = 0;
+ }
+
+ //trim off the leading zeros and their corresponding months
+ if (response.results.months != null) {
+ // iterate all the metrics objects and remove the entry if the counts are 0
+ for (var i = 0; i < metricsObject["months"].length; i++) {
+ if (
+ metricsObject["citations"] != null &&
+ metricsObject["views"] != null &&
+ metricsObject["downloads"] != null
+ ) {
+ if (
+ metricsObject["citations"][i] == 0 &&
+ metricsObject["views"][i] == 0 &&
+ metricsObject["downloads"][i] == 0
+ ) {
+ metricsObject["months"].splice(i, 1);
+ metricsObject["citations"].splice(i, 1);
+ metricsObject["views"].splice(i, 1);
+ metricsObject["downloads"].splice(i, 1);
+
+ // if country facet was part of the request; update object;
+ if (metricsObject["country"] != null) {
+ metricsObject["country"].splice(i, 1);
}
- }
- return metricsObject;
- },
+ // modified array size; decrement the counter;
+ i--;
+ } else {
+ break;
+ }
+ }
+ }
+ }
- });
- return Metrics;
+ return metricsObject;
+ },
+ },
+ );
+ return Metrics;
});
diff --git a/src/js/models/NodeModel.js b/src/js/models/NodeModel.js
index f466235db..c39e98f09 100644
--- a/src/js/models/NodeModel.js
+++ b/src/js/models/NodeModel.js
@@ -1,95 +1,94 @@
/*global define */
-define(['jquery', 'underscore', 'backbone'],
- function($, _, Backbone) {
- 'use strict';
-
- // Node Model
- // ------------------
- var Node = Backbone.Model.extend({
-
- // This model contains all of the information retrieved from calling listNodes() using the DataONE API
- defaults: {
- members: [],
- coordinators: [],
- hiddenMembers: [],
- currentMemberNode: MetacatUI.appModel.get("nodeId") || null,
- checked: false,
- error: false
- },
-
- initialize: function(){
- var model = this;
-
- if(MetacatUI.appModel.get('nodeServiceUrl')){
- //Get the node information from the CN
- this.getNodeInfo();
- }
- },
-
- getMember: function(memberInfo){
- if(!memberInfo) return false;
-
- //Get the member ID
- var memberId = null;
- if((typeof memberInfo === "object") && memberInfo.type == "SolrResult")
- memberId = memberInfo.get("datasource");
- else if(typeof memberInfo === "string")
- memberId = memberInfo;
- else
- return false;
-
- //Find the member by its ID
- var member = _.findWhere(this.get("members"), {identifier: memberId});
- if(!member) return false;
-
- return member;
- },
-
- getMembers: function(memberInfo){
- if(!memberInfo) return false;
-
- if(!Array.isArray(memberInfo))
- memberInfo = [memberInfo];
-
- var members = [];
-
- _.each(memberInfo, function(info){
-
- var foundMember = this.getMember(info);
-
- if(foundMember)
- members.push(foundMember);
-
- }, this);
-
- if(members.length) return members;
- else return false;
- },
-
- getNodeInfo: function(){
- var thisModel = this,
- memberList = this.get('members'),
- coordList = this.get('coordinators');
-
- $.ajax({
- url: MetacatUI.appModel.get('nodeServiceUrl'),
- dataType: "text",
- success: function(data, textStatus, xhr) {
-
- var xmlResponse = $.parseXML(data) || null;
- if(!xmlResponse) return;
-
- thisModel.saveNodeInfo(xmlResponse);
-
- thisModel.set("checked", true);
- },
- error: function(xhr, textStatus, errorThrown){
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ "use strict";
+
+ // Node Model
+ // ------------------
+ var Node = Backbone.Model.extend({
+ // This model contains all of the information retrieved from calling listNodes() using the DataONE API
+ defaults: {
+ members: [],
+ coordinators: [],
+ hiddenMembers: [],
+ currentMemberNode: MetacatUI.appModel.get("nodeId") || null,
+ checked: false,
+ error: false,
+ },
+
+ initialize: function () {
+ var model = this;
+
+ if (MetacatUI.appModel.get("nodeServiceUrl")) {
+ //Get the node information from the CN
+ this.getNodeInfo();
+ }
+ },
+
+ getMember: function (memberInfo) {
+ if (!memberInfo) return false;
+
+ //Get the member ID
+ var memberId = null;
+ if (typeof memberInfo === "object" && memberInfo.type == "SolrResult")
+ memberId = memberInfo.get("datasource");
+ else if (typeof memberInfo === "string") memberId = memberInfo;
+ else return false;
+
+ //Find the member by its ID
+ var member = _.findWhere(this.get("members"), { identifier: memberId });
+ if (!member) return false;
+
+ return member;
+ },
+
+ getMembers: function (memberInfo) {
+ if (!memberInfo) return false;
+
+ if (!Array.isArray(memberInfo)) memberInfo = [memberInfo];
+
+ var members = [];
+
+ _.each(
+ memberInfo,
+ function (info) {
+ var foundMember = this.getMember(info);
+
+ if (foundMember) members.push(foundMember);
+ },
+ this,
+ );
+
+ if (members.length) return members;
+ else return false;
+ },
+
+ getNodeInfo: function () {
+ var thisModel = this,
+ memberList = this.get("members"),
+ coordList = this.get("coordinators");
+
+ $.ajax({
+ url: MetacatUI.appModel.get("nodeServiceUrl"),
+ dataType: "text",
+ success: function (data, textStatus, xhr) {
+ var xmlResponse = $.parseXML(data) || null;
+ if (!xmlResponse) return;
+
+ thisModel.saveNodeInfo(xmlResponse);
+ thisModel.set("checked", true);
+ },
+ error: function (xhr, textStatus, errorThrown) {
//Log the error to the console
- console.error("Couldn't get the DataONE Node info document: ", textStatus, errorThrown);
+ console.error(
+ "Couldn't get the DataONE Node info document: ",
+ textStatus,
+ errorThrown,
+ );
// Track the error
- const message = `Couldn't get the DataONE Node info document: ` +
+ const message =
+ `Couldn't get the DataONE Node info document: ` +
`${textStatus}, ${errorThrown}`;
MetacatUI.analytics?.trackException(message, null, false);
@@ -98,127 +97,131 @@ define(['jquery', 'underscore', 'backbone'],
thisModel.trigger("error");
thisModel.set("checked", true);
+ },
+ });
+ },
+
+ saveNodeInfo: function (xml) {
+ var thisModel = this,
+ memberList = this.get("members"),
+ coordList = this.get("coordinators"),
+ children = xml.children || xml.childNodes;
+
+ //Traverse the XML response to get the MN info
+ _.each(children, function (d1NodeList) {
+ var d1NodeListChildren = d1NodeList.children || d1NodeList.childNodes;
+
+ //The first (and only) child should be the d1NodeList
+ _.each(d1NodeListChildren, function (thisNode) {
+ //Ignore parts of the XML that is not MN info
+ if (!thisNode.attributes) return;
+
+ //'node' will be a single node
+ var node = {},
+ nodeProperties = thisNode.children || thisNode.childNodes;
+
+ //Grab information about this node from XML nodes
+ _.each(nodeProperties, function (nodeProperty) {
+ if (nodeProperty.nodeName == "property")
+ node[$(nodeProperty).attr("key")] = nodeProperty.textContent;
+ else node[nodeProperty.nodeName] = nodeProperty.textContent;
+
+ //Check if this member node has v2 read capabilities - important for the Package service
+ if (
+ nodeProperty.nodeName == "services" &&
+ nodeProperty.childNodes.length
+ ) {
+ var v2 = $(nodeProperty).find(
+ "service[name='MNRead'][version='v2'][available='true']",
+ ).length;
+ node["readv2"] = v2;
+ }
+ });
+
+ //Grab information about this node from XLM attributes
+ _.each(thisNode.attributes, function (attribute) {
+ node[attribute.nodeName] = attribute.nodeValue;
+ });
+
+ //Create some aliases for node info properties
+ if (node.CN_logo_url) node.logo = node.CN_logo_url;
+
+ if (node.CN_node_name) node.name = node.CN_node_name;
+
+ if (node.CN_operational_status)
+ node.status = node.CN_operational_status;
+
+ if (node.CN_date_operational)
+ node.memberSince = node.CN_date_operational;
+
+ node.shortIdentifier = node.identifier.substring(
+ node.identifier.lastIndexOf(":") + 1,
+ );
+
+ // Push only if the node is not present in the list
+ if (node.type == "mn") {
+ if (!thisModel.containsObject(node, memberList)) {
+ memberList.push(node);
+ }
+ }
+
+ // Push only if the node is not present in the list
+ if (node.type == "cn") {
+ if (!thisModel.containsObject(node, coordList)) {
+ coordList.push(node);
+ }
+ }
+ });
+
+ //Save the cn and mn lists in the model when all members have been added
+ thisModel.set("members", memberList);
+ thisModel.trigger("change:members");
+ thisModel.set("coordinators", coordList);
+ thisModel.trigger("change:coordinators");
+
+ //If we don't have a current member node yet, find it
+ if (!thisModel.get("currentMemberNode")) {
+ // Find the current member node by matching the current DataONE MN API base URL
+ // with the DataONE MN API base URLs in the member list
+ var thisMember = _.findWhere(thisModel.get("members"), {
+ baseURL: (
+ MetacatUI.appModel.get("baseUrl") +
+ MetacatUI.appModel.get("context") +
+ MetacatUI.appModel.get("d1Service")
+ )
+ .replace("/v2", "")
+ .replace("/v1", ""),
+ });
+
+ if (thisMember !== undefined) {
+ //If a matching member node is found, set the node ID
+ thisModel.set("currentMemberNode", thisMember.identifier);
+
+ //If the node ID is not set in the appModel user the matching MN we found
+ if (!MetacatUI.appModel.get("nodeId"))
+ MetacatUI.appModel.set("nodeId", thisMember.identifier);
+ }
+
+ //Trigger a change so the rest of the app knows we at least looked for the current MN
+ thisModel.trigger("change:currentMemberNode");
}
- });
- },
-
- saveNodeInfo: function(xml){
- var thisModel = this,
- memberList = this.get('members'),
- coordList = this.get('coordinators'),
- children = xml.children || xml.childNodes;
-
-
- //Traverse the XML response to get the MN info
- _.each(children, function(d1NodeList){
-
- var d1NodeListChildren = d1NodeList.children || d1NodeList.childNodes;
-
- //The first (and only) child should be the d1NodeList
- _.each(d1NodeListChildren, function(thisNode){
-
- //Ignore parts of the XML that is not MN info
- if(!thisNode.attributes) return;
-
- //'node' will be a single node
- var node = {},
- nodeProperties = thisNode.children || thisNode.childNodes;
-
- //Grab information about this node from XML nodes
- _.each(nodeProperties, function(nodeProperty){
-
- if(nodeProperty.nodeName == "property")
- node[$(nodeProperty).attr("key")] = nodeProperty.textContent;
- else
- node[nodeProperty.nodeName] = nodeProperty.textContent;
-
- //Check if this member node has v2 read capabilities - important for the Package service
- if((nodeProperty.nodeName == "services") && nodeProperty.childNodes.length){
- var v2 = $(nodeProperty).find("service[name='MNRead'][version='v2'][available='true']").length;
- node["readv2"] = v2;
- }
- });
-
- //Grab information about this node from XLM attributes
- _.each(thisNode.attributes, function(attribute){
- node[attribute.nodeName] = attribute.nodeValue;
- });
-
- //Create some aliases for node info properties
- if(node.CN_logo_url)
- node.logo = node.CN_logo_url;
-
- if(node.CN_node_name)
- node.name = node.CN_node_name;
-
- if(node.CN_operational_status)
- node.status = node.CN_operational_status;
-
- if(node.CN_date_operational)
- node.memberSince = node.CN_date_operational;
-
- node.shortIdentifier = node.identifier.substring(node.identifier.lastIndexOf(":") + 1);
-
-
- // Push only if the node is not present in the list
- if ( node.type == "mn" ) {
- if ( !thisModel.containsObject(node, memberList) ) {
- memberList.push(node);
- }
- }
-
- // Push only if the node is not present in the list
- if ( node.type == "cn" ) {
- if ( !thisModel.containsObject(node, coordList) ) {
- coordList.push(node);
- }
- }
-
- });
-
- //Save the cn and mn lists in the model when all members have been added
- thisModel.set('members', memberList);
- thisModel.trigger('change:members');
- thisModel.set('coordinators', coordList);
- thisModel.trigger('change:coordinators');
-
- //If we don't have a current member node yet, find it
- if(!thisModel.get("currentMemberNode")){
-
- // Find the current member node by matching the current DataONE MN API base URL
- // with the DataONE MN API base URLs in the member list
- var thisMember = _.findWhere(thisModel.get("members"), { baseURL: (MetacatUI.appModel.get("baseUrl") + MetacatUI.appModel.get('context') + MetacatUI.appModel.get("d1Service")).replace("/v2", "").replace("/v1", "") });
-
- if(thisMember !== undefined){
- //If a matching member node is found, set the node ID
- thisModel.set("currentMemberNode", thisMember.identifier);
-
- //If the node ID is not set in the appModel user the matching MN we found
- if(!MetacatUI.appModel.get("nodeId"))
- MetacatUI.appModel.set("nodeId", thisMember.identifier);
- }
-
- //Trigger a change so the rest of the app knows we at least looked for the current MN
- thisModel.trigger("change:currentMemberNode");
-
- }
- });
- },
-
- // Checks if the node already exists in the List
- containsObject: function (obj, list) {
- var res = _.find(list, function(val){ return _.isEqual(obj, val)});
- return (_.isObject(res))? true:false;
- },
-
-
- /*
- * Returns true if the given nodeId is a Coordinating Node
- */
- isCN: function(nodeId){
- return _.findWhere(this.get("coordinators"), { identifier: nodeId });
- }
-
- });
- return Node;
+ });
+ },
+
+ // Checks if the node already exists in the List
+ containsObject: function (obj, list) {
+ var res = _.find(list, function (val) {
+ return _.isEqual(obj, val);
+ });
+ return _.isObject(res) ? true : false;
+ },
+
+ /*
+ * Returns true if the given nodeId is a Coordinating Node
+ */
+ isCN: function (nodeId) {
+ return _.findWhere(this.get("coordinators"), { identifier: nodeId });
+ },
+ });
+ return Node;
});
diff --git a/src/js/models/PackageModel.js b/src/js/models/PackageModel.js
index 0f8daf8b5..00ee3a7a7 100644
--- a/src/js/models/PackageModel.js
+++ b/src/js/models/PackageModel.js
@@ -1,1479 +1,1854 @@
/*global define */
-define(['jquery', 'underscore', 'backbone', 'uuid', 'md5', 'rdflib', 'models/SolrResult'],
- function($, _, Backbone, uuid, md5, rdf, SolrResult) {
-
- // Package Model
- // ------------------
- var PackageModel = Backbone.Model.extend(
- /** @lends PackageModel.prototype */{
- // This model contains information about a package/resource map
- defaults: function(){
- return {
- id: null, //The id of the resource map/package itself
- url: null, //the URL to retrieve this package
- memberId: null, //An id of a member of the data package
- indexDoc: null, //A SolrResult object representation of the resource map
- size: 0, //The number of items aggregated in this package
- totalSize: null,
- formattedSize: "",
- formatId: null,
- obsoletedBy: null,
- obsoletes: null,
- read_count_i: null,
- isPublic: true,
- members: [],
- memberIds: [],
- sources: [],
- derivations: [],
- provenanceFlag: null,
- sourcePackages: [],
- derivationPackages: [],
- sourceDocs: [],
- derivationDocs: [],
- relatedModels: [], //A condensed list of all SolrResult models related to this package in some way
- parentPackageMetadata: null,
- //If true, when the member objects are retrieved, archived content will be included
- getArchivedMembers: false,
- }
- },
-
- //Define the namespaces
- namespaces: {
- RDF: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
- FOAF: "http://xmlns.com/foaf/0.1/",
- OWL: "http://www.w3.org/2002/07/owl#",
- DC: "http://purl.org/dc/elements/1.1/",
- ORE: "http://www.openarchives.org/ore/terms/",
- DCTERMS: "http://purl.org/dc/terms/",
- CITO: "http://purl.org/spar/cito/",
- XML: "http://www.w3.org/2001/XMLSchema#"
- },
-
- sysMetaNodeMap: {
- accesspolicy: "accessPolicy",
- accessrule: "accessRule",
- authoritativemembernode: "authoritativeMemberNode",
- dateuploaded: "dateUploaded",
- datesysmetadatamodified: "dateSysMetadataModified",
- dateuploaded: "dateUploaded",
- formatid: "formatId",
- nodereference: "nodeReference",
- obsoletedby: "obsoletedBy",
- originmembernode: "originMemberNode",
- replicamembernode: "replicaMemberNode",
- replicapolicy: "replicaPolicy",
- replicationstatus: "replicationStatus",
- replicaverified: "replicaVerified",
- rightsholder: "rightsHolder",
- serialversion: "serialVersion"
- },
-
- complete: false,
-
- pending: false,
-
- type: "Package",
-
- // The RDF graph representing this data package
- dataPackageGraph: null,
-
- initialize: function(options){
- this.setURL();
-
- // Create an initial RDF graph
- this.dataPackageGraph = rdf.graph();
- },
-
- setURL: function(){
- if(MetacatUI.appModel.get("packageServiceUrl"))
- this.set("url", MetacatUI.appModel.get("packageServiceUrl") + encodeURIComponent(this.get("id")));
- },
-
- /*
- * Set the URL for fetch
- */
- url: function(){
- return MetacatUI.appModel.get("objectServiceUrl") + encodeURIComponent(this.get("id"));
- },
-
- /* Retrieve the id of the resource map/package that this id belongs to */
- getMembersByMemberID: function(id){
- this.pending = true;
-
- if((typeof id === "undefined") || !id) var id = this.memberId;
-
- var model = this;
-
- //Get the id of the resource map for this member
- var provFlList = MetacatUI.appSearchModel.getProvFlList() + "prov_instanceOfClass,";
- var query = 'fl=resourceMap,fileName,read:read_count_i,obsoletedBy,size,formatType,formatId,id,datasource,title,origin,pubDate,dateUploaded,isPublic,isService,serviceTitle,serviceEndpoint,serviceOutput,serviceDescription,' + provFlList +
- '&rows=1' +
- '&q=id:%22' + encodeURIComponent(id) + '%22' +
- '&wt=json';
-
-
- var requestSettings = {
- url: MetacatUI.appModel.get("queryServiceUrl") + query,
- success: function(data, textStatus, xhr) {
- //There should be only one response since we searched by id
- if(typeof data.response.docs !== "undefined"){
- var doc = data.response.docs[0];
-
- //Is this document a resource map itself?
- if(doc.formatId == "http://www.openarchives.org/ore/terms"){
- model.set("id", doc.id); //this is the package model ID
- model.set("members", new Array()); //Reset the member list
- model.getMembers();
- }
- //If there is no resource map, then this is the only document to in this package
- else if((typeof doc.resourceMap === "undefined") || !doc.resourceMap){
- model.set('id', null);
- model.set('memberIds', new Array(doc.id));
- model.set('members', [new SolrResult(doc)]);
- model.trigger("change:members");
- model.flagComplete();
- }
- else{
- model.set('id', doc.resourceMap[0]);
- model.getMembers();
- }
- }
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- /* Get all the members of a resource map/package based on the id attribute of this model.
- * Create a SolrResult model for each member and save it in the members[] attribute of this model. */
- getMembers: function(options){
- this.pending = true;
-
- var model = this,
- members = [],
- pids = []; //Keep track of each object pid
-
- //*** Find all the files that are a part of this resource map and the resource map itself
- var provFlList = MetacatUI.appSearchModel.getProvFlList();
- var query = 'fl=resourceMap,fileName,obsoletes,obsoletedBy,size,formatType,formatId,id,datasource,' +
- 'rightsHolder,dateUploaded,archived,title,origin,prov_instanceOfClass,isDocumentedBy,isPublic' +
- '&rows=1000' +
- '&q=%28resourceMap:%22' + encodeURIComponent(this.id) + '%22%20OR%20id:%22' + encodeURIComponent(this.id) + '%22%29' +
- '&wt=json';
-
- if( this.get("getArchivedMembers") ){
- query += "&archived=archived:*";
- }
-
- var requestSettings = {
- url: MetacatUI.appModel.get("queryServiceUrl") + query,
- success: function(data, textStatus, xhr) {
-
- //Separate the resource maps from the data/metadata objects
- _.each(data.response.docs, function(doc){
- if(doc.id == model.get("id")){
- model.set("indexDoc", doc);
- model.set(doc);
- if(model.get("resourceMap") && (options && options.getParentMetadata))
- model.getParentMetadata();
- }
- else{
- pids.push(doc.id);
-
- if(doc.formatType == "RESOURCE"){
- var newPckg = new PackageModel(doc);
- newPckg.set("parentPackage", model);
- members.push(newPckg);
- }
- else
- members.push(new SolrResult(doc));
- }
- });
-
- model.set('memberIds', _.uniq(pids));
- model.set('members', members);
-
- if(model.getNestedPackages().length > 0)
- model.createNestedPackages();
- else
- model.flagComplete();
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
-
- return this;
- },
-
- /*
- * Send custom options to the Backbone.Model.fetch() function
- */
- fetch: function(options){
- if(!options) var options = {};
-
- var fetchOptions = _.extend({dataType: "text"}, options);
-
- //Add the authorization options
- fetchOptions = _.extend(fetchOptions, MetacatUI.appUserModel.createAjaxSettings());
-
-
- return Backbone.Model.prototype.fetch.call(this, fetchOptions);
- },
-
- /*
- * Deserialize a Package from OAI-ORE RDF XML
- */
- parse: function(response, options) {
-
- //Save the raw XML in case it needs to be used later
- this.set("objectXML", $.parseHTML(response));
-
- //Define the namespaces
- var RDF = rdf.Namespace(this.namespaces.RDF),
- FOAF = rdf.Namespace(this.namespaces.FOAF),
- OWL = rdf.Namespace(this.namespaces.OWL),
- DC = rdf.Namespace(this.namespaces.DC),
- ORE = rdf.Namespace(this.namespaces.ORE),
- DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
- CITO = rdf.Namespace(this.namespaces.CITO);
-
- var memberStatements = [],
- memberURIParts,
- memberPIDStr,
- memberPID,
- memberModel,
- models = []; // the models returned by parse()
-
- try {
- rdf.parse(response, this.dataPackageGraph, MetacatUI.appModel.get("objectServiceUrl") + (encodeURIComponent(this.id) || encodeURIComponent(this.seriesid)), 'application/rdf+xml');
-
- // List the package members
- memberStatements = this.dataPackageGraph.statementsMatching(
- undefined, ORE('aggregates'), undefined, undefined);
-
- var memberPIDs = [],
- members = [],
- currentMembers = this.get("members"),
- model = this;
-
- // Get system metadata for each member to eval the formatId
- _.each(memberStatements, function(memberStatement){
- memberURIParts = memberStatement.object.value.split('/');
- memberPIDStr = _.last(memberURIParts);
- memberPID = decodeURIComponent(memberPIDStr);
-
- if ( memberPID ){
- memberPIDs.push(memberPID);
-
- //Get the current model from the member list, if it exists
- var existingModel = _.find(currentMembers, function(m){
- return m.get("id") == decodeURIComponent(memberPID);
- });
-
- //Add the existing model to the new member list
- if(existingModel){
- members.push(existingModel);
- }
- //Or create a new SolrResult model
- else{
- members.push(new SolrResult({
- id: decodeURIComponent(memberPID)
- }));
- }
- }
-
- }, this);
-
- //Get the documents relationships
- var documentedByStatements = this.dataPackageGraph.statementsMatching(
- undefined, CITO('isDocumentedBy'), undefined, undefined),
- metadataPids = [];
-
- _.each(documentedByStatements, function(statement){
- //Get the data object that is documentedBy metadata
- var dataPid = decodeURIComponent(_.last(statement.subject.value.split('/'))),
- dataObj = _.find(members, function(m){ return (m.get("id") == dataPid) }),
- metadataPid = _.last(statement.object.value.split('/'));
-
- //Save this as a metadata model
- metadataPids.push(metadataPid);
-
- //Set the isDocumentedBy field
- var isDocBy = dataObj.get("isDocumentedBy");
- if(isDocBy && Array.isArray(isDocBy)) isDocBy.push(metadataPid);
- else if(isDocBy && !Array.isArray(isDocBy)) isDocBy = [isDocBy, metadataPid];
- else isDocBy = [metadataPid];
-
- dataObj.set("isDocumentedBy", isDocBy);
- }, this);
-
- //Get the metadata models and mark them as metadata
- var metadataModels = _.filter(members, function(m){ return _.contains(metadataPids, m.get("id")) });
- _.invoke(metadataModels, "set", "formatType", "METADATA");
-
- //Keep the pids in the collection for easy access later
- this.set("memberIds", memberPIDs);
- this.set("members", members);
-
- } catch (error) {
- console.log(error);
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "uuid",
+ "md5",
+ "rdflib",
+ "models/SolrResult",
+], function ($, _, Backbone, uuid, md5, rdf, SolrResult) {
+ // Package Model
+ // ------------------
+ var PackageModel = Backbone.Model.extend(
+ /** @lends PackageModel.prototype */ {
+ // This model contains information about a package/resource map
+ defaults: function () {
+ return {
+ id: null, //The id of the resource map/package itself
+ url: null, //the URL to retrieve this package
+ memberId: null, //An id of a member of the data package
+ indexDoc: null, //A SolrResult object representation of the resource map
+ size: 0, //The number of items aggregated in this package
+ totalSize: null,
+ formattedSize: "",
+ formatId: null,
+ obsoletedBy: null,
+ obsoletes: null,
+ read_count_i: null,
+ isPublic: true,
+ members: [],
+ memberIds: [],
+ sources: [],
+ derivations: [],
+ provenanceFlag: null,
+ sourcePackages: [],
+ derivationPackages: [],
+ sourceDocs: [],
+ derivationDocs: [],
+ relatedModels: [], //A condensed list of all SolrResult models related to this package in some way
+ parentPackageMetadata: null,
+ //If true, when the member objects are retrieved, archived content will be included
+ getArchivedMembers: false,
+ };
+ },
+
+ //Define the namespaces
+ namespaces: {
+ RDF: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+ FOAF: "http://xmlns.com/foaf/0.1/",
+ OWL: "http://www.w3.org/2002/07/owl#",
+ DC: "http://purl.org/dc/elements/1.1/",
+ ORE: "http://www.openarchives.org/ore/terms/",
+ DCTERMS: "http://purl.org/dc/terms/",
+ CITO: "http://purl.org/spar/cito/",
+ XML: "http://www.w3.org/2001/XMLSchema#",
+ },
+
+ sysMetaNodeMap: {
+ accesspolicy: "accessPolicy",
+ accessrule: "accessRule",
+ authoritativemembernode: "authoritativeMemberNode",
+ dateuploaded: "dateUploaded",
+ datesysmetadatamodified: "dateSysMetadataModified",
+ dateuploaded: "dateUploaded",
+ formatid: "formatId",
+ nodereference: "nodeReference",
+ obsoletedby: "obsoletedBy",
+ originmembernode: "originMemberNode",
+ replicamembernode: "replicaMemberNode",
+ replicapolicy: "replicaPolicy",
+ replicationstatus: "replicationStatus",
+ replicaverified: "replicaVerified",
+ rightsholder: "rightsHolder",
+ serialversion: "serialVersion",
+ },
+
+ complete: false,
+
+ pending: false,
+
+ type: "Package",
+
+ // The RDF graph representing this data package
+ dataPackageGraph: null,
+
+ initialize: function (options) {
+ this.setURL();
+
+ // Create an initial RDF graph
+ this.dataPackageGraph = rdf.graph();
+ },
+
+ setURL: function () {
+ if (MetacatUI.appModel.get("packageServiceUrl"))
+ this.set(
+ "url",
+ MetacatUI.appModel.get("packageServiceUrl") +
+ encodeURIComponent(this.get("id")),
+ );
+ },
+
+ /*
+ * Set the URL for fetch
+ */
+ url: function () {
+ return (
+ MetacatUI.appModel.get("objectServiceUrl") +
+ encodeURIComponent(this.get("id"))
+ );
+ },
+
+ /* Retrieve the id of the resource map/package that this id belongs to */
+ getMembersByMemberID: function (id) {
+ this.pending = true;
+
+ if (typeof id === "undefined" || !id) var id = this.memberId;
+
+ var model = this;
+
+ //Get the id of the resource map for this member
+ var provFlList =
+ MetacatUI.appSearchModel.getProvFlList() + "prov_instanceOfClass,";
+ var query =
+ "fl=resourceMap,fileName,read:read_count_i,obsoletedBy,size,formatType,formatId,id,datasource,title,origin,pubDate,dateUploaded,isPublic,isService,serviceTitle,serviceEndpoint,serviceOutput,serviceDescription," +
+ provFlList +
+ "&rows=1" +
+ "&q=id:%22" +
+ encodeURIComponent(id) +
+ "%22" +
+ "&wt=json";
+
+ var requestSettings = {
+ url: MetacatUI.appModel.get("queryServiceUrl") + query,
+ success: function (data, textStatus, xhr) {
+ //There should be only one response since we searched by id
+ if (typeof data.response.docs !== "undefined") {
+ var doc = data.response.docs[0];
+
+ //Is this document a resource map itself?
+ if (doc.formatId == "http://www.openarchives.org/ore/terms") {
+ model.set("id", doc.id); //this is the package model ID
+ model.set("members", new Array()); //Reset the member list
+ model.getMembers();
+ }
+ //If there is no resource map, then this is the only document to in this package
+ else if (
+ typeof doc.resourceMap === "undefined" ||
+ !doc.resourceMap
+ ) {
+ model.set("id", null);
+ model.set("memberIds", new Array(doc.id));
+ model.set("members", [new SolrResult(doc)]);
+ model.trigger("change:members");
+ model.flagComplete();
+ } else {
+ model.set("id", doc.resourceMap[0]);
+ model.getMembers();
+ }
}
- return models;
- },
-
-
- /*
- * Overwrite the Backbone.Model.save() function to set custom options
- */
- save: function(attrs, options){
- if(!options) var options = {};
-
- //Get the system metadata first
- if(!this.get("hasSystemMetadata")){
- var model = this;
- var requestSettings = {
- url: MetacatUI.appModel.get("metaServiceUrl") + encodeURIComponent(this.get("id")),
- success: function(response){
- model.parseSysMeta(response);
-
- model.set("hasSystemMetadata", true);
- model.save.call(model, null, options);
- },
- dataType: "text"
- }
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- return;
- }
-
- //Create a new pid if we are updating the object
- if(!options.sysMetaOnly){
- //Set a new id
- var oldPid = this.get("id");
- this.set("oldPid", oldPid);
- this.set("id", "urn:uuid:" + uuid.v4());
- this.set("obsoletes", oldPid);
- this.set("obsoletedBy", null);
- this.set("archived", false);
- }
-
- //Create the system metadata
- var sysMetaXML = this.serializeSysMeta();
-
- //Send the new pid, old pid, and system metadata
- var xmlBlob = new Blob([sysMetaXML], {type : 'application/xml'});
- var formData = new FormData();
- formData.append("sysmeta", xmlBlob, "sysmeta");
-
- //Let's try updating the system metadata for now
- if(options.sysMetaOnly){
- formData.append("pid", this.get("id"));
-
- var requestSettings = {
- url: MetacatUI.appModel.get("metaServiceUrl"),
- type: "PUT",
- cache: false,
- contentType: false,
- processData: false,
- data: formData,
- success: function(response){
- },
- error: function(data){
- console.log("error updating system metadata");
- }
- }
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- }
- else{
- //Add the ids to the form data
- formData.append("newPid", this.get("id"));
- formData.append("pid", oldPid);
-
- //Create the resource map XML
- var mapXML = this.serialize();
- var mapBlob = new Blob([mapXML], {type : 'application/xml'});
- formData.append("object", mapBlob);
-
- //Get the size of the new resource map
- this.set("size", mapBlob.size);
-
- //Get the new checksum of the resource map
- var checksum = md5(mapXML);
- this.set("checksum", checksum);
-
- var requestSettings = {
- url: MetacatUI.appModel.get("objectServiceUrl"),
- type: "PUT",
- cache: false,
- contentType: false,
- processData: false,
- data: formData,
- success: function(response){
- },
- error: function(data){
- console.log("error udpating object");
- }
- }
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- }
- },
-
- parseSysMeta: function(response){
- this.set("sysMetaXML", $.parseHTML(response));
-
- var responseDoc = $.parseHTML(response),
- systemMetadata,
- prependXML = "",
- appendXML = "";
-
- for(var i=0; i -1))
- systemMetadata = responseDoc[i];
- }
-
- //Parse the XML to JSON
- var sysMetaValues = this.toJson(systemMetadata),
- camelCasedValues = {};
- //Convert the JSON to a camel-cased version, which matches Solr and is easier to work with in code
- _.each(Object.keys(sysMetaValues), function(key){
- camelCasedValues[this.sysMetaNodeMap[key]] = sysMetaValues[key];
- }, this);
-
- //Set the values on the model
- this.set(camelCasedValues);
- },
-
- serialize: function(){
- //Create an RDF serializer
- var serializer = rdf.Serializer();
- serializer.store = this.dataPackageGraph;
-
- //Define the namespaces
- var ORE = rdf.Namespace(this.namespaces.ORE),
- CITO = rdf.Namespace(this.namespaces.CITO);
-
- //Get the pid of this package - depends on whether we are updating or creating a resource map
- var pid = this.get("id"),
- oldPid = this.get("oldPid"),
- updating = oldPid? true : false;
-
- //Update the pids in the RDF graph only if we are updating the resource map with a new pid
- if(updating){
-
- //Find the identifier statement in the resource map
- var idNode = rdf.lit(oldPid),
- idStatement = this.dataPackageGraph.statementsMatching(undefined, undefined, idNode);
-
- //Get the CN Resolve Service base URL from the resource map (mostly important in dev environments where it will not always be cn.dataone.org)
- var cnResolveUrl = idStatement[0].subject.value.substring(0, idStatement[0].subject.value.indexOf(oldPid));
- this.dataPackageGraph.cnResolveUrl = cnResolveUrl;
-
- //Create variations of the resource map ID using the resolve URL so we can always find it in the RDF graph
- var oldPidVariations = [oldPid, encodeURIComponent(oldPid), cnResolveUrl+ encodeURIComponent(oldPid)];
-
- //Get all the isAggregatedBy statements
- var aggregationNode = rdf.sym(cnResolveUrl + encodeURIComponent(oldPid) + "#aggregation"),
- aggByStatements = this.dataPackageGraph.statementsMatching(undefined, ORE("isAggregatedBy"));
-
- //Using the isAggregatedBy statements, find all the DataONE object ids in the RDF graph
- var idsFromXML = [];
- _.each(aggByStatements, function(statement){
-
- //Check if the resource map ID is the old existing id, so we don't collect ids that are not about this resource map
- if(_.find(oldPidVariations, function(oldPidV){ return (oldPidV + "#aggregation" == statement.object.value) })){
- var statementID = statement.subject.value;
- idsFromXML.push(statementID);
-
- //Add variations of the ID so we make sure we account for all the ways they exist in the RDF XML
- if(statementID.indexOf(cnResolveUrl) > -1)
- idsFromXML.push(statementID.substring(statementID.lastIndexOf("/") + 1));
- else
- idsFromXML.push(cnResolveUrl + encodeURIComponent(statementID));
- }
-
- }, this);
-
- //Get all the ids from this model
- var idsFromModel = _.invoke(this.get("members"), "get", "id");
-
- //Find the difference between the model IDs and the XML IDs to get a list of added members
- var addedIds = _.without(_.difference(idsFromModel, idsFromXML), oldPidVariations);
- //Create variations of all these ids too
- var allMemberIds = idsFromModel;
- _.each(idsFromModel, function(id){
- allMemberIds.push(cnResolveUrl + encodeURIComponent(id));
- });
-
- //Remove any other isAggregatedBy statements that are not listed as members of this model
- _.each(aggByStatements, function(statement){
- if(!_.contains(allMemberIds, statement.subject.value))
- this.removeFromAggregation(statement.subject.value);
- else if(_.find(oldPidVariations, function(oldPidV){ return (oldPidV + "#aggregation" == statement.object.value) }))
- statement.object.value = cnResolveUrl+ encodeURIComponent(pid) + "#aggregation";
- }, this);
-
- //Change all the statements in the RDF where the aggregation is the subject, to reflect the new resource map ID
- var aggregationSubjStatements = this.dataPackageGraph.statementsMatching(aggregationNode);
- _.each(aggregationSubjStatements, function(statement){
- statement.subject.value = cnResolveUrl + encodeURIComponent(pid) + "#aggregation";
- });
-
- //Change all the statements in the RDF where the aggregation is the object, to reflect the new resource map ID
- var aggregationObjStatements = this.dataPackageGraph.statementsMatching(undefined, undefined, aggregationNode);
- _.each(aggregationObjStatements, function(statement){
- statement.object.value = cnResolveUrl+ encodeURIComponent(pid) + "#aggregation";
- });
-
- //Change all the resource map subject nodes in the RDF graph
- var rMapNode = rdf.sym(cnResolveUrl + encodeURIComponent(oldPid));
- var rMapStatements = this.dataPackageGraph.statementsMatching(rMapNode);
- _.each(rMapStatements, function(statement){
- statement.subject.value = cnResolveUrl + encodeURIComponent(pid);
- });
-
- //Change the idDescribedBy statement
- var isDescribedByStatements = this.dataPackageGraph.statementsMatching(undefined, ORE("isDescribedBy"), rdf.sym(oldPid));
- if(isDescribedByStatements[0])
- isDescribedByStatements[0].object.value = pid;
-
- //Add nodes for new package members
- _.each(addedIds, function(id){
- this.addToAggregation(id);
- }, this);
-
- //Change all the resource map identifier literal node in the RDF graph
- if(idStatement[0]) idStatement[0].object.value = pid;
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ /* Get all the members of a resource map/package based on the id attribute of this model.
+ * Create a SolrResult model for each member and save it in the members[] attribute of this model. */
+ getMembers: function (options) {
+ this.pending = true;
+
+ var model = this,
+ members = [],
+ pids = []; //Keep track of each object pid
+
+ //*** Find all the files that are a part of this resource map and the resource map itself
+ var provFlList = MetacatUI.appSearchModel.getProvFlList();
+ var query =
+ "fl=resourceMap,fileName,obsoletes,obsoletedBy,size,formatType,formatId,id,datasource," +
+ "rightsHolder,dateUploaded,archived,title,origin,prov_instanceOfClass,isDocumentedBy,isPublic" +
+ "&rows=1000" +
+ "&q=%28resourceMap:%22" +
+ encodeURIComponent(this.id) +
+ "%22%20OR%20id:%22" +
+ encodeURIComponent(this.id) +
+ "%22%29" +
+ "&wt=json";
+
+ if (this.get("getArchivedMembers")) {
+ query += "&archived=archived:*";
+ }
+
+ var requestSettings = {
+ url: MetacatUI.appModel.get("queryServiceUrl") + query,
+ success: function (data, textStatus, xhr) {
+ //Separate the resource maps from the data/metadata objects
+ _.each(data.response.docs, function (doc) {
+ if (doc.id == model.get("id")) {
+ model.set("indexDoc", doc);
+ model.set(doc);
+ if (
+ model.get("resourceMap") &&
+ options &&
+ options.getParentMetadata
+ )
+ model.getParentMetadata();
+ } else {
+ pids.push(doc.id);
+
+ if (doc.formatType == "RESOURCE") {
+ var newPckg = new PackageModel(doc);
+ newPckg.set("parentPackage", model);
+ members.push(newPckg);
+ } else members.push(new SolrResult(doc));
+ }
+ });
+
+ model.set("memberIds", _.uniq(pids));
+ model.set("members", members);
+
+ if (model.getNestedPackages().length > 0)
+ model.createNestedPackages();
+ else model.flagComplete();
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+
+ return this;
+ },
+
+ /*
+ * Send custom options to the Backbone.Model.fetch() function
+ */
+ fetch: function (options) {
+ if (!options) var options = {};
+
+ var fetchOptions = _.extend({ dataType: "text" }, options);
+
+ //Add the authorization options
+ fetchOptions = _.extend(
+ fetchOptions,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
+
+ return Backbone.Model.prototype.fetch.call(this, fetchOptions);
+ },
+
+ /*
+ * Deserialize a Package from OAI-ORE RDF XML
+ */
+ parse: function (response, options) {
+ //Save the raw XML in case it needs to be used later
+ this.set("objectXML", $.parseHTML(response));
+
+ //Define the namespaces
+ var RDF = rdf.Namespace(this.namespaces.RDF),
+ FOAF = rdf.Namespace(this.namespaces.FOAF),
+ OWL = rdf.Namespace(this.namespaces.OWL),
+ DC = rdf.Namespace(this.namespaces.DC),
+ ORE = rdf.Namespace(this.namespaces.ORE),
+ DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
+ CITO = rdf.Namespace(this.namespaces.CITO);
+
+ var memberStatements = [],
+ memberURIParts,
+ memberPIDStr,
+ memberPID,
+ memberModel,
+ models = []; // the models returned by parse()
+
+ try {
+ rdf.parse(
+ response,
+ this.dataPackageGraph,
+ MetacatUI.appModel.get("objectServiceUrl") +
+ (encodeURIComponent(this.id) ||
+ encodeURIComponent(this.seriesid)),
+ "application/rdf+xml",
+ );
+
+ // List the package members
+ memberStatements = this.dataPackageGraph.statementsMatching(
+ undefined,
+ ORE("aggregates"),
+ undefined,
+ undefined,
+ );
+
+ var memberPIDs = [],
+ members = [],
+ currentMembers = this.get("members"),
+ model = this;
+
+ // Get system metadata for each member to eval the formatId
+ _.each(
+ memberStatements,
+ function (memberStatement) {
+ memberURIParts = memberStatement.object.value.split("/");
+ memberPIDStr = _.last(memberURIParts);
+ memberPID = decodeURIComponent(memberPIDStr);
+
+ if (memberPID) {
+ memberPIDs.push(memberPID);
+
+ //Get the current model from the member list, if it exists
+ var existingModel = _.find(currentMembers, function (m) {
+ return m.get("id") == decodeURIComponent(memberPID);
+ });
+
+ //Add the existing model to the new member list
+ if (existingModel) {
+ members.push(existingModel);
+ }
+ //Or create a new SolrResult model
+ else {
+ members.push(
+ new SolrResult({
+ id: decodeURIComponent(memberPID),
+ }),
+ );
+ }
+ }
+ },
+ this,
+ );
+
+ //Get the documents relationships
+ var documentedByStatements = this.dataPackageGraph.statementsMatching(
+ undefined,
+ CITO("isDocumentedBy"),
+ undefined,
+ undefined,
+ ),
+ metadataPids = [];
+
+ _.each(
+ documentedByStatements,
+ function (statement) {
+ //Get the data object that is documentedBy metadata
+ var dataPid = decodeURIComponent(
+ _.last(statement.subject.value.split("/")),
+ ),
+ dataObj = _.find(members, function (m) {
+ return m.get("id") == dataPid;
+ }),
+ metadataPid = _.last(statement.object.value.split("/"));
+
+ //Save this as a metadata model
+ metadataPids.push(metadataPid);
+
+ //Set the isDocumentedBy field
+ var isDocBy = dataObj.get("isDocumentedBy");
+ if (isDocBy && Array.isArray(isDocBy)) isDocBy.push(metadataPid);
+ else if (isDocBy && !Array.isArray(isDocBy))
+ isDocBy = [isDocBy, metadataPid];
+ else isDocBy = [metadataPid];
+
+ dataObj.set("isDocumentedBy", isDocBy);
+ },
+ this,
+ );
+
+ //Get the metadata models and mark them as metadata
+ var metadataModels = _.filter(members, function (m) {
+ return _.contains(metadataPids, m.get("id"));
+ });
+ _.invoke(metadataModels, "set", "formatType", "METADATA");
+
+ //Keep the pids in the collection for easy access later
+ this.set("memberIds", memberPIDs);
+ this.set("members", members);
+ } catch (error) {
+ console.log(error);
+ }
+ return models;
+ },
+
+ /*
+ * Overwrite the Backbone.Model.save() function to set custom options
+ */
+ save: function (attrs, options) {
+ if (!options) var options = {};
+
+ //Get the system metadata first
+ if (!this.get("hasSystemMetadata")) {
+ var model = this;
+ var requestSettings = {
+ url:
+ MetacatUI.appModel.get("metaServiceUrl") +
+ encodeURIComponent(this.get("id")),
+ success: function (response) {
+ model.parseSysMeta(response);
+
+ model.set("hasSystemMetadata", true);
+ model.save.call(model, null, options);
+ },
+ dataType: "text",
+ };
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ return;
+ }
+
+ //Create a new pid if we are updating the object
+ if (!options.sysMetaOnly) {
+ //Set a new id
+ var oldPid = this.get("id");
+ this.set("oldPid", oldPid);
+ this.set("id", "urn:uuid:" + uuid.v4());
+ this.set("obsoletes", oldPid);
+ this.set("obsoletedBy", null);
+ this.set("archived", false);
+ }
+
+ //Create the system metadata
+ var sysMetaXML = this.serializeSysMeta();
+
+ //Send the new pid, old pid, and system metadata
+ var xmlBlob = new Blob([sysMetaXML], { type: "application/xml" });
+ var formData = new FormData();
+ formData.append("sysmeta", xmlBlob, "sysmeta");
+
+ //Let's try updating the system metadata for now
+ if (options.sysMetaOnly) {
+ formData.append("pid", this.get("id"));
+
+ var requestSettings = {
+ url: MetacatUI.appModel.get("metaServiceUrl"),
+ type: "PUT",
+ cache: false,
+ contentType: false,
+ processData: false,
+ data: formData,
+ success: function (response) {},
+ error: function (data) {
+ console.log("error updating system metadata");
+ },
+ };
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ } else {
+ //Add the ids to the form data
+ formData.append("newPid", this.get("id"));
+ formData.append("pid", oldPid);
+
+ //Create the resource map XML
+ var mapXML = this.serialize();
+ var mapBlob = new Blob([mapXML], { type: "application/xml" });
+ formData.append("object", mapBlob);
+
+ //Get the size of the new resource map
+ this.set("size", mapBlob.size);
+
+ //Get the new checksum of the resource map
+ var checksum = md5(mapXML);
+ this.set("checksum", checksum);
+
+ var requestSettings = {
+ url: MetacatUI.appModel.get("objectServiceUrl"),
+ type: "PUT",
+ cache: false,
+ contentType: false,
+ processData: false,
+ data: formData,
+ success: function (response) {},
+ error: function (data) {
+ console.log("error udpating object");
+ },
+ };
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ }
+ },
+
+ parseSysMeta: function (response) {
+ this.set("sysMetaXML", $.parseHTML(response));
+
+ var responseDoc = $.parseHTML(response),
+ systemMetadata,
+ prependXML = "",
+ appendXML = "";
+
+ for (var i = 0; i < responseDoc.length; i++) {
+ if (
+ responseDoc[i].nodeType == 1 &&
+ responseDoc[i].localName.indexOf("systemmetadata") > -1
+ )
+ systemMetadata = responseDoc[i];
+ }
+ //Parse the XML to JSON
+ var sysMetaValues = this.toJson(systemMetadata),
+ camelCasedValues = {};
+ //Convert the JSON to a camel-cased version, which matches Solr and is easier to work with in code
+ _.each(
+ Object.keys(sysMetaValues),
+ function (key) {
+ camelCasedValues[this.sysMetaNodeMap[key]] = sysMetaValues[key];
+ },
+ this,
+ );
+
+ //Set the values on the model
+ this.set(camelCasedValues);
+ },
+
+ serialize: function () {
+ //Create an RDF serializer
+ var serializer = rdf.Serializer();
+ serializer.store = this.dataPackageGraph;
+
+ //Define the namespaces
+ var ORE = rdf.Namespace(this.namespaces.ORE),
+ CITO = rdf.Namespace(this.namespaces.CITO);
+
+ //Get the pid of this package - depends on whether we are updating or creating a resource map
+ var pid = this.get("id"),
+ oldPid = this.get("oldPid"),
+ updating = oldPid ? true : false;
+
+ //Update the pids in the RDF graph only if we are updating the resource map with a new pid
+ if (updating) {
+ //Find the identifier statement in the resource map
+ var idNode = rdf.lit(oldPid),
+ idStatement = this.dataPackageGraph.statementsMatching(
+ undefined,
+ undefined,
+ idNode,
+ );
+
+ //Get the CN Resolve Service base URL from the resource map (mostly important in dev environments where it will not always be cn.dataone.org)
+ var cnResolveUrl = idStatement[0].subject.value.substring(
+ 0,
+ idStatement[0].subject.value.indexOf(oldPid),
+ );
+ this.dataPackageGraph.cnResolveUrl = cnResolveUrl;
+
+ //Create variations of the resource map ID using the resolve URL so we can always find it in the RDF graph
+ var oldPidVariations = [
+ oldPid,
+ encodeURIComponent(oldPid),
+ cnResolveUrl + encodeURIComponent(oldPid),
+ ];
+
+ //Get all the isAggregatedBy statements
+ var aggregationNode = rdf.sym(
+ cnResolveUrl + encodeURIComponent(oldPid) + "#aggregation",
+ ),
+ aggByStatements = this.dataPackageGraph.statementsMatching(
+ undefined,
+ ORE("isAggregatedBy"),
+ );
+
+ //Using the isAggregatedBy statements, find all the DataONE object ids in the RDF graph
+ var idsFromXML = [];
+ _.each(
+ aggByStatements,
+ function (statement) {
+ //Check if the resource map ID is the old existing id, so we don't collect ids that are not about this resource map
+ if (
+ _.find(oldPidVariations, function (oldPidV) {
+ return oldPidV + "#aggregation" == statement.object.value;
+ })
+ ) {
+ var statementID = statement.subject.value;
+ idsFromXML.push(statementID);
+
+ //Add variations of the ID so we make sure we account for all the ways they exist in the RDF XML
+ if (statementID.indexOf(cnResolveUrl) > -1)
+ idsFromXML.push(
+ statementID.substring(statementID.lastIndexOf("/") + 1),
+ );
+ else
+ idsFromXML.push(
+ cnResolveUrl + encodeURIComponent(statementID),
+ );
+ }
+ },
+ this,
+ );
+
+ //Get all the ids from this model
+ var idsFromModel = _.invoke(this.get("members"), "get", "id");
+
+ //Find the difference between the model IDs and the XML IDs to get a list of added members
+ var addedIds = _.without(
+ _.difference(idsFromModel, idsFromXML),
+ oldPidVariations,
+ );
+ //Create variations of all these ids too
+ var allMemberIds = idsFromModel;
+ _.each(idsFromModel, function (id) {
+ allMemberIds.push(cnResolveUrl + encodeURIComponent(id));
+ });
+
+ //Remove any other isAggregatedBy statements that are not listed as members of this model
+ _.each(
+ aggByStatements,
+ function (statement) {
+ if (!_.contains(allMemberIds, statement.subject.value))
+ this.removeFromAggregation(statement.subject.value);
+ else if (
+ _.find(oldPidVariations, function (oldPidV) {
+ return oldPidV + "#aggregation" == statement.object.value;
+ })
+ )
+ statement.object.value =
+ cnResolveUrl + encodeURIComponent(pid) + "#aggregation";
+ },
+ this,
+ );
+
+ //Change all the statements in the RDF where the aggregation is the subject, to reflect the new resource map ID
+ var aggregationSubjStatements =
+ this.dataPackageGraph.statementsMatching(aggregationNode);
+ _.each(aggregationSubjStatements, function (statement) {
+ statement.subject.value =
+ cnResolveUrl + encodeURIComponent(pid) + "#aggregation";
+ });
+
+ //Change all the statements in the RDF where the aggregation is the object, to reflect the new resource map ID
+ var aggregationObjStatements =
+ this.dataPackageGraph.statementsMatching(
+ undefined,
+ undefined,
+ aggregationNode,
+ );
+ _.each(aggregationObjStatements, function (statement) {
+ statement.object.value =
+ cnResolveUrl + encodeURIComponent(pid) + "#aggregation";
+ });
+
+ //Change all the resource map subject nodes in the RDF graph
+ var rMapNode = rdf.sym(cnResolveUrl + encodeURIComponent(oldPid));
+ var rMapStatements =
+ this.dataPackageGraph.statementsMatching(rMapNode);
+ _.each(rMapStatements, function (statement) {
+ statement.subject.value = cnResolveUrl + encodeURIComponent(pid);
+ });
+
+ //Change the idDescribedBy statement
+ var isDescribedByStatements =
+ this.dataPackageGraph.statementsMatching(
+ undefined,
+ ORE("isDescribedBy"),
+ rdf.sym(oldPid),
+ );
+ if (isDescribedByStatements[0])
+ isDescribedByStatements[0].object.value = pid;
+
+ //Add nodes for new package members
+ _.each(
+ addedIds,
+ function (id) {
+ this.addToAggregation(id);
+ },
+ this,
+ );
+
+ //Change all the resource map identifier literal node in the RDF graph
+ if (idStatement[0]) idStatement[0].object.value = pid;
+ }
+
+ //Now serialize the RDF XML
+ var serializer = rdf.Serializer();
+ serializer.store = this.dataPackageGraph;
+
+ var xmlString = serializer.statementsToXML(
+ this.dataPackageGraph.statements,
+ );
+
+ return xmlString;
+ },
+
+ serializeSysMeta: function () {
+ //Get the system metadata XML that currently exists in the system
+ var xml = $(this.get("sysMetaXML"));
+
+ //Update the system metadata values
+ xml.find("serialversion").text(this.get("serialVersion") || "0");
+ xml.find("identifier").text(this.get("newPid") || this.get("id"));
+ xml.find("formatid").text(this.get("formatId"));
+ xml.find("size").text(this.get("size"));
+ xml.find("checksum").text(this.get("checksum"));
+ xml
+ .find("submitter")
+ .text(
+ this.get("submitter") || MetacatUI.appUserModel.get("username"),
+ );
+ xml
+ .find("rightsholder")
+ .text(
+ this.get("rightsHolder") || MetacatUI.appUserModel.get("username"),
+ );
+ xml.find("archived").text(this.get("archived"));
+ xml
+ .find("dateuploaded")
+ .text(this.get("dateUploaded") || new Date().toISOString());
+ xml
+ .find("datesysmetadatamodified")
+ .text(
+ this.get("dateSysMetadataModified") || new Date().toISOString(),
+ );
+ xml
+ .find("originmembernode")
+ .text(
+ this.get("originMemberNode") ||
+ MetacatUI.nodeModel.get("currentMemberNode"),
+ );
+ xml
+ .find("authoritativemembernode")
+ .text(
+ this.get("authoritativeMemberNode") ||
+ MetacatUI.nodeModel.get("currentMemberNode"),
+ );
+
+ if (this.get("obsoletes"))
+ xml.find("obsoletes").text(this.get("obsoletes"));
+ else xml.find("obsoletes").remove();
+
+ if (this.get("obsoletedBy"))
+ xml.find("obsoletedby").text(this.get("obsoletedBy"));
+ else xml.find("obsoletedby").remove();
+
+ //Write the access policy
+ var accessPolicyXML = "\n";
+ _.each(this.get("accesspolicy"), function (policy, policyType, all) {
+ var fullPolicy = all[policyType];
+
+ _.each(fullPolicy, function (policyPart) {
+ accessPolicyXML += "\t<" + policyType + ">\n";
+
+ accessPolicyXML +=
+ "\t\t" + policyPart.subject + " \n";
+
+ var permissions = Array.isArray(policyPart.permission)
+ ? policyPart.permission
+ : [policyPart.permission];
+ _.each(permissions, function (perm) {
+ accessPolicyXML += "\t\t" + perm + " \n";
+ });
+
+ accessPolicyXML += "\t" + policyType + ">\n";
+ });
+ });
+ accessPolicyXML += " ";
+
+ //Replace the old access policy with the new one
+ xml.find("accesspolicy").replaceWith(accessPolicyXML);
+
+ var xmlString = $(document.createElement("div"))
+ .append(xml.clone())
+ .html();
+
+ //Now camel case the nodes
+ _.each(
+ Object.keys(this.sysMetaNodeMap),
+ function (name, i, allNodeNames) {
+ var regEx = new RegExp("<" + name, "g");
+ xmlString = xmlString.replace(
+ regEx,
+ "<" + this.sysMetaNodeMap[name],
+ );
+ var regEx = new RegExp(name + ">", "g");
+ xmlString = xmlString.replace(
+ regEx,
+ this.sysMetaNodeMap[name] + ">",
+ );
+ },
+ this,
+ );
+
+ xmlString = xmlString.replace(/systemmetadata/g, "systemMetadata");
+
+ return xmlString;
+ },
+
+ //Adds a new object to the resource map RDF graph
+ addToAggregation: function (id) {
+ if (id.indexOf(this.dataPackageGraph.cnResolveUrl) < 0)
+ var fullID =
+ this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id);
+ else {
+ var fullID = id;
+ id = id.substring(
+ this.dataPackageGraph.cnResolveUrl.lastIndexOf("/") + 1,
+ );
+ }
+
+ //Initialize the namespaces
+ var ORE = rdf.Namespace(this.namespaces.ORE),
+ DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
+ XML = rdf.Namespace(this.namespaces.XML),
+ CITO = rdf.Namespace(this.namespaces.CITO);
+
+ //Create a node for this object, the identifier, the resource map, and the aggregation
+ var objectNode = rdf.sym(fullID),
+ mapNode = rdf.sym(
+ this.dataPackageGraph.cnResolveUrl +
+ encodeURIComponent(this.get("id")),
+ ),
+ aggNode = rdf.sym(
+ this.dataPackageGraph.cnResolveUrl +
+ encodeURIComponent(this.get("id")) +
+ "#aggregation",
+ ),
+ idNode = rdf.literal(id, undefined, XML("string"));
+
+ //Add the statement: this object isAggregatedBy the resource map aggregation
+ this.dataPackageGraph.addStatement(
+ rdf.st(objectNode, ORE("isAggregatedBy"), aggNode),
+ );
+ //Add the statement: The resource map aggregation aggregates this object
+ this.dataPackageGraph.addStatement(
+ rdf.st(aggNode, ORE("aggregates"), objectNode),
+ );
+ //Add the statement: This object has the identifier {id}
+ this.dataPackageGraph.addStatement(
+ rdf.st(objectNode, DCTERMS("identifier"), idNode),
+ );
+
+ //Find the metadata doc that describes this object
+ var model = _.find(this.get("members"), function (m) {
+ return m.get("id") == id;
+ }),
+ isDocBy = model.get("isDocumentedBy");
+
+ //If this object is documented by any metadata...
+ if (isDocBy) {
+ //Get the ids of all the metadata objects in this package
+ var metadataInPackage = _.compact(
+ _.map(this.get("members"), function (m) {
+ if (m.get("formatType") == "METADATA") return m.get("id");
+ }),
+ );
+ //Find the metadata IDs that are in this package that also documents this data object
+ var metadataIds = Array.isArray(isDocBy)
+ ? _.intersection(metadataInPackage, isDocBy)
+ : _.intersection(metadataInPackage, [isDocBy]);
+
+ //For each metadata that documents this object, add a CITO:isDocumentedBy and CITO:documents statement
+ _.each(
+ metadataIds,
+ function (metaId) {
+ //Create the named nodes and statements
+ var memberNode = rdf.sym(
+ this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id),
+ ),
+ metadataNode = rdf.sym(
+ this.dataPackageGraph.cnResolveUrl +
+ encodeURIComponent(metaId),
+ ),
+ isDocByStatement = rdf.st(
+ memberNode,
+ CITO("isDocumentedBy"),
+ metadataNode,
+ ),
+ documentsStatement = rdf.st(
+ metadataNode,
+ CITO("documents"),
+ memberNode,
+ );
+ //Add the statements
+ this.dataPackageGraph.addStatement(isDocByStatement);
+ this.dataPackageGraph.addStatement(documentsStatement);
+ },
+ this,
+ );
+ }
+ },
+
+ removeFromAggregation: function (id) {
+ if (!id.indexOf(this.dataPackageGraph.cnResolveUrl))
+ id = this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id);
+
+ var removedObjNode = rdf.sym(id),
+ statements = _.union(
+ this.dataPackageGraph.statementsMatching(
+ undefined,
+ undefined,
+ removedObjNode,
+ ),
+ this.dataPackageGraph.statementsMatching(removedObjNode),
+ );
+
+ this.dataPackageGraph.removeStatements(statements);
+ },
+
+ getParentMetadata: function () {
+ var rMapIds = this.get("resourceMap");
+
+ //Create a query that searches for any resourceMap with an id matching one of the parents OR an id that matches one of the parents.
+ //This will return all members of the parent resource maps AND the parent resource maps themselves
+ var rMapQuery = "",
+ idQuery = "";
+ if (Array.isArray(rMapIds) && rMapIds.length > 1) {
+ _.each(rMapIds, function (id, i, ids) {
+ //At the begininng of the list of ids
+ if (rMapQuery.length == 0) {
+ rMapQuery += "resourceMap:(";
+ idQuery += "id:(";
}
- //Now serialize the RDF XML
- var serializer = rdf.Serializer();
- serializer.store = this.dataPackageGraph;
-
- var xmlString = serializer.statementsToXML(this.dataPackageGraph.statements);
-
- return xmlString;
- },
-
- serializeSysMeta: function(){
- //Get the system metadata XML that currently exists in the system
- var xml = $(this.get("sysMetaXML"));
-
- //Update the system metadata values
- xml.find("serialversion").text(this.get("serialVersion") || "0");
- xml.find("identifier").text((this.get("newPid") || this.get("id")));
- xml.find("formatid").text(this.get("formatId"));
- xml.find("size").text(this.get("size"));
- xml.find("checksum").text(this.get("checksum"));
- xml.find("submitter").text(this.get("submitter") || MetacatUI.appUserModel.get("username"));
- xml.find("rightsholder").text(this.get("rightsHolder") || MetacatUI.appUserModel.get("username"));
- xml.find("archived").text(this.get("archived"));
- xml.find("dateuploaded").text(this.get("dateUploaded") || new Date().toISOString());
- xml.find("datesysmetadatamodified").text(this.get("dateSysMetadataModified") || new Date().toISOString());
- xml.find("originmembernode").text(this.get("originMemberNode") || MetacatUI.nodeModel.get("currentMemberNode"));
- xml.find("authoritativemembernode").text(this.get("authoritativeMemberNode") || MetacatUI.nodeModel.get("currentMemberNode"));
-
- if(this.get("obsoletes"))
- xml.find("obsoletes").text(this.get("obsoletes"));
- else
- xml.find("obsoletes").remove();
-
- if(this.get("obsoletedBy"))
- xml.find("obsoletedby").text(this.get("obsoletedBy"));
- else
- xml.find("obsoletedby").remove();
-
- //Write the access policy
- var accessPolicyXML = '\n';
- _.each(this.get("accesspolicy"), function(policy, policyType, all){
- var fullPolicy = all[policyType];
-
- _.each(fullPolicy, function(policyPart){
- accessPolicyXML += '\t<' + policyType + '>\n';
-
- accessPolicyXML += '\t\t' + policyPart.subject + ' \n';
-
- var permissions = Array.isArray(policyPart.permission)? policyPart.permission : [policyPart.permission];
- _.each(permissions, function(perm){
- accessPolicyXML += '\t\t' + perm + ' \n';
- });
-
- accessPolicyXML += '\t' + policyType + '>\n';
- });
- });
- accessPolicyXML += ' ';
-
- //Replace the old access policy with the new one
- xml.find("accesspolicy").replaceWith(accessPolicyXML);
-
- var xmlString = $(document.createElement("div")).append(xml.clone()).html();
-
- //Now camel case the nodes
- _.each(Object.keys(this.sysMetaNodeMap), function(name, i, allNodeNames){
- var regEx = new RegExp("<" + name, "g");
- xmlString = xmlString.replace(regEx, "<" + this.sysMetaNodeMap[name]);
- var regEx = new RegExp(name + ">", "g");
- xmlString = xmlString.replace(regEx, this.sysMetaNodeMap[name] + ">");
- }, this);
-
- xmlString = xmlString.replace(/systemmetadata/g, "systemMetadata");
-
- return xmlString;
- },
-
- //Adds a new object to the resource map RDF graph
- addToAggregation: function(id){
- if(id.indexOf(this.dataPackageGraph.cnResolveUrl) < 0)
- var fullID = this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id);
- else{
- var fullID = id;
- id = id.substring(this.dataPackageGraph.cnResolveUrl.lastIndexOf("/") + 1);
- }
-
- //Initialize the namespaces
- var ORE = rdf.Namespace(this.namespaces.ORE),
- DCTERMS = rdf.Namespace(this.namespaces.DCTERMS),
- XML = rdf.Namespace(this.namespaces.XML),
- CITO = rdf.Namespace(this.namespaces.CITO);
-
- //Create a node for this object, the identifier, the resource map, and the aggregation
- var objectNode = rdf.sym(fullID),
- mapNode = rdf.sym(this.dataPackageGraph.cnResolveUrl + encodeURIComponent(this.get("id"))),
- aggNode = rdf.sym(this.dataPackageGraph.cnResolveUrl + encodeURIComponent(this.get("id")) + "#aggregation"),
- idNode = rdf.literal(id, undefined, XML("string"));
-
- //Add the statement: this object isAggregatedBy the resource map aggregation
- this.dataPackageGraph.addStatement(rdf.st(objectNode, ORE("isAggregatedBy"), aggNode));
- //Add the statement: The resource map aggregation aggregates this object
- this.dataPackageGraph.addStatement(rdf.st(aggNode, ORE("aggregates"), objectNode));
- //Add the statement: This object has the identifier {id}
- this.dataPackageGraph.addStatement(rdf.st(objectNode, DCTERMS("identifier"), idNode));
-
- //Find the metadata doc that describes this object
- var model = _.find(this.get("members"), function(m){ return m.get("id") == id }),
- isDocBy = model.get("isDocumentedBy");
-
- //If this object is documented by any metadata...
- if(isDocBy){
- //Get the ids of all the metadata objects in this package
- var metadataInPackage = _.compact(_.map(this.get("members"), function(m){ if(m.get("formatType") == "METADATA") return m.get("id"); }));
- //Find the metadata IDs that are in this package that also documents this data object
- var metadataIds = Array.isArray(isDocBy)? _.intersection(metadataInPackage, isDocBy) : _.intersection(metadataInPackage, [isDocBy]);
-
- //For each metadata that documents this object, add a CITO:isDocumentedBy and CITO:documents statement
- _.each(metadataIds, function(metaId){
- //Create the named nodes and statements
- var memberNode = rdf.sym(this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id)),
- metadataNode = rdf.sym(this.dataPackageGraph.cnResolveUrl + encodeURIComponent(metaId)),
- isDocByStatement = rdf.st(memberNode, CITO("isDocumentedBy"), metadataNode),
- documentsStatement = rdf.st(metadataNode, CITO("documents"), memberNode);
- //Add the statements
- this.dataPackageGraph.addStatement(isDocByStatement);
- this.dataPackageGraph.addStatement(documentsStatement);
- }, this);
- }
- },
-
- removeFromAggregation: function(id){
- if(!id.indexOf(this.dataPackageGraph.cnResolveUrl)) id = this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id);
-
- var removedObjNode = rdf.sym(id),
- statements = _.union(this.dataPackageGraph.statementsMatching(undefined, undefined, removedObjNode),
- this.dataPackageGraph.statementsMatching(removedObjNode));
-
- this.dataPackageGraph.removeStatements(statements);
- },
-
- getParentMetadata: function(){
- var rMapIds = this.get("resourceMap");
-
- //Create a query that searches for any resourceMap with an id matching one of the parents OR an id that matches one of the parents.
- //This will return all members of the parent resource maps AND the parent resource maps themselves
- var rMapQuery = "",
- idQuery = "";
- if(Array.isArray(rMapIds) && (rMapIds.length > 1)){
- _.each(rMapIds, function(id, i, ids){
-
- //At the begininng of the list of ids
- if(rMapQuery.length == 0){
- rMapQuery += "resourceMap:(";
- idQuery += "id:(";
- }
-
- //The id
- rMapQuery += "%22" + encodeURIComponent(id) + "%22";
- idQuery += "%22" + encodeURIComponent(id) + "%22";
-
- //At the end of the list of ids
- if(i+1 == ids.length){
- rMapQuery += ")";
- idQuery += ")";
- }
- //In-between each id
- else{
- rMapQuery += " OR ";
- idQuery += " OR ";
- }
- });
- }
- else{
- //When there is just one parent, the query is simple
- var rMapId = Array.isArray(rMapIds)? rMapIds[0] : rMapIds;
- rMapQuery += "resourceMap:%22" + encodeURIComponent(rMapId) + "%22";
- idQuery += "id:%22" + encodeURIComponent(rMapId) + "%22";
- }
- var query = "fl=title,id,obsoletedBy,resourceMap" +
- "&wt=json" +
- "&group=true&group.field=formatType&group.limit=-1" +
- "&q=((formatType:METADATA AND " + rMapQuery + ") OR " + idQuery + ")";
-
- var model = this;
- var requestSettings = {
- url: MetacatUI.appModel.get("queryServiceUrl") + query,
- success: function(data, textStatus, xhr) {
- var results = data.grouped.formatType.groups,
- resourceMapGroup = _.where(results, { groupValue: "RESOURCE" })[0],
- rMapList = resourceMapGroup? resourceMapGroup.doclist : null,
- rMaps = rMapList? rMapList.docs : [],
- rMapIds = _.pluck(rMaps, "id"),
- parents = [],
- parentIds = [];
-
- //As long as this map isn't obsoleted by another map in our results list, we will show it
- _.each(rMaps, function(map){
- if(! (map.obsoletedBy && _.contains(rMapIds, map.obsoletedBy))){
- parents.push(map);
- parentIds.push(map.id);
- }
- });
-
- var metadataList = _.where(results, {groupValue: "METADATA"})[0],
- metadata = (metadataList && metadataList.doclist)? metadataList.doclist.docs : [],
- metadataModels = [];
+ //The id
+ rMapQuery += "%22" + encodeURIComponent(id) + "%22";
+ idQuery += "%22" + encodeURIComponent(id) + "%22";
+
+ //At the end of the list of ids
+ if (i + 1 == ids.length) {
+ rMapQuery += ")";
+ idQuery += ")";
+ }
+ //In-between each id
+ else {
+ rMapQuery += " OR ";
+ idQuery += " OR ";
+ }
+ });
+ } else {
+ //When there is just one parent, the query is simple
+ var rMapId = Array.isArray(rMapIds) ? rMapIds[0] : rMapIds;
+ rMapQuery += "resourceMap:%22" + encodeURIComponent(rMapId) + "%22";
+ idQuery += "id:%22" + encodeURIComponent(rMapId) + "%22";
+ }
+ var query =
+ "fl=title,id,obsoletedBy,resourceMap" +
+ "&wt=json" +
+ "&group=true&group.field=formatType&group.limit=-1" +
+ "&q=((formatType:METADATA AND " +
+ rMapQuery +
+ ") OR " +
+ idQuery +
+ ")";
+
+ var model = this;
+ var requestSettings = {
+ url: MetacatUI.appModel.get("queryServiceUrl") + query,
+ success: function (data, textStatus, xhr) {
+ var results = data.grouped.formatType.groups,
+ resourceMapGroup = _.where(results, {
+ groupValue: "RESOURCE",
+ })[0],
+ rMapList = resourceMapGroup ? resourceMapGroup.doclist : null,
+ rMaps = rMapList ? rMapList.docs : [],
+ rMapIds = _.pluck(rMaps, "id"),
+ parents = [],
+ parentIds = [];
//As long as this map isn't obsoleted by another map in our results list, we will show it
- _.each(metadata, function(m){
+ _.each(rMaps, function (map) {
+ if (!(map.obsoletedBy && _.contains(rMapIds, map.obsoletedBy))) {
+ parents.push(map);
+ parentIds.push(map.id);
+ }
+ });
+
+ var metadataList = _.where(results, { groupValue: "METADATA" })[0],
+ metadata =
+ metadataList && metadataList.doclist
+ ? metadataList.doclist.docs
+ : [],
+ metadataModels = [];
+ //As long as this map isn't obsoleted by another map in our results list, we will show it
+ _.each(metadata, function (m) {
//Find the metadata doc that obsoletes this one
var isObsoletedBy = _.findWhere(metadata, { id: m.obsoletedBy });
//If one isn't found, then this metadata doc is the most recent
- if(typeof isObsoletedBy == "undefined"){
+ if (typeof isObsoletedBy == "undefined") {
//If this metadata doc is in one of the filtered parent resource maps
- if(_.intersection(parentIds, m.resourceMap).length){
+ if (_.intersection(parentIds, m.resourceMap).length) {
//Create a SolrResult model and add to an array
- metadataModels.push(new SolrResult(m));
+ metadataModels.push(new SolrResult(m));
+ }
+ }
+ });
+
+ model.set("parentPackageMetadata", metadataModels);
+ model.trigger("change:parentPackageMetadata");
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ //Create the URL string that is used to download this package
+ getURL: function () {
+ var url = null;
+
+ //If we haven't set a packageServiceURL upon app initialization and we are querying a CN, then the packageServiceURL is dependent on the MN this package is from
+ if (
+ MetacatUI.appModel.get("d1Service").toLowerCase().indexOf("cn/") >
+ -1 &&
+ MetacatUI.nodeModel.get("members").length
+ ) {
+ var source = this.get("datasource"),
+ node = _.find(MetacatUI.nodeModel.get("members"), {
+ identifier: source,
+ });
+
+ //If this node has MNRead v2 services...
+ if (node && node.readv2)
+ url =
+ node.baseURL +
+ "/v2/packages/application%2Fbagit-097/" +
+ encodeURIComponent(this.get("id"));
+ } else if (MetacatUI.appModel.get("packageServiceUrl"))
+ url =
+ MetacatUI.appModel.get("packageServiceUrl") +
+ encodeURIComponent(this.get("id"));
+
+ this.set("url", url);
+ return url;
+ },
+
+ createNestedPackages: function () {
+ var parentPackage = this,
+ nestedPackages = this.getNestedPackages(),
+ numNestedPackages = nestedPackages.length,
+ numComplete = 0;
+
+ _.each(nestedPackages, function (nestedPackage, i, nestedPackages) {
+ //Flag the parent model as complete when all the nested package info is ready
+ nestedPackage.on("complete", function () {
+ numComplete++;
+
+ //This is the last package in this package - finish up details and flag as complete
+ if (numNestedPackages == numComplete) {
+ var sorted = _.sortBy(parentPackage.get("members"), function (p) {
+ return p.get("id");
+ });
+ parentPackage.set("members", sorted);
+ parentPackage.flagComplete();
+ }
+ });
+
+ //Only look one-level deep at all times to avoid going down a rabbit hole
+ if (
+ nestedPackage.get("parentPackage") &&
+ nestedPackage.get("parentPackage").get("parentPackage")
+ ) {
+ nestedPackage.flagComplete();
+ return;
+ } else {
+ //Get the members of this nested package
+ nestedPackage.getMembers();
+ }
+ });
+ },
+
+ getNestedPackages: function () {
+ return _.where(this.get("members"), { type: "Package" });
+ },
+
+ getMemberNames: function () {
+ var metadata = this.getMetadata();
+ if (!metadata) return false;
+
+ //Load the rendered metadata from the view service
+ var viewService =
+ MetacatUI.appModel.get("viewServiceUrl") +
+ encodeURIComponent(metadata.get("id"));
+ var requestSettings = {
+ url: viewService,
+ success: function (data, response, xhr) {
+ if (solrResult.get("formatType") == "METADATA")
+ entityName = solrResult.get("title");
+ else {
+ var container = viewRef.findEntityDetailsContainer(
+ solrResult.get("id"),
+ );
+ if (container && container.length > 0) {
+ var entityName = $(container)
+ .find(".entityName")
+ .attr("data-entity-name");
+ if (typeof entityName === "undefined" || !entityName) {
+ entityName = $(container)
+ .find(
+ ".control-label:contains('Entity Name') + .controls-well",
+ )
+ .text();
+ if (typeof entityName === "undefined" || !entityName)
+ entityName = null;
}
- }
- });
-
- model.set("parentPackageMetadata", metadataModels);
- model.trigger("change:parentPackageMetadata");
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- //Create the URL string that is used to download this package
- getURL: function(){
- var url = null;
-
- //If we haven't set a packageServiceURL upon app initialization and we are querying a CN, then the packageServiceURL is dependent on the MN this package is from
- if((MetacatUI.appModel.get("d1Service").toLowerCase().indexOf("cn/") > -1) && MetacatUI.nodeModel.get("members").length){
- var source = this.get("datasource"),
- node = _.find(MetacatUI.nodeModel.get("members"), {identifier: source});
-
- //If this node has MNRead v2 services...
- if(node && node.readv2)
- url = node.baseURL + "/v2/packages/application%2Fbagit-097/" + encodeURIComponent(this.get("id"));
- }
- else if(MetacatUI.appModel.get("packageServiceUrl"))
- url = MetacatUI.appModel.get("packageServiceUrl") + encodeURIComponent(this.get("id"));
-
- this.set("url", url);
- return url;
- },
-
- createNestedPackages: function(){
- var parentPackage = this,
- nestedPackages = this.getNestedPackages(),
- numNestedPackages = nestedPackages.length,
- numComplete = 0;
-
- _.each(nestedPackages, function(nestedPackage, i, nestedPackages){
-
- //Flag the parent model as complete when all the nested package info is ready
- nestedPackage.on("complete", function(){
- numComplete++;
-
- //This is the last package in this package - finish up details and flag as complete
- if(numNestedPackages == numComplete){
- var sorted = _.sortBy(parentPackage.get("members"), function(p){ return p.get("id") });
- parentPackage.set("members", sorted);
- parentPackage.flagComplete();
- }
- });
-
- //Only look one-level deep at all times to avoid going down a rabbit hole
- if( nestedPackage.get("parentPackage") && nestedPackage.get("parentPackage").get("parentPackage") ){
- nestedPackage.flagComplete();
- return;
- }
- else{
- //Get the members of this nested package
- nestedPackage.getMembers();
- }
-
- });
- },
-
- getNestedPackages: function(){
- return _.where(this.get("members"), {type: "Package"});
- },
-
- getMemberNames: function(){
- var metadata = this.getMetadata();
- if(!metadata) return false;
-
- //Load the rendered metadata from the view service
- var viewService = MetacatUI.appModel.get("viewServiceUrl") + encodeURIComponent(metadata.get("id"));
- var requestSettings = {
- url: viewService,
- success: function(data, response, xhr){
- if(solrResult.get("formatType") == "METADATA")
- entityName = solrResult.get("title");
- else{
- var container = viewRef.findEntityDetailsContainer(solrResult.get("id"));
- if(container && container.length > 0){
- var entityName = $(container).find(".entityName").attr("data-entity-name");
- if((typeof entityName === "undefined") || (!entityName)){
- entityName = $(container).find(".control-label:contains('Entity Name') + .controls-well").text();
- if((typeof entityName === "undefined") || (!entityName))
- entityName = null;
- }
- }
- else
- entityName = null;
-
- }
- }
- }
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- /*
- * Will query for the derivations of this package, and sort all entities in the prov trace
- * into sources and derivations.
- */
- getProvTrace: function(){
- var model = this;
-
- //See if there are any prov fields in our index before continuing
- if(!MetacatUI.appSearchModel.getProvFields()) return this;
-
- //Start keeping track of the sources and derivations
- var sources = new Array(),
- derivations = new Array();
-
- //Search for derivations of this package
- var derivationsQuery = MetacatUI.appSearchModel.getGroupedQuery("prov_wasDerivedFrom",
- _.map(this.get("members"), function(m){ return m.get("id"); }), "OR") +
- "%20-obsoletedBy:*";
-
- var requestSettings = {
- url: MetacatUI.appModel.get("queryServiceUrl") + "&q=" + derivationsQuery + "&wt=json&rows=1000" +
- "&fl=id,resourceMap,documents,isDocumentedBy,prov_wasDerivedFrom",
- success: function(data){
- _.each(data.response.docs, function(result){
- derivations.push(result.id);
- });
-
- //Make arrays of unique IDs of objects that are sources or derivations of this package.
- _.each(model.get("members"), function(member, i){
- if(member.type == "Package") return;
-
- if(member.hasProvTrace()){
- sources = _.union(sources, member.getSources());
- derivations = _.union(derivations, member.getDerivations());
- }
- });
-
- //Save the arrays of sources and derivations
- model.set("sources", sources);
- model.set("derivations", derivations);
-
- //Now get metadata about all the entities in the prov trace not in this package
- model.getExternalProvTrace();
- }
- }
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- getExternalProvTrace: function(){
- var model = this;
-
- //Compact our list of ids that are in the prov trace by combining the sources and derivations and removing ids of members of this package
- var externalProvEntities = _.difference(_.union(this.get("sources"), this.get("derivations")), this.get("memberIds"));
-
- //If there are no sources or derivations, then we do not need to find resource map ids for anything
- if(!externalProvEntities.length){
-
- //Save this prov trace on a package-member/document/object level.
- if(this.get("sources").length || this.get("derivations").length)
- this.setMemberProvTrace();
-
- //Flag that the provenance trace is complete
- this.set("provenanceFlag", "complete");
-
- return this;
- }
- else{
- //Create a query where we retrieve the ID of the resource map of each source and derivation
- var idQuery = MetacatUI.appSearchModel.getGroupedQuery("id", externalProvEntities, "OR");
-
- //Create a query where we retrieve the metadata for each source and derivation
- var metadataQuery = MetacatUI.appSearchModel.getGroupedQuery("documents", externalProvEntities, "OR");
- }
-
- //TODO: Find the products of programs/executions
-
- //Make a comma-separated list of the provenance field names
- var provFieldList = "";
- _.each(MetacatUI.appSearchModel.getProvFields(), function(fieldName, i, list){
- provFieldList += fieldName;
- if(i < list.length-1) provFieldList += ",";
- });
-
- //Combine the two queries with an OR operator
- if(idQuery.length && metadataQuery.length) var combinedQuery = idQuery + "%20OR%20" + metadataQuery;
- else return this;
-
- //the full and final query in Solr syntax
- var query = "q=" + combinedQuery +
- "&fl=id,resourceMap,documents,isDocumentedBy,formatType,formatId,dateUploaded,rightsHolder,datasource,prov_instanceOfClass," +
- provFieldList +
- "&rows=100&wt=json";
-
- //Send the query to the query service
- var requestSettings = {
- url: MetacatUI.appModel.get("queryServiceUrl") + query,
- success: function(data, textStatus, xhr){
-
- //Do any of our docs have multiple resource maps?
- var hasMultipleMaps = _.filter(data.response.docs, function(doc){
- return((typeof doc.resourceMap !== "undefined") && (doc.resourceMap.length > 1))
- });
- //If so, we want to find the latest version of each resource map and only represent that one in the Prov Chart
- if(typeof hasMultipleMaps !== "undefined"){
- var allMapIDs = _.uniq(_.flatten(_.pluck(hasMultipleMaps, "resourceMap")));
- if(allMapIDs.length){
-
- var query = "q=+-obsoletedBy:*+" + MetacatUI.appSearchModel.getGroupedQuery("id", allMapIDs, "OR") +
- "&fl=obsoletes,id" +
- "&wt=json";
- var requestSettings = {
- url: MetacatUI.appModel.get("queryServiceUrl") + query,
- success: function(mapData, textStatus, xhr){
-
- //Create a list of resource maps that are not obsoleted by any other resource map retrieved
- var resourceMaps = mapData.response.docs;
-
- model.obsoletedResourceMaps = _.pluck(resourceMaps, "obsoletes");
- model.latestResourceMaps = _.difference(resourceMaps, model.obsoletedResourceMaps);
-
- model.sortProvTrace(data.response.docs);
- }
- }
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- }
- else
- model.sortProvTrace(data.response.docs);
- }
- else
- model.sortProvTrace(data.response.docs);
-
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
-
- return this;
- },
-
- sortProvTrace: function(docs){
- var model = this;
-
- //Start an array to hold the packages in the prov trace
- var sourcePackages = new Array(),
- derPackages = new Array(),
- sourceDocs = new Array(),
- derDocs = new Array(),
- sourceIDs = this.get("sources"),
- derivationIDs = this.get("derivations");
-
- //Separate the results into derivations and sources and group by their resource map.
- _.each(docs, function(doc, i){
-
- var docModel = new SolrResult(doc),
- mapIds = docModel.get("resourceMap");
-
-
- if(((typeof mapIds === "undefined") || !mapIds) && (docModel.get("formatType") == "DATA") && ((typeof docModel.get("isDocumentedBy") === "undefined") || !docModel.get("isDocumentedBy"))){
- //If this object is not in a resource map and does not have metadata, it is a "naked" data doc, so save it by itself
- if(_.contains(sourceIDs, doc.id))
- sourceDocs.push(docModel);
- if(_.contains(derivationIDs, doc.id))
- derDocs.push(docModel);
- }
- else if(((typeof mapIds === "undefined") || !mapIds) && (docModel.get("formatType") == "DATA") && docModel.get("isDocumentedBy")){
- //If this data doc does not have a resource map but has a metadata doc that documents it, create a blank package model and save it
- var p = new PackageModel({
- members: new Array(docModel)
- });
- //Add this package model to the sources and/or derivations packages list
- if(_.contains(sourceIDs, docModel.get("id")))
- sourcePackages[docModel.get("id")] = p;
- if(_.contains(derivationIDs, docModel.get("id")))
- derPackages[docModel.get("id")] = p;
- }
- else if(mapIds.length){
- //If this doc has a resource map, create a package model and SolrResult model and store it
- var id = docModel.get("id");
-
- //Some of these objects may have multiple resource maps
- _.each(mapIds, function(mapId, i, list){
-
- if(!_.contains(model.obsoletedResourceMaps, mapId)){
- var documentsSource, documentsDerivation;
- if(docModel.get("formatType") == "METADATA"){
- if(_.intersection(docModel.get("documents"), sourceIDs).length) documentsSource = true;
- if(_.intersection(docModel.get("documents"), derivationIDs).length) documentsDerivation = true;
- }
-
- //Is this a source object or a metadata doc of a source object?
- if(_.contains(sourceIDs, id) || documentsSource){
- //Have we encountered this source package yet?
- if(!sourcePackages[mapId] && (mapId != model.get("id"))){
- //Now make a new package model for it
- var p = new PackageModel({
- id: mapId,
- members: new Array(docModel)
- });
- //Add to the array of source packages
- sourcePackages[mapId] = p;
- }
- //If so, add this member to its package model
- else if(mapId != model.get("id")){
- var memberList = sourcePackages[mapId].get("members");
- memberList.push(docModel);
- sourcePackages[mapId].set("members", memberList);
- }
- }
-
- //Is this a derivation object or a metadata doc of a derivation object?
- if(_.contains(derivationIDs, id) || documentsDerivation){
- //Have we encountered this derivation package yet?
- if(!derPackages[mapId] && (mapId != model.get("id"))){
- //Now make a new package model for it
- var p = new PackageModel({
- id: mapId,
- members: new Array(docModel)
- });
- //Add to the array of source packages
- derPackages[mapId] = p;
- }
- //If so, add this member to its package model
- else if(mapId != model.get("id")){
- var memberList = derPackages[mapId].get("members");
- memberList.push(docModel);
- derPackages[mapId].set("members", memberList);
- }
- }
- }
- });
- }
- });
-
- //Transform our associative array (Object) of packages into an array
- var newArrays = new Array();
- _.each(new Array(sourcePackages, derPackages, sourceDocs, derDocs), function(provObject){
- var newArray = new Array(), key;
- for(key in provObject){
- newArray.push(provObject[key]);
- }
- newArrays.push(newArray);
- });
-
- //We now have an array of source packages and an array of derivation packages.
- model.set("sourcePackages", newArrays[0]);
- model.set("derivationPackages", newArrays[1]);
- model.set("sourceDocs", newArrays[2]);
- model.set("derivationDocs", newArrays[3]);
-
- //Save this prov trace on a package-member/document/object level.
- model.setMemberProvTrace();
-
- //Flag that the provenance trace is complete
- model.set("provenanceFlag", "complete");
- },
-
- setMemberProvTrace: function(){
- var model = this,
- relatedModels = this.get("relatedModels"),
- relatedModelIDs = new Array();
-
- //Now for each doc, we want to find which member it is related to
- _.each(this.get("members"), function(member, i, members){
- if(member.type == "Package") return;
-
- //Get the sources and derivations of this member
- var memberSourceIDs = member.getSources();
- var memberDerIDs = member.getDerivations();
-
- //Look through each source package, derivation package, source doc, and derivation doc.
- _.each(model.get("sourcePackages"), function(pkg, i){
- _.each(pkg.get("members"), function(sourcePkgMember, i){
- //Is this package member a direct source of this package member?
- if(_.contains(memberSourceIDs, sourcePkgMember.get("id")))
- //Save this source package member as a source of this member
- member.set("provSources", _.union(member.get("provSources"), [sourcePkgMember]));
-
- //Save this in the list of related models
- if(!_.contains(relatedModelIDs, sourcePkgMember.get("id"))){
- relatedModels.push(sourcePkgMember);
- relatedModelIDs.push(sourcePkgMember.get("id"));
- }
- });
- });
- _.each(model.get("derivationPackages"), function(pkg, i){
- _.each(pkg.get("members"), function(derPkgMember, i){
- //Is this package member a direct source of this package member?
- if(_.contains(memberDerIDs, derPkgMember.get("id")))
- //Save this derivation package member as a derivation of this member
- member.set("provDerivations", _.union(member.get("provDerivations"), [derPkgMember]));
-
- //Save this in the list of related models
- if(!_.contains(relatedModelIDs, derPkgMember.get("id"))){
- relatedModels.push(derPkgMember);
- relatedModelIDs.push(derPkgMember.get("id"));
- }
- });
- });
- _.each(model.get("sourceDocs"), function(doc, i){
- //Is this package member a direct source of this package member?
- if(_.contains(memberSourceIDs, doc.get("id")))
- //Save this source package member as a source of this member
- member.set("provSources", _.union(member.get("provSources"), [doc]));
-
- //Save this in the list of related models
- if(!_.contains(relatedModelIDs, doc.get("id"))){
- relatedModels.push(doc);
- relatedModelIDs.push(doc.get("id"));
- }
- });
- _.each(model.get("derivationDocs"), function(doc, i){
- //Is this package member a direct derivation of this package member?
- if(_.contains(memberDerIDs, doc.get("id")))
- //Save this derivation package member as a derivation of this member
- member.set("provDerivations", _.union(member.get("provDerivations"), [doc]));
-
- //Save this in the list of related models
- if(!_.contains(relatedModelIDs, doc.get("id"))){
- relatedModels.push(doc);
- relatedModelIDs.push(doc.get("id"));
- }
- });
- _.each(members, function(otherMember, i){
- //Is this other package member a direct derivation of this package member?
- if(_.contains(memberDerIDs, otherMember.get("id")))
- //Save this other derivation package member as a derivation of this member
- member.set("provDerivations", _.union(member.get("provDerivations"), [otherMember]));
- //Is this other package member a direct source of this package member?
- if(_.contains(memberSourceIDs, otherMember.get("id")))
- //Save this other source package member as a source of this member
- member.set("provSources", _.union(member.get("provSources"), [otherMember]));
-
- //Is this other package member an indirect source or derivation?
- if((otherMember.get("type") == "program") && (_.contains(member.get("prov_generatedByProgram"), otherMember.get("id")))){
- var indirectSources = _.filter(members, function(m){
- return _.contains(otherMember.getInputs(), m.get("id"));
- });
- indirectSourcesIds = _.each(indirectSources, function(m){ return m.get("id") });
- member.set("prov_wasDerivedFrom", _.union(member.get("prov_wasDerivedFrom"), indirectSourcesIds));
- //otherMember.set("prov_hasDerivations", _.union(otherMember.get("prov_hasDerivations"), [member.get("id")]));
- member.set("provSources", _.union(member.get("provSources"), indirectSources));
- }
- if((otherMember.get("type") == "program") && (_.contains(member.get("prov_usedByProgram"), otherMember.get("id")))){
- var indirectDerivations = _.filter(members, function(m){
- return _.contains(otherMember.getOutputs(), m.get("id"));
- });
- indirectDerivationsIds = _.each(indirectDerivations, function(m){ return m.get("id") });
- member.set("prov_hasDerivations", _.union(member.get("prov_hasDerivations"), indirectDerivationsIds));
- //otherMember.set("prov_wasDerivedFrom", _.union(otherMember.get("prov_wasDerivedFrom"), [member.get("id")]));
- member.set("provDerivations", _.union(member.get("provDerivations"), indirectDerivationsIds));
- }
- });
-
- //Add this member to the list of related models
- if(!_.contains(relatedModelIDs, member.get("id"))){
- relatedModels.push(member);
- relatedModelIDs.push(member.get("id"));
- }
-
- //Clear out any duplicates
- member.set("provSources", _.uniq(member.get("provSources")));
- member.set("provDerivations", _.uniq(member.get("provDerivations")));
- });
-
- //Update the list of related models
- this.set("relatedModels", relatedModels);
-
- },
-
- downloadWithCredentials: function(){
- //Get info about this object
- var url = this.get("url"),
- model = this;
-
- //Create an XHR
- var xhr = new XMLHttpRequest();
- xhr.withCredentials = true;
-
- //When the XHR is ready, create a link with the raw data (Blob) and click the link to download
- xhr.onload = function(){
-
- //Get the file name from the Content-Disposition header
- var filename = xhr.getResponseHeader('Content-Disposition');
-
- //As a backup, use the system metadata file name or the id
- if(!filename){
- filename = model.get("filename") || model.get("id");
+ } else entityName = null;
+ }
+ },
+ };
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ /*
+ * Will query for the derivations of this package, and sort all entities in the prov trace
+ * into sources and derivations.
+ */
+ getProvTrace: function () {
+ var model = this;
+
+ //See if there are any prov fields in our index before continuing
+ if (!MetacatUI.appSearchModel.getProvFields()) return this;
+
+ //Start keeping track of the sources and derivations
+ var sources = new Array(),
+ derivations = new Array();
+
+ //Search for derivations of this package
+ var derivationsQuery =
+ MetacatUI.appSearchModel.getGroupedQuery(
+ "prov_wasDerivedFrom",
+ _.map(this.get("members"), function (m) {
+ return m.get("id");
+ }),
+ "OR",
+ ) + "%20-obsoletedBy:*";
+
+ var requestSettings = {
+ url:
+ MetacatUI.appModel.get("queryServiceUrl") +
+ "&q=" +
+ derivationsQuery +
+ "&wt=json&rows=1000" +
+ "&fl=id,resourceMap,documents,isDocumentedBy,prov_wasDerivedFrom",
+ success: function (data) {
+ _.each(data.response.docs, function (result) {
+ derivations.push(result.id);
+ });
+
+ //Make arrays of unique IDs of objects that are sources or derivations of this package.
+ _.each(model.get("members"), function (member, i) {
+ if (member.type == "Package") return;
+
+ if (member.hasProvTrace()) {
+ sources = _.union(sources, member.getSources());
+ derivations = _.union(derivations, member.getDerivations());
+ }
+ });
+
+ //Save the arrays of sources and derivations
+ model.set("sources", sources);
+ model.set("derivations", derivations);
+
+ //Now get metadata about all the entities in the prov trace not in this package
+ model.getExternalProvTrace();
+ },
+ };
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ getExternalProvTrace: function () {
+ var model = this;
+
+ //Compact our list of ids that are in the prov trace by combining the sources and derivations and removing ids of members of this package
+ var externalProvEntities = _.difference(
+ _.union(this.get("sources"), this.get("derivations")),
+ this.get("memberIds"),
+ );
+
+ //If there are no sources or derivations, then we do not need to find resource map ids for anything
+ if (!externalProvEntities.length) {
+ //Save this prov trace on a package-member/document/object level.
+ if (this.get("sources").length || this.get("derivations").length)
+ this.setMemberProvTrace();
+
+ //Flag that the provenance trace is complete
+ this.set("provenanceFlag", "complete");
+
+ return this;
+ } else {
+ //Create a query where we retrieve the ID of the resource map of each source and derivation
+ var idQuery = MetacatUI.appSearchModel.getGroupedQuery(
+ "id",
+ externalProvEntities,
+ "OR",
+ );
+
+ //Create a query where we retrieve the metadata for each source and derivation
+ var metadataQuery = MetacatUI.appSearchModel.getGroupedQuery(
+ "documents",
+ externalProvEntities,
+ "OR",
+ );
}
- //Add a ".zip" extension if it doesn't exist
- if( filename.indexOf(".zip") < 0 || (filename.indexOf(".zip") != (filename.length-4)) ){
- filename += ".zip";
+ //TODO: Find the products of programs/executions
+
+ //Make a comma-separated list of the provenance field names
+ var provFieldList = "";
+ _.each(
+ MetacatUI.appSearchModel.getProvFields(),
+ function (fieldName, i, list) {
+ provFieldList += fieldName;
+ if (i < list.length - 1) provFieldList += ",";
+ },
+ );
+
+ //Combine the two queries with an OR operator
+ if (idQuery.length && metadataQuery.length)
+ var combinedQuery = idQuery + "%20OR%20" + metadataQuery;
+ else return this;
+
+ //the full and final query in Solr syntax
+ var query =
+ "q=" +
+ combinedQuery +
+ "&fl=id,resourceMap,documents,isDocumentedBy,formatType,formatId,dateUploaded,rightsHolder,datasource,prov_instanceOfClass," +
+ provFieldList +
+ "&rows=100&wt=json";
+
+ //Send the query to the query service
+ var requestSettings = {
+ url: MetacatUI.appModel.get("queryServiceUrl") + query,
+ success: function (data, textStatus, xhr) {
+ //Do any of our docs have multiple resource maps?
+ var hasMultipleMaps = _.filter(data.response.docs, function (doc) {
+ return (
+ typeof doc.resourceMap !== "undefined" &&
+ doc.resourceMap.length > 1
+ );
+ });
+ //If so, we want to find the latest version of each resource map and only represent that one in the Prov Chart
+ if (typeof hasMultipleMaps !== "undefined") {
+ var allMapIDs = _.uniq(
+ _.flatten(_.pluck(hasMultipleMaps, "resourceMap")),
+ );
+ if (allMapIDs.length) {
+ var query =
+ "q=+-obsoletedBy:*+" +
+ MetacatUI.appSearchModel.getGroupedQuery(
+ "id",
+ allMapIDs,
+ "OR",
+ ) +
+ "&fl=obsoletes,id" +
+ "&wt=json";
+ var requestSettings = {
+ url: MetacatUI.appModel.get("queryServiceUrl") + query,
+ success: function (mapData, textStatus, xhr) {
+ //Create a list of resource maps that are not obsoleted by any other resource map retrieved
+ var resourceMaps = mapData.response.docs;
+
+ model.obsoletedResourceMaps = _.pluck(
+ resourceMaps,
+ "obsoletes",
+ );
+ model.latestResourceMaps = _.difference(
+ resourceMaps,
+ model.obsoletedResourceMaps,
+ );
+
+ model.sortProvTrace(data.response.docs);
+ },
+ };
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ } else model.sortProvTrace(data.response.docs);
+ } else model.sortProvTrace(data.response.docs);
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+
+ return this;
+ },
+
+ sortProvTrace: function (docs) {
+ var model = this;
+
+ //Start an array to hold the packages in the prov trace
+ var sourcePackages = new Array(),
+ derPackages = new Array(),
+ sourceDocs = new Array(),
+ derDocs = new Array(),
+ sourceIDs = this.get("sources"),
+ derivationIDs = this.get("derivations");
+
+ //Separate the results into derivations and sources and group by their resource map.
+ _.each(docs, function (doc, i) {
+ var docModel = new SolrResult(doc),
+ mapIds = docModel.get("resourceMap");
+
+ if (
+ (typeof mapIds === "undefined" || !mapIds) &&
+ docModel.get("formatType") == "DATA" &&
+ (typeof docModel.get("isDocumentedBy") === "undefined" ||
+ !docModel.get("isDocumentedBy"))
+ ) {
+ //If this object is not in a resource map and does not have metadata, it is a "naked" data doc, so save it by itself
+ if (_.contains(sourceIDs, doc.id)) sourceDocs.push(docModel);
+ if (_.contains(derivationIDs, doc.id)) derDocs.push(docModel);
+ } else if (
+ (typeof mapIds === "undefined" || !mapIds) &&
+ docModel.get("formatType") == "DATA" &&
+ docModel.get("isDocumentedBy")
+ ) {
+ //If this data doc does not have a resource map but has a metadata doc that documents it, create a blank package model and save it
+ var p = new PackageModel({
+ members: new Array(docModel),
+ });
+ //Add this package model to the sources and/or derivations packages list
+ if (_.contains(sourceIDs, docModel.get("id")))
+ sourcePackages[docModel.get("id")] = p;
+ if (_.contains(derivationIDs, docModel.get("id")))
+ derPackages[docModel.get("id")] = p;
+ } else if (mapIds.length) {
+ //If this doc has a resource map, create a package model and SolrResult model and store it
+ var id = docModel.get("id");
+
+ //Some of these objects may have multiple resource maps
+ _.each(mapIds, function (mapId, i, list) {
+ if (!_.contains(model.obsoletedResourceMaps, mapId)) {
+ var documentsSource, documentsDerivation;
+ if (docModel.get("formatType") == "METADATA") {
+ if (
+ _.intersection(docModel.get("documents"), sourceIDs).length
+ )
+ documentsSource = true;
+ if (
+ _.intersection(docModel.get("documents"), derivationIDs)
+ .length
+ )
+ documentsDerivation = true;
+ }
+
+ //Is this a source object or a metadata doc of a source object?
+ if (_.contains(sourceIDs, id) || documentsSource) {
+ //Have we encountered this source package yet?
+ if (!sourcePackages[mapId] && mapId != model.get("id")) {
+ //Now make a new package model for it
+ var p = new PackageModel({
+ id: mapId,
+ members: new Array(docModel),
+ });
+ //Add to the array of source packages
+ sourcePackages[mapId] = p;
+ }
+ //If so, add this member to its package model
+ else if (mapId != model.get("id")) {
+ var memberList = sourcePackages[mapId].get("members");
+ memberList.push(docModel);
+ sourcePackages[mapId].set("members", memberList);
+ }
+ }
+
+ //Is this a derivation object or a metadata doc of a derivation object?
+ if (_.contains(derivationIDs, id) || documentsDerivation) {
+ //Have we encountered this derivation package yet?
+ if (!derPackages[mapId] && mapId != model.get("id")) {
+ //Now make a new package model for it
+ var p = new PackageModel({
+ id: mapId,
+ members: new Array(docModel),
+ });
+ //Add to the array of source packages
+ derPackages[mapId] = p;
+ }
+ //If so, add this member to its package model
+ else if (mapId != model.get("id")) {
+ var memberList = derPackages[mapId].get("members");
+ memberList.push(docModel);
+ derPackages[mapId].set("members", memberList);
+ }
+ }
+ }
+ });
+ }
+ });
+
+ //Transform our associative array (Object) of packages into an array
+ var newArrays = new Array();
+ _.each(
+ new Array(sourcePackages, derPackages, sourceDocs, derDocs),
+ function (provObject) {
+ var newArray = new Array(),
+ key;
+ for (key in provObject) {
+ newArray.push(provObject[key]);
+ }
+ newArrays.push(newArray);
+ },
+ );
+
+ //We now have an array of source packages and an array of derivation packages.
+ model.set("sourcePackages", newArrays[0]);
+ model.set("derivationPackages", newArrays[1]);
+ model.set("sourceDocs", newArrays[2]);
+ model.set("derivationDocs", newArrays[3]);
+
+ //Save this prov trace on a package-member/document/object level.
+ model.setMemberProvTrace();
+
+ //Flag that the provenance trace is complete
+ model.set("provenanceFlag", "complete");
+ },
+
+ setMemberProvTrace: function () {
+ var model = this,
+ relatedModels = this.get("relatedModels"),
+ relatedModelIDs = new Array();
+
+ //Now for each doc, we want to find which member it is related to
+ _.each(this.get("members"), function (member, i, members) {
+ if (member.type == "Package") return;
+
+ //Get the sources and derivations of this member
+ var memberSourceIDs = member.getSources();
+ var memberDerIDs = member.getDerivations();
+
+ //Look through each source package, derivation package, source doc, and derivation doc.
+ _.each(model.get("sourcePackages"), function (pkg, i) {
+ _.each(pkg.get("members"), function (sourcePkgMember, i) {
+ //Is this package member a direct source of this package member?
+ if (_.contains(memberSourceIDs, sourcePkgMember.get("id")))
+ //Save this source package member as a source of this member
+ member.set(
+ "provSources",
+ _.union(member.get("provSources"), [sourcePkgMember]),
+ );
+
+ //Save this in the list of related models
+ if (!_.contains(relatedModelIDs, sourcePkgMember.get("id"))) {
+ relatedModels.push(sourcePkgMember);
+ relatedModelIDs.push(sourcePkgMember.get("id"));
+ }
+ });
+ });
+ _.each(model.get("derivationPackages"), function (pkg, i) {
+ _.each(pkg.get("members"), function (derPkgMember, i) {
+ //Is this package member a direct source of this package member?
+ if (_.contains(memberDerIDs, derPkgMember.get("id")))
+ //Save this derivation package member as a derivation of this member
+ member.set(
+ "provDerivations",
+ _.union(member.get("provDerivations"), [derPkgMember]),
+ );
+
+ //Save this in the list of related models
+ if (!_.contains(relatedModelIDs, derPkgMember.get("id"))) {
+ relatedModels.push(derPkgMember);
+ relatedModelIDs.push(derPkgMember.get("id"));
+ }
+ });
+ });
+ _.each(model.get("sourceDocs"), function (doc, i) {
+ //Is this package member a direct source of this package member?
+ if (_.contains(memberSourceIDs, doc.get("id")))
+ //Save this source package member as a source of this member
+ member.set(
+ "provSources",
+ _.union(member.get("provSources"), [doc]),
+ );
+
+ //Save this in the list of related models
+ if (!_.contains(relatedModelIDs, doc.get("id"))) {
+ relatedModels.push(doc);
+ relatedModelIDs.push(doc.get("id"));
+ }
+ });
+ _.each(model.get("derivationDocs"), function (doc, i) {
+ //Is this package member a direct derivation of this package member?
+ if (_.contains(memberDerIDs, doc.get("id")))
+ //Save this derivation package member as a derivation of this member
+ member.set(
+ "provDerivations",
+ _.union(member.get("provDerivations"), [doc]),
+ );
+
+ //Save this in the list of related models
+ if (!_.contains(relatedModelIDs, doc.get("id"))) {
+ relatedModels.push(doc);
+ relatedModelIDs.push(doc.get("id"));
+ }
+ });
+ _.each(members, function (otherMember, i) {
+ //Is this other package member a direct derivation of this package member?
+ if (_.contains(memberDerIDs, otherMember.get("id")))
+ //Save this other derivation package member as a derivation of this member
+ member.set(
+ "provDerivations",
+ _.union(member.get("provDerivations"), [otherMember]),
+ );
+ //Is this other package member a direct source of this package member?
+ if (_.contains(memberSourceIDs, otherMember.get("id")))
+ //Save this other source package member as a source of this member
+ member.set(
+ "provSources",
+ _.union(member.get("provSources"), [otherMember]),
+ );
+
+ //Is this other package member an indirect source or derivation?
+ if (
+ otherMember.get("type") == "program" &&
+ _.contains(
+ member.get("prov_generatedByProgram"),
+ otherMember.get("id"),
+ )
+ ) {
+ var indirectSources = _.filter(members, function (m) {
+ return _.contains(otherMember.getInputs(), m.get("id"));
+ });
+ indirectSourcesIds = _.each(indirectSources, function (m) {
+ return m.get("id");
+ });
+ member.set(
+ "prov_wasDerivedFrom",
+ _.union(member.get("prov_wasDerivedFrom"), indirectSourcesIds),
+ );
+ //otherMember.set("prov_hasDerivations", _.union(otherMember.get("prov_hasDerivations"), [member.get("id")]));
+ member.set(
+ "provSources",
+ _.union(member.get("provSources"), indirectSources),
+ );
+ }
+ if (
+ otherMember.get("type") == "program" &&
+ _.contains(
+ member.get("prov_usedByProgram"),
+ otherMember.get("id"),
+ )
+ ) {
+ var indirectDerivations = _.filter(members, function (m) {
+ return _.contains(otherMember.getOutputs(), m.get("id"));
+ });
+ indirectDerivationsIds = _.each(
+ indirectDerivations,
+ function (m) {
+ return m.get("id");
+ },
+ );
+ member.set(
+ "prov_hasDerivations",
+ _.union(
+ member.get("prov_hasDerivations"),
+ indirectDerivationsIds,
+ ),
+ );
+ //otherMember.set("prov_wasDerivedFrom", _.union(otherMember.get("prov_wasDerivedFrom"), [member.get("id")]));
+ member.set(
+ "provDerivations",
+ _.union(member.get("provDerivations"), indirectDerivationsIds),
+ );
+ }
+ });
+
+ //Add this member to the list of related models
+ if (!_.contains(relatedModelIDs, member.get("id"))) {
+ relatedModels.push(member);
+ relatedModelIDs.push(member.get("id"));
+ }
+
+ //Clear out any duplicates
+ member.set("provSources", _.uniq(member.get("provSources")));
+ member.set("provDerivations", _.uniq(member.get("provDerivations")));
+ });
+
+ //Update the list of related models
+ this.set("relatedModels", relatedModels);
+ },
+
+ downloadWithCredentials: function () {
+ //Get info about this object
+ var url = this.get("url"),
+ model = this;
+
+ //Create an XHR
+ var xhr = new XMLHttpRequest();
+ xhr.withCredentials = true;
+
+ //When the XHR is ready, create a link with the raw data (Blob) and click the link to download
+ xhr.onload = function () {
+ //Get the file name from the Content-Disposition header
+ var filename = xhr.getResponseHeader("Content-Disposition");
+
+ //As a backup, use the system metadata file name or the id
+ if (!filename) {
+ filename = model.get("filename") || model.get("id");
+ }
+
+ //Add a ".zip" extension if it doesn't exist
+ if (
+ filename.indexOf(".zip") < 0 ||
+ filename.indexOf(".zip") != filename.length - 4
+ ) {
+ filename += ".zip";
+ }
+
+ //For IE, we need to use the navigator API
+ if (navigator && navigator.msSaveOrOpenBlob) {
+ navigator.msSaveOrOpenBlob(xhr.response, filename);
+ } else {
+ var a = document.createElement("a");
+ a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob
+ a.download = filename; // Set the file name.
+ a.style.display = "none";
+ document.body.appendChild(a);
+ a.click();
+ a.remove();
+ }
+
+ model.trigger("downloadComplete");
+
+ // Track this event
+ MetacatUI.analytics?.trackEvent(
+ "download",
+ "Download Package",
+ model.get("id"),
+ );
+ };
+
+ xhr.onprogress = function (e) {
+ if (e.lengthComputable) {
+ var percent = (e.loaded / e.total) * 100;
+ model.set("downloadPercent", percent);
+ }
+ };
+
+ xhr.onerror = function (e) {
+ model.trigger("downloadError");
+
+ // Track this event
+ MetacatUI.analytics?.trackEvent(
+ "download",
+ "Download Package",
+ model.get("id"),
+ );
+ };
+ //Open and send the request with the user's auth token
+ xhr.open("GET", url);
+ xhr.responseType = "blob";
+ xhr.setRequestHeader(
+ "Authorization",
+ "Bearer " + MetacatUI.appUserModel.get("token"),
+ );
+ xhr.send();
+ },
+
+ /* Returns the SolrResult that represents the metadata doc */
+ getMetadata: function () {
+ var members = this.get("members");
+ for (var i = 0; i < members.length; i++) {
+ if (members[i].get("formatType") == "METADATA") return members[i];
}
- //For IE, we need to use the navigator API
- if (navigator && navigator.msSaveOrOpenBlob) {
- navigator.msSaveOrOpenBlob(xhr.response, filename);
- }
- else{
- var a = document.createElement('a');
- a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob
- a.download = filename; // Set the file name.
- a.style.display = 'none';
- document.body.appendChild(a);
- a.click();
- a.remove();
- }
-
- model.trigger("downloadComplete");
-
- // Track this event
- MetacatUI.analytics?.trackEvent("download", "Download Package", model.get("id"))
-
- };
-
- xhr.onprogress = function(e){
- if (e.lengthComputable){
- var percent = (e.loaded / e.total) * 100;
- model.set("downloadPercent", percent);
- }
- };
-
- xhr.onerror = function (e) {
- model.trigger("downloadError");
-
- // Track this event
- MetacatUI.analytics?.trackEvent(
- "download",
- "Download Package",
- model.get("id")
- );
-
- };
- //Open and send the request with the user's auth token
- xhr.open('GET', url);
- xhr.responseType = "blob";
- xhr.setRequestHeader("Authorization", "Bearer " + MetacatUI.appUserModel.get("token"));
- xhr.send();
- },
-
- /* Returns the SolrResult that represents the metadata doc */
- getMetadata: function(){
- var members = this.get("members");
- for(var i=0; i= 0) && (bytes < kibibyte)) {
- return bytes + ' B';
-
- } else if ((bytes >= kibibyte) && (bytes < mebibyte)) {
- return (bytes / kibibyte).toFixed(precision) + ' KiB';
-
- } else if ((bytes >= mebibyte) && (bytes < gibibyte)) {
- return (bytes / mebibyte).toFixed(precision) + ' MiB';
-
- } else if ((bytes >= gibibyte) && (bytes < tebibyte)) {
- return (bytes / gibibyte).toFixed(precision) + ' GiB';
-
- } else if (bytes >= tebibyte) {
- return (bytes / tebibyte).toFixed(precision) + ' TiB';
-
- } else {
- return bytes + ' B';
- }
- }
-
- });
- return PackageModel;
+ //Sums up the byte size of each member
+ getTotalSize: function () {
+ if (this.get("totalSize")) return this.get("totalSize");
+
+ if (this.get("members").length == 1) {
+ var totalSize = this.get("members")[0].get("size");
+ } else {
+ var totalSize = _.reduce(this.get("members"), function (sum, member) {
+ if (typeof sum == "object") sum = sum.get("size");
+
+ return sum + member.get("size");
+ });
+ }
+
+ this.set("totalSize", totalSize);
+ return totalSize;
+ },
+
+ /****************************/
+ /**
+ * Convert number of bytes into human readable format
+ *
+ * @param integer bytes Number of bytes to convert
+ * @param integer precision Number of digits after the decimal separator
+ * @return string
+ */
+ bytesToSize: function (bytes, precision) {
+ var kibibyte = 1024;
+ var mebibyte = kibibyte * 1024;
+ var gibibyte = mebibyte * 1024;
+ var tebibyte = gibibyte * 1024;
+
+ if (typeof bytes === "undefined") var bytes = this.get("size");
+
+ if (bytes >= 0 && bytes < kibibyte) {
+ return bytes + " B";
+ } else if (bytes >= kibibyte && bytes < mebibyte) {
+ return (bytes / kibibyte).toFixed(precision) + " KiB";
+ } else if (bytes >= mebibyte && bytes < gibibyte) {
+ return (bytes / mebibyte).toFixed(precision) + " MiB";
+ } else if (bytes >= gibibyte && bytes < tebibyte) {
+ return (bytes / gibibyte).toFixed(precision) + " GiB";
+ } else if (bytes >= tebibyte) {
+ return (bytes / tebibyte).toFixed(precision) + " TiB";
+ } else {
+ return bytes + " B";
+ }
+ },
+ },
+ );
+ return PackageModel;
});
diff --git a/src/js/models/QualityCheckModel.js b/src/js/models/QualityCheckModel.js
index 5a58988d1..fa586bbc9 100644
--- a/src/js/models/QualityCheckModel.js
+++ b/src/js/models/QualityCheckModel.js
@@ -1,40 +1,38 @@
/* global define */
"use strict";
-define(['jquery', 'underscore', 'backbone'], function ($, _, Backbone) {
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ /**
+ * @class QualityCheck
+ * @classdesc This model represents a single metadata quality check. Currently, This
+ * model is only used when an entire quality suite resuslt is fetched from
+ * the quality server (and all quality checks are populated), but in the
+ * future it may be used to request/fetch quality result for a single
+ * quality check (and not an entire suite).
+ * @classcategory Models
+ * @extends Backbone.Model
+ */
+ var QualityCheck = Backbone.Model.extend(
+ /** @lends QualityCheck.prototype */ {
+ /* The default object format fields */
+ defaults: function () {
+ return {
+ check: null,
+ output: null,
+ status: null,
+ timestamp: null,
+ };
+ },
- /**
- * @class QualityCheck
- * @classdesc This model represents a single metadata quality check. Currently, This
- * model is only used when an entire quality suite resuslt is fetched from
- * the quality server (and all quality checks are populated), but in the
- * future it may be used to request/fetch quality result for a single
- * quality check (and not an entire suite).
- * @classcategory Models
- * @extends Backbone.Model
- */
- var QualityCheck = Backbone.Model.extend(
- /** @lends QualityCheck.prototype */{
+ /* Constructs a new instance */
+ initialize: function (attrs, options) {},
- /* The default object format fields */
- defaults: function () {
- return {
- check: null,
- output: null,
- status: null,
- timestamp: null
- };
- },
+ /* No op - Formats are read only */
+ save: function () {
+ return false;
+ },
+ },
+ );
- /* Constructs a new instance */
- initialize: function (attrs, options) {
- },
-
- /* No op - Formats are read only */
- save: function () {
- return false;
- }
- });
-
- return QualityCheck;
+ return QualityCheck;
});
diff --git a/src/js/models/Search.js b/src/js/models/Search.js
index 86dcf18ff..829645d67 100644
--- a/src/js/models/Search.js
+++ b/src/js/models/Search.js
@@ -1,1216 +1,1425 @@
/*global define */
-define(["jquery", "underscore", "backbone", "models/SolrResult", "collections/Filters"],
- function($, _, Backbone, SolrResult, Filters) {
- 'use strict';
-
- /**
- * @class Search
- * @classdesc Search filters can be either plain text or a filter object with the following options:
- * filterLabel - text that will be displayed in the filter element in the UI
- * label - text that will be displayed in the autocomplete list
- * value - the value that will be included in the query
- * description - a longer text description of the filter value
- * Example: {filterLabel: "Creator", label: "Jared Kibele (16)", value: "Kibele", description: "Search for data creators"}
- * @classcategory Models
- * @extends Backbone.Model
- * @constructor
- */
- var Search = Backbone.Model.extend(
- /** @lends Search.prototype */{
-
- /**
- * @type {object}
- * @property {Filters} filters - The collection of filters used to build a query, an instance of Filters
- */
- defaults: function() {
- return {
- all: [],
- projectText: [],
- creator: [],
- taxon: [],
- isPrivate: null,
- documents: false,
- resourceMap: false,
- yearMin: 1900, //The user-selected minimum year
- yearMax: new Date().getUTCFullYear(), //The user-selected maximum year
- pubYear: false,
- dataYear: false,
- sortOrder: 'dateUploaded+desc',
- sortByReads: false, // True if we can sort by reads/popularity
- east: null,
- west: null,
- north: null,
- south: null,
- useGeohash: true,
- geohashes: [],
- geohashLevel: 9,
- geohashGroups: {},
- dataSource: [],
- username: [],
- rightsHolder: [],
- submitter: [],
- spatial: [],
- attribute: [],
- sem_annotation: [],
- annotation: [],
- additionalCriteria: [],
- id: [],
- seriesId: [],
- idOnly: [],
- provFields: [],
- formatType: [{
- value: "METADATA",
- label: "science metadata",
- description: null
- }],
- exclude: [{
- field: "obsoletedBy",
- value: "*"
- },
- {
- field: "formatId",
- value: "*dataone.org/collections*"
- },
- {
- field: "formatId",
- value: "*dataone.org/portals*"
- }],
- filters: null
- }
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/SolrResult",
+ "collections/Filters",
+], function ($, _, Backbone, SolrResult, Filters) {
+ "use strict";
+
+ /**
+ * @class Search
+ * @classdesc Search filters can be either plain text or a filter object with the following options:
+ * filterLabel - text that will be displayed in the filter element in the UI
+ * label - text that will be displayed in the autocomplete list
+ * value - the value that will be included in the query
+ * description - a longer text description of the filter value
+ * Example: {filterLabel: "Creator", label: "Jared Kibele (16)", value: "Kibele", description: "Search for data creators"}
+ * @classcategory Models
+ * @extends Backbone.Model
+ * @constructor
+ */
+ var Search = Backbone.Model.extend(
+ /** @lends Search.prototype */ {
+ /**
+ * @type {object}
+ * @property {Filters} filters - The collection of filters used to build a query, an instance of Filters
+ */
+ defaults: function () {
+ return {
+ all: [],
+ projectText: [],
+ creator: [],
+ taxon: [],
+ isPrivate: null,
+ documents: false,
+ resourceMap: false,
+ yearMin: 1900, //The user-selected minimum year
+ yearMax: new Date().getUTCFullYear(), //The user-selected maximum year
+ pubYear: false,
+ dataYear: false,
+ sortOrder: "dateUploaded+desc",
+ sortByReads: false, // True if we can sort by reads/popularity
+ east: null,
+ west: null,
+ north: null,
+ south: null,
+ useGeohash: true,
+ geohashes: [],
+ geohashLevel: 9,
+ geohashGroups: {},
+ dataSource: [],
+ username: [],
+ rightsHolder: [],
+ submitter: [],
+ spatial: [],
+ attribute: [],
+ sem_annotation: [],
+ annotation: [],
+ additionalCriteria: [],
+ id: [],
+ seriesId: [],
+ idOnly: [],
+ provFields: [],
+ formatType: [
+ {
+ value: "METADATA",
+ label: "science metadata",
+ description: null,
},
-
- //A list of all the filter names that are related to the spatial/map filter
- spatialFilters: ["useGeohash", "geohashes", "geohashLevel",
- "geohashGroups", "east", "west", "north", "south"],
-
- initialize: function() {
- this.listenTo(this, "change:geohashes", this.groupGeohashes);
+ ],
+ exclude: [
+ {
+ field: "obsoletedBy",
+ value: "*",
},
-
- fieldLabels: {
- attribute: "Data attribute",
- documents: "Only results with data",
- annotation: "Annotation",
- dataSource: "Data source",
- creator: "Creator",
- dataYear: "Data coverage",
- pubYear: "Publish year",
- id: "Identifier",
- seriesId: "seriesId",
- taxon: "Taxon",
- spatial: "Location",
- isPrivate: "Private datasets",
- all: "",
- projectText: "Project",
+ {
+ field: "formatId",
+ value: "*dataone.org/collections*",
},
-
- //Map the filter names to their index field names
- fieldNameMap: {
- attribute: "attribute",
- annotation: "sem_annotation",
- dataSource: "datasource",
- documents: "documents",
- formatType: "formatType",
- all: "",
- creator: "originText",
- spatial: "siteText",
- resourceMap: "resourceMap",
- pubYear: ["datePublished", "dateUploaded"],
- id: ["id", "identifier", "documents", "resourceMap", "seriesId"],
- idOnly: ["id", "seriesId"],
- rightsHolder: "rightsHolder",
- submitter: "submitter",
- username: ["rightsHolder", "writePermission", "changePermission"],
- taxon: ["kingdom", "phylum", "class", "order", "family", "genus", "species"],
- isPrivate: "isPublic",
- projectText: "projectText"
- },
-
- facetNameMap: {
- "creator": "origin",
- "attribute": "attribute",
- "annotation": "sem_annotation",
- "spatial": "site",
- "taxon": ["kingdom", "phylum", "class", "order", "family", "genus", "species"],
- "isPublic": "isPublic",
- "all": "keywords",
- "projectText": "project"
+ {
+ field: "formatId",
+ value: "*dataone.org/portals*",
},
+ ],
+ filters: null,
+ };
+ },
+
+ //A list of all the filter names that are related to the spatial/map filter
+ spatialFilters: [
+ "useGeohash",
+ "geohashes",
+ "geohashLevel",
+ "geohashGroups",
+ "east",
+ "west",
+ "north",
+ "south",
+ ],
+
+ initialize: function () {
+ this.listenTo(this, "change:geohashes", this.groupGeohashes);
+ },
+
+ fieldLabels: {
+ attribute: "Data attribute",
+ documents: "Only results with data",
+ annotation: "Annotation",
+ dataSource: "Data source",
+ creator: "Creator",
+ dataYear: "Data coverage",
+ pubYear: "Publish year",
+ id: "Identifier",
+ seriesId: "seriesId",
+ taxon: "Taxon",
+ spatial: "Location",
+ isPrivate: "Private datasets",
+ all: "",
+ projectText: "Project",
+ },
+
+ //Map the filter names to their index field names
+ fieldNameMap: {
+ attribute: "attribute",
+ annotation: "sem_annotation",
+ dataSource: "datasource",
+ documents: "documents",
+ formatType: "formatType",
+ all: "",
+ creator: "originText",
+ spatial: "siteText",
+ resourceMap: "resourceMap",
+ pubYear: ["datePublished", "dateUploaded"],
+ id: ["id", "identifier", "documents", "resourceMap", "seriesId"],
+ idOnly: ["id", "seriesId"],
+ rightsHolder: "rightsHolder",
+ submitter: "submitter",
+ username: ["rightsHolder", "writePermission", "changePermission"],
+ taxon: [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ ],
+ isPrivate: "isPublic",
+ projectText: "projectText",
+ },
+
+ facetNameMap: {
+ creator: "origin",
+ attribute: "attribute",
+ annotation: "sem_annotation",
+ spatial: "site",
+ taxon: [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ ],
+ isPublic: "isPublic",
+ all: "keywords",
+ projectText: "project",
+ },
+
+ getCurrentFilters: function () {
+ var changedAttr = this.changedAttributes(_.clone(this.defaults())),
+ currentFilters = Object.keys(changedAttr),
+ ignoreAttr = ["sortOrder", "provFields"];
+
+ if (!changedAttr) return new Array();
+
+ //Check for changed attributes that should be ignored
+ _.each(
+ Object.keys(changedAttr),
+ function (attr) {
+ //If the value is an empty array, but the default value is an empty array too,
+ // then it's not a changed filter attribute
+ if (
+ Array.isArray(this.get(attr)) &&
+ this.get(attr).length == 0 &&
+ Array.isArray(this.defaults()[attr]) &&
+ this.defaults()[attr].length == 0
+ ) {
+ currentFilters = _.without(currentFilters, attr);
+ } else if (ignoreAttr.includes(attr)) {
+ currentFilters = _.without(currentFilters, attr);
+ }
+ },
+ this,
+ );
+
+ //Don't count the geohashes or directions as a filter if the geohash filter is turned off
+ if (!this.get("useGeohash")) {
+ currentFilters = _.difference(currentFilters, this.spatialFilters);
+ }
+
+ return currentFilters;
+ },
+
+ filterCount: function () {
+ var currentFilters = this.getCurrentFilters();
+
+ return currentFilters.length;
+ },
+
+ //Function filterIsAvailable will check if a filter is available in this search index -
+ //if the filter name if included in the defaults of this model, it is marked as available.
+ //Comment out or remove defaults that are not in the index or should not be included in queries
+ filterIsAvailable: function (name) {
+ //Get the keys for this model as a way to list the filters that are available
+ var defaults = _.keys(this.defaults());
+ if (_.indexOf(defaults, name) >= 0) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /*
+ * Removes a specified filter from the search model
+ */
+ removeFromModel: function (category, filterValueToRemove) {
+ //Remove this filter term from the model
+ if (category) {
+ //Get the current filter terms array
+ var currentFilterValues = this.get(category);
+
+ //The year filters have special rules
+ //If both year types will be reset/default, then also reset the year min and max values
+ if (category == "pubYear" || category == "dataYear") {
+ var otherType = category == "pubYear" ? "dataYear" : "pubYear";
+
+ if (_.contains(this.getCurrentFilters(), otherType))
+ var newFilterValues = this.defaults()[category];
+ else {
+ this.set(category, this.defaults()[category]);
+ this.set("yearMin", this.defaults()["yearMin"]);
+ this.set("yearMax", this.defaults()["yearMax"]);
+ return;
+ }
+ } else if (Array.isArray(currentFilterValues)) {
+ //Remove this filter term from the array
+ var newFilterValues = _.without(
+ currentFilterValues,
+ filterValueToRemove,
+ );
+ _.each(currentFilterValues, function (currentFilterValue, key) {
+ var valueString =
+ typeof currentFilterValue == "object"
+ ? currentFilterValue.value
+ : currentFilterValue;
+ if (valueString == filterValueToRemove) {
+ newFilterValues = _.without(
+ newFilterValues,
+ currentFilterValue,
+ );
+ }
+ });
+ } else {
+ //Get the default value
+ var newFilterValues = this.defaults()[category];
+ }
+
+ //Set the new value
+ this.set(category, newFilterValues);
+ }
+ },
+
+ /**
+ *
+ * @param {Filters|Filter[]} filters The collection of filters to add to this model OR an array of Filter models
+ */
+ addFilters: function (filters) {
+ try {
+ let currentFilters = this.get("filters");
+
+ //If the passed collection is the same as the one set already, return
+ if (currentFilters == filters) return;
+ //If the given Filters collec is different than the one set on the model now, combine them
+ else if (
+ Filters.isPrototypeOf(currentFilters) &&
+ Filters.isPrototypeOf(filters)
+ ) {
+ filters.models.forEach((f) => {
+ currentFilters.add(f);
+ });
+ this.set("filters", currentFilters);
+ } else if (
+ Filters.isPrototypeOf(currentFilters) &&
+ Array.isArray(filters)
+ ) {
+ filters.forEach((f) => {
+ currentFilters.add(f);
+ });
+ this.set("filters", currentFilters);
+ } else if (!currentFilters) this.set("filters", new Filters(filters));
+ } catch (e) {
+ console.error("Couldn't add Filters to the Search model: ", e);
+ }
+ },
+
+ /*
+ * Resets the geoashes and geohashLevel filters to default
+ */
+ resetGeohash: function () {
+ this.set("geohashes", this.defaults().geohashes);
+ this.set("geohashLevel", this.defaults().geohashLevel);
+ this.set("geohashGroups", this.defaults().geohashGroups);
+ },
+
+ groupGeohashes: function () {
+ //Find out if there are any geohashes that can be combined together, by looking for all 32 geohashes within the same precision level
+ var sortedGeohashes = this.get("geohashes");
+ sortedGeohashes.sort();
+
+ var groupedGeohashes = _.groupBy(sortedGeohashes, function (n) {
+ return n.substring(0, n.length - 1);
+ });
- getCurrentFilters: function() {
- var changedAttr = this.changedAttributes(_.clone(this.defaults())),
- currentFilters = Object.keys(changedAttr),
- ignoreAttr = ["sortOrder", "provFields"];
-
- if (!changedAttr) return new Array();
-
- //Check for changed attributes that should be ignored
- _.each( Object.keys(changedAttr), function(attr){
-
- //If the value is an empty array, but the default value is an empty array too,
- // then it's not a changed filter attribute
- if( Array.isArray(this.get(attr)) && this.get(attr).length == 0 &&
- Array.isArray(this.defaults()[attr]) && this.defaults()[attr].length == 0 ){
- currentFilters = _.without(currentFilters, attr);
- }
- else if( ignoreAttr.includes(attr) ){
- currentFilters = _.without(currentFilters, attr);
- }
- }, this);
-
- //Don't count the geohashes or directions as a filter if the geohash filter is turned off
- if (!this.get("useGeohash")) {
- currentFilters = _.difference(currentFilters, this.spatialFilters);
- }
-
- return currentFilters;
- },
-
- filterCount: function() {
- var currentFilters = this.getCurrentFilters();
-
- return currentFilters.length;
- },
-
- //Function filterIsAvailable will check if a filter is available in this search index -
- //if the filter name if included in the defaults of this model, it is marked as available.
- //Comment out or remove defaults that are not in the index or should not be included in queries
- filterIsAvailable: function(name) {
- //Get the keys for this model as a way to list the filters that are available
- var defaults = _.keys(this.defaults());
- if (_.indexOf(defaults, name) >= 0) {
- return true;
- } else {
- return false;
- }
- },
-
- /*
- * Removes a specified filter from the search model
- */
- removeFromModel: function(category, filterValueToRemove) {
- //Remove this filter term from the model
- if (category) {
- //Get the current filter terms array
- var currentFilterValues = this.get(category);
-
- //The year filters have special rules
- //If both year types will be reset/default, then also reset the year min and max values
- if ((category == "pubYear") || (category == "dataYear")) {
- var otherType = (category == "pubYear") ? "dataYear" : "pubYear";
-
- if (_.contains(this.getCurrentFilters(), otherType))
- var newFilterValues = this.defaults()[category];
- else {
- this.set(category, this.defaults()[category]);
- this.set("yearMin", this.defaults()["yearMin"]);
- this.set("yearMax", this.defaults()["yearMax"]);
- return;
- }
-
- } else if (Array.isArray(currentFilterValues)) {
- //Remove this filter term from the array
- var newFilterValues = _.without(currentFilterValues, filterValueToRemove);
- _.each(currentFilterValues, function(currentFilterValue, key) {
- var valueString = (typeof currentFilterValue == "object") ? currentFilterValue.value : currentFilterValue;
- if (valueString == filterValueToRemove) {
- newFilterValues = _.without(newFilterValues, currentFilterValue);
- }
- });
- } else {
- //Get the default value
- var newFilterValues = this.defaults()[category];
- }
-
- //Set the new value
- this.set(category, newFilterValues);
-
- }
- },
+ //Find groups of geohashes that makeup a complete geohash tile (32) so we can shorten the query
+ var completeGroups = _.filter(
+ Object.keys(groupedGeohashes),
+ function (n) {
+ return groupedGeohashes[n].length == 32;
+ },
+ );
+ //Find the remaining incomplete geohash groupss
+ var incompleteGroups = [];
+ _.each(
+ _.filter(Object.keys(groupedGeohashes), function (n) {
+ return groupedGeohashes[n].length < 32;
+ }),
+ function (n) {
+ incompleteGroups.push(groupedGeohashes[n]);
+ },
+ );
+ incompleteGroups = _.flatten(incompleteGroups);
+
+ //Start a geohash group object
+ var geohashGroups = {};
+ if (
+ typeof incompleteGroups !== "undefined" &&
+ incompleteGroups.length > 0
+ ) {
+ geohashGroups[incompleteGroups[0].length.toString()] =
+ incompleteGroups;
+ }
+ if (
+ typeof completeGroups !== "undefined" &&
+ completeGroups.length > 0
+ ) {
+ geohashGroups[completeGroups[0].length.toString()] = completeGroups;
+ }
+ //Save it
+ this.set("geohashGroups", geohashGroups);
+ this.trigger("change", "geohashGroups");
+ },
+
+ hasGeohashFilter: function () {
+ var currentGeohashFilter = this.get("geohashGroups");
+ return (
+ typeof currentGeohashFilter == "object" &&
+ Object.keys(currentGeohashFilter).length > 0
+ );
+ },
+
+ /**
+ * Builds the query string to send to the query engine. Goes over each filter specified in this model and adds to the query string.
+ * Some filters have special rules on how to format the query, which are built first, then the remaining filters are tacked on to the
+ * query string as a basic name:value pair. These "other filters" are specified in the otherFilters variable.
+ * @param {string} filter - A single filter to get a query fragment for
+ * @param {object} options - Additional options for this function
+ * @property {boolean} options.forPOST - If true, the query will not be url-encoded, for POST requests
+ */
+ getQuery: function (filter, options) {
+ //----All other filters with a basic name:value pair pattern----
+ var otherFilters = [
+ "attribute",
+ "formatType",
+ "rightsHolder",
+ "submitter",
+ ];
+
+ //Start the query string
+ var query = "",
+ forPOST = false;
+
+ //See if we are looking for a sub-query or a query for all filters
+ if (typeof filter == "undefined") {
+ var filter = null;
+ var getAll = true;
+ } else {
+ var getAll = false;
+ }
+
+ //Get the options sent to this function via the options object
+ if (typeof options == "object" && options) {
+ forPOST = options.forPOST;
+ }
+
+ var model = this;
+
+ //-----Annotation-----
+ if (
+ this.filterIsAvailable("annotation") &&
+ (filter == "annotation" || getAll)
+ ) {
+ var annotations = this.get("annotation");
+ _.each(annotations, function (annotationFilter, key, list) {
+ var filterValue = "";
+
+ //Get the filter value
+ if (typeof annotationFilter == "object") {
+ filterValue = annotationFilter.value || "";
+ } else {
+ filterValue = annotationFilter;
+ }
- /**
- *
- * @param {Filters|Filter[]} filters The collection of filters to add to this model OR an array of Filter models
- */
- addFilters: function(filters){
- try{
-
- let currentFilters = this.get("filters");
-
- //If the passed collection is the same as the one set already, return
- if( currentFilters == filters )
- return;
- //If the given Filters collec is different than the one set on the model now, combine them
- else if( Filters.isPrototypeOf(currentFilters) && Filters.isPrototypeOf(filters) ){
- filters.models.forEach(f => { currentFilters.add(f) });
- this.set("filters", currentFilters);
- }
- else if( Filters.isPrototypeOf(currentFilters) && Array.isArray(filters) ){
- filters.forEach(f => { currentFilters.add(f) });
- this.set("filters", currentFilters);
- }
- else if( !currentFilters )
- this.set("filters", new Filters(filters));
-
- }
- catch(e){
- console.error("Couldn't add Filters to the Search model: ", e);
- }
- },
+ // Trim leading and trailing whitespace just in case
+ filterValue = filterValue.trim();
- /*
- * Resets the geoashes and geohashLevel filters to default
- */
- resetGeohash: function() {
- this.set("geohashes", this.defaults().geohashes);
- this.set("geohashLevel", this.defaults().geohashLevel);
- this.set("geohashGroups", this.defaults().geohashGroups);
- },
+ if (forPOST) {
+ // Encode and wrap URI in urlencoded double quote chars
+ filterValue = '"' + filterValue.trim() + '"';
+ } else {
+ // Encode and wrap URI in urlencoded double quote chars
+ filterValue =
+ "%22" + encodeURIComponent(filterValue.trim()) + "%22";
+ }
- groupGeohashes: function() {
- //Find out if there are any geohashes that can be combined together, by looking for all 32 geohashes within the same precision level
- var sortedGeohashes = this.get("geohashes");
- sortedGeohashes.sort();
+ query += model.fieldNameMap["annotation"] + ":" + filterValue;
+ });
+ }
+
+ //---Identifier---
+ if (
+ this.filterIsAvailable("id") &&
+ (filter == "id" || getAll) &&
+ this.get("id").length
+ ) {
+ var identifiers = this.get("id");
+
+ if (Array.isArray(identifiers)) {
+ if (query.length) {
+ query += " AND ";
+ }
- var groupedGeohashes = _.groupBy(sortedGeohashes, function(n) {
- return n.substring(0, n.length - 1);
- });
+ query += this.getGroupedQuery(
+ this.fieldNameMap["id"],
+ identifiers,
+ {
+ operator: "OR",
+ subtext: true,
+ },
+ );
+ } else if (identifiers) {
+ if (query.length) {
+ query += " AND ";
+ }
- //Find groups of geohashes that makeup a complete geohash tile (32) so we can shorten the query
- var completeGroups = _.filter(Object.keys(groupedGeohashes), function(n) {
- return (groupedGeohashes[n].length == 32)
- });
- //Find the remaining incomplete geohash groupss
- var incompleteGroups = [];
- _.each(_.filter(Object.keys(groupedGeohashes), function(n) {
- return (groupedGeohashes[n].length < 32)
- }), function(n) {
- incompleteGroups.push(groupedGeohashes[n]);
- });
- incompleteGroups = _.flatten(incompleteGroups);
+ if (forPOST) {
+ query +=
+ this.fieldNameMap["id"] +
+ ":*" +
+ this.escapeSpecialChar(identifiers) +
+ "*";
+ } else {
+ query +=
+ this.fieldNameMap["id"] +
+ ":*" +
+ this.escapeSpecialChar(encodeURIComponent(identifiers)) +
+ "*";
+ }
+ }
+ }
+
+ //---resourceMap---
+ if (
+ this.filterIsAvailable("resourceMap") &&
+ (filter == "resourceMap" || getAll)
+ ) {
+ var resourceMap = this.get("resourceMap");
+
+ //If the resource map search setting is a list of resource map IDs
+ if (Array.isArray(resourceMap)) {
+ if (query.length) {
+ query += " AND ";
+ }
- //Start a geohash group object
- var geohashGroups = {};
- if ((typeof incompleteGroups !== "undefined") && (incompleteGroups.length > 0)) {
- geohashGroups[incompleteGroups[0].length.toString()] = incompleteGroups;
- }
- if ((typeof completeGroups !== "undefined") && (completeGroups.length > 0)) {
- geohashGroups[completeGroups[0].length.toString()] = completeGroups;
- }
- //Save it
- this.set("geohashGroups", geohashGroups);
- this.trigger("change", "geohashGroups");
- },
+ query += this.getGroupedQuery(
+ this.fieldNameMap["resourceMap"],
+ resourceMap,
+ {
+ operator: "OR",
+ forPOST: forPOST,
+ },
+ );
+ } else if (resourceMap) {
+ if (query.length) {
+ query += " AND ";
+ }
+ //Otherwise, treat it as a binary setting
+ query += this.fieldNameMap["resourceMap"] + ":*";
+ }
+ }
+
+ //---documents---
+ if (
+ this.filterIsAvailable("documents") &&
+ (filter == "documents" || getAll)
+ ) {
+ var documents = this.get("documents");
+
+ //If the documents search setting is a list ofdocuments IDs
+ if (Array.isArray(documents)) {
+ if (query.length) {
+ query += " AND ";
+ }
- hasGeohashFilter: function(){
- var currentGeohashFilter = this.get("geohashGroups");
- return (typeof currentGeohashFilter == "object" && Object.keys(currentGeohashFilter).length > 0);
- },
+ query += this.getGroupedQuery(
+ this.fieldNameMap["documents"],
+ documents,
+ {
+ operator: "OR",
+ forPOST: forPOST,
+ },
+ );
+ } else if (documents) {
+ if (query.length) {
+ query += " AND ";
+ }
+ //Otherwise, treat it as a binary setting
+ query += this.fieldNameMap["documents"] + ":*";
+ }
+ }
+
+ //---Username: search for this username in rightsHolder and submitter ---
+ if (
+ this.filterIsAvailable("username") &&
+ (filter == "username" || getAll) &&
+ this.get("username").length
+ ) {
+ var username = this.get("username");
+ if (username) {
+ if (query.length) {
+ query += " AND ";
+ }
- /**
- * Builds the query string to send to the query engine. Goes over each filter specified in this model and adds to the query string.
- * Some filters have special rules on how to format the query, which are built first, then the remaining filters are tacked on to the
- * query string as a basic name:value pair. These "other filters" are specified in the otherFilters variable.
- * @param {string} filter - A single filter to get a query fragment for
- * @param {object} options - Additional options for this function
- * @property {boolean} options.forPOST - If true, the query will not be url-encoded, for POST requests
- */
- getQuery: function(filter, options) {
-
- //----All other filters with a basic name:value pair pattern----
- var otherFilters = ["attribute", "formatType", "rightsHolder", "submitter"];
-
- //Start the query string
- var query = "",
- forPOST = false;
-
- //See if we are looking for a sub-query or a query for all filters
- if (typeof filter == "undefined") {
- var filter = null;
- var getAll = true;
- } else {
- var getAll = false;
- }
+ query += this.getGroupedQuery(
+ this.fieldNameMap["username"],
+ username,
+ {
+ operator: "OR",
+ forPOST: forPOST,
+ },
+ );
+ }
+ }
+
+ //--- ID Only - searches only the id and seriesId fields ---
+ if (
+ this.filterIsAvailable("idOnly") &&
+ (filter == "idOnly" || getAll) &&
+ this.get("idOnly").length
+ ) {
+ var idOnly = this.get("idOnly");
+ if (idOnly) {
+ if (query.length) {
+ query += " AND ";
+ }
- //Get the options sent to this function via the options object
- if( typeof options == "object" && options ){
- forPOST = options.forPOST;
- }
+ query += this.getGroupedQuery(this.fieldNameMap["idOnly"], idOnly, {
+ operator: "OR",
+ forPOST: forPOST,
+ });
+ }
+ }
+
+ //---Taxon---
+ if (
+ this.filterIsAvailable("taxon") &&
+ (filter == "taxon" || getAll) &&
+ this.get("taxon").length
+ ) {
+ var taxon = this.get("taxon");
+
+ for (var i = 0; i < taxon.length; i++) {
+ var value =
+ typeof taxon == "object" ? taxon[i].value : taxon[i].trim();
+
+ query += this.getMultiFieldQuery(
+ this.fieldNameMap["taxon"],
+ value,
+ {
+ subtext: true,
+ forPOST: forPOST,
+ },
+ );
+ }
+ }
+
+ //------Pub Year-----
+ if (
+ this.filterIsAvailable("pubYear") &&
+ (filter == "pubYear" || getAll)
+ ) {
+ //Get the types of year to be searched first
+ var pubYear = this.get("pubYear");
+ if (pubYear) {
+ //Get the minimum and maximum years chosen
+ var yearMin = this.get("yearMin");
+ var yearMax = this.get("yearMax");
+
+ if (query.length) {
+ query += " AND ";
+ }
- var model = this;
-
- //-----Annotation-----
- if (this.filterIsAvailable("annotation") && ((filter == "annotation") || getAll)) {
- var annotations = this.get("annotation");
- _.each(annotations, function(annotationFilter, key, list) {
- var filterValue = "";
-
- //Get the filter value
- if (typeof annotationFilter == "object") {
- filterValue = annotationFilter.value || "";
- } else {
- filterValue = annotationFilter;
- }
-
- // Trim leading and trailing whitespace just in case
- filterValue = filterValue.trim();
-
- if( forPOST ){
- // Encode and wrap URI in urlencoded double quote chars
- filterValue = '"' + filterValue.trim() + '"';
- }
- else{
- // Encode and wrap URI in urlencoded double quote chars
- filterValue = "%22" + encodeURIComponent(filterValue.trim()) + "%22";
- }
-
- query += model.fieldNameMap["annotation"] + ":" + filterValue;
- });
- }
+ var value =
+ "[" +
+ yearMin +
+ "-01-01T00:00:00Z TO " +
+ yearMax +
+ "-12-31T00:00:00Z]";
+ var opts = {
+ forPOST: forPOST,
+ escapeSquareBrackets: false,
+ };
+
+ //Add to the query if we are searching publication year
+ query += this.getMultiFieldQuery(
+ this.fieldNameMap["pubYear"],
+ value,
+ opts,
+ );
+ }
+ }
+
+ //-----Data year------
+ if (
+ this.filterIsAvailable("dataYear") &&
+ (filter == "dataYear" || getAll)
+ ) {
+ var dataYear = this.get("dataYear");
+
+ if (dataYear) {
+ //Get the minimum and maximum years chosen
+ var yearMin = this.get("yearMin");
+ var yearMax = this.get("yearMax");
+
+ if (query.length) {
+ query += " AND ";
+ }
- //---Identifier---
- if (this.filterIsAvailable("id") && ((filter == "id") || getAll) && this.get('id').length) {
- var identifiers = this.get('id');
-
- if (Array.isArray(identifiers)) {
- if( query.length ){
- query += " AND ";
- }
-
- query += this.getGroupedQuery(this.fieldNameMap["id"], identifiers, {
- operator: "OR",
- subtext: true
- });
-
- } else if (identifiers) {
- if( query.length ){
- query += " AND ";
- }
-
- if( forPOST ){
- query += this.fieldNameMap["id"] + ':*' + this.escapeSpecialChar(identifiers) + "*";
- }
- else{
- query += this.fieldNameMap["id"] + ':*' + this.escapeSpecialChar(encodeURIComponent(identifiers)) + "*";
- }
- }
- }
+ query +=
+ "beginDate:[" +
+ yearMin +
+ "-01-01T00:00:00Z TO *]" +
+ " AND endDate:[* TO " +
+ yearMax +
+ "-12-31T00:00:00Z]";
+ }
+ }
+
+ //----- public/private ------
+ if (
+ this.filterIsAvailable("isPrivate") &&
+ (filter == "isPrivate" || getAll)
+ ) {
+ var isPrivate = this.get("isPrivate");
+ if (isPrivate !== null && isPrivate !== "undefined") {
+ if (query.length) {
+ query += " AND ";
+ }
- //---resourceMap---
- if (this.filterIsAvailable("resourceMap") && ((filter == "resourceMap") || getAll)) {
- var resourceMap = this.get('resourceMap');
-
- //If the resource map search setting is a list of resource map IDs
- if (Array.isArray(resourceMap)) {
- if( query.length ){
- query += " AND ";
- }
-
- query += this.getGroupedQuery(this.fieldNameMap["resourceMap"], resourceMap, {
- operator: "OR",
- forPOST: forPOST
- });
-
- } else if (resourceMap) {
- if( query.length ){
- query += " AND ";
- }
- //Otherwise, treat it as a binary setting
- query += this.fieldNameMap["resourceMap"] + ':*';
- }
- }
+ // Currently, the Solr field 'isPublic' can be set to true or false, or not set.
+ // isPrivate is equivalent to "isPublic:false" or isPublic not set
+ if (isPrivate) {
+ query += "-isPublic:true";
+ }
+ }
+ }
+
+ //-----Data Source--------
+ if (
+ this.filterIsAvailable("dataSource") &&
+ (filter == "dataSource" || getAll)
+ ) {
+ var filterValue = null;
+ var filterValues = [];
+
+ if (this.get("dataSource").length > 0) {
+ var objectValues = _.filter(this.get("dataSource"), function (v) {
+ return typeof v == "object";
+ });
+ if (objectValues && objectValues.length) {
+ filterValues.push(_.pluck(objectValues, "value"));
+ }
+ }
- //---documents---
- if (this.filterIsAvailable("documents") && ((filter == "documents") || getAll)) {
- var documents = this.get('documents');
-
- //If the documents search setting is a list ofdocuments IDs
- if (Array.isArray(documents)) {
- if( query.length ){
- query += " AND ";
- }
-
- query += this.getGroupedQuery(this.fieldNameMap["documents"], documents, {
- operator: "OR",
- forPOST: forPOST
- });
- } else if (documents) {
-
- if( query.length ){
- query += " AND ";
- }
- //Otherwise, treat it as a binary setting
- query += this.fieldNameMap["documents"] + ':*';
- }
- }
+ var stringValues = _.filter(this.get("dataSource"), function (v) {
+ return typeof v == "string";
+ });
+ if (stringValues && stringValues.length) {
+ filterValues.push(stringValues);
+ }
- //---Username: search for this username in rightsHolder and submitter ---
- if (this.filterIsAvailable("username") && ((filter == "username") || getAll) && this.get('username').length) {
- var username = this.get('username');
- if (username) {
- if( query.length ){
- query += " AND ";
- }
-
- query += this.getGroupedQuery(this.fieldNameMap["username"], username, {
- operator: "OR",
- forPOST: forPOST
- });
- }
- }
+ filterValues = _.flatten(filterValues);
- //--- ID Only - searches only the id and seriesId fields ---
- if (this.filterIsAvailable("idOnly") && ((filter == "idOnly") || getAll) && this.get('idOnly').length) {
- var idOnly = this.get('idOnly');
- if (idOnly) {
+ if (filterValues.length) {
+ if (query.length) {
+ query += " AND ";
+ }
- if( query.length ){
- query += " AND ";
- }
+ query += this.getGroupedQuery(
+ this.fieldNameMap["dataSource"],
+ filterValues,
+ {
+ operator: "OR",
+ forPOST: forPOST,
+ },
+ );
+ }
+ }
+
+ //-----Excluded fields-----
+ if (
+ this.filterIsAvailable("exclude") &&
+ (filter == "exclude" || getAll)
+ ) {
+ var exclude = this.get("exclude");
+ _.each(exclude, function (excludeField, key, list) {
+ if (model.needsQuotes(excludeField.value)) {
+ if (forPOST) {
+ var filterValue = '"' + excludeField.value + '"';
+ } else {
+ var filterValue =
+ "%22" + encodeURIComponent(excludeField.value) + "%22";
+ }
+ } else {
+ if (forPOST) {
+ var filterValue = excludeField.value;
+ } else {
+ var filterValue = encodeURIComponent(excludeField.value);
+ }
+ }
- query += this.getGroupedQuery(this.fieldNameMap["idOnly"], idOnly, {
- operator: "OR",
- forPOST: forPOST
- });
- }
- }
+ filterValue = model.escapeSpecialChar(filterValue);
- //---Taxon---
- if (this.filterIsAvailable("taxon") && ((filter == "taxon") || getAll) && this.get('taxon').length) {
- var taxon = this.get('taxon');
+ if (query.length) {
+ query += " AND ";
+ }
- for (var i = 0; i < taxon.length; i++) {
- var value = (typeof taxon == "object") ? taxon[i].value : taxon[i].trim();
+ query += " -" + excludeField.field + ":" + filterValue;
+ });
+ }
+
+ //-----Additional criteria - both field and value are provided-----
+ if (
+ this.filterIsAvailable("additionalCriteria") &&
+ (filter == "additionalCriteria" || getAll)
+ ) {
+ var additionalCriteria = this.get("additionalCriteria");
+ for (var i = 0; i < additionalCriteria.length; i++) {
+ var value;
+
+ if (forPOST) {
+ value = additionalCriteria[i];
+ } else {
+ //if(this.needsQuotes(additionalCriteria[i])) value = "%22" + encodeURIComponent(additionalCriteria[i]) + "%22";
+ value = encodeURIComponent(additionalCriteria[i]);
+ }
- query += this.getMultiFieldQuery(this.fieldNameMap["taxon"], value, {
- subtext: true,
- forPOST: forPOST
- });
- }
- }
+ if (query.length) {
+ query += " AND ";
+ }
- //------Pub Year-----
- if (this.filterIsAvailable("pubYear") && ((filter == "pubYear") || getAll)) {
- //Get the types of year to be searched first
- var pubYear = this.get('pubYear');
- if (pubYear) {
- //Get the minimum and maximum years chosen
- var yearMin = this.get('yearMin');
- var yearMax = this.get('yearMax');
-
- if( query.length ){
- query += " AND ";
- }
-
- var value = "[" + yearMin + "-01-01T00:00:00Z TO " + yearMax + "-12-31T00:00:00Z]";
- var opts = {
- forPOST: forPOST,
- escapeSquareBrackets: false
- }
-
- //Add to the query if we are searching publication year
- query += this.getMultiFieldQuery(
- this.fieldNameMap["pubYear"], value, opts
- );
- }
- }
+ query += model.escapeSpecialChar(value);
+ }
+ }
+
+ //-----All (full text search) -----
+ if (this.filterIsAvailable("all") && (filter == "all" || getAll)) {
+ var all = this.get("all");
+ for (var i = 0; i < all.length; i++) {
+ var filterValue = all[i];
+
+ if (typeof filterValue == "object") {
+ filterValue = filterValue.value;
+ } else if (
+ (typeof filterValue == "string" && !filterValue.length) ||
+ typeof filterValue == "undefined" ||
+ filterValue === null
+ ) {
+ continue;
+ }
- //-----Data year------
- if (this.filterIsAvailable("dataYear") && ((filter == "dataYear") || getAll)) {
- var dataYear = this.get('dataYear');
+ if (this.needsQuotes(filterValue)) {
+ if (forPOST) {
+ filterValue = '"' + filterValue + '"';
+ } else {
+ filterValue = "%22" + encodeURIComponent(filterValue) + "%22";
+ }
+ } else {
+ if (forPOST) {
+ filterValue = filterValue;
+ } else {
+ filterValue = encodeURIComponent(filterValue);
+ }
+ }
- if (dataYear) {
- //Get the minimum and maximum years chosen
- var yearMin = this.get('yearMin');
- var yearMax = this.get('yearMax');
+ if (query.length) {
+ query += " AND ";
+ }
- if( query.length ){
- query += " AND ";
- }
+ query += model.escapeSpecialChar(filterValue);
+ }
+ }
+
+ //-----Other Filters/Basic Filters-----
+ _.each(otherFilters, function (filterName, key, list) {
+ if (
+ model.filterIsAvailable(filterName) &&
+ (filter == filterName || getAll)
+ ) {
+ var filterValue = null;
+ var filterValues = model.get(filterName);
+
+ for (var i = 0; i < filterValues.length; i++) {
+ //Trim the spaces off
+ var filterValue = filterValues[i];
+ if (typeof filterValue == "object") {
+ filterValue = filterValue.value;
+ }
+ filterValue = filterValue.trim();
- query += "beginDate:[" + yearMin + "-01-01T00:00:00Z TO *]" +
- " AND endDate:[* TO " + yearMax + "-12-31T00:00:00Z]";
- }
+ // Does this need to be wrapped in quotes?
+ if (model.needsQuotes(filterValue)) {
+ if (forPOST) {
+ filterValue = '"' + filterValue + '"';
+ } else {
+ filterValue = "%22" + encodeURIComponent(filterValue) + "%22";
}
-
- //----- public/private ------
- if (this.filterIsAvailable("isPrivate") && ((filter == "isPrivate") || getAll)) {
-
- var isPrivate = this.get('isPrivate');
- if (isPrivate !== null && isPrivate !== 'undefined') {
-
- if( query.length ){
- query += " AND ";
- }
-
- // Currently, the Solr field 'isPublic' can be set to true or false, or not set.
- // isPrivate is equivalent to "isPublic:false" or isPublic not set
- if(isPrivate) {
- query += "-isPublic:true"
- }
- }
+ } else {
+ if (forPOST) {
+ filterValue = filterValue;
+ } else {
+ filterValue = encodeURIComponent(filterValue);
}
+ }
+ if (query.length) {
+ query += " AND ";
+ }
- //-----Data Source--------
- if (this.filterIsAvailable("dataSource") && ((filter == "dataSource") || getAll)) {
- var filterValue = null;
- var filterValues = [];
-
- if (this.get("dataSource").length > 0) {
- var objectValues = _.filter(this.get("dataSource"), function(v) {
- return (typeof v == "object")
- });
- if (objectValues && objectValues.length) {
- filterValues.push(_.pluck(objectValues, "value"));
- }
- }
+ query +=
+ model.fieldNameMap[filterName] +
+ ":" +
+ model.escapeSpecialChar(filterValue);
+ }
+ }
+ });
- var stringValues = _.filter(this.get("dataSource"), function(v) {
- return (typeof v == "string")
- });
- if (stringValues && stringValues.length) {
- filterValues.push(stringValues);
- }
+ //-----Geohashes-----
+ if (
+ this.filterIsAvailable("geohashLevel") &&
+ (filter == "geohash" || getAll) &&
+ this.get("useGeohash")
+ ) {
+ var geohashes = this.get("geohashes");
+
+ if (typeof geohashes != undefined && geohashes.length > 0) {
+ var groups = this.get("geohashGroups"),
+ numGroups =
+ typeof groups == "object" ? Object.keys(groups).length : 0;
+
+ if (numGroups > 0) {
+ //Add the AND operator in front of the geohash filter
+ if (query.length) {
+ query += " AND ";
+ }
- filterValues = _.flatten(filterValues);
+ //If there is more than one geohash group/level, wrap them in paranthesis
+ if (numGroups > 1) {
+ query += "(";
+ }
- if( filterValues.length ){
- if( query.length ){
- query += " AND ";
- }
+ _.each(Object.keys(groups), function (level, i, allLevels) {
+ var geohashList = groups[level];
- query += this.getGroupedQuery(this.fieldNameMap["dataSource"], filterValues, {
- operator: "OR",
- forPOST: forPOST
- });
- }
- }
+ query += "geohash_" + level + ":";
- //-----Excluded fields-----
- if (this.filterIsAvailable("exclude") && ((filter == "exclude") || getAll)) {
- var exclude = this.get("exclude");
- _.each(exclude, function(excludeField, key, list) {
-
- if (model.needsQuotes(excludeField.value)) {
- if( forPOST ){
- var filterValue = '"' + excludeField.value + '"';
- }
- else{
- var filterValue = "%22" + encodeURIComponent(excludeField.value) + "%22";
- }
- } else {
- if( forPOST ){
- var filterValue = excludeField.value;
- }
- else{
- var filterValue = encodeURIComponent(excludeField.value);
- }
- }
-
- filterValue = model.escapeSpecialChar(filterValue);
-
- if( query.length ){
- query += " AND ";
- }
-
- query += " -" + excludeField.field + ":" + filterValue;
- });
+ if (geohashList.length > 1) {
+ query += "(";
}
- //-----Additional criteria - both field and value are provided-----
- if (this.filterIsAvailable("additionalCriteria") && ((filter == "additionalCriteria") || getAll)) {
- var additionalCriteria = this.get('additionalCriteria');
- for (var i = 0; i < additionalCriteria.length; i++) {
- var value;
-
- if( forPOST ){
- value = additionalCriteria[i];
- }
- else{
- //if(this.needsQuotes(additionalCriteria[i])) value = "%22" + encodeURIComponent(additionalCriteria[i]) + "%22";
- value = encodeURIComponent(additionalCriteria[i]);
- }
-
- if( query.length ){
- query += " AND ";
- }
-
- query += model.escapeSpecialChar(value);
+ _.each(geohashList, function (g, ii, allGeohashes) {
+ //Keep URI's from getting too long if we are using GET
+ if (
+ MetacatUI.appModel.get("disableQueryPOSTs") &&
+ query.length > 1900
+ ) {
+ //Remove the last " OR "
+ if (query.endsWith(" OR ")) {
+ query = query.substring(0, query.length - 4);
}
- }
- //-----All (full text search) -----
- if (this.filterIsAvailable("all") && ((filter == "all") || getAll)) {
- var all = this.get('all');
- for (var i = 0; i < all.length; i++) {
- var filterValue = all[i];
-
- if (typeof filterValue == "object") {
- filterValue = filterValue.value;
- }
- else if( (typeof filterValue == "string" && !filterValue.length) ||
- typeof filterValue == "undefined" || filterValue === null){
- continue;
- }
-
- if (this.needsQuotes(filterValue)) {
- if( forPOST ){
- filterValue = '"' + filterValue + '"';
- }
- else{
- filterValue = "%22" + encodeURIComponent(filterValue) + "%22";
- }
- } else {
- if( forPOST ){
- filterValue = filterValue;
- }
- else{
- filterValue = encodeURIComponent(filterValue);
- }
- }
-
- if( query.length ){
- query += " AND ";
- }
-
- query += model.escapeSpecialChar(filterValue);
- }
- }
+ return;
+ } else {
+ //Add the geohash value to the query
+ query += g;
- //-----Other Filters/Basic Filters-----
- _.each(otherFilters, function(filterName, key, list) {
- if (model.filterIsAvailable(filterName) && ((filter == filterName) || getAll)) {
- var filterValue = null;
- var filterValues = model.get(filterName);
-
- for (var i = 0; i < filterValues.length; i++) {
-
- //Trim the spaces off
- var filterValue = filterValues[i];
- if (typeof filterValue == "object") {
- filterValue = filterValue.value;
- }
- filterValue = filterValue.trim();
-
- // Does this need to be wrapped in quotes?
- if (model.needsQuotes(filterValue)) {
- if( forPOST ){
- filterValue = '"' + filterValue + '"';
- }
- else{
- filterValue = "%22" + encodeURIComponent(filterValue) + "%22";
- }
- } else {
- if( forPOST ){
- filterValue = filterValue;
- }
- else{
- filterValue = encodeURIComponent(filterValue);
- }
- }
-
- if( query.length ){
- query += " AND ";
- }
-
- query += model.fieldNameMap[filterName] + ":" + model.escapeSpecialChar(filterValue);
- }
+ //Add an " OR " operator inbetween geohashes
+ if (ii < allGeohashes.length - 1) {
+ query += " OR ";
}
+ }
});
- //-----Geohashes-----
- if (this.filterIsAvailable("geohashLevel") && (((filter == "geohash") || getAll)) && this.get("useGeohash")) {
- var geohashes = this.get("geohashes");
-
- if ((typeof geohashes != undefined) && (geohashes.length > 0)) {
- var groups = this.get("geohashGroups"),
- numGroups = (typeof groups == "object")? Object.keys(groups).length : 0;
-
- if(numGroups > 0){
- //Add the AND operator in front of the geohash filter
- if( query.length ){
- query += " AND ";
- }
-
- //If there is more than one geohash group/level, wrap them in paranthesis
- if( numGroups > 1){
- query += "(";
- }
-
- _.each(Object.keys(groups), function(level, i, allLevels) {
- var geohashList = groups[level];
-
- query += "geohash_" + level + ":";
-
- if( geohashList.length > 1 ){
- query += "(";
- }
-
- _.each(geohashList, function(g, ii, allGeohashes) {
- //Keep URI's from getting too long if we are using GET
- if( MetacatUI.appModel.get("disableQueryPOSTs") && query.length > 1900){
-
- //Remove the last " OR "
- if( query.endsWith(" OR ") ){
- query = query.substring(0, query.length-4)
- }
-
- return;
- }
- else{
- //Add the geohash value to the query
- query += g;
-
- //Add an " OR " operator inbetween geohashes
- if( ii < allGeohashes.length-1 ){
- query += " OR ";
- }
- }
- });
-
- //Close the paranthesis
- if( geohashList.length > 1 ){
- query += ")";
- }
-
- //Add an " OR " operator inbetween geohash levels
- if( i < allLevels.length-1 ){
- query += " OR "
- }
-
- });
-
- //Close the paranthesis
- if(numGroups > 1){
- query += ")";
- }
- }
- }
+ //Close the paranthesis
+ if (geohashList.length > 1) {
+ query += ")";
}
- //---Spatial---
- if (this.filterIsAvailable("spatial") && ((filter == "spatial") || getAll)) {
- var spatial = this.get('spatial');
-
- if (Array.isArray(spatial) && spatial.length) {
-
- if( query.length ){
- query += " AND ";
- }
-
- query += this.getGroupedQuery(this.fieldNameMap["spatial"], spatial, {
- operator: "AND",
- subtext: false,
- forPOST: forPOST
- });
-
- } else if( typeof spatial == "string" && spatial.length) {
-
- if( query.length ){
- query += " AND ";
- }
-
- if( forPOST ){
- query += this.fieldNameMap["spatial"] + ':' + model.escapeSpecialChar(spatial);
- }
- else{
- query += this.fieldNameMap["spatial"] + ':' + model.escapeSpecialChar(encodeURIComponent(spatial));
- }
-
- }
- }
-
- //---Creator---
- if (this.filterIsAvailable("creator") && ((filter == "creator") || getAll)) {
- var creator = this.get('creator');
-
- if (Array.isArray(creator) && creator.length) {
- if( query.length ){
- query += " AND ";
- }
-
- query += this.getGroupedQuery(this.fieldNameMap["creator"], creator, {
- operator: "AND",
- subtext: false,
- forPOST: forPOST
- });
- } else if (typeof creator == "string" && creator.length) {
- if( query.length ){
- query += " AND ";
- }
-
- if( forPOST ){
- query += this.fieldNameMap["creator"] + ':' + model.escapeSpecialChar(creator);
- }
- else{
- query += this.fieldNameMap["creator"] + ':' + model.escapeSpecialChar(encodeURIComponent(creator));
- }
- }
+ //Add an " OR " operator inbetween geohash levels
+ if (i < allLevels.length - 1) {
+ query += " OR ";
}
-
- // Add project filter
- if (this.filterIsAvailable("projectText") && ((filter == "projectText") || getAll)) {
-
- var project = this.get('projectText');
- if (project && project.length > 0) {
+ });
- if( query.length ){
- query += " AND ";
- }
- query += 'projectText:"' + project[0].value + '"';
- }
- }
-
-
- return query;
- },
-
- getFacetQuery: function(fields) {
-
- var facetQuery = "&facet=true" +
- "&facet.sort=count" +
- "&facet.mincount=1" +
- "&facet.limit=-1";
-
- //Get the list of fields
- if (!fields) {
- var fields = "keywords,origin,family,species,genus,kingdom,phylum,order,class,site";
- if (this.filterIsAvailable("annotation")) {
- fields += "," + this.facetNameMap["annotation"];
- }
- if (this.filterIsAvailable("attribute")) {
- fields += ",attributeName,attributeLabel";
- }
- }
-
- var model = this;
- //Add the fields to the query string
- _.each(fields.split(","), function(f) {
- var fieldNames = model.facetNameMap[f] || f;
+ //Close the paranthesis
+ if (numGroups > 1) {
+ query += ")";
+ }
+ }
+ }
+ }
+
+ //---Spatial---
+ if (
+ this.filterIsAvailable("spatial") &&
+ (filter == "spatial" || getAll)
+ ) {
+ var spatial = this.get("spatial");
+
+ if (Array.isArray(spatial) && spatial.length) {
+ if (query.length) {
+ query += " AND ";
+ }
- if (typeof fieldNames == "string") {
- fieldNames = [fieldNames];
- }
+ query += this.getGroupedQuery(
+ this.fieldNameMap["spatial"],
+ spatial,
+ {
+ operator: "AND",
+ subtext: false,
+ forPOST: forPOST,
+ },
+ );
+ } else if (typeof spatial == "string" && spatial.length) {
+ if (query.length) {
+ query += " AND ";
+ }
- _.each(fieldNames, function(fName) {
- facetQuery += "&facet.field=" + fName;
- });
- });
+ if (forPOST) {
+ query +=
+ this.fieldNameMap["spatial"] +
+ ":" +
+ model.escapeSpecialChar(spatial);
+ } else {
+ query +=
+ this.fieldNameMap["spatial"] +
+ ":" +
+ model.escapeSpecialChar(encodeURIComponent(spatial));
+ }
+ }
+ }
+
+ //---Creator---
+ if (
+ this.filterIsAvailable("creator") &&
+ (filter == "creator" || getAll)
+ ) {
+ var creator = this.get("creator");
+
+ if (Array.isArray(creator) && creator.length) {
+ if (query.length) {
+ query += " AND ";
+ }
- return facetQuery;
- },
+ query += this.getGroupedQuery(
+ this.fieldNameMap["creator"],
+ creator,
+ {
+ operator: "AND",
+ subtext: false,
+ forPOST: forPOST,
+ },
+ );
+ } else if (typeof creator == "string" && creator.length) {
+ if (query.length) {
+ query += " AND ";
+ }
- //Check for spaces in a string - we'll use this to url encode the query
- needsQuotes: function(entry) {
+ if (forPOST) {
+ query +=
+ this.fieldNameMap["creator"] +
+ ":" +
+ model.escapeSpecialChar(creator);
+ } else {
+ query +=
+ this.fieldNameMap["creator"] +
+ ":" +
+ model.escapeSpecialChar(encodeURIComponent(creator));
+ }
+ }
+ }
+
+ // Add project filter
+ if (
+ this.filterIsAvailable("projectText") &&
+ (filter == "projectText" || getAll)
+ ) {
+ var project = this.get("projectText");
+ if (project && project.length > 0) {
+ if (query.length) {
+ query += " AND ";
+ }
+ query += 'projectText:"' + project[0].value + '"';
+ }
+ }
+
+ return query;
+ },
+
+ getFacetQuery: function (fields) {
+ var facetQuery =
+ "&facet=true" +
+ "&facet.sort=count" +
+ "&facet.mincount=1" +
+ "&facet.limit=-1";
+
+ //Get the list of fields
+ if (!fields) {
+ var fields =
+ "keywords,origin,family,species,genus,kingdom,phylum,order,class,site";
+ if (this.filterIsAvailable("annotation")) {
+ fields += "," + this.facetNameMap["annotation"];
+ }
+ if (this.filterIsAvailable("attribute")) {
+ fields += ",attributeName,attributeLabel";
+ }
+ }
+
+ var model = this;
+ //Add the fields to the query string
+ _.each(fields.split(","), function (f) {
+ var fieldNames = model.facetNameMap[f] || f;
+
+ if (typeof fieldNames == "string") {
+ fieldNames = [fieldNames];
+ }
+
+ _.each(fieldNames, function (fName) {
+ facetQuery += "&facet.field=" + fName;
+ });
+ });
- //Check for spaces
- var value = "";
+ return facetQuery;
+ },
+
+ //Check for spaces in a string - we'll use this to url encode the query
+ needsQuotes: function (entry) {
+ //Check for spaces
+ var value = "";
+
+ if (typeof entry == "object") {
+ value = entry.value;
+ } else if (typeof entry == "string") {
+ value = entry;
+ } else {
+ return false;
+ }
+
+ //Is this a date range search? If so, we don't use quote marks
+ var ISODateRegEx =
+ /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)/;
+ if (ISODateRegEx.exec(value)) {
+ return false;
+ }
+
+ //Check for a space character
+ if (value.indexOf(" ") > -1) {
+ return true;
+ }
+
+ //Check if this is an account subject string
+ var LDAPSubjectRegEx =
+ /(uid=|UID=|cn=|CN=).+([a-zA-Z]=).+([a-zA-Z]=).*/,
+ ORCIDRegEx =
+ /^http\:\/\/orcid\.org\/[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9X]{4}/;
+
+ if (LDAPSubjectRegEx.exec(value) || ORCIDRegEx.exec(value)) {
+ return true;
+ }
+
+ return false;
+ },
+
+ escapeSpecialChar: function (term, escapeSquareBrackets = true) {
+ term = term.replace(/%7B/g, "%5C%7B");
+ term = term.replace(/%7D/g, "%5C%7D");
+ term = term.replace(/%3A/g, "%5C%3A");
+ term = term.replace(/:/g, "%5C:");
+ term = term.replace(/\(/g, "%5C(");
+ term = term.replace(/\)/g, "%5C)");
+ term = term.replace(/\?/g, "%5C?");
+ term = term.replace(/%3F/g, "%5C%3F");
+ term = term.replace(/%2B/g, "%5C%2B");
+ //Remove ampersands (&) for now since they are reserved Solr characters and the Metacat Solr can't seem to handle them even when they are escaped properly for some reason
+ term = term.replace(/%26/g, "");
+ term = term.replace(/%7C%7C/g, "%5C%7C%5C%7C");
+ term = term.replace(/%21/g, "%5C%21");
+ term = term.replace(/%28/g, "%5C%28");
+ term = term.replace(/%29/g, "%5C%29");
+ term = term.replace(/%5E/g, "%5C%5E");
+ term = term.replace(/%22/g, "%5C%22");
+ term = term.replace(/~/g, "%5C~");
+ term = term.replace(/-/g, "%5C-");
+ term = term.replace(/%2F/g, "%5C%2F");
+
+ if (escapeSquareBrackets) {
+ term = term.replace(/%5B/g, "%5C%5B");
+ term = term.replace(/%5D/g, "%5C%5D");
+ }
+
+ return term;
+ },
+ /*
+ * Makes a Solr syntax grouped query using the field name, the field values to search for, and the operator.
+ * Example: title:(resistance OR salmon OR "pink salmon")
+ */
+ getGroupedQuery: function (fieldName, values, options) {
+ if (!values) return "";
+ values = _.compact(values);
+ if (!values.length) return "";
+
+ var query = "",
+ numValues = values.length,
+ model = this;
+
+ if (Array.isArray(fieldName) && fieldName.length > 1) {
+ return this.getMultiFieldQuery(fieldName, values, options);
+ }
+
+ if (options && typeof options == "object") {
+ var operator = options.operator,
+ subtext = options.subtext,
+ forPOST = options.forPOST;
+ }
+
+ if (
+ typeof operator === "undefined" ||
+ !operator ||
+ (operator != "OR" && operator != "AND")
+ ) {
+ var operator = "OR";
+ }
+
+ if (numValues == 1) {
+ var value = values[0],
+ queryAddition;
+
+ if (
+ !Array.isArray(value) &&
+ typeof value === "object" &&
+ value.value
+ ) {
+ value = value.value.trim();
+ }
+
+ if (this.needsQuotes(values[0])) {
+ if (forPOST) {
+ queryAddition = '"' + this.escapeSpecialChar(value) + '"';
+ } else {
+ queryAddition =
+ "%22" +
+ this.escapeSpecialChar(encodeURIComponent(value)) +
+ "%22";
+ }
+ } else if (subtext) {
+ if (forPOST) {
+ queryAddition = "*" + this.escapeSpecialChar(value) + "*";
+ } else {
+ queryAddition =
+ "*" + this.escapeSpecialChar(encodeURIComponent(value)) + "*";
+ }
+ } else {
+ if (forPOST) {
+ queryAddition = this.escapeSpecialChar(value);
+ } else {
+ queryAddition = this.escapeSpecialChar(encodeURIComponent(value));
+ }
+ }
+ query = fieldName + ":" + queryAddition;
+ } else {
+ _.each(
+ values,
+ function (value, i) {
+ //Check for filter objects
+ if (
+ !Array.isArray(value) &&
+ typeof value === "object" &&
+ value.value
+ ) {
+ value = value.value.trim();
+ }
- if (typeof entry == "object") {
- value = entry.value;
- } else if (typeof entry == "string") {
- value = entry;
+ if (model.needsQuotes(value)) {
+ if (forPOST) {
+ value = '"' + value + '"';
} else {
- return false;
- }
-
- //Is this a date range search? If so, we don't use quote marks
- var ISODateRegEx = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)/;
- if (ISODateRegEx.exec(value)) {
- return false;
+ value = "%22" + encodeURIComponent(value) + "%22";
}
-
- //Check for a space character
- if (value.indexOf(" ") > -1) {
- return true;
+ } else if (subtext) {
+ if (forPOST) {
+ value = "*" + this.escapeSpecialChar(value) + "*";
+ } else {
+ value =
+ "*" +
+ this.escapeSpecialChar(encodeURIComponent(value)) +
+ "*";
}
-
- //Check if this is an account subject string
- var LDAPSubjectRegEx = /(uid=|UID=|cn=|CN=).+([a-zA-Z]=).+([a-zA-Z]=).*/,
- ORCIDRegEx = /^http\:\/\/orcid\.org\/[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9X]{4}/;
-
- if (LDAPSubjectRegEx.exec(value) || ORCIDRegEx.exec(value)) {
- return true;
+ } else {
+ if (forPOST) {
+ value = this.escapeSpecialChar(value);
+ } else {
+ value = this.escapeSpecialChar(encodeURIComponent(value));
}
+ }
- return false;
+ if (i == 0 && numValues > 1) {
+ query += fieldName + ":(" + value;
+ } else if (i > 0 && i < numValues - 1 && query.length) {
+ query += " " + operator + " " + value;
+ } else if (i > 0 && i < numValues - 1) {
+ query += value;
+ } else if (i == numValues - 1) {
+ query += " " + operator + " " + value + ")";
+ }
},
-
- escapeSpecialChar: function(term, escapeSquareBrackets=true) {
- term = term.replace(/%7B/g, "%5C%7B");
- term = term.replace(/%7D/g, "%5C%7D");
- term = term.replace(/%3A/g, "%5C%3A");
- term = term.replace(/:/g, "%5C:");
- term = term.replace(/\(/g, "%5C(");
- term = term.replace(/\)/g, "%5C)");
- term = term.replace(/\?/g, "%5C?");
- term = term.replace(/%3F/g, "%5C%3F");
- term = term.replace(/%2B/g, "%5C%2B");
- //Remove ampersands (&) for now since they are reserved Solr characters and the Metacat Solr can't seem to handle them even when they are escaped properly for some reason
- term = term.replace(/%26/g, "");
- term = term.replace(/%7C%7C/g, "%5C%7C%5C%7C");
- term = term.replace(/%21/g, "%5C%21");
- term = term.replace(/%28/g, "%5C%28");
- term = term.replace(/%29/g, "%5C%29");
- term = term.replace(/%5E/g, "%5C%5E");
- term = term.replace(/%22/g, "%5C%22");
- term = term.replace(/~/g, "%5C~");
- term = term.replace(/-/g, "%5C-");
- term = term.replace(/%2F/g, "%5C%2F");
-
- if (escapeSquareBrackets) {
- term = term.replace(/%5B/g, "%5C%5B");
- term = term.replace(/%5D/g, "%5C%5D");
+ this,
+ );
+ }
+
+ return query;
+ },
+
+ /*
+ * Makes a Solr syntax query using multiple field names, one field value to search for, and some options.
+ * Example: (family:*Pin* OR class:*Pin* OR order:*Pin* OR phlyum:*Pin*)
+ * Options:
+ * - operator (OR or AND)
+ * - subtext (binary) - will surround search value with wildcards to search for partial matches
+ * - Example:
+ * var options = { operator: "OR", subtext: true }
+ */
+ getMultiFieldQuery: function (fieldNames, value, options) {
+ var query = "",
+ numFields = fieldNames.length,
+ model = this;
+
+ //Catch errors
+ if (numFields < 1 || !value) return "";
+
+ //If only one field was given, then use the grouped query function
+ if (numFields == 1) {
+ return this.getGroupedQuery(fieldNames, value, options);
+ }
+
+ //Get the options
+ if (options && typeof options == "object") {
+ var operator = options.operator,
+ subtext = options.subtext,
+ forPOST = options.forPOST;
+ }
+ var esb =
+ typeof options.escapeSquareBrackets == "boolean"
+ ? options.escapeSquareBrackets
+ : true;
+
+ //Default to the OR operator
+ if (
+ typeof operator === "undefined" ||
+ !operator ||
+ (operator != "OR" && operator != "AND")
+ ) {
+ var operator = "OR";
+ }
+ if (typeof subtext === "undefined") {
+ var subtext = false;
+ }
+
+ //Create the value string
+ //Trim the spaces off
+ if (!Array.isArray(value) && typeof value === "object" && value.value) {
+ value = [value.value.trim()];
+ } else if (typeof value == "string") {
+ value = [value.trim()];
+ }
+
+ var valueString = "";
+ if (Array.isArray(value)) {
+ var model = this;
+
+ _.each(
+ value,
+ function (v, i) {
+ if (typeof v == "object" && v.value) {
+ v = v.value;
}
- return term;
- },
- /*
- * Makes a Solr syntax grouped query using the field name, the field values to search for, and the operator.
- * Example: title:(resistance OR salmon OR "pink salmon")
- */
- getGroupedQuery: function(fieldName, values, options) {
- if (!values) return "";
- values = _.compact(values);
- if (!values.length) return "";
-
- var query = "",
- numValues = values.length,
- model = this;
-
- if (Array.isArray(fieldName) && (fieldName.length > 1)) {
- return this.getMultiFieldQuery(fieldName, values, options);
- }
+ if (value.length > 1 && i == 0) {
+ valueString += "(";
+ }
- if (options && (typeof options == "object")) {
- var operator = options.operator,
- subtext = options.subtext,
- forPOST = options.forPOST;
+ if (model.needsQuotes(v) || _.contains(fieldNames, "id")) {
+ if (forPOST) {
+ valueString +=
+ '"' + this.escapeSpecialChar(v.trim(), esb) + '"';
+ } else {
+ valueString +=
+ '"' +
+ this.escapeSpecialChar(encodeURIComponent(v.trim()), esb) +
+ '"';
}
-
- if ((typeof operator === "undefined") || !operator || ((operator != "OR") && (operator != "AND"))) {
- var operator = "OR";
+ } else if (subtext) {
+ if (forPOST) {
+ valueString +=
+ "*" + this.escapeSpecialChar(v.trim(), esb) + "*";
+ } else {
+ valueString +=
+ "*" +
+ this.escapeSpecialChar(encodeURIComponent(v.trim()), esb) +
+ "*";
}
-
- if (numValues == 1) {
- var value = values[0],
- queryAddition;
-
- if (!Array.isArray(value) && (typeof value === "object") && value.value) {
- value = value.value.trim();
- }
-
- if (this.needsQuotes(values[0])) {
- if( forPOST ){
- queryAddition = '"' + this.escapeSpecialChar(value) + '"';
- }
- else{
- queryAddition = '%22' + this.escapeSpecialChar(encodeURIComponent(value)) + '%22';
- }
- } else if (subtext) {
- if( forPOST ){
- queryAddition = "*" + this.escapeSpecialChar(value) + "*";
- }
- else{
- queryAddition = "*" + this.escapeSpecialChar(encodeURIComponent(value)) + "*";
- }
- } else {
- if( forPOST ){
- queryAddition = this.escapeSpecialChar(value);
- }
- else{
- queryAddition = this.escapeSpecialChar(encodeURIComponent(value));
- }
- }
- query = fieldName + ":" + queryAddition;
+ } else {
+ if (forPOST) {
+ valueString += this.escapeSpecialChar(v.trim(), esb);
} else {
- _.each(values, function(value, i) {
- //Check for filter objects
- if (!Array.isArray(value) && (typeof value === "object") && value.value) {
- value = value.value.trim();
- }
-
- if (model.needsQuotes(value)) {
- if( forPOST ){
- value = '"' + value + '"';
- }
- else{
- value = '%22' + encodeURIComponent(value) + '%22';
- }
- } else if (subtext) {
- if( forPOST ){
- value = "*" + this.escapeSpecialChar(value) + "*";
- }
- else{
- value = "*" + this.escapeSpecialChar(encodeURIComponent(value)) + "*";
- }
- } else {
- if( forPOST ){
- value = this.escapeSpecialChar(value);
- }
- else{
- value = this.escapeSpecialChar(encodeURIComponent(value));
- }
- }
-
- if ((i == 0) && (numValues > 1)) {
- query += fieldName + ":(" + value;
- } else if ((i > 0) && (i < numValues - 1) && query.length) {
- query += " " + operator + " " + value;
- } else if( (i > 0) && (i < numValues - 1) ){
- query += value;
- } else if (i == numValues - 1) {
- query += " " + operator + " " + value + ")";
- }
- }, this);
+ valueString += this.escapeSpecialChar(
+ encodeURIComponent(v.trim()),
+ esb,
+ );
}
+ }
- return query;
+ if (i < value.length - 1) {
+ valueString += " OR ";
+ } else if (i == value.length - 1 && value.length > 1) {
+ valueString += ")";
+ }
},
+ this,
+ );
+ } else valueString = value;
+
+ query = "(";
+
+ //Create the query string
+ var last = numFields - 1;
+ _.each(fieldNames, function (field, i) {
+ query += field + ":" + valueString;
+ if (i < last) {
+ query += " " + operator + " ";
+ }
+ });
- /*
- * Makes a Solr syntax query using multiple field names, one field value to search for, and some options.
- * Example: (family:*Pin* OR class:*Pin* OR order:*Pin* OR phlyum:*Pin*)
- * Options:
- * - operator (OR or AND)
- * - subtext (binary) - will surround search value with wildcards to search for partial matches
- * - Example:
- * var options = { operator: "OR", subtext: true }
- */
- getMultiFieldQuery: function(fieldNames, value, options) {
- var query = "",
- numFields = fieldNames.length,
- model = this;
-
- //Catch errors
- if ((numFields < 1) || !value) return "";
-
- //If only one field was given, then use the grouped query function
- if (numFields == 1) {
- return this.getGroupedQuery(fieldNames, value, options);
- }
-
- //Get the options
- if (options && (typeof options == "object")) {
- var operator = options.operator,
- subtext = options.subtext,
- forPOST = options.forPOST;
- }
- var esb = (typeof options.escapeSquareBrackets == "boolean") ?
- options.escapeSquareBrackets : true;
-
- //Default to the OR operator
- if ((typeof operator === "undefined") || !operator ||
- ((operator != "OR") && (operator != "AND"))) {
- var operator = "OR";
- }
- if ((typeof subtext === "undefined")) {
- var subtext = false;
- }
-
- //Create the value string
- //Trim the spaces off
- if (!Array.isArray(value) && (typeof value === "object") && value.value) {
- value = [value.value.trim()];
- } else if (typeof value == "string") {
- value = [value.trim()];
- }
-
- var valueString = "";
- if (Array.isArray(value)) {
- var model = this;
-
- _.each(value, function(v, i) {
- if ((typeof v == "object") && v.value) {
- v = v.value;
- }
-
- if ((value.length > 1) && (i == 0)) {
- valueString += "("
- }
-
- if (model.needsQuotes(v) || _.contains(fieldNames, "id")) {
- if( forPOST ){
- valueString += '"' + this.escapeSpecialChar(v.trim(), esb) + '"';
- }
- else{
- valueString += '"' + this.escapeSpecialChar(encodeURIComponent(v.trim()), esb) + '"';
- }
- } else if (subtext) {
- if( forPOST ){
- valueString += "*" + this.escapeSpecialChar(v.trim(), esb) + "*";
- }
- else{
- valueString += "*" + this.escapeSpecialChar(encodeURIComponent(v.trim()), esb) + "*";
- }
- } else {
- if (forPOST) {
- valueString += this.escapeSpecialChar(v.trim(), esb);
- }
- else {
- valueString += this.escapeSpecialChar(encodeURIComponent(v.trim()), esb);
- }
- }
-
- if (i < value.length - 1) {
- valueString += " OR ";
- } else if ((i == value.length - 1) && (value.length > 1)) {
- valueString += ")";
- }
-
- }, this);
- } else valueString = value;
-
- query = "(";
-
- //Create the query string
- var last = numFields - 1;
- _.each(fieldNames, function(field, i) {
- query += field + ":" + valueString;
- if (i < last) {
- query += " " + operator + " ";
- }
- });
-
- query += ")";
+ query += ")";
- return query;
- },
+ return query;
+ },
- /**** Provenance-related functions ****/
- // Returns which fields are provenance-related in this model
- // Useful for querying the index and such
- getProvFields: function() {
- var provFields = this.get("provFields");
+ /**** Provenance-related functions ****/
+ // Returns which fields are provenance-related in this model
+ // Useful for querying the index and such
+ getProvFields: function () {
+ var provFields = this.get("provFields");
- if( !provFields.length ){
- var defaultFields = Object.keys(SolrResult.prototype.defaults);
- provFields = _.filter(defaultFields, function(fieldName) {
- if (fieldName.indexOf("prov_") == 0) return true;
- });
+ if (!provFields.length) {
+ var defaultFields = Object.keys(SolrResult.prototype.defaults);
+ provFields = _.filter(defaultFields, function (fieldName) {
+ if (fieldName.indexOf("prov_") == 0) return true;
+ });
- this.set("provFields", provFields);
- }
+ this.set("provFields", provFields);
+ }
- return provFields;
- },
+ return provFields;
+ },
- getProvFlList: function() {
- var provFields = this.getProvFields(),
- provFl = "";
- _.each(provFields, function(provField, i) {
- provFl += provField;
- if (i < provFields.length - 1) provFl += ",";
- });
-
- return provFl;
- },
+ getProvFlList: function () {
+ var provFields = this.getProvFields(),
+ provFl = "";
+ _.each(provFields, function (provField, i) {
+ provFl += provField;
+ if (i < provFields.length - 1) provFl += ",";
+ });
- clear: function() {
- return this.set(_.clone(this.defaults()));
- }
+ return provFl;
+ },
- });
- return Search;
- });
+ clear: function () {
+ return this.set(_.clone(this.defaults()));
+ },
+ },
+ );
+ return Search;
+});
diff --git a/src/js/models/SolrHeader.js b/src/js/models/SolrHeader.js
index 8a2471486..12b84c7b4 100644
--- a/src/js/models/SolrHeader.js
+++ b/src/js/models/SolrHeader.js
@@ -1,12 +1,11 @@
/*global define */
-define(['jquery', 'underscore', 'backbone'],
- function($, _, Backbone) {
- 'use strict';
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ "use strict";
- // SolrHeader Model
- // ------------------
- var SolrHeader = Backbone.Model.extend({
- // attributes: status, QTime, params
- });
- return SolrHeader;
-});
\ No newline at end of file
+ // SolrHeader Model
+ // ------------------
+ var SolrHeader = Backbone.Model.extend({
+ // attributes: status, QTime, params
+ });
+ return SolrHeader;
+});
diff --git a/src/js/models/SolrResult.js b/src/js/models/SolrResult.js
index 3cd2168da..3137e9552 100644
--- a/src/js/models/SolrResult.js
+++ b/src/js/models/SolrResult.js
@@ -1,795 +1,898 @@
/*global define */
-define(['jquery', 'underscore', 'backbone'],
- function($, _, Backbone) {
-
- /**
- * @class SolrResult
- * @classdesc A single result from the Solr search service
- * @classcategory Models
- * @extends Backbone.Model
- */
- var SolrResult = Backbone.Model.extend(
- /** @lends SolrResult.prototype */{
- // This model contains all of the attributes found in the SOLR 'docs' field inside of the SOLR response element
- defaults: {
- abstract: null,
- entityName: null,
- indexed: true,
- archived: false,
- origin: '',
- keywords: '',
- title: '',
- pubDate: '',
- eastBoundCoord: '',
- westBoundCoord: '',
- northBoundCoord: '',
- southBoundCoord: '',
- attributeName: '',
- beginDate: '',
- endDate: '',
- pubDate: '',
- id: '',
- seriesId: null,
- resourceMap: null,
- downloads: null,
- citations: 0,
- selected: false,
- formatId: null,
- formatType: null,
- fileName: null,
- datasource: null,
- rightsHolder: null,
- size: 0,
- type: "",
- url: null,
- obsoletedBy: null,
- geohash_9: null,
- read_count_i: 0,
- reads: 0,
- isDocumentedBy: null,
- isPublic: null,
- isService: false,
- serviceDescription: null,
- serviceTitle: null,
- serviceEndpoint: null,
- serviceOutput: null,
- notFound: false,
- newestVersion: null,
- //@type {string} - The system metadata XML as a string
- systemMetadata: null,
- provSources: [],
- provDerivations: [],
- //Provenance index fields
- prov_generated: null,
- prov_generatedByDataONEDN: null,
- prov_generatedByExecution: null,
- prov_generatedByFoafName: null,
- prov_generatedByOrcid: null,
- prov_generatedByProgram: null,
- prov_generatedByUser: null,
- prov_hasDerivations: null,
- prov_hasSources: null,
- prov_instanceOfClass: null,
- prov_used: null,
- prov_usedByDataONEDN: null,
- prov_usedByExecution: null,
- prov_usedByFoafName: null,
- prov_usedByOrcid: null,
- prov_usedByProgram: null,
- prov_usedByUser: null,
- prov_wasDerivedFrom: null,
- prov_wasExecutedByExecution: null,
- prov_wasExecutedByUser: null,
- prov_wasGeneratedBy: null,
- prov_wasInformedBy: null
- },
-
- initialize: function(){
- this.setURL();
- this.on("change:id", this.setURL);
-
- this.set("type", this.getType());
- this.on("change:read_count_i", function(){ this.set("reads", this.get("read_count_i"))});
- },
-
- type: "SolrResult",
-
- // Toggle the `selected` state of the result
- toggle: function () {
- this.selected = !this.get('selected');
- },
-
- /**
- * Returns a plain-english version of the general format - either image, program, metadata, PDF, annotation or data
- * @return {string}
- */
- getType: function(){
- //The list of formatIds that are images
- var imageIds = ["image/gif",
- "image/jp2",
- "image/jpeg",
- "image/png",
- "image/svg xml",
- "image/svg+xml",
- "image/bmp"];
- //The list of formatIds that are images
- var pdfIds = ["application/pdf"];
- var annotationIds = ["http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html"];
- var collectionIds = ["https://purl.dataone.org/collections-1.0.0",
- "https://purl.dataone.org/collections-1.1.0"];
- var portalIds = ["https://purl.dataone.org/portals-1.0.0",
- "https://purl.dataone.org/portals-1.1.0"];
-
- //Determine the type via provONE
- var instanceOfClass = this.get("prov_instanceOfClass");
- if(typeof instanceOfClass !== "undefined"){
- var programClass = _.filter(instanceOfClass, function(className){
- return (className.indexOf("#Program") > -1);
- });
- if((typeof programClass !== "undefined") && programClass.length)
- return "program";
- }
- else{
- if(this.get("prov_generated") || this.get("prov_used"))
- return "program";
- }
-
- //Determine the type via file format
- if(_.contains(collectionIds, this.get("formatId"))) return "collection";
- if(_.contains(portalIds, this.get("formatId"))) return "portal";
- if(this.get("formatType") == "METADATA") return "metadata";
- if(_.contains(imageIds, this.get("formatId"))) return "image";
- if(_.contains(pdfIds, this.get("formatId"))) return "PDF";
- if(_.contains(annotationIds, this.get("formatId"))) return "annotation";
-
- else return "data";
- },
-
- //Returns a plain-english version of the specific format ID (for selected ids)
- getFormat: function(){
- var formatMap = {
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" : "Microsoft Excel OpenXML",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document" : "Microsoft Word OpenXML",
- "application/vnd.ms-excel.sheet.binary.macroEnabled.12" : "Microsoft Office Excel 2007 binary workbooks",
- "application/vnd.openxmlformats-officedocument.presentationml.presentation" : "Microsoft Office OpenXML Presentation",
- "application/vnd.ms-excel" : "Microsoft Excel",
- "application/msword" : "Microsoft Word",
- "application/vnd.ms-powerpoint" : "Microsoft Powerpoint",
- "text/html" : "HTML",
- "text/plain": "plain text (.txt)",
- "video/avi" : "Microsoft AVI file",
- "video/x-ms-wmv" : "Windows Media Video (.wmv)",
- "audio/x-ms-wma" : "Windows Media Audio (.wma)",
- "application/vnd.google-earth.kml xml" : "Google Earth Keyhole Markup Language (KML)",
- "http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html" : "annotation",
- "application/mathematica" : "Mathematica Notebook",
- "application/postscript" : "Postscript",
- "application/rtf" : "Rich Text Format (RTF)",
- "application/xml" : "XML Application",
- "text/xml" : "XML",
- "application/x-fasta" : "FASTA sequence file",
- "nexus/1997" : "NEXUS File Format for Systematic Information",
- "anvl/erc-v02" : "Kernel Metadata and Electronic Resource Citations (ERCs), 2010.05.13",
- "http://purl.org/dryad/terms/" : "Dryad Metadata Application Profile Version 3.0",
- "http://datadryad.org/profile/v3.1" : "Dryad Metadata Application Profile Version 3.1",
- "application/pdf" : "PDF",
- "application/zip" : "ZIP file",
- "http://www.w3.org/TR/rdf-syntax-grammar" : "RDF/XML",
- "http://www.w3.org/TR/rdfa-syntax" : "RDFa",
- "application/rdf xml" : "RDF",
- "text/turtle" : "TURTLE",
- "text/n3" : "N3",
- "application/x-gzip" : "GZIP Format",
- "application/x-python" : "Python script",
- "http://www.w3.org/2005/Atom" : "ATOM-1.0",
- "application/octet-stream" : "octet stream (application file)",
- "http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd" : "Darwin Core, v2.0",
- "http://rs.tdwg.org/dwc/xsd/simpledarwincore/" : "Simple Darwin Core",
- "eml://ecoinformatics.org/eml-2.1.0" : "EML v2.1.0",
- "eml://ecoinformatics.org/eml-2.1.1" : "EML v2.1.1",
- "eml://ecoinformatics.org/eml-2.0.1" : "EML v2.0.1",
- "eml://ecoinformatics.org/eml-2.0.0" : "EML v2.0.0",
- "https://eml.ecoinformatics.org/eml-2.2.0" : "EML v2.2.0",
-
- }
-
- return formatMap[this.get("formatId")] || this.get("formatId");
- },
-
- setURL: function(){
- if(MetacatUI.appModel.get("objectServiceUrl"))
- this.set("url", MetacatUI.appModel.get("objectServiceUrl") + encodeURIComponent(this.get("id")));
- else if(MetacatUI.appModel.get("resolveServiceUrl"))
- this.set("url", MetacatUI.appModel.get("resolveServiceUrl") + encodeURIComponent(this.get("id")));
- },
-
- /**
- * Checks if the pid or sid or given string is a DOI
- *
- * @param {string} customString - Optional. An identifier string to check instead of the id and seriesId attributes on the model
- * @returns {boolean} True if it is a DOI
- */
- isDOI: function (customString) {
- return MetacatUI.appModel.isDOI(customString) ||
- MetacatUI.appModel.isDOI(this.get("id")) ||
- MetacatUI.appModel.isDOI(this.get("seriesId"));
- },
-
- /*
- * Checks if the currently-logged-in user is authorized to change
- * permissions (or other action if set as parameter) on this doc
- * @param {string} [action=changePermission] - The action (read, write, or changePermission) to check
- * if the current user has authorization to perform. By default checks for the highest level of permission.
- */
- checkAuthority: function(action = "changePermission"){
- var authServiceUrl = MetacatUI.appModel.get('authServiceUrl');
- if(!authServiceUrl) return false;
-
- var model = this;
-
- var requestSettings = {
- url: authServiceUrl + encodeURIComponent(this.get("id")) + "?action=" + action,
- type: "GET",
- success: function(data, textStatus, xhr) {
- model.set("isAuthorized_" + action, true);
- model.set("isAuthorized", true);
- model.trigger("change:isAuthorized");
- },
- error: function(xhr, textStatus, errorThrown) {
- model.set("isAuthorized_" + action, false);
- model.set("isAuthorized", false);
- }
- }
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- /*
- * This method will download this object while sending the user's auth token in the request.
- */
- downloadWithCredentials: function(){
- //if(this.get("isPublic")) return;
-
- //Get info about this object
- var url = this.get("url"),
- model = this;
-
- //Create an XHR
- var xhr = new XMLHttpRequest();
-
- //Open and send the request with the user's auth token
- xhr.open('GET', url);
-
- if(MetacatUI.appUserModel.get("loggedIn"))
- xhr.withCredentials = true;
-
- //When the XHR is ready, create a link with the raw data (Blob) and click the link to download
- xhr.onload = function(){
-
- if( this.status == 404 ){
- this.onerror.call(this);
- return;
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ /**
+ * @class SolrResult
+ * @classdesc A single result from the Solr search service
+ * @classcategory Models
+ * @extends Backbone.Model
+ */
+ var SolrResult = Backbone.Model.extend(
+ /** @lends SolrResult.prototype */ {
+ // This model contains all of the attributes found in the SOLR 'docs' field inside of the SOLR response element
+ defaults: {
+ abstract: null,
+ entityName: null,
+ indexed: true,
+ archived: false,
+ origin: "",
+ keywords: "",
+ title: "",
+ pubDate: "",
+ eastBoundCoord: "",
+ westBoundCoord: "",
+ northBoundCoord: "",
+ southBoundCoord: "",
+ attributeName: "",
+ beginDate: "",
+ endDate: "",
+ pubDate: "",
+ id: "",
+ seriesId: null,
+ resourceMap: null,
+ downloads: null,
+ citations: 0,
+ selected: false,
+ formatId: null,
+ formatType: null,
+ fileName: null,
+ datasource: null,
+ rightsHolder: null,
+ size: 0,
+ type: "",
+ url: null,
+ obsoletedBy: null,
+ geohash_9: null,
+ read_count_i: 0,
+ reads: 0,
+ isDocumentedBy: null,
+ isPublic: null,
+ isService: false,
+ serviceDescription: null,
+ serviceTitle: null,
+ serviceEndpoint: null,
+ serviceOutput: null,
+ notFound: false,
+ newestVersion: null,
+ //@type {string} - The system metadata XML as a string
+ systemMetadata: null,
+ provSources: [],
+ provDerivations: [],
+ //Provenance index fields
+ prov_generated: null,
+ prov_generatedByDataONEDN: null,
+ prov_generatedByExecution: null,
+ prov_generatedByFoafName: null,
+ prov_generatedByOrcid: null,
+ prov_generatedByProgram: null,
+ prov_generatedByUser: null,
+ prov_hasDerivations: null,
+ prov_hasSources: null,
+ prov_instanceOfClass: null,
+ prov_used: null,
+ prov_usedByDataONEDN: null,
+ prov_usedByExecution: null,
+ prov_usedByFoafName: null,
+ prov_usedByOrcid: null,
+ prov_usedByProgram: null,
+ prov_usedByUser: null,
+ prov_wasDerivedFrom: null,
+ prov_wasExecutedByExecution: null,
+ prov_wasExecutedByUser: null,
+ prov_wasGeneratedBy: null,
+ prov_wasInformedBy: null,
+ },
+
+ initialize: function () {
+ this.setURL();
+ this.on("change:id", this.setURL);
+
+ this.set("type", this.getType());
+ this.on("change:read_count_i", function () {
+ this.set("reads", this.get("read_count_i"));
+ });
+ },
+
+ type: "SolrResult",
+
+ // Toggle the `selected` state of the result
+ toggle: function () {
+ this.selected = !this.get("selected");
+ },
+
+ /**
+ * Returns a plain-english version of the general format - either image, program, metadata, PDF, annotation or data
+ * @return {string}
+ */
+ getType: function () {
+ //The list of formatIds that are images
+ var imageIds = [
+ "image/gif",
+ "image/jp2",
+ "image/jpeg",
+ "image/png",
+ "image/svg xml",
+ "image/svg+xml",
+ "image/bmp",
+ ];
+ //The list of formatIds that are images
+ var pdfIds = ["application/pdf"];
+ var annotationIds = [
+ "http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html",
+ ];
+ var collectionIds = [
+ "https://purl.dataone.org/collections-1.0.0",
+ "https://purl.dataone.org/collections-1.1.0",
+ ];
+ var portalIds = [
+ "https://purl.dataone.org/portals-1.0.0",
+ "https://purl.dataone.org/portals-1.1.0",
+ ];
+
+ //Determine the type via provONE
+ var instanceOfClass = this.get("prov_instanceOfClass");
+ if (typeof instanceOfClass !== "undefined") {
+ var programClass = _.filter(instanceOfClass, function (className) {
+ return className.indexOf("#Program") > -1;
+ });
+ if (typeof programClass !== "undefined" && programClass.length)
+ return "program";
+ } else {
+ if (this.get("prov_generated") || this.get("prov_used"))
+ return "program";
}
- //Get the file name to save this file as
- var filename = xhr.getResponseHeader('Content-Disposition');
-
- if(!filename){
- filename = model.get("fileName") || model.get("title") || model.get("id") || "download";
- }
- else
- filename = filename.substring(filename.indexOf("filename=")+9).replace(/"/g, "");
-
- //Replace any whitespaces
- filename = filename.trim().replace(/ /g, "_");
-
- //For IE, we need to use the navigator API
- if (navigator && navigator.msSaveOrOpenBlob) {
- navigator.msSaveOrOpenBlob(xhr.response, filename);
- }
- //Other browsers can download it via a link
- else{
- var a = document.createElement('a');
- a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob
-
- // Set the file name.
- a.download = filename
-
- a.style.display = 'none';
- document.body.appendChild(a);
- a.click();
- a.remove();
- }
-
- model.trigger("downloadComplete");
-
- // Track this event
- MetacatUI.analytics?.trackEvent(
- "download",
- "Download DataONEObject",
- model.get("id")
- );
- };
-
- xhr.onerror = function(e){
- model.trigger("downloadError");
-
- // Track the error
- MetacatUI.analytics?.trackException(
- `Download DataONEObject error: ${e || ""}`, model.get("id"), true
- );
- };
-
- xhr.onprogress = function(e){
- if (e.lengthComputable){
- var percent = (e.loaded / e.total) * 100;
- model.set("downloadPercent", percent);
- }
- };
-
- xhr.responseType = "blob";
-
- if(MetacatUI.appUserModel.get("loggedIn"))
- xhr.setRequestHeader("Authorization", "Bearer " + MetacatUI.appUserModel.get("token"));
-
- xhr.send();
- },
-
- getInfo: function(fields){
- var model = this;
-
- if(!fields)
- var fields = "abstract,id,seriesId,fileName,resourceMap,formatType,formatId,obsoletedBy,isDocumentedBy,documents,title,origin,keywords,attributeName,pubDate,eastBoundCoord,westBoundCoord,northBoundCoord,southBoundCoord,beginDate,endDate,dateUploaded,archived,datasource,replicaMN,isAuthorized,isPublic,size,read_count_i,isService,serviceTitle,serviceEndpoint,serviceOutput,serviceDescription,serviceType,project,dateModified";
-
- var escapeSpecialChar = MetacatUI.appSearchModel.escapeSpecialChar;
-
- var query = "q=";
-
- //If there is no seriesId set, then search for pid or sid
- if(!this.get("seriesId"))
- query += '(id:"' + escapeSpecialChar(encodeURIComponent(this.get("id"))) + '" OR seriesId:"' + escapeSpecialChar(encodeURIComponent(this.get("id"))) + '")';
- //If a seriesId is specified, then search for that
- else if(this.get("seriesId") && (this.get("id").length > 0))
- query += '(seriesId:"' + escapeSpecialChar(encodeURIComponent(this.get("seriesId"))) + '" AND id:"' + escapeSpecialChar(encodeURIComponent(this.get("id"))) + '")';
- //If only a seriesId is specified, then just search for the most recent version
- else if(this.get("seriesId") && !this.get("id"))
- query += 'seriesId:"' + escapeSpecialChar(encodeURIComponent(this.get("id"))) + '" -obsoletedBy:*';
-
- query += "&fl=" + fields + //Specify the fields to return
- "&wt=json&rows=1000" + //Get the results in JSON format and get 1000 rows
- "&archived=archived:*"; //Get archived or unarchived content
-
- var requestSettings = {
- url: MetacatUI.appModel.get("queryServiceUrl") + query,
- type: "GET",
- success: function(data, response, xhr){
- //If the Solr response was not as expected, trigger and error and exit
- if( !data || typeof data.response == "undefined" ){
- model.set("indexed", false);
- model.trigger("getInfoError");
+ //Determine the type via file format
+ if (_.contains(collectionIds, this.get("formatId")))
+ return "collection";
+ if (_.contains(portalIds, this.get("formatId"))) return "portal";
+ if (this.get("formatType") == "METADATA") return "metadata";
+ if (_.contains(imageIds, this.get("formatId"))) return "image";
+ if (_.contains(pdfIds, this.get("formatId"))) return "PDF";
+ if (_.contains(annotationIds, this.get("formatId")))
+ return "annotation";
+ else return "data";
+ },
+
+ //Returns a plain-english version of the specific format ID (for selected ids)
+ getFormat: function () {
+ var formatMap = {
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
+ "Microsoft Excel OpenXML",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
+ "Microsoft Word OpenXML",
+ "application/vnd.ms-excel.sheet.binary.macroEnabled.12":
+ "Microsoft Office Excel 2007 binary workbooks",
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation":
+ "Microsoft Office OpenXML Presentation",
+ "application/vnd.ms-excel": "Microsoft Excel",
+ "application/msword": "Microsoft Word",
+ "application/vnd.ms-powerpoint": "Microsoft Powerpoint",
+ "text/html": "HTML",
+ "text/plain": "plain text (.txt)",
+ "video/avi": "Microsoft AVI file",
+ "video/x-ms-wmv": "Windows Media Video (.wmv)",
+ "audio/x-ms-wma": "Windows Media Audio (.wma)",
+ "application/vnd.google-earth.kml xml":
+ "Google Earth Keyhole Markup Language (KML)",
+ "http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html":
+ "annotation",
+ "application/mathematica": "Mathematica Notebook",
+ "application/postscript": "Postscript",
+ "application/rtf": "Rich Text Format (RTF)",
+ "application/xml": "XML Application",
+ "text/xml": "XML",
+ "application/x-fasta": "FASTA sequence file",
+ "nexus/1997": "NEXUS File Format for Systematic Information",
+ "anvl/erc-v02":
+ "Kernel Metadata and Electronic Resource Citations (ERCs), 2010.05.13",
+ "http://purl.org/dryad/terms/":
+ "Dryad Metadata Application Profile Version 3.0",
+ "http://datadryad.org/profile/v3.1":
+ "Dryad Metadata Application Profile Version 3.1",
+ "application/pdf": "PDF",
+ "application/zip": "ZIP file",
+ "http://www.w3.org/TR/rdf-syntax-grammar": "RDF/XML",
+ "http://www.w3.org/TR/rdfa-syntax": "RDFa",
+ "application/rdf xml": "RDF",
+ "text/turtle": "TURTLE",
+ "text/n3": "N3",
+ "application/x-gzip": "GZIP Format",
+ "application/x-python": "Python script",
+ "http://www.w3.org/2005/Atom": "ATOM-1.0",
+ "application/octet-stream": "octet stream (application file)",
+ "http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd":
+ "Darwin Core, v2.0",
+ "http://rs.tdwg.org/dwc/xsd/simpledarwincore/": "Simple Darwin Core",
+ "eml://ecoinformatics.org/eml-2.1.0": "EML v2.1.0",
+ "eml://ecoinformatics.org/eml-2.1.1": "EML v2.1.1",
+ "eml://ecoinformatics.org/eml-2.0.1": "EML v2.0.1",
+ "eml://ecoinformatics.org/eml-2.0.0": "EML v2.0.0",
+ "https://eml.ecoinformatics.org/eml-2.2.0": "EML v2.2.0",
+ };
+
+ return formatMap[this.get("formatId")] || this.get("formatId");
+ },
+
+ setURL: function () {
+ if (MetacatUI.appModel.get("objectServiceUrl"))
+ this.set(
+ "url",
+ MetacatUI.appModel.get("objectServiceUrl") +
+ encodeURIComponent(this.get("id")),
+ );
+ else if (MetacatUI.appModel.get("resolveServiceUrl"))
+ this.set(
+ "url",
+ MetacatUI.appModel.get("resolveServiceUrl") +
+ encodeURIComponent(this.get("id")),
+ );
+ },
+
+ /**
+ * Checks if the pid or sid or given string is a DOI
+ *
+ * @param {string} customString - Optional. An identifier string to check instead of the id and seriesId attributes on the model
+ * @returns {boolean} True if it is a DOI
+ */
+ isDOI: function (customString) {
+ return (
+ MetacatUI.appModel.isDOI(customString) ||
+ MetacatUI.appModel.isDOI(this.get("id")) ||
+ MetacatUI.appModel.isDOI(this.get("seriesId"))
+ );
+ },
+
+ /*
+ * Checks if the currently-logged-in user is authorized to change
+ * permissions (or other action if set as parameter) on this doc
+ * @param {string} [action=changePermission] - The action (read, write, or changePermission) to check
+ * if the current user has authorization to perform. By default checks for the highest level of permission.
+ */
+ checkAuthority: function (action = "changePermission") {
+ var authServiceUrl = MetacatUI.appModel.get("authServiceUrl");
+ if (!authServiceUrl) return false;
+
+ var model = this;
+
+ var requestSettings = {
+ url:
+ authServiceUrl +
+ encodeURIComponent(this.get("id")) +
+ "?action=" +
+ action,
+ type: "GET",
+ success: function (data, textStatus, xhr) {
+ model.set("isAuthorized_" + action, true);
+ model.set("isAuthorized", true);
+ model.trigger("change:isAuthorized");
+ },
+ error: function (xhr, textStatus, errorThrown) {
+ model.set("isAuthorized_" + action, false);
+ model.set("isAuthorized", false);
+ },
+ };
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ /*
+ * This method will download this object while sending the user's auth token in the request.
+ */
+ downloadWithCredentials: function () {
+ //if(this.get("isPublic")) return;
+
+ //Get info about this object
+ var url = this.get("url"),
+ model = this;
+
+ //Create an XHR
+ var xhr = new XMLHttpRequest();
+
+ //Open and send the request with the user's auth token
+ xhr.open("GET", url);
+
+ if (MetacatUI.appUserModel.get("loggedIn")) xhr.withCredentials = true;
+
+ //When the XHR is ready, create a link with the raw data (Blob) and click the link to download
+ xhr.onload = function () {
+ if (this.status == 404) {
+ this.onerror.call(this);
return;
}
- var docs = data.response.docs;
-
- if(docs.length == 1){
- docs[0].resourceMap = model.parseResourceMapField(docs[0]);
- model.set(docs[0]);
- model.trigger("sync");
- }
- //If we searched by seriesId, then let's find the most recent version in the series
- else if(docs.length > 1){
- //Filter out docs that are obsoleted
- var mostRecent = _.reject(docs, function(doc){
- return (typeof doc.obsoletedBy !== "undefined");
- });
-
- //If there is only one doc that is not obsoleted (the most recent), then
- // set this doc's values on this model
- if(mostRecent.length == 1){
- mostRecent[0].resourceMap = model.parseResourceMapField(mostRecent[0]);
- model.set(mostRecent[0]);
- model.trigger("sync");
- }
- else{
- //If there are multiple docs without an obsoletedBy statement, then
- // retreive the head of the series via the system metadata
- var sysMetaRequestSettings = {
- url: MetacatUI.appModel.get("metaServiceUrl") + encodeURIComponent(docs[0].seriesId),
- type: "GET",
- success: function(sysMetaData){
- //Get the identifier node from the system metadata
- var seriesHeadID = $(sysMetaData).find("identifier").text();
- //Get the doc from the Solr results with that identifier
- var seriesHead = _.findWhere(docs, { id: seriesHeadID });
-
- //If there is a doc in the Solr results list that matches the series head id
- if(seriesHead){
- seriesHead.resourceMap = model.parseResourceMapField(seriesHead);
- //Set those values on this model
- model.set(seriesHead);
- }
- //Otherwise, just fall back on the first doc in the list
- else if( mostRecent.length ){
- mostRecent[0].resourceMap = model.parseResourceMapField(mostRecent[0]);
- model.set(mostRecent[0]);
- }
- else {
- docs[0].resourceMap = model.parseResourceMapField(docs[0]);
- model.set(docs[0]);
- }
-
- model.trigger("sync");
-
- },
- error: function(xhr, textStatus, errorThrown){
-
- // Fall back on the first doc in the list
- if( mostRecent.length ){
- model.set(mostRecent[0]);
- }
- else {
- model.set(docs[0]);
- }
-
- model.trigger("sync");
-
- }
- };
-
- $.ajax(_.extend(sysMetaRequestSettings, MetacatUI.appUserModel.createAjaxSettings()));
-
- }
-
- }
- else{
- model.set("indexed", false);
- //Try getting the system metadata as a backup
- model.getSysMeta();
- }
- },
- error: function(xhr, textStatus, errorThrown){
- model.set("indexed", false);
- model.trigger("getInfoError");
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- getCitationInfo: function(){
- this.getInfo("id,seriesId,origin,pubDate,dateUploaded,title,datasource,project");
- },
-
- /*
- * Get the system metadata for this object
- */
- getSysMeta: function(){
- var url = MetacatUI.appModel.get("metaServiceUrl") + encodeURIComponent(this.get("id")),
- model = this;
-
- var requestSettings = {
- url: url,
- type: "GET",
- dataType: "text",
- success: function(data, response, xhr){
-
- if( data && data.length ){
- model.set("systemMetadata", data);
- }
-
- //Check if this is archvied
- var archived = ($(data).find("archived").text() == "true");
- model.set("archived", archived);
-
- //Get the file size
- model.set("size", ($(data).find("size").text() || ""));
-
- //Get the entity name
- model.set("filename", ($(data).find("filename").text() || ""));
-
- //Check if this is a metadata doc
- var formatId = $(data).find("formatid").text() || "",
- formatType;
- model.set("formatId", formatId);
- if((formatId.indexOf("ecoinformatics.org") > -1) ||
- (formatId.indexOf("FGDC") > -1) ||
- (formatId.indexOf("INCITS") > -1) ||
- (formatId.indexOf("namespaces/netcdf") > -1) ||
- (formatId.indexOf("waterML") > -1) ||
- (formatId.indexOf("darwin") > -1) ||
- (formatId.indexOf("dryad") > -1) ||
- (formatId.indexOf("http://www.loc.gov/METS") > -1) ||
- (formatId.indexOf("ddi:codebook:2_5") > -1) ||
- (formatId.indexOf("http://www.icpsr.umich.edu/DDI") > -1) ||
- (formatId.indexOf("http://purl.org/ornl/schema/mercury/terms/v1.0") > -1) ||
- (formatId.indexOf("datacite") > -1) ||
- (formatId.indexOf("isotc211") > -1) ||
- (formatId.indexOf("metadata") > -1))
- model.set("formatType", "METADATA");
-
- //Trigger the sync event so the app knows we found the model info
- model.trigger("sync");
- },
- error: function(response){
-
- //When the user is unauthorized to access this object, trigger a 401 error
- if( response.status == 401 ){
- model.set("notFound", true);
- model.trigger("401");
- }
- //When the object doesn't exist, trigger a 404 error
- else if( response.status == 404 ){
- model.set("notFound", true);
- model.trigger("404");
- }
- //Other error codes trigger a generic error
- else{
- model.trigger("error");
- }
-
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- //Transgresses the obsolence chain until it finds the newest version that this user is authorized to read
- findLatestVersion: function(newestVersion, possiblyNewer) {
- // Make sure we have the /meta service configured
- if(!MetacatUI.appModel.get('metaServiceUrl')) return;
-
- //If no pid was supplied, use this model's id
- if(!newestVersion){
- var newestVersion = this.get("id");
- var possiblyNewer = this.get("obsoletedBy");
- }
-
- //If this isn't obsoleted by anything, then there is no newer version
- if(!possiblyNewer){
- this.set("newestVersion", newestVersion);
- return;
- }
-
- var model = this;
-
- //Get the system metadata for the possibly newer version
- var requestSettings = {
- url: MetacatUI.appModel.get('metaServiceUrl') + encodeURIComponent(possiblyNewer),
- type: "GET",
- success: function(data) {
-
- // the response may have an obsoletedBy element
- var obsoletedBy = $(data).find("obsoletedBy").text();
-
- //If there is an even newer version, then get it and rerun this function
- if(obsoletedBy)
- model.findLatestVersion(possiblyNewer, obsoletedBy);
- //If there isn't a newer version, then this is it
- else
- model.set("newestVersion", possiblyNewer);
-
- },
- error: function(xhr){
- //If this newer version isn't found or accessible, then save the last
- // accessible id as the newest version
- if(xhr.status == 401 || xhr.status == 404 || xhr.status == "401" ||
- xhr.status == "404"){
- model.set("newestVersion", newestVersion);
+ //Get the file name to save this file as
+ var filename = xhr.getResponseHeader("Content-Disposition");
+
+ if (!filename) {
+ filename =
+ model.get("fileName") ||
+ model.get("title") ||
+ model.get("id") ||
+ "download";
+ } else
+ filename = filename
+ .substring(filename.indexOf("filename=") + 9)
+ .replace(/"/g, "");
+
+ //Replace any whitespaces
+ filename = filename.trim().replace(/ /g, "_");
+
+ //For IE, we need to use the navigator API
+ if (navigator && navigator.msSaveOrOpenBlob) {
+ navigator.msSaveOrOpenBlob(xhr.response, filename);
}
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
-
- },
-
- /**** Provenance-related functions ****/
- /*
- * Returns true if this provenance field points to a source of this data or metadata object
- */
- isSourceField: function(field){
- if((typeof field == "undefined") || !field) return false;
- if(!_.contains(MetacatUI.appSearchModel.getProvFields(), field)) return false;
-
- if(field == "prov_generatedByExecution" ||
- field == "prov_generatedByProgram" ||
- field == "prov_used" ||
- field == "prov_wasDerivedFrom" ||
- field == "prov_wasInformedBy")
- return true;
- else
- return false;
- },
-
- /*
- * Returns true if this provenance field points to a derivation of this data or metadata object
- */
- isDerivationField: function(field){
- if((typeof field == "undefined") || !field) return false;
- if(!_.contains(MetacatUI.appSearchModel.getProvFields(), field)) return false;
-
- if(field == "prov_usedByExecution" ||
- field == "prov_usedByProgram" ||
- field == "prov_hasDerivations" ||
- field == "prov_generated")
- return true;
- else
- return false;
- },
-
- /*
- * Returns true if this SolrResult has a provenance trace (i.e. has either sources or derivations)
- */
- hasProvTrace: function(){
-
- if(this.get("formatType") == "METADATA"){
- if(this.get("prov_hasSources") || this.get("prov_hasDerivations"))
- return true;
- }
-
- var fieldNames = MetacatUI.appSearchModel.getProvFields(),
- currentField = "";
-
- for(var i=0; i < fieldNames.length; i++){
- currentField = fieldNames[i];
- if(this.has(currentField))
- return true;
- }
-
- return false;
- },
-
- /*
- * Returns an array of all the IDs of objects that are sources of this object
- */
- getSources: function(){
- var sources = new Array(),
- model = this,
- //Get the prov fields but leave out references to executions which are not used in the UI yet
- fields = _.reject(MetacatUI.appSearchModel.getProvFields(), function(f){ return f.indexOf("xecution") > -1 }); //Leave out the first e in execution so we don't have to worry about case sensitivity
-
- _.each(fields, function(provField, i){
- if(model.isSourceField(provField) && model.has(provField))
- sources.push(model.get(provField));
- });
-
- return _.uniq(_.flatten(sources));
- },
-
- /*
- * Returns an array of all the IDs of objects that are derivations of this object
- */
- getDerivations: function(){
- var derivations = new Array(),
- model = this,
- //Get the prov fields but leave out references to executions which are not used in the UI yet
- fields = _.reject(MetacatUI.appSearchModel.getProvFields(), function(f){ return f.indexOf("xecution") > -1 }); //Leave out the first e in execution so we don't have to worry about case sensitivity
-
- _.each(fields, function(provField, i){
- if(model.isDerivationField(provField) && model.has(provField))
- derivations.push(model.get(provField));
- });
-
- return _.uniq(_.flatten(derivations));
- },
-
- getInputs: function(){
- return this.get("prov_used");
- },
-
- getOutputs: function(){
- return this.get("prov_generated");
- },
-
- /*
- * Uses the app configuration to check if this model's metrics should be hidden in the display
- *
- * @return {boolean}
- */
- hideMetrics: function(){
-
- //If the AppModel is configured with cases of where to hide metrics,
- if( typeof MetacatUI.appModel.get("hideMetricsWhen") == "object" && MetacatUI.appModel.get("hideMetricsWhen") ){
-
- //Check for at least one match
- return _.some( MetacatUI.appModel.get("hideMetricsWhen"), function(value, modelProperty){
-
- //Get the value of this property from this model
- var modelValue = this.get(modelProperty);
-
- //Check for the presence of this model's value in the AppModel value
- if( Array.isArray(value) && typeof modelValue == "string" ){
- return _.contains(value, modelValue)
+ //Other browsers can download it via a link
+ else {
+ var a = document.createElement("a");
+ a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob
+
+ // Set the file name.
+ a.download = filename;
+
+ a.style.display = "none";
+ document.body.appendChild(a);
+ a.click();
+ a.remove();
}
- //Check for the presence of the AppModel's value in this model's value
- else if( typeof value == "string" && Array.isArray(modelValue) ){
- return _.contains(modelValue, value);
- }
- //Check for overlap of two arrays
- else if( Array.isArray(value) && Array.isArray(modelValue) ){
- return ( _.intersection(value, modelValue).length > 0 );
- }
- //If the AppModel value is a function, execute it
- else if( typeof value == "function" ){
- return value(modelValue);
- }
- //Otherwise, just check for equality
- else{
- return value === modelValue;
- }
-
- }, this);
- }
- else {
- return false;
- }
- },
-
- /**
- * Creates a URL for viewing more information about this metadata
- * @return {string}
- */
- createViewURL: function(){
- return (this.getType() == "portal" || this.getType() == "collection") ?
- MetacatUI.root + "/" + MetacatUI.appModel.get("portalTermPlural") + "/" + encodeURIComponent((this.get("label") || this.get("seriesId") || this.get("id"))) :
- MetacatUI.root + "/view/" + encodeURIComponent((this.get("seriesId") || this.get("id")));
- },
- parseResourceMapField: function(json){
- if( typeof json.resourceMap == "string" ){
- return json.resourceMap.trim();
- }
- else if( Array.isArray(json.resourceMap) ){
- let newResourceMapIds = [];
- _.each(json.resourceMap, function(rMapId){
- if( typeof rMapId == "string" ){
- newResourceMapIds.push(rMapId.trim());
+ model.trigger("downloadComplete");
+
+ // Track this event
+ MetacatUI.analytics?.trackEvent(
+ "download",
+ "Download DataONEObject",
+ model.get("id"),
+ );
+ };
+
+ xhr.onerror = function (e) {
+ model.trigger("downloadError");
+
+ // Track the error
+ MetacatUI.analytics?.trackException(
+ `Download DataONEObject error: ${e || ""}`,
+ model.get("id"),
+ true,
+ );
+ };
+
+ xhr.onprogress = function (e) {
+ if (e.lengthComputable) {
+ var percent = (e.loaded / e.total) * 100;
+ model.set("downloadPercent", percent);
}
- });
- return newResourceMapIds;
- }
-
- //If nothing works so far, return an empty array
- return [];
- },
-
- /****************************/
-
- /**
- * Convert number of bytes into human readable format
- *
- * @param integer bytes Number of bytes to convert
- * @param integer precision Number of digits after the decimal separator
- * @return string
- */
- bytesToSize: function(bytes, precision){
- var kibibyte = 1024;
- var mebibyte = kibibyte * 1024;
- var gibibyte = mebibyte * 1024;
- var tebibyte = gibibyte * 1024;
+ };
+
+ xhr.responseType = "blob";
+
+ if (MetacatUI.appUserModel.get("loggedIn"))
+ xhr.setRequestHeader(
+ "Authorization",
+ "Bearer " + MetacatUI.appUserModel.get("token"),
+ );
+
+ xhr.send();
+ },
+
+ getInfo: function (fields) {
+ var model = this;
+
+ if (!fields)
+ var fields =
+ "abstract,id,seriesId,fileName,resourceMap,formatType,formatId,obsoletedBy,isDocumentedBy,documents,title,origin,keywords,attributeName,pubDate,eastBoundCoord,westBoundCoord,northBoundCoord,southBoundCoord,beginDate,endDate,dateUploaded,archived,datasource,replicaMN,isAuthorized,isPublic,size,read_count_i,isService,serviceTitle,serviceEndpoint,serviceOutput,serviceDescription,serviceType,project,dateModified";
+
+ var escapeSpecialChar = MetacatUI.appSearchModel.escapeSpecialChar;
+
+ var query = "q=";
+
+ //If there is no seriesId set, then search for pid or sid
+ if (!this.get("seriesId"))
+ query +=
+ '(id:"' +
+ escapeSpecialChar(encodeURIComponent(this.get("id"))) +
+ '" OR seriesId:"' +
+ escapeSpecialChar(encodeURIComponent(this.get("id"))) +
+ '")';
+ //If a seriesId is specified, then search for that
+ else if (this.get("seriesId") && this.get("id").length > 0)
+ query +=
+ '(seriesId:"' +
+ escapeSpecialChar(encodeURIComponent(this.get("seriesId"))) +
+ '" AND id:"' +
+ escapeSpecialChar(encodeURIComponent(this.get("id"))) +
+ '")';
+ //If only a seriesId is specified, then just search for the most recent version
+ else if (this.get("seriesId") && !this.get("id"))
+ query +=
+ 'seriesId:"' +
+ escapeSpecialChar(encodeURIComponent(this.get("id"))) +
+ '" -obsoletedBy:*';
+
+ query +=
+ "&fl=" +
+ fields + //Specify the fields to return
+ "&wt=json&rows=1000" + //Get the results in JSON format and get 1000 rows
+ "&archived=archived:*"; //Get archived or unarchived content
+
+ var requestSettings = {
+ url: MetacatUI.appModel.get("queryServiceUrl") + query,
+ type: "GET",
+ success: function (data, response, xhr) {
+ //If the Solr response was not as expected, trigger and error and exit
+ if (!data || typeof data.response == "undefined") {
+ model.set("indexed", false);
+ model.trigger("getInfoError");
+ return;
+ }
+
+ var docs = data.response.docs;
+
+ if (docs.length == 1) {
+ docs[0].resourceMap = model.parseResourceMapField(docs[0]);
+ model.set(docs[0]);
+ model.trigger("sync");
+ }
+ //If we searched by seriesId, then let's find the most recent version in the series
+ else if (docs.length > 1) {
+ //Filter out docs that are obsoleted
+ var mostRecent = _.reject(docs, function (doc) {
+ return typeof doc.obsoletedBy !== "undefined";
+ });
+
+ //If there is only one doc that is not obsoleted (the most recent), then
+ // set this doc's values on this model
+ if (mostRecent.length == 1) {
+ mostRecent[0].resourceMap = model.parseResourceMapField(
+ mostRecent[0],
+ );
+ model.set(mostRecent[0]);
+ model.trigger("sync");
+ } else {
+ //If there are multiple docs without an obsoletedBy statement, then
+ // retreive the head of the series via the system metadata
+ var sysMetaRequestSettings = {
+ url:
+ MetacatUI.appModel.get("metaServiceUrl") +
+ encodeURIComponent(docs[0].seriesId),
+ type: "GET",
+ success: function (sysMetaData) {
+ //Get the identifier node from the system metadata
+ var seriesHeadID = $(sysMetaData).find("identifier").text();
+ //Get the doc from the Solr results with that identifier
+ var seriesHead = _.findWhere(docs, { id: seriesHeadID });
+
+ //If there is a doc in the Solr results list that matches the series head id
+ if (seriesHead) {
+ seriesHead.resourceMap =
+ model.parseResourceMapField(seriesHead);
+ //Set those values on this model
+ model.set(seriesHead);
+ }
+ //Otherwise, just fall back on the first doc in the list
+ else if (mostRecent.length) {
+ mostRecent[0].resourceMap = model.parseResourceMapField(
+ mostRecent[0],
+ );
+ model.set(mostRecent[0]);
+ } else {
+ docs[0].resourceMap = model.parseResourceMapField(
+ docs[0],
+ );
+ model.set(docs[0]);
+ }
+
+ model.trigger("sync");
+ },
+ error: function (xhr, textStatus, errorThrown) {
+ // Fall back on the first doc in the list
+ if (mostRecent.length) {
+ model.set(mostRecent[0]);
+ } else {
+ model.set(docs[0]);
+ }
+
+ model.trigger("sync");
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ sysMetaRequestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ }
+ } else {
+ model.set("indexed", false);
+ //Try getting the system metadata as a backup
+ model.getSysMeta();
+ }
+ },
+ error: function (xhr, textStatus, errorThrown) {
+ model.set("indexed", false);
+ model.trigger("getInfoError");
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ getCitationInfo: function () {
+ this.getInfo(
+ "id,seriesId,origin,pubDate,dateUploaded,title,datasource,project",
+ );
+ },
+
+ /*
+ * Get the system metadata for this object
+ */
+ getSysMeta: function () {
+ var url =
+ MetacatUI.appModel.get("metaServiceUrl") +
+ encodeURIComponent(this.get("id")),
+ model = this;
+
+ var requestSettings = {
+ url: url,
+ type: "GET",
+ dataType: "text",
+ success: function (data, response, xhr) {
+ if (data && data.length) {
+ model.set("systemMetadata", data);
+ }
+
+ //Check if this is archvied
+ var archived = $(data).find("archived").text() == "true";
+ model.set("archived", archived);
+
+ //Get the file size
+ model.set("size", $(data).find("size").text() || "");
+
+ //Get the entity name
+ model.set("filename", $(data).find("filename").text() || "");
+
+ //Check if this is a metadata doc
+ var formatId = $(data).find("formatid").text() || "",
+ formatType;
+ model.set("formatId", formatId);
+ if (
+ formatId.indexOf("ecoinformatics.org") > -1 ||
+ formatId.indexOf("FGDC") > -1 ||
+ formatId.indexOf("INCITS") > -1 ||
+ formatId.indexOf("namespaces/netcdf") > -1 ||
+ formatId.indexOf("waterML") > -1 ||
+ formatId.indexOf("darwin") > -1 ||
+ formatId.indexOf("dryad") > -1 ||
+ formatId.indexOf("http://www.loc.gov/METS") > -1 ||
+ formatId.indexOf("ddi:codebook:2_5") > -1 ||
+ formatId.indexOf("http://www.icpsr.umich.edu/DDI") > -1 ||
+ formatId.indexOf(
+ "http://purl.org/ornl/schema/mercury/terms/v1.0",
+ ) > -1 ||
+ formatId.indexOf("datacite") > -1 ||
+ formatId.indexOf("isotc211") > -1 ||
+ formatId.indexOf("metadata") > -1
+ )
+ model.set("formatType", "METADATA");
+
+ //Trigger the sync event so the app knows we found the model info
+ model.trigger("sync");
+ },
+ error: function (response) {
+ //When the user is unauthorized to access this object, trigger a 401 error
+ if (response.status == 401) {
+ model.set("notFound", true);
+ model.trigger("401");
+ }
+ //When the object doesn't exist, trigger a 404 error
+ else if (response.status == 404) {
+ model.set("notFound", true);
+ model.trigger("404");
+ }
+ //Other error codes trigger a generic error
+ else {
+ model.trigger("error");
+ }
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ //Transgresses the obsolence chain until it finds the newest version that this user is authorized to read
+ findLatestVersion: function (newestVersion, possiblyNewer) {
+ // Make sure we have the /meta service configured
+ if (!MetacatUI.appModel.get("metaServiceUrl")) return;
+
+ //If no pid was supplied, use this model's id
+ if (!newestVersion) {
+ var newestVersion = this.get("id");
+ var possiblyNewer = this.get("obsoletedBy");
+ }
- if(typeof bytes === "undefined") var bytes = this.get("size");
+ //If this isn't obsoleted by anything, then there is no newer version
+ if (!possiblyNewer) {
+ this.set("newestVersion", newestVersion);
+ return;
+ }
- if ((bytes >= 0) && (bytes < kibibyte)) {
- return bytes + ' B';
+ var model = this;
+
+ //Get the system metadata for the possibly newer version
+ var requestSettings = {
+ url:
+ MetacatUI.appModel.get("metaServiceUrl") +
+ encodeURIComponent(possiblyNewer),
+ type: "GET",
+ success: function (data) {
+ // the response may have an obsoletedBy element
+ var obsoletedBy = $(data).find("obsoletedBy").text();
+
+ //If there is an even newer version, then get it and rerun this function
+ if (obsoletedBy)
+ model.findLatestVersion(possiblyNewer, obsoletedBy);
+ //If there isn't a newer version, then this is it
+ else model.set("newestVersion", possiblyNewer);
+ },
+ error: function (xhr) {
+ //If this newer version isn't found or accessible, then save the last
+ // accessible id as the newest version
+ if (
+ xhr.status == 401 ||
+ xhr.status == 404 ||
+ xhr.status == "401" ||
+ xhr.status == "404"
+ ) {
+ model.set("newestVersion", newestVersion);
+ }
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ /**** Provenance-related functions ****/
+ /*
+ * Returns true if this provenance field points to a source of this data or metadata object
+ */
+ isSourceField: function (field) {
+ if (typeof field == "undefined" || !field) return false;
+ if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field))
+ return false;
+
+ if (
+ field == "prov_generatedByExecution" ||
+ field == "prov_generatedByProgram" ||
+ field == "prov_used" ||
+ field == "prov_wasDerivedFrom" ||
+ field == "prov_wasInformedBy"
+ )
+ return true;
+ else return false;
+ },
+
+ /*
+ * Returns true if this provenance field points to a derivation of this data or metadata object
+ */
+ isDerivationField: function (field) {
+ if (typeof field == "undefined" || !field) return false;
+ if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field))
+ return false;
+
+ if (
+ field == "prov_usedByExecution" ||
+ field == "prov_usedByProgram" ||
+ field == "prov_hasDerivations" ||
+ field == "prov_generated"
+ )
+ return true;
+ else return false;
+ },
+
+ /*
+ * Returns true if this SolrResult has a provenance trace (i.e. has either sources or derivations)
+ */
+ hasProvTrace: function () {
+ if (this.get("formatType") == "METADATA") {
+ if (this.get("prov_hasSources") || this.get("prov_hasDerivations"))
+ return true;
+ }
- } else if ((bytes >= kibibyte) && (bytes < mebibyte)) {
- return (bytes / kibibyte).toFixed(precision) + ' KiB';
+ var fieldNames = MetacatUI.appSearchModel.getProvFields(),
+ currentField = "";
- } else if ((bytes >= mebibyte) && (bytes < gibibyte)) {
- return (bytes / mebibyte).toFixed(precision) + ' MiB';
+ for (var i = 0; i < fieldNames.length; i++) {
+ currentField = fieldNames[i];
+ if (this.has(currentField)) return true;
+ }
- } else if ((bytes >= gibibyte) && (bytes < tebibyte)) {
- return (bytes / gibibyte).toFixed(precision) + ' GiB';
+ return false;
+ },
+
+ /*
+ * Returns an array of all the IDs of objects that are sources of this object
+ */
+ getSources: function () {
+ var sources = new Array(),
+ model = this,
+ //Get the prov fields but leave out references to executions which are not used in the UI yet
+ fields = _.reject(
+ MetacatUI.appSearchModel.getProvFields(),
+ function (f) {
+ return f.indexOf("xecution") > -1;
+ },
+ ); //Leave out the first e in execution so we don't have to worry about case sensitivity
+
+ _.each(fields, function (provField, i) {
+ if (model.isSourceField(provField) && model.has(provField))
+ sources.push(model.get(provField));
+ });
- } else if (bytes >= tebibyte) {
- return (bytes / tebibyte).toFixed(precision) + ' TiB';
+ return _.uniq(_.flatten(sources));
+ },
+
+ /*
+ * Returns an array of all the IDs of objects that are derivations of this object
+ */
+ getDerivations: function () {
+ var derivations = new Array(),
+ model = this,
+ //Get the prov fields but leave out references to executions which are not used in the UI yet
+ fields = _.reject(
+ MetacatUI.appSearchModel.getProvFields(),
+ function (f) {
+ return f.indexOf("xecution") > -1;
+ },
+ ); //Leave out the first e in execution so we don't have to worry about case sensitivity
+
+ _.each(fields, function (provField, i) {
+ if (model.isDerivationField(provField) && model.has(provField))
+ derivations.push(model.get(provField));
+ });
- } else {
- return bytes + ' B';
- }
- }
+ return _.uniq(_.flatten(derivations));
+ },
+
+ getInputs: function () {
+ return this.get("prov_used");
+ },
+
+ getOutputs: function () {
+ return this.get("prov_generated");
+ },
+
+ /*
+ * Uses the app configuration to check if this model's metrics should be hidden in the display
+ *
+ * @return {boolean}
+ */
+ hideMetrics: function () {
+ //If the AppModel is configured with cases of where to hide metrics,
+ if (
+ typeof MetacatUI.appModel.get("hideMetricsWhen") == "object" &&
+ MetacatUI.appModel.get("hideMetricsWhen")
+ ) {
+ //Check for at least one match
+ return _.some(
+ MetacatUI.appModel.get("hideMetricsWhen"),
+ function (value, modelProperty) {
+ //Get the value of this property from this model
+ var modelValue = this.get(modelProperty);
+
+ //Check for the presence of this model's value in the AppModel value
+ if (Array.isArray(value) && typeof modelValue == "string") {
+ return _.contains(value, modelValue);
+ }
+ //Check for the presence of the AppModel's value in this model's value
+ else if (typeof value == "string" && Array.isArray(modelValue)) {
+ return _.contains(modelValue, value);
+ }
+ //Check for overlap of two arrays
+ else if (Array.isArray(value) && Array.isArray(modelValue)) {
+ return _.intersection(value, modelValue).length > 0;
+ }
+ //If the AppModel value is a function, execute it
+ else if (typeof value == "function") {
+ return value(modelValue);
+ }
+ //Otherwise, just check for equality
+ else {
+ return value === modelValue;
+ }
+ },
+ this,
+ );
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Creates a URL for viewing more information about this metadata
+ * @return {string}
+ */
+ createViewURL: function () {
+ return this.getType() == "portal" || this.getType() == "collection"
+ ? MetacatUI.root +
+ "/" +
+ MetacatUI.appModel.get("portalTermPlural") +
+ "/" +
+ encodeURIComponent(
+ this.get("label") || this.get("seriesId") || this.get("id"),
+ )
+ : MetacatUI.root +
+ "/view/" +
+ encodeURIComponent(this.get("seriesId") || this.get("id"));
+ },
+
+ parseResourceMapField: function (json) {
+ if (typeof json.resourceMap == "string") {
+ return json.resourceMap.trim();
+ } else if (Array.isArray(json.resourceMap)) {
+ let newResourceMapIds = [];
+ _.each(json.resourceMap, function (rMapId) {
+ if (typeof rMapId == "string") {
+ newResourceMapIds.push(rMapId.trim());
+ }
+ });
+ return newResourceMapIds;
+ }
- });
- return SolrResult;
+ //If nothing works so far, return an empty array
+ return [];
+ },
+
+ /****************************/
+
+ /**
+ * Convert number of bytes into human readable format
+ *
+ * @param integer bytes Number of bytes to convert
+ * @param integer precision Number of digits after the decimal separator
+ * @return string
+ */
+ bytesToSize: function (bytes, precision) {
+ var kibibyte = 1024;
+ var mebibyte = kibibyte * 1024;
+ var gibibyte = mebibyte * 1024;
+ var tebibyte = gibibyte * 1024;
+
+ if (typeof bytes === "undefined") var bytes = this.get("size");
+
+ if (bytes >= 0 && bytes < kibibyte) {
+ return bytes + " B";
+ } else if (bytes >= kibibyte && bytes < mebibyte) {
+ return (bytes / kibibyte).toFixed(precision) + " KiB";
+ } else if (bytes >= mebibyte && bytes < gibibyte) {
+ return (bytes / mebibyte).toFixed(precision) + " MiB";
+ } else if (bytes >= gibibyte && bytes < tebibyte) {
+ return (bytes / gibibyte).toFixed(precision) + " GiB";
+ } else if (bytes >= tebibyte) {
+ return (bytes / tebibyte).toFixed(precision) + " TiB";
+ } else {
+ return bytes + " B";
+ }
+ },
+ },
+ );
+ return SolrResult;
});
diff --git a/src/js/models/Stats.js b/src/js/models/Stats.js
index 3d4554ea2..825b18b77 100644
--- a/src/js/models/Stats.js
+++ b/src/js/models/Stats.js
@@ -1,116 +1,121 @@
/*global define */
-define(['jquery', 'underscore', 'backbone', 'promise'],
- function($, _, Backbone, Promise) {
- 'use strict';
+define(["jquery", "underscore", "backbone", "promise"], function (
+ $,
+ _,
+ Backbone,
+ Promise,
+) {
+ "use strict";
/**
- * @class Stats
- * @classdesc This model contains all a collection of statistics/metrics about a collection of DataONE objects
- * @classcategory Models
- * @name Stats
- * @extends Backbone.Model
- * @constructor
- */
+ * @class Stats
+ * @classdesc This model contains all a collection of statistics/metrics about a collection of DataONE objects
+ * @classcategory Models
+ * @name Stats
+ * @extends Backbone.Model
+ * @constructor
+ */
var Stats = Backbone.Model.extend(
- /** @lends Stats.prototype */{
-
- /**
- * Default attributes for Stats models
- * @type {Object}
- * @property {string} query - The base query that defines the data collection to get statistis about.
- * @property {string} postQuery - A copy of the `query`, but without any URL encoding
- * @property {boolean} isSystemMetadataQuery - If true, the `query` set on this model is only filtering on system metadata fields which are common between both metadata and data objects.
- * @property {number} metadataCount - The number of metadata objects in this data collection @readonly
- * @property {number} dataCount - The number of data objects in this data collection
- * @property {number} totalCount - The number of metadata and data objects in this data collection. Essentially this is the sum of metadataCount and dataCount
- * @property {number|string[]} metadataFormatIDs - An array of metadata formatIds and the number of metadata objects with that formatId. Uses same structure as Solr facet counts: ["text/csv", 5]
- * @property {number|string[]} dataFormatIDs - An array of data formatIds and the number of data objects with that formatId. Uses same structure as Solr facet counts: ["text/csv", 5]
- * @property {string} firstUpdate - The earliest upload date for any object in this collection, excluding uploads of obsoleted objects
- * @property {number|string[]} dataUpdateDates - An array of date strings and the number of data objects uploaded on that date. Uses same structure as Solr facet counts: ["2015-08-02", 5]
- * @property {number|string[]} metadataUpdateDates An array of date strings and the number of data objects uploaded on that date. Uses same structure as Solr facet counts: ["2015-08-02", 5]
- * @property {string} firstBeginDate - An ISO date string of the earliest year that this data collection describes, from the science metadata
- * @property {string} lastEndDate - An ISO date string of the latest year that this data collection describes, from the science metadata
- * @property {string} firstPossibleDate - The first possible date (as a string) that data could have been collected. This is to weed out badly formatted dates when sending queries.
- * @property {object} temporalCoverage A simple object of date ranges (the object key) and the number of metadata objects uploaded in that date range (the object value). Example: { "1990-2000": 5 }
- * @property {number} queryCoverageFrom - The year to start the temporal coverage range query
- * @property {number} queryCoverageUntil - The year to end the temporal coverage range query
- * @property {number} metadataTotalSize - The total number of bytes of all metadata files
- * @property {number} dataTotalSize - The total number of bytes of all data files
- * @property {number} totalSize - The total number of bytes or metadata and data files
- * @property {boolean} hideMetadataAssessment - If true, metadata assessment scores will not be retrieved
- * @property {Image} mdqScoresImage - The Image objet of an aggregated metadata assessment chart
- * @property {number} maxQueryLength - The maximum query length that will be sent via GET to the query service. Queries that go beyond this length will be sent via POST, if POST is enabled in the AppModel
- * @property {string} mdqImageId - The identifier to use in the request for the metadata assessment chart
- */
- defaults: function(){
- return{
- query: "*:* ",
- postQuery: "",
- isSystemMetadataQuery: false,
-
- metadataCount: 0,
- dataCount: 0,
- totalCount: 0,
-
- metadataFormatIDs: [],
- dataFormatIDs: [],
-
- firstUpload: 0,
- totalUploads: 0,
- metadataUploads: null,
- dataUploads: null,
- metadataUploadDates: null,
- dataUploadDates: null,
-
- firstUpdate: null,
- dataUpdateDates: null,
- metadataUpdateDates: null,
-
- firstBeginDate: null,
- lastEndDate : null,
- firstPossibleDate: "1800-01-01T00:00:00Z",
- temporalCoverage: 0,
- queryCoverageFrom: null,
- queryCoverageUntil: null,
-
- metadataTotalSize: null,
- dataTotalSize: null,
- totalSize: null,
-
- hideMetadataAssessment: false,
- mdqScoresImage: null,
- mdqImageId: null,
-
- //HTTP GET requests are typically limited to 2,083 characters. So query lengths
- // should have this maximum before switching over to HTTP POST
- maxQueryLength: 2000
- }},
-
- /**
- * This function serves as a shorthand way to get all of the statistics stored in the model
- */
- getAll: function(){
-
- // Only get the MetaDIG scores if MetacatUI is configured to display metadata assesments
- // AND this model has them enabled, too.
- if ( !MetacatUI.appModel.get("hideSummaryMetadataAssessment") && !this.get("hideMetadataAssessment") ){
- this.getMdqScores();
- }
-
- //Send the call the get both the metadata and data stats
- this.getMetadataStats();
- this.getDataStats();
-
- },
+ /** @lends Stats.prototype */ {
+ /**
+ * Default attributes for Stats models
+ * @type {Object}
+ * @property {string} query - The base query that defines the data collection to get statistis about.
+ * @property {string} postQuery - A copy of the `query`, but without any URL encoding
+ * @property {boolean} isSystemMetadataQuery - If true, the `query` set on this model is only filtering on system metadata fields which are common between both metadata and data objects.
+ * @property {number} metadataCount - The number of metadata objects in this data collection @readonly
+ * @property {number} dataCount - The number of data objects in this data collection
+ * @property {number} totalCount - The number of metadata and data objects in this data collection. Essentially this is the sum of metadataCount and dataCount
+ * @property {number|string[]} metadataFormatIDs - An array of metadata formatIds and the number of metadata objects with that formatId. Uses same structure as Solr facet counts: ["text/csv", 5]
+ * @property {number|string[]} dataFormatIDs - An array of data formatIds and the number of data objects with that formatId. Uses same structure as Solr facet counts: ["text/csv", 5]
+ * @property {string} firstUpdate - The earliest upload date for any object in this collection, excluding uploads of obsoleted objects
+ * @property {number|string[]} dataUpdateDates - An array of date strings and the number of data objects uploaded on that date. Uses same structure as Solr facet counts: ["2015-08-02", 5]
+ * @property {number|string[]} metadataUpdateDates An array of date strings and the number of data objects uploaded on that date. Uses same structure as Solr facet counts: ["2015-08-02", 5]
+ * @property {string} firstBeginDate - An ISO date string of the earliest year that this data collection describes, from the science metadata
+ * @property {string} lastEndDate - An ISO date string of the latest year that this data collection describes, from the science metadata
+ * @property {string} firstPossibleDate - The first possible date (as a string) that data could have been collected. This is to weed out badly formatted dates when sending queries.
+ * @property {object} temporalCoverage A simple object of date ranges (the object key) and the number of metadata objects uploaded in that date range (the object value). Example: { "1990-2000": 5 }
+ * @property {number} queryCoverageFrom - The year to start the temporal coverage range query
+ * @property {number} queryCoverageUntil - The year to end the temporal coverage range query
+ * @property {number} metadataTotalSize - The total number of bytes of all metadata files
+ * @property {number} dataTotalSize - The total number of bytes of all data files
+ * @property {number} totalSize - The total number of bytes or metadata and data files
+ * @property {boolean} hideMetadataAssessment - If true, metadata assessment scores will not be retrieved
+ * @property {Image} mdqScoresImage - The Image objet of an aggregated metadata assessment chart
+ * @property {number} maxQueryLength - The maximum query length that will be sent via GET to the query service. Queries that go beyond this length will be sent via POST, if POST is enabled in the AppModel
+ * @property {string} mdqImageId - The identifier to use in the request for the metadata assessment chart
+ */
+ defaults: function () {
+ return {
+ query: "*:* ",
+ postQuery: "",
+ isSystemMetadataQuery: false,
+
+ metadataCount: 0,
+ dataCount: 0,
+ totalCount: 0,
+
+ metadataFormatIDs: [],
+ dataFormatIDs: [],
+
+ firstUpload: 0,
+ totalUploads: 0,
+ metadataUploads: null,
+ dataUploads: null,
+ metadataUploadDates: null,
+ dataUploadDates: null,
+
+ firstUpdate: null,
+ dataUpdateDates: null,
+ metadataUpdateDates: null,
+
+ firstBeginDate: null,
+ lastEndDate: null,
+ firstPossibleDate: "1800-01-01T00:00:00Z",
+ temporalCoverage: 0,
+ queryCoverageFrom: null,
+ queryCoverageUntil: null,
+
+ metadataTotalSize: null,
+ dataTotalSize: null,
+ totalSize: null,
+
+ hideMetadataAssessment: false,
+ mdqScoresImage: null,
+ mdqImageId: null,
+
+ //HTTP GET requests are typically limited to 2,083 characters. So query lengths
+ // should have this maximum before switching over to HTTP POST
+ maxQueryLength: 2000,
+ };
+ },
+
+ /**
+ * This function serves as a shorthand way to get all of the statistics stored in the model
+ */
+ getAll: function () {
+ // Only get the MetaDIG scores if MetacatUI is configured to display metadata assesments
+ // AND this model has them enabled, too.
+ if (
+ !MetacatUI.appModel.get("hideSummaryMetadataAssessment") &&
+ !this.get("hideMetadataAssessment")
+ ) {
+ this.getMdqScores();
+ }
- /**
- * Queries for statistics about metadata objects
- */
- getMetadataStats: function(){
+ //Send the call the get both the metadata and data stats
+ this.getMetadataStats();
+ this.getDataStats();
+ },
- var query = this.get("query"),
+ /**
+ * Queries for statistics about metadata objects
+ */
+ getMetadataStats: function () {
+ var query = this.get("query"),
//Filter out the portal and collection documents
- filterQuery = "-formatId:*dataone.org/collections* AND -formatId:*dataone.org/portals* AND formatType:METADATA AND -obsoletedBy:*",
+ filterQuery =
+ "-formatId:*dataone.org/collections* AND -formatId:*dataone.org/portals* AND formatType:METADATA AND -obsoletedBy:*",
//Use the stats feature to get the sum of the file size
stats = "true",
statsField = "size",
@@ -124,7 +129,7 @@ define(['jquery', 'underscore', 'backbone', 'promise'],
facetRange = "dateUploaded",
facetRangeGap = "+1MONTH",
facetRangeStart = "1900-01-01T00:00:00.000Z",
- facetRangeEnd = (new Date()).toISOString(),
+ facetRangeEnd = new Date().toISOString(),
facetMissing = "true",
//Query for the temporal coverage ranges
facetQueries = [],
@@ -137,325 +142,458 @@ define(['jquery', 'underscore', 'backbone', 'promise'],
//Use JSON for the response format
wt = "json";
- //How many years back should we look for temporal coverage?
- var lastYear = this.get('queryCoverageUntil') || new Date().getUTCFullYear(),
- firstYear = this.get('queryCoverageFrom') || 1950,
+ //How many years back should we look for temporal coverage?
+ var lastYear =
+ this.get("queryCoverageUntil") || new Date().getUTCFullYear(),
+ firstYear = this.get("queryCoverageFrom") || 1950,
totalYears = lastYear - firstYear,
today = new Date().getUTCFullYear(),
yearsFromToday = {
- fromBeginning: today - firstYear,
- fromEnd: today - lastYear
+ fromBeginning: today - firstYear,
+ fromEnd: today - lastYear,
};
- //Determine our year range/bin size
- var binSize = 1;
-
- if((totalYears > 10) && (totalYears <= 20)){
- binSize = 2;
- }
- else if((totalYears > 20) && (totalYears <= 50)){
- binSize = 5;
- }
- else if((totalYears > 50) && (totalYears <= 100)){
- binSize = 10;
- }
- else if(totalYears > 100){
- binSize = 25;
- }
-
- //Count all the datasets with coverage before the first year in the year range queries
- var beginDateLimit = new Date(Date.UTC(firstYear-1, 11, 31, 23, 59, 59, 999));
- facetQueries.push("{!key=<" + firstYear + "}(beginDate:[* TO " +
- beginDateLimit.toISOString() +"/YEAR])");
-
- //Construct our facet.queries for the beginDate and endDates, starting with all years before this current year
- var key = "";
-
- for(var yearsAgo = yearsFromToday.fromBeginning; (yearsAgo >= yearsFromToday.fromEnd && yearsAgo > 0); yearsAgo -= binSize){
- // The query logic here is: If the beginnning year is anytime before or
- // during the last year of the bin AND the ending year is anytime after
- // or during the first year of the bin, it counts.
- if(binSize == 1){
- //Querying for just the current year needs to be treated a bit differently
- // and won't be caught in our for loop
- if(lastYear == today){
- var oneYearFromNow = new Date(Date.UTC(today+1, 0, 1));
- var now = new Date();
+ //Determine our year range/bin size
+ var binSize = 1;
+
+ if (totalYears > 10 && totalYears <= 20) {
+ binSize = 2;
+ } else if (totalYears > 20 && totalYears <= 50) {
+ binSize = 5;
+ } else if (totalYears > 50 && totalYears <= 100) {
+ binSize = 10;
+ } else if (totalYears > 100) {
+ binSize = 25;
+ }
- facetQueries.push("{!key=" + lastYear + "}(beginDate:[* TO " +
- oneYearFromNow.toISOString() + "/YEAR] AND endDate:[" +
- now.toISOString() + "/YEAR TO *])");
+ //Count all the datasets with coverage before the first year in the year range queries
+ var beginDateLimit = new Date(
+ Date.UTC(firstYear - 1, 11, 31, 23, 59, 59, 999),
+ );
+ facetQueries.push(
+ "{!key=<" +
+ firstYear +
+ "}(beginDate:[* TO " +
+ beginDateLimit.toISOString() +
+ "/YEAR])",
+ );
+
+ //Construct our facet.queries for the beginDate and endDates, starting with all years before this current year
+ var key = "";
+
+ for (
+ var yearsAgo = yearsFromToday.fromBeginning;
+ yearsAgo >= yearsFromToday.fromEnd && yearsAgo > 0;
+ yearsAgo -= binSize
+ ) {
+ // The query logic here is: If the beginnning year is anytime before or
+ // during the last year of the bin AND the ending year is anytime after
+ // or during the first year of the bin, it counts.
+ if (binSize == 1) {
+ //Querying for just the current year needs to be treated a bit differently
+ // and won't be caught in our for loop
+ if (lastYear == today) {
+ var oneYearFromNow = new Date(Date.UTC(today + 1, 0, 1));
+ var now = new Date();
+
+ facetQueries.push(
+ "{!key=" +
+ lastYear +
+ "}(beginDate:[* TO " +
+ oneYearFromNow.toISOString() +
+ "/YEAR] AND endDate:[" +
+ now.toISOString() +
+ "/YEAR TO *])",
+ );
+ } else {
+ key = today - yearsAgo;
+
+ //The coverage should start sometime in this year range or earlier.
+ var beginDateLimit = new Date(
+ Date.UTC(today - (yearsAgo - 1), 0, 1),
+ );
+ //The coverage should end sometime in this year range or later.
+ var endDateLimit = new Date(Date.UTC(today - yearsAgo, 0, 1));
+
+ facetQueries.push(
+ "{!key=" +
+ key +
+ "}(beginDate:[* TO " +
+ beginDateLimit.toISOString() +
+ "/YEAR] AND endDate:[" +
+ endDateLimit.toISOString() +
+ "/YEAR TO *])",
+ );
+ }
}
- else{
- key = today - yearsAgo;
+ //If this is the last date range
+ else if (yearsAgo <= binSize) {
+ //Get the last year that will be included in this bin
+ var firstYearInBin = today - yearsAgo,
+ lastYearInBin = lastYear;
+
+ //Label the facet query with a key for easier parsing
+ // Because this is the last year range, which could be uneven with the other year ranges, use the exact end year
+ key = firstYearInBin + "-" + lastYearInBin;
//The coverage should start sometime in this year range or earlier.
- var beginDateLimit = new Date(Date.UTC(today-(yearsAgo-1), 0, 1));
+ // Because this is the last year range, which could be uneven with the other year ranges, use the exact end year
+ var beginDateLimit = new Date(
+ Date.UTC(lastYearInBin, 11, 31, 23, 59, 59, 999),
+ );
//The coverage should end sometime in this year range or later.
- var endDateLimit = new Date(Date.UTC(today-yearsAgo, 0, 1));
+ var endDateLimit = new Date(Date.UTC(firstYearInBin, 0, 1));
+
+ facetQueries.push(
+ "{!key=" +
+ key +
+ "}(beginDate:[* TO " +
+ beginDateLimit.toISOString() +
+ "/YEAR] AND endDate:[" +
+ endDateLimit.toISOString() +
+ "/YEAR TO *])",
+ );
+ }
+ //For all other bins,
+ else {
+ //Get the last year that will be included in this bin
+ var firstYearInBin = today - yearsAgo,
+ lastYearInBin = today - yearsAgo + binSize - 1;
+
+ //Label the facet query with a key for easier parsing
+ key = firstYearInBin + "-" + lastYearInBin;
- facetQueries.push("{!key=" + key + "}(beginDate:[* TO " +
- beginDateLimit.toISOString() + "/YEAR] AND endDate:[" +
- endDateLimit.toISOString() + "/YEAR TO *])");
+ //The coverage should start sometime in this year range or earlier.
+ // var beginDateLimit = new Date(Date.UTC(today - (yearsAgo - binSize), 0, 1));
+ var beginDateLimit = new Date(
+ Date.UTC(lastYearInBin, 11, 31, 23, 59, 59, 999),
+ );
+ //The coverage should end sometime in this year range or later.
+ var endDateLimit = new Date(Date.UTC(firstYearInBin, 0, 1));
+
+ facetQueries.push(
+ "{!key=" +
+ key +
+ "}(beginDate:[* TO " +
+ beginDateLimit.toISOString() +
+ "/YEAR] AND endDate:[" +
+ endDateLimit.toISOString() +
+ "/YEAR TO *])",
+ );
}
}
- //If this is the last date range
- else if (yearsAgo <= binSize){
- //Get the last year that will be included in this bin
- var firstYearInBin = (today - yearsAgo),
- lastYearInBin = lastYear;
-
- //Label the facet query with a key for easier parsing
- // Because this is the last year range, which could be uneven with the other year ranges, use the exact end year
- key = firstYearInBin + "-" + lastYearInBin;
-
- //The coverage should start sometime in this year range or earlier.
- // Because this is the last year range, which could be uneven with the other year ranges, use the exact end year
- var beginDateLimit = new Date(Date.UTC(lastYearInBin, 11, 31, 23, 59, 59, 999));
- //The coverage should end sometime in this year range or later.
- var endDateLimit = new Date(Date.UTC(firstYearInBin, 0, 1));
-
- facetQueries.push("{!key=" + key + "}(beginDate:[* TO " +
- beginDateLimit.toISOString() +"/YEAR] AND endDate:[" +
- endDateLimit.toISOString() + "/YEAR TO *])");
- }
- //For all other bins,
- else{
- //Get the last year that will be included in this bin
- var firstYearInBin = (today - yearsAgo),
- lastYearInBin = (today - yearsAgo + binSize-1);
-
- //Label the facet query with a key for easier parsing
- key = firstYearInBin + "-" + lastYearInBin;
-
- //The coverage should start sometime in this year range or earlier.
- // var beginDateLimit = new Date(Date.UTC(today - (yearsAgo - binSize), 0, 1));
- var beginDateLimit = new Date(Date.UTC(lastYearInBin, 11, 31, 23, 59, 59, 999));
- //The coverage should end sometime in this year range or later.
- var endDateLimit = new Date(Date.UTC(firstYearInBin, 0, 1));
-
- facetQueries.push("{!key=" + key + "}(beginDate:[* TO " +
- beginDateLimit.toISOString() + "/YEAR] AND endDate:[" +
- endDateLimit.toISOString() + "/YEAR TO *])");
- }
- }
-
- var model = this;
- var successCallback = function(data, textStatus, xhr) {
-
- if( !data || !data.response || !data.response.numFound ){
- //Store falsey data
- model.set("totalCount", 0);
- model.trigger("change:totalCount");
- model.set('metadataCount', 0);
- model.trigger("change:metadataCount");
- model.set('metadataFormatIDs', ["", 0]);
- model.set('firstUpdate', null);
- model.set("metadataUpdateDates", []);
- model.set("temporalCoverage", 0);
- model.trigger("change:temporalCoverage");
- }
- else{
- //Save tthe number of metadata docs found
- model.set('metadataCount', data.response.numFound);
- model.set("totalCount", model.get("dataCount") + data.response.numFound);
-
- //Save the format ID facet counts
- if( data.facet_counts && data.facet_counts.facet_fields && data.facet_counts.facet_fields.formatId ){
- model.set("metadataFormatIDs", data.facet_counts.facet_fields.formatId);
- }
- else{
- model.set("metadataFormatIDs", ["", 0]);
- }
- //Save the metadata update date counts
- if( data.facet_counts && data.facet_counts.facet_ranges && data.facet_counts.facet_ranges.dateUploaded ){
+ var model = this;
+ var successCallback = function (data, textStatus, xhr) {
+ if (!data || !data.response || !data.response.numFound) {
+ //Store falsey data
+ model.set("totalCount", 0);
+ model.trigger("change:totalCount");
+ model.set("metadataCount", 0);
+ model.trigger("change:metadataCount");
+ model.set("metadataFormatIDs", ["", 0]);
+ model.set("firstUpdate", null);
+ model.set("metadataUpdateDates", []);
+ model.set("temporalCoverage", 0);
+ model.trigger("change:temporalCoverage");
+ } else {
+ //Save tthe number of metadata docs found
+ model.set("metadataCount", data.response.numFound);
+ model.set(
+ "totalCount",
+ model.get("dataCount") + data.response.numFound,
+ );
+
+ //Save the format ID facet counts
+ if (
+ data.facet_counts &&
+ data.facet_counts.facet_fields &&
+ data.facet_counts.facet_fields.formatId
+ ) {
+ model.set(
+ "metadataFormatIDs",
+ data.facet_counts.facet_fields.formatId,
+ );
+ } else {
+ model.set("metadataFormatIDs", ["", 0]);
+ }
- //Find the index of the first update date
- var updateFacets = data.facet_counts.facet_ranges.dateUploaded.counts,
+ //Save the metadata update date counts
+ if (
+ data.facet_counts &&
+ data.facet_counts.facet_ranges &&
+ data.facet_counts.facet_ranges.dateUploaded
+ ) {
+ //Find the index of the first update date
+ var updateFacets =
+ data.facet_counts.facet_ranges.dateUploaded.counts,
cropAt = 0;
- for( var i=1; i 0){
- //Save the first first update date
- cropAt = i;
- model.set('firstUpdate', updateFacets[i-1]);
- //Save the update dates, but crop out months that are empty
- model.set("metadataUpdateDates", updateFacets.slice(cropAt+1));
- i = updateFacets.length;
+ for (var i = 1; i < updateFacets.length; i += 2) {
+ //If there was at least one update/upload in this date range, then save this as the first update
+ if (typeof updateFacets[i] == "number" && updateFacets[i] > 0) {
+ //Save the first first update date
+ cropAt = i;
+ model.set("firstUpdate", updateFacets[i - 1]);
+ //Save the update dates, but crop out months that are empty
+ model.set(
+ "metadataUpdateDates",
+ updateFacets.slice(cropAt + 1),
+ );
+ i = updateFacets.length;
+ }
}
- }
- //If no update dates were found, save falsey values
- if( cropAt === 0 ){
- model.set('firstUpdate', null);
- model.set("metadataUpdateDates", []);
+ //If no update dates were found, save falsey values
+ if (cropAt === 0) {
+ model.set("firstUpdate", null);
+ model.set("metadataUpdateDates", []);
+ }
}
- }
-
- //Save the temporal coverage dates
- if( data.facet_counts && data.facet_counts.facet_queries ){
- //Find the beginDate and facets so we can store the earliest beginDate
- if( data.facet_counts.facet_fields && data.facet_counts.facet_fields.beginDate ){
- var earliestBeginDate = _.find(data.facet_counts.facet_fields.beginDate, function(value){
- return ( typeof value == "string" && parseInt(value.substring(0,4)) > 1000 );
- });
- if( earliestBeginDate ){
- model.set("firstBeginDate", earliestBeginDate);
+ //Save the temporal coverage dates
+ if (data.facet_counts && data.facet_counts.facet_queries) {
+ //Find the beginDate and facets so we can store the earliest beginDate
+ if (
+ data.facet_counts.facet_fields &&
+ data.facet_counts.facet_fields.beginDate
+ ) {
+ var earliestBeginDate = _.find(
+ data.facet_counts.facet_fields.beginDate,
+ function (value) {
+ return (
+ typeof value == "string" &&
+ parseInt(value.substring(0, 4)) > 1000
+ );
+ },
+ );
+ if (earliestBeginDate) {
+ model.set("firstBeginDate", earliestBeginDate);
+ }
}
- }
- //Find the endDate and facets so we can store the latest endDate
- if( data.facet_counts.facet_fields && data.facet_counts.facet_fields.endDate ){
- var latestEndDate,
+ //Find the endDate and facets so we can store the latest endDate
+ if (
+ data.facet_counts.facet_fields &&
+ data.facet_counts.facet_fields.endDate
+ ) {
+ var latestEndDate,
endDates = data.facet_counts.facet_fields.endDate,
- nextYear = (new Date()).getUTCFullYear() + 1,
+ nextYear = new Date().getUTCFullYear() + 1,
i = 0;
- //Iterate over each endDate and find the first valid one. (After year 1000 but not after today)
- while( !latestEndDate && i 1000 && endDate < nextYear){
- latestEndDate = endDate;
+ //Iterate over each endDate and find the first valid one. (After year 1000 but not after today)
+ while (!latestEndDate && i < endDates.length) {
+ var endDate = endDates[i];
+ if (typeof endDate == "string") {
+ endDate = parseInt(endDate.substring(0, 3));
+ if (endDate > 1000 && endDate < nextYear) {
+ latestEndDate = endDate;
+ }
}
+ i++;
}
- i++;
- }
- //Save the latest endDate if one was found
- if( latestEndDate ){
- model.set("lastEndDate", latestEndDate);
+ //Save the latest endDate if one was found
+ if (latestEndDate) {
+ model.set("lastEndDate", latestEndDate);
+ }
}
- }
- //Save the temporal coverage year ranges
- var tempCoverages = data.facet_counts.facet_queries;
- model.set("temporalCoverage", tempCoverages);
- }
+ //Save the temporal coverage year ranges
+ var tempCoverages = data.facet_counts.facet_queries;
+ model.set("temporalCoverage", tempCoverages);
+ }
- //Get the total size of all the files in the index
- if( data.stats && data.stats.stats_fields && data.stats.stats_fields.size && data.stats.stats_fields.size.sum ){
- //Save the size sum
- model.set("metadataTotalSize", data.stats.stats_fields.size.sum);
- //If there is a data size sum,
- if( typeof model.get("dataTotalSize") == "number" ){
- //Add it to the metadata size sum as the total sum
- model.set("totalSize", model.get("dataTotalSize") + data.stats.stats_fields.size.sum);
+ //Get the total size of all the files in the index
+ if (
+ data.stats &&
+ data.stats.stats_fields &&
+ data.stats.stats_fields.size &&
+ data.stats.stats_fields.size.sum
+ ) {
+ //Save the size sum
+ model.set("metadataTotalSize", data.stats.stats_fields.size.sum);
+ //If there is a data size sum,
+ if (typeof model.get("dataTotalSize") == "number") {
+ //Add it to the metadata size sum as the total sum
+ model.set(
+ "totalSize",
+ model.get("dataTotalSize") + data.stats.stats_fields.size.sum,
+ );
+ }
}
}
- }
- }
-
- //Construct the full URL for the query
- var fullQueryURL = MetacatUI.appModel.get('queryServiceUrl') +
- "q=" + query +
- "&fq=" + filterQuery +
- "&stats=" + stats +
- "&stats.field=" + statsField +
- "&facet=" + facet +
- "&facet.field=" + facetFormatIdField +
- "&facet.field=" + facetBeginDateField +
- "&facet.field=" + facetEndDateField +
- "&f." + facetFormatIdField + ".facet.mincount=" + facetFormatIdMin +
- "&f." + facetFormatIdField + ".facet.missing=" + facetFormatIdMissing +
- "&f." + facetBeginDateField + ".facet.mincount=" + facetDateMin +
- "&f." + facetEndDateField + ".facet.mincount=" + facetDateMin +
- "&f." + facetBeginDateField + ".facet.missing=" + facetDateMissing +
- "&f." + facetEndDateField + ".facet.missing=" + facetDateMissing +
- "&facet.limit=" + facetLimit +
- "&f." + facetRange + ".facet.missing=" + facetMissing +
- "&facet.range=" + facetRange +
- "&facet.range.start=" + facetRangeStart +
- "&facet.range.end=" + facetRangeEnd +
- "&facet.range.gap=" + encodeURIComponent(facetRangeGap) +
- "&facet.query=" + facetQueries.join("&facet.query=") +
- "&rows=" + rows +
- "&wt=" + wt;
-
- if( this.getRequestType(fullQueryURL) == "POST" ){
-
- if( this.get("postQuery") ){
- query = this.get("postQuery");
- }
- else if( this.get("searchModel") ){
- query = this.get("searchModel").getQuery(undefined, { forPOST: true });
- this.set("postQuery", query);
- }
+ };
+
+ //Construct the full URL for the query
+ var fullQueryURL =
+ MetacatUI.appModel.get("queryServiceUrl") +
+ "q=" +
+ query +
+ "&fq=" +
+ filterQuery +
+ "&stats=" +
+ stats +
+ "&stats.field=" +
+ statsField +
+ "&facet=" +
+ facet +
+ "&facet.field=" +
+ facetFormatIdField +
+ "&facet.field=" +
+ facetBeginDateField +
+ "&facet.field=" +
+ facetEndDateField +
+ "&f." +
+ facetFormatIdField +
+ ".facet.mincount=" +
+ facetFormatIdMin +
+ "&f." +
+ facetFormatIdField +
+ ".facet.missing=" +
+ facetFormatIdMissing +
+ "&f." +
+ facetBeginDateField +
+ ".facet.mincount=" +
+ facetDateMin +
+ "&f." +
+ facetEndDateField +
+ ".facet.mincount=" +
+ facetDateMin +
+ "&f." +
+ facetBeginDateField +
+ ".facet.missing=" +
+ facetDateMissing +
+ "&f." +
+ facetEndDateField +
+ ".facet.missing=" +
+ facetDateMissing +
+ "&facet.limit=" +
+ facetLimit +
+ "&f." +
+ facetRange +
+ ".facet.missing=" +
+ facetMissing +
+ "&facet.range=" +
+ facetRange +
+ "&facet.range.start=" +
+ facetRangeStart +
+ "&facet.range.end=" +
+ facetRangeEnd +
+ "&facet.range.gap=" +
+ encodeURIComponent(facetRangeGap) +
+ "&facet.query=" +
+ facetQueries.join("&facet.query=") +
+ "&rows=" +
+ rows +
+ "&wt=" +
+ wt;
+
+ if (this.getRequestType(fullQueryURL) == "POST") {
+ if (this.get("postQuery")) {
+ query = this.get("postQuery");
+ } else if (this.get("searchModel")) {
+ query = this.get("searchModel").getQuery(undefined, {
+ forPOST: true,
+ });
+ this.set("postQuery", query);
+ }
- var queryData = new FormData();
- queryData.append("q", decodeURIComponent(query));
- queryData.append("fq", filterQuery);
- queryData.append("stats", stats);
- queryData.append("stats.field", statsField);
- queryData.append("facet", facet);
- queryData.append("facet.field", facetFormatIdField);
- queryData.append("facet.field", facetBeginDateField);
- queryData.append("facet.field", facetEndDateField);
- queryData.append("f." + facetFormatIdField + ".facet.mincount", facetFormatIdMin);
- queryData.append("f." + facetFormatIdField + ".facet.missing", facetFormatIdMissing);
- queryData.append("f." + facetBeginDateField + ".facet.mincount", facetDateMin);
- queryData.append("f." + facetEndDateField + ".facet.mincount", facetDateMin);
- queryData.append("f." + facetBeginDateField + ".facet.missing", facetDateMissing);
- queryData.append("f." + facetEndDateField + ".facet.missing", facetDateMissing);
- queryData.append("facet.limit", facetLimit);
- queryData.append("facet.range", facetRange);
- queryData.append("facet.range.start", facetRangeStart);
- queryData.append("facet.range.end", facetRangeEnd);
- queryData.append("facet.range.gap", facetRangeGap);
- queryData.append("f." + facetRange + ".facet.missing", facetMissing);
- queryData.append("rows", rows);
- queryData.append("wt", wt);
-
- //Add the facet queries to the POST body
- _.each(facetQueries, function(facetQuery){
- queryData.append("facet.query", facetQuery);
- });
+ var queryData = new FormData();
+ queryData.append("q", decodeURIComponent(query));
+ queryData.append("fq", filterQuery);
+ queryData.append("stats", stats);
+ queryData.append("stats.field", statsField);
+ queryData.append("facet", facet);
+ queryData.append("facet.field", facetFormatIdField);
+ queryData.append("facet.field", facetBeginDateField);
+ queryData.append("facet.field", facetEndDateField);
+ queryData.append(
+ "f." + facetFormatIdField + ".facet.mincount",
+ facetFormatIdMin,
+ );
+ queryData.append(
+ "f." + facetFormatIdField + ".facet.missing",
+ facetFormatIdMissing,
+ );
+ queryData.append(
+ "f." + facetBeginDateField + ".facet.mincount",
+ facetDateMin,
+ );
+ queryData.append(
+ "f." + facetEndDateField + ".facet.mincount",
+ facetDateMin,
+ );
+ queryData.append(
+ "f." + facetBeginDateField + ".facet.missing",
+ facetDateMissing,
+ );
+ queryData.append(
+ "f." + facetEndDateField + ".facet.missing",
+ facetDateMissing,
+ );
+ queryData.append("facet.limit", facetLimit);
+ queryData.append("facet.range", facetRange);
+ queryData.append("facet.range.start", facetRangeStart);
+ queryData.append("facet.range.end", facetRangeEnd);
+ queryData.append("facet.range.gap", facetRangeGap);
+ queryData.append("f." + facetRange + ".facet.missing", facetMissing);
+ queryData.append("rows", rows);
+ queryData.append("wt", wt);
+
+ //Add the facet queries to the POST body
+ _.each(facetQueries, function (facetQuery) {
+ queryData.append("facet.query", facetQuery);
+ });
- //Create the request settings for POST requests
- var requestSettings = {
- url: MetacatUI.appModel.get('queryServiceUrl'),
- type: "POST",
- contentType: false,
- processData: false,
- data: queryData,
- dataType: "json",
- success: successCallback
- }
- }
- else{
- //Create the request settings for GET requests
- var requestSettings = {
- url: fullQueryURL,
- type: "GET",
- dataType: "json",
- success: successCallback
+ //Create the request settings for POST requests
+ var requestSettings = {
+ url: MetacatUI.appModel.get("queryServiceUrl"),
+ type: "POST",
+ contentType: false,
+ processData: false,
+ data: queryData,
+ dataType: "json",
+ success: successCallback,
+ };
+ } else {
+ //Create the request settings for GET requests
+ var requestSettings = {
+ url: fullQueryURL,
+ type: "GET",
+ dataType: "json",
+ success: successCallback,
+ };
}
- }
-
- //Send the request
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
+ //Send the request
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ /**
+ * Queries for statistics about data objects
+ */
+ getDataStats: function () {
+ //Get the query string from this model
+ var query = this.get("query") || "";
+ //If there is a query set on the model, do a join on the resourceMap field
+ if (
+ query.trim() !== "*:*" &&
+ query.trim().length > 0 &&
+ !this.get("isSystemMetadataQuery") &&
+ MetacatUI.appModel.get("enableSolrJoins")
+ ) {
+ query = "{!join from=resourceMap to=resourceMap}" + query;
+ }
- /**
- * Queries for statistics about data objects
- */
- getDataStats: function(){
-
- //Get the query string from this model
- var query = this.get("query") || "";
- //If there is a query set on the model, do a join on the resourceMap field
- if((query.trim() !== "*:*" && query.trim().length > 0 && !this.get("isSystemMetadataQuery"))
- && MetacatUI.appModel.get("enableSolrJoins")){
- query = "{!join from=resourceMap to=resourceMap}" + query;
- }
-
- //Filter out resource maps and metatdata objects
- var filterQuery = "formatType:DATA AND -obsoletedBy:*",
+ //Filter out resource maps and metatdata objects
+ var filterQuery = "formatType:DATA AND -obsoletedBy:*",
//Use the stats feature to get the sum of the file size
stats = "true",
statsField = "size",
@@ -469,266 +607,335 @@ define(['jquery', 'underscore', 'backbone', 'promise'],
facetRange = "dateUploaded",
facetRangeGap = "+1MONTH",
facetRangeStart = "1900-01-01T00:00:00.000Z",
- facetRangeEnd = (new Date()).toISOString(),
+ facetRangeEnd = new Date().toISOString(),
facetRangeMissing = "true",
//Don't return any result docs
rows = "0",
//Use JSON for the response format
wt = "json";
- var fullQueryURL = MetacatUI.appModel.get('queryServiceUrl') +
- "q=" + query +
- "&fq=" + filterQuery +
- "&stats=" + stats +
- "&stats.field=" + statsField +
- "&facet=" + facet +
- "&facet.field=" + facetField +
- "&facet.limit=" + facetLimit +
- "&f." + facetField + ".facet.mincount=" + facetFormatIdMin +
- "&f." + facetField + ".facet.missing=" + facetFormatIdMissing +
- "&f." + facetRange + ".facet.missing=" + facetRangeMissing +
- "&facet.range=" + facetRange +
- "&facet.range.start=" + facetRangeStart +
- "&facet.range.end=" + facetRangeEnd +
- "&facet.range.gap=" + encodeURIComponent(facetRangeGap) +
- "&rows=" + rows +
- "&wt=" + wt;
-
- var model = this;
- var successCallback = function(data, textStatus, xhr) {
-
- if( !data || !data.response || !data.response.numFound ){
- //Store falsey data
- model.set('dataCount', 0);
- model.trigger("change:dataCount");
- model.set('dataFormatIDs', ["", 0]);
- model.set("dataUpdateDates", []);
- model.set("dataTotalSize", 0);
-
- if( typeof model.get("metadataTotalSize") == "number" ){
- //Use the metadata total size as the total size
- model.set("totalSize", model.get("metadataTotalSize"));
- }
- }
- else{
- //Save the number of data docs found
- model.set('dataCount', data.response.numFound);
- model.set("totalCount", model.get("metadataCount") + data.response.numFound);
-
- //Save the format ID facet counts
- if( data.facet_counts && data.facet_counts.facet_fields && data.facet_counts.facet_fields.formatId ){
- model.set("dataFormatIDs", data.facet_counts.facet_fields.formatId);
- }
- else{
+ var fullQueryURL =
+ MetacatUI.appModel.get("queryServiceUrl") +
+ "q=" +
+ query +
+ "&fq=" +
+ filterQuery +
+ "&stats=" +
+ stats +
+ "&stats.field=" +
+ statsField +
+ "&facet=" +
+ facet +
+ "&facet.field=" +
+ facetField +
+ "&facet.limit=" +
+ facetLimit +
+ "&f." +
+ facetField +
+ ".facet.mincount=" +
+ facetFormatIdMin +
+ "&f." +
+ facetField +
+ ".facet.missing=" +
+ facetFormatIdMissing +
+ "&f." +
+ facetRange +
+ ".facet.missing=" +
+ facetRangeMissing +
+ "&facet.range=" +
+ facetRange +
+ "&facet.range.start=" +
+ facetRangeStart +
+ "&facet.range.end=" +
+ facetRangeEnd +
+ "&facet.range.gap=" +
+ encodeURIComponent(facetRangeGap) +
+ "&rows=" +
+ rows +
+ "&wt=" +
+ wt;
+
+ var model = this;
+ var successCallback = function (data, textStatus, xhr) {
+ if (!data || !data.response || !data.response.numFound) {
+ //Store falsey data
+ model.set("dataCount", 0);
+ model.trigger("change:dataCount");
model.set("dataFormatIDs", ["", 0]);
- }
+ model.set("dataUpdateDates", []);
+ model.set("dataTotalSize", 0);
- //Save the data update date counts
- if( data.facet_counts && data.facet_counts.facet_ranges && data.facet_counts.facet_ranges.dateUploaded ){
+ if (typeof model.get("metadataTotalSize") == "number") {
+ //Use the metadata total size as the total size
+ model.set("totalSize", model.get("metadataTotalSize"));
+ }
+ } else {
+ //Save the number of data docs found
+ model.set("dataCount", data.response.numFound);
+ model.set(
+ "totalCount",
+ model.get("metadataCount") + data.response.numFound,
+ );
+
+ //Save the format ID facet counts
+ if (
+ data.facet_counts &&
+ data.facet_counts.facet_fields &&
+ data.facet_counts.facet_fields.formatId
+ ) {
+ model.set(
+ "dataFormatIDs",
+ data.facet_counts.facet_fields.formatId,
+ );
+ } else {
+ model.set("dataFormatIDs", ["", 0]);
+ }
- //Find the index of the first update date
- var updateFacets = data.facet_counts.facet_ranges.dateUploaded.counts,
+ //Save the data update date counts
+ if (
+ data.facet_counts &&
+ data.facet_counts.facet_ranges &&
+ data.facet_counts.facet_ranges.dateUploaded
+ ) {
+ //Find the index of the first update date
+ var updateFacets =
+ data.facet_counts.facet_ranges.dateUploaded.counts,
cropAt = 0;
- for( var i=1; i 0){
- //Save the first first update date
- cropAt = i;
- model.set('firstUpdate', updateFacets[i-1]);
- //Save the update dates, but crop out months that are empty
- model.set("dataUpdateDates", updateFacets.slice(cropAt+1));
- i = updateFacets.length;
+ for (var i = 1; i < updateFacets.length; i += 2) {
+ //If there was at least one update/upload in this date range, then save this as the first update
+ if (typeof updateFacets[i] == "number" && updateFacets[i] > 0) {
+ //Save the first first update date
+ cropAt = i;
+ model.set("firstUpdate", updateFacets[i - 1]);
+ //Save the update dates, but crop out months that are empty
+ model.set("dataUpdateDates", updateFacets.slice(cropAt + 1));
+ i = updateFacets.length;
+ }
}
- }
- //If no update dates were found, save falsey values
- if( cropAt === 0 ){
- model.set('firstUpdate', null);
- model.set("dataUpdateDates", []);
+ //If no update dates were found, save falsey values
+ if (cropAt === 0) {
+ model.set("firstUpdate", null);
+ model.set("dataUpdateDates", []);
+ }
}
- }
- //Get the total size of all the files in the index
- if( data.stats && data.stats.stats_fields && data.stats.stats_fields.size && data.stats.stats_fields.size.sum ){
- //Save the size sum
- model.set("dataTotalSize", data.stats.stats_fields.size.sum);
- //If there is a metadata size sum,
- if( model.get("metadataTotalSize") > 0 ){
- //Add it to the data size sum as the total sum
- model.set("totalSize", model.get("metadataTotalSize") + data.stats.stats_fields.size.sum);
+ //Get the total size of all the files in the index
+ if (
+ data.stats &&
+ data.stats.stats_fields &&
+ data.stats.stats_fields.size &&
+ data.stats.stats_fields.size.sum
+ ) {
+ //Save the size sum
+ model.set("dataTotalSize", data.stats.stats_fields.size.sum);
+ //If there is a metadata size sum,
+ if (model.get("metadataTotalSize") > 0) {
+ //Add it to the data size sum as the total sum
+ model.set(
+ "totalSize",
+ model.get("metadataTotalSize") +
+ data.stats.stats_fields.size.sum,
+ );
+ }
}
}
- }
- }
-
- if( this.getRequestType(fullQueryURL) == "POST" ){
-
- if( this.get("postQuery") ){
- query = this.get("postQuery");
- }
- else if( this.get("searchModel") ){
- query = this.get("searchModel").getQuery(undefined, { forPOST: true });
- this.set("postQuery", query);
- }
-
- var queryData = new FormData();
- queryData.append("q", decodeURIComponent(query));
- queryData.append("fq", filterQuery);
- queryData.append("stats", stats);
- queryData.append("stats.field", statsField);
- queryData.append("facet", facet);
- queryData.append("facet.field", facetField);
- queryData.append("facet.limit", facetLimit);
- queryData.append("f." + facetField + ".facet.mincount", facetFormatIdMin);
- queryData.append("f." + facetField + ".facet.missing", facetFormatIdMissing);
- queryData.append("f." + facetRange + ".facet.missing", facetRangeMissing);
- queryData.append("facet.range", facetRange);
- queryData.append("facet.range.start", facetRangeStart);
- queryData.append("facet.range.end", facetRangeEnd);
- queryData.append("facet.range.gap", facetRangeGap);
- queryData.append("rows", rows);
- queryData.append("wt", wt);
-
- //Create the request settings for POST requests
- var requestSettings = {
- url: MetacatUI.appModel.get('queryServiceUrl'),
- type: "POST",
- contentType: false,
- processData: false,
- data: queryData,
- dataType: "json",
- success: successCallback
- }
- }
- else{
- //Create the request settings for GET requests
- var requestSettings = {
- url: fullQueryURL,
- type: "GET",
- dataType: "json",
- success: successCallback
- }
- }
-
- //Send the request
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
-
- },
-
- /**
- * Retrieves an image of the metadata assessment scores
- */
- getMdqScores: function(){
- try{
- var myImage = new Image();
- var model = this;
- myImage.crossOrigin = ""; // or "anonymous"
-
- // Call the function with the URL we want to load, but then chain the
- // promise then() method on to the end of it. This contains two callbacks
- var serviceUrl = MetacatUI.appModel.get('mdqScoresServiceUrl');
+ };
+
+ if (this.getRequestType(fullQueryURL) == "POST") {
+ if (this.get("postQuery")) {
+ query = this.get("postQuery");
+ } else if (this.get("searchModel")) {
+ query = this.get("searchModel").getQuery(undefined, {
+ forPOST: true,
+ });
+ this.set("postQuery", query);
+ }
- if( !serviceUrl ){
- this.set("mdqScoresImage", this.defaults().mdqScoresImage);
- this.trigger("change:mdqScoresImage");
- return;
+ var queryData = new FormData();
+ queryData.append("q", decodeURIComponent(query));
+ queryData.append("fq", filterQuery);
+ queryData.append("stats", stats);
+ queryData.append("stats.field", statsField);
+ queryData.append("facet", facet);
+ queryData.append("facet.field", facetField);
+ queryData.append("facet.limit", facetLimit);
+ queryData.append(
+ "f." + facetField + ".facet.mincount",
+ facetFormatIdMin,
+ );
+ queryData.append(
+ "f." + facetField + ".facet.missing",
+ facetFormatIdMissing,
+ );
+ queryData.append(
+ "f." + facetRange + ".facet.missing",
+ facetRangeMissing,
+ );
+ queryData.append("facet.range", facetRange);
+ queryData.append("facet.range.start", facetRangeStart);
+ queryData.append("facet.range.end", facetRangeEnd);
+ queryData.append("facet.range.gap", facetRangeGap);
+ queryData.append("rows", rows);
+ queryData.append("wt", wt);
+
+ //Create the request settings for POST requests
+ var requestSettings = {
+ url: MetacatUI.appModel.get("queryServiceUrl"),
+ type: "POST",
+ contentType: false,
+ processData: false,
+ data: queryData,
+ dataType: "json",
+ success: successCallback,
+ };
+ } else {
+ //Create the request settings for GET requests
+ var requestSettings = {
+ url: fullQueryURL,
+ type: "GET",
+ dataType: "json",
+ success: successCallback,
+ };
}
- if( Array.isArray(MetacatUI.appModel.get('mdqAggregatedSuiteIds')) && MetacatUI.appModel.get('mdqAggregatedSuiteIds').length ){
- var suite = MetacatUI.appModel.get('mdqAggregatedSuiteIds')[0];
-
- var id;
-
- if( this.get("mdqImageId") && typeof this.get("mdqImageId") == "string" ){
- id = this.get("mdqImageId");
- }
- else if( MetacatUI.appView.currentView ){
- id = MetacatUI.appView.currentView.model.get("seriesId");
- }
-
- //If no ID was found, exit without getting the image
- if( !id ){
+ //Send the request
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ /**
+ * Retrieves an image of the metadata assessment scores
+ */
+ getMdqScores: function () {
+ try {
+ var myImage = new Image();
+ var model = this;
+ myImage.crossOrigin = ""; // or "anonymous"
+
+ // Call the function with the URL we want to load, but then chain the
+ // promise then() method on to the end of it. This contains two callbacks
+ var serviceUrl = MetacatUI.appModel.get("mdqScoresServiceUrl");
+
+ if (!serviceUrl) {
+ this.set("mdqScoresImage", this.defaults().mdqScoresImage);
+ this.trigger("change:mdqScoresImage");
return;
}
- var url = serviceUrl + "?id=" + id + "&suite=" + suite;
+ if (
+ Array.isArray(MetacatUI.appModel.get("mdqAggregatedSuiteIds")) &&
+ MetacatUI.appModel.get("mdqAggregatedSuiteIds").length
+ ) {
+ var suite = MetacatUI.appModel.get("mdqAggregatedSuiteIds")[0];
+
+ var id;
+
+ if (
+ this.get("mdqImageId") &&
+ typeof this.get("mdqImageId") == "string"
+ ) {
+ id = this.get("mdqImageId");
+ } else if (MetacatUI.appView.currentView) {
+ id = MetacatUI.appView.currentView.model.get("seriesId");
+ }
- this.imgLoad(url).then(function (response) {
- // The first runs when the promise resolves, with the request.reponse specified within the resolve() method.
- var imageURL = window.URL.createObjectURL(response);
- myImage.src = imageURL;
- model.set('mdqScoresImage', myImage);
- // The second runs when the promise is rejected, and logs the Error specified with the reject() method.
- }, function (Error) {
- console.error(Error);
- });
- }
- else{
+ //If no ID was found, exit without getting the image
+ if (!id) {
+ return;
+ }
+
+ var url = serviceUrl + "?id=" + id + "&suite=" + suite;
+
+ this.imgLoad(url).then(
+ function (response) {
+ // The first runs when the promise resolves, with the request.reponse specified within the resolve() method.
+ var imageURL = window.URL.createObjectURL(response);
+ myImage.src = imageURL;
+ model.set("mdqScoresImage", myImage);
+ // The second runs when the promise is rejected, and logs the Error specified with the reject() method.
+ },
+ function (Error) {
+ console.error(Error);
+ },
+ );
+ } else {
+ this.set("mdqScoresImage", this.defaults().mdqScoresImage);
+ }
+ } catch (e) {
this.set("mdqScoresImage", this.defaults().mdqScoresImage);
+ this.trigger("change:mdqScoresImage");
+ console.error("Cannot get the Metadata Assessment scores: ", e);
}
- }
- catch(e){
- this.set("mdqScoresImage", this.defaults().mdqScoresImage);
- this.trigger("change:mdqScoresImage");
- console.error("Cannot get the Metadata Assessment scores: ", e);
- }
- },
+ },
- /**
- * Retrieves an image via a Promise. Primarily used by {@link Stats#getMdqScores}
- * @param {string} url - The URL of the image
- */
- imgLoad: function(url) {
+ /**
+ * Retrieves an image via a Promise. Primarily used by {@link Stats#getMdqScores}
+ * @param {string} url - The URL of the image
+ */
+ imgLoad: function (url) {
// Create new promise with the Promise() constructor;
// This has as its argument a function with two parameters, resolve and reject
var model = this;
return new Promise(function (resolve, reject) {
- // Standard XHR to load an image
- var request = new XMLHttpRequest();
- request.open('GET', url);
- request.responseType = 'blob';
-
- // When the request loads, check whether it was successful
- request.onload = function () {
- if (request.status === 200) {
- // If successful, resolve the promise by passing back the request response
- resolve(request.response);
- } else {
- // If it fails, reject the promise with a error message
- reject(new Error('Image didn\'t load successfully; error code:' + request.statusText));
- model.set('mdqScoresError', request.statusText);
- }
- };
+ // Standard XHR to load an image
+ var request = new XMLHttpRequest();
+ request.open("GET", url);
+ request.responseType = "blob";
+
+ // When the request loads, check whether it was successful
+ request.onload = function () {
+ if (request.status === 200) {
+ // If successful, resolve the promise by passing back the request response
+ resolve(request.response);
+ } else {
+ // If it fails, reject the promise with a error message
+ reject(
+ new Error(
+ "Image didn't load successfully; error code:" +
+ request.statusText,
+ ),
+ );
+ model.set("mdqScoresError", request.statusText);
+ }
+ };
- request.onerror = function () {
- console.log("onerror");
- // Also deal with the case when the entire request fails to begin with
- // This is probably a network error, so reject the promise with an appropriate message
- reject(new Error('There was a network error.'));
- };
+ request.onerror = function () {
+ console.log("onerror");
+ // Also deal with the case when the entire request fails to begin with
+ // This is probably a network error, so reject the promise with an appropriate message
+ reject(new Error("There was a network error."));
+ };
- // Send the request
- request.send();
+ // Send the request
+ request.send();
});
- },
+ },
- /**
- * Sends a Solr query to get the earliest beginDate. If there are no beginDates in the index, then it
- * searches for the earliest endDate.
- */
- getFirstBeginDate: function(){
- var model = this;
-
- //Define a success callback when the query is successful
- var successCallback = function(data, textStatus, xhr) {
-
- //If nothing was found...
- if( !data || !data.response || !data.response.numFound ){
+ /**
+ * Sends a Solr query to get the earliest beginDate. If there are no beginDates in the index, then it
+ * searches for the earliest endDate.
+ */
+ getFirstBeginDate: function () {
+ var model = this;
- //Construct a query to find the earliest endDate
- var query = model.get('query') +
- " AND endDate:[" + model.get("firstPossibleDate") + " TO " + (new Date()).toISOString() + "]" + //Use date filter to weed out badly formatted data
+ //Define a success callback when the query is successful
+ var successCallback = function (data, textStatus, xhr) {
+ //If nothing was found...
+ if (!data || !data.response || !data.response.numFound) {
+ //Construct a query to find the earliest endDate
+ var query =
+ model.get("query") +
+ " AND endDate:[" +
+ model.get("firstPossibleDate") +
+ " TO " +
+ new Date().toISOString() +
+ "]" + //Use date filter to weed out badly formatted data
" AND -obsoletedBy:*",
//Get one row only
rows = "1",
@@ -737,59 +944,83 @@ define(['jquery', 'underscore', 'backbone', 'promise'],
//Return only the endDate field
fl = "endDate";
- var successCallback = function(endDateData, textStatus, xhr) {
- //If not endDates or beginDates are found, there is no temporal data in the index, so save falsey values
- if( !endDateData || !endDateData.response || !endDateData.response.numFound){
- model.set('firstBeginDate', null);
- model.set('lastEndDate', null);
- }
- else{
- model.set('firstBeginDate', new Date(endDateData.response.docs[0].endDate));
- }
- }
+ var successCallback = function (endDateData, textStatus, xhr) {
+ //If not endDates or beginDates are found, there is no temporal data in the index, so save falsey values
+ if (
+ !endDateData ||
+ !endDateData.response ||
+ !endDateData.response.numFound
+ ) {
+ model.set("firstBeginDate", null);
+ model.set("lastEndDate", null);
+ } else {
+ model.set(
+ "firstBeginDate",
+ new Date(endDateData.response.docs[0].endDate),
+ );
+ }
+ };
- if( model.get("usePOST") ){
-
- var queryData = new FormData();
- queryData.append("q", decodeURIComponent(query));
- queryData.append("rows", rows);
- queryData.append("sort", sort);
- queryData.append("fl", fl);
- queryData.append("wt", "json");
-
- var requestSettings = {
- url: MetacatUI.appModel.get('queryServiceUrl'),
- type: "POST",
- contentType: false,
- processData: false,
- data: queryData,
- dataType: "json",
- success: successCallback
+ if (model.get("usePOST")) {
+ var queryData = new FormData();
+ queryData.append("q", decodeURIComponent(query));
+ queryData.append("rows", rows);
+ queryData.append("sort", sort);
+ queryData.append("fl", fl);
+ queryData.append("wt", "json");
+
+ var requestSettings = {
+ url: MetacatUI.appModel.get("queryServiceUrl"),
+ type: "POST",
+ contentType: false,
+ processData: false,
+ data: queryData,
+ dataType: "json",
+ success: successCallback,
+ };
+ } else {
+ //Find the earliest endDate if there are no beginDates
+ var requestSettings = {
+ url:
+ MetacatUI.appModel.get("queryServiceUrl") +
+ "q=" +
+ query +
+ "&rows=" +
+ rows +
+ "&sort=" +
+ sort +
+ "&fl=" +
+ fl +
+ "&wt=json",
+ type: "GET",
+ dataType: "json",
+ success: successCallback,
+ };
}
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ } else {
+ // Save the earliest beginDate
+ model.set(
+ "firstBeginDate",
+ new Date(data.response.docs[0].beginDate),
+ );
+ model.trigger("change:firstBeginDate");
}
- else{
- //Find the earliest endDate if there are no beginDates
- var requestSettings = {
- url: MetacatUI.appModel.get('queryServiceUrl') + "q=" + query +
- "&rows=" + rows + "&sort=" + sort + "&fl=" + fl + "&wt=json",
- type: "GET",
- dataType: "json",
- success: successCallback
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- }
- else{
- // Save the earliest beginDate
- model.set('firstBeginDate', new Date(data.response.docs[0].beginDate));
- model.trigger("change:firstBeginDate");
- }
- }
-
- //Construct a query
- var specialQueryParams = " AND beginDate:[" + this.get("firstPossibleDate") + " TO " + (new Date()).toISOString() + "] AND -obsoletedBy:* AND -formatId:*dataone.org/collections* AND -formatId:*dataone.org/portals*",
+ };
+
+ //Construct a query
+ var specialQueryParams =
+ " AND beginDate:[" +
+ this.get("firstPossibleDate") +
+ " TO " +
+ new Date().toISOString() +
+ "] AND -obsoletedBy:* AND -formatId:*dataone.org/collections* AND -formatId:*dataone.org/portals*",
query = this.get("query") + specialQueryParams,
//Get one row only
rows = "1",
@@ -798,226 +1029,263 @@ define(['jquery', 'underscore', 'backbone', 'promise'],
//Return only the beginDate field
fl = "beginDate";
- if( this.get("usePOST") ){
-
- //Get the unencoded query string
- if( this.get("postQuery") ){
- query = this.get("postQuery") + specialQueryParams;
- }
- else if( this.get("searchModel") ){
- query = this.get("searchModel").getQuery(undefined, { forPOST: true });
- this.set("postQuery", query);
- query = query + specialQueryParams;
- }
-
- var queryData = new FormData();
- queryData.append("q", decodeURIComponent(query));
- queryData.append("rows", rows);
- queryData.append("sort", sort);
- queryData.append("fl", fl);
- queryData.append("wt", "json");
+ if (this.get("usePOST")) {
+ //Get the unencoded query string
+ if (this.get("postQuery")) {
+ query = this.get("postQuery") + specialQueryParams;
+ } else if (this.get("searchModel")) {
+ query = this.get("searchModel").getQuery(undefined, {
+ forPOST: true,
+ });
+ this.set("postQuery", query);
+ query = query + specialQueryParams;
+ }
- var requestSettings = {
- url: MetacatUI.appModel.get('queryServiceUrl'),
- type: "POST",
- contentType: false,
- processData: false,
- data: queryData,
- dataType: "json",
- success: successCallback
+ var queryData = new FormData();
+ queryData.append("q", decodeURIComponent(query));
+ queryData.append("rows", rows);
+ queryData.append("sort", sort);
+ queryData.append("fl", fl);
+ queryData.append("wt", "json");
+
+ var requestSettings = {
+ url: MetacatUI.appModel.get("queryServiceUrl"),
+ type: "POST",
+ contentType: false,
+ processData: false,
+ data: queryData,
+ dataType: "json",
+ success: successCallback,
+ };
+ } else {
+ var requestSettings = {
+ url:
+ MetacatUI.appModel.get("queryServiceUrl") +
+ "q=" +
+ query +
+ "&rows=" +
+ rows +
+ "&fl=" +
+ fl +
+ "&sort=" +
+ sort +
+ "&wt=json",
+ type: "GET",
+ dataType: "json",
+ success: successCallback,
+ };
}
- }
- else{
+ //Send the query
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ // Getting total number of replicas for repository profiles
+ getTotalReplicas: function (memberNodeID) {
+ var model = this;
var requestSettings = {
- url: MetacatUI.appModel.get('queryServiceUrl') + "q=" + query +
- "&rows=" + rows +
- "&fl=" + fl +
- "&sort=" + sort +
- "&wt=json",
- type: "GET",
- dataType: "json",
- success: successCallback
- }
-
- }
-
- //Send the query
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- // Getting total number of replicas for repository profiles
- getTotalReplicas: function(memberNodeID) {
-
- var model = this;
-
- var requestSettings = {
- url: MetacatUI.appModel.get("queryServiceUrl") +
- "q=replicaMN:" + memberNodeID +
- " AND -datasource:" + memberNodeID +
+ url:
+ MetacatUI.appModel.get("queryServiceUrl") +
+ "q=replicaMN:" +
+ memberNodeID +
+ " AND -datasource:" +
+ memberNodeID +
" AND formatType:METADATA" +
" AND -obsoletedBy:*" +
" &wt=json&rows=0",
type: "GET",
dataType: "json",
- success: function(data, textStatus, xhr){
- model.set("totalReplicas", data.response.numFound );
+ success: function (data, textStatus, xhr) {
+ model.set("totalReplicas", data.response.numFound);
},
- error: function(data, textStatus, xhr){
- model.set("totalReplicas", 0 );
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- /**
- * Gets the latest endDate from the Solr index
- */
- getLastEndDate: function(){
- var model = this;
+ error: function (data, textStatus, xhr) {
+ model.set("totalReplicas", 0);
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ /**
+ * Gets the latest endDate from the Solr index
+ */
+ getLastEndDate: function () {
+ var model = this;
- var now = new Date();
+ var now = new Date();
- //Get the latest temporal data coverage year
- var specialQueryParams = " AND endDate:[" + this.get("firstPossibleDate") + " TO " + now.toISOString() + "]" + //Use date filter to weed out badly formatted data
+ //Get the latest temporal data coverage year
+ var specialQueryParams =
+ " AND endDate:[" +
+ this.get("firstPossibleDate") +
+ " TO " +
+ now.toISOString() +
+ "]" + //Use date filter to weed out badly formatted data
" AND -obsoletedBy:* AND -formatId:*dataone.org/collections* AND -formatId:*dataone.org/portals*",
- query = this.get('query') + specialQueryParams,
+ query = this.get("query") + specialQueryParams,
rows = 1,
- fl = "endDate",
+ fl = "endDate",
sort = "endDate desc",
- wt = "json";
+ wt = "json";
- var successCallback = function(data, textStatus, xhr) {
- if(typeof data == "string"){
- data = JSON.parse(data);
- }
+ var successCallback = function (data, textStatus, xhr) {
+ if (typeof data == "string") {
+ data = JSON.parse(data);
+ }
- if(!data || !data.response || !data.response.numFound){
- //Save some falsey values if none are found
- model.set('lastEndDate', null);
- }
- else{
- // Save the earliest beginDate and total found in our model - but do not accept a year greater than this current year
- var now = new Date();
- if(new Date(data.response.docs[0].endDate).getUTCFullYear() > now.getUTCFullYear()){
- model.set('lastEndDate', now);
+ if (!data || !data.response || !data.response.numFound) {
+ //Save some falsey values if none are found
+ model.set("lastEndDate", null);
+ } else {
+ // Save the earliest beginDate and total found in our model - but do not accept a year greater than this current year
+ var now = new Date();
+ if (
+ new Date(data.response.docs[0].endDate).getUTCFullYear() >
+ now.getUTCFullYear()
+ ) {
+ model.set("lastEndDate", now);
+ } else {
+ model.set("lastEndDate", new Date(data.response.docs[0].endDate));
+ }
+
+ model.trigger("change:lastEndDate");
}
- else{
- model.set('lastEndDate', new Date(data.response.docs[0].endDate));
+ };
+
+ if (this.get("usePOST")) {
+ //Get the unencoded query string
+ if (this.get("postQuery")) {
+ query = this.get("postQuery") + specialQueryParams;
+ } else if (this.get("searchModel")) {
+ query = this.get("searchModel").getQuery(undefined, {
+ forPOST: true,
+ });
+ this.set("postQuery", query);
+ query = query + specialQueryParams;
}
- model.trigger("change:lastEndDate");
+ var queryData = new FormData();
+ queryData.append("q", decodeURIComponent(query));
+ queryData.append("rows", rows);
+ queryData.append("sort", sort);
+ queryData.append("fl", fl);
+ queryData.append("wt", "json");
+
+ var requestSettings = {
+ url: MetacatUI.appModel.get("queryServiceUrl"),
+ type: "POST",
+ contentType: false,
+ processData: false,
+ data: queryData,
+ dataType: "json",
+ success: successCallback,
+ };
+ } else {
+ //Query for the latest endDate
+ var requestSettings = {
+ url:
+ MetacatUI.appModel.get("queryServiceUrl") +
+ "q=" +
+ query +
+ "&rows=" +
+ rows +
+ "&fl=" +
+ fl +
+ "&sort=" +
+ sort +
+ "&wt=" +
+ wt,
+ type: "GET",
+ dataType: "json",
+ success: successCallback,
+ };
}
- }
- if( this.get("usePOST") ){
-
- //Get the unencoded query string
- if( this.get("postQuery") ){
- query = this.get("postQuery") + specialQueryParams;
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ /**
+ * Given the query or URL, determine whether this model should send GET or POST
+ * requests, because of URL length restrictions in browsers.
+ * @param {string} queryOrURLString - The full query or URL that will be sent to the query service
+ * @returns {string} The request type to use. Either `GET` or `POST`
+ */
+ getRequestType: function (queryOrURLString) {
+ //If POSTs to the query service are disabled completely, use GET
+ if (MetacatUI.appModel.get("disableQueryPOSTs")) {
+ return "GET";
}
- else if( this.get("searchModel") ){
- query = this.get("searchModel").getQuery(undefined, { forPOST: true });
- this.set("postQuery", query);
- query = query + specialQueryParams;
+ //If POSTs are enabled and the URL is over the maximum, use POST
+ else if (
+ queryOrURLString &&
+ queryOrURLString.length > this.get("maxQueryLength")
+ ) {
+ return "POST";
}
-
- var queryData = new FormData();
- queryData.append("q", decodeURIComponent(query));
- queryData.append("rows", rows);
- queryData.append("sort", sort);
- queryData.append("fl", fl);
- queryData.append("wt", "json");
-
- var requestSettings = {
- url: MetacatUI.appModel.get('queryServiceUrl'),
- type: "POST",
- contentType: false,
- processData: false,
- data: queryData,
- dataType: "json",
- success: successCallback
+ //Otherwise, default to GET
+ else {
+ return "GET";
}
- }
- else{
- //Query for the latest endDate
- var requestSettings = {
- url: MetacatUI.appModel.get('queryServiceUrl') + "q=" + query +
- "&rows=" + rows + "&fl=" + fl + "&sort=" + sort + "&wt=" + wt,
- type: "GET",
- dataType: "json",
- success: successCallback
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- /**
- * Given the query or URL, determine whether this model should send GET or POST
- * requests, because of URL length restrictions in browsers.
- * @param {string} queryOrURLString - The full query or URL that will be sent to the query service
- * @returns {string} The request type to use. Either `GET` or `POST`
- */
- getRequestType: function(queryOrURLString){
- //If POSTs to the query service are disabled completely, use GET
- if( MetacatUI.appModel.get("disableQueryPOSTs") ){
- return "GET";
- }
- //If POSTs are enabled and the URL is over the maximum, use POST
- else if( queryOrURLString && queryOrURLString.length > this.get("maxQueryLength") ){
- return "POST";
- }
- //Otherwise, default to GET
- else{
- return "GET";
- }
- },
-
- /**
- * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} and {@link Stats#getDataStats} to get the formatTypes.
- * This function may be removed in a future release.
- */
- getFormatTypes: function(){
- this.getMetadataStats();
- this.getDataStats();
+ },
+
+ /**
+ * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} and {@link Stats#getDataStats} to get the formatTypes.
+ * This function may be removed in a future release.
+ */
+ getFormatTypes: function () {
+ this.getMetadataStats();
+ this.getDataStats();
+ },
+
+ /**
+ * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getDataStats} to get the formatTypes.
+ * This function may be removed in a future release.
+ */
+ getDataFormatIDs: function () {
+ this.getDataStats();
+ },
+
+ /**
+ * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} to get the formatTypes.
+ * This function may be removed in a future release.
+ */
+ getMetadataFormatIDs: function () {
+ this.getMetadataStats();
+ },
+
+ /**
+ * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} and {@link Stats#getDataStats} to get the formatTypes.
+ * This function may be removed in a future release.
+ */
+ getUpdateDates: function () {
+ this.getMetadataStats();
+ this.getDataStats();
+ },
+
+ /**
+ * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} to get the formatTypes.
+ * This function may be removed in a future release.
+ */
+ getCollectionYearFacets: function () {
+ this.getMetadataStats();
+ },
},
-
- /**
- * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getDataStats} to get the formatTypes.
- * This function may be removed in a future release.
- */
- getDataFormatIDs: function(){
- this.getDataStats();
- },
-
- /**
- * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} to get the formatTypes.
- * This function may be removed in a future release.
- */
- getMetadataFormatIDs: function(){
- this.getMetadataStats();
- },
-
- /**
- * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} and {@link Stats#getDataStats} to get the formatTypes.
- * This function may be removed in a future release.
- */
- getUpdateDates: function(){
- this.getMetadataStats();
- this.getDataStats();
- },
-
- /**
- * @deprecated as of MetacatUI version 2.12.0. Use {@link Stats#getMetadataStats} to get the formatTypes.
- * This function may be removed in a future release.
- */
- getCollectionYearFacets: function(){
- this.getMetadataStats();
- }
-
- });
+ );
return Stats;
});
diff --git a/src/js/models/UserModel.js b/src/js/models/UserModel.js
index 3acc36250..733f912bf 100644
--- a/src/js/models/UserModel.js
+++ b/src/js/models/UserModel.js
@@ -1,1144 +1,1258 @@
/*global define */
-define(['jquery', 'underscore', 'backbone', 'jws', 'models/Search', "collections/SolrResults"],
- function($, _, Backbone, JWS, SearchModel, SearchResults) {
- 'use strict';
-
- /**
- * @class UserModel
- * @classcategory Models
- * @extends Backbone.Model
- * @constructor
- */
- var UserModel = Backbone.Model.extend(
- /** @lends UserModel.prototype */{
- defaults: function(){
- return{
- type: "person", //assume this is a person unless we are told otherwise - other possible type is a "group"
- checked: false, //Is set to true when we have checked the account/subject info of this user
- tokenChecked: false, //Is set to true when the uer auth token has been checked
- basicUser: false, //Set to true to only query for basic info about this user - prevents sending queries for info that will never be displayed in the UI
- lastName: null,
- firstName: null,
- fullName: null,
- email: null,
- logo: null,
- description: null,
- verified: null,
- username: null,
- usernameReadable: null,
- orcid: null,
- searchModel: null,
- searchResults: null,
- loggedIn: false,
- ldapError: false, //Was there an error logging in to LDAP
- registered: false,
- isMemberOf: [],
- isOwnerOf: [],
- identities: [],
- identitiesUsernames: [],
- allIdentitiesAndGroups: [],
- pending: [],
- token: null,
- expires: null,
- timeoutId: null,
- rawData: null,
- portalQuota: -1,
- isAuthorizedCreatePortal: null,
- dataoneQuotas: null,
- dataoneSubscription: null
- }
- },
-
- initialize: function(options){
- if(typeof options !== "undefined"){
- if(options.username) this.set("username", options.username);
- if(options.rawData) this.set(this.parseXML(options.rawData));
- }
-
- this.on("change:identities", this.pluckIdentityUsernames);
-
- this.on("change:username change:identities change:type", this.updateSearchModel);
- this.createSearchModel();
-
- this.on("change:username", this.createReadableUsername());
-
- //Create a search results model for this person
- var searchResults = new SearchResults([], { rows: 5, start: 0 });
- this.set("searchResults", searchResults);
-
- if( MetacatUI.appModel.get("enableBookkeeperServices") ){
- //When the user is logged in, see if they have a DataONE subscription
- this.on("change:loggedIn", this.fetchSubscription);
- }
- },
-
- createSearchModel: function(){
- //Create a search model that will retrieve data created by this person
- this.set("searchModel", new SearchModel());
- this.updateSearchModel();
- },
-
- updateSearchModel: function(){
- if(this.get("type") == "node"){
- this.get("searchModel").set("dataSource", [this.get("node").identifier]);
- this.get("searchModel").set("username", []);
- }
- else{
- //Get all the identities for this person
- var ids = [this.get("username")];
-
- _.each(this.get("identities"), function(equivalentUser){
- ids.push(equivalentUser.get("username"));
- });
- this.get("searchModel").set("username", ids);
- }
-
- this.trigger("change:searchModel");
- },
-
- parseXML: function(data){
- var model = this,
- username = this.get("username");
-
- //Reset the group list so we don't just add it to it with push()
- this.set("isMemberOf", this.defaults().isMemberOf, {silent: true});
- this.set("isOwnerOf", this.defaults().isOwnerOf, {silent: true});
- //Reset the equivalent id list so we don't just add it to it with push()
- this.set("identities", this.defaults().identities, {silent: true});
-
- //Find this person's node in the XML
- var userNode = null;
- if(!username)
- var username = $(data).children("subject").text();
- if(username){
- var subjects = $(data).find("subject");
- for(var i=0; i 0)
- var allPersons = $(data).find("person subject");
-
- _.each(equivalentIds, function(identity, i){
- //push onto the list
- var username = $(identity).text(),
- equivUserNode;
-
- //Find the matching person node in the response
- _.each(allPersons, function(person){
- if($(person).text().toLowerCase() == username.toLowerCase()){
- equivUserNode = $(person).parent().first();
- allPersons = _.without(allPersons, person);
- }
- });
-
- var equivalentUser = new UserModel({ username: username, basicUser: true, rawData: equivUserNode });
- identities.push(equivalentUser);
- equivUsernames.push(username);
- });
- }
-
- //Get each group and save
- _.each($(data).find("group"), function(group, i){
- //Save group ID
- var groupId = $(group).find("subject").first().text(),
- groupName = $(group).find("groupName").text();
-
- memberOf.push({ groupId: groupId, name: groupName });
-
- //Check if this person is a rightsholder
- var allRightsHolders = [];
- _.each($(group).children("rightsHolder"), function(rightsHolder){
- allRightsHolders.push($(rightsHolder).text().toLowerCase());
- });
- if(_.contains(allRightsHolders, username.toLowerCase()))
- ownerOf.push(groupId);
- });
- }
-
- var allSubjects = _.pluck( this.get("isMemberOf"), "groupId" );
- allSubjects.push(this.get("username"));
- allSubjects = allSubjects.concat(equivUsernames);
-
- return {
- isMemberOf: memberOf,
- isOwnerOf: ownerOf,
- identities: identities,
- allIdentitiesAndGroups: allSubjects,
- verified: verified,
- username: username,
- firstName: firstName,
- lastName: lastName,
- fullName: fullName,
- email: email,
- registered: true,
- type: type,
- rawData: data
- }
- },
-
- getInfo: function(){
- var model = this;
-
- //If the accounts service is not on, flag this user as checked/completed
- if(!MetacatUI.appModel.get("accountsUrl")){
- this.set("fullName", this.getNameFromSubject());
- this.set("checked", true);
- return;
- }
-
- //Only proceed if there is a username
- if(!this.get("username")) return;
-
- //Get the user info using the DataONE API
- var url = MetacatUI.appModel.get("accountsUrl") + encodeURIComponent(this.get("username"));
-
- var requestSettings = {
- type: "GET",
- url: url,
- success: function(data, textStatus, xhr) {
- //Parse the XML response to get user info
- var userProperties = model.parseXML(data);
- //Filter out all the falsey values
- _.each(userProperties, function(v, k) {
- if(!v) {
- delete userProperties[k];
- }
- });
- model.set(userProperties);
-
- //Trigger the change events
- model.trigger("change:isMemberOf");
- model.trigger("change:isOwnerOf");
- model.trigger("change:identities");
-
- model.set("checked", true);
- },
- error: function(xhr, textStatus, errorThrown){
- // Sometimes the node info has not been received before this getInfo() is called.
- // If the node info was received while this getInfo request was pending, and this user was determined
- // to be a node, then we can skip any further action here.
- if(model.get("type") == "node")
- return;
-
- if((xhr.status == 404) && MetacatUI.nodeModel.get("checked")){
- model.set("fullName", model.getNameFromSubject());
- model.set("checked", true);
- }
- else if((xhr.status == 404) && !MetacatUI.nodeModel.get("checked")){
- model.listenToOnce(MetacatUI.nodeModel, "change:checked", function(){
- if(!model.isNode()){
- model.set("fullName", model.getNameFromSubject());
- model.set("checked", true);
- }
- });
- }
- else{
- //As a backup, search for this user instead
- var requestSettings = {
- type: "GET",
- url: MetacatUI.appModel.get("accountsUrl") + "?query=" + encodeURIComponent(model.get("username")),
- success: function(data, textStatus, xhr) {
- //Parse the XML response to get user info
- model.set(model.parseXML(data));
-
- //Trigger the change events
- model.trigger("change:isMemberOf");
- model.trigger("change:isOwnerOf");
- model.trigger("change:identities");
-
- model.set("checked", true);
- },
- error: function(){
- //Set some blank values and flag as checked
- //model.set("username", "");
- //model.set("fullName", "");
- model.set("notFound", true);
- model.set("checked", true);
- }
- }
- //Send the request
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
-
- }
- }
- }
-
- //Send the request
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- //Get the pending identity map requests, if the service is turned on
- getPendingIdentities: function(){
- if(!MetacatUI.appModel.get("pendingMapsUrl")) return false;
-
- var model = this;
-
- //Get the pending requests
- var requestSettings = {
- url: MetacatUI.appModel.get("pendingMapsUrl") + encodeURIComponent(this.get("username")),
- success: function(data, textStatus, xhr){
- //Reset the equivalent id list so we don't just add it to it with push()
- model.set("pending", model.defaults().pending);
- var pending = model.get("pending");
- _.each($(data).find("person"), function(person, i) {
-
- //Don't list yourself as a pending map request
- var personsUsername = $(person).find("subject").text();
- if(personsUsername.toLowerCase() == model.get("username").toLowerCase())
- return;
-
- //Create a new User Model for this pending identity
- var pendingUser = new UserModel({ rawData: person });
-
- if(pendingUser.isOrcid())
- pendingUser.getInfo();
-
- pending.push(pendingUser);
- });
- model.set("pending", pending);
- model.trigger("change:pending"); //Trigger the change event
- },
- error: function(xhr, textStatus){
- if(xhr.responseText.indexOf("error code 34")){
- model.set("pending", model.defaults().pending);
- model.trigger("change:pending"); //Trigger the change event
- }
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- getNameFromSubject: function(username){
- var username = username || this.get("username"),
- fullName = "";
-
- if(!username) return;
-
- if((username.indexOf("uid=") > -1) && (username.indexOf(",") > -1))
- fullName = username.substring(username.indexOf("uid=") + 4, username.indexOf(","));
- else if((username.indexOf("CN=") > -1) && (username.indexOf(",") > -1))
- fullName = username.substring(username.indexOf("CN=") + 3, username.indexOf(","));
-
- //Cut off the last string after the name when it contains digits - not part of this person's names
- if(fullName.lastIndexOf(" ") > fullName.indexOf(" ")){
- var lastWord = fullName.substring(fullName.lastIndexOf(" "));
- if(lastWord.search(/\d/) > -1)
- fullName = fullName.substring(0, fullName.lastIndexOf(" "));
- }
-
- //Default to the username
- if(!fullName) fullName = this.get("fullname") || username;
-
- return fullName;
- },
-
- isOrcid: function(orcid){
- var username = (typeof orcid === "string")? orcid : this.get("username");
-
- //Have we already verified this?
- if((typeof orcid == "undefined") && (username == this.get("orcid"))) return true;
-
- //Checks for ORCIDs using the orcid base URL as a prefix
- if(username.indexOf("orcid.org/") > -1){
- return true;
- }
-
- //If the ORCID base url is not present, we will check if this is a 19-digit ORCID ID
- //A simple and fast check first
- //ORCiDs are 16 digits and 3 dashes - 19 characters
- if(username.length != 19) return false;
-
- /* The ORCID checksum algorithm to determine is a character string is an ORCiD
- * http://support.orcid.org/knowledgebase/articles/116780-structure-of-the-orcid-identifier
- */
- var total = 0,
- baseDigits = username.replace(/-/g, "").substr(0, 15);
-
- for(var i=0; i 99999))
- issuedAt.setMilliseconds(lifeSpan);
- else if(issuedAt && lifeSpan)
- issuedAt.setSeconds(lifeSpan);
-
- expires = issuedAt;
- }
-
- this.set("expires", expires);
- },
-
- checkToken: function(onSuccess, onError){
-
- //First check if the token has expired
- if(MetacatUI.appUserModel.get("expires") > new Date()){
- if(onSuccess) onSuccess();
-
- return;
- }
-
- var model = this;
-
- var url = MetacatUI.appModel.get("tokenUrl");
- if(!url) return;
-
- var requestSettings = {
- type: "GET",
- url: url,
- headers: {
- "Cache-Control": "no-cache"
- },
- success: function(data, textStatus, xhr){
- if(data){
- // the response should have the token
- var payload = model.parseToken(data),
- username = payload ? payload.userId : null,
- fullName = payload ? payload.fullName : null,
- token = payload ? data : null,
- loggedIn = payload ? true : false;
-
- // set in the model
- model.set('fullName', fullName);
- model.set('username', username);
- model.set("token", token);
- model.set("loggedIn", loggedIn);
-
- model.getTokenExpiration(payload);
-
- MetacatUI.appUserModel.set("checked", true);
-
- if(onSuccess) onSuccess(data, textStatus, xhr);
- }
- else if(onError)
- onError(data, textStatus, xhr);
- },
- error: function(data, textStatus, xhr){
- //If this token in invalid, then reset the user model/log out
- MetacatUI.appUserModel.reset();
-
- if(onError) onError(data, textStatus, xhr);
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- parseToken: function(token) {
- if(typeof token == "undefined")
- var token = this.get("token");
-
- var jws = new KJUR.jws.JWS();
- var result = 0;
- try {
- result = jws.parseJWS(token);
- } catch (ex) {
- result = 0;
- }
-
- if(!jws.parsedJWS) return "";
-
- var payload = $.parseJSON(jws.parsedJWS.payloadS);
- return payload;
- },
-
- update: function(onSuccess, onError){
- var model = this;
-
- var person =
- ''
- + ''
- + '' + this.get("username") + ' '
- + '' + this.get("firstName") + ' '
- + '' + this.get("lastName") + ' '
- + '' + this.get("email") + ' '
- + ' ';
-
- var xmlBlob = new Blob([person], {type : 'application/xml'});
- var formData = new FormData();
- formData.append("subject", this.get("username"));
- formData.append("person", xmlBlob, "person");
-
- var updateUrl = MetacatUI.appModel.get("accountsUrl") + encodeURIComponent(this.get("username"));
-
- // ajax call to update
- var requestSettings = {
- type: "PUT",
- cache: false,
- contentType: false,
- processData: false,
- url: updateUrl,
- data: formData,
- success: function(data, textStatus, xhr) {
- if(typeof onSuccess != "undefined")
- onSuccess(data);
-
- //model.getInfo();
- },
- error: function(data, textStatus, xhr) {
- if(typeof onError != "undefined")
- onError(data);
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- confirmMapRequest: function(otherUsername, onSuccess, onError){
- if(!otherUsername) return;
-
- var mapUrl = MetacatUI.appModel.get("pendingMapsUrl") + encodeURIComponent(otherUsername),
- model = this;
-
- if(!onSuccess)
- var onSuccess = function(){};
- if(!onError)
- var onError = function(){};
-
- // ajax call to confirm map
- var requestSettings = {
- type: "PUT",
- url: mapUrl,
- success: function(data, textStatus, xhr) {
- if(onSuccess)
- onSuccess(data, textStatus, xhr);
-
- //Get updated info
- model.getInfo();
- },
- error: function(xhr, textStatus, error) {
- if(onError)
- onError(xhr, textStatus, error);
- }
- }
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- denyMapRequest: function(otherUsername, onSuccess, onError){
- if(!otherUsername) return;
-
- var mapUrl = MetacatUI.appModel.get("pendingMapsUrl") + encodeURIComponent(otherUsername),
- model = this;
-
- // ajax call to reject map
- var requestSettings = {
- type: "DELETE",
- url: mapUrl,
- success: function(data, textStatus, xhr) {
- if(typeof onSuccess == "function")
- onSuccess(data, textStatus, xhr);
-
- model.getInfo();
- },
- error: function(xhr, textStatus, error) {
- if(typeof onError == "function")
- onError(xhr, textStatus, error);
- }
- }
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- addMap: function(otherUsername, onSuccess, onError){
- if(!otherUsername) return;
-
- var mapUrl = MetacatUI.appModel.get("pendingMapsUrl"),
- model = this;
-
- if(mapUrl.charAt(mapUrl.length-1) == "/"){
- mapUrl = mapUrl.substring(0, mapUrl.length-1)
- }
-
- // ajax call to map
- var requestSettings = {
- type: "POST",
- xhrFields: {
- withCredentials: true
- },
- headers: {
- "Authorization": "Bearer " + this.get("token")
- },
- url: mapUrl,
- data: {
- subject: otherUsername
- },
- success: function(data, textStatus, xhr) {
- if(typeof onSuccess == "function")
- onSuccess(data, textStatus, xhr);
-
- model.getInfo();
- },
- error: function(xhr, textStatus, error) {
-
- //Check if the username might have been spelled or formatted incorrectly
- //ORCIDs, in particular, have different formats that we should account for
- if(xhr.responseText.indexOf("LDAP: error code 32 - No Such Object") > -1 && model.isOrcid(otherUsername)){
- if(otherUsername.length == 19)
- model.addMap("http://orcid.org/" + otherUsername, onSuccess, onError);
- else if(otherUsername.indexOf("https://orcid.org") == 0)
- model.addMap(otherUsername.replace("https", "http"), onSuccess, onError);
- else if(otherUsername.indexOf("orcid.org") == 0)
- model.addMap("http://" + otherUsername, onSuccess, onError);
- else if(otherUsername.indexOf("www.orcid.org") == 0)
- model.addMap(otherUsername.replace("www.", "http://"), onSuccess, onError);
- else if(otherUsername.indexOf("http://www.orcid.org") == 0)
- model.addMap(otherUsername.replace("www.", ""), onSuccess, onError);
- else if(otherUsername.indexOf("https://www.orcid.org") == 0)
- model.addMap(otherUsername.replace("https://www.", "http://"), onSuccess, onError);
- else if(typeof onError == "function")
- onError(xhr, textStatus, error);
- }
- else{
- if(typeof onError == "function")
- onError(xhr, textStatus, error);
- }
- }
- }
-
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- removeMap: function(otherUsername, onSuccess, onError){
- if(!otherUsername) return;
-
- var mapUrl = MetacatUI.appModel.get("accountsMapsUrl") + encodeURIComponent(otherUsername),
- model = this;
-
- // ajax call to remove mapping
- var requestSettings = {
- type: "DELETE",
- url: mapUrl,
- success: function(data, textStatus, xhr) {
- if(typeof onSuccess == "function")
- onSuccess(data, textStatus, xhr);
-
- model.getInfo();
- },
- error: function(xhr, textStatus, error) {
- if(typeof onError == "function")
- onError(xhr, textStatus, error);
- }
- }
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- },
-
- failedLdapLogin: function(){
- this.set("loggedIn", false);
- this.set("checked", true);
- this.set("ldapError", true);
- },
-
- pluckIdentityUsernames: function(){
- var models = this.get("identities"),
- usernames = [];
-
- _.each(models, function(m){
- usernames.push(m.get("username").toLowerCase());
- });
-
- this.set("identitiesUsernames", usernames);
- this.trigger("change:identitiesUsernames");
- },
-
- createReadableUsername: function(){
- if(!this.get("username")) return;
-
- var username = this.get("username"),
- readableUsername = username.substring(username.indexOf("=")+1, username.indexOf(",")) || username;
-
- this.set("usernameReadable", readableUsername);
- },
-
- createAjaxSettings: function(){
- if(!this.get("token")) return {}
-
- return { xhrFields: {
- withCredentials: true
- },
- headers: {
- "Authorization": "Bearer " + this.get("token")
- }
- }
- },
-
- /**
- * Checks if this user has the quota to perform the given action
- * @param {string} action - The action to be performed
- * @param {string} customerGroup - The subject or identifier of the customer/membership group
- * to use this quota against
- */
- checkQuota: function(action, customerGroup){
-
- //Temporarily reset the quota so a trigger event is changed when the XHR is complete
- this.set("portalQuota", -1, {silent: true});
-
- //Start of temporary code
- //TODO: Replace this function with real code once the quota service is working
- this.set("portalQuota", 999);
- return;
- //End of temporary code
-
- /* var model = this;
-
- var requestSettings = {
- url: "",
- type: "GET",
- success: function(data, textStatus, xhr) {
- model.set("portalQuota", data.remainingQuota);
- },
- error: function(xhr, textStatus, errorThrown) {
- model.set("portalQuota", 0);
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "jws",
+ "models/Search",
+ "collections/SolrResults",
+], function ($, _, Backbone, JWS, SearchModel, SearchResults) {
+ "use strict";
+
+ /**
+ * @class UserModel
+ * @classcategory Models
+ * @extends Backbone.Model
+ * @constructor
+ */
+ var UserModel = Backbone.Model.extend(
+ /** @lends UserModel.prototype */ {
+ defaults: function () {
+ return {
+ type: "person", //assume this is a person unless we are told otherwise - other possible type is a "group"
+ checked: false, //Is set to true when we have checked the account/subject info of this user
+ tokenChecked: false, //Is set to true when the uer auth token has been checked
+ basicUser: false, //Set to true to only query for basic info about this user - prevents sending queries for info that will never be displayed in the UI
+ lastName: null,
+ firstName: null,
+ fullName: null,
+ email: null,
+ logo: null,
+ description: null,
+ verified: null,
+ username: null,
+ usernameReadable: null,
+ orcid: null,
+ searchModel: null,
+ searchResults: null,
+ loggedIn: false,
+ ldapError: false, //Was there an error logging in to LDAP
+ registered: false,
+ isMemberOf: [],
+ isOwnerOf: [],
+ identities: [],
+ identitiesUsernames: [],
+ allIdentitiesAndGroups: [],
+ pending: [],
+ token: null,
+ expires: null,
+ timeoutId: null,
+ rawData: null,
+ portalQuota: -1,
+ isAuthorizedCreatePortal: null,
+ dataoneQuotas: null,
+ dataoneSubscription: null,
+ };
+ },
+
+ initialize: function (options) {
+ if (typeof options !== "undefined") {
+ if (options.username) this.set("username", options.username);
+ if (options.rawData) this.set(this.parseXML(options.rawData));
}
- }
- $.ajax(_.extend(requestSettings, this.createAjaxSettings()));
-*/
- },
+ this.on("change:identities", this.pluckIdentityUsernames);
- /**
- * Checks if the user has authorization to perform the given action.
- */
- isAuthorizedCreatePortal: function(){
+ this.on(
+ "change:username change:identities change:type",
+ this.updateSearchModel,
+ );
+ this.createSearchModel();
- //Reset the isAuthorized attribute silently so a change event is always triggered
- this.set("isAuthorizedCreatePortal", null, {silent: true});
+ this.on("change:username", this.createReadableUsername());
- //If the user isn't logged in, set authorization to false
- if( !this.get("loggedIn") ){
- this.set("isAuthorizedCreatePortal", false);
- return;
- }
+ //Create a search results model for this person
+ var searchResults = new SearchResults([], { rows: 5, start: 0 });
+ this.set("searchResults", searchResults);
- //If creating portals has been disabled app-wide, then set to false
- if( MetacatUI.appModel.get("enableCreatePortals") === false ){
- this.set("isAuthorizedCreatePortal", false);
- return;
- }
- //If creating portals has been limited to only certain subjects, check if this user is one of them
- else if( MetacatUI.appModel.get("limitPortalsToSubjects").length ){
- if( !this.get("allIdentitiesAndGroups").length ){
- this.on("change:allIdentitiesAndGroups", this.isAuthorizedCreatePortal);
- return;
+ if (MetacatUI.appModel.get("enableBookkeeperServices")) {
+ //When the user is logged in, see if they have a DataONE subscription
+ this.on("change:loggedIn", this.fetchSubscription);
}
-
- //Find the subjects that have access to create portals. Could be specific users or groups.
- var subjectsThatHaveAccess = _.intersection(MetacatUI.appModel.get("limitPortalsToSubjects"), this.get("allIdentitiesAndGroups"));
- if( !subjectsThatHaveAccess.length ){
- //If this user is not in the whitelist, set to false
- this.set("isAuthorizedCreatePortal", false);
+ },
+
+ createSearchModel: function () {
+ //Create a search model that will retrieve data created by this person
+ this.set("searchModel", new SearchModel());
+ this.updateSearchModel();
+ },
+
+ updateSearchModel: function () {
+ if (this.get("type") == "node") {
+ this.get("searchModel").set("dataSource", [
+ this.get("node").identifier,
+ ]);
+ this.get("searchModel").set("username", []);
+ } else {
+ //Get all the identities for this person
+ var ids = [this.get("username")];
+
+ _.each(this.get("identities"), function (equivalentUser) {
+ ids.push(equivalentUser.get("username"));
+ });
+ this.get("searchModel").set("username", ids);
}
- else{
- //If this user is in the whitelist, set to true
- this.set("isAuthorizedCreatePortal", true);
+
+ this.trigger("change:searchModel");
+ },
+
+ parseXML: function (data) {
+ var model = this,
+ username = this.get("username");
+
+ //Reset the group list so we don't just add it to it with push()
+ this.set("isMemberOf", this.defaults().isMemberOf, { silent: true });
+ this.set("isOwnerOf", this.defaults().isOwnerOf, { silent: true });
+ //Reset the equivalent id list so we don't just add it to it with push()
+ this.set("identities", this.defaults().identities, { silent: true });
+
+ //Find this person's node in the XML
+ var userNode = null;
+ if (!username) var username = $(data).children("subject").text();
+ if (username) {
+ var subjects = $(data).find("subject");
+ for (var i = 0; i < subjects.length; i++) {
+ if ($(subjects[i]).text().toLowerCase() == username.toLowerCase()) {
+ userNode = $(subjects[i]).parent();
+ break;
+ }
+ }
}
- return;
- }
- //If anyone is allowed to create a portal, check if they have the quota to create a portal
- else if( MetacatUI.appModel.get("enableBookkeeperServices") ){
+ if (!userNode) userNode = $(data).first();
+
+ //Get the type of user - either a person or group
+ var type = $(userNode).prop("tagName");
+ if (type) type = type.toLowerCase();
+
+ if (type == "group") {
+ var fullName = $(userNode).find("groupName").first().text();
+ } else if (type) {
+ //Find the person's info
+ var firstName = $(userNode).find("givenName").first().text(),
+ lastName = $(userNode).find("familyName").first().text(),
+ email = $(userNode).find("email").first().text(),
+ verified = $(userNode).find("verified").first().text(),
+ memberOf = this.get("isMemberOf"),
+ ownerOf = this.get("isOwnerOf"),
+ identities = this.get("identities"),
+ equivUsernames = [];
+
+ //Sometimes names are saved as "NA" when they are not available - translate these to false values
+ if (firstName == "NA") firstName = null;
+ if (lastName == "NA") lastName = null;
+
+ //Construct the fullname from the first and last names, but watch out for falsely values
+ var fullName = "";
+ fullName += firstName ? firstName : "";
+ fullName += lastName ? " " + lastName : "";
+
+ if (!fullName) fullName = this.getNameFromSubject(username);
+
+ //Don't get this detailed info about basic users
+ if (!this.get("basicUser")) {
+ //Get all the equivalent identities for this user
+ var equivalentIds = $(userNode).find("equivalentIdentity");
+ if (equivalentIds.length > 0)
+ var allPersons = $(data).find("person subject");
+
+ _.each(equivalentIds, function (identity, i) {
+ //push onto the list
+ var username = $(identity).text(),
+ equivUserNode;
+
+ //Find the matching person node in the response
+ _.each(allPersons, function (person) {
+ if ($(person).text().toLowerCase() == username.toLowerCase()) {
+ equivUserNode = $(person).parent().first();
+ allPersons = _.without(allPersons, person);
+ }
+ });
+
+ var equivalentUser = new UserModel({
+ username: username,
+ basicUser: true,
+ rawData: equivUserNode,
+ });
+ identities.push(equivalentUser);
+ equivUsernames.push(username);
+ });
+ }
- //Get the Quotas for this user
- var quotas = this.get("dataoneQuotas"),
- portalQuotas;
+ //Get each group and save
+ _.each($(data).find("group"), function (group, i) {
+ //Save group ID
+ var groupId = $(group).find("subject").first().text(),
+ groupName = $(group).find("groupName").text();
+
+ memberOf.push({ groupId: groupId, name: groupName });
- //If the Quotas are still being fetched,
- if(quotas == this.defaults().dataoneQuotas && !quotas){
- this.on("change:dataoneQuotas", this.isAuthorizedCreatePortal);
+ //Check if this person is a rightsholder
+ var allRightsHolders = [];
+ _.each($(group).children("rightsHolder"), function (rightsHolder) {
+ allRightsHolders.push($(rightsHolder).text().toLowerCase());
+ });
+ if (_.contains(allRightsHolders, username.toLowerCase()))
+ ownerOf.push(groupId);
+ });
+ }
+
+ var allSubjects = _.pluck(this.get("isMemberOf"), "groupId");
+ allSubjects.push(this.get("username"));
+ allSubjects = allSubjects.concat(equivUsernames);
+
+ return {
+ isMemberOf: memberOf,
+ isOwnerOf: ownerOf,
+ identities: identities,
+ allIdentitiesAndGroups: allSubjects,
+ verified: verified,
+ username: username,
+ firstName: firstName,
+ lastName: lastName,
+ fullName: fullName,
+ email: email,
+ registered: true,
+ type: type,
+ rawData: data,
+ };
+ },
+
+ getInfo: function () {
+ var model = this;
+
+ //If the accounts service is not on, flag this user as checked/completed
+ if (!MetacatUI.appModel.get("accountsUrl")) {
+ this.set("fullName", this.getNameFromSubject());
+ this.set("checked", true);
return;
}
- else{
- portalQuotas = quotas.where({ quotaType: "portal" });
+
+ //Only proceed if there is a username
+ if (!this.get("username")) return;
+
+ //Get the user info using the DataONE API
+ var url =
+ MetacatUI.appModel.get("accountsUrl") +
+ encodeURIComponent(this.get("username"));
+
+ var requestSettings = {
+ type: "GET",
+ url: url,
+ success: function (data, textStatus, xhr) {
+ //Parse the XML response to get user info
+ var userProperties = model.parseXML(data);
+ //Filter out all the falsey values
+ _.each(userProperties, function (v, k) {
+ if (!v) {
+ delete userProperties[k];
+ }
+ });
+ model.set(userProperties);
+
+ //Trigger the change events
+ model.trigger("change:isMemberOf");
+ model.trigger("change:isOwnerOf");
+ model.trigger("change:identities");
+
+ model.set("checked", true);
+ },
+ error: function (xhr, textStatus, errorThrown) {
+ // Sometimes the node info has not been received before this getInfo() is called.
+ // If the node info was received while this getInfo request was pending, and this user was determined
+ // to be a node, then we can skip any further action here.
+ if (model.get("type") == "node") return;
+
+ if (xhr.status == 404 && MetacatUI.nodeModel.get("checked")) {
+ model.set("fullName", model.getNameFromSubject());
+ model.set("checked", true);
+ } else if (
+ xhr.status == 404 &&
+ !MetacatUI.nodeModel.get("checked")
+ ) {
+ model.listenToOnce(
+ MetacatUI.nodeModel,
+ "change:checked",
+ function () {
+ if (!model.isNode()) {
+ model.set("fullName", model.getNameFromSubject());
+ model.set("checked", true);
+ }
+ },
+ );
+ } else {
+ //As a backup, search for this user instead
+ var requestSettings = {
+ type: "GET",
+ url:
+ MetacatUI.appModel.get("accountsUrl") +
+ "?query=" +
+ encodeURIComponent(model.get("username")),
+ success: function (data, textStatus, xhr) {
+ //Parse the XML response to get user info
+ model.set(model.parseXML(data));
+
+ //Trigger the change events
+ model.trigger("change:isMemberOf");
+ model.trigger("change:isOwnerOf");
+ model.trigger("change:identities");
+
+ model.set("checked", true);
+ },
+ error: function () {
+ //Set some blank values and flag as checked
+ //model.set("username", "");
+ //model.set("fullName", "");
+ model.set("notFound", true);
+ model.set("checked", true);
+ },
+ };
+ //Send the request
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ }
+ },
+ };
+
+ //Send the request
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ //Get the pending identity map requests, if the service is turned on
+ getPendingIdentities: function () {
+ if (!MetacatUI.appModel.get("pendingMapsUrl")) return false;
+
+ var model = this;
+
+ //Get the pending requests
+ var requestSettings = {
+ url:
+ MetacatUI.appModel.get("pendingMapsUrl") +
+ encodeURIComponent(this.get("username")),
+ success: function (data, textStatus, xhr) {
+ //Reset the equivalent id list so we don't just add it to it with push()
+ model.set("pending", model.defaults().pending);
+ var pending = model.get("pending");
+ _.each($(data).find("person"), function (person, i) {
+ //Don't list yourself as a pending map request
+ var personsUsername = $(person).find("subject").text();
+ if (
+ personsUsername.toLowerCase() ==
+ model.get("username").toLowerCase()
+ )
+ return;
+
+ //Create a new User Model for this pending identity
+ var pendingUser = new UserModel({ rawData: person });
+
+ if (pendingUser.isOrcid()) pendingUser.getInfo();
+
+ pending.push(pendingUser);
+ });
+ model.set("pending", pending);
+ model.trigger("change:pending"); //Trigger the change event
+ },
+ error: function (xhr, textStatus) {
+ if (xhr.responseText.indexOf("error code 34")) {
+ model.set("pending", model.defaults().pending);
+ model.trigger("change:pending"); //Trigger the change event
+ }
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ getNameFromSubject: function (username) {
+ var username = username || this.get("username"),
+ fullName = "";
+
+ if (!username) return;
+
+ if (username.indexOf("uid=") > -1 && username.indexOf(",") > -1)
+ fullName = username.substring(
+ username.indexOf("uid=") + 4,
+ username.indexOf(","),
+ );
+ else if (username.indexOf("CN=") > -1 && username.indexOf(",") > -1)
+ fullName = username.substring(
+ username.indexOf("CN=") + 3,
+ username.indexOf(","),
+ );
+
+ //Cut off the last string after the name when it contains digits - not part of this person's names
+ if (fullName.lastIndexOf(" ") > fullName.indexOf(" ")) {
+ var lastWord = fullName.substring(fullName.lastIndexOf(" "));
+ if (lastWord.search(/\d/) > -1)
+ fullName = fullName.substring(0, fullName.lastIndexOf(" "));
}
- //If this user has no portal Quota at all, they are not auth to create a portal
- if( !portalQuotas ){
- this.set("isAuthorizedCreatePortal", false);
+ //Default to the username
+ if (!fullName) fullName = this.get("fullname") || username;
+
+ return fullName;
+ },
+
+ isOrcid: function (orcid) {
+ var username = typeof orcid === "string" ? orcid : this.get("username");
+
+ //Have we already verified this?
+ if (typeof orcid == "undefined" && username == this.get("orcid"))
+ return true;
+
+ //Checks for ORCIDs using the orcid base URL as a prefix
+ if (username.indexOf("orcid.org/") > -1) {
+ return true;
}
- else{
- //Check that there is at least one Quota where the totalUsage < softLimit
- var hasRemainingUsage = _.some(portalQuotas, function(quota){
- return quota.get("totalUsage") < quota.get("softLimit");
- });
+ //If the ORCID base url is not present, we will check if this is a 19-digit ORCID ID
+ //A simple and fast check first
+ //ORCiDs are 16 digits and 3 dashes - 19 characters
+ if (username.length != 19) return false;
- //If there is remaining usage left in at least one Quota, then the user can create a portal
- if( hasRemainingUsage ){
- this.set("isAuthorizedCreatePortal", true);
- }
- //Otherwise they cannot create a new portal
- else{
- this.set("isAuthorizedCreatePortal", false);
- }
+ /* The ORCID checksum algorithm to determine is a character string is an ORCiD
+ * http://support.orcid.org/knowledgebase/articles/116780-structure-of-the-orcid-identifier
+ */
+ var total = 0,
+ baseDigits = username.replace(/-/g, "").substr(0, 15);
+
+ for (var i = 0; i < baseDigits.length; i++) {
+ var digit = parseInt(baseDigits.charAt(i));
+ total = (total + digit) * 2;
}
- //@todoGet the admin group and force admins to have at least one quota left
+ var remainder = total % 11,
+ result = (12 - remainder) % 11,
+ checkDigit = result == 10 ? "X" : result.toString(),
+ isOrcid = checkDigit == username.charAt(username.length - 1);
+
+ if (isOrcid) this.set("orcid", username);
+
+ return isOrcid;
+ },
+
+ isNode: function () {
+ var model = this;
+ var node = _.find(
+ MetacatUI.nodeModel.get("members"),
+ function (nodeModel) {
+ return (
+ nodeModel.shortIdentifier.toLowerCase() ==
+ model.get("username").toLowerCase()
+ );
+ },
+ );
+
+ return node && node !== undefined;
+ },
+
+ // Will check if this user is a Member Node. If so, it will save the MN info to the model
+ saveAsNode: function () {
+ if (!this.isNode()) return;
+
+ var model = this;
+ var node = _.find(
+ MetacatUI.nodeModel.get("members"),
+ function (nodeModel) {
+ return (
+ nodeModel.shortIdentifier.toLowerCase() ==
+ model.get("username").toLowerCase()
+ );
+ },
+ );
+
+ this.set({
+ type: "node",
+ logo: node.logo,
+ description: node.description,
+ node: node,
+ fullName: node.name,
+ usernameReadable: this.get("username"),
+ });
+ this.updateSearchModel();
+ this.set("checked", true);
+ },
+
+ loginLdap: function (formData, success, error) {
+ if (!formData || !appModel.get("signInUrlLdap")) return false;
+
+ var model = this;
+
+ var requestSettings = {
+ type: "POST",
+ url: MetacatUI.appModel.get("signInUrlLdap") + window.location.href,
+ data: formData,
+ success: function (data, textStatus, xhr) {
+ if (success) success(this);
+
+ model.getToken();
+ },
+ error: function () {
+ /*if(error)
+ error(this);
+ */
+ model.getToken();
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ logout: function () {
+ //Construct the sign out url and redirect
+ var signOutUrl = MetacatUI.appModel.get("signOutUrl"),
+ target = Backbone.history.location.href;
+
+ // DO NOT include the route otherwise we have an infinite redirect
+ // target = target.split("#")[0];
+ target = target.slice(0, -8);
+
+ // make sure to include the target
+ signOutUrl += "?target=" + target;
+
+ // do it!
+ window.location.replace(signOutUrl);
+ },
+
+ // call Metacat or the DataONE CN to validate the session and tell us the user's name
+ checkStatus: function (onSuccess, onError) {
+ var model = this;
+
+ if (!MetacatUI.appModel.get("tokenUrl")) {
+ // look up the URL
+ var metacatUrl = MetacatUI.appModel.get("metacatServiceUrl");
+
+ // ajax call to validate the session/get the user info
+ var requestSettings = {
+ type: "POST",
+ url: metacatUrl,
+ data: { action: "validatesession" },
+ success: function (data, textStatus, xhr) {
+ // the Metacat (XML) response should have a fullName element
+ var username = $(data).find("name").text();
+
+ // set in the model
+ model.set("username", username);
+
+ //Are we logged in?
+ if (username) {
+ model.set("loggedIn", true);
+ model.getInfo();
+ } else {
+ model.set("loggedIn", false);
+ model.trigger("change:loggedIn");
+ model.set("checked", true);
+ }
+
+ if (onSuccess) onSuccess(data);
+ },
+ error: function (data, textStatus, xhr) {
+ //User is not logged in
+ model.reset();
+
+ if (onError) onError();
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ } else {
+ // use the token method for checking authentication
+ this.getToken();
+ }
+ },
+
+ getToken: function (customCallback) {
+ var tokenUrl = MetacatUI.appModel.get("tokenUrl");
+ var model = this;
+
+ if (!tokenUrl) return false;
+
+ //Set up the function that will be called when we retrieve a token
+ var callback =
+ typeof customCallback === "function"
+ ? customCallback
+ : function (data, textStatus, xhr) {
+ // the response should have the token
+ var payload = model.parseToken(data),
+ username = payload ? payload.userId : null,
+ fullName = payload
+ ? payload.fullName
+ : model.getNameFromSubject(username) || null,
+ token = payload ? data : null,
+ loggedIn = payload ? true : false;
+
+ // set in the model
+ model.set("fullName", fullName);
+ model.set("username", username);
+ model.set("token", token);
+ model.set("loggedIn", loggedIn);
+ model.set("tokenChecked", true);
+
+ model.getTokenExpiration(payload);
+
+ if (username) model.getInfo();
+ else model.set("checked", true);
+ };
+
+ // ajax call to get token
+ var requestSettings = {
+ type: "GET",
+ dataType: "text",
+ xhrFields: {
+ withCredentials: true,
+ },
+ url: tokenUrl,
+ data: {},
+ success: callback,
+ error: function (xhr, textStatus, errorThrown) {
+ model.set("checked", true);
+ },
+ };
+
+ $.ajax(requestSettings);
+ },
+
+ getTokenExpiration: function (payload) {
+ if (!payload && this.get("token"))
+ var payload = this.parseToken(this.get("token"));
+ if (!payload) return;
+
+ //The exp claim should be standard - it is in UTC seconds
+ var expires = payload.exp ? new Date(payload.exp * 1000) : null;
+
+ //Use the issuedAt and ttl as a backup (only used in d1 2.0.0 and 2.0.1)
+ if (!expires) {
+ var issuedAt = payload.issuedAt ? new Date(payload.issuedAt) : null,
+ lifeSpan = payload.ttl ? payload.ttl : null;
+
+ if (issuedAt && lifeSpan && lifeSpan > 99999)
+ issuedAt.setMilliseconds(lifeSpan);
+ else if (issuedAt && lifeSpan) issuedAt.setSeconds(lifeSpan);
+
+ expires = issuedAt;
+ }
- }
- else{
- //Default to letting people create portals
- this.set("isAuthorizedCreatePortal", true);
- }
+ this.set("expires", expires);
+ },
- },
+ checkToken: function (onSuccess, onError) {
+ //First check if the token has expired
+ if (MetacatUI.appUserModel.get("expires") > new Date()) {
+ if (onSuccess) onSuccess();
- /**
- * Given a list of user and/or group subjects, this function checks if this user
- * has an equivalent identity in that list, or is a member of a group in the list.
- * A single subject string can be passed instead of an array of subjects.
- * TODO: This needs to support nested group membership.
- * @param {string|string[]} subjects
- * @returns {boolean}
- */
- hasIdentityOverlap: function(subjects){
-
- try{
- //If only a single subject is given, put it in an array
- if( typeof subjects == "string" ){
- subjects = [subjects];
+ return;
}
- //If the subjects are not a string or an array, or if it's an empty array, exit this function.
- else if( !Array.isArray(subjects) || !subjects.length ){
- return false;
+
+ var model = this;
+
+ var url = MetacatUI.appModel.get("tokenUrl");
+ if (!url) return;
+
+ var requestSettings = {
+ type: "GET",
+ url: url,
+ headers: {
+ "Cache-Control": "no-cache",
+ },
+ success: function (data, textStatus, xhr) {
+ if (data) {
+ // the response should have the token
+ var payload = model.parseToken(data),
+ username = payload ? payload.userId : null,
+ fullName = payload ? payload.fullName : null,
+ token = payload ? data : null,
+ loggedIn = payload ? true : false;
+
+ // set in the model
+ model.set("fullName", fullName);
+ model.set("username", username);
+ model.set("token", token);
+ model.set("loggedIn", loggedIn);
+
+ model.getTokenExpiration(payload);
+
+ MetacatUI.appUserModel.set("checked", true);
+
+ if (onSuccess) onSuccess(data, textStatus, xhr);
+ } else if (onError) onError(data, textStatus, xhr);
+ },
+ error: function (data, textStatus, xhr) {
+ //If this token in invalid, then reset the user model/log out
+ MetacatUI.appUserModel.reset();
+
+ if (onError) onError(data, textStatus, xhr);
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ parseToken: function (token) {
+ if (typeof token == "undefined") var token = this.get("token");
+
+ var jws = new KJUR.jws.JWS();
+ var result = 0;
+ try {
+ result = jws.parseJWS(token);
+ } catch (ex) {
+ result = 0;
}
- return _.intersection(this.get("allIdentitiesAndGroups"), subjects).length;
- }
- catch(e){
- console.error(e);
- return false;
- }
+ if (!jws.parsedJWS) return "";
+
+ var payload = $.parseJSON(jws.parsedJWS.payloadS);
+ return payload;
+ },
+
+ update: function (onSuccess, onError) {
+ var model = this;
+
+ var person =
+ '' +
+ '' +
+ "" +
+ this.get("username") +
+ " " +
+ "" +
+ this.get("firstName") +
+ " " +
+ "" +
+ this.get("lastName") +
+ " " +
+ "" +
+ this.get("email") +
+ " " +
+ " ";
+
+ var xmlBlob = new Blob([person], { type: "application/xml" });
+ var formData = new FormData();
+ formData.append("subject", this.get("username"));
+ formData.append("person", xmlBlob, "person");
+
+ var updateUrl =
+ MetacatUI.appModel.get("accountsUrl") +
+ encodeURIComponent(this.get("username"));
+
+ // ajax call to update
+ var requestSettings = {
+ type: "PUT",
+ cache: false,
+ contentType: false,
+ processData: false,
+ url: updateUrl,
+ data: formData,
+ success: function (data, textStatus, xhr) {
+ if (typeof onSuccess != "undefined") onSuccess(data);
+
+ //model.getInfo();
+ },
+ error: function (data, textStatus, xhr) {
+ if (typeof onError != "undefined") onError(data);
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ confirmMapRequest: function (otherUsername, onSuccess, onError) {
+ if (!otherUsername) return;
+
+ var mapUrl =
+ MetacatUI.appModel.get("pendingMapsUrl") +
+ encodeURIComponent(otherUsername),
+ model = this;
+
+ if (!onSuccess) var onSuccess = function () {};
+ if (!onError) var onError = function () {};
+
+ // ajax call to confirm map
+ var requestSettings = {
+ type: "PUT",
+ url: mapUrl,
+ success: function (data, textStatus, xhr) {
+ if (onSuccess) onSuccess(data, textStatus, xhr);
+
+ //Get updated info
+ model.getInfo();
+ },
+ error: function (xhr, textStatus, error) {
+ if (onError) onError(xhr, textStatus, error);
+ },
+ };
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ denyMapRequest: function (otherUsername, onSuccess, onError) {
+ if (!otherUsername) return;
+
+ var mapUrl =
+ MetacatUI.appModel.get("pendingMapsUrl") +
+ encodeURIComponent(otherUsername),
+ model = this;
+
+ // ajax call to reject map
+ var requestSettings = {
+ type: "DELETE",
+ url: mapUrl,
+ success: function (data, textStatus, xhr) {
+ if (typeof onSuccess == "function")
+ onSuccess(data, textStatus, xhr);
+
+ model.getInfo();
+ },
+ error: function (xhr, textStatus, error) {
+ if (typeof onError == "function") onError(xhr, textStatus, error);
+ },
+ };
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ addMap: function (otherUsername, onSuccess, onError) {
+ if (!otherUsername) return;
+
+ var mapUrl = MetacatUI.appModel.get("pendingMapsUrl"),
+ model = this;
+
+ if (mapUrl.charAt(mapUrl.length - 1) == "/") {
+ mapUrl = mapUrl.substring(0, mapUrl.length - 1);
+ }
- },
+ // ajax call to map
+ var requestSettings = {
+ type: "POST",
+ xhrFields: {
+ withCredentials: true,
+ },
+ headers: {
+ Authorization: "Bearer " + this.get("token"),
+ },
+ url: mapUrl,
+ data: {
+ subject: otherUsername,
+ },
+ success: function (data, textStatus, xhr) {
+ if (typeof onSuccess == "function")
+ onSuccess(data, textStatus, xhr);
+
+ model.getInfo();
+ },
+ error: function (xhr, textStatus, error) {
+ //Check if the username might have been spelled or formatted incorrectly
+ //ORCIDs, in particular, have different formats that we should account for
+ if (
+ xhr.responseText.indexOf("LDAP: error code 32 - No Such Object") >
+ -1 &&
+ model.isOrcid(otherUsername)
+ ) {
+ if (otherUsername.length == 19)
+ model.addMap(
+ "http://orcid.org/" + otherUsername,
+ onSuccess,
+ onError,
+ );
+ else if (otherUsername.indexOf("https://orcid.org") == 0)
+ model.addMap(
+ otherUsername.replace("https", "http"),
+ onSuccess,
+ onError,
+ );
+ else if (otherUsername.indexOf("orcid.org") == 0)
+ model.addMap("http://" + otherUsername, onSuccess, onError);
+ else if (otherUsername.indexOf("www.orcid.org") == 0)
+ model.addMap(
+ otherUsername.replace("www.", "http://"),
+ onSuccess,
+ onError,
+ );
+ else if (otherUsername.indexOf("http://www.orcid.org") == 0)
+ model.addMap(
+ otherUsername.replace("www.", ""),
+ onSuccess,
+ onError,
+ );
+ else if (otherUsername.indexOf("https://www.orcid.org") == 0)
+ model.addMap(
+ otherUsername.replace("https://www.", "http://"),
+ onSuccess,
+ onError,
+ );
+ else if (typeof onError == "function")
+ onError(xhr, textStatus, error);
+ } else {
+ if (typeof onError == "function") onError(xhr, textStatus, error);
+ }
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ removeMap: function (otherUsername, onSuccess, onError) {
+ if (!otherUsername) return;
+
+ var mapUrl =
+ MetacatUI.appModel.get("accountsMapsUrl") +
+ encodeURIComponent(otherUsername),
+ model = this;
+
+ // ajax call to remove mapping
+ var requestSettings = {
+ type: "DELETE",
+ url: mapUrl,
+ success: function (data, textStatus, xhr) {
+ if (typeof onSuccess == "function")
+ onSuccess(data, textStatus, xhr);
+
+ model.getInfo();
+ },
+ error: function (xhr, textStatus, error) {
+ if (typeof onError == "function") onError(xhr, textStatus, error);
+ },
+ };
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ ),
+ );
+ },
+
+ failedLdapLogin: function () {
+ this.set("loggedIn", false);
+ this.set("checked", true);
+ this.set("ldapError", true);
+ },
+
+ pluckIdentityUsernames: function () {
+ var models = this.get("identities"),
+ usernames = [];
+
+ _.each(models, function (m) {
+ usernames.push(m.get("username").toLowerCase());
+ });
+
+ this.set("identitiesUsernames", usernames);
+ this.trigger("change:identitiesUsernames");
+ },
+
+ createReadableUsername: function () {
+ if (!this.get("username")) return;
+
+ var username = this.get("username"),
+ readableUsername =
+ username.substring(
+ username.indexOf("=") + 1,
+ username.indexOf(","),
+ ) || username;
+
+ this.set("usernameReadable", readableUsername);
+ },
- /**
- * Retrieve all the info about this user's DataONE Subscription
- */
- fetchSubscription: function(){
+ createAjaxSettings: function () {
+ if (!this.get("token")) return {};
- //If Bookkeeper services are disabled, exit
- if( !MetacatUI.appModel.get("enableBookkeeperServices") ){
+ return {
+ xhrFields: {
+ withCredentials: true,
+ },
+ headers: {
+ Authorization: "Bearer " + this.get("token"),
+ },
+ };
+ },
+
+ /**
+ * Checks if this user has the quota to perform the given action
+ * @param {string} action - The action to be performed
+ * @param {string} customerGroup - The subject or identifier of the customer/membership group
+ * to use this quota against
+ */
+ checkQuota: function (action, customerGroup) {
+ //Temporarily reset the quota so a trigger event is changed when the XHR is complete
+ this.set("portalQuota", -1, { silent: true });
+
+ //Start of temporary code
+ //TODO: Replace this function with real code once the quota service is working
+ this.set("portalQuota", 999);
return;
+ //End of temporary code
+
+ /* var model = this;
+
+ var requestSettings = {
+ url: "",
+ type: "GET",
+ success: function(data, textStatus, xhr) {
+ model.set("portalQuota", data.remainingQuota);
+ },
+ error: function(xhr, textStatus, errorThrown) {
+ model.set("portalQuota", 0);
+ }
}
- try{
- var thisUser = this;
- require(["collections/bookkeeper/Quotas", "models/bookkeeper/Subscription"], function(Quotas, Subscription){
-
- //Create a Quotas collection
- var quotas = new Quotas();
-
- //Create a Subscription model
- var subscription = new Subscription();
-
- if( MetacatUI.appModel.get("dataonePlusPreviewMode") ){
- //Create Quota models for preview mode
- quotas.add({
- softLimit: MetacatUI.appModel.get("portalLimit"),
- hardLimit: MetacatUI.appModel.get("portalLimit"),
- quotaType: "portal",
- unit: "portal",
- subject: thisUser.get("username"),
- subscription: subscription
- });
+ $.ajax(_.extend(requestSettings, this.createAjaxSettings()));
+*/
+ },
- //Default to all people being in trial mode
- subscription.set("status", "trialing");
+ /**
+ * Checks if the user has authorization to perform the given action.
+ */
+ isAuthorizedCreatePortal: function () {
+ //Reset the isAuthorized attribute silently so a change event is always triggered
+ this.set("isAuthorizedCreatePortal", null, { silent: true });
- //Save a reference to the Quotas on this UserModel
- thisUser.set("dataoneQuotas", quotas);
+ //If the user isn't logged in, set authorization to false
+ if (!this.get("loggedIn")) {
+ this.set("isAuthorizedCreatePortal", false);
+ return;
+ }
- //Save a reference to the Subscriptioin on this UserModel
- thisUser.set("dataoneSubscription", subscription);
+ //If creating portals has been disabled app-wide, then set to false
+ if (MetacatUI.appModel.get("enableCreatePortals") === false) {
+ this.set("isAuthorizedCreatePortal", false);
+ return;
+ }
+ //If creating portals has been limited to only certain subjects, check if this user is one of them
+ else if (MetacatUI.appModel.get("limitPortalsToSubjects").length) {
+ if (!this.get("allIdentitiesAndGroups").length) {
+ this.on(
+ "change:allIdentitiesAndGroups",
+ this.isAuthorizedCreatePortal,
+ );
+ return;
+ }
+ //Find the subjects that have access to create portals. Could be specific users or groups.
+ var subjectsThatHaveAccess = _.intersection(
+ MetacatUI.appModel.get("limitPortalsToSubjects"),
+ this.get("allIdentitiesAndGroups"),
+ );
+ if (!subjectsThatHaveAccess.length) {
+ //If this user is not in the whitelist, set to false
+ this.set("isAuthorizedCreatePortal", false);
+ } else {
+ //If this user is in the whitelist, set to true
+ this.set("isAuthorizedCreatePortal", true);
}
- else{
- thisUser.listenToOnce(quotas, "reset", function(){
- //Save a reference to the Quotas on this UserModel
- thisUser.set("dataoneQuotas", quotas);
- });
+ return;
+ }
+ //If anyone is allowed to create a portal, check if they have the quota to create a portal
+ else if (MetacatUI.appModel.get("enableBookkeeperServices")) {
+ //Get the Quotas for this user
+ var quotas = this.get("dataoneQuotas"),
+ portalQuotas;
- thisUser.listenToOnce(subscription, "sync", function(){
- //Save a reference to the Subscriptioin on this UserModel
- thisUser.set("dataoneSubscription", subscription);
+ //If the Quotas are still being fetched,
+ if (quotas == this.defaults().dataoneQuotas && !quotas) {
+ this.on("change:dataoneQuotas", this.isAuthorizedCreatePortal);
+ return;
+ } else {
+ portalQuotas = quotas.where({ quotaType: "portal" });
+ }
+
+ //If this user has no portal Quota at all, they are not auth to create a portal
+ if (!portalQuotas) {
+ this.set("isAuthorizedCreatePortal", false);
+ } else {
+ //Check that there is at least one Quota where the totalUsage < softLimit
+ var hasRemainingUsage = _.some(portalQuotas, function (quota) {
+ return quota.get("totalUsage") < quota.get("softLimit");
});
- //Fetch the Quotas
- quotas.fetch({ subscriber: thisUser.get("username") });
+ //If there is remaining usage left in at least one Quota, then the user can create a portal
+ if (hasRemainingUsage) {
+ this.set("isAuthorizedCreatePortal", true);
+ }
+ //Otherwise they cannot create a new portal
+ else {
+ this.set("isAuthorizedCreatePortal", false);
+ }
+ }
- //Fetch the Subscriptioin
- subscription.fetch();
+ //@todoGet the admin group and force admins to have at least one quota left
+ } else {
+ //Default to letting people create portals
+ this.set("isAuthorizedCreatePortal", true);
+ }
+ },
+
+ /**
+ * Given a list of user and/or group subjects, this function checks if this user
+ * has an equivalent identity in that list, or is a member of a group in the list.
+ * A single subject string can be passed instead of an array of subjects.
+ * TODO: This needs to support nested group membership.
+ * @param {string|string[]} subjects
+ * @returns {boolean}
+ */
+ hasIdentityOverlap: function (subjects) {
+ try {
+ //If only a single subject is given, put it in an array
+ if (typeof subjects == "string") {
+ subjects = [subjects];
+ }
+ //If the subjects are not a string or an array, or if it's an empty array, exit this function.
+ else if (!Array.isArray(subjects) || !subjects.length) {
+ return false;
}
- });
- }
- catch(e){
- console.error("Couldn't get DataONE Subscription info. Proceeding as an unsubscribed user. ", e);
- }
+ return _.intersection(this.get("allIdentitiesAndGroups"), subjects)
+ .length;
+ } catch (e) {
+ console.error(e);
+ return false;
+ }
+ },
+
+ /**
+ * Retrieve all the info about this user's DataONE Subscription
+ */
+ fetchSubscription: function () {
+ //If Bookkeeper services are disabled, exit
+ if (!MetacatUI.appModel.get("enableBookkeeperServices")) {
+ return;
+ }
- },
+ try {
+ var thisUser = this;
+ require([
+ "collections/bookkeeper/Quotas",
+ "models/bookkeeper/Subscription",
+ ], function (Quotas, Subscription) {
+ //Create a Quotas collection
+ var quotas = new Quotas();
+
+ //Create a Subscription model
+ var subscription = new Subscription();
+
+ if (MetacatUI.appModel.get("dataonePlusPreviewMode")) {
+ //Create Quota models for preview mode
+ quotas.add({
+ softLimit: MetacatUI.appModel.get("portalLimit"),
+ hardLimit: MetacatUI.appModel.get("portalLimit"),
+ quotaType: "portal",
+ unit: "portal",
+ subject: thisUser.get("username"),
+ subscription: subscription,
+ });
+
+ //Default to all people being in trial mode
+ subscription.set("status", "trialing");
- /**
- * Gets the already-fetched Quotas for the User, filters down to the type given, and returns them.
- * @param {string} [type] - The Quota type to return
- * @returns {Quota[]} The filtered array of Quota models or an empty array, if none are found
- */
- getQuotas: function(type){
- var quotas = this.get("dataoneQuotas");
+ //Save a reference to the Quotas on this UserModel
+ thisUser.set("dataoneQuotas", quotas);
- if( quotas && type ){
- return quotas.where({ quotaType: type });
- }
- else if( quotas && !type ){
- return quotas;
- }
- else{
- return [];
- }
+ //Save a reference to the Subscriptioin on this UserModel
+ thisUser.set("dataoneSubscription", subscription);
+ } else {
+ thisUser.listenToOnce(quotas, "reset", function () {
+ //Save a reference to the Quotas on this UserModel
+ thisUser.set("dataoneQuotas", quotas);
+ });
+
+ thisUser.listenToOnce(subscription, "sync", function () {
+ //Save a reference to the Subscriptioin on this UserModel
+ thisUser.set("dataoneSubscription", subscription);
+ });
+
+ //Fetch the Quotas
+ quotas.fetch({ subscriber: thisUser.get("username") });
+
+ //Fetch the Subscriptioin
+ subscription.fetch();
+ }
+ });
+ } catch (e) {
+ console.error(
+ "Couldn't get DataONE Subscription info. Proceeding as an unsubscribed user. ",
+ e,
+ );
+ }
+ },
+
+ /**
+ * Gets the already-fetched Quotas for the User, filters down to the type given, and returns them.
+ * @param {string} [type] - The Quota type to return
+ * @returns {Quota[]} The filtered array of Quota models or an empty array, if none are found
+ */
+ getQuotas: function (type) {
+ var quotas = this.get("dataoneQuotas");
+
+ if (quotas && type) {
+ return quotas.where({ quotaType: type });
+ } else if (quotas && !type) {
+ return quotas;
+ } else {
+ return [];
+ }
+ },
+
+ reset: function () {
+ var defaults = _.omit(this.defaults(), [
+ "searchModel",
+ "searchResults",
+ ]);
+ this.set(defaults);
+ },
},
+ );
- reset: function(){
- var defaults = _.omit(this.defaults(), ["searchModel", "searchResults"]);
- this.set(defaults);
- }
- });
-
- return UserModel;
+ return UserModel;
});
diff --git a/src/js/models/analytics/Analytics.js b/src/js/models/analytics/Analytics.js
index 409222d6e..b70be7744 100644
--- a/src/js/models/analytics/Analytics.js
+++ b/src/js/models/analytics/Analytics.js
@@ -126,7 +126,7 @@ define(["backbone"], function (Backbone) {
trackPageView: function (path, title) {
return;
},
- }
+ },
);
return Analytics;
diff --git a/src/js/models/analytics/GoogleAnalytics.js b/src/js/models/analytics/GoogleAnalytics.js
index 47dce61fe..a02de7fd7 100644
--- a/src/js/models/analytics/GoogleAnalytics.js
+++ b/src/js/models/analytics/GoogleAnalytics.js
@@ -113,7 +113,7 @@ define(["models/analytics/Analytics"], function (Analytics) {
page_title: title,
});
},
- }
+ },
);
return GoogleAnalytics;
diff --git a/src/js/models/bookkeeper/Quota.js b/src/js/models/bookkeeper/Quota.js
index 2f6dd8ae6..a652b7393 100644
--- a/src/js/models/bookkeeper/Quota.js
+++ b/src/js/models/bookkeeper/Quota.js
@@ -1,48 +1,44 @@
/* global define */
-define(["jquery",
- "underscore",
- "backbone"],
- function($, _, Backbone) {
- /**
- * @classdesc A Quota Model represents a single instance of a Quota object from the
- * DataONE Bookkeeper data model. Quotas are limits set
- * for a particular Product, such as the number of portals allowed, disk space
- * allowed, etc. Quotas have a soft and hard limit per unit to help with communicating limit warnings.
- * See https://github.com/DataONEorg/bookkeeper for documentation on the
- * DataONE Bookkeeper service and data model.
- * @classcategory Models/Bookkeeper
- * @class Quota
- * @name Quota
- * @since 2.14.0
- * @constructor
- * @extends Backbone.Model
- */
- var Quota = Backbone.Model.extend(
- /** @lends Quota.prototype */ {
-
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ /**
+ * @classdesc A Quota Model represents a single instance of a Quota object from the
+ * DataONE Bookkeeper data model. Quotas are limits set
+ * for a particular Product, such as the number of portals allowed, disk space
+ * allowed, etc. Quotas have a soft and hard limit per unit to help with communicating limit warnings.
+ * See https://github.com/DataONEorg/bookkeeper for documentation on the
+ * DataONE Bookkeeper service and data model.
+ * @classcategory Models/Bookkeeper
+ * @class Quota
+ * @name Quota
+ * @since 2.14.0
+ * @constructor
+ * @extends Backbone.Model
+ */
+ var Quota = Backbone.Model.extend(
+ /** @lends Quota.prototype */ {
/**
- * The name of this type of model
- * @type {string}
- */
+ * The name of this type of model
+ * @type {string}
+ */
type: "Quota",
/**
- * Default attributes for Quota models
- * @name Quota#defaults
- * @type {Object}
- * @property {string} id The unique id of this Quota, from Bookkeeper
- * @property {string} quotaType The quotaType of this Quota type
- * @property {string[]} quotaTypeOptions The controlled list of `quotaType` values that can be set on a Quota model
- * @property {string} object The name of this type of Bookkeeper object, which will always be "quota"
- * @property {number} softLimit The soft quota limit, which may be surpassed under certain conditions
- * @property {number} hardLimit The hard quota limit, which cannot be surpassed
- * @property {string} unit The unit of each Usage of this Quota (e.g. bytes, portals)
- * @property {string[]} unitOptions The controlled list of `unit` values that can be set on a Quota model
- * @property {string} customerId The id of the Customer associated with this Quota
- * @property {string} subject The user or group subject associated with this Quota
- * @property {number} totalUsage The total or sum of usage of this Quota
- */
- defaults: function(){
+ * Default attributes for Quota models
+ * @name Quota#defaults
+ * @type {Object}
+ * @property {string} id The unique id of this Quota, from Bookkeeper
+ * @property {string} quotaType The quotaType of this Quota type
+ * @property {string[]} quotaTypeOptions The controlled list of `quotaType` values that can be set on a Quota model
+ * @property {string} object The name of this type of Bookkeeper object, which will always be "quota"
+ * @property {number} softLimit The soft quota limit, which may be surpassed under certain conditions
+ * @property {number} hardLimit The hard quota limit, which cannot be surpassed
+ * @property {string} unit The unit of each Usage of this Quota (e.g. bytes, portals)
+ * @property {string[]} unitOptions The controlled list of `unit` values that can be set on a Quota model
+ * @property {string} customerId The id of the Customer associated with this Quota
+ * @property {string} subject The user or group subject associated with this Quota
+ * @property {number} totalUsage The total or sum of usage of this Quota
+ */
+ defaults: function () {
return {
id: "",
quotaType: "",
@@ -54,11 +50,11 @@ define(["jquery",
unitOptions: ["portal", "byte"],
customerId: "",
subject: "",
- totalUsage: 0
- }
- }
-
- });
+ totalUsage: 0,
+ };
+ },
+ },
+ );
return Quota;
});
diff --git a/src/js/models/bookkeeper/Subscription.js b/src/js/models/bookkeeper/Subscription.js
index ea8679a8d..d208d9cc0 100644
--- a/src/js/models/bookkeeper/Subscription.js
+++ b/src/js/models/bookkeeper/Subscription.js
@@ -1,51 +1,47 @@
/* global define */
-define(["jquery",
- "underscore",
- "backbone"],
- function($, _, Backbone) {
- /**
- * @classdesc A Subscription Model represents a single instance of a Subscription object from the
- * DataONE Bookkeeper data model.
- * Subscriptions represent a Product that has been ordered by a Customer
- * and is paid for on a recurring basis or is in a free trial period.
- * See https://github.com/DataONEorg/bookkeeper for documentation on the
- * DataONE Bookkeeper service and data model.
- * @classcategory Models/Bookkeeper
- * @class Subscription
- * @name Subscription
- * @since 2.14.0
- * @constructor
- * @extends Backbone.Model
- */
- var Subscription = Backbone.Model.extend(
- /** @lends Subscription.prototype */ {
-
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ /**
+ * @classdesc A Subscription Model represents a single instance of a Subscription object from the
+ * DataONE Bookkeeper data model.
+ * Subscriptions represent a Product that has been ordered by a Customer
+ * and is paid for on a recurring basis or is in a free trial period.
+ * See https://github.com/DataONEorg/bookkeeper for documentation on the
+ * DataONE Bookkeeper service and data model.
+ * @classcategory Models/Bookkeeper
+ * @class Subscription
+ * @name Subscription
+ * @since 2.14.0
+ * @constructor
+ * @extends Backbone.Model
+ */
+ var Subscription = Backbone.Model.extend(
+ /** @lends Subscription.prototype */ {
/**
- * The name of this type of model
- * @type {string}
- */
+ * The name of this type of model
+ * @type {string}
+ */
type: "Subscription",
/**
- * Default attributes for Subscription models
- * @name Subscription#defaults
- * @type {Object}
- * @property {string} id The unique identifier of this Subscription, from Bookkeeper
- * @property {string} object The name of this type of Bookkeeper object, which will always be "subscription"
- * @property {number} canceledAt The timestamp of the date that this Subscription was canceled
- * @property {string} collectionMethod The method of payment collection for this Subscription, which is a string from a controlled vocabulary from Bookkeeper
- * @property {number} created The timestamp of the date that this Subscription was created
- * @property {number} customerId The identifier of the Customer that is associated with this Subscription
- * @property {Object} metadata Arbitrary metadata about this Subscription. These values should be parsed and set on this model (TODO)
- * @property {number} productId The identifier of a Product in this Subscription
- * @property {number} quantity The number of Subscriptions
- * @property {number} startDate The timestamp of the date that this Subscription was started
- * @property {string} status The status of this Subscription, which is taken from a controlled vocabulary set on this model (statusOptions)
- * @property {string[]} statusOptions The controlled vocabulary from which the `status` value can be from
- * @property {number} trialEnd The timestamp of the date that this free trial Subscription ends
- * @property {number} trialStart The timestamp of the date that this free trial Subscription starts
- */
- defaults: function(){
+ * Default attributes for Subscription models
+ * @name Subscription#defaults
+ * @type {Object}
+ * @property {string} id The unique identifier of this Subscription, from Bookkeeper
+ * @property {string} object The name of this type of Bookkeeper object, which will always be "subscription"
+ * @property {number} canceledAt The timestamp of the date that this Subscription was canceled
+ * @property {string} collectionMethod The method of payment collection for this Subscription, which is a string from a controlled vocabulary from Bookkeeper
+ * @property {number} created The timestamp of the date that this Subscription was created
+ * @property {number} customerId The identifier of the Customer that is associated with this Subscription
+ * @property {Object} metadata Arbitrary metadata about this Subscription. These values should be parsed and set on this model (TODO)
+ * @property {number} productId The identifier of a Product in this Subscription
+ * @property {number} quantity The number of Subscriptions
+ * @property {number} startDate The timestamp of the date that this Subscription was started
+ * @property {string} status The status of this Subscription, which is taken from a controlled vocabulary set on this model (statusOptions)
+ * @property {string[]} statusOptions The controlled vocabulary from which the `status` value can be from
+ * @property {number} trialEnd The timestamp of the date that this free trial Subscription ends
+ * @property {number} trialStart The timestamp of the date that this free trial Subscription starts
+ */
+ defaults: function () {
return {
id: null,
object: "subscription",
@@ -58,22 +54,30 @@ define(["jquery",
quantity: 0,
startDate: null,
status: null,
- statusOptions: ["trialing", "active", "past_due", "canceled", "unpaid", "incomplete_expired", "incomplete"],
+ statusOptions: [
+ "trialing",
+ "active",
+ "past_due",
+ "canceled",
+ "unpaid",
+ "incomplete_expired",
+ "incomplete",
+ ],
trialEnd: null,
- trialStart: null
- }
+ trialStart: null,
+ };
},
/**
- *
- * Returns true if this Subscription is in a free trial period.
- * @returns {boolean}
- */
- isTrialing: function(){
+ *
+ * Returns true if this Subscription is in a free trial period.
+ * @returns {boolean}
+ */
+ isTrialing: function () {
return this.get("status") == "trialing";
- }
-
- });
+ },
+ },
+ );
return Subscription;
});
diff --git a/src/js/models/bookkeeper/Usage.js b/src/js/models/bookkeeper/Usage.js
index 99f4eb23b..e7c29c5e9 100644
--- a/src/js/models/bookkeeper/Usage.js
+++ b/src/js/models/bookkeeper/Usage.js
@@ -1,46 +1,42 @@
/* global define */
-define(["jquery",
- "underscore",
- "backbone"],
- function($, _, Backbone) {
- /**
- * @classdesc A Usage Model represents a single instance of a Usage object from the
- * DataONE Bookkeeper data model. A Usage tracks which objects use a portion of a Quota.
- * A Quota can be associated with multiple Usages.
- * See https://github.com/DataONEorg/bookkeeper for documentation on the
- * DataONE Bookkeeper service and data model.
- * @classcategory Models/Bookkeeper
- * @class Usage
- * @name Usage
- * @since 2.14.0
- * @extends Backbone.Model
- * @constructor
- */
- var Usage = Backbone.Model.extend(
- /** @lends Usage.prototype */ {
-
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ /**
+ * @classdesc A Usage Model represents a single instance of a Usage object from the
+ * DataONE Bookkeeper data model. A Usage tracks which objects use a portion of a Quota.
+ * A Quota can be associated with multiple Usages.
+ * See https://github.com/DataONEorg/bookkeeper for documentation on the
+ * DataONE Bookkeeper service and data model.
+ * @classcategory Models/Bookkeeper
+ * @class Usage
+ * @name Usage
+ * @since 2.14.0
+ * @extends Backbone.Model
+ * @constructor
+ */
+ var Usage = Backbone.Model.extend(
+ /** @lends Usage.prototype */ {
/**
- * The name of this type of model
- * @type {string}
- */
+ * The name of this type of model
+ * @type {string}
+ */
type: "Usage",
/**
- * Default attributes for Usage models
- * @name Usage#defaults
- * @type {Object}
- * @property {string} id The unique id of this Usage, from Bookkeeper
- * @property {string} object The name of this type of Bookkeeper object, which will always be "usage"
- * @property {number} quotaId The id of the Quota object that this Usage is associated with, from Bookkeeper. This is a match to {@link Quota#defaults#id}
- * @property {string} instanceId The id of the {@link DataONEObject} that makes up this Usage
- * @property {number} quantity The quantity of the {@link Quota} that this Usage uses, expressed as {@link Quota#defaults#unit}
- * @property {string} status The status of this Usage
- * @property {string[]} statusOptions The controlled list of `status` values that can be set on a Usage model
- * @property {string} nodeId The Member Node ID that the object is from
- * @property {DataONEObject} DataONEObject A reference to the DataONEObject that has the id/seriesId of this Usage instanceId
- * @property {SolrResult} SolrResult A reference to the SolrResult that has the id/seriesId of this Usage instanceId
- */
- defaults: function(){
+ * Default attributes for Usage models
+ * @name Usage#defaults
+ * @type {Object}
+ * @property {string} id The unique id of this Usage, from Bookkeeper
+ * @property {string} object The name of this type of Bookkeeper object, which will always be "usage"
+ * @property {number} quotaId The id of the Quota object that this Usage is associated with, from Bookkeeper. This is a match to {@link Quota#defaults#id}
+ * @property {string} instanceId The id of the {@link DataONEObject} that makes up this Usage
+ * @property {number} quantity The quantity of the {@link Quota} that this Usage uses, expressed as {@link Quota#defaults#unit}
+ * @property {string} status The status of this Usage
+ * @property {string[]} statusOptions The controlled list of `status` values that can be set on a Usage model
+ * @property {string} nodeId The Member Node ID that the object is from
+ * @property {DataONEObject} DataONEObject A reference to the DataONEObject that has the id/seriesId of this Usage instanceId
+ * @property {SolrResult} SolrResult A reference to the SolrResult that has the id/seriesId of this Usage instanceId
+ */
+ defaults: function () {
return {
id: null,
object: "usage",
@@ -51,11 +47,11 @@ define(["jquery",
statusOptions: ["active", "inactive"],
nodeId: "",
DataONEObject: null,
- SolrResult: null
- }
- }
-
- });
+ SolrResult: null,
+ };
+ },
+ },
+ );
return Usage;
});
diff --git a/src/js/models/connectors/Filters-Map.js b/src/js/models/connectors/Filters-Map.js
index 266b05073..71284878a 100644
--- a/src/js/models/connectors/Filters-Map.js
+++ b/src/js/models/connectors/Filters-Map.js
@@ -2,7 +2,7 @@
define(["backbone", "collections/Filters", "models/maps/Map"], function (
Backbone,
Filters,
- Map
+ Map,
) {
"use strict";
@@ -69,7 +69,7 @@ define(["backbone", "collections/Filters", "models/maps/Map"], function (
initialize: function (attr, options) {
try {
if (!this.get("filters")) {
- this.set("filters", new Filters([], { catalogSearch: true }));
+ this.set("filters", new Filters([], { catalogSearch: true }));
}
if (!this.get("map")) {
this.set("map", new Map());
@@ -118,7 +118,7 @@ define(["backbone", "collections/Filters", "models/maps/Map"], function (
this.listenToOnce(
this.get("filters"),
"add remove",
- this.findAndSetSpatialFilters
+ this.findAndSetSpatialFilters,
);
},
@@ -149,7 +149,7 @@ define(["backbone", "collections/Filters", "models/maps/Map"], function (
this.stopListening(
this.get("filters"),
"add remove",
- this.findAndSetSpatialFilters
+ this.findAndSetSpatialFilters,
);
spatialFilters.forEach((filter) => {
filter.collection.remove(filter);
@@ -247,6 +247,6 @@ define(["backbone", "collections/Filters", "models/maps/Map"], function (
console.log("Error updating spatial filters: ", e);
}
},
- }
+ },
);
});
diff --git a/src/js/models/connectors/Filters-Search.js b/src/js/models/connectors/Filters-Search.js
index 99b915af8..76ad3f5a9 100644
--- a/src/js/models/connectors/Filters-Search.js
+++ b/src/js/models/connectors/Filters-Search.js
@@ -64,13 +64,13 @@ define([
search.trigger("changing");
});
- this.listenTo(search, "change:sort change:facet", () => this.triggerSearch());
+ this.listenTo(search, "change:sort change:facet", () =>
+ this.triggerSearch(),
+ );
// If the logged-in status changes, send a new search
- this.listenTo(
- MetacatUI.appUserModel,
- "change:loggedIn",
- () => this.triggerSearch()
+ this.listenTo(MetacatUI.appUserModel, "change:loggedIn", () =>
+ this.triggerSearch(),
);
this.set("isConnected", true);
@@ -127,6 +127,6 @@ define([
// Send the query to the server via the SolrResults collection
searchResults.toPage(page);
},
- }
+ },
);
});
diff --git a/src/js/models/connectors/GeoPoints-Cesium.js b/src/js/models/connectors/GeoPoints-Cesium.js
index 7c7f5f4b6..64ed46cbb 100644
--- a/src/js/models/connectors/GeoPoints-Cesium.js
+++ b/src/js/models/connectors/GeoPoints-Cesium.js
@@ -162,6 +162,6 @@ define([
console.warn('Error handling a "' + eventName + '" event.', e);
}
},
- }
+ },
);
});
diff --git a/src/js/models/connectors/GeoPoints-CesiumPoints.js b/src/js/models/connectors/GeoPoints-CesiumPoints.js
index 1ff6aa0d7..9901b6847 100644
--- a/src/js/models/connectors/GeoPoints-CesiumPoints.js
+++ b/src/js/models/connectors/GeoPoints-CesiumPoints.js
@@ -3,7 +3,7 @@
/*global define */
define(["cesium", "models/connectors/GeoPoints-Cesium"], function (
Cesium,
- GeoPointsCesiumConnector
+ GeoPointsCesiumConnector,
) {
/**
* @class GeoPointsCesiumPointsConnector
@@ -164,6 +164,6 @@ define(["cesium", "models/connectors/GeoPoints-Cesium"], function (
console.log("Failed to remove a point from a CesiumVectorData.", e);
}
},
- }
+ },
);
});
diff --git a/src/js/models/connectors/GeoPoints-CesiumPolygon.js b/src/js/models/connectors/GeoPoints-CesiumPolygon.js
index 73b63b92f..19bd8914d 100644
--- a/src/js/models/connectors/GeoPoints-CesiumPolygon.js
+++ b/src/js/models/connectors/GeoPoints-CesiumPolygon.js
@@ -3,7 +3,7 @@
/*global define */
define(["cesium", "models/connectors/GeoPoints-Cesium"], function (
Cesium,
- GeoPointsCesiumConnector
+ GeoPointsCesiumConnector,
) {
/**
* @class GeoPointsCesiumPolygonConnector
@@ -70,6 +70,6 @@ define(["cesium", "models/connectors/GeoPoints-Cesium"], function (
this.get("polygon") || this.addPolygon();
this.get("layer").updateAppearance();
},
- }
+ },
);
});
diff --git a/src/js/models/connectors/Map-Search-Filters.js b/src/js/models/connectors/Map-Search-Filters.js
index 7c411f55d..027f62f49 100644
--- a/src/js/models/connectors/Map-Search-Filters.js
+++ b/src/js/models/connectors/Map-Search-Filters.js
@@ -16,7 +16,7 @@ define([
MapSearchConnector,
FiltersSearchConnector,
FiltersMapConnector,
- FilterGroup
+ FilterGroup,
) {
"use strict";
@@ -165,7 +165,7 @@ define([
*/
setConnectors: function (
addGeohashLayer = true,
- addSpatialFilter = true
+ addSpatialFilter = true,
) {
const map = this.get("map");
const searchResults = this.get("searchResults");
@@ -173,15 +173,15 @@ define([
this.set(
"mapSearchConnector",
- new MapSearchConnector({ map, searchResults }, { addGeohashLayer })
+ new MapSearchConnector({ map, searchResults }, { addGeohashLayer }),
);
this.set(
"filtersSearchConnector",
- new FiltersSearchConnector({ filters, searchResults })
+ new FiltersSearchConnector({ filters, searchResults }),
);
this.set(
"filtersMapConnector",
- new FiltersMapConnector({ filters, map }, { addSpatialFilter })
+ new FiltersMapConnector({ filters, map }, { addSpatialFilter }),
);
},
@@ -328,6 +328,6 @@ define([
removeSpatialFilter: function () {
this.get("filtersMapConnector").removeSpatialFilter();
},
- }
+ },
);
});
diff --git a/src/js/models/connectors/Map-Search.js b/src/js/models/connectors/Map-Search.js
index e5bf07ee3..21799666b 100644
--- a/src/js/models/connectors/Map-Search.js
+++ b/src/js/models/connectors/Map-Search.js
@@ -1,9 +1,9 @@
/*global define */
-define([
- "backbone",
- "models/maps/Map",
- "collections/SolrResults",
-], function (Backbone, Map, SearchResults) {
+define(["backbone", "models/maps/Map", "collections/SolrResults"], function (
+ Backbone,
+ Map,
+ SearchResults,
+) {
"use strict";
/**
@@ -17,13 +17,12 @@ define([
*/
return Backbone.Model.extend(
/** @lends MapSearchConnector.prototype */ {
-
/**
* The type of Backbone.Model this is.
* @type {string}
* @since 2.25.0
* @default "MapSearchConnector"
- */
+ */
type: "MapSearchConnector",
/**
@@ -38,7 +37,7 @@ define([
return {
searchResults: null,
map: null,
- onMoveEnd: this.onMoveEnd
+ onMoveEnd: this.onMoveEnd,
};
},
@@ -81,13 +80,17 @@ define([
// TODO: Since only the first Geohash is needed, create a getFirst
// function in MapAssets.
- let geohashes = _.reduce(layerGroups, (memo, layers) => {
- const geohashes = layers.getAll("CesiumGeohash");
- if (geohashes && geohashes.length) {
- memo.push(...geohashes);
- }
- return memo;
- }, []);
+ let geohashes = _.reduce(
+ layerGroups,
+ (memo, layers) => {
+ const geohashes = layers.getAll("CesiumGeohash");
+ if (geohashes && geohashes.length) {
+ memo.push(...geohashes);
+ }
+ return memo;
+ },
+ [],
+ );
if (!geohashes || !geohashes.length) {
return null;
} else {
@@ -151,7 +154,7 @@ define([
}
// If there is still no Geohash layer, then we should wait for one to
// be added to the Layers collection, then try to find it again.
- _.each(layerGroups, layers => {
+ _.each(layerGroups, (layers) => {
this.stopListening(layers, "add", this.findAndSetGeohashLayer);
if (!geohash) {
this.listenTo(layers, "add", this.findAndSetGeohashLayer);
@@ -191,7 +194,11 @@ define([
// When the user is panning/zooming in the map, hide the GeoHash layer
// to indicate that the map is not up to date with the search results,
// which are about to be updated.
- this.listenTo(interactions, "moveStartAndChanged", this.hideGeoHashLayer);
+ this.listenTo(
+ interactions,
+ "moveStartAndChanged",
+ this.hideGeoHashLayer,
+ );
// When the user is done panning/zooming in the map, show the GeoHash
// layer again and update the search results (thereby updating the
@@ -290,7 +297,7 @@ define([
const facetCounts = searchResults?.facetCounts;
if (!facetCounts) return null;
const geohashFacets = Object.keys(facetCounts).filter((key) =>
- key.startsWith("geohash_")
+ key.startsWith("geohash_"),
);
return geohashFacets.flatMap((key) => facetCounts[key]);
},
@@ -340,6 +347,6 @@ define([
const geohashes9 = searchResult.get("geohash_9");
this.get("geohashLayer").selectGeohashes(geohashes9);
},
- }
+ },
);
});
diff --git a/src/js/models/filters/BooleanFilter.js b/src/js/models/filters/BooleanFilter.js
index 75ebbbbba..bc43bd088 100644
--- a/src/js/models/filters/BooleanFilter.js
+++ b/src/js/models/filters/BooleanFilter.js
@@ -1,107 +1,103 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/filters/Filter'],
- function($, _, Backbone, Filter) {
-
+define(["jquery", "underscore", "backbone", "models/filters/Filter"], function (
+ $,
+ _,
+ Backbone,
+ Filter,
+) {
/**
- * @class BooleanFilter
- * @classdesc A search filter that only has `true` or `false` as a search term
- * @classcategory Models/Filters
- * @name BooleanFilter
- * @extends Filter
- * @constructs
- */
- var BooleanFilter = Filter.extend(
+ * @class BooleanFilter
+ * @classdesc A search filter that only has `true` or `false` as a search term
+ * @classcategory Models/Filters
+ * @name BooleanFilter
+ * @extends Filter
+ * @constructs
+ */
+ var BooleanFilter = Filter.extend(
/** @lends BooleanFilter.prototype */
{
+ /** @inheritdoc */
+ type: "BooleanFilter",
+
+ /** @inheritdoc */
+ defaults: function () {
+ return _.extend(Filter.prototype.defaults(), {
+ //Boolean filters can't match substrings
+ matchSubstring: false,
+ nodeName: "booleanFilter",
+ });
+ },
+
+ /**
+ * Parses the booleanFilter XML node into JSON
+ *
+ * @param {Element} xml - The XML Element that contains all the BooleanFilter elements
+ * @return {JSON} - The JSON object literal to be set on the model
+ */
+ parse: function (xml) {
+ var modelJSON = Filter.prototype.parse.call(this, xml);
+
+ //If this Filter is in a filter group, don't parse the value
+ if (!this.get("isUIFilterType")) {
+ //Parse the boolean value
+ modelJSON.values = this.parseTextNode(xml, "value");
+
+ if (modelJSON.values === "true") {
+ modelJSON.values = [true];
+ } else if (modelJSON.values === "false") {
+ modelJSON.values = [false];
+ }
+ }
- /** @inheritdoc */
- type: "BooleanFilter",
-
- /** @inheritdoc */
- defaults: function(){
- return _.extend(Filter.prototype.defaults(), {
- //Boolean filters can't match substrings
- matchSubstring: false,
- nodeName: "booleanFilter",
- });
- },
+ return modelJSON;
+ },
+
+ /**
+ * Updates the XML DOM with the new values from the model
+ * @inheritdoc
+ * @return {Element} An updated booleanFilter XML element from a portal document
+ */
+ updateDOM: function (options) {
+ if (typeof options == "undefined") {
+ var options = {};
+ }
- /**
- * Parses the booleanFilter XML node into JSON
- *
- * @param {Element} xml - The XML Element that contains all the BooleanFilter elements
- * @return {JSON} - The JSON object literal to be set on the model
- */
- parse: function(xml){
+ //Update the DOM using the parent Filter model
+ var objectDOM = Filter.prototype.updateDOM.call(this);
- var modelJSON = Filter.prototype.parse.call(this, xml);
+ //Boolean Filters don't use matchSubstring and operator nodes
+ $(objectDOM).children("matchSubstring, operator").remove();
- //If this Filter is in a filter group, don't parse the value
- if( !this.get("isUIFilterType") ){
+ // Get the new boolean value
+ var value = this.get("value");
- //Parse the boolean value
- modelJSON.values = this.parseTextNode(xml, "value");
+ // Make a node with the new boolean value and append it to DOM
+ if (value || value === false) {
+ var valueSerialized = objectDOM.ownerDocument.createElement("value");
+ $(valueSerialized).text(value);
+ $(objectDOM).append(valueSerialized);
+ }
- if(modelJSON.values === "true"){
- modelJSON.values = [true];
+ if (this.get("isUIFilterType")) {
+ //Make sure the filterOptions are listed last
+ //Get the filterOptions element
+ var filterOptions = $(objectDOM).children("filterOptions");
+ //If the filterOptions exist
+ if (filterOptions.length) {
+ //Detach from their current position and append to the end
+ filterOptions.detach();
+ $(objectDOM).append(filterOptions);
+ }
}
- else if(modelJSON.values === "false"){
- modelJSON.values = [false];
+ //Remove filter options if this is for a collection definition
+ else {
+ $(objectDOM).children("filterOptions").remove();
}
- }
-
- return modelJSON;
+ return objectDOM;
+ },
},
-
- /**
- * Updates the XML DOM with the new values from the model
- * @inheritdoc
- * @return {Element} An updated booleanFilter XML element from a portal document
- */
- updateDOM: function(options) {
-
- if( typeof options == "undefined" ){
- var options = {};
- }
-
- //Update the DOM using the parent Filter model
- var objectDOM = Filter.prototype.updateDOM.call(this);
-
- //Boolean Filters don't use matchSubstring and operator nodes
- $(objectDOM).children("matchSubstring, operator").remove();
-
- // Get the new boolean value
- var value = this.get("value");
-
- // Make a node with the new boolean value and append it to DOM
- if( value || value === false){
- var valueSerialized = objectDOM.ownerDocument.createElement("value");
- $(valueSerialized).text(value);
- $(objectDOM).append(valueSerialized);
- }
-
- if( this.get("isUIFilterType") ){
- //Make sure the filterOptions are listed last
- //Get the filterOptions element
- var filterOptions = $(objectDOM).children("filterOptions");
- //If the filterOptions exist
- if( filterOptions.length ){
- //Detach from their current position and append to the end
- filterOptions.detach();
- $(objectDOM).append(filterOptions);
- }
- }
- //Remove filter options if this is for a collection definition
- else{
- $(objectDOM).children("filterOptions").remove();
- }
-
- return objectDOM
-
- }
-
- });
+ );
return BooleanFilter;
});
diff --git a/src/js/models/filters/ChoiceFilter.js b/src/js/models/filters/ChoiceFilter.js
index a920a2b08..54cc67be9 100644
--- a/src/js/models/filters/ChoiceFilter.js
+++ b/src/js/models/filters/ChoiceFilter.js
@@ -1,214 +1,212 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/filters/Filter'],
- function($, _, Backbone, Filter) {
-
+define(["jquery", "underscore", "backbone", "models/filters/Filter"], function (
+ $,
+ _,
+ Backbone,
+ Filter,
+) {
/**
- * @class ChoiceFilter
- * @classdesc A Filter whose search term is one or more choices from a defined list
- * @classcategory Models/Filters
- * @name ChoiceFilter
- * @constructs ChoiceFilter
- * @extends Filter
- */
- var ChoiceFilter = Filter.extend(
- /** @lends ChoiceFilter.prototype */{
-
- /**
- * @inheritdoc
- * @type {string}
- */
- type: "ChoiceFilter",
-
- /**
- * The Backbone Model attributes set on this ChoiceFilter
- * @type {object}
- * @extends Filter#defaultts
- * @property {boolean} chooseMultiple - If true, this ChoiceFilter can have multiple choices set as the search term
- * @property {string[]} choices - The list of search terms that can possibly be set on this Filter
- * @property {string} nodeName - The XML node name to use when serializing this model into XML
- */
- defaults: function(){
- return _.extend(Filter.prototype.defaults(), {
- chooseMultiple: true,
- //@type {object} - A literal JS object with a "label" and "value" attribute
- choices: [],
- nodeName: "choiceFilter"
- });
- },
-
- /**
- * Parses the choiceFilter XML node into JSON
- *
- * @param {Element} xml - The XML Element that contains all the ChoiceFilter elements
- * @return {JSON} - The JSON object literal to be set on the model
- */
- parse: function(xml){
-
- var modelJSON = Filter.prototype.parse.call(this, xml);
-
- //Parse the chooseMultiple boolean field
- modelJSON.chooseMultiple = (this.parseTextNode(xml, "chooseMultiple") === "true")? true : false;
-
- //Start an array for the choices
- modelJSON.choices = [];
-
- //Iterate over each choice and parse it
- var self = this;
- $(xml).find("choice").each(function(i, choiceNode){
+ * @class ChoiceFilter
+ * @classdesc A Filter whose search term is one or more choices from a defined list
+ * @classcategory Models/Filters
+ * @name ChoiceFilter
+ * @constructs ChoiceFilter
+ * @extends Filter
+ */
+ var ChoiceFilter = Filter.extend(
+ /** @lends ChoiceFilter.prototype */ {
+ /**
+ * @inheritdoc
+ * @type {string}
+ */
+ type: "ChoiceFilter",
+
+ /**
+ * The Backbone Model attributes set on this ChoiceFilter
+ * @type {object}
+ * @extends Filter#defaultts
+ * @property {boolean} chooseMultiple - If true, this ChoiceFilter can have multiple choices set as the search term
+ * @property {string[]} choices - The list of search terms that can possibly be set on this Filter
+ * @property {string} nodeName - The XML node name to use when serializing this model into XML
+ */
+ defaults: function () {
+ return _.extend(Filter.prototype.defaults(), {
+ chooseMultiple: true,
+ //@type {object} - A literal JS object with a "label" and "value" attribute
+ choices: [],
+ nodeName: "choiceFilter",
+ });
+ },
- //Parse the label and value nodes into a literal object
- var choiceObject = {
- label: self.parseTextNode(choiceNode, "label"),
- value: self.parseTextNode(choiceNode, "value")
- }
+ /**
+ * Parses the choiceFilter XML node into JSON
+ *
+ * @param {Element} xml - The XML Element that contains all the ChoiceFilter elements
+ * @return {JSON} - The JSON object literal to be set on the model
+ */
+ parse: function (xml) {
+ var modelJSON = Filter.prototype.parse.call(this, xml);
+
+ //Parse the chooseMultiple boolean field
+ modelJSON.chooseMultiple =
+ this.parseTextNode(xml, "chooseMultiple") === "true" ? true : false;
+
+ //Start an array for the choices
+ modelJSON.choices = [];
+
+ //Iterate over each choice and parse it
+ var self = this;
+ $(xml)
+ .find("choice")
+ .each(function (i, choiceNode) {
+ //Parse the label and value nodes into a literal object
+ var choiceObject = {
+ label: self.parseTextNode(choiceNode, "label"),
+ value: self.parseTextNode(choiceNode, "value"),
+ };
+
+ //Check that there is a label and value (value can be boolean false or 0, so just check for null or undefined)
+ if (
+ choiceObject.label &&
+ choiceObject.value !== null &&
+ typeof choiceObject.value !== "undefined"
+ ) {
+ modelJSON.choices.push(choiceObject);
+ }
+ });
- //Check that there is a label and value (value can be boolean false or 0, so just check for null or undefined)
- if(choiceObject.label && choiceObject.value !== null && typeof choiceObject.value !== "undefined"){
- modelJSON.choices.push(choiceObject);
- }
+ return modelJSON;
+ },
- });
+ /**
+ * Updates the XML DOM with the new values from the model
+ * @inheritdoc
+ * @return {XMLElement} An updated choiceFilter XML element from a portal document
+ */
+ updateDOM: function (options) {
+ try {
+ var objectDOM = Filter.prototype.updateDOM.call(this);
+
+ if (typeof options != "object") {
+ var options = {};
+ }
- return modelJSON;
- },
+ if (this.get("isUIFilterType")) {
+ // Serialize elements
+ var choices = this.get("choices");
+
+ if (choices) {
+ //Remove all the choice elements
+ $(objectDOM).children("choice").remove();
+
+ //Make a new choice element for each choice in the model
+ _.each(choices, function (choice) {
+ // Make new node
+ choiceSerialized =
+ objectDOM.ownerDocument.createElement("choice");
+ // Make choice subnodes and
+ _.map(choice, function (value, nodeName) {
+ if (value || value === false) {
+ var nodeSerialized =
+ objectDOM.ownerDocument.createElement(nodeName);
+ $(nodeSerialized).text(value);
+ $(choiceSerialized).append(nodeSerialized);
+ }
+ });
+ // append subnodes
+ $(objectDOM).append(choiceSerialized);
+ });
+ }
- /**
- * Updates the XML DOM with the new values from the model
- * @inheritdoc
- * @return {XMLElement} An updated choiceFilter XML element from a portal document
- */
- updateDOM:function(options){
+ //Get the chooseMultiple value from the model
+ var chooseMultiple = this.get("chooseMultiple");
+ //Remove the chooseMultiple element
+ $(objectDOM).children("chooseMultiple").remove();
+ //If the model value is a boolean, create a chooseMultiple element and add it to the DOM
+ if (chooseMultiple === true || chooseMultiple === false) {
+ chooseMultipleSerialized =
+ objectDOM.ownerDocument.createElement("chooseMultiple");
+ $(chooseMultipleSerialized).text(chooseMultiple);
+ $(objectDOM).append(chooseMultipleSerialized);
+ }
+ } else {
+ //Remove the filterOptions
+ $(objectDOM).find("filterOptions").remove();
- try{
+ //Change the root element into a element
+ var newFilterEl = objectDOM.ownerDocument.createElement("filter");
+ $(newFilterEl).html($(objectDOM).children());
- var objectDOM = Filter.prototype.updateDOM.call(this);
+ //Return this node
+ return newFilterEl;
+ }
- if(typeof options != "object"){
- var options = {};
+ return objectDOM;
+ } catch (e) {
+ //If there's an error, return the original DOM or an empty string
+ return this.get("objectDOM") || "";
}
+ },
- if( this.get("isUIFilterType") ){
- // Serialize elements
- var choices = this.get("choices");
-
- if(choices){
- //Remove all the choice elements
- $(objectDOM).children("choice").remove();
-
- //Make a new choice element for each choice in the model
- _.each(choices, function(choice){
- // Make new node
- choiceSerialized = objectDOM.ownerDocument.createElement("choice");
- // Make choice subnodes and
- _.map(choice, function(value, nodeName){
+ /**
+ * Checks if the values set on this model are valid and expected
+ * @return {object} - Returns a literal object with the invalid attributes and their
+ * corresponding error message
+ */
+ validate: function () {
+ try {
+ // Validate most of the ChoiceFilter attributes using the parent validate
+ // function
+ var errors = Filter.prototype.validate.call(this);
+
+ // If everything is valid so far, then we have to create a new object to store
+ // errors
+ if (typeof errors != "object") {
+ errors = {};
+ }
- if(value || value === false){
- var nodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
- $(nodeSerialized).text(value);
- $(choiceSerialized).append(nodeSerialized);
- }
+ // Delete error messages for the attributes that are going to be validated
+ // specially for the ChoiceFilter
+ delete errors.choices;
- });
- // append subnodes
- $(objectDOM).append(choiceSerialized);
+ // Save a reference to the choices
+ var choices = this.get("choices");
+ // Ensure that there is at least one choice
+ if (!choices || choices.length === 0) {
+ errors.choices = "At least one search term option is required.";
+ } else {
+ // Remove any empty choices
+ choices = choices.filter(function (choice) {
+ return choice.value || choice.label;
+ });
+ // If there is no value but there is a label, then set the search value to the
+ // label. If there is a value but no label, set the label to the value.
+ choices.forEach(function (choice) {
+ if (!choice.value) {
+ choice.value = choice.label;
+ }
+ if (!choice.label) {
+ choice.label = choice.value;
+ }
});
-
}
- //Get the chooseMultiple value from the model
- var chooseMultiple = this.get("chooseMultiple");
- //Remove the chooseMultiple element
- $(objectDOM).children("chooseMultiple").remove();
- //If the model value is a boolean, create a chooseMultiple element and add it to the DOM
- if(chooseMultiple === true || chooseMultiple === false){
- chooseMultipleSerialized = objectDOM.ownerDocument.createElement("chooseMultiple");
- $(chooseMultipleSerialized).text(chooseMultiple);
- $(objectDOM).append(chooseMultipleSerialized);
- };
- }
- else{
- //Remove the filterOptions
- $(objectDOM).find("filterOptions").remove();
-
- //Change the root element into a element
- var newFilterEl = objectDOM.ownerDocument.createElement("filter");
- $(newFilterEl).html( $(objectDOM).children() );
-
- //Return this node
- return newFilterEl;
+ // Return the errors, if there are any
+ if (Object.keys(errors).length) return errors;
+ else {
+ return;
+ }
+ } catch (error) {
+ console.log(
+ "There was an error validating a ChoiceFilter" +
+ ". Error details: " +
+ error,
+ );
}
-
- return objectDOM;
- }
- //If there's an error, return the original DOM or an empty string
- catch(e){
- return this.get("objectDOM") || "";
- }
-
},
-
- /**
- * Checks if the values set on this model are valid and expected
- * @return {object} - Returns a literal object with the invalid attributes and their
- * corresponding error message
- */
- validate : function(){
- try {
-
- // Validate most of the ChoiceFilter attributes using the parent validate
- // function
- var errors = Filter.prototype.validate.call(this);
-
- // If everything is valid so far, then we have to create a new object to store
- // errors
- if (typeof errors != "object") {
- errors = {};
- }
-
- // Delete error messages for the attributes that are going to be validated
- // specially for the ChoiceFilter
- delete errors.choices;
-
- // Save a reference to the choices
- var choices = this.get("choices")
-
- // Ensure that there is at least one choice
- if (!choices || choices.length === 0) {
- errors.choices = "At least one search term option is required."
- } else {
- // Remove any empty choices
- choices = choices.filter(function (choice) {
- return (choice.value || choice.label)
- })
- // If there is no value but there is a label, then set the search value to the
- // label. If there is a value but no label, set the label to the value.
- choices.forEach(function (choice) {
- if (!choice.value) {
- choice.value = choice.label
- }
- if (!choice.label) {
- choice.label = choice.value
- }
- })
- }
-
- // Return the errors, if there are any
- if (Object.keys(errors).length)
- return errors;
- else {
- return;
- }
- }
- catch (error) {
- console.log(
- 'There was an error validating a ChoiceFilter' +
- '. Error details: ' + error
- );
- }
},
-
- });
+ );
return ChoiceFilter;
});
diff --git a/src/js/models/filters/DateFilter.js b/src/js/models/filters/DateFilter.js
index 721e12a5e..d1f65b50b 100644
--- a/src/js/models/filters/DateFilter.js
+++ b/src/js/models/filters/DateFilter.js
@@ -1,439 +1,465 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/filters/Filter'],
- function($, _, Backbone, Filter) {
-
+define(["jquery", "underscore", "backbone", "models/filters/Filter"], function (
+ $,
+ _,
+ Backbone,
+ Filter,
+) {
/**
- * @class DateFilter
- * @classdesc A search filter whose search term is an exact date or date range
- * @classcategory Models/Filters
- * @constructs DateFilter
- * @extends Filter
- */
- var DateFilter = Filter.extend(
- /** @lends DateFilter.prototype */{
-
- type: "DateFilter",
-
- /**
- * The Backbone Model attributes set on this DateFilter
- * @type {object}
- * @extends Filter#defaultts
- * @property {Date} min - The minimum Date to use in the query for this filter
- * @property {Date} max - The maximum Date to use in the query for this filter
- * @property {Date} rangeMin - The earliest possible Date that 'min' can be
- * @property {Date} rangeMax - The latest possible Date that 'max' can be
- * @property {Boolean} matchSubstring - Will always be stet to false, since Dates don't have substrings
- * @property {string} nodeName - The XML node name to use when serializing this model into XML
- */
- defaults: function(){
- return _.extend(Filter.prototype.defaults(), {
- min: 0,
- max: (new Date()).getUTCFullYear(),
- rangeMin: 1800,
- rangeMax: (new Date()).getUTCFullYear(),
- matchSubstring: false,
- nodeName: "dateFilter"
- });
- },
-
- /**
- * Parses the dateFilter XML node into JSON
- *
- * @param {Element} xml - The XML Element that contains all the DateFilter elements
- * @return {JSON} - The JSON object literal to be set on the model
- */
- parse: function(xml){
-
- try{
- var modelJSON = Filter.prototype.parse.call(this, xml);
-
- //Get the rangeMin and rangeMax nodes
- var rangeMinNode = $(xml).find("rangeMin"),
+ * @class DateFilter
+ * @classdesc A search filter whose search term is an exact date or date range
+ * @classcategory Models/Filters
+ * @constructs DateFilter
+ * @extends Filter
+ */
+ var DateFilter = Filter.extend(
+ /** @lends DateFilter.prototype */ {
+ type: "DateFilter",
+
+ /**
+ * The Backbone Model attributes set on this DateFilter
+ * @type {object}
+ * @extends Filter#defaultts
+ * @property {Date} min - The minimum Date to use in the query for this filter
+ * @property {Date} max - The maximum Date to use in the query for this filter
+ * @property {Date} rangeMin - The earliest possible Date that 'min' can be
+ * @property {Date} rangeMax - The latest possible Date that 'max' can be
+ * @property {Boolean} matchSubstring - Will always be stet to false, since Dates don't have substrings
+ * @property {string} nodeName - The XML node name to use when serializing this model into XML
+ */
+ defaults: function () {
+ return _.extend(Filter.prototype.defaults(), {
+ min: 0,
+ max: new Date().getUTCFullYear(),
+ rangeMin: 1800,
+ rangeMax: new Date().getUTCFullYear(),
+ matchSubstring: false,
+ nodeName: "dateFilter",
+ });
+ },
+
+ /**
+ * Parses the dateFilter XML node into JSON
+ *
+ * @param {Element} xml - The XML Element that contains all the DateFilter elements
+ * @return {JSON} - The JSON object literal to be set on the model
+ */
+ parse: function (xml) {
+ try {
+ var modelJSON = Filter.prototype.parse.call(this, xml);
+
+ //Get the rangeMin and rangeMax nodes
+ var rangeMinNode = $(xml).find("rangeMin"),
rangeMaxNode = $(xml).find("rangeMax");
- //Parse the range min
- if( rangeMinNode.length ){
- modelJSON.rangeMin = new Date(rangeMinNode[0].textContent).getUTCFullYear();
- }
- //Parse the range max
- if( rangeMaxNode.length ){
- modelJSON.rangeMax = new Date(rangeMaxNode[0].textContent).getUTCFullYear();
- }
+ //Parse the range min
+ if (rangeMinNode.length) {
+ modelJSON.rangeMin = new Date(
+ rangeMinNode[0].textContent,
+ ).getUTCFullYear();
+ }
+ //Parse the range max
+ if (rangeMaxNode.length) {
+ modelJSON.rangeMax = new Date(
+ rangeMaxNode[0].textContent,
+ ).getUTCFullYear();
+ }
- //If this Filter is in a filter group, don't parse the values
- if( !this.get("isUIFilterType") ){
- //Get the min, max, and value nodes
- var minNode = $(xml).find("min"),
+ //If this Filter is in a filter group, don't parse the values
+ if (!this.get("isUIFilterType")) {
+ //Get the min, max, and value nodes
+ var minNode = $(xml).find("min"),
maxNode = $(xml).find("max"),
valueNode = $(xml).find("value");
- //Parse the min value
- if( minNode.length ){
- modelJSON.min = new Date(minNode[0].textContent).getUTCFullYear();
- }
- //Parse the max value
- if( maxNode.length ){
- modelJSON.max = new Date(maxNode[0].textContent).getUTCFullYear();
- }
- //Parse the value
- if( valueNode.length ){
- modelJSON.values = [new Date(valueNode[0].textContent).getUTCFullYear()];
+ //Parse the min value
+ if (minNode.length) {
+ modelJSON.min = new Date(minNode[0].textContent).getUTCFullYear();
+ }
+ //Parse the max value
+ if (maxNode.length) {
+ modelJSON.max = new Date(maxNode[0].textContent).getUTCFullYear();
+ }
+ //Parse the value
+ if (valueNode.length) {
+ modelJSON.values = [
+ new Date(valueNode[0].textContent).getUTCFullYear(),
+ ];
+ }
}
- }
- //If a range min and max was given, or if a min and max value was given,
- // then this DateFilter should be presented as a date range (rather than
- // an exact date value).
- if( rangeMinNode.length || rangeMinNode.length || minNode || maxNode ){
- //Set the range attribute on the JSON
- modelJSON.range = true;
- }
- else{
- //Set the range attribute on the JSON
- modelJSON.range = false;
+ //If a range min and max was given, or if a min and max value was given,
+ // then this DateFilter should be presented as a date range (rather than
+ // an exact date value).
+ if (
+ rangeMinNode.length ||
+ rangeMinNode.length ||
+ minNode ||
+ maxNode
+ ) {
+ //Set the range attribute on the JSON
+ modelJSON.range = true;
+ } else {
+ //Set the range attribute on the JSON
+ modelJSON.range = false;
+ }
+ } catch (e) {
+ //If an error occured while parsing the XML, return a blank JS object
+ //(i.e. this model will just have the default values).
+ return {};
}
- }
- catch(e){
- //If an error occured while parsing the XML, return a blank JS object
- //(i.e. this model will just have the default values).
- return {};
- }
-
- return modelJSON;
-
- },
-
- /**
- * Builds a query string that represents this filter.
- *
- * @return {string} The query string to send to Solr
- */
- getQuery: function(){
-
- //Start the query string
- var queryString = "";
-
- //Only construct the query if the min or max is different than the default
- if( ((this.get("min") != this.defaults().min) && (this.get("min") != this.get("rangeMin"))) ||
- ((this.get("max") != this.defaults().max)) && (this.get("max") != this.get("rangeMax")) ){
-
- //Iterate over each filter field and add to the query string
- _.each(this.get("fields"), function(field, i, allFields){
- //Add the date range for this field to the query string
- queryString += field + ":" + this.getRangeQuery().replace(/ /g, "%20");
-
- //If there is another field, add an operator
- if( allFields[i+1] ){
- queryString += "%20" + this.get("fieldsOperator") + "%20";
+ return modelJSON;
+ },
+
+ /**
+ * Builds a query string that represents this filter.
+ *
+ * @return {string} The query string to send to Solr
+ */
+ getQuery: function () {
+ //Start the query string
+ var queryString = "";
+
+ //Only construct the query if the min or max is different than the default
+ if (
+ (this.get("min") != this.defaults().min &&
+ this.get("min") != this.get("rangeMin")) ||
+ (this.get("max") != this.defaults().max &&
+ this.get("max") != this.get("rangeMax"))
+ ) {
+ //Iterate over each filter field and add to the query string
+ _.each(
+ this.get("fields"),
+ function (field, i, allFields) {
+ //Add the date range for this field to the query string
+ queryString +=
+ field + ":" + this.getRangeQuery().replace(/ /g, "%20");
+
+ //If there is another field, add an operator
+ if (allFields[i + 1]) {
+ queryString += "%20" + this.get("fieldsOperator") + "%20";
+ }
+ },
+ this,
+ );
+
+ //If there is more than one field, wrap the query in parenthesis
+ if (this.get("fields").length > 1) {
+ queryString = "(" + queryString + ")";
}
-
- }, this);
-
- //If there is more than one field, wrap the query in parenthesis
- if( this.get("fields").length > 1 ){
- queryString = "(" + queryString + ")";
}
- }
-
- return queryString;
-
- },
+ return queryString;
+ },
- /**
- * Constructs a subquery string from the minimum and maximum dates.
- * @return {string} - THe subquery string
- */
- getRangeQuery: function(){
- //Get the minimum and maximum values
- var max = this.get("max"),
+ /**
+ * Constructs a subquery string from the minimum and maximum dates.
+ * @return {string} - THe subquery string
+ */
+ getRangeQuery: function () {
+ //Get the minimum and maximum values
+ var max = this.get("max"),
min = this.get("min");
- //If no min or max was set, but there is a value, construct an exact value match query
- if( !min && min !== 0 && !max && max !== 0 &&
- (this.get("values")[0] || this.get("values")[0] === 0) ){
- return this.get("values")[0];
- }
- //If there is no min or max or value, set an empty query string
- else if( !min && min !== 0 && !max && max !== 0 &&
- ( !this.get("values")[0] && this.get("values")[0] !== 0) ){
- return "";
- }
- //If there is at least a min or max
- else{
- //If there's a min but no max, set the max to a wildcard (unbounded)
- if( (min || min === 0) && !max ){
- max = "*";
- }
- //If there's a max but no min, set the min to a wildcard (unbounded)
- else if ( !min && min !== 0 && max ){
- min = "*";
- }
- //If the max is higher than the min, set the max to a wildcard (unbounded)
- else if( (max || max === 0) && (min || min === 0) && (max < min) ){
- max = "*";
- }
-
- if(min != "*"){
- min = min + "-01-01T00:00:00Z";
+ //If no min or max was set, but there is a value, construct an exact value match query
+ if (
+ !min &&
+ min !== 0 &&
+ !max &&
+ max !== 0 &&
+ (this.get("values")[0] || this.get("values")[0] === 0)
+ ) {
+ return this.get("values")[0];
}
- if(max != "*"){
- max = max + "-12-31T23:59:59Z";
+ //If there is no min or max or value, set an empty query string
+ else if (
+ !min &&
+ min !== 0 &&
+ !max &&
+ max !== 0 &&
+ !this.get("values")[0] &&
+ this.get("values")[0] !== 0
+ ) {
+ return "";
}
+ //If there is at least a min or max
+ else {
+ //If there's a min but no max, set the max to a wildcard (unbounded)
+ if ((min || min === 0) && !max) {
+ max = "*";
+ }
+ //If there's a max but no min, set the min to a wildcard (unbounded)
+ else if (!min && min !== 0 && max) {
+ min = "*";
+ }
+ //If the max is higher than the min, set the max to a wildcard (unbounded)
+ else if ((max || max === 0) && (min || min === 0) && max < min) {
+ max = "*";
+ }
- //Add the range for this field to the query string
- return "[" + min + "%20TO%20" + max + "]";
- }
-
- },
-
- /**
- * Updates the XML DOM with the new values from the model
- *
- * @inheritdoc
- */
- updateDOM: function(options){
-
- var objectDOM = Filter.prototype.updateDOM.call(this, options);
-
- //Date Filters don't use matchSubstring nodes, and the value node will be recreated later
- $(objectDOM).children("matchSubstring, value").remove();
-
- //Get a clone of the original DOM
- var originalDOM;
- if( this.get("objectDOM") ){
- originalDOM = this.get("objectDOM").cloneNode(true);
- }
-
- if( typeof options == "undefined" ){
- var options = {};
- }
-
- // Get min and max dates
- var dateData = {
- min: this.get("min"),
- max: this.get("max"),
- value: this.get("values") ? this.get("values")[0] : null
- };
-
- var isRange = false;
-
- // Make subnodes and and append to DOM
- _.map(dateData, function(value, nodeName){
-
- // dateFilters don't have a min or max when the values should range from
- // a min to infinity, or from a max to infinity (e.g. "date is before...")
- if(!value){
- return
- }
+ if (min != "*") {
+ min = min + "-01-01T00:00:00Z";
+ }
+ if (max != "*") {
+ max = max + "-12-31T23:59:59Z";
+ }
- if( nodeName == "min" ){
- var dateTime = "-01-01T00:00:00Z";
+ //Add the range for this field to the query string
+ return "[" + min + "%20TO%20" + max + "]";
}
- else{
- var dateTime = "-12-31T23:59:59Z";
+ },
+
+ /**
+ * Updates the XML DOM with the new values from the model
+ *
+ * @inheritdoc
+ */
+ updateDOM: function (options) {
+ var objectDOM = Filter.prototype.updateDOM.call(this, options);
+
+ //Date Filters don't use matchSubstring nodes, and the value node will be recreated later
+ $(objectDOM).children("matchSubstring, value").remove();
+
+ //Get a clone of the original DOM
+ var originalDOM;
+ if (this.get("objectDOM")) {
+ originalDOM = this.get("objectDOM").cloneNode(true);
}
- //If this value is the same as the default value, but it wasn't previously serialized,
- if( (value == this.defaults()[nodeName]) &&
- ( !$(originalDOM).children(nodeName).length ||
- ($(originalDOM).children(nodeName).text() != value + dateTime) )){
- return;
+ if (typeof options == "undefined") {
+ var options = {};
}
- //Create an XML node
- var nodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
-
- //Add the date string to the XML node
- $(nodeSerialized).text( value + dateTime );
-
- $(objectDOM).append(nodeSerialized);
-
- //If either a min or max was serialized, then mark this as a range
- isRange = true;
-
- }, this);
-
- //If a value is set on this model,
- if( !isRange && this.get("values").length ){
-
- //Create a value XML node
- var valueNode = $(objectDOM.ownerDocument.createElement("value"));
- //Get a Date object for this value
- var date = new Date();
- date.setUTCFullYear(this.get("values")[0] + "-12-31T23:59:59Z");
- //Set the text of the XML node to the date string
- valueNode.text( date.toISOString() );
- $(objectDOM).append( valueNode );
-
- }
-
-
- if( this.get("isUIFilterType") ){
-
- // Get new date data
+ // Get min and max dates
var dateData = {
- rangeMin: this.get("rangeMin"),
- rangeMax: this.get("rangeMax")
+ min: this.get("min"),
+ max: this.get("max"),
+ value: this.get("values") ? this.get("values")[0] : null,
};
- // Make subnodes and and append to DOM
- _.map(dateData, function(value, nodeName){
+ var isRange = false;
- if( nodeName == "rangeMin" ){
- var dateTime = "-01-01T00:00:00Z";
- }
- else{
- var dateTime = "-12-31T23:59:59Z";
- }
-
- //If this value is the same as the default value, but it wasn't previously serialized,
- if( (value == this.defaults()[nodeName]) &&
- ( !$(originalDOM).children(nodeName).length ||
- ($(originalDOM).children(nodeName).text() != value + dateTime) )){
+ // Make subnodes and and append to DOM
+ _.map(
+ dateData,
+ function (value, nodeName) {
+ // dateFilters don't have a min or max when the values should range from
+ // a min to infinity, or from a max to infinity (e.g. "date is before...")
+ if (!value) {
return;
- }
-
- //Create an XML node
- var nodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
+ }
+
+ if (nodeName == "min") {
+ var dateTime = "-01-01T00:00:00Z";
+ } else {
+ var dateTime = "-12-31T23:59:59Z";
+ }
+
+ //If this value is the same as the default value, but it wasn't previously serialized,
+ if (
+ value == this.defaults()[nodeName] &&
+ (!$(originalDOM).children(nodeName).length ||
+ $(originalDOM).children(nodeName).text() != value + dateTime)
+ ) {
+ return;
+ }
+
+ //Create an XML node
+ var nodeSerialized =
+ objectDOM.ownerDocument.createElement(nodeName);
+
+ //Add the date string to the XML node
+ $(nodeSerialized).text(value + dateTime);
+
+ $(objectDOM).append(nodeSerialized);
+
+ //If either a min or max was serialized, then mark this as a range
+ isRange = true;
+ },
+ this,
+ );
+
+ //If a value is set on this model,
+ if (!isRange && this.get("values").length) {
+ //Create a value XML node
+ var valueNode = $(objectDOM.ownerDocument.createElement("value"));
+ //Get a Date object for this value
+ var date = new Date();
+ date.setUTCFullYear(this.get("values")[0] + "-12-31T23:59:59Z");
+ //Set the text of the XML node to the date string
+ valueNode.text(date.toISOString());
+ $(objectDOM).append(valueNode);
+ }
- //Add the date string to the XML node
- $(nodeSerialized).text( value + dateTime );
+ if (this.get("isUIFilterType")) {
+ // Get new date data
+ var dateData = {
+ rangeMin: this.get("rangeMin"),
+ rangeMax: this.get("rangeMax"),
+ };
+
+ // Make subnodes and and append to DOM
+ _.map(
+ dateData,
+ function (value, nodeName) {
+ if (nodeName == "rangeMin") {
+ var dateTime = "-01-01T00:00:00Z";
+ } else {
+ var dateTime = "-12-31T23:59:59Z";
+ }
+
+ //If this value is the same as the default value, but it wasn't previously serialized,
+ if (
+ value == this.defaults()[nodeName] &&
+ (!$(originalDOM).children(nodeName).length ||
+ $(originalDOM).children(nodeName).text() != value + dateTime)
+ ) {
+ return;
+ }
+
+ //Create an XML node
+ var nodeSerialized =
+ objectDOM.ownerDocument.createElement(nodeName);
+
+ //Add the date string to the XML node
+ $(nodeSerialized).text(value + dateTime);
+
+ //Remove existing nodes and add the new one
+ $(objectDOM).children(nodeName).remove();
+ $(objectDOM).append(nodeSerialized);
+ },
+ this,
+ );
+
+ //Move the filterOptions node to the end of the filter node
+ var filterOptionsNode = $(objectDOM).find("filterOptions");
+ filterOptionsNode.detach();
+ $(objectDOM).append(filterOptionsNode);
+ }
+ //Remove filterOptions for Date filters in collection definitions
+ else {
+ $(objectDOM).find("filterOptions").remove();
+ }
- //Remove existing nodes and add the new one
- $(objectDOM).children(nodeName).remove();
- $(objectDOM).append(nodeSerialized);
+ return objectDOM;
+ },
- }, this);
+ /**
+ * Creates a human-readable string that represents the value set on this model
+ * @return {string}
+ */
+ getReadableValue: function () {
+ var readableValue = "";
- //Move the filterOptions node to the end of the filter node
- var filterOptionsNode = $(objectDOM).find("filterOptions");
- filterOptionsNode.detach();
- $(objectDOM).append(filterOptionsNode);
- }
- //Remove filterOptions for Date filters in collection definitions
- else{
- $(objectDOM).find("filterOptions").remove();
- }
+ var min = this.get("min"),
+ max = this.get("max"),
+ value = this.get("values")[0];
- return objectDOM;
- },
+ if (!value && value !== 0) {
+ //If there is a min and max
+ if ((min || min === 0) && (max || max === 0)) {
+ readableValue = min + " to " + max;
+ }
+ //If there is only a max
+ else if (max || max === 0) {
+ readableValue = "Before " + max;
+ } else {
+ readableValue = "After " + min;
+ }
+ } else {
+ readableValue = value;
+ }
- /**
- * Creates a human-readable string that represents the value set on this model
- * @return {string}
- */
- getReadableValue: function(){
+ return readableValue;
+ },
+
+ /**
+ * @inheritdoc
+ */
+ hasChangedValues: function () {
+ return (
+ (this.get("min") > this.get("rangeMin") &&
+ this.get("min") !== this.defaults().min) ||
+ (this.get("max") < this.get("rangeMax") &&
+ this.get("max") !== this.defaults().max)
+ );
+ },
+
+ /**
+ * Checks if the values set on this model are valid and expected
+ * @return {object} - Returns a literal object with the invalid attributes and their corresponding error message
+ */
+ validate: function () {
+ //Validate most of the DateFilter attributes using the parent validate function
+ var errors = Filter.prototype.validate.call(this);
+
+ //If everything is valid so far, then we have to create a new object to store errors
+ if (typeof errors != "object") {
+ errors = {};
+ }
- var readableValue = "";
+ //Delete error messages for the attributes that are going to be validated specially for the DateFilter
+ delete errors.values;
+ delete errors.min;
+ delete errors.max;
- var min = this.get("min"),
- max = this.get("max"),
- value = this.get("values")[0];
+ // Check that there is a rangeMin and a rangeMax. If there isn't, then just set to
+ // the default rather than creating an error.
+ if (!this.get("rangeMin") && this.get("rangeMin") !== 0) {
+ this.set("rangeMin", this.defaults().rangeMin);
+ }
+ if (!this.get("rangeMax") && this.get("rangeMax") !== 0) {
+ this.set("rangeMax", this.defaults().rangeMax);
+ }
- if( !value && value !== 0 ){
- //If there is a min and max
- if( (min || min === 0) && (max || max === 0) ){
- readableValue = min + " to " + max;
+ //Check that there aren't any negative numbers
+ if (this.get("min") < 0) {
+ errors.min = "The minimum year cannot be a negative number.";
}
- //If there is only a max
- else if(max || max === 0){
- readableValue = "Before " + max;
+ if (this.get("max") < 0) {
+ errors.max = "The maximum year cannot be a negative number.";
}
- else{
- readableValue = "After " + min;
+ if (this.get("rangeMin") < 0) {
+ errors.rangeMin =
+ "The range minimum year cannot be a negative number.";
+ }
+ if (this.get("rangeMax") < 0) {
+ errors.rangeMax =
+ "The range maximum year cannot be a negative number.";
}
- }
- else{
- readableValue = value;
- }
-
- return readableValue;
- },
+ //Check that the min and max values are in order, if the minimum is not the default value of 0
+ if (this.get("min") > this.get("max") && this.get("min") != 0) {
+ errors.min =
+ "The minimum year is after the maximum year. The minimum year must be a year before the maximum year of " +
+ this.get("max");
+ }
- /**
- * @inheritdoc
- */
- hasChangedValues: function(){
+ //Check that all the values are numbers
+ if (!errors.min && typeof this.get("min") != "number") {
+ errors.min = "The minimum year must be a number.";
+ }
+ if (!errors.max && typeof this.get("max") != "number") {
+ errors.max = "The maximum year must be a number.";
+ }
+ if (!errors.rangeMax && typeof this.get("rangeMax") != "number") {
+ errors.rangeMax =
+ "The maximum year in the date slider must be a number.";
+ }
- return ((this.get("min") > this.get("rangeMin") && this.get("min") !== this.defaults().min) ||
- (this.get("max") < this.get("rangeMax") && this.get("max") !== this.defaults().max))
+ if (!errors.rangeMin && typeof this.get("rangeMin") != "number") {
+ errors.rangeMin =
+ "The minimum year in the date slider must be a number.";
+ }
+ if (Object.keys(errors).length) return errors;
+ else {
+ return;
+ }
+ },
},
-
- /**
- * Checks if the values set on this model are valid and expected
- * @return {object} - Returns a literal object with the invalid attributes and their corresponding error message
- */
- validate: function(){
-
- //Validate most of the DateFilter attributes using the parent validate function
- var errors = Filter.prototype.validate.call(this);
-
- //If everything is valid so far, then we have to create a new object to store errors
- if (typeof errors != "object") {
- errors = {};
- }
-
- //Delete error messages for the attributes that are going to be validated specially for the DateFilter
- delete errors.values;
- delete errors.min;
- delete errors.max;
-
- // Check that there is a rangeMin and a rangeMax. If there isn't, then just set to
- // the default rather than creating an error.
- if (!this.get("rangeMin") && this.get("rangeMin") !== 0) {
- this.set("rangeMin", this.defaults().rangeMin)
- }
- if (!this.get("rangeMax") && this.get("rangeMax") !== 0) {
- this.set("rangeMax", this.defaults().rangeMax)
- }
-
- //Check that there aren't any negative numbers
- if( this.get("min") < 0 ){
- errors.min = "The minimum year cannot be a negative number."
- }
- if( this.get("max") < 0 ){
- errors.max = "The maximum year cannot be a negative number."
- }
- if( this.get("rangeMin") < 0 ){
- errors.rangeMin = "The range minimum year cannot be a negative number."
- }
- if( this.get("rangeMax") < 0 ){
- errors.rangeMax = "The range maximum year cannot be a negative number."
- }
-
- //Check that the min and max values are in order, if the minimum is not the default value of 0
- if( this.get("min") > this.get("max") && this.get("min") != 0 ){
- errors.min = "The minimum year is after the maximum year. The minimum year must be a year before the maximum year of " + this.get("max");
- }
-
- //Check that all the values are numbers
- if( !errors.min && typeof this.get("min") != "number" ){
- errors.min = "The minimum year must be a number.";
- }
- if( !errors.max && typeof this.get("max") != "number" ){
- errors.max = "The maximum year must be a number.";
- }
- if( !errors.rangeMax && typeof this.get("rangeMax") != "number" ){
- errors.rangeMax = "The maximum year in the date slider must be a number.";
- }
-
- if( !errors.rangeMin && typeof this.get("rangeMin") != "number" ){
- errors.rangeMin = "The minimum year in the date slider must be a number.";
- }
-
- if (Object.keys(errors).length)
- return errors;
- else {
- return;
- }
-
- }
-
- });
+ );
return DateFilter;
});
diff --git a/src/js/models/filters/Filter.js b/src/js/models/filters/Filter.js
index 980961863..02902658b 100644
--- a/src/js/models/filters/Filter.js
+++ b/src/js/models/filters/Filter.js
@@ -1,1008 +1,1053 @@
/* global define */
-define(['jquery', 'underscore', 'backbone'],
- function($, _, Backbone) {
-
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
/**
- * @class Filter
- * @classdesc A single search filter that is used in queries sent to the DataONE search service.
- * @classcategory Models/Filters
- * @extends Backbone.Model
- * @constructs
- */
+ * @class Filter
+ * @classdesc A single search filter that is used in queries sent to the DataONE search service.
+ * @classcategory Models/Filters
+ * @extends Backbone.Model
+ * @constructs
+ */
var FilterModel = Backbone.Model.extend(
/** @lends Filter.prototype */
{
+ /**
+ * The name of this Model
+ * @name Filter#type
+ * @type {string}
+ * @readonly
+ */
+ type: "Filter",
+
+ /**
+ * Default attributes for this model
+ * @type {object}
+ * @returns {object}
+ * @property {Element} objectDOM - The XML DOM for this filter
+ * @property {string} nodeName - The XML node name for this filter's XML DOM
+ * @property {string[]} fields - The search index fields to search
+ * @property {string[]} values - The values to search for in the given search fields
+ * @property {object} valueLabels - Optional human-readable labels for the elements of
+ * values. Keys are the value and the human-readable label is the value at that key.
+ * @property {string} operator - The operator to use between values set on this model.
+ * "AND" or "OR"
+ * @property {string} fieldsOperator - The operator to use between fields set on this
+ * model. "AND" or "OR"
+ * @property {string} queryGroup - Deprecated: Add this filter along with other the
+ * other associated query group filters to a FilterGroup model instead. Old definition:
+ * The name of the group this Filter is a part of, which is primarily used when
+ * creating a query string from multiple Filter models. Filters in the same group will
+ * be wrapped in parenthesis in the query.
+ * @property {boolean} exclude - If true, search index docs matching this filter will
+ * be excluded from the search results
+ * @property {boolean} matchSubstring - If true, the search values will be wrapped in
+ * wildcard characters to match substrings
+ * @property {string} label - A human-readable short label for this Filter
+ * @property {string} placeholder - A short example or description of this Filter
+ * @property {string} icon - A term that identifies a single icon in a supported icon
+ * library
+ * @property {string} description - A longer description of this Filter's function
+ * @property {boolean} isInvisible - If true, this filter will be added to the query
+ * but will act in the "background", like a default filter
+ * @property {boolean} inFilterGroup - Deprecated: use isUIFilterType instead.
+ * @property {boolean} isUIFilterType - If true, this filter is one of the
+ * UIFilterTypes, belongs to a UIFilterGroupType model, and is used to create a custom
+ * Portal search filters. This changes how the XML is parsed and how the model is
+ * validated and serialized.
+ */
+ defaults: function () {
+ return {
+ objectDOM: null,
+ nodeName: "filter",
+ fields: [],
+ values: [],
+ valueLabels: {},
+ operator: "AND",
+ fieldsOperator: "AND",
+ exclude: false,
+ matchSubstring: false,
+ label: null,
+ placeholder: null,
+ icon: null,
+ description: null,
+ isInvisible: false,
+ isUIFilterType: false,
+ };
+ },
- /**
- * The name of this Model
- * @name Filter#type
- * @type {string}
- * @readonly
- */
- type: "Filter",
-
- /**
- * Default attributes for this model
- * @type {object}
- * @returns {object}
- * @property {Element} objectDOM - The XML DOM for this filter
- * @property {string} nodeName - The XML node name for this filter's XML DOM
- * @property {string[]} fields - The search index fields to search
- * @property {string[]} values - The values to search for in the given search fields
- * @property {object} valueLabels - Optional human-readable labels for the elements of
- * values. Keys are the value and the human-readable label is the value at that key.
- * @property {string} operator - The operator to use between values set on this model.
- * "AND" or "OR"
- * @property {string} fieldsOperator - The operator to use between fields set on this
- * model. "AND" or "OR"
- * @property {string} queryGroup - Deprecated: Add this filter along with other the
- * other associated query group filters to a FilterGroup model instead. Old definition:
- * The name of the group this Filter is a part of, which is primarily used when
- * creating a query string from multiple Filter models. Filters in the same group will
- * be wrapped in parenthesis in the query.
- * @property {boolean} exclude - If true, search index docs matching this filter will
- * be excluded from the search results
- * @property {boolean} matchSubstring - If true, the search values will be wrapped in
- * wildcard characters to match substrings
- * @property {string} label - A human-readable short label for this Filter
- * @property {string} placeholder - A short example or description of this Filter
- * @property {string} icon - A term that identifies a single icon in a supported icon
- * library
- * @property {string} description - A longer description of this Filter's function
- * @property {boolean} isInvisible - If true, this filter will be added to the query
- * but will act in the "background", like a default filter
- * @property {boolean} inFilterGroup - Deprecated: use isUIFilterType instead.
- * @property {boolean} isUIFilterType - If true, this filter is one of the
- * UIFilterTypes, belongs to a UIFilterGroupType model, and is used to create a custom
- * Portal search filters. This changes how the XML is parsed and how the model is
- * validated and serialized.
- */
- defaults: function(){
- return{
- objectDOM: null,
- nodeName: "filter",
- fields: [],
- values: [],
- valueLabels: {},
- operator: "AND",
- fieldsOperator: "AND",
- exclude: false,
- matchSubstring: false,
- label: null,
- placeholder: null,
- icon: null,
- description: null,
- isInvisible: false,
- isUIFilterType: false
- }
- },
+ /**
+ * Creates a new Filter model
+ */
+ initialize: function (attributes) {
+ if (this.get("objectDOM")) {
+ this.set(this.parse(this.get("objectDOM")));
+ }
- /**
- * Creates a new Filter model
- */
- initialize: function(attributes){
-
- if( this.get("objectDOM") ){
- this.set( this.parse(this.get("objectDOM")) );
- }
-
- if (attributes && attributes.isUIFilterType){
- this.set("isUIFilterType", true)
- }
-
- //If this is an isPartOf filter, then add a label and description. Make it invisible
- //depending on how MetacatUI is configured.
- if( this.get("fields").length == 1 && this.get("fields").includes("isPartOf") ){
- this.set({
- label: "Datasets added manually",
- description: "Datasets added to this collection manually by dataset owners",
- isInvisible: MetacatUI.appModel.get("hideIsPartOfFilter") === true ? true : false,
- });
- }
+ if (attributes && attributes.isUIFilterType) {
+ this.set("isUIFilterType", true);
+ }
- // Operator must be AND or OR
- ["fieldsOperator", "operator"].forEach(function(op){
- if( !["AND", "OR"].includes(this.get(op)) ){
- // Set the value to the default
- this.set(op, this.defaults()[op])
+ //If this is an isPartOf filter, then add a label and description. Make it invisible
+ //depending on how MetacatUI is configured.
+ if (
+ this.get("fields").length == 1 &&
+ this.get("fields").includes("isPartOf")
+ ) {
+ this.set({
+ label: "Datasets added manually",
+ description:
+ "Datasets added to this collection manually by dataset owners",
+ isInvisible:
+ MetacatUI.appModel.get("hideIsPartOfFilter") === true
+ ? true
+ : false,
+ });
}
- }, this);
- },
+ // Operator must be AND or OR
+ ["fieldsOperator", "operator"].forEach(function (op) {
+ if (!["AND", "OR"].includes(this.get(op))) {
+ // Set the value to the default
+ this.set(op, this.defaults()[op]);
+ }
+ }, this);
+ },
- /**
- * Parses the given XML node into a JSON object to be set on the model
- *
- * @param {Element} xml - The XML element that contains all the filter elements
- * @return {JSON} - The JSON object of all the filter attributes
- */
- parse: function(xml){
+ /**
+ * Parses the given XML node into a JSON object to be set on the model
+ *
+ * @param {Element} xml - The XML element that contains all the filter elements
+ * @return {JSON} - The JSON object of all the filter attributes
+ */
+ parse: function (xml) {
+ //If an XML element wasn't sent as a parameter, get it from the model
+ if (!xml) {
+ var xml = this.get("objectDOM");
+
+ //Return an empty JSON object if there is no objectDOM saved in the model
+ if (!xml) return {};
+ }
- //If an XML element wasn't sent as a parameter, get it from the model
- if(!xml){
- var xml = this.get("objectDOM");
+ var modelJSON = {};
- //Return an empty JSON object if there is no objectDOM saved in the model
- if(!xml)
- return {};
- }
-
- var modelJSON = {};
-
- if( $(xml).children("field").length ){
- //Parse the field(s)
- modelJSON.fields = this.parseTextNode(xml, "field", true);
- }
-
- if( $(xml).children("label").length ){
- //Parse the label
- modelJSON.label = this.parseTextNode(xml, "label");
- }
-
- // Check if this filter contains one of the Id fields - we use OR by default for the
- // operator for these fields.
- var idFields = MetacatUI.appModel.get("queryIdentifierFields");
- var isIdFilter = false;
- if(modelJSON.fields){
- isIdFilter = _.some( idFields, function(idField) {
- return modelJSON.fields.includes(idField)
- });
- }
-
- //Parse the operators, if they exist
- if( $(xml).find("operator").length ){
- modelJSON.operator = this.parseTextNode(xml, "operator");
- }
- else{
- if( isIdFilter ){
- modelJSON.operator = "OR";
+ if ($(xml).children("field").length) {
+ //Parse the field(s)
+ modelJSON.fields = this.parseTextNode(xml, "field", true);
}
- }
- if( $(xml).find("fieldsOperator").length ){
- modelJSON.fieldsOperator = this.parseTextNode(xml, "fieldsOperator");
- }
- else{
- if( isIdFilter ){
- modelJSON.fieldsOperator = "OR";
+ if ($(xml).children("label").length) {
+ //Parse the label
+ modelJSON.label = this.parseTextNode(xml, "label");
}
- }
- //Parse the exclude, if it exists
- if( $(xml).find("exclude").length ){
- modelJSON.exclude = (this.parseTextNode(xml, "exclude") === "true")? true : false;
- }
+ // Check if this filter contains one of the Id fields - we use OR by default for the
+ // operator for these fields.
+ var idFields = MetacatUI.appModel.get("queryIdentifierFields");
+ var isIdFilter = false;
+ if (modelJSON.fields) {
+ isIdFilter = _.some(idFields, function (idField) {
+ return modelJSON.fields.includes(idField);
+ });
+ }
- //Parse the matchSubstring
- if( $(xml).find("matchSubstring").length ){
- modelJSON.matchSubstring = (this.parseTextNode(xml, "matchSubstring") === "true")? true : false;
- }
+ //Parse the operators, if they exist
+ if ($(xml).find("operator").length) {
+ modelJSON.operator = this.parseTextNode(xml, "operator");
+ } else {
+ if (isIdFilter) {
+ modelJSON.operator = "OR";
+ }
+ }
- var filterOptionsNode = $(xml).children("filterOptions");
- if( filterOptionsNode.length ){
- //Parse the filterOptions XML node
- modelJSON = _.extend(this.parseFilterOptions(filterOptionsNode), modelJSON);
- }
+ if ($(xml).find("fieldsOperator").length) {
+ modelJSON.fieldsOperator = this.parseTextNode(xml, "fieldsOperator");
+ } else {
+ if (isIdFilter) {
+ modelJSON.fieldsOperator = "OR";
+ }
+ }
- //If this Filter is in a filter group, don't parse the values
- if( !this.get("isUIFilterType") ){
- if( $(xml).children("value").length ){
- //Parse the value(s)
- modelJSON.values = this.parseTextNode(xml, "value", true);
+ //Parse the exclude, if it exists
+ if ($(xml).find("exclude").length) {
+ modelJSON.exclude =
+ this.parseTextNode(xml, "exclude") === "true" ? true : false;
}
- }
- return modelJSON;
+ //Parse the matchSubstring
+ if ($(xml).find("matchSubstring").length) {
+ modelJSON.matchSubstring =
+ this.parseTextNode(xml, "matchSubstring") === "true" ? true : false;
+ }
- },
+ var filterOptionsNode = $(xml).children("filterOptions");
+ if (filterOptionsNode.length) {
+ //Parse the filterOptions XML node
+ modelJSON = _.extend(
+ this.parseFilterOptions(filterOptionsNode),
+ modelJSON,
+ );
+ }
- /**
- * Gets the text content of the XML node matching the given node name
- *
- * @param {Element} parentNode - The parent node to select from
- * @param {string} nodeName - The name of the XML node to parse
- * @param {boolean} isMultiple - If true, parses the nodes into an array
- * @return {(string|Array)} - Returns a string or array of strings of the text content
- */
- parseTextNode: function( parentNode, nodeName, isMultiple ){
- var node = $(parentNode).children(nodeName);
-
- //If no matching nodes were found, return falsey values
- if( !node || !node.length ){
-
- //Return an empty array if the isMultiple flag is true
- if( isMultiple )
- return [];
- //Return null if the isMultiple flag is false
- else
- return null;
- }
- //If exactly one node is found and we are only expecting one, return the text content
- else if( node.length == 1 && !isMultiple ){
- if( !node[0].textContent )
- return null;
- else
- return node[0].textContent;
- }
- //If more than one node is found, parse into an array
- else{
-
- var allContents = [];
-
- _.each(node, function(node){
- if(node.textContent || node.textContent === 0)
- allContents.push( node.textContent );
- });
+ //If this Filter is in a filter group, don't parse the values
+ if (!this.get("isUIFilterType")) {
+ if ($(xml).children("value").length) {
+ //Parse the value(s)
+ modelJSON.values = this.parseTextNode(xml, "value", true);
+ }
+ }
- return allContents;
+ return modelJSON;
+ },
- }
- },
+ /**
+ * Gets the text content of the XML node matching the given node name
+ *
+ * @param {Element} parentNode - The parent node to select from
+ * @param {string} nodeName - The name of the XML node to parse
+ * @param {boolean} isMultiple - If true, parses the nodes into an array
+ * @return {(string|Array)} - Returns a string or array of strings of the text content
+ */
+ parseTextNode: function (parentNode, nodeName, isMultiple) {
+ var node = $(parentNode).children(nodeName);
+
+ //If no matching nodes were found, return falsey values
+ if (!node || !node.length) {
+ //Return an empty array if the isMultiple flag is true
+ if (isMultiple) return [];
+ //Return null if the isMultiple flag is false
+ else return null;
+ }
+ //If exactly one node is found and we are only expecting one, return the text content
+ else if (node.length == 1 && !isMultiple) {
+ if (!node[0].textContent) return null;
+ else return node[0].textContent;
+ }
+ //If more than one node is found, parse into an array
+ else {
+ var allContents = [];
- /**
- * Parses the filterOptions XML node into a literal object
- * @param {Element} filterOptionsNode - The filterOptions XML element to parse
- * @return {Object} - The parsed filter options, in literal object form
- */
- parseFilterOptions: function(filterOptionsNode){
+ _.each(node, function (node) {
+ if (node.textContent || node.textContent === 0)
+ allContents.push(node.textContent);
+ });
- if( typeof filterOptionsNode == "undefined" ){
- return {};
- }
+ return allContents;
+ }
+ },
- var modelJSON = {};
+ /**
+ * Parses the filterOptions XML node into a literal object
+ * @param {Element} filterOptionsNode - The filterOptions XML element to parse
+ * @return {Object} - The parsed filter options, in literal object form
+ */
+ parseFilterOptions: function (filterOptionsNode) {
+ if (typeof filterOptionsNode == "undefined") {
+ return {};
+ }
- try{
- //The list of options to parse
- var options = ["placeholder", "icon", "description"];
+ var modelJSON = {};
+
+ try {
+ //The list of options to parse
+ var options = ["placeholder", "icon", "description"];
+
+ //Parse the text nodes for each filter option
+ _.each(
+ options,
+ function (option) {
+ if ($(filterOptionsNode).children(option).length) {
+ modelJSON[option] = this.parseTextNode(
+ filterOptionsNode,
+ option,
+ false,
+ );
+ }
+ },
+ this,
+ );
+
+ //Parse the generic option name and value pairs and set on the model JSON
+ $(filterOptionsNode)
+ .children("option")
+ .each(function (i, optionNode) {
+ var optName = $(optionNode).children("optionName").text();
+ var optValue = $(optionNode).children("optionValue").text();
+
+ modelJSON[optName] = optValue;
+ });
+
+ //Return the JSON to be set on this model
+ return modelJSON;
+ } catch (e) {
+ return {};
+ }
+ },
- //Parse the text nodes for each filter option
- _.each(options, function(option){
- if( $(filterOptionsNode).children(option).length ){
- modelJSON[option] = this.parseTextNode(filterOptionsNode, option, false);
- }
- }, this);
+ /**
+ * Builds a query string that represents this filter.
+ *
+ * @return {string} The query string to send to Solr
+ * @param {string} [groupLevelOperator] - "AND" or "OR". The operator used in the
+ * parent Filters collection to combine the filter query fragments together. If the
+ * group level operator is "OR" and this filter has exclude set to TRUE, then a
+ * positive clause is added.
+ */
+ getQuery: function (groupLevelOperator) {
+ //Get the values of this filter in Array format
+ var values = this.get("values");
+ if (!Array.isArray(values)) {
+ values = [values];
+ }
- //Parse the generic option name and value pairs and set on the model JSON
- $(filterOptionsNode).children("option").each(function(i, optionNode){
- var optName = $(optionNode).children("optionName").text();
- var optValue = $(optionNode).children("optionValue").text();
+ //Check that there are actually values to serialize
+ if (!values.length) {
+ return "";
+ }
- modelJSON[optName] = optValue;
+ //Filter out any invalid values (can't use _.compact() because we want to keep 'false' values)
+ values = _.reject(values, function (value) {
+ return (
+ value === null ||
+ typeof value == "undefined" ||
+ value === NaN ||
+ value === "" ||
+ (Array.isArray(value) && !value.length)
+ );
});
- //Return the JSON to be set on this model
- return modelJSON;
-
- }
- catch(e){
- return {};
- }
-
- },
+ if (!values.length) {
+ return "";
+ }
- /**
- * Builds a query string that represents this filter.
- *
- * @return {string} The query string to send to Solr
- * @param {string} [groupLevelOperator] - "AND" or "OR". The operator used in the
- * parent Filters collection to combine the filter query fragments together. If the
- * group level operator is "OR" and this filter has exclude set to TRUE, then a
- * positive clause is added.
- */
- getQuery: function(groupLevelOperator){
-
- //Get the values of this filter in Array format
- var values = this.get("values");
- if( !Array.isArray(values) ){
- values = [values];
- }
-
- //Check that there are actually values to serialize
- if( !values.length ){
- return "";
- }
-
- //Filter out any invalid values (can't use _.compact() because we want to keep 'false' values)
- values = _.reject(values, function(value){
- return (value === null || typeof value == "undefined" ||
- value === NaN || value === "" || (Array.isArray(value) && !value.length));
- });
-
- if(!values.length){
- return "";
- }
-
- //Start a query string for this model and get the fields
- var queryString = "",
+ //Start a query string for this model and get the fields
+ var queryString = "",
fields = this.get("fields");
- //If the fields are not an array, convert it to an array
- if( !Array.isArray(fields) ){
- fields = [fields];
- }
-
- //Iterate over each field
- _.each( fields, function(field, i){
-
- //Add the query string for this field to the overall model query string
- queryString += field + ":" + this.getValueQuerySubstring(values);
-
- //Add the OR operator between field names
- if( fields.length > i+1 && queryString.length ){
- queryString += "%20" + this.get("fieldsOperator") + "%20";
+ //If the fields are not an array, convert it to an array
+ if (!Array.isArray(fields)) {
+ fields = [fields];
}
- }, this);
+ //Iterate over each field
+ _.each(
+ fields,
+ function (field, i) {
+ //Add the query string for this field to the overall model query string
+ queryString += field + ":" + this.getValueQuerySubstring(values);
+
+ //Add the OR operator between field names
+ if (fields.length > i + 1 && queryString.length) {
+ queryString += "%20" + this.get("fieldsOperator") + "%20";
+ }
+ },
+ this,
+ );
- //If there is more than one field, wrap the multiple fields in parenthesis
- if( fields.length > 1 ){
- queryString = "(" + queryString + ")"
- }
+ //If there is more than one field, wrap the multiple fields in parenthesis
+ if (fields.length > 1) {
+ queryString = "(" + queryString + ")";
+ }
- //If this filter should be excluding matches from the results,
- // then add a hyphen in front
- if( queryString && this.get("exclude") ){
- queryString = "-" + queryString;
- if (this.requiresPositiveClause(groupLevelOperator)){
- queryString = queryString + "%20AND%20*:*";
- if (groupLevelOperator && groupLevelOperator === "OR"){
- queryString = "(" + queryString + ")"
+ //If this filter should be excluding matches from the results,
+ // then add a hyphen in front
+ if (queryString && this.get("exclude")) {
+ queryString = "-" + queryString;
+ if (this.requiresPositiveClause(groupLevelOperator)) {
+ queryString = queryString + "%20AND%20*:*";
+ if (groupLevelOperator && groupLevelOperator === "OR") {
+ queryString = "(" + queryString + ")";
+ }
}
}
- }
-
- return queryString;
- },
+ return queryString;
+ },
- /**
- * For "negative" Filters (filter models where exclude is set to true), detects
- * whether the query requires an additional "positive" query phrase in order to avoid
- * the problem of pure negative queries returning zero results. If this Filter is not
- * part of a collection of Filters, assume it needs the positive clause. If this
- * Filter is part of a collection of Filters, detect whether there are other,
- * "positive" filters in the same query (i.e. filter models where exclude is set to
- * false). If there are other positive queries, then an additional clause is not
- * required. If the Filter is part of a pure negative query, but it is not the last
- * filter, then don't add a clause since it will be added to the last, and only one
- * is required. When looking for other positive and negative filters, exclude empty
- * filters and filters that use any of the identifier fields, as these are appended to
- * the end of the query.
- * @see {@link https://github.com/NCEAS/metacatui/issues/1600}
- * @see {@link https://cwiki.apache.org/confluence/display/SOLR/NegativeQueryProblems}
- * @param {string} [groupLevelOperator] - "AND" or "OR". The operator used in the
- * parent Filters collection to combine the filter query fragments together. If the
- * group level operator is "OR" and this filter has exclude set to TRUE, then a
- * positive clause is required.
- * @return {boolean} returns true of this Filter needs a positive clause, false
- * otherwise
- */
- requiresPositiveClause: function (groupLevelOperator){
-
- try {
-
- // Only negative queries require the additional clause
- if(this.get("exclude") == false ){
- return false
- }
- // If this Filter is not part of a collection of Filters, assume it needs the
- // positive clause.
- if(!this.collection){
- return true
- }
- // If this Filter is the only one in the group, assume it needs a positive clause
- if(this.collection.length === 1){
- return true
- }
- // If this filter is being "OR"'ed together with other filters, then assume it
- // needs the additional clause.
- if (groupLevelOperator && groupLevelOperator === "OR"){
- return true
- }
- // Get all of the other filters in the same collection that are not ID filters.
- // These filters are always appended to the end of the query as a separated group.
- var nonIDFilters = this.collection.getNonIdFilters();
- // Exclude filters that would give an empty query string (e.g. because value is
- // missing)
- var filters = _.reject(nonIDFilters, function(filterModel){
- if(filterModel === this){
- return false
- }
- return !filterModel.isValid()
- })
-
- // If at least one filter in the collection is positive (exclude = false), then we
- // don't need to add anything
- var positiveFilters = _.find(filters, function(filterModel){
- return filterModel.get("exclude") != true;
- });
- if(positiveFilters){
- return false
- }
- // Assuming that all the non-ID filters are negative, check if this is the first
- // last the list. Since we only need one additional positive query phrase to avoid
- // the pure negative query problem, by convention, only add the positive phrase at
- // the end of the filter group
- if(this === _.last(filters)){
- return true
- } else {
- return false
+ /**
+ * For "negative" Filters (filter models where exclude is set to true), detects
+ * whether the query requires an additional "positive" query phrase in order to avoid
+ * the problem of pure negative queries returning zero results. If this Filter is not
+ * part of a collection of Filters, assume it needs the positive clause. If this
+ * Filter is part of a collection of Filters, detect whether there are other,
+ * "positive" filters in the same query (i.e. filter models where exclude is set to
+ * false). If there are other positive queries, then an additional clause is not
+ * required. If the Filter is part of a pure negative query, but it is not the last
+ * filter, then don't add a clause since it will be added to the last, and only one
+ * is required. When looking for other positive and negative filters, exclude empty
+ * filters and filters that use any of the identifier fields, as these are appended to
+ * the end of the query.
+ * @see {@link https://github.com/NCEAS/metacatui/issues/1600}
+ * @see {@link https://cwiki.apache.org/confluence/display/SOLR/NegativeQueryProblems}
+ * @param {string} [groupLevelOperator] - "AND" or "OR". The operator used in the
+ * parent Filters collection to combine the filter query fragments together. If the
+ * group level operator is "OR" and this filter has exclude set to TRUE, then a
+ * positive clause is required.
+ * @return {boolean} returns true of this Filter needs a positive clause, false
+ * otherwise
+ */
+ requiresPositiveClause: function (groupLevelOperator) {
+ try {
+ // Only negative queries require the additional clause
+ if (this.get("exclude") == false) {
+ return false;
+ }
+ // If this Filter is not part of a collection of Filters, assume it needs the
+ // positive clause.
+ if (!this.collection) {
+ return true;
+ }
+ // If this Filter is the only one in the group, assume it needs a positive clause
+ if (this.collection.length === 1) {
+ return true;
+ }
+ // If this filter is being "OR"'ed together with other filters, then assume it
+ // needs the additional clause.
+ if (groupLevelOperator && groupLevelOperator === "OR") {
+ return true;
+ }
+ // Get all of the other filters in the same collection that are not ID filters.
+ // These filters are always appended to the end of the query as a separated group.
+ var nonIDFilters = this.collection.getNonIdFilters();
+ // Exclude filters that would give an empty query string (e.g. because value is
+ // missing)
+ var filters = _.reject(nonIDFilters, function (filterModel) {
+ if (filterModel === this) {
+ return false;
+ }
+ return !filterModel.isValid();
+ });
+
+ // If at least one filter in the collection is positive (exclude = false), then we
+ // don't need to add anything
+ var positiveFilters = _.find(filters, function (filterModel) {
+ return filterModel.get("exclude") != true;
+ });
+ if (positiveFilters) {
+ return false;
+ }
+ // Assuming that all the non-ID filters are negative, check if this is the first
+ // last the list. Since we only need one additional positive query phrase to avoid
+ // the pure negative query problem, by convention, only add the positive phrase at
+ // the end of the filter group
+ if (this === _.last(filters)) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (error) {
+ console.log(
+ "There was a problem detecting whether a Filter required a positive" +
+ " clause. Assuming that it needs one. Error details: " +
+ error,
+ );
+ return true;
}
- } catch (error) {
- console.log("There was a problem detecting whether a Filter required a positive" +
- " clause. Assuming that it needs one. Error details: " + error
- );
- return true
- }
- },
+ },
- /**
- * Constructs a query substring for each of the values set on this model
- *
- * @example
- * values: ["walker", "jones"]
- * Returns: "(walker%20OR%20jones)"
- *
- * @param {string[]} [values] - The values to use in this query substring.
- * If not provided, the values set on the model are used.
- * @return {string} The query substring
- */
- getValueQuerySubstring: function(values){
-
- //Start a query string for this field and get the values
- var valuesQueryString = "",
+ /**
+ * Constructs a query substring for each of the values set on this model
+ *
+ * @example
+ * values: ["walker", "jones"]
+ * Returns: "(walker%20OR%20jones)"
+ *
+ * @param {string[]} [values] - The values to use in this query substring.
+ * If not provided, the values set on the model are used.
+ * @return {string} The query substring
+ */
+ getValueQuerySubstring: function (values) {
+ //Start a query string for this field and get the values
+ var valuesQueryString = "",
values = values || this.get("values");
- //If the values are not an array, convert it to an array
- if( !Array.isArray(values) ){
- values = [values];
- }
-
- //Iterate over each value set on the model
- _.each( values, function(value, i){
-
- //If the value is not a string, then convert it to a string
- if( typeof value != "string" ){
- value = value.toString();
+ //If the values are not an array, convert it to an array
+ if (!Array.isArray(values)) {
+ values = [values];
}
- //Trim off whitespace
- value = value.trim();
-
- var dateRangeRegEx = /^\[((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d*Z)|\*)( |%20)TO( |%20)((\d{4}-[01]\d-[0-3]\dT[0-2]\d(:|\\:)[0-5]\d(:|\\:)[0-5]\d\.\d*Z)|\*)\]/,
- isDateRange = dateRangeRegEx.test(value),
- isSearchPhrase = value.indexOf(" ") > -1,
- isIdFilter = this.isIdFilter(),
- //Test for ORCIDs and group subjects
- isSubject = /^(?:https?:\/\/orcid\.org\/)?(?:\w{4}-){3}\w{4}$|^(?:CN=.{1,},DC=.{1,},DC=.{1,})$/i.test(value);
+ //Iterate over each value set on the model
+ _.each(
+ values,
+ function (value, i) {
+ //If the value is not a string, then convert it to a string
+ if (typeof value != "string") {
+ value = value.toString();
+ }
+
+ //Trim off whitespace
+ value = value.trim();
+
+ var dateRangeRegEx =
+ /^\[((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d*Z)|\*)( |%20)TO( |%20)((\d{4}-[01]\d-[0-3]\dT[0-2]\d(:|\\:)[0-5]\d(:|\\:)[0-5]\d\.\d*Z)|\*)\]/,
+ isDateRange = dateRangeRegEx.test(value),
+ isSearchPhrase = value.indexOf(" ") > -1,
+ isIdFilter = this.isIdFilter(),
+ //Test for ORCIDs and group subjects
+ isSubject =
+ /^(?:https?:\/\/orcid\.org\/)?(?:\w{4}-){3}\w{4}$|^(?:CN=.{1,},DC=.{1,},DC=.{1,})$/i.test(
+ value,
+ );
+
+ // Escape special characters
+ value = this.escapeSpecialChar(value);
+
+ //URL encode the search value
+ value = encodeURIComponent(value);
+
+ // If the value is a search phrase (more than one word), is part of an ID filter,
+ // and not a date range string, wrap in quotes
+ if ((isSearchPhrase || isIdFilter || isSubject) && !isDateRange) {
+ value = '"' + value + '"';
+ }
+
+ if (this.get("matchSubstring") && !isDateRange) {
+ // Look for existing wildcard characters at the end of the value string, wrap
+ // the value string in wildcard characters if there aren't any yet.
+ if (!value.match(/^\*|\*$/)) {
+ value = "*" + value + "*";
+ }
+ }
- // Escape special characters
- value = this.escapeSpecialChar(value);
+ // Add the value to the query string
+ valuesQueryString += value;
- //URL encode the search value
- value = encodeURIComponent(value);
+ //Add the operator between values
+ if (values.length > i + 1 && valuesQueryString.length) {
+ valuesQueryString += "%20" + this.get("operator") + "%20";
+ }
+ },
+ this,
+ );
- // If the value is a search phrase (more than one word), is part of an ID filter,
- // and not a date range string, wrap in quotes
- if( (isSearchPhrase || isIdFilter || isSubject) && !isDateRange ){
- value = "\"" + value + "\"";
+ if (values.length > 1) {
+ valuesQueryString = "(" + valuesQueryString + ")";
}
- if( this.get("matchSubstring") && !isDateRange ){
- // Look for existing wildcard characters at the end of the value string, wrap
- // the value string in wildcard characters if there aren't any yet.
- if(! value.match( /^\*|\*$/ ) ){
- value = "*" + value + "*"
- }
- }
+ return valuesQueryString;
+ },
- // Add the value to the query string
- valuesQueryString += value;
+ /**
+ * Checks if any of the fields in this Filter match one of the
+ * {@link AppConfig#queryIdentifierFields}
+ * @since 2.17.0
+ */
+ isIdFilter: function () {
+ try {
+ let fields = this.get("fields");
+ let values = this.get("values");
+
+ if (!fields) {
+ return false;
+ }
+ let idFields = MetacatUI.appModel.get("queryIdentifierFields");
+ let match = _.some(idFields, (idField) => fields.includes(idField));
+
+ //Check if the values are all identifiers by checking for uuids and dois
+ if (!match && values.length) {
+ match = values.every((v) =>
+ /^urn:uuid:\w{8,}-\w{4,}-\w{4,}-\w{4,}-\w{12,}$|^.{0,}doi.{1,}10.\d{4,9}\/[-._;()\/:A-Z0-9]+$/i.test(
+ v,
+ ),
+ );
+ }
- //Add the operator between values
- if( values.length > i+1 && valuesQueryString.length ){
- valuesQueryString += "%20" + this.get("operator") + "%20";
+ return match;
+ } catch (error) {
+ console.log(
+ "Error checking if a Filter model is an ID filter. " +
+ "Assuming it is not. Error details:" +
+ error,
+ );
+ return false;
}
+ },
- }, this);
-
- if( values.length > 1 ){
- valuesQueryString = "(" + valuesQueryString + ")"
- }
-
- return valuesQueryString;
- },
-
- /**
- * Checks if any of the fields in this Filter match one of the
- * {@link AppConfig#queryIdentifierFields}
- * @since 2.17.0
- */
- isIdFilter: function(){
- try {
- let fields = this.get("fields");
- let values = this.get("values");
-
- if(!fields){
- return false
- }
- let idFields = MetacatUI.appModel.get("queryIdentifierFields");
- let match = _.some(idFields, idField => fields.includes(idField));
-
- //Check if the values are all identifiers by checking for uuids and dois
- if(!match && values.length){
- match = values.every(v => /^urn:uuid:\w{8,}-\w{4,}-\w{4,}-\w{4,}-\w{12,}$|^.{0,}doi.{1,}10.\d{4,9}\/[-._;()\/:A-Z0-9]+$/i.test(v));
- }
-
- return match;
-
- } catch (error) {
- console.log("Error checking if a Filter model is an ID filter. " +
- "Assuming it is not. Error details:" + error );
- return false
- }
- },
-
- /**
- * Resets the values attribute on this filter
- */
- resetValue: function(){
- this.set("values", this.defaults().values);
- },
-
- /**
- * Checks if this Filter has values different than the default values.
- * @return {boolean} - Returns true if this Filter has values set on it, otherwise will return false
- */
- hasChangedValues: function(){
-
- return (this.get("values").length > 0);
+ /**
+ * Resets the values attribute on this filter
+ */
+ resetValue: function () {
+ this.set("values", this.defaults().values);
+ },
- },
+ /**
+ * Checks if this Filter has values different than the default values.
+ * @return {boolean} - Returns true if this Filter has values set on it, otherwise will return false
+ */
+ hasChangedValues: function () {
+ return this.get("values").length > 0;
+ },
- /**
- * isEmpty - Checks whether this Filter has any values or fields set
- *
- * @return {boolean} returns true if the Filter's values and fields are empty
- */
- isEmpty: function(){
- try {
- var fields = this.get("fields"),
- values = this.get("values"),
- noFields = !fields || fields.length == 0,
- fieldsEmpty = _.every(fields, function(item) { return item == "" }),
- noValues = !values || values.length == 0,
- valuesEmpty = _.every(values, function(item) { return item == "" });
-
- var noMinNoMax = _.every(
- [this.get("min"), this.get("max")],
- function(num) {
- return (typeof num === "undefined") || (!num && num !== 0);
+ /**
+ * isEmpty - Checks whether this Filter has any values or fields set
+ *
+ * @return {boolean} returns true if the Filter's values and fields are empty
+ */
+ isEmpty: function () {
+ try {
+ var fields = this.get("fields"),
+ values = this.get("values"),
+ noFields = !fields || fields.length == 0,
+ fieldsEmpty = _.every(fields, function (item) {
+ return item == "";
+ }),
+ noValues = !values || values.length == 0,
+ valuesEmpty = _.every(values, function (item) {
+ return item == "";
+ });
+
+ var noMinNoMax = _.every(
+ [this.get("min"), this.get("max")],
+ function (num) {
+ return typeof num === "undefined" || (!num && num !== 0);
+ },
+ );
+
+ // Values aren't required for UI filter types. Labels, icons, and descriptions are
+ // available.
+ if (this.get("isUIFilterType")) {
+ noUIVals = _.every(
+ ["label", "icon", "description"],
+ function (attrName) {
+ var setValue = this.get(attrName);
+ var defaultValue = this.defaults()[attrName];
+ return !setValue || setValue === defaultValue;
+ },
+ this,
+ );
+ return noUIVals && noFields && fieldsEmpty && noMinNoMax;
}
- );
- // Values aren't required for UI filter types. Labels, icons, and descriptions are
- // available.
- if(this.get("isUIFilterType")){
- noUIVals = _.every(["label", "icon", "description"], function(attrName){
- var setValue = this.get(attrName);
- var defaultValue = this.defaults()[attrName];
- return !setValue || (setValue === defaultValue)
- }, this)
- return noUIVals && noFields && fieldsEmpty && noMinNoMax
+ // For regular search filters, just a field and some sort of search term/value is
+ // required
+ return (
+ noFields && fieldsEmpty && noValues && valuesEmpty && noMinNoMax
+ );
+ } catch (e) {
+ console.log(
+ "Failed to check if a Filter is empty, error message: " + e,
+ );
}
+ },
- // For regular search filters, just a field and some sort of search term/value is
- // required
- return noFields && fieldsEmpty && noValues && valuesEmpty && noMinNoMax
+ /**
+ * Escapes Solr query reserved characters so that search terms can include
+ * those characters without throwing an error.
+ *
+ * @param {string} term - The search term or phrase to escape
+ * @return {string} - The search term or phrase, after special characters are escaped
+ */
+ escapeSpecialChar: function (term) {
+ if (!term || typeof term != "string") {
+ return "";
+ }
- } catch (e) {
- console.log("Failed to check if a Filter is empty, error message: " + e);
- }
- },
+ // Removes all the ampersands since Metacat cannot handle escaped ampersands for some reason
+ // See https://github.com/NCEAS/metacat/issues/1576
+ term = term.replaceAll("&", "");
- /**
- * Escapes Solr query reserved characters so that search terms can include
- * those characters without throwing an error.
- *
- * @param {string} term - The search term or phrase to escape
- * @return {string} - The search term or phrase, after special characters are escaped
- */
- escapeSpecialChar: function(term) {
+ return term.replace(
+ /\+|-|&|\||!|\(|\)|\{|\}|\[|\]|\^|\\|\"|~|\?|:|\//g,
+ "\\$&",
+ );
+ },
- if(!term || typeof term != "string"){
- return "";
- }
+ /**
+ * Updates XML DOM with the new values from the model
+ *
+ * @param {object} [options] A literal object with options for this serialization
+ * @return {Element} A new XML element with the updated values
+ */
+ updateDOM: function (options) {
+ try {
+ if (typeof options == "undefined") {
+ var options = {};
+ }
- // Removes all the ampersands since Metacat cannot handle escaped ampersands for some reason
- // See https://github.com/NCEAS/metacat/issues/1576
- term = term.replaceAll("&", "");
+ var objectDOM = this.get("objectDOM"),
+ filterOptionsNode;
- return term.replace(/\+|-|&|\||!|\(|\)|\{|\}|\[|\]|\^|\\|\"|~|\?|:|\//g, "\\$&");
+ if (
+ typeof objectDOM == "undefined" ||
+ !objectDOM ||
+ !$(objectDOM).length
+ ) {
+ // Node name differs for different filters, all of which use this function
+ var nodeName = this.get("nodeName") || "filter";
+ // Create an XML filter element from scratch
+ var objectDOM = new DOMParser().parseFromString(
+ "<" + nodeName + ">" + nodeName + ">",
+ "text/xml",
+ );
+ var $objectDOM = $(objectDOM).find(nodeName);
+ } else {
+ objectDOM = objectDOM.cloneNode(true);
+ var $objectDOM = $(objectDOM);
- },
+ //Detach the filterOptions so they are saved
+ filterOptionsNode = $objectDOM.children("filterOptions");
+ filterOptionsNode.detach();
- /**
- * Updates XML DOM with the new values from the model
- *
- * @param {object} [options] A literal object with options for this serialization
- * @return {Element} A new XML element with the updated values
- */
- updateDOM: function(options){
+ //Empty the DOM
+ $objectDOM.empty();
+ }
- try{
+ var xmlDocument = $objectDOM[0].ownerDocument;
+
+ // Get new values. Must store in an array because the order that we add each
+ // element to the DOM matters
+ var filterData = [
+ {
+ nodeName: "label",
+ value: this.get("label"),
+ },
+ {
+ nodeName: "field",
+ value: this.get("fields"),
+ },
+ {
+ nodeName: "operator",
+ value: this.get("operator"),
+ },
+ {
+ nodeName: "exclude",
+ value: this.get("exclude"),
+ },
+ {
+ nodeName: "fieldsOperator",
+ value: this.get("fieldsOperator"),
+ },
+ {
+ nodeName: "matchSubstring",
+ value: this.get("matchSubstring"),
+ },
+ {
+ nodeName: "value",
+ value: this.get("values"),
+ },
+ ];
+
+ filterData.forEach(function (element) {
+ var values = element.value;
+ var nodeName = element.nodeName;
+
+ // Serialize the nodes with multiple occurrences
+ if (Array.isArray(values)) {
+ _.each(
+ values,
+ function (value) {
+ // Don't serialize empty, null, or undefined values
+ if (value || value === false || value === 0) {
+ var nodeSerialized = xmlDocument.createElement(nodeName);
+ $(nodeSerialized).text(value);
+ $objectDOM.append(nodeSerialized);
+ }
+ },
+ this,
+ );
+ }
+ // Serialize the single occurrence nodes. Don't serialize falsey or default values
+ else if (
+ (values || values === false) &&
+ values != this.defaults()[nodeName]
+ ) {
+ var nodeSerialized = xmlDocument.createElement(nodeName);
+ $(nodeSerialized).text(values);
+ $objectDOM.append(nodeSerialized);
+ }
+ }, this);
+
+ // If this is a UIFilterType that won't be serialized into a Collection definition,
+ // then add extra XML nodes
+ if (this.get("isUIFilterType")) {
+ //Update the filterOptions XML DOM
+ filterOptionsNode = this.updateFilterOptionsDOM(filterOptionsNode);
+
+ //Add the filterOptions to the filter DOM
+ if (
+ typeof filterOptionsNode != "undefined" &&
+ $(filterOptionsNode).children().length
+ ) {
+ $objectDOM.append(filterOptionsNode);
+ }
+ }
- if(typeof options == "undefined"){
- var options = {};
+ return $objectDOM[0];
+ } catch (e) {
+ console.error("Unable to serialize a Filter.", e);
+ return this.get("objectDOM") || "";
}
+ },
- var objectDOM = this.get("objectDOM"),
- filterOptionsNode;
-
- if( typeof objectDOM == "undefined" || !objectDOM || !$(objectDOM).length ){
- // Node name differs for different filters, all of which use this function
- var nodeName = this.get("nodeName") || "filter";
- // Create an XML filter element from scratch
- var objectDOM = new DOMParser().parseFromString(
- "<" + nodeName + ">" + nodeName + ">",
- "text/xml"
+ /**
+ * Serializes the filter options into an XML DOM and returns it
+ * @param {Element} [filterOptionsNode] - The XML filterOptions node to update
+ * @return {Element} - The updated DOM
+ */
+ updateFilterOptionsDOM: function (filterOptionsNode) {
+ try {
+ if (
+ typeof filterOptionsNode == "undefined" ||
+ !filterOptionsNode.length
+ ) {
+ var filterOptionsNode = new DOMParser().parseFromString(
+ " ",
+ "text/xml",
);
- var $objectDOM = $(objectDOM).find(nodeName);
- }
- else{
- objectDOM = objectDOM.cloneNode(true);
- var $objectDOM = $(objectDOM);
-
- //Detach the filterOptions so they are saved
- filterOptionsNode = $objectDOM.children("filterOptions");
- filterOptionsNode.detach();
-
- //Empty the DOM
- $objectDOM.empty();
- }
+ var filterOptionsNode =
+ $(filterOptionsNode).find("filterOptions")[0];
+ }
+ //Convert the XML node into a jQuery object
+ var $filterOptionsNode = $(filterOptionsNode);
- var xmlDocument = $objectDOM[0].ownerDocument;
+ //Get the first option element
+ var firstOptionNode = $filterOptionsNode.children("option").first();
- // Get new values. Must store in an array because the order that we add each
- // element to the DOM matters
- var filterData = [
- {
- nodeName: "label",
- value: this.get("label"),
- },
- {
- nodeName: "field",
- value: this.get("fields"),
- },
- {
- nodeName: "operator",
- value: this.get("operator"),
- },
- {
- nodeName: "exclude",
- value: this.get("exclude"),
- },
- {
- nodeName: "fieldsOperator",
- value: this.get("fieldsOperator"),
- },
- {
- nodeName: "matchSubstring",
- value: this.get("matchSubstring"),
- },
- {
- nodeName: "value",
- value: this.get("values"),
- },
- ]
-
- filterData.forEach(function(element){
- var values = element.value;
- var nodeName = element.nodeName;
-
- // Serialize the nodes with multiple occurrences
- if( Array.isArray(values) ){
- _.each(values, function(value){
- // Don't serialize empty, null, or undefined values
- if( value || value === false || value === 0 ){
- var nodeSerialized = xmlDocument.createElement(nodeName);
- $(nodeSerialized).text(value);
- $objectDOM.append(nodeSerialized);
- }
- }, this);
+ var xmlDocument;
+ if (filterOptionsNode.length && filterOptionsNode[0]) {
+ xmlDocument = filterOptionsNode[0].ownerDocument;
}
- // Serialize the single occurrence nodes. Don't serialize falsey or default values
- else if((values || values === false) && values != this.defaults()[nodeName]) {
- var nodeSerialized = xmlDocument.createElement(nodeName);
- $(nodeSerialized).text(values);
- $objectDOM.append(nodeSerialized);
+ if (!xmlDocument) {
+ xmlDocument = filterOptionsNode.ownerDocument;
}
-
- }, this);
-
- // If this is a UIFilterType that won't be serialized into a Collection definition,
- // then add extra XML nodes
- if( this.get("isUIFilterType") ){
-
- //Update the filterOptions XML DOM
- filterOptionsNode = this.updateFilterOptionsDOM(filterOptionsNode);
-
- //Add the filterOptions to the filter DOM
- if( typeof filterOptionsNode != "undefined" && $(filterOptionsNode).children().length ){
- $objectDOM.append(filterOptionsNode);
+ if (!xmlDocument) {
+ xmlDocument = filterOptionsNode;
}
+ // Update the text value of UI nodes. The following values are for
+ // UIFilterOptionsType
+ ["placeholder", "icon", "description"].forEach(function (nodeName) {
+ //Remove the existing node, if it exists
+ $filterOptionsNode.children(nodeName).remove();
+
+ // If there is a value set on the model for this attribute, then create an XML
+ // node for this attribute and set the text value
+ var value = this.get(nodeName);
+ if (value) {
+ var newNode = $(xmlDocument.createElement(nodeName)).text(value);
+
+ if (firstOptionNode.length) firstOptionNode.before(newNode);
+ else $filterOptionsNode.append(newNode);
+ }
+ }, this);
+
+ //If no options were serialized, then return an empty string
+ if (!$filterOptionsNode.children().length) {
+ return "";
+ } else {
+ return filterOptionsNode;
+ }
+ } catch (e) {
+ console.log(
+ "Error updating the FilterOptions DOM in a Filter model, " +
+ "error details: ",
+ e,
+ );
+ return "";
}
+ },
- return $objectDOM[0];
- }
- catch(e){
- console.error("Unable to serialize a Filter.", e);
- return this.get("objectDOM") || "";
- }
- },
-
- /**
- * Serializes the filter options into an XML DOM and returns it
- * @param {Element} [filterOptionsNode] - The XML filterOptions node to update
- * @return {Element} - The updated DOM
- */
- updateFilterOptionsDOM: function(filterOptionsNode){
-
- try{
-
- if (typeof filterOptionsNode == "undefined" || !filterOptionsNode.length) {
- var filterOptionsNode = new DOMParser().parseFromString(" ", "text/xml");
- var filterOptionsNode = $(filterOptionsNode).find("filterOptions")[0];
+ /**
+ * Returns true if the given value or value set on this filter is a date range query
+ * @param {string} value - The string to test
+ * @return {boolean}
+ */
+ isDateQuery: function (value) {
+ if (typeof value == "undefined" && this.get("values").length == 1) {
+ var value = this.get("values")[0];
}
- //Convert the XML node into a jQuery object
- var $filterOptionsNode = $(filterOptionsNode);
-
- //Get the first option element
- var firstOptionNode = $filterOptionsNode.children("option").first();
- var xmlDocument;
- if (filterOptionsNode.length && filterOptionsNode[0]) {
- xmlDocument = filterOptionsNode[0].ownerDocument;
- }
- if (!xmlDocument) {
- xmlDocument = filterOptionsNode.ownerDocument;
- }
- if (!xmlDocument) {
- xmlDocument = filterOptionsNode;
+ if (value) {
+ return /[\d|\-|:|T]*Z TO [\d|\-|:|T]*Z/.test(value);
+ } else {
+ return false;
}
+ },
- // Update the text value of UI nodes. The following values are for
- // UIFilterOptionsType
- ["placeholder", "icon", "description"].forEach(function(nodeName){
-
- //Remove the existing node, if it exists
- $filterOptionsNode.children(nodeName).remove();
-
- // If there is a value set on the model for this attribute, then create an XML
- // node for this attribute and set the text value
- var value = this.get(nodeName);
- if( value ){
- var newNode = $(xmlDocument.createElement(nodeName)).text(value);
-
- if( firstOptionNode.length )
- firstOptionNode.before(newNode);
- else
- $filterOptionsNode.append(newNode);
+ /**
+ * Check whether a set of query fields contain only fields that specify latitude and/or
+ * longitude
+ * @param {string[]} [fields] A list of fields to check for coordinate fields. If not
+ * provided, the fields set on the model will be used.
+ * @returns {Boolean} Returns true if every field is a field that specifies latitude or
+ * longitude
+ * @since 2.21.0
+ */
+ isCoordinateQuery: function (fields) {
+ try {
+ if (!fields) {
+ fields = this.get("fields");
}
- }, this);
-
- //If no options were serialized, then return an empty string
- if( !$filterOptionsNode.children().length ){
- return "";
+ const latitudeFields = MetacatUI.appModel.get("queryLatitudeFields");
+ const longitudeFields = MetacatUI.appModel.get(
+ "queryLongitudeFields",
+ );
+ const coordinateFields = latitudeFields.concat(longitudeFields);
+ return _.every(fields, function (field) {
+ return _.contains(coordinateFields, field);
+ });
+ } catch (e) {
+ console.log(
+ "Error checking if filter is a coordinate filter. Returning false. ",
+ e,
+ );
+ return false;
}
- else{
- return filterOptionsNode;
- }
- }
- catch(e){
- console.log("Error updating the FilterOptions DOM in a Filter model, "+
- "error details: ", e);
- return "";
- }
-
- },
-
- /**
- * Returns true if the given value or value set on this filter is a date range query
- * @param {string} value - The string to test
- * @return {boolean}
- */
- isDateQuery: function(value){
-
- if( typeof value == "undefined" && this.get("values").length == 1 ){
- var value = this.get("values")[0];
- }
-
- if( value ){
- return /[\d|\-|:|T]*Z TO [\d|\-|:|T]*Z/.test(value);
- }
- else{
- return false;
- }
},
-
- /**
- * Check whether a set of query fields contain only fields that specify latitude and/or
- * longitude
- * @param {string[]} [fields] A list of fields to check for coordinate fields. If not
- * provided, the fields set on the model will be used.
- * @returns {Boolean} Returns true if every field is a field that specifies latitude or
- * longitude
- * @since 2.21.0
- */
- isCoordinateQuery: function (fields) {
- try {
- if (!fields) {
- fields = this.get('fields');
- }
- const latitudeFields = MetacatUI.appModel.get('queryLatitudeFields');
- const longitudeFields = MetacatUI.appModel.get('queryLongitudeFields');
- const coordinateFields = latitudeFields.concat(longitudeFields);
- return _.every(fields, function (field) {
- return _.contains(coordinateFields, field);
- })
- }
- catch (e) {
- console.log('Error checking if filter is a coordinate filter. Returning false. ', e);
- return false;
- }
- },
-
- /**
- * Check whether a set of query fields contain only fields that specify latitude
- * @param {string[]} [fields] A list of fields to check for coordinate fields. If not
- * provided, the fields set on the model will be used.
- * @returns {Boolean} Returns true if every field is a field that specifies latitude
- * @since 2.21.0
- */
- isLatitudeQuery: function (fields) {
- try {
- if (!fields) {
- fields = this.get('fields');
- }
- const latitudeFields = MetacatUI.appModel.get('queryLatitudeFields');
- return _.every(fields, function (field) {
- return _.contains(latitudeFields, field);
- })
- }
- catch (e) {
- console.log('Error checking if filter is a latitude filter. Returning false. ', e);
- return false;
- }
- },
-
- /**
- * Check whether a set of query fields contain only fields that specify longitude
- * @param {string[]} [fields] A list of fields to check for longitude fields. If not
- * provided, the fields set on the model will be used.
- * @returns {Boolean} Returns true if every field is a field that specifies longitude
- * @since 2.21.0
- */
- isLongitudeQuery: function (fields) {
- try {
- if (!fields) {
- fields = this.get('fields');
- }
- const longitudeFields = MetacatUI.appModel.get('queryLongitudeFields');
- return _.every(fields, function (field) {
- return _.contains(longitudeFields, field);
- })
- }
- catch (error) {
- console.log('Error checking if filter is a longitude filter. Returning false. ', e);
- return false;
- }
- },
-
- /**
- * Checks if the values set on this model are valid.
- * Some of the attributes are changed during this process if they are found to be invalid,
- * since there aren't any easy ways for users to fix these issues themselves in the UI.
- * @return {object} - Returns a literal object with the invalid attributes and their corresponding error message
- */
- validate: function(){
-
- try{
-
- var errors = {};
- // UI filter types have
- var isUIFilterType = this.get("isUIFilterType");
-
- //---Validate fields----
- var fields = this.get("fields");
- //All fields should be strings
- var nonStrings = _.filter(fields, function(field){
- return (typeof field != "string" || !field.trim().length);
- });
- if( nonStrings.length ){
- //Remove the nonstrings from the model, rather than returning an error
- this.set("fields", _.without(fields, nonStrings));
+ /**
+ * Check whether a set of query fields contain only fields that specify latitude
+ * @param {string[]} [fields] A list of fields to check for coordinate fields. If not
+ * provided, the fields set on the model will be used.
+ * @returns {Boolean} Returns true if every field is a field that specifies latitude
+ * @since 2.21.0
+ */
+ isLatitudeQuery: function (fields) {
+ try {
+ if (!fields) {
+ fields = this.get("fields");
+ }
+ const latitudeFields = MetacatUI.appModel.get("queryLatitudeFields");
+ return _.every(fields, function (field) {
+ return _.contains(latitudeFields, field);
+ });
+ } catch (e) {
+ console.log(
+ "Error checking if filter is a latitude filter. Returning false. ",
+ e,
+ );
+ return false;
}
- //If there are no fields, set an error message
- if( !this.get("fields").length ){
- errors.fields = "Filters should have at least one search field.";
+ },
+
+ /**
+ * Check whether a set of query fields contain only fields that specify longitude
+ * @param {string[]} [fields] A list of fields to check for longitude fields. If not
+ * provided, the fields set on the model will be used.
+ * @returns {Boolean} Returns true if every field is a field that specifies longitude
+ * @since 2.21.0
+ */
+ isLongitudeQuery: function (fields) {
+ try {
+ if (!fields) {
+ fields = this.get("fields");
+ }
+ const longitudeFields = MetacatUI.appModel.get(
+ "queryLongitudeFields",
+ );
+ return _.every(fields, function (field) {
+ return _.contains(longitudeFields, field);
+ });
+ } catch (error) {
+ console.log(
+ "Error checking if filter is a longitude filter. Returning false. ",
+ e,
+ );
+ return false;
}
+ },
- //---Validate values----
- var values = this.get("values");
- // All values should be strings, booleans, numbers, or dates
- var invalidValues = _.filter(values, function(value){
- //Empty strings are invalid
- if( typeof value == "string" && !value.trim().length ){
- return true;
+ /**
+ * Checks if the values set on this model are valid.
+ * Some of the attributes are changed during this process if they are found to be invalid,
+ * since there aren't any easy ways for users to fix these issues themselves in the UI.
+ * @return {object} - Returns a literal object with the invalid attributes and their corresponding error message
+ */
+ validate: function () {
+ try {
+ var errors = {};
+ // UI filter types have
+ var isUIFilterType = this.get("isUIFilterType");
+
+ //---Validate fields----
+ var fields = this.get("fields");
+ //All fields should be strings
+ var nonStrings = _.filter(fields, function (field) {
+ return typeof field != "string" || !field.trim().length;
+ });
+
+ if (nonStrings.length) {
+ //Remove the nonstrings from the model, rather than returning an error
+ this.set("fields", _.without(fields, nonStrings));
}
- //Non-empty strings, booleans, numbers, or dates are valid
- else if( typeof value == "string" || typeof value == "boolean" ||
- typeof value == "number" || Date.prototype.isPrototypeOf(value) ){
- return false;
+ //If there are no fields, set an error message
+ if (!this.get("fields").length) {
+ errors.fields = "Filters should have at least one search field.";
}
- });
- if( invalidValues.length ){
- //Remove the invalid values from the model, rather than returning an error
- this.set("values", _.without(values, invalidValues));
- }
+ //---Validate values----
+ var values = this.get("values");
+ // All values should be strings, booleans, numbers, or dates
+ var invalidValues = _.filter(values, function (value) {
+ //Empty strings are invalid
+ if (typeof value == "string" && !value.trim().length) {
+ return true;
+ }
+ //Non-empty strings, booleans, numbers, or dates are valid
+ else if (
+ typeof value == "string" ||
+ typeof value == "boolean" ||
+ typeof value == "number" ||
+ Date.prototype.isPrototypeOf(value)
+ ) {
+ return false;
+ }
+ });
+
+ if (invalidValues.length) {
+ //Remove the invalid values from the model, rather than returning an error
+ this.set("values", _.without(values, invalidValues));
+ }
- //If there are no values, and this isn't a custom search filter, set an error
- //message.
- if ( !isUIFilterType && !this.get("values").length ){
- errors.values = "Filters should include at least one search term.";
- }
+ //If there are no values, and this isn't a custom search filter, set an error
+ //message.
+ if (!isUIFilterType && !this.get("values").length) {
+ errors.values = "Filters should include at least one search term.";
+ }
- //---Validate operators ----
- //The operator must be either AND or OR
- ["operator", "fieldsOperator"].forEach(function(op){
- if( !["AND", "OR"].includes(this.get(op)) ){
+ //---Validate operators ----
+ //The operator must be either AND or OR
+ ["operator", "fieldsOperator"].forEach(function (op) {
+ if (!["AND", "OR"].includes(this.get(op))) {
+ //Reset the value to the default rather than return an error
+ this.set(op, this.defaults()[op]);
+ }
+ }, this);
+
+ //---Validate exclude and matchSubstring----
+ //Exclude should always be a boolean
+ if (typeof this.get("exclude") != "boolean") {
//Reset the value to the default rather than return an error
- this.set(op, this.defaults()[op]);
+ this.set("exclude", this.defaults().exclude);
}
- }, this);
-
-
- //---Validate exclude and matchSubstring----
- //Exclude should always be a boolean
- if( typeof this.get("exclude") != "boolean" ){
- //Reset the value to the default rather than return an error
- this.set("exclude", this.defaults().exclude);
- }
- //matchSubstring should always be a boolean
- if( typeof this.get("matchSubstring") != "boolean" ){
- //Reset the value to the default rather than return an error
- this.set("matchSubstring", this.defaults().matchSubstring);
- }
-
- //---Validate label, placeholder, icon, and description----
- var textAttributes = ["label", "placeholder", "icon", "description"];
- //These fields should be strings
- _.each(textAttributes, function(attr){
- if( typeof this.get(attr) != "string" ){
+ //matchSubstring should always be a boolean
+ if (typeof this.get("matchSubstring") != "boolean") {
//Reset the value to the default rather than return an error
- this.set(attr, this.defaults()[attr]);
+ this.set("matchSubstring", this.defaults().matchSubstring);
}
- }, this);
-
- if( Object.keys(errors).length )
- return errors;
- else{
- return;
- }
- }
- catch(e){
- console.error(e);
- }
- }
+ //---Validate label, placeholder, icon, and description----
+ var textAttributes = ["label", "placeholder", "icon", "description"];
+ //These fields should be strings
+ _.each(
+ textAttributes,
+ function (attr) {
+ if (typeof this.get(attr) != "string") {
+ //Reset the value to the default rather than return an error
+ this.set(attr, this.defaults()[attr]);
+ }
+ },
+ this,
+ );
- });
+ if (Object.keys(errors).length) return errors;
+ else {
+ return;
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ },
+ },
+ );
return FilterModel;
-
});
diff --git a/src/js/models/filters/FilterGroup.js b/src/js/models/filters/FilterGroup.js
index 6819c4317..bd283ec5e 100644
--- a/src/js/models/filters/FilterGroup.js
+++ b/src/js/models/filters/FilterGroup.js
@@ -1,610 +1,634 @@
/* global define */
-define(["jquery", "underscore", "backbone", "collections/Filters", "models/filters/Filter" ],
- function ($, _, Backbone, Filters, Filter) {
-
- /**
- * @class FilterGroup
- * @classdesc A group of multiple Filters, and optionally nested Filter Groups, which
- * may be combined to create a complex query. A FilterGroup may be a Collection
- * FilterGroupType or a Portal UIFilterGroupType.
- * @classcategory Models/Filters
- * @extends Backbone.Model
- * @constructs
- */
- var FilterGroup = Backbone.Model.extend(
- /** @lends FilterGroup.prototype */{
-
- /**
- * The name of this Model
- * @type {string}
- * @readonly
- */
- type: "FilterGroup",
-
- /**
- * Default attributes for FilterGroup models
- * @type {Object}
- * @property {string} label - For UIFilterGroupType filter groups, a
- * human-readable short label for this Filter Group
- * @property {string} description - For UIFilterGroupType filter groups, a
- * description of the Filter Group's function
- * @property {string} icon - For UIFilterGroupType filter groups, a term that
- * identifies a single icon in a supported icon library.
- * @property {Filters} filters - A collection of Filter models that represent a
- * full or partial query
- * @property {XMLElement} objectDOM - FilterGroup XML
- * @property {string} operator - The operator to use between filters (including
- * filter groups) set on this model. Must be set to "AND" or "OR".
- * @property {boolean} exclude - If true, search index docs matching the filters
- * within this group will be excluded from the search results
- * @property {boolean} isUIFilterType - Set to true if this group is
- * UIFilterGroupType (aka custom Portal search filter). Otherwise, it's assumed
- * that this model is FilterGroupType (e.g. a Collection FilterGroupType)
- * @property {string} nodeName - the XML node name to use when serializing this
- * model. For example, may be "filterGroup" or "definition".
- * @property {boolean} isInvisible - If true, this filter will be added to the
- * query but will act in the "background", like a default filter. It will not
- * appear in the Query Builder or other UIs. If this is invisible, then the
- * "isInvisible" property on sub-filters will be ignored.
- * @property {boolean} mustMatchIds - If the search results must always match one
- * of the ids in the id filters, then the id filters will be added to the query
- * with an AND operator.
- */
- defaults: function () {
- return {
- label: null,
- description: null,
- icon: null,
- filters: null,
- objectDOM: null,
- operator: "AND",
- exclude: false,
- isUIFilterType: false,
- nodeName: "filterGroup",
- isInvisible: false,
- mustMatchIds: false
- // TODO: support options for UIFilterGroupType 1.1.0
- // options: [],
- }
- },
-
- /**
- * This function is executed whenever a new FilterGroup model is created. Model
- * attributes are set either by parsing attributes.objectDOM or ny extracting the
- * properties from attributes (e.g. attributes.nodeName, attributes.operator, etc)
- */
- initialize: function (attributes) {
-
- if(!attributes){
- attributes = {}
- }
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "collections/Filters",
+ "models/filters/Filter",
+], function ($, _, Backbone, Filters, Filter) {
+ /**
+ * @class FilterGroup
+ * @classdesc A group of multiple Filters, and optionally nested Filter Groups, which
+ * may be combined to create a complex query. A FilterGroup may be a Collection
+ * FilterGroupType or a Portal UIFilterGroupType.
+ * @classcategory Models/Filters
+ * @extends Backbone.Model
+ * @constructs
+ */
+ var FilterGroup = Backbone.Model.extend(
+ /** @lends FilterGroup.prototype */ {
+ /**
+ * The name of this Model
+ * @type {string}
+ * @readonly
+ */
+ type: "FilterGroup",
+
+ /**
+ * Default attributes for FilterGroup models
+ * @type {Object}
+ * @property {string} label - For UIFilterGroupType filter groups, a
+ * human-readable short label for this Filter Group
+ * @property {string} description - For UIFilterGroupType filter groups, a
+ * description of the Filter Group's function
+ * @property {string} icon - For UIFilterGroupType filter groups, a term that
+ * identifies a single icon in a supported icon library.
+ * @property {Filters} filters - A collection of Filter models that represent a
+ * full or partial query
+ * @property {XMLElement} objectDOM - FilterGroup XML
+ * @property {string} operator - The operator to use between filters (including
+ * filter groups) set on this model. Must be set to "AND" or "OR".
+ * @property {boolean} exclude - If true, search index docs matching the filters
+ * within this group will be excluded from the search results
+ * @property {boolean} isUIFilterType - Set to true if this group is
+ * UIFilterGroupType (aka custom Portal search filter). Otherwise, it's assumed
+ * that this model is FilterGroupType (e.g. a Collection FilterGroupType)
+ * @property {string} nodeName - the XML node name to use when serializing this
+ * model. For example, may be "filterGroup" or "definition".
+ * @property {boolean} isInvisible - If true, this filter will be added to the
+ * query but will act in the "background", like a default filter. It will not
+ * appear in the Query Builder or other UIs. If this is invisible, then the
+ * "isInvisible" property on sub-filters will be ignored.
+ * @property {boolean} mustMatchIds - If the search results must always match one
+ * of the ids in the id filters, then the id filters will be added to the query
+ * with an AND operator.
+ */
+ defaults: function () {
+ return {
+ label: null,
+ description: null,
+ icon: null,
+ filters: null,
+ objectDOM: null,
+ operator: "AND",
+ exclude: false,
+ isUIFilterType: false,
+ nodeName: "filterGroup",
+ isInvisible: false,
+ mustMatchIds: false,
+ // TODO: support options for UIFilterGroupType 1.1.0
+ // options: [],
+ };
+ },
+
+ /**
+ * This function is executed whenever a new FilterGroup model is created. Model
+ * attributes are set either by parsing attributes.objectDOM or ny extracting the
+ * properties from attributes (e.g. attributes.nodeName, attributes.operator, etc)
+ */
+ initialize: function (attributes) {
+ if (!attributes) {
+ attributes = {};
+ }
- if(attributes.isUIFilterType){
- this.set("isUIFilterType", true);
- }
+ if (attributes.isUIFilterType) {
+ this.set("isUIFilterType", true);
+ }
- // When a Filter model within this Filter group changes, or when the Filters
- // collection is updated, trigger a change event in this filterGroup model.
- // Updates and Changes in the Filters collection won't trigger an event from
- // this model otherwise. This helps when other models, collections, views are
- // listening to this filterGroup, e.g. when the collections model updates the
- // searchModel whenever the definition changes.
- this.off("change:filters");
- this.on("change:filters", function(){
+ // When a Filter model within this Filter group changes, or when the Filters
+ // collection is updated, trigger a change event in this filterGroup model.
+ // Updates and Changes in the Filters collection won't trigger an event from
+ // this model otherwise. This helps when other models, collections, views are
+ // listening to this filterGroup, e.g. when the collections model updates the
+ // searchModel whenever the definition changes.
+ this.off("change:filters");
+ this.on(
+ "change:filters",
+ function () {
this.stopListening(this.get("filters"), "update change");
this.listenTo(
this.get("filters"),
"update change",
- function(model, record){
- this.trigger("update", model, record)
- }
+ function (model, record) {
+ this.trigger("update", model, record);
+ },
);
- }, this);
-
- var newFiltersOptions = {};
- var catalogSearch = false;
-
- if(attributes.catalogSearch){
- this.set("catalogSearch", true)
- }
+ },
+ this,
+ );
- // Set the attributes on this model by parsing XML if some was provided,
- // or by using any attributes provided to this model
- if (attributes.objectDOM) {
- var groupAttrs = this.parse(attributes.objectDOM, catalogSearch);
- this.set(groupAttrs);
- } else{
- ["label", "description", "icon", "operator",
- "exclude", "nodeName", "isInvisible"].forEach(function(modelAttribute){
- if(attributes[modelAttribute] || attributes[modelAttribute] === false){
- this.set(modelAttribute, attributes[modelAttribute])
- }
- }, this);
- }
+ var newFiltersOptions = {};
+ var catalogSearch = false;
- if (attributes.filters) {
- var filtersCollection = new Filters(null, newFiltersOptions);
- filtersCollection.add(attributes.filters);
- this.set("filters", filtersCollection);
- }
+ if (attributes.catalogSearch) {
+ this.set("catalogSearch", true);
+ }
- // Start a new Filters collection if no filters were provided
- if(!this.get("filters")){
- this.set("filters", new Filters(null, newFiltersOptions))
- }
+ // Set the attributes on this model by parsing XML if some was provided,
+ // or by using any attributes provided to this model
+ if (attributes.objectDOM) {
+ var groupAttrs = this.parse(attributes.objectDOM, catalogSearch);
+ this.set(groupAttrs);
+ } else {
+ [
+ "label",
+ "description",
+ "icon",
+ "operator",
+ "exclude",
+ "nodeName",
+ "isInvisible",
+ ].forEach(function (modelAttribute) {
+ if (
+ attributes[modelAttribute] ||
+ attributes[modelAttribute] === false
+ ) {
+ this.set(modelAttribute, attributes[modelAttribute]);
+ }
+ }, this);
+ }
- if (attributes.mustMatchIds) {
- this.set("mustMatchIds", true);
- this.get("filters").mustMatchIds = true;
- }
+ if (attributes.filters) {
+ var filtersCollection = new Filters(null, newFiltersOptions);
+ filtersCollection.add(attributes.filters);
+ this.set("filters", filtersCollection);
+ }
- // The operator must be AND or OR
- if( !["AND", "OR"].includes(this.get("operator")) ){
- // Set the value to the default
- this.set("operator", this.defaults()["operator"])
- }
+ // Start a new Filters collection if no filters were provided
+ if (!this.get("filters")) {
+ this.set("filters", new Filters(null, newFiltersOptions));
+ }
- },
+ if (attributes.mustMatchIds) {
+ this.set("mustMatchIds", true);
+ this.get("filters").mustMatchIds = true;
+ }
- /**
- * Overrides the default Backbone.Model.parse() function to parse the filterGroup
- * XML snippet
- *
- * @param {Element} xml - The XML Element that contains all the FilterGroup elements
- * @param {boolean} catalogSearch [false] - Set to true to append a catalog search phrase
- * to the search query created from Filters that limits the results to un-obsoleted
- * metadata.
- * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
- */
- parse: function (xml, catalogSearch = false) {
+ // The operator must be AND or OR
+ if (!["AND", "OR"].includes(this.get("operator"))) {
+ // Set the value to the default
+ this.set("operator", this.defaults()["operator"]);
+ }
+ },
+
+ /**
+ * Overrides the default Backbone.Model.parse() function to parse the filterGroup
+ * XML snippet
+ *
+ * @param {Element} xml - The XML Element that contains all the FilterGroup elements
+ * @param {boolean} catalogSearch [false] - Set to true to append a catalog search phrase
+ * to the search query created from Filters that limits the results to un-obsoleted
+ * metadata.
+ * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
+ */
+ parse: function (xml, catalogSearch = false) {
+ var modelJSON = {};
+
+ if (!xml) {
+ return modelJSON;
+ }
- var modelJSON = {}
+ // FilterGroups can be either or
+ this.set("nodeName", xml.nodeName);
- if(!xml){
- return modelJSON
+ // Parse all the text nodes. Node names and model attributes always match
+ // in this case.
+ ["label", "description", "icon", "operator"].forEach(function (
+ nodeName,
+ ) {
+ if ($(xml).find(nodeName).length) {
+ modelJSON[nodeName] = this.parseTextNode(xml, nodeName);
}
+ }, this);
- // FilterGroups can be either or
- this.set("nodeName", xml.nodeName);
+ // Parse the exclude field node (true or false)
+ if ($(xml).find("exclude").length) {
+ modelJSON.exclude =
+ this.parseTextNode(xml, "exclude") === "true" ? true : false;
+ }
- // Parse all the text nodes. Node names and model attributes always match
- // in this case.
- ["label", "description", "icon", "operator"].forEach(
- function(nodeName){
- if($(xml).find(nodeName).length){
- modelJSON[nodeName] = this.parseTextNode(xml, nodeName)
- }
- },
- this
- );
+ // Remove any nodes that aren't filters or filter groups from the XML
+ var filterNodeNames = [
+ "filter",
+ "booleanFilter",
+ "dateFilter",
+ "numericFilter",
+ "filterGroup",
+ "choiceFilter",
+ "toggleFilter",
+ ];
+ filterXML = xml.cloneNode(true);
+ $(filterXML).children().not(filterNodeNames.join(", ")).remove();
+
+ // Add the filters and nested filter groups to this filters model
+ // TODO: Add isNested property for filterGroups that are within filterGroups?
+ var filtersOptions = {
+ objectDOM: filterXML,
+ isUIFilterType: this.get("isUIFilterType"),
+ };
+
+ if (catalogSearch) {
+ filtersOptions.catalogSearch = true;
+ }
- // Parse the exclude field node (true or false)
- if( $(xml).find("exclude").length ){
- modelJSON.exclude = (this.parseTextNode(xml, "exclude") === "true") ? true : false;
- }
-
- // Remove any nodes that aren't filters or filter groups from the XML
- var filterNodeNames = [
- "filter", "booleanFilter", "dateFilter", "numericFilter", "filterGroup",
- "choiceFilter", "toggleFilter"
- ]
- filterXML = xml.cloneNode(true)
- $(filterXML)
- .children()
- .not(filterNodeNames.join(", "))
- .remove();
-
- // Add the filters and nested filter groups to this filters model
- // TODO: Add isNested property for filterGroups that are within filterGroups?
- var filtersOptions = {
- objectDOM: filterXML,
- isUIFilterType: this.get("isUIFilterType"),
+ modelJSON.filters = new Filters(null, filtersOptions);
+
+ return modelJSON;
+ },
+
+ /**
+ * Gets the text content of the XML node matching the given node name
+ *
+ * @param {Element} parentNode - The parent node to select from
+ * @param {string} nodeName - The name of the XML node to parse
+ * @param {boolean} isMultiple - If true, parses the nodes into an array
+ * @return {(string|Array)} - Returns a string or array of strings of the text content
+ */
+ parseTextNode: function (parentNode, nodeName, isMultiple) {
+ var node = $(parentNode).children(nodeName);
+
+ //If no matching nodes were found, return falsey values
+ if (!node || !node.length) {
+ //Return an empty array if the isMultiple flag is true
+ if (isMultiple) return [];
+ //Return null if the isMultiple flag is false
+ else return null;
+ }
+ //If exactly one node is found and we are only expecting one, return the text content
+ else if (node.length == 1 && !isMultiple) {
+ return node[0].textContent.trim();
+ }
+ //If more than one node is found, parse into an array
+ else {
+ return _.map(node, function (node) {
+ return node.textContent.trim() || null;
+ });
+ }
+ },
+
+ /**
+ * Builds the query string to send to the query engine. Iterates over each filter
+ * in the filter group and adds to the query string.
+ *
+ * @return {string} The query string to send to Solr
+ */
+ getQuery: function () {
+ try {
+ // Although the logic used in this function is very similar to the getQuery()
+ // function in the Filters collection, we can't just use
+ // this.get("filters").getQuery(operator), because there are some subtle
+ // differences with how queries are built using the information from
+ // filterGroups, especially when the exclude attribute is set to true.
+
+ var queryString = "";
+ if (this.isEmpty()) {
+ return queryString;
}
- if(catalogSearch){
- filtersOptions.catalogSearch = true
- }
+ // The operator to use between queries from filters/sub-filterGroups
+ var operator = this.get("operator");
+
+ // Helper function that adds URI encoded spaces to either side of a string
+ var padString = function (string) {
+ return "%20" + string + "%20";
+ };
+ // Helper function that appends a new part to a query fragment, using an
+ // operator if the initial fragment is not empty. Returns the string as-is if
+ // the newFragment is empty.
+ var addQueryFragment = function (string, newFragment, operator) {
+ if (
+ !newFragment ||
+ (newFragment && newFragment.trim().length == 0)
+ ) {
+ return string;
+ }
+ if (string && string.trim().length) {
+ string += padString(operator);
+ }
+ string += newFragment;
+ return string;
+ };
+ // Helper function that wraps a string in parentheses
+ var wrapInParentheses = function (string) {
+ if (!string || (string && string.trim().length == 0)) {
+ return string;
+ }
+ // TODO: We still want to wrap in parentheses in cases like "(a) OR (b)" and
+ // "a OR (b) or c" but not in cases like (a OR b)
- modelJSON.filters = new Filters(null, filtersOptions);
+ // var alreadyWrapped = /^\(.*\)$/.test(string);
+ // if (alreadyWrapped) {
+ // return string
+ // }
- return modelJSON;
- },
-
- /**
- * Gets the text content of the XML node matching the given node name
- *
- * @param {Element} parentNode - The parent node to select from
- * @param {string} nodeName - The name of the XML node to parse
- * @param {boolean} isMultiple - If true, parses the nodes into an array
- * @return {(string|Array)} - Returns a string or array of strings of the text content
- */
- parseTextNode: function (parentNode, nodeName, isMultiple) {
- var node = $(parentNode).children(nodeName);
-
- //If no matching nodes were found, return falsey values
- if (!node || !node.length) {
-
- //Return an empty array if the isMultiple flag is true
- if (isMultiple)
- return [];
- //Return null if the isMultiple flag is false
- else
- return null;
- }
- //If exactly one node is found and we are only expecting one, return the text content
- else if (node.length == 1 && !isMultiple) {
- return node[0].textContent.trim();
+ return "(" + string + ")";
+ };
+
+ // Get the list of filters that use id fields since these are used differently.
+ var idFilters = this.get("filters").getIdFilters();
+ // Get the remaining filters that don't contain any ID fields
+ var mainFilters = this.get("filters").getNonIdFilters();
+
+ // If the filterGroup should be excluded from the results, then don't include
+ // the isPartOf filter in the part of the query that gets excluded. The
+ // isPartOf filter is only meant to *include* additional results, never
+ // exclude any.
+ if (this.get("exclude")) {
+ var isPartOfFilter = null;
+ idFilters.forEach(function (filterModel, index) {
+ if (filterModel.get("fields")[0] == "isPartOf") {
+ idFilters.splice(index, 1);
+ isPartOfFilter = filterModel;
+ }
+ }, this);
}
- //If more than one node is found, parse into an array
- else {
-
- return _.map(node, function (node) {
- return node.textContent.trim() || null;
- });
+ // Create the grouped query for the id filters (this will have the isPartOf
+ // filter query if exclude is false, and will not have it if exclude is true)
+ var idFilterQuery = this.get("filters")
+ .getGroupQuery(idFilters, "OR")
+ .trim();
+ // Make the query fragment for all of the filters that do not contain ID fields
+ var mainQuery = this.get("filters")
+ .getGroupQuery(mainFilters, operator)
+ .trim();
+ // Make the query string that should be added to all catalog searches
+ var categoryQuery = "";
+ if (this.get("catalogSearch")) {
+ categoryQuery = this.get("filters")
+ .createCatalogSearchQuery()
+ .trim();
+ }
+ // Make the query string for the isPartOf filter when the filter group should
+ // be excluded
+ var isPartOfQuery = "";
+ if (isPartOfFilter) {
+ isPartOfQuery = isPartOfFilter.getQuery().trim();
}
- },
-
- /**
- * Builds the query string to send to the query engine. Iterates over each filter
- * in the filter group and adds to the query string.
- *
- * @return {string} The query string to send to Solr
- */
- getQuery: function(){
-
- try {
-
- // Although the logic used in this function is very similar to the getQuery()
- // function in the Filters collection, we can't just use
- // this.get("filters").getQuery(operator), because there are some subtle
- // differences with how queries are built using the information from
- // filterGroups, especially when the exclude attribute is set to true.
-
- var queryString = ""
- if(this.isEmpty()){
- return queryString
- }
- // The operator to use between queries from filters/sub-filterGroups
- var operator = this.get("operator")
-
- // Helper function that adds URI encoded spaces to either side of a string
- var padString = function(string){ return "%20" + string + "%20" };
- // Helper function that appends a new part to a query fragment, using an
- // operator if the initial fragment is not empty. Returns the string as-is if
- // the newFragment is empty.
- var addQueryFragment = function(string, newFragment, operator){
- if(!newFragment || (newFragment && newFragment.trim().length == 0) ){
- return string
- }
- if(string && string.trim().length){
- string += padString(operator)
- }
- string += newFragment
- return string
+ if (this.get("exclude")) {
+ // The query is constructed like so for filter groups with exclude set to true:
+ // ( ( -( mainQuery OR idFilterQuery ) AND *:* ) OR isPartOfQuery ) AND categoryQuery
+ // Build the query string piece by piece:
+
+ // 1. mainQuery
+ queryString += mainQuery;
+ queryString = wrapInParentheses(queryString);
+ // 2. ( mainQuery OR idFilterQuery )
+ if (idFilterQuery.trim().length) {
+ idOperator = this.get("mustMatchIds") ? "AND" : "OR";
+ queryString = addQueryFragment(
+ queryString,
+ idFilterQuery,
+ idOperator,
+ );
+ queryString = wrapInParentheses(queryString);
}
- // Helper function that wraps a string in parentheses
- var wrapInParentheses = function(string){
- if (!string || (string && string.trim().length == 0)) {
- return string
- }
- // TODO: We still want to wrap in parentheses in cases like "(a) OR (b)" and
- // "a OR (b) or c" but not in cases like (a OR b)
-
- // var alreadyWrapped = /^\(.*\)$/.test(string);
- // if (alreadyWrapped) {
- // return string
- // }
-
- return "(" + string + ")"
+ // 3. -( mainQuery OR idFilterQuery )
+ if (queryString.trim().length) {
+ queryString = "-" + queryString;
}
-
- // Get the list of filters that use id fields since these are used differently.
- var idFilters = this.get("filters").getIdFilters();
- // Get the remaining filters that don't contain any ID fields
- var mainFilters = this.get("filters").getNonIdFilters();
-
- // If the filterGroup should be excluded from the results, then don't include
- // the isPartOf filter in the part of the query that gets excluded. The
- // isPartOf filter is only meant to *include* additional results, never
- // exclude any.
- if(this.get("exclude")){
- var isPartOfFilter = null;
- idFilters.forEach(function(filterModel, index){
- if(filterModel.get("fields")[0] == "isPartOf"){
- idFilters.splice(index, 1);
- isPartOfFilter = filterModel
- }
- }, this)
- }
-
- // Create the grouped query for the id filters (this will have the isPartOf
- // filter query if exclude is false, and will not have it if exclude is true)
- var idFilterQuery = this.get("filters").getGroupQuery(idFilters, "OR").trim();
- // Make the query fragment for all of the filters that do not contain ID fields
- var mainQuery = this.get("filters").getGroupQuery(mainFilters, operator).trim();
- // Make the query string that should be added to all catalog searches
- var categoryQuery = ""
- if(this.get("catalogSearch")){
- categoryQuery = this.get("filters").createCatalogSearchQuery().trim()
+ // 4. ( -( mainQuery OR idFilterQuery ) AND *:* ) - see Filter model
+ // requiresPositiveClause for details on why positive clause is
+ // needed here
+ if (queryString.trim().length) {
+ queryString = addQueryFragment(queryString, "*:*", "AND");
+ queryString = wrapInParentheses(queryString);
}
- // Make the query string for the isPartOf filter when the filter group should
- // be excluded
- var isPartOfQuery = ""
- if(isPartOfFilter){
- isPartOfQuery = isPartOfFilter.getQuery().trim();
+ // 5. ( ( -( mainQuery OR idFilterQuery ) AND *:* ) OR isPartOfQuery)
+ if (isPartOfQuery) {
+ queryString = addQueryFragment(queryString, isPartOfQuery, "OR");
+ queryString = wrapInParentheses(queryString);
}
- if(this.get("exclude")){
-
- // The query is constructed like so for filter groups with exclude set to true:
- // ( ( -( mainQuery OR idFilterQuery ) AND *:* ) OR isPartOfQuery ) AND categoryQuery
- // Build the query string piece by piece:
-
- // 1. mainQuery
- queryString += mainQuery;
- queryString = wrapInParentheses(queryString)
- // 2. ( mainQuery OR idFilterQuery )
- if (idFilterQuery.trim().length){
- idOperator = this.get("mustMatchIds") ? "AND" : "OR"
- queryString = addQueryFragment(queryString, idFilterQuery, idOperator)
- queryString = wrapInParentheses(queryString)
- }
- // 3. -( mainQuery OR idFilterQuery )
- if(queryString.trim().length){
- queryString = "-" + queryString
- }
- // 4. ( -( mainQuery OR idFilterQuery ) AND *:* ) - see Filter model
- // requiresPositiveClause for details on why positive clause is
- // needed here
- if (queryString.trim().length) {
- queryString = addQueryFragment(queryString, "*:*", "AND")
- queryString = wrapInParentheses(queryString)
- }
- // 5. ( ( -( mainQuery OR idFilterQuery ) AND *:* ) OR isPartOfQuery)
- if (isPartOfQuery){
- queryString = addQueryFragment(queryString, isPartOfQuery, "OR")
- queryString = wrapInParentheses(queryString)
- }
-
- // 6. (-( mainQuery OR idFilterQuery ) AND *:* OR isPartOfQuery) AND
- // categoryQuery
- queryString = addQueryFragment(queryString, categoryQuery, "AND")
-
-
- } else {
-
- // The query is constructed like so for filter groups with exclude set to
- // false: ( mainQuery OR idFilterQuery ) AND catalogQuery where
- // idFilterQuery includes the isPartOfQuery
-
- // 1. mainQuery
- queryString += mainQuery;
- queryString = wrapInParentheses(queryString)
- // 2. ( mainQuery OR idFilterQuery )
- if (idFilterQuery.trim().length) {
- queryString = addQueryFragment(queryString, idFilterQuery, "OR")
- queryString = wrapInParentheses(queryString)
- }
- // 3. ( mainQuery OR idFilterQuery ) AND catalogQuery
- queryString = addQueryFragment(queryString, categoryQuery, "AND")
-
+ // 6. (-( mainQuery OR idFilterQuery ) AND *:* OR isPartOfQuery) AND
+ // categoryQuery
+ queryString = addQueryFragment(queryString, categoryQuery, "AND");
+ } else {
+ // The query is constructed like so for filter groups with exclude set to
+ // false: ( mainQuery OR idFilterQuery ) AND catalogQuery where
+ // idFilterQuery includes the isPartOfQuery
+
+ // 1. mainQuery
+ queryString += mainQuery;
+ queryString = wrapInParentheses(queryString);
+ // 2. ( mainQuery OR idFilterQuery )
+ if (idFilterQuery.trim().length) {
+ queryString = addQueryFragment(queryString, idFilterQuery, "OR");
+ queryString = wrapInParentheses(queryString);
}
+ // 3. ( mainQuery OR idFilterQuery ) AND catalogQuery
+ queryString = addQueryFragment(queryString, categoryQuery, "AND");
+ }
- return queryString
+ return queryString;
+ } catch (error) {
+ console.log(
+ "Error creating a query for a Filter Group, error details:" + error,
+ );
+ }
+ },
+
+ /**
+ * Overrides the default Backbone.Model.validate.function() to check if this
+ * FilterGroup model has all the required values.
+ *
+ * @param {Object} [attrs] - A literal object of model attributes to validate.
+ * @param {Object} [options] - A literal object of options for this validation
+ * process
+ * @return {Object} If there are errors, an object comprising error messages. If
+ * no errors, returns nothing.
+ */
+ validate: function () {
+ try {
+ var errors = {};
- } catch (error) {
- console.log("Error creating a query for a Filter Group, error details:" +
- error
- );
+ // The operator must be AND or OR
+ if (!["AND", "OR"].includes(this.get("operator"))) {
+ //Reset the value to the default rather than return an error
+ this.set("operator", this.defaults()["operator"]);
}
- },
-
- /**
- * Overrides the default Backbone.Model.validate.function() to check if this
- * FilterGroup model has all the required values.
- *
- * @param {Object} [attrs] - A literal object of model attributes to validate.
- * @param {Object} [options] - A literal object of options for this validation
- * process
- * @return {Object} If there are errors, an object comprising error messages. If
- * no errors, returns nothing.
- */
- validate: function(){
-
- try {
- var errors = {};
-
- // The operator must be AND or OR
- if( !["AND", "OR"].includes(this.get("operator")) ){
- //Reset the value to the default rather than return an error
- this.set("operator", this.defaults()["operator"]);
- }
- //Exclude should always be a boolean
- if( typeof this.get("exclude") !== "boolean" ){
- // Reset the value to the default rather than return an error
- this.set("exclude", this.defaults().exclude);
- }
+ //Exclude should always be a boolean
+ if (typeof this.get("exclude") !== "boolean") {
+ // Reset the value to the default rather than return an error
+ this.set("exclude", this.defaults().exclude);
+ }
- // Validate label, icon, and description for UI Filter Groups
- if(this.get("isUIFilterType")){
- var textAttributes = ["label", "icon", "description"];
- // These fields should be strings
- _.each(textAttributes, function(attr){
- if( typeof this.get(attr) !== "string" ){
+ // Validate label, icon, and description for UI Filter Groups
+ if (this.get("isUIFilterType")) {
+ var textAttributes = ["label", "icon", "description"];
+ // These fields should be strings
+ _.each(
+ textAttributes,
+ function (attr) {
+ if (typeof this.get(attr) !== "string") {
// Reset the value to the default rather than return an error
this.set(attr, this.defaults()[attr]);
}
- }, this);
- // If this filter group is not empty, and it's a UI Filter Group, then
- // the group needs a label to be valid.
- if(!this.isEmpty() && !this.get("label")){
- // Set a generic label instead of returning an error
- this.set("label", "Search")
- }
- }
-
- // There must be at least one filter or filter group within each group,
- // and each filter must be valid.
- if( this.get("filters").length == 0 ){
- errors.noFilters = "At least one filter is required."
- }
- else{
- this.get("filters").each(function(filter){
- if( !filter.isValid() ){
- errors.filter = "At least one filter is invalid.";
- }
- });
+ },
+ this,
+ );
+ // If this filter group is not empty, and it's a UI Filter Group, then
+ // the group needs a label to be valid.
+ if (!this.isEmpty() && !this.get("label")) {
+ // Set a generic label instead of returning an error
+ this.set("label", "Search");
}
+ }
- if( Object.keys(errors).length ) {
- return errors;
- } else {
- return;
- }
+ // There must be at least one filter or filter group within each group,
+ // and each filter must be valid.
+ if (this.get("filters").length == 0) {
+ errors.noFilters = "At least one filter is required.";
+ } else {
+ this.get("filters").each(function (filter) {
+ if (!filter.isValid()) {
+ errors.filter = "At least one filter is invalid.";
+ }
+ });
+ }
- } catch (error) {
- console.log("Error validating a FilterGroup. Error details: " + error);
+ if (Object.keys(errors).length) {
+ return errors;
+ } else {
+ return;
}
-
- },
-
- /**
- * isEmpty - Checks whether this Filter Group has any filter models that are not
- * empty.
- *
- * @return {boolean} returns true if the Filter Group has Filter models that are
- * not empty
- */
- isEmpty: function(){
- try {
- var filters = this.get("filters");
- if(!filters || !filters.length){
- return true
- }
- var subFilters = filters.getNonEmptyFilters();
- if(!subFilters || !subFilters.length){
- return true
- } else {
- return false
- }
- } catch (error) {
- console.log("Error checking if a Filter Group is empty. Assuming it is not." +
- " Error details: " + error);
- return false
+ } catch (error) {
+ console.log(
+ "Error validating a FilterGroup. Error details: " + error,
+ );
+ }
+ },
+
+ /**
+ * isEmpty - Checks whether this Filter Group has any filter models that are not
+ * empty.
+ *
+ * @return {boolean} returns true if the Filter Group has Filter models that are
+ * not empty
+ */
+ isEmpty: function () {
+ try {
+ var filters = this.get("filters");
+ if (!filters || !filters.length) {
+ return true;
+ }
+ var subFilters = filters.getNonEmptyFilters();
+ if (!subFilters || !subFilters.length) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (error) {
+ console.log(
+ "Error checking if a Filter Group is empty. Assuming it is not." +
+ " Error details: " +
+ error,
+ );
+ return false;
+ }
+ },
+
+ /**
+ * Updates the XML DOM with the new values from the model
+ * @param {object} [options] A literal object with options for this serialization
+ * @return {XMLElement} An updated filterGroup XML element
+ */
+ updateDOM: function (options) {
+ try {
+ // Don't serialize an empty filter group
+ if (this.isEmpty()) {
+ return null;
}
- },
-
- /**
- * Updates the XML DOM with the new values from the model
- * @param {object} [options] A literal object with options for this serialization
- * @return {XMLElement} An updated filterGroup XML element
- */
- updateDOM: function(options){
-
- try {
- // Don't serialize an empty filter group
- if(this.isEmpty()){
- return null
+ // Clone the DOM if it exists
+ var objectDOM = this.get("objectDOM");
+
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+ } else {
+ // Create an XML filterGroup or definition element from scratch
+ if (!objectDOM) {
+ var name = this.get("nodeName");
+ objectDOM = new DOMParser().parseFromString(
+ "<" + name + ">" + name + ">",
+ "text/xml",
+ );
+ objectDOM = $(objectDOM).find(name)[0];
}
+ }
- // Clone the DOM if it exists
- var objectDOM = this.get("objectDOM");
-
- if(objectDOM){
- objectDOM = objectDOM.cloneNode(true);
- } else {
- // Create an XML filterGroup or definition element from scratch
- if(!objectDOM){
- var name = this.get("nodeName");
- objectDOM = new DOMParser().parseFromString(
- "<" + name + ">" + name + ">",
- "text/xml"
- );
- objectDOM = $(objectDOM).find(name)[0];
+ $(objectDOM).empty();
+
+ // label, description, and icon are elements that are used in Portal
+ // UIFilterGroupType filterGroups only. Collection FilterGroupType filterGroups
+ // do not use these elements.
+ if (this.get("isUIFilterType")) {
+ // Get the new values for the simple text elements
+ var filterGroupData = {
+ label: this.get("label"),
+ description: this.get("description"),
+ icon: this.get("icon"),
+ };
+ // Serialize the simple text elements
+ _.map(filterGroupData, function (value, nodeName) {
+ // Don't serialize falsey values
+ if (value) {
+ // Make new sub-node
+ var nodeSerialized =
+ objectDOM.ownerDocument.createElement(nodeName);
+ $(nodeSerialized).text(value);
+ // Append new sub-node to objectDOM
+ $(objectDOM).append(nodeSerialized);
}
- }
+ });
+ }
- $(objectDOM).empty();
+ // Serialize the filters
+ var filterModels = this.get("filters").models;
- // label, description, and icon are elements that are used in Portal
- // UIFilterGroupType filterGroups only. Collection FilterGroupType filterGroups
- // do not use these elements.
- if(this.get("isUIFilterType")){
+ // TODO: Remove filter types depending on isUIFilterType attribute?
+ // toggleFilter and choiceFilter are only allowed in Portal UIFilterGroupType.
+ // nested filterGroups are only allowed in Collection FilterGroupType.
- // Get the new values for the simple text elements
- var filterGroupData = {
- label: this.get("label"),
- description: this.get("description"),
- icon: this.get("icon")
+ // Don't serialize falsey values
+ if (filterModels && filterModels.length) {
+ // Update each filter and append it to the DOM
+ _.each(filterModels, function (filterModel) {
+ if (filterModel) {
+ var filterModelSerialized = filterModel.updateDOM();
}
- // Serialize the simple text elements
- _.map(filterGroupData, function (value, nodeName) {
- // Don't serialize falsey values
- if (value) {
- // Make new sub-node
- var nodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
- $(nodeSerialized).text(value);
- // Append new sub-node to objectDOM
- $(objectDOM).append(nodeSerialized);
- }
- });
- }
-
- // Serialize the filters
- var filterModels = this.get("filters").models;
-
- // TODO: Remove filter types depending on isUIFilterType attribute?
- // toggleFilter and choiceFilter are only allowed in Portal UIFilterGroupType.
- // nested filterGroups are only allowed in Collection FilterGroupType.
-
- // Don't serialize falsey values
- if (filterModels && filterModels.length) {
- // Update each filter and append it to the DOM
- _.each(filterModels, function (filterModel) {
- if (filterModel) {
- var filterModelSerialized = filterModel.updateDOM();
- }
- $(objectDOM).append(filterModelSerialized);
- });
- }
-
- // exclude and operator are elements used only in Collection FilterGroupType
- // filterGroups. Portal UIFilterGroupType filterGroups do not use either of
- // these elements.
- if(!this.get("isUIFilterType")){
- // The nodeName and model attribute are the same in these cases.
- ["operator", "exclude"].forEach(function(nodeName){
- // Don't serialize empty, null, undefined, or default values
- var value = this.get(nodeName);
- if( (value || value === false) && value !== this.defaults()[nodeName] ){
- // Make new sub-node
- var nodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
- $(nodeSerialized).text(value);
- // Append new sub-node to objectDOM
- $(objectDOM).append(nodeSerialized);
- }
- }, this);
- }
-
- // TODO: serialize the new elements supported for Portal
- // UIFilterGroupType 1.1.0
- // if(this.get("isUIFilterType")){
- // ... serialize options ...
- // }
+ $(objectDOM).append(filterModelSerialized);
+ });
+ }
- return objectDOM
- } catch (error) {
- console.error("Unable to serialize a Filter Group.", error);
- return this.get("objectDOM") || "";
+ // exclude and operator are elements used only in Collection FilterGroupType
+ // filterGroups. Portal UIFilterGroupType filterGroups do not use either of
+ // these elements.
+ if (!this.get("isUIFilterType")) {
+ // The nodeName and model attribute are the same in these cases.
+ ["operator", "exclude"].forEach(function (nodeName) {
+ // Don't serialize empty, null, undefined, or default values
+ var value = this.get(nodeName);
+ if (
+ (value || value === false) &&
+ value !== this.defaults()[nodeName]
+ ) {
+ // Make new sub-node
+ var nodeSerialized =
+ objectDOM.ownerDocument.createElement(nodeName);
+ $(nodeSerialized).text(value);
+ // Append new sub-node to objectDOM
+ $(objectDOM).append(nodeSerialized);
+ }
+ }, this);
}
- }
+ // TODO: serialize the new elements supported for Portal
+ // UIFilterGroupType 1.1.0
+ // if(this.get("isUIFilterType")){
+ // ... serialize options ...
+ // }
- });
+ return objectDOM;
+ } catch (error) {
+ console.error("Unable to serialize a Filter Group.", error);
+ return this.get("objectDOM") || "";
+ }
+ },
+ },
+ );
- return FilterGroup;
- });
+ return FilterGroup;
+});
diff --git a/src/js/models/filters/NumericFilter.js b/src/js/models/filters/NumericFilter.js
index c5a433065..a8de1bfd9 100644
--- a/src/js/models/filters/NumericFilter.js
+++ b/src/js/models/filters/NumericFilter.js
@@ -1,373 +1,385 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/filters/Filter'],
- function ($, _, Backbone, Filter) {
-
- /**
- * @class NumericFilter
- * @classdesc A search filter whose search term is always an exact number or numbber range
- * @classcategory Models/Filters
- * @extends Filter
- * @constructs
- */
- var NumericFilter = Filter.extend(
- /** @lends NumericFilter.prototype */{
-
- type: "NumericFilter",
-
- /**
- * Default attributes for this model
- * @extends Filter#defaults
- * @type {Object}
- * @property {Date} min - The minimum number to use in the query for this filter
- * @property {Date} max - The maximum number to use in the query for this filter
- * @property {Date} rangeMin - The lowest possible number that 'min' can be
- * @property {Date} rangeMax - The highest possible number that 'max' can be
- * @property {string} nodeName - The XML node name to use when serializing this model into XML
- * @property {boolean} range - If true, this Filter will use a numeric range as the search term instead of an exact number
- * @property {number} step - The number to increase the search value by when incrementally increasing or decreasing the numeric range
- */
- defaults: function () {
- return _.extend(Filter.prototype.defaults(), {
- nodeName: "numericFilter",
- min: null,
- max: null,
- rangeMin: null,
- rangeMax: null,
- range: true,
- step: 1
+define(["jquery", "underscore", "backbone", "models/filters/Filter"], function (
+ $,
+ _,
+ Backbone,
+ Filter,
+) {
+ /**
+ * @class NumericFilter
+ * @classdesc A search filter whose search term is always an exact number or numbber range
+ * @classcategory Models/Filters
+ * @extends Filter
+ * @constructs
+ */
+ var NumericFilter = Filter.extend(
+ /** @lends NumericFilter.prototype */ {
+ type: "NumericFilter",
+
+ /**
+ * Default attributes for this model
+ * @extends Filter#defaults
+ * @type {Object}
+ * @property {Date} min - The minimum number to use in the query for this filter
+ * @property {Date} max - The maximum number to use in the query for this filter
+ * @property {Date} rangeMin - The lowest possible number that 'min' can be
+ * @property {Date} rangeMax - The highest possible number that 'max' can be
+ * @property {string} nodeName - The XML node name to use when serializing this model into XML
+ * @property {boolean} range - If true, this Filter will use a numeric range as the search term instead of an exact number
+ * @property {number} step - The number to increase the search value by when incrementally increasing or decreasing the numeric range
+ */
+ defaults: function () {
+ return _.extend(Filter.prototype.defaults(), {
+ nodeName: "numericFilter",
+ min: null,
+ max: null,
+ rangeMin: null,
+ rangeMax: null,
+ range: true,
+ step: 1,
+ });
+ },
+
+ initialize: function (attributes, options) {
+ const model = this;
+ Filter.prototype.initialize.call(this, attributes, options);
+
+ // Limit the range min, range max, and update step if the model switches from
+ // being a coordinate filter to a regular numeric filter or vice versa
+ model.listenTo(model, "change:fields", function () {
+ model.toggleCoordinateLimits();
+ });
+ model.toggleCoordinateLimits();
+ },
+
+ /**
+ * For filters that represent geographic coordinates, return the
+ * appropriate defaults for the NumericFilter model.
+ * @param {'latitude'|'longitude'} coord - The coordinate type to get
+ * defaults for.
+ * @returns {Object} The rangeMin, rangeMax, and step values for the
+ * given coordinate type
+ */
+ coordDefaults: function (coord = "longitude") {
+ return {
+ rangeMin: coord === "longitude" ? -180 : -90,
+ rangeMax: coord === "longitude" ? 180 : 90,
+ step: 0.00001,
+ };
+ },
+
+ /**
+ * Add or remove the rangeMin, rangeMax, and step associated with
+ * coordinate queries. If the filter is a coordinate filter, then add
+ * the appropriate defaults for the rangeMin, rangeMax, and step. If
+ * the filter is NOT a coordinate filter, then set rangeMin, rangeMax,
+ * and step to the regular defaults for a numeric filter.
+ * @param {Boolean} [overwrite=false] - By default, the rangeMin,
+ * rangeMax, and step will only be reset if they are currently set to
+ * one of the default values (e.g. if the model has default values for
+ * a numeric filter, they will be set to the default values for a
+ * coordinate filter). To change this behaviour to always reset the
+ * attributes to the new defaults values, set overwrite to true.
+ */
+ toggleCoordinateLimits: function (overwrite = false) {
+ try {
+ const model = this;
+ const lonDefaults = model.coordDefaults("longitude");
+ const latDefaults = model.coordDefaults("latitude");
+ const numDefaults = model.defaults();
+ const attrs = Object.keys(lonDefaults); // 'rangeMin', 'rangeMax', and 'step'
+
+ const isDefault = function (attr) {
+ const val = model.get(attr);
+ return (
+ val == numDefaults[attr] ||
+ val == latDefaults[attr] ||
+ val == lonDefaults[attr]
+ );
+ };
+
+ // When the model has changed to a numeric filter, set the range min, range max,
+ // and step to the default values for a numeric filter, if they are currently set
+ // to the default values for a coordinate filter (or when overwrite is true).
+ let defaultsToSet = numDefaults;
+
+ // When the model has changed to a coordinate filter, set the range min, range max,
+ // and step to the default values for a coordinate filter, if they are currently set
+ // to the default values for a numeric filter (or when overwrite is true).
+ if (model.isCoordinateQuery()) {
+ // Use longitude range (-180, 180) for longitude only queries, or queries with
+ // both longitude and latitude
+ defaultsToSet = lonDefaults;
+ if (model.isLatitudeQuery()) {
+ defaultsToSet = latDefaults;
+ }
+ }
+ attrs.forEach(function (attr) {
+ if (isDefault(attr) || overwrite) {
+ model.set(attr, defaultsToSet[attr]);
+ }
});
- },
- initialize: function (attributes, options) {
+ model.limitToRange();
+ model.roundToStep();
+ } catch (error) {
+ console.log(
+ "There was an error toggling Coordinate limits in a NumericFilter" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+ /**
+ * Ensures that the min, max, and value are within the rangeMin and rangeMax.
+ */
+ limitToRange: function () {
+ try {
const model = this;
- Filter.prototype.initialize.call(this, attributes, options);
+ const min = model.get("min");
+ const max = model.get("max");
- // Limit the range min, range max, and update step if the model switches from
- // being a coordinate filter to a regular numeric filter or vice versa
- model.listenTo(model, 'change:fields', function () {
- model.toggleCoordinateLimits()
- })
- model.toggleCoordinateLimits();
+ const rangeMin = model.get("rangeMin");
+ const rangeMax = model.get("rangeMax");
- },
-
- /**
- * For filters that represent geographic coordinates, return the
- * appropriate defaults for the NumericFilter model.
- * @param {'latitude'|'longitude'} coord - The coordinate type to get
- * defaults for.
- * @returns {Object} The rangeMin, rangeMax, and step values for the
- * given coordinate type
- */
- coordDefaults: function (coord = 'longitude') {
- return {
- rangeMin: coord === 'longitude' ? -180 : -90,
- rangeMax: coord === 'longitude' ? 180 : 90,
- step: 0.00001
- }
- },
-
- /**
- * Add or remove the rangeMin, rangeMax, and step associated with
- * coordinate queries. If the filter is a coordinate filter, then add
- * the appropriate defaults for the rangeMin, rangeMax, and step. If
- * the filter is NOT a coordinate filter, then set rangeMin, rangeMax,
- * and step to the regular defaults for a numeric filter.
- * @param {Boolean} [overwrite=false] - By default, the rangeMin,
- * rangeMax, and step will only be reset if they are currently set to
- * one of the default values (e.g. if the model has default values for
- * a numeric filter, they will be set to the default values for a
- * coordinate filter). To change this behaviour to always reset the
- * attributes to the new defaults values, set overwrite to true.
- */
- toggleCoordinateLimits: function (overwrite = false) {
- try {
- const model = this;
- const lonDefaults = model.coordDefaults('longitude');
- const latDefaults = model.coordDefaults('latitude');
- const numDefaults = model.defaults();
- const attrs = Object.keys(lonDefaults); // 'rangeMin', 'rangeMax', and 'step'
-
- const isDefault = function (attr) {
- const val = model.get(attr)
- return (val == numDefaults[attr]) || (val == latDefaults[attr]) || (val == lonDefaults[attr])
+ const values = model.get("values");
+ const value = values != null && values.length ? values[0] : null;
+
+ // Set MIN to min or max if it is outside the range
+ if (min != null) {
+ if (rangeMin != null && min < rangeMin) {
+ model.set("min", rangeMin);
+ }
+ if (rangeMax != null && min > rangeMax) {
+ model.set("min", rangeMax);
}
+ }
- // When the model has changed to a numeric filter, set the range min, range max,
- // and step to the default values for a numeric filter, if they are currently set
- // to the default values for a coordinate filter (or when overwrite is true).
- let defaultsToSet = numDefaults
-
- // When the model has changed to a coordinate filter, set the range min, range max,
- // and step to the default values for a coordinate filter, if they are currently set
- // to the default values for a numeric filter (or when overwrite is true).
- if (model.isCoordinateQuery()) {
- // Use longitude range (-180, 180) for longitude only queries, or queries with
- // both longitude and latitude
- defaultsToSet = lonDefaults
- if (model.isLatitudeQuery()) {
- defaultsToSet = latDefaults
- }
+ // Set the MAX to min or max if it is outside the range
+ if (max != null) {
+ if (rangeMax != null && max > rangeMax) {
+ model.set("max", rangeMax);
}
- attrs.forEach(function (attr) {
- if (isDefault(attr) || overwrite) {
- model.set(attr, defaultsToSet[attr])
- }
- })
+ if (rangeMin != null && max < rangeMin) {
+ model.set("max", rangeMin);
+ }
+ }
- model.limitToRange()
- model.roundToStep()
+ // Set the VALUE to min or max if it is outside the range
+ if (value != null) {
+ if (rangeMax != null && value > rangeMax) {
+ values[0] = rangeMax;
+ model.set("values", values);
+ }
+ if (rangeMin != null && value < rangeMin) {
+ values[0] = rangeMin;
+ model.set("values", values);
+ }
}
- catch (error) {
- console.log(
- 'There was an error toggling Coordinate limits in a NumericFilter' +
- '. Error details: ' + error
- );
+ } catch (error) {
+ console.log(
+ "There was an error limiting a NumericFilter to the range" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ /**
+ * Rounds the min, max, and/or value to the same number of decimal
+ * places as the step.
+ */
+ roundToStep: function () {
+ try {
+ const model = this;
+ const min = model.get("min");
+ const max = model.get("max");
+ const step = model.get("step");
+
+ const values = model.get("values");
+ const value = values != null && values.length ? values[0] : null;
+
+ // Returns the number of decimal places in a number
+ function countDecimals(n) {
+ let text = n.toString();
+ // verify if number 0.000005 is represented as "5e-6"
+ if (text.indexOf("e-") > -1) {
+ let [base, trail] = text.split("e-");
+ let deg = parseInt(trail, 10);
+ return deg;
+ }
+ // count decimals for number in representation like "0.123456"
+ if (Math.floor(n) !== n) {
+ return n.toString().split(".")[1].length || 0;
+ }
+ return 0;
}
- },
-
- /**
- * Ensures that the min, max, and value are within the rangeMin and rangeMax.
- */
- limitToRange: function () {
- try {
- const model = this;
- const min = model.get('min');
- const max = model.get('max');
-
- const rangeMin = model.get('rangeMin');
- const rangeMax = model.get('rangeMax');
- const values = model.get('values');
- const value = values != null && values.length ? values[0] : null;
+ // Rounds a number to the specified number of decimal places
+ function roundTo(n, digits) {
+ if (digits === undefined) {
+ digits = 0;
+ }
+ const multiplicator = Math.pow(10, digits);
+ n = parseFloat((n * multiplicator).toFixed(11));
+ const test = Math.round(n) / multiplicator;
+ return +test.toFixed(digits);
+ }
- // Set MIN to min or max if it is outside the range
+ // Round min & max to number of decimal places in step
+ if (step != null) {
+ let digits = countDecimals(step);
if (min != null) {
- if (rangeMin != null && min < rangeMin) {
- model.set('min', rangeMin);
- }
- if (rangeMax != null && min > rangeMax) {
- model.set('min', rangeMax);
- }
+ model.set("min", roundTo(min, digits));
}
-
- // Set the MAX to min or max if it is outside the range
if (max != null) {
- if (rangeMax != null && max > rangeMax) {
- model.set('max', rangeMax);
- }
- if (rangeMin != null && max < rangeMin) {
- model.set('max', rangeMin);
- }
+ model.set("max", roundTo(max, digits));
}
-
- // Set the VALUE to min or max if it is outside the range
if (value != null) {
- if (rangeMax != null && value > rangeMax) {
- values[0] = rangeMax;
- model.set('values', values);
- }
- if (rangeMin != null && value < rangeMin) {
- values[0] = rangeMin;
- model.set('values', values);
- }
+ values[0] = roundTo(value, digits);
+ model.set("values", values);
}
}
- catch (error) {
- console.log(
- 'There was an error limiting a NumericFilter to the range' +
- '. Error details: ' + error
- );
- }
- },
-
- /**
- * Rounds the min, max, and/or value to the same number of decimal
- * places as the step.
- */
- roundToStep: function () {
- try {
- const model = this;
- const min = model.get('min');
- const max = model.get('max');
- const step = model.get('step');
-
- const values = model.get('values');
- const value = values != null && values.length ? values[0] : null;
-
- // Returns the number of decimal places in a number
- function countDecimals(n) {
- let text = n.toString()
- // verify if number 0.000005 is represented as "5e-6"
- if (text.indexOf('e-') > -1) {
- let [base, trail] = text.split('e-');
- let deg = parseInt(trail, 10);
- return deg;
- }
- // count decimals for number in representation like "0.123456"
- if (Math.floor(n) !== n) {
- return n.toString().split(".")[1].length || 0;
- }
- return 0;
- }
-
- // Rounds a number to the specified number of decimal places
- function roundTo(n, digits) {
- if (digits === undefined) {
- digits = 0;
- }
- const multiplicator = Math.pow(10, digits);
- n = parseFloat((n * multiplicator).toFixed(11));
- const test = (Math.round(n) / multiplicator);
- return +(test.toFixed(digits));
- }
-
- // Round min & max to number of decimal places in step
- if (step != null) {
- let digits = countDecimals(step)
- if (min != null) {
- model.set('min', roundTo(min, digits))
- }
- if (max != null) {
- model.set('max', roundTo(max, digits))
- }
- if (value != null) {
- values[0] = roundTo(value, digits)
- model.set('values', values)
- }
- }
+ } catch (error) {
+ console.log(
+ "There was an error rounding values in a NumericFilter to the step" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ /**
+ * Parses the numericFilter XML node into JSON
+ *
+ * @param {Element} xml - The XML Element that contains all the NumericFilter elements
+ * @return {JSON} - The JSON object literal to be set on the model
+ */
+ parse: function (xml) {
+ try {
+ var modelJSON = Filter.prototype.parse.call(this, xml);
+
+ //Get the rangeMin and rangeMax nodes
+ var rangeMinNode = $(xml).find("rangeMin"),
+ rangeMaxNode = $(xml).find("rangeMax");
+
+ //Parse the range min
+ if (rangeMinNode.length) {
+ modelJSON.rangeMin = parseFloat(rangeMinNode[0].textContent);
}
- catch (error) {
- console.log(
- 'There was an error rounding values in a NumericFilter to the step' +
- '. Error details: ' + error
- );
+ //Parse the range max
+ if (rangeMaxNode.length) {
+ modelJSON.rangeMax = parseFloat(rangeMaxNode[0].textContent);
}
- },
-
- /**
- * Parses the numericFilter XML node into JSON
- *
- * @param {Element} xml - The XML Element that contains all the NumericFilter elements
- * @return {JSON} - The JSON object literal to be set on the model
- */
- parse: function (xml) {
-
- try {
- var modelJSON = Filter.prototype.parse.call(this, xml);
-
- //Get the rangeMin and rangeMax nodes
- var rangeMinNode = $(xml).find("rangeMin"),
- rangeMaxNode = $(xml).find("rangeMax");
-
- //Parse the range min
- if (rangeMinNode.length) {
- modelJSON.rangeMin = parseFloat(rangeMinNode[0].textContent);
- }
- //Parse the range max
- if (rangeMaxNode.length) {
- modelJSON.rangeMax = parseFloat(rangeMaxNode[0].textContent);
- }
- //If this Filter is in a filter group, don't parse the values
- if (!this.get("isUIFilterType")) {
- //Get the min, max, and value nodes
- var minNode = $(xml).find("min"),
- maxNode = $(xml).find("max"),
- valueNode = $(xml).find("value");
+ //If this Filter is in a filter group, don't parse the values
+ if (!this.get("isUIFilterType")) {
+ //Get the min, max, and value nodes
+ var minNode = $(xml).find("min"),
+ maxNode = $(xml).find("max"),
+ valueNode = $(xml).find("value");
- //Parse the min value
- if (minNode.length) {
- modelJSON.min = parseFloat(minNode[0].textContent);
- }
- //Parse the max value
- if (maxNode.length) {
- modelJSON.max = parseFloat(maxNode[0].textContent);
- }
- //Parse the value
- if (valueNode.length) {
- modelJSON.values = [parseFloat(valueNode[0].textContent)];
- }
+ //Parse the min value
+ if (minNode.length) {
+ modelJSON.min = parseFloat(minNode[0].textContent);
}
- //If a range min and max was given, or if a min and max value was given,
- // then this NumericFilter should be presented as a numeric range (rather than
- // an exact numeric value).
- if (rangeMinNode.length || rangeMaxNode.length || (minNode.length && maxNode.length)) {
- //Set the range attribute on the JSON
- modelJSON.range = true;
+ //Parse the max value
+ if (maxNode.length) {
+ modelJSON.max = parseFloat(maxNode[0].textContent);
}
- else {
- //Set the range attribute on the JSON
- modelJSON.range = false;
- }
-
- //If a range step was given, save it
- if (modelJSON.range) {
- var stepNode = $(xml).find("step");
-
- if (stepNode.length) {
- //Parse the text content of the node into a float
- modelJSON.step = parseFloat(stepNode[0].textContent);
- }
+ //Parse the value
+ if (valueNode.length) {
+ modelJSON.values = [parseFloat(valueNode[0].textContent)];
}
}
- catch (e) {
- //If an error occurred while parsing the XML, return a blank JS object
- //(i.e. this model will just have the default values).
- return {};
- }
-
- return modelJSON;
- },
-
-
- /**
- * Builds a query string that represents this filter.
- *
- * @return {string} The query string to send to Solr
- */
- getQuery: function () {
-
- //Start the query string
- var queryString = "";
-
+ //If a range min and max was given, or if a min and max value was given,
+ // then this NumericFilter should be presented as a numeric range (rather than
+ // an exact numeric value).
if (
- // For numeric filters that are ranges, only construct the query if the min or max
- // is different than the default
- this.get("min") != this.get("rangeMin") ||
- this.get("max") != this.get("rangeMax") ||
- // Otherwise, a numeric filter could search for an exact value
- (this.get("values") && this.get("values").length)
-
+ rangeMinNode.length ||
+ rangeMaxNode.length ||
+ (minNode.length && maxNode.length)
) {
+ //Set the range attribute on the JSON
+ modelJSON.range = true;
+ } else {
+ //Set the range attribute on the JSON
+ modelJSON.range = false;
+ }
- //Iterate over each filter field and add to the query string
- _.each(this.get("fields"), function (field, i, allFields) {
+ //If a range step was given, save it
+ if (modelJSON.range) {
+ var stepNode = $(xml).find("step");
+ if (stepNode.length) {
+ //Parse the text content of the node into a float
+ modelJSON.step = parseFloat(stepNode[0].textContent);
+ }
+ }
+ } catch (e) {
+ //If an error occurred while parsing the XML, return a blank JS object
+ //(i.e. this model will just have the default values).
+ return {};
+ }
+
+ return modelJSON;
+ },
+
+ /**
+ * Builds a query string that represents this filter.
+ *
+ * @return {string} The query string to send to Solr
+ */
+ getQuery: function () {
+ //Start the query string
+ var queryString = "";
+
+ if (
+ // For numeric filters that are ranges, only construct the query if the min or max
+ // is different than the default
+ this.get("min") != this.get("rangeMin") ||
+ this.get("max") != this.get("rangeMax") ||
+ // Otherwise, a numeric filter could search for an exact value
+ (this.get("values") && this.get("values").length)
+ ) {
+ //Iterate over each filter field and add to the query string
+ _.each(
+ this.get("fields"),
+ function (field, i, allFields) {
//Get the minimum, maximum, and value.
var max = this.get("max"),
min = this.get("min"),
value = this.get("values") ? this.get("values")[0] : null,
- escapeMinus = function (val) { return val.toString().replace("-", "\\%2D") },
- exists = function (val) { return val !== null && val !== undefined }
-
+ escapeMinus = function (val) {
+ return val.toString().replace("-", "\\%2D");
+ },
+ exists = function (val) {
+ return val !== null && val !== undefined;
+ };
//Construct a query string for ranges, min, or max
- if (
- this.get("range") ||
- (max || max === 0) ||
- (min || min === 0)
- ) {
-
+ if (this.get("range") || max || max === 0 || min || min === 0) {
//If no min or max was set, but there is a value, construct an exact value match query
- if (!min && min !== 0 && !max && max !== 0 && (value || value === 0)) {
+ if (
+ !min &&
+ min !== 0 &&
+ !max &&
+ max !== 0 &&
+ (value || value === 0)
+ ) {
// Escape the minus sign if needed
queryString += field + ":" + escapeMinus(value);
}
//If there is no min or max or value, set an empty query string
- else if (!min && min !== 0 && !max && max !== 0 &&
- (!value && value !== 0)) {
+ else if (
+ !min &&
+ min !== 0 &&
+ !max &&
+ max !== 0 &&
+ !value &&
+ value !== 0
+ ) {
queryString = "";
}
//If there is at least a min or max
@@ -381,12 +393,18 @@ define(['jquery', 'underscore', 'backbone', 'models/filters/Filter'],
min = "*";
}
//If the max is higher than the min, set the max to a wildcard (unbounded)
- else if (exists(max) && exists(min) && (max < min)) {
+ else if (exists(max) && exists(min) && max < min) {
max = "*";
}
//Add the range for this field to the query string
- queryString += field + ":[" + escapeMinus(min) + "%20TO%20" + escapeMinus(max) + "]";
+ queryString +=
+ field +
+ ":[" +
+ escapeMinus(min) +
+ "%20TO%20" +
+ escapeMinus(max) +
+ "]";
}
}
//If there is a value set, construct an exact numeric match query
@@ -399,216 +417,236 @@ define(['jquery', 'underscore', 'backbone', 'models/filters/Filter'],
if (allFields[i + 1] && queryString.length) {
queryString += "%20" + this.get("fieldsOperator") + "%20";
}
+ },
+ this,
+ );
- }, this);
-
- //If there is more than one field, wrap the query in parentheses
- if (this.get("fields").length > 1 && queryString.length) {
- queryString = "(" + queryString + ")";
- }
-
+ //If there is more than one field, wrap the query in parentheses
+ if (this.get("fields").length > 1 && queryString.length) {
+ queryString = "(" + queryString + ")";
}
+ }
- return queryString;
-
- },
-
- /**
- * Updates the XML DOM with the new values from the model
- * @inheritdoc
- * @return {XMLElement} An updated numericFilter XML element from a portal document
- */
- updateDOM: function (options) {
-
- try {
- if (typeof options == "undefined") {
- var options = {};
- }
-
- var objectDOM = Filter.prototype.updateDOM.call(this);
+ return queryString;
+ },
+
+ /**
+ * Updates the XML DOM with the new values from the model
+ * @inheritdoc
+ * @return {XMLElement} An updated numericFilter XML element from a portal document
+ */
+ updateDOM: function (options) {
+ try {
+ if (typeof options == "undefined") {
+ var options = {};
+ }
- //Numeric Filters don't use matchSubstring nodes
- $(objectDOM).children("matchSubstring").remove();
+ var objectDOM = Filter.prototype.updateDOM.call(this);
- //Get a clone of the original DOM
- var originalDOM;
- if (this.get("objectDOM")) {
- originalDOM = this.get("objectDOM").cloneNode(true);
- }
+ //Numeric Filters don't use matchSubstring nodes
+ $(objectDOM).children("matchSubstring").remove();
- // Get new numeric data
- var numericData = {
- min: this.get("min"),
- max: this.get("max")
- };
-
- if (this.get("isUIFilterType")) {
- numericData = _.extend(numericData, {
- rangeMin: this.get("rangeMin"),
- rangeMax: this.get("rangeMax"),
- step: this.get("step")
- });
- }
+ //Get a clone of the original DOM
+ var originalDOM;
+ if (this.get("objectDOM")) {
+ originalDOM = this.get("objectDOM").cloneNode(true);
+ }
- // Make subnodes and append to DOM
- _.map(numericData, function (value, nodeName) {
+ // Get new numeric data
+ var numericData = {
+ min: this.get("min"),
+ max: this.get("max"),
+ };
+
+ if (this.get("isUIFilterType")) {
+ numericData = _.extend(numericData, {
+ rangeMin: this.get("rangeMin"),
+ rangeMax: this.get("rangeMax"),
+ step: this.get("step"),
+ });
+ }
+ // Make subnodes and append to DOM
+ _.map(
+ numericData,
+ function (value, nodeName) {
if (value || value === 0) {
-
//If this value is the same as the default value, but it wasn't previously serialized,
- if ((value == this.defaults()[nodeName]) &&
+ if (
+ value == this.defaults()[nodeName] &&
(!$(originalDOM).children(nodeName).length ||
- ($(originalDOM).children(nodeName).text() != value + "-01-01T00:00:00Z"))) {
+ $(originalDOM).children(nodeName).text() !=
+ value + "-01-01T00:00:00Z")
+ ) {
return;
}
- var nodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
+ var nodeSerialized =
+ objectDOM.ownerDocument.createElement(nodeName);
$(nodeSerialized).text(value);
$(objectDOM).append(nodeSerialized);
}
-
- }, this);
-
- //Remove filterOptions for collection definition filters
- if (!this.get("isUIFilterType")) {
- $(objectDOM).children("filterOptions").remove();
- }
- else {
- //Make sure the filterOptions are listed last
- //Get the filterOptions element
- var filterOptions = $(objectDOM).children("filterOptions");
- //If the filterOptions exist
- if (filterOptions.length) {
- //Detach from their current position and append to the end
- filterOptions.detach();
- $(objectDOM).append(filterOptions);
- }
+ },
+ this,
+ );
+
+ //Remove filterOptions for collection definition filters
+ if (!this.get("isUIFilterType")) {
+ $(objectDOM).children("filterOptions").remove();
+ } else {
+ //Make sure the filterOptions are listed last
+ //Get the filterOptions element
+ var filterOptions = $(objectDOM).children("filterOptions");
+ //If the filterOptions exist
+ if (filterOptions.length) {
+ //Detach from their current position and append to the end
+ filterOptions.detach();
+ $(objectDOM).append(filterOptions);
}
-
- // If there is a min or max or both, there must not be a value
- if (numericData.min || numericData.min === 0 || numericData.max || numericData.max === 0) {
- $(objectDOM).children("value").remove();
- }
-
- return objectDOM;
}
- catch (e) {
- return "";
- }
-
- },
- /**
- * Creates a human-readable string that represents the value set on this model
- * @return {string}
- */
- getReadableValue: function () {
-
- var readableValue = "";
-
- var min = this.get("min"),
- max = this.get("max"),
- value = this.get("values")[0];
+ // If there is a min or max or both, there must not be a value
+ if (
+ numericData.min ||
+ numericData.min === 0 ||
+ numericData.max ||
+ numericData.max === 0
+ ) {
+ $(objectDOM).children("value").remove();
+ }
- if (!value && value !== 0) {
- //If there is a min and max
- if ((min || min === 0) && (max || max === 0)) {
- readableValue = min + " to " + max;
- }
- //If there is only a max
- else if (max || max === 0) {
- readableValue = "No more than " + max;
- }
- else {
- readableValue = "At least " + min;
- }
+ return objectDOM;
+ } catch (e) {
+ return "";
+ }
+ },
+
+ /**
+ * Creates a human-readable string that represents the value set on this model
+ * @return {string}
+ */
+ getReadableValue: function () {
+ var readableValue = "";
+
+ var min = this.get("min"),
+ max = this.get("max"),
+ value = this.get("values")[0];
+
+ if (!value && value !== 0) {
+ //If there is a min and max
+ if ((min || min === 0) && (max || max === 0)) {
+ readableValue = min + " to " + max;
}
- else {
- readableValue = value;
+ //If there is only a max
+ else if (max || max === 0) {
+ readableValue = "No more than " + max;
+ } else {
+ readableValue = "At least " + min;
}
+ } else {
+ readableValue = value;
+ }
- return readableValue;
-
- },
-
- /**
- * @inheritdoc
- */
- hasChangedValues: function () {
-
- return (this.get("values").length > 0 ||
- this.get("min") != this.defaults().min ||
- this.get("max") != this.defaults().max);
-
- },
-
- /**
- * Checks if the values set on this model are valid and expected
- * @return {object} - Returns a literal object with the invalid attributes and their corresponding error message
- */
- validate: function () {
-
- //Validate most of the NumericFilter attributes using the parent validate function
- var errors = Filter.prototype.validate.call(this);
+ return readableValue;
+ },
+
+ /**
+ * @inheritdoc
+ */
+ hasChangedValues: function () {
+ return (
+ this.get("values").length > 0 ||
+ this.get("min") != this.defaults().min ||
+ this.get("max") != this.defaults().max
+ );
+ },
+
+ /**
+ * Checks if the values set on this model are valid and expected
+ * @return {object} - Returns a literal object with the invalid attributes and their corresponding error message
+ */
+ validate: function () {
+ //Validate most of the NumericFilter attributes using the parent validate function
+ var errors = Filter.prototype.validate.call(this);
+
+ //If everything is valid so far, then we have to create a new object to store errors
+ if (typeof errors != "object") {
+ errors = {};
+ }
- //If everything is valid so far, then we have to create a new object to store errors
- if (typeof errors != "object") {
- errors = {};
- }
+ //Delete error messages for the attributes that are going to be validated specially for the NumericFilter
+ delete errors.values;
+ delete errors.min;
+ delete errors.max;
+ delete errors.rangeMin;
+ delete errors.rangeMax;
- //Delete error messages for the attributes that are going to be validated specially for the NumericFilter
- delete errors.values;
- delete errors.min;
- delete errors.max;
- delete errors.rangeMin;
- delete errors.rangeMax;
-
- //If there is an exact number set as the search term
- if (Array.isArray(this.get("values")) && this.get("values").length) {
- //Check that all the values are numbers
- if (_.find(this.get("values"), function (n) { return typeof n != "number" })) {
- errors.values = "All of the search terms for this filter need to be numbers.";
- }
- }
- //If there is a search term set on the model that is not an array, or number,
- // or undefined, or null, then it is some other invalid value like a string or date.
- else if (!Array.isArray(this.get("values")) && typeof values != "number" && typeof values != "undefined" && values !== null) {
- errors.values = "The search term for this filter needs to a number.";
- }
- //Check that the min and max values are in order, if the minimum is not the default value of 0
- else if (typeof this.get("min") == "number" && typeof this.get("max") == "number") {
- if (this.get("min") > this.get("max") && this.get("min") != 0) {
- errors.min = "The minimum is after the maximum. The minimum must be a number less than the maximum, which is " + this.get("max");
- }
- }
- //If there is only a minimum number specified, check that it is a number
- else if (this.get("min") && typeof this.get("min") != "number") {
- errors.min = "The minimum needs to be a number."
- if (this.get("max") && typeof this.get("max") != "number") {
- errors.max = "The maximum needs to be a number."
- }
- }
- //Check if the maximum is a value other than a number
- else if (this.get("max") && typeof this.get("max") != "number") {
- errors.max = "The maximum needs to be a number."
- }
- //If there is no min, max, or value, then return an errors
- else if (!this.get("max") && this.get("max") !== 0 && !this.get("min") && this.get("min") !== 0 &&
- ((!this.get("values") && this.get("values") !== 0) || (Array.isArray(this.get("values")) && !this.get("values").length))) {
- errors.values = "This search filter needs an exact number or a number range to use in the search query."
+ //If there is an exact number set as the search term
+ if (Array.isArray(this.get("values")) && this.get("values").length) {
+ //Check that all the values are numbers
+ if (
+ _.find(this.get("values"), function (n) {
+ return typeof n != "number";
+ })
+ ) {
+ errors.values =
+ "All of the search terms for this filter need to be numbers.";
}
-
- //Return the error messages
- if (Object.keys(errors).length) {
- return errors;
+ }
+ //If there is a search term set on the model that is not an array, or number,
+ // or undefined, or null, then it is some other invalid value like a string or date.
+ else if (
+ !Array.isArray(this.get("values")) &&
+ typeof values != "number" &&
+ typeof values != "undefined" &&
+ values !== null
+ ) {
+ errors.values = "The search term for this filter needs to a number.";
+ }
+ //Check that the min and max values are in order, if the minimum is not the default value of 0
+ else if (
+ typeof this.get("min") == "number" &&
+ typeof this.get("max") == "number"
+ ) {
+ if (this.get("min") > this.get("max") && this.get("min") != 0) {
+ errors.min =
+ "The minimum is after the maximum. The minimum must be a number less than the maximum, which is " +
+ this.get("max");
}
- else {
- return;
+ }
+ //If there is only a minimum number specified, check that it is a number
+ else if (this.get("min") && typeof this.get("min") != "number") {
+ errors.min = "The minimum needs to be a number.";
+ if (this.get("max") && typeof this.get("max") != "number") {
+ errors.max = "The maximum needs to be a number.";
}
-
+ }
+ //Check if the maximum is a value other than a number
+ else if (this.get("max") && typeof this.get("max") != "number") {
+ errors.max = "The maximum needs to be a number.";
+ }
+ //If there is no min, max, or value, then return an errors
+ else if (
+ !this.get("max") &&
+ this.get("max") !== 0 &&
+ !this.get("min") &&
+ this.get("min") !== 0 &&
+ ((!this.get("values") && this.get("values") !== 0) ||
+ (Array.isArray(this.get("values")) && !this.get("values").length))
+ ) {
+ errors.values =
+ "This search filter needs an exact number or a number range to use in the search query.";
}
- });
+ //Return the error messages
+ if (Object.keys(errors).length) {
+ return errors;
+ } else {
+ return;
+ }
+ },
+ },
+ );
- return NumericFilter;
- });
+ return NumericFilter;
+});
diff --git a/src/js/models/filters/SpatialFilter.js b/src/js/models/filters/SpatialFilter.js
index 1d327fd83..73aa47a7d 100644
--- a/src/js/models/filters/SpatialFilter.js
+++ b/src/js/models/filters/SpatialFilter.js
@@ -2,7 +2,7 @@ define([
"underscore",
"jquery",
"models/filters/Filter",
- "collections/maps/Geohashes"
+ "collections/maps/Geohashes",
], function (_, $, Filter, Geohashes) {
/**
* @classdesc A SpatialFilter represents a spatial constraint on the query to
@@ -132,7 +132,7 @@ define([
* a GeoBoundingBox model
* @since 2.25.0
*/
- getBounds: function (as="object") {
+ getBounds: function (as = "object") {
const coords = {
north: this.get("north"),
south: this.get("south"),
@@ -245,7 +245,7 @@ define([
if (precisions.length === 1) {
return this.createBaseFilter(
precisions,
- geohashes.getAllHashStrings()
+ geohashes.getAllHashStrings(),
).getQuery();
}
@@ -258,8 +258,8 @@ define([
filters.add(
this.createBaseFilter(
[precision],
- geohashes.getAllHashStrings(precision)
- )
+ geohashes.getAllHashStrings(precision),
+ ),
);
}
});
@@ -313,7 +313,7 @@ define([
//Insert the matchSubstring node
$(matchSubstringNode).insertBefore(
- $updatedDOM.children("value").first()
+ $updatedDOM.children("value").first(),
);
//Return the updated DOM
@@ -334,7 +334,7 @@ define([
this.removeListeners();
let df = this.defaults();
-
+
this.set({
values: df.values,
east: df.east,
@@ -343,11 +343,11 @@ define([
south: df.south,
height: df.height,
});
-
+
// Reset the listeners
this.setListeners();
},
- }
+ },
);
return SpatialFilter;
});
diff --git a/src/js/models/filters/ToggleFilter.js b/src/js/models/filters/ToggleFilter.js
index 2f58e063d..f74bcd079 100644
--- a/src/js/models/filters/ToggleFilter.js
+++ b/src/js/models/filters/ToggleFilter.js
@@ -1,143 +1,150 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/filters/Filter'],
- function($, _, Backbone, Filter) {
-
+define(["jquery", "underscore", "backbone", "models/filters/Filter"], function (
+ $,
+ _,
+ Backbone,
+ Filter,
+) {
/**
- * @class ToggleFilter
- * @classdesc A search filter whose search term is only one of two opposing choices
- * @classcategory Models/Filters
- * @constructs ToggleFilter
- * @extends Filter
- */
- var ToggleFilter = Filter.extend(
- /** @lends ToggleFilter.prototype */{
-
- type: "ToggleFilter",
-
- /**
- * The Backbone Model attributes set on this ToggleFilter
- * @type {object}
- * @extends Filter#defaultts
- * @property {string} trueLabel - A human-readable label for the first search term
- * @property {string} falseLabel - A human-readable label for the second search term
- * @property {string|boolean} trueValue - The exact search value to use for search term one
- * @property {string|boolean} falseValue - The exact search value to use for search term two
- * @property {string} nodeName - The XML node name to use when serializing this model into XML
- */
- defaults: function(){
- return _.extend(Filter.prototype.defaults(), {
- trueLabel: "On",
- trueValue: null,
- falseLabel: "Off",
- falseValue: null,
- nodeName: "toggleFilter"
- });
- },
-
- /*
- * Parses the ToggleFilter XML node into JSON
- *
- * @param {Element} xml - The XML Element that contains all the ToggleFilter elements
- * @return {JSON} - The JSON object literal to be set on the model
- */
- parse: function(xml){
-
- var modelJSON = Filter.prototype.parse.call(this, xml);
-
- //Parse the trueLabel and falseLabels
- modelJSON.trueLabel = this.parseTextNode(xml, "trueLabel");
- modelJSON.trueValue = this.parseTextNode(xml, "trueValue");
- modelJSON.falseLabel = this.parseTextNode(xml, "falseLabel");
- modelJSON.falseValue = this.parseTextNode(xml, "falseValue");
-
- //Delete any attributes from the JSON that don't exist in the XML
- if( !modelJSON.trueLabel ){
- delete modelJSON.trueLabel;
- }
- if( !modelJSON.falseLabel ){
- delete modelJSON.falseLabel;
- }
- if( !modelJSON.trueValue && modelJSON.trueValue !== false ){
- delete modelJSON.trueValue;
- }
- if( !modelJSON.falseValue && modelJSON.falseValue !== false ){
- delete modelJSON.falseValue;
- }
-
- return modelJSON;
- },
-
- /**
- * Updates the XML DOM with the new values from the model
- * @inheritdoc
- * @return {XMLElement} An updated toggleFilter XML element from a portal document
- */
- updateDOM: function(options){
-
- try{
- var objectDOM = Filter.prototype.updateDOM.call(this, options);
-
- if( (typeof options == "undefined") || (typeof options == "object" && this.get("isUIFilterType")) ){
-
- var toggleData = {
- trueValue: this.get("trueValue"),
- trueLabel: this.get("trueLabel"),
- falseValue: this.get("falseValue"),
- falseLabel: this.get("falseLabel")
- }
+ * @class ToggleFilter
+ * @classdesc A search filter whose search term is only one of two opposing choices
+ * @classcategory Models/Filters
+ * @constructs ToggleFilter
+ * @extends Filter
+ */
+ var ToggleFilter = Filter.extend(
+ /** @lends ToggleFilter.prototype */ {
+ type: "ToggleFilter",
- // Make and append new subnodes
- _.map(toggleData, function(value, nodeName){
-
- // Remove the node if it exists in the DOM already
- $(objectDOM).find(nodeName).remove();
-
- // Don't serialize falsey or default values
- if((value || value === false) && value != this.defaults()[nodeName]){
+ /**
+ * The Backbone Model attributes set on this ToggleFilter
+ * @type {object}
+ * @extends Filter#defaultts
+ * @property {string} trueLabel - A human-readable label for the first search term
+ * @property {string} falseLabel - A human-readable label for the second search term
+ * @property {string|boolean} trueValue - The exact search value to use for search term one
+ * @property {string|boolean} falseValue - The exact search value to use for search term two
+ * @property {string} nodeName - The XML node name to use when serializing this model into XML
+ */
+ defaults: function () {
+ return _.extend(Filter.prototype.defaults(), {
+ trueLabel: "On",
+ trueValue: null,
+ falseLabel: "Off",
+ falseValue: null,
+ nodeName: "toggleFilter",
+ });
+ },
- var nodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
- $(nodeSerialized).text(value);
- $(objectDOM).append(nodeSerialized);
- }
+ /*
+ * Parses the ToggleFilter XML node into JSON
+ *
+ * @param {Element} xml - The XML Element that contains all the ToggleFilter elements
+ * @return {JSON} - The JSON object literal to be set on the model
+ */
+ parse: function (xml) {
+ var modelJSON = Filter.prototype.parse.call(this, xml);
+
+ //Parse the trueLabel and falseLabels
+ modelJSON.trueLabel = this.parseTextNode(xml, "trueLabel");
+ modelJSON.trueValue = this.parseTextNode(xml, "trueValue");
+ modelJSON.falseLabel = this.parseTextNode(xml, "falseLabel");
+ modelJSON.falseValue = this.parseTextNode(xml, "falseValue");
+
+ //Delete any attributes from the JSON that don't exist in the XML
+ if (!modelJSON.trueLabel) {
+ delete modelJSON.trueLabel;
+ }
+ if (!modelJSON.falseLabel) {
+ delete modelJSON.falseLabel;
+ }
+ if (!modelJSON.trueValue && modelJSON.trueValue !== false) {
+ delete modelJSON.trueValue;
+ }
+ if (!modelJSON.falseValue && modelJSON.falseValue !== false) {
+ delete modelJSON.falseValue;
+ }
- }, this);
+ return modelJSON;
+ },
- //Move the filterOptions node to the end of the filter node
- /* var filterOptionsNode = $(objectDOM).find("filterOptions");
+ /**
+ * Updates the XML DOM with the new values from the model
+ * @inheritdoc
+ * @return {XMLElement} An updated toggleFilter XML element from a portal document
+ */
+ updateDOM: function (options) {
+ try {
+ var objectDOM = Filter.prototype.updateDOM.call(this, options);
+
+ if (
+ typeof options == "undefined" ||
+ (typeof options == "object" && this.get("isUIFilterType"))
+ ) {
+ var toggleData = {
+ trueValue: this.get("trueValue"),
+ trueLabel: this.get("trueLabel"),
+ falseValue: this.get("falseValue"),
+ falseLabel: this.get("falseLabel"),
+ };
+
+ // Make and append new subnodes
+ _.map(
+ toggleData,
+ function (value, nodeName) {
+ // Remove the node if it exists in the DOM already
+ $(objectDOM).find(nodeName).remove();
+
+ // Don't serialize falsey or default values
+ if (
+ (value || value === false) &&
+ value != this.defaults()[nodeName]
+ ) {
+ var nodeSerialized =
+ objectDOM.ownerDocument.createElement(nodeName);
+ $(nodeSerialized).text(value);
+ $(objectDOM).append(nodeSerialized);
+ }
+ },
+ this,
+ );
+
+ //Move the filterOptions node to the end of the filter node
+ /* var filterOptionsNode = $(objectDOM).find("filterOptions");
filterOptionsNode.detach();
$(objectDOM).append(filterOptionsNode);*/
+ }
+ //For collection definitions, serialize the filter differently
+ else {
+ //Remove the filterOptions
+ $(objectDOM).find("filterOptions").remove();
- }
- //For collection definitions, serialize the filter differently
- else{
- //Remove the filterOptions
- $(objectDOM).find("filterOptions").remove();
+ //Change the root element into a element
+ var newFilterEl = objectDOM.ownerDocument.createElement("filter");
+ $(newFilterEl).html($(objectDOM).children());
- //Change the root element into a element
- var newFilterEl = objectDOM.ownerDocument.createElement("filter");
- $(newFilterEl).html( $(objectDOM).children() );
+ //Return this node
+ return newFilterEl;
+ }
- //Return this node
- return newFilterEl;
+ return objectDOM;
+ } catch (e) {
+ //If there's an error, return the original DOM or an empty string
+ console.log(
+ "error updating the toggle filter object DOM, returning un-updated object DOM instead. Error message: " +
+ e,
+ );
+ return this.get("objectDOM") || "";
}
-
- return objectDOM;
- }
- //If there's an error, return the original DOM or an empty string
- catch(e){
- console.log("error updating the toggle filter object DOM, returning un-updated object DOM instead. Error message: " + e);
- return this.get("objectDOM") || "";
- }
},
-
+
/**
- * Checks if the values set on this model are valid and expected
- * @return {object} - Returns a literal object with the invalid attributes and their
- * corresponding error message
- */
+ * Checks if the values set on this model are valid and expected
+ * @return {object} - Returns a literal object with the invalid attributes and their
+ * corresponding error message
+ */
validate: function () {
try {
-
// Validate most of the ToggleFilter attributes using the parent validate
// function
var errors = Filter.prototype.validate.call(this);
@@ -150,32 +157,34 @@ define(['jquery', 'underscore', 'backbone', 'models/filters/Filter'],
// Delete error messages for the attributes that are going to be validated
// specially for the ToggleFilter
- ["trueLabel", "trueValue", "falseLabel", "falseValue"].forEach(function (attr) {
- delete errors[attr]
- });
+ ["trueLabel", "trueValue", "falseLabel", "falseValue"].forEach(
+ function (attr) {
+ delete errors[attr];
+ },
+ );
// At least one trueValue required
- var trueValue = this.get("trueValue")
+ var trueValue = this.get("trueValue");
if (!trueValue) {
- errors.trueValue = "The filter requires a term to search for when the toggle is on"
+ errors.trueValue =
+ "The filter requires a term to search for when the toggle is on";
}
// Return the errors, if there are any
- if (Object.keys(errors).length)
- return errors;
+ if (Object.keys(errors).length) return errors;
else {
return;
}
- }
- catch (error) {
+ } catch (error) {
console.log(
- 'There was an error validating a ToggleFilter' +
- '. Error details: ' + error
+ "There was an error validating a ToggleFilter" +
+ ". Error details: " +
+ error,
);
}
},
-
- });
+ },
+ );
return ToggleFilter;
});
diff --git a/src/js/models/formats/ObjectFormat.js b/src/js/models/formats/ObjectFormat.js
index e0da89125..e4a33814d 100644
--- a/src/js/models/formats/ObjectFormat.js
+++ b/src/js/models/formats/ObjectFormat.js
@@ -1,50 +1,49 @@
/* global define */
"use strict";
-define(['jquery', 'underscore', 'backbone'], function($, _, Backbone) {
-
- /**
- * @class ObjectFormat
- * @classdesc An ObjectFormat represents a V2 DataONE object format
- * See https://purl.dataone.org/architecture/apis/Types2.html#v2_0.Types.ObjectFormat
- * @classcategory Models/Formats
- * @extends Backbone.Model
- */
- var ObjectFormat = Backbone.Model.extend(
- /** @lends ObjectFormat.prototype */{
-
- /* The default object format fields */
- defaults: function() {
- return {
- formatId: null,
- formatName: null,
- formatType: null,
- mediaType: null,
- extension: null
- };
- },
-
- /* Constructs a new instance */
- initialize: function(attrs, options) {
- },
-
- /*
- * The constructed URL of the model
- * (/cn/v2/formats/{formatId})
- */
- url: function() {
- if( ! this.get("formatId") ) return "";
-
- return MetacatUI.appModel.get("formatsServiceUrl") +
- (encodeURIComponent(this.get("formatId")));
- },
-
- /* No op - Formats are read only */
- save: function() {
-
- return false;
- }
- });
-
- return ObjectFormat;
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ /**
+ * @class ObjectFormat
+ * @classdesc An ObjectFormat represents a V2 DataONE object format
+ * See https://purl.dataone.org/architecture/apis/Types2.html#v2_0.Types.ObjectFormat
+ * @classcategory Models/Formats
+ * @extends Backbone.Model
+ */
+ var ObjectFormat = Backbone.Model.extend(
+ /** @lends ObjectFormat.prototype */ {
+ /* The default object format fields */
+ defaults: function () {
+ return {
+ formatId: null,
+ formatName: null,
+ formatType: null,
+ mediaType: null,
+ extension: null,
+ };
+ },
+
+ /* Constructs a new instance */
+ initialize: function (attrs, options) {},
+
+ /*
+ * The constructed URL of the model
+ * (/cn/v2/formats/{formatId})
+ */
+ url: function () {
+ if (!this.get("formatId")) return "";
+
+ return (
+ MetacatUI.appModel.get("formatsServiceUrl") +
+ encodeURIComponent(this.get("formatId"))
+ );
+ },
+
+ /* No op - Formats are read only */
+ save: function () {
+ return false;
+ },
+ },
+ );
+
+ return ObjectFormat;
});
diff --git a/src/js/models/geocoder/GeocodedLocation.js b/src/js/models/geocoder/GeocodedLocation.js
index b3a0c8b58..f4ae4f291 100644
--- a/src/js/models/geocoder/GeocodedLocation.js
+++ b/src/js/models/geocoder/GeocodedLocation.js
@@ -1,47 +1,52 @@
-'use strict';
+"use strict";
-define(
- ['backbone', 'models/maps/GeoBoundingBox'],
- (Backbone, GeoBoundingBox) => {
- /**
- * @class GeocodedLocation
- * @classdes GeocodedLocation is the representation of a place that has been
- * geocoded to provide latitude and longitude bounding coordinates for
- * navigating to on a map.
- * @classcategory Models/Geocoder
- * @since 2.28.0
- * @extends Backbone.Model
- */
- const GeocodedLocation = Backbone.Model.extend(
- /** @lends GeocodedLocation.prototype */{
- /**
- * Overrides the default Backbone.Model.defaults() function to specify
- * default attributes.
- * @name GeocodedLocation#defaults
- * @type {Object}
- * @property {GeoBoundingBox} box Bounding box representing this location
- * on a map.
- * @property {string} displayName A name that can be displayed to the user
- * representing this location.
- */
- defaults() {
- return {
- box: new GeoBoundingBox,
- displayName: '',
- };
- },
+define(["backbone", "models/maps/GeoBoundingBox"], (
+ Backbone,
+ GeoBoundingBox,
+) => {
+ /**
+ * @class GeocodedLocation
+ * @classdes GeocodedLocation is the representation of a place that has been
+ * geocoded to provide latitude and longitude bounding coordinates for
+ * navigating to on a map.
+ * @classcategory Models/Geocoder
+ * @since 2.28.0
+ * @extends Backbone.Model
+ */
+ const GeocodedLocation = Backbone.Model.extend(
+ /** @lends GeocodedLocation.prototype */ {
+ /**
+ * Overrides the default Backbone.Model.defaults() function to specify
+ * default attributes.
+ * @name GeocodedLocation#defaults
+ * @type {Object}
+ * @property {GeoBoundingBox} box Bounding box representing this location
+ * on a map.
+ * @property {string} displayName A name that can be displayed to the user
+ * representing this location.
+ */
+ defaults() {
+ return {
+ box: new GeoBoundingBox(),
+ displayName: "",
+ };
+ },
- /**
- * @typedef {Object} GeocodedLocationOptions
- * @property {Object} box An object representing a boundary around a
- * location on a map.
- * @property {string} displayName A display name for the location.
- */
- initialize({ box: { north, south, east, west } = {}, displayName = '' } = {}) {
- this.set('box', new GeoBoundingBox({ north, south, east, west }));
- this.set('displayName', displayName);
- },
- });
+ /**
+ * @typedef {Object} GeocodedLocationOptions
+ * @property {Object} box An object representing a boundary around a
+ * location on a map.
+ * @property {string} displayName A display name for the location.
+ */
+ initialize({
+ box: { north, south, east, west } = {},
+ displayName = "",
+ } = {}) {
+ this.set("box", new GeoBoundingBox({ north, south, east, west }));
+ this.set("displayName", displayName);
+ },
+ },
+ );
- return GeocodedLocation;
- });
+ return GeocodedLocation;
+});
diff --git a/src/js/models/geocoder/GeocoderSearch.js b/src/js/models/geocoder/GeocoderSearch.js
index 191ad1be1..1bd946e08 100644
--- a/src/js/models/geocoder/GeocoderSearch.js
+++ b/src/js/models/geocoder/GeocoderSearch.js
@@ -1,53 +1,51 @@
-'use strict';
+"use strict";
-define(
- [
- 'models/geocoder/GoogleMapsGeocoder',
- 'models/geocoder/GoogleMapsAutocompleter',
- ],
- (GoogleMapsGeocoder, GoogleMapsAutocompleter) => {
+define([
+ "models/geocoder/GoogleMapsGeocoder",
+ "models/geocoder/GoogleMapsAutocompleter",
+], (GoogleMapsGeocoder, GoogleMapsAutocompleter) => {
+ /**
+ * GeocoderSearch interfaces with various geocoding and location
+ * searching services.
+ * @classcategory Models/Geocoder
+ * @since 2.28.0
+ */
+ class GeocoderSearch {
/**
- * GeocoderSearch interfaces with various geocoding and location
- * searching services.
- * @classcategory Models/Geocoder
- * @since 2.28.0
+ * GoogleMapsAutocompleter model for interacting with Google Maps Places
+ * Autocomplete APIs.
*/
- class GeocoderSearch {
- /**
- * GoogleMapsAutocompleter model for interacting with Google Maps Places
- * Autocomplete APIs.
- */
- googleMapsAutocompleter = new GoogleMapsAutocompleter();
+ googleMapsAutocompleter = new GoogleMapsAutocompleter();
- /**
- * GoogleMapsGeocoder for interacting with Google Maps Geocoder APIs.
- */
- googleMapsGeocoder = new GoogleMapsGeocoder();
+ /**
+ * GoogleMapsGeocoder for interacting with Google Maps Geocoder APIs.
+ */
+ googleMapsGeocoder = new GoogleMapsGeocoder();
- /**
- * Convert a Google Maps Place ID into a list geocoded objects that can be
- * displayed in the map widget.
- * @param {string} newQuery - The user's input search query.
- * @returns {Prediction[]} An array of places that could be the result the
- * user is looking for. Most often this comes in five or less results.
- */
- async autocomplete(newQuery) {
- return this.googleMapsAutocompleter.autocomplete(newQuery);
- }
+ /**
+ * Convert a Google Maps Place ID into a list geocoded objects that can be
+ * displayed in the map widget.
+ * @param {string} newQuery - The user's input search query.
+ * @returns {Prediction[]} An array of places that could be the result the
+ * user is looking for. Most often this comes in five or less results.
+ */
+ async autocomplete(newQuery) {
+ return this.googleMapsAutocompleter.autocomplete(newQuery);
+ }
- /**
- * Convert a Google Maps Place ID into a list geocoded objects that can be
- * displayed in the map widget.
- * @param {Prediction} prediction An autocomplete prediction that includes
- * a unique identifier for geocoding.
- * @returns {GeocodedLocation[]} An array of locations with an associated
- * bounding box. According to Google Maps API this should most often be a
- * single value, but could potentially be many.
- */
- async geocode(prediction) {
- return this.googleMapsGeocoder.geocode(prediction);
- }
+ /**
+ * Convert a Google Maps Place ID into a list geocoded objects that can be
+ * displayed in the map widget.
+ * @param {Prediction} prediction An autocomplete prediction that includes
+ * a unique identifier for geocoding.
+ * @returns {GeocodedLocation[]} An array of locations with an associated
+ * bounding box. According to Google Maps API this should most often be a
+ * single value, but could potentially be many.
+ */
+ async geocode(prediction) {
+ return this.googleMapsGeocoder.geocode(prediction);
}
+ }
- return GeocoderSearch;
- });
+ return GeocoderSearch;
+});
diff --git a/src/js/models/geocoder/GoogleMapsAutocompleter.js b/src/js/models/geocoder/GoogleMapsAutocompleter.js
index db57381ee..c33b0fac6 100644
--- a/src/js/models/geocoder/GoogleMapsAutocompleter.js
+++ b/src/js/models/geocoder/GoogleMapsAutocompleter.js
@@ -1,48 +1,53 @@
-'use strict';
+"use strict";
-define(
- ['backbone', 'gmaps', 'models/geocoder/Prediction'],
- (Backbone, gmaps, Prediction) => {
+define(["backbone", "gmaps", "models/geocoder/Prediction"], (
+ Backbone,
+ gmaps,
+ Prediction,
+) => {
+ /**
+ * Integrate with the Google Maps Places Autocomplete API using the
+ * Google Maps AutocompleteService JS library.
+ * @classcategory Models/Geocoder
+ * @since 2.28.0
+ */
+ class GoogleMapsAutocompleter {
/**
- * Integrate with the Google Maps Places Autocomplete API using the
- * Google Maps AutocompleteService JS library.
- * @classcategory Models/Geocoder
- * @since 2.28.0
+ * Google Maps service for interacting with the Places Autocomplete API.
*/
- class GoogleMapsAutocompleter {
- /**
- * Google Maps service for interacting with the Places Autocomplete API.
- */
- autocompleter = new gmaps.places.AutocompleteService();
+ autocompleter = new gmaps.places.AutocompleteService();
- /**
- * Use the Google Maps Places API to get place predictions based off of a
- * user input string as the user types.
- * @param {string} input - User input to search for Google Maps places.
- * @returns {Prediction[]} An array of places that could be the result the
- * user is looking for. Most often this comes in five or less results.
- */
- async autocomplete(input) {
- if (!input) return [];
- const response = await this.autocompleter.getPlacePredictions({
- input,
- });
- return this.getPredictionsFromResults(response.predictions);
- }
+ /**
+ * Use the Google Maps Places API to get place predictions based off of a
+ * user input string as the user types.
+ * @param {string} input - User input to search for Google Maps places.
+ * @returns {Prediction[]} An array of places that could be the result the
+ * user is looking for. Most often this comes in five or less results.
+ */
+ async autocomplete(input) {
+ if (!input) return [];
+ const response = await this.autocompleter.getPlacePredictions({
+ input,
+ });
+ return this.getPredictionsFromResults(response.predictions);
+ }
- /**
- * Helper function that converts a Google Maps Autocomplete API result
- * into a useable Prediction model.
- * @param {Object[]} List of Google Maps Autocomplete API results.
- * @returns {Prediction[]} List of corresponding predictions.
- */
- getPredictionsFromResults(results) {
- return results.map(result => new Prediction({
- description: result.description,
- googleMapsPlaceId: result.place_id,
- }));
- }
+ /**
+ * Helper function that converts a Google Maps Autocomplete API result
+ * into a useable Prediction model.
+ * @param {Object[]} List of Google Maps Autocomplete API results.
+ * @returns {Prediction[]} List of corresponding predictions.
+ */
+ getPredictionsFromResults(results) {
+ return results.map(
+ (result) =>
+ new Prediction({
+ description: result.description,
+ googleMapsPlaceId: result.place_id,
+ }),
+ );
}
+ }
- return GoogleMapsAutocompleter;
- });
\ No newline at end of file
+ return GoogleMapsAutocompleter;
+});
diff --git a/src/js/models/geocoder/GoogleMapsGeocoder.js b/src/js/models/geocoder/GoogleMapsGeocoder.js
index 203af567e..7f90aca5e 100644
--- a/src/js/models/geocoder/GoogleMapsGeocoder.js
+++ b/src/js/models/geocoder/GoogleMapsGeocoder.js
@@ -1,50 +1,52 @@
-'use strict';
+"use strict";
+
+define(["backbone", "gmaps", "models/geocoder/GeocodedLocation"], (
+ Backbone,
+ gmaps,
+ GeocodedLocation,
+) => {
+ /**
+ * Integrate with the Google Maps Geocoder API using the Google
+ * Maps Geocoder JS library.
+ * @classcategory Models/Geocoder
+ * @since 2.28.0
+ */
+ class GoogleMapsGeocoder {
+ /** Google Maps service for interacting with the Geocoder API. */
+ geocoder = new gmaps.Geocoder();
-define(
- ['backbone', 'gmaps', 'models/geocoder/GeocodedLocation'],
- (Backbone, gmaps, GeocodedLocation) => {
/**
- * Integrate with the Google Maps Geocoder API using the Google
- * Maps Geocoder JS library.
- * @classcategory Models/Geocoder
- * @since 2.28.0
+ * Use the Google Maps Geocoder API to convert a Google Maps Place ID into
+ * a geocoded object that includes latitude and longitude information
+ * along with a bound box for viewing the location.
+ * @param {Prediction} prediction An autocomplete prediction that includes
+ * a unique identifier for geocoding.
+ * @returns {GeocodedLocation[]} An array of locations with an associated
+ * bounding box. According to Google Maps API this should most often be a
+ * single value, but could potentially be many.
*/
- class GoogleMapsGeocoder {
- /** Google Maps service for interacting with the Geocoder API. */
- geocoder = new gmaps.Geocoder();
-
- /**
- * Use the Google Maps Geocoder API to convert a Google Maps Place ID into
- * a geocoded object that includes latitude and longitude information
- * along with a bound box for viewing the location.
- * @param {Prediction} prediction An autocomplete prediction that includes
- * a unique identifier for geocoding.
- * @returns {GeocodedLocation[]} An array of locations with an associated
- * bounding box. According to Google Maps API this should most often be a
- * single value, but could potentially be many.
- */
- async geocode(prediction) {
- const response = await this.geocoder.geocode({
- placeId: prediction.get('googleMapsPlaceId')
- });
- return this.getGeocodedLocationsFromResults(response.results);
- }
+ async geocode(prediction) {
+ const response = await this.geocoder.geocode({
+ placeId: prediction.get("googleMapsPlaceId"),
+ });
+ return this.getGeocodedLocationsFromResults(response.results);
+ }
- /**
- * Helper function that converts a Google Maps Places API result into a
- * useable GeocodedLocation model.
- * @param {Object[]} List of Google Maps Places API results.
- * @returns {GeocodedLocation[]} List of corresponding geocoded locations.
- */
- getGeocodedLocationsFromResults(results) {
- return results.map(result => {
- return new GeocodedLocation({
- box: result.geometry.viewport.toJSON(),
- displayName: result.address_components[0].long_name,
- });
+ /**
+ * Helper function that converts a Google Maps Places API result into a
+ * useable GeocodedLocation model.
+ * @param {Object[]} List of Google Maps Places API results.
+ * @returns {GeocodedLocation[]} List of corresponding geocoded locations.
+ */
+ getGeocodedLocationsFromResults(results) {
+ return results.map((result) => {
+ return new GeocodedLocation({
+ box: result.geometry.viewport.toJSON(),
+ displayName: result.address_components[0].long_name,
});
- }
+ });
}
+ }
- return GoogleMapsGeocoder;
- });
+ return GoogleMapsGeocoder;
+});
diff --git a/src/js/models/geocoder/Prediction.js b/src/js/models/geocoder/Prediction.js
index 40a46bdbc..217e046f3 100644
--- a/src/js/models/geocoder/Prediction.js
+++ b/src/js/models/geocoder/Prediction.js
@@ -1,16 +1,16 @@
-'use strict';
+"use strict";
-define(['backbone'], (Backbone) => {
+define(["backbone"], (Backbone) => {
/**
- * @class Prediction
- * @classdes Prediction represents a value returned from a location
- * autocompletion search.
- * @classcategory Models/Geocoder
- * @since 2.28.0
- * @extends Backbone.Model
- */
+ * @class Prediction
+ * @classdes Prediction represents a value returned from a location
+ * autocompletion search.
+ * @classcategory Models/Geocoder
+ * @since 2.28.0
+ * @extends Backbone.Model
+ */
const Prediction = Backbone.Model.extend(
- /** @lends Prediction.prototype */{
+ /** @lends Prediction.prototype */ {
/**
* Overrides the default Backbone.Model.defaults() function to specify
* default attributes for the Map
@@ -18,25 +18,26 @@ define(['backbone'], (Backbone) => {
* @type {Object}
* @property {string} description A user-friendly description of a Google
* Maps Place.
- * @property {string} googleMapsPlaceId Unique identifier that can be
+ * @property {string} googleMapsPlaceId Unique identifier that can be
* geocoded by the Google Maps Geocoder API.
*/
defaults() {
- return { description: '', googleMapsPlaceId: '' };
+ return { description: "", googleMapsPlaceId: "" };
},
/**
* @typedef {Object} PredictionOptions
* @property {string} description A string describing the location
- * represented by the Prediction.
+ * represented by the Prediction.
* @property {string} googleMapsPlaceId The place ID that is used to
- * uniquely identify a place in Google Maps API.
+ * uniquely identify a place in Google Maps API.
*/
- initialize({ description, googleMapsPlaceId, } = {}) {
- this.set('description', description);
- this.set('googleMapsPlaceId', googleMapsPlaceId);
+ initialize({ description, googleMapsPlaceId } = {}) {
+ this.set("description", description);
+ this.set("googleMapsPlaceId", googleMapsPlaceId);
},
- });
+ },
+ );
return Prediction;
});
diff --git a/src/js/models/maps/AssetCategory.js b/src/js/models/maps/AssetCategory.js
index f9aeb6e2e..093ca3b61 100644
--- a/src/js/models/maps/AssetCategory.js
+++ b/src/js/models/maps/AssetCategory.js
@@ -42,8 +42,8 @@ define([
*/
defaults() {
return {
- label: '',
- icon: '',
+ label: "",
+ icon: "",
expanded: false,
};
},
@@ -70,7 +70,9 @@ define([
*/
initialize(categoryConfig) {
if (!categoryConfig?.layers) {
- throw new Error("Category " + categoryConfig.label + " has empty layers.");
+ throw new Error(
+ "Category " + categoryConfig.label + " has empty layers.",
+ );
}
this.set("mapAssets", new MapAssets(categoryConfig.layers));
@@ -82,8 +84,9 @@ define([
if (IconUtilities.isSVG(categoryConfig.icon)) {
this.updateIcon(categoryConfig.icon);
} else {
- IconUtilities.fetchIcon(categoryConfig.icon)
- .then(icon => this.updateIcon(icon));
+ IconUtilities.fetchIcon(categoryConfig.icon).then((icon) =>
+ this.updateIcon(icon),
+ );
}
} catch (error) {
// Do nothing. Use the default icon instead.
@@ -112,7 +115,7 @@ define([
setMapModel(mapModel) {
this.get("mapAssets").setMapModel(mapModel);
},
- }
+ },
);
return AssetCategory;
diff --git a/src/js/models/maps/AssetColor.js b/src/js/models/maps/AssetColor.js
index 63921061c..495237a69 100644
--- a/src/js/models/maps/AssetColor.js
+++ b/src/js/models/maps/AssetColor.js
@@ -1,166 +1,158 @@
-'use strict';
+"use strict";
-define(
- [
- 'jquery',
- 'underscore',
- 'backbone'
- ],
- function (
- $,
- _,
- Backbone
- ) {
- /**
- * @classdesc An AssetColor Model represents one color in a color scale that maps to
- * attributes of a Map Asset. For vector assets (e.g. Cesium3DTileset models), the
- * color is used to conditionally color vector data in a map (or plot).
- * @classcategory Models/Maps
- * @class AssetColor
- * @name AssetColor
- * @extends Backbone.Model
- * @since 2.18.0
- * @constructor
- */
- var AssetColor = Backbone.Model.extend(
- /** @lends AssetColor.prototype */ {
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ /**
+ * @classdesc An AssetColor Model represents one color in a color scale that maps to
+ * attributes of a Map Asset. For vector assets (e.g. Cesium3DTileset models), the
+ * color is used to conditionally color vector data in a map (or plot).
+ * @classcategory Models/Maps
+ * @class AssetColor
+ * @name AssetColor
+ * @extends Backbone.Model
+ * @since 2.18.0
+ * @constructor
+ */
+ var AssetColor = Backbone.Model.extend(
+ /** @lends AssetColor.prototype */ {
+ /**
+ * The name of this type of model
+ * @type {string}
+ */
+ type: "AssetColor",
- /**
- * The name of this type of model
- * @type {string}
- */
- type: 'AssetColor',
+ /**
+ * A color to use in a map color palette, along with the value that the color
+ * represents.
+ * @typedef {Object} ColorConfig
+ * @name MapConfig#ColorConfig
+ * @property {string|number} value The value of the attribute in a MapAsset that
+ * corresponds to this color. If set to null, then this color will be the default
+ * color.
+ * @property {string} [label] A user-facing name for this attribute value,
+ * to show in map legends, etc. If not set, then the value will be displayed
+ * instead.
+ * @property {(string|AssetColor#Color)} color Either an object with 'red',
+ * 'green', 'blue' properties defining the intensity of each of the three colours
+ * with a value between 0 and 1, OR a string with a hex color code beginning with
+ * #, e.g. '#44A96A'. The {@link AssetColor} model will convert the string to an
+ * {@link AssetColor#Color} object.
+ *
+ * @example
+ * {
+ * value: 0,
+ * label: 'water',
+ * color: {
+ * red: 0,
+ * green: 0.1,
+ * blue: 1
+ * }
+ * }
+ *
+ * @example
+ * {
+ * value: 'landmark',
+ * color: '#7B44A9'
+ * }
+ */
- /**
- * A color to use in a map color palette, along with the value that the color
- * represents.
- * @typedef {Object} ColorConfig
- * @name MapConfig#ColorConfig
- * @property {string|number} value The value of the attribute in a MapAsset that
- * corresponds to this color. If set to null, then this color will be the default
- * color.
- * @property {string} [label] A user-facing name for this attribute value,
- * to show in map legends, etc. If not set, then the value will be displayed
- * instead.
- * @property {(string|AssetColor#Color)} color Either an object with 'red',
- * 'green', 'blue' properties defining the intensity of each of the three colours
- * with a value between 0 and 1, OR a string with a hex color code beginning with
- * #, e.g. '#44A96A'. The {@link AssetColor} model will convert the string to an
- * {@link AssetColor#Color} object.
- *
- * @example
- * {
- * value: 0,
- * label: 'water',
- * color: {
- * red: 0,
- * green: 0.1,
- * blue: 1
- * }
- * }
- *
- * @example
- * {
- * value: 'landmark',
- * color: '#7B44A9'
- * }
- */
+ /**
+ * An object that defines the properties of a color
+ * @typedef {Object} Color
+ * @name AssetColor#Color
+ * @property {number} [red=1] A number between 0 and 1 indicating the intensity of red
+ * in this color.
+ * @property {number} [blue=1] A number between 0 and 1 indicating the intensity of
+ * red in this color.
+ * @property {number} [green=1] A number between 0 and 1 indicating the intensity of
+ * red in this color.
+ * @property {number} [alpha=1] A number between 0 and 1 indicating the opacity of
+ * this color.
+ */
- /**
- * An object that defines the properties of a color
- * @typedef {Object} Color
- * @name AssetColor#Color
- * @property {number} [red=1] A number between 0 and 1 indicating the intensity of red
- * in this color.
- * @property {number} [blue=1] A number between 0 and 1 indicating the intensity of
- * red in this color.
- * @property {number} [green=1] A number between 0 and 1 indicating the intensity of
- * red in this color.
- * @property {number} [alpha=1] A number between 0 and 1 indicating the opacity of
- * this color.
- */
+ /**
+ * Default attributes for AssetColor models
+ * @name AssetColor#defaults
+ * @type {Object}
+ * @property {string|number} value The value of the attribute that corresponds to
+ * this color. If set to null, then this color will be the default color.
+ * @property {string} [label] A user-facing name for this attribute value,
+ * to show in map legends, etc. If not set, then the value will be displayed
+ * instead.
+ * @property {AssetColor#Color} color The red, green, and blue intensities that define the
+ * color
+ */
+ defaults: function () {
+ return {
+ value: null,
+ label: null,
+ color: {
+ red: 1,
+ blue: 1,
+ green: 1,
+ alpha: 1,
+ },
+ };
+ },
- /**
- * Default attributes for AssetColor models
- * @name AssetColor#defaults
- * @type {Object}
- * @property {string|number} value The value of the attribute that corresponds to
- * this color. If set to null, then this color will be the default color.
- * @property {string} [label] A user-facing name for this attribute value,
- * to show in map legends, etc. If not set, then the value will be displayed
- * instead.
- * @property {AssetColor#Color} color The red, green, and blue intensities that define the
- * color
- */
- defaults: function () {
- return {
- value: null,
- label: null,
- color: {
- red: 1,
- blue: 1,
- green: 1,
- alpha: 1
- }
- }
- },
-
- /**
- * Executed when a new AssetColor model is created.
- * @param {MapConfig#ColorConfig} [colorConfig] The initial values of the
- * attributes, which will be set on the model.
- */
- initialize: function (colorConfig) {
- try {
- // If the color is a hex code instead of an object with RGB values, then
- // convert it.
- if (colorConfig && colorConfig.color && typeof colorConfig.color === 'string') {
- // Assume the string is an hex color code and convert it to RGBA,
- // otherwise use the default color
- this.set('color',
- this.hexToRGBA(colorConfig.color) ||
- this.defaults().color
- )
- }
- // Set missing RGB values to 0, and alpha to 1
- let color = this.get('color');
- color.red = color.red || 0;
- color.green = color.green || 0;
- color.blue = color.blue || 0;
- if (!color.alpha && color.alpha !== 0) {
- color.alpha = 1;
- }
- this.set('color', color)
-
- }
- catch (error) {
- console.log(
- 'There was an error initializing a AssetColor model' +
- '. Error details: ' + error
+ /**
+ * Executed when a new AssetColor model is created.
+ * @param {MapConfig#ColorConfig} [colorConfig] The initial values of the
+ * attributes, which will be set on the model.
+ */
+ initialize: function (colorConfig) {
+ try {
+ // If the color is a hex code instead of an object with RGB values, then
+ // convert it.
+ if (
+ colorConfig &&
+ colorConfig.color &&
+ typeof colorConfig.color === "string"
+ ) {
+ // Assume the string is an hex color code and convert it to RGBA,
+ // otherwise use the default color
+ this.set(
+ "color",
+ this.hexToRGBA(colorConfig.color) || this.defaults().color,
);
}
- },
-
-
- /**
- * Converts an 6 to 8 digit hex color value to RGBA values between 0 and 1
- * @param {string} hex - A hex color code, e.g. '#44A96A' or '#44A96A88'
- * @return {Color} - The RGBA values of the color
- * @since 2.25.0
- */
- hexToRGBA: function (hex) {
- var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(hex);
- return result ? {
- red: parseInt(result[1], 16) / 255,
- green: parseInt(result[2], 16) / 255,
- blue: parseInt(result[3], 16) / 255,
- alpha: parseInt(result[4], 16) / 255
- } : null;
- },
-
- });
+ // Set missing RGB values to 0, and alpha to 1
+ let color = this.get("color");
+ color.red = color.red || 0;
+ color.green = color.green || 0;
+ color.blue = color.blue || 0;
+ if (!color.alpha && color.alpha !== 0) {
+ color.alpha = 1;
+ }
+ this.set("color", color);
+ } catch (error) {
+ console.log(
+ "There was an error initializing a AssetColor model" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
- return AssetColor;
+ /**
+ * Converts an 6 to 8 digit hex color value to RGBA values between 0 and 1
+ * @param {string} hex - A hex color code, e.g. '#44A96A' or '#44A96A88'
+ * @return {Color} - The RGBA values of the color
+ * @since 2.25.0
+ */
+ hexToRGBA: function (hex) {
+ var result =
+ /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(hex);
+ return result
+ ? {
+ red: parseInt(result[1], 16) / 255,
+ green: parseInt(result[2], 16) / 255,
+ blue: parseInt(result[3], 16) / 255,
+ alpha: parseInt(result[4], 16) / 255,
+ }
+ : null;
+ },
+ },
+ );
- }
-);
+ return AssetColor;
+});
diff --git a/src/js/models/maps/AssetColorPalette.js b/src/js/models/maps/AssetColorPalette.js
index 91f382f87..ddcb24c63 100644
--- a/src/js/models/maps/AssetColorPalette.js
+++ b/src/js/models/maps/AssetColorPalette.js
@@ -140,7 +140,7 @@ define([
console.log(
"There was an error initializing a AssetColorPalette model" +
". Error details: " +
- error
+ error,
);
}
},
@@ -284,8 +284,7 @@ define([
getDefaultColor: function () {
return this.get("colors").getDefaultColor().get("color");
},
-
- }
+ },
);
return AssetColorPalette;
diff --git a/src/js/models/maps/Feature.js b/src/js/models/maps/Feature.js
index d02dec4ff..8fe30d096 100644
--- a/src/js/models/maps/Feature.js
+++ b/src/js/models/maps/Feature.js
@@ -62,7 +62,7 @@ define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
} catch (error) {
console.log(
"Failed to check if a Feature model is the default.",
- error
+ error,
);
}
},
@@ -83,7 +83,7 @@ define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
console.log(
"There was an error reset a Feature model to default" +
". Error details: " +
- error
+ error,
);
}
},
@@ -139,7 +139,7 @@ define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
if (input.featureObject && options.assets) {
const attrs = this.attrsFromFeatureObject(
input.featureObject,
- options.assets
+ options.assets,
);
input = Object.assign({}, input, attrs);
}
@@ -149,7 +149,7 @@ define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
console.log("Failed to parse a Feature model", error);
}
},
- }
+ },
);
return Feature;
diff --git a/src/js/models/maps/GeoBoundingBox.js b/src/js/models/maps/GeoBoundingBox.js
index 599ca3032..c3144ffe2 100644
--- a/src/js/models/maps/GeoBoundingBox.js
+++ b/src/js/models/maps/GeoBoundingBox.js
@@ -175,7 +175,7 @@ define(["backbone"], function (Backbone) {
);
}
},
- }
+ },
);
return GeoBoundingBox;
diff --git a/src/js/models/maps/GeoPoint.js b/src/js/models/maps/GeoPoint.js
index 9853173a3..972539d95 100644
--- a/src/js/models/maps/GeoPoint.js
+++ b/src/js/models/maps/GeoPoint.js
@@ -2,7 +2,7 @@
define(["backbone", "models/maps/GeoUtilities"], function (
Backbone,
- GeoUtilities
+ GeoUtilities,
) {
// Regular expression matching a string that contains two numbers optionally separated by a comma.
const FLOATS_REGEX = /[+-]?[0-9]*[.]?[0-9]+/g;
@@ -49,24 +49,28 @@ define(["backbone", "models/maps/GeoUtilities"], function (
},
/**
- * Parse a string according to a regular expression.
+ * Parse a string according to a regular expression.
* @param {string} value A user-entered value for parsing into a latiude
* and longitude pair.
* @throws An error indicating that more than two numbers have been
* entered.
- * @returns {Object} Latitude and longitude information for creating a
+ * @returns {Object} Latitude and longitude information for creating a
* GeoPoint.
*/
parse(value) {
- if (typeof value !== 'string') {
+ if (typeof value !== "string") {
return {};
}
const matches = value?.match(FLOATS_REGEX);
- if (matches?.length !== 2 || isNaN(matches[0]) || isNaN(matches[1])
- || !GeoPoint.couldBeLatLong(value)) {
+ if (
+ matches?.length !== 2 ||
+ isNaN(matches[0]) ||
+ isNaN(matches[1]) ||
+ !GeoPoint.couldBeLatLong(value)
+ ) {
throw new Error(
- 'Try entering a search query with two numerical values representing a latitude and longitude (e.g. 64.84, -147.72).'
+ "Try entering a search query with two numerical values representing a latitude and longitude (e.g. 64.84, -147.72).",
);
}
@@ -154,7 +158,7 @@ define(["backbone", "models/maps/GeoUtilities"], function (
if (attrs.longitude < -180 || attrs.longitude > 180) {
return {
- longitude: "Invalid longitude. Must be between -180 and 180."
+ longitude: "Invalid longitude. Must be between -180 and 180.",
};
}
@@ -175,13 +179,13 @@ define(["backbone", "models/maps/GeoUtilities"], function (
* be in a lat,long pair.
*/
couldBeLatLong(value) {
- if (typeof value !== 'string') {
+ if (typeof value !== "string") {
return false;
}
return value?.match(NON_LAT_LONG_CHARS_REGEX) == null;
},
- }
+ },
);
return GeoPoint;
diff --git a/src/js/models/maps/GeoScale.js b/src/js/models/maps/GeoScale.js
index 8ce40ad14..028d5b345 100644
--- a/src/js/models/maps/GeoScale.js
+++ b/src/js/models/maps/GeoScale.js
@@ -55,7 +55,7 @@ define(["backbone"], function (Backbone) {
return "Invalid meters scale. Must be greater than 0.";
}
},
- }
+ },
);
return GeoScale;
diff --git a/src/js/models/maps/GeoUtilities.js b/src/js/models/maps/GeoUtilities.js
index 4b1123af5..fca37976b 100644
--- a/src/js/models/maps/GeoUtilities.js
+++ b/src/js/models/maps/GeoUtilities.js
@@ -48,7 +48,7 @@ define(["backbone"], function (Backbone) {
return [x, y, z];
},
- }
+ },
);
return GeoUtilities;
diff --git a/src/js/models/maps/Geohash.js b/src/js/models/maps/Geohash.js
index cb213dc65..dc223fbcd 100644
--- a/src/js/models/maps/Geohash.js
+++ b/src/js/models/maps/Geohash.js
@@ -79,7 +79,7 @@ define([
// Must use prototype.get to avoid infinite loop
const properties = Backbone.Model.prototype.get.call(
this,
- "properties"
+ "properties",
);
return properties?.hasOwnProperty(key);
},
@@ -160,7 +160,7 @@ define([
new Geohash({
hashString: hashString + i.toString(32),
properties: keepProperties ? this.get("properties") : {},
- })
+ }),
);
}
return geohashes;
@@ -300,7 +300,7 @@ define([
const id = properties["hashString"];
const ecefCoordinates = rectangle.map((coord) =>
- this.geodeticToECEF(coord)
+ this.geodeticToECEF(coord),
);
const ecefPosition = this.geodeticToECEF([
point.longitude,
@@ -349,7 +349,7 @@ define([
geodeticToECEF: function (coord) {
return GeoUtilities.prototype.geodeticToECEF(coord);
},
- }
+ },
);
return Geohash;
diff --git a/src/js/models/maps/Map.js b/src/js/models/maps/Map.js
index 32fda29b4..d342acaf7 100644
--- a/src/js/models/maps/Map.js
+++ b/src/js/models/maps/Map.js
@@ -9,7 +9,8 @@ define([
"collections/maps/AssetCategories",
"models/maps/GeoPoint",
"collections/maps/viewfinder/ZoomPresets",
-], function ($,
+], function (
+ $,
_,
Backbone,
MapAssets,
@@ -65,8 +66,8 @@ define([
* home button in the toolbar.
* @property {Boolean} [showViewfinder=false] - Whether or not to show the
* viewfinder UI and viewfinder button in the toolbar. The ViewfinderView
- * requires a Google Maps API key present in the AppModel. In order to
- * work properly the Geocoding API and Places API must be enabled.
+ * requires a Google Maps API key present in the AppModel. In order to
+ * work properly the Geocoding API and Places API must be enabled.
* @property {Boolean} [toolbarOpen=false] - Whether or not the toolbar is
* open when the map is initialized. Set to false by default, so that the
* toolbar is hidden by default.
@@ -93,7 +94,7 @@ define([
* @property {ZoomPresets} [zoomPresets=null] - A Backbone.Collection of a
* predefined list of locations with an enabled list of layer IDs to be
* shown the zoom presets UI. Requires `showViewfinder` to be true as this
- * UI appears within the ViewfinderView.
+ * UI appears within the ViewfinderView.
*
* @example
* {
@@ -216,7 +217,7 @@ define([
* @property {ZoomPresets} [zoomPresets=null] - A Backbone.Collection of a
* predefined list of locations with an enabled list of layer IDs to be
* shown the zoom presets UI. Requires `showViewfinder` to be true as this
- * UI appears within the ViewfinderView.
+ * UI appears within the ViewfinderView.
*/
defaults: function () {
return {
@@ -265,7 +266,9 @@ define([
}
if (isNonEmptyArray(config.layerCategories)) {
- const assetCategories = new AssetCategories(config.layerCategories);
+ const assetCategories = new AssetCategories(
+ config.layerCategories,
+ );
assetCategories.setMapModel(this);
this.set("layerCategories", assetCategories);
this.unset("layers");
@@ -278,14 +281,20 @@ define([
this.set("terrains", new MapAssets(config.terrains));
}
- this.set('zoomPresetsCollection', new ZoomPresets({
- zoomPresetObjects: config.zoomPresets,
- allLayers: this.getAllLayers(),
- }, { parse: true }));
+ this.set(
+ "zoomPresetsCollection",
+ new ZoomPresets(
+ {
+ zoomPresetObjects: config.zoomPresets,
+ allLayers: this.getAllLayers(),
+ },
+ { parse: true },
+ ),
+ );
}
this.setUpInteractions();
} catch (error) {
- console.log('Failed to initialize a Map model.', error);
+ console.log("Failed to initialize a Map model.", error);
}
},
@@ -347,7 +356,7 @@ define([
*/
resetLayerVisibility: function () {
for (const layer of this.getAllLayers()) {
- layer.set("visible", layer.get('originalVisibility'));
+ layer.set("visible", layer.get("originalVisibility"));
}
},
@@ -389,7 +398,7 @@ define([
if (this.has("layerCategories")) {
return this.get("layerCategories")
.getMapAssets()
- .map(assets => assets.models)
+ .map((assets) => assets.models)
.flat();
} else if (this.has("layers")) {
return this.get("layers").models;
@@ -425,7 +434,7 @@ define([
// is a bug in the MapAssets collection or Backbone?
if (layers) layers.remove(asset.cid);
},
- }
+ },
);
return MapModel;
diff --git a/src/js/models/maps/MapInteraction.js b/src/js/models/maps/MapInteraction.js
index 84e08818e..5cba5459c 100644
--- a/src/js/models/maps/MapInteraction.js
+++ b/src/js/models/maps/MapInteraction.js
@@ -8,7 +8,15 @@ define([
"models/maps/GeoPoint",
"models/maps/GeoScale",
"collections/maps/MapAssets",
-], function (Backbone, Features, Feature, GeoBoundingBox, GeoPoint, GeoScale, MapAssets) {
+], function (
+ Backbone,
+ Features,
+ Feature,
+ GeoBoundingBox,
+ GeoPoint,
+ GeoScale,
+ MapAssets,
+) {
/**
* @class MapInteraction
* @classdesc The Map Interaction stores information about user interaction
@@ -39,7 +47,7 @@ define([
* user last clicked.
* @property {GeoScale} scale - The current scale of the map in
* pixels:meters, i.e. The number of pixels on the screen that equal the
- * number of meters on the map/globe. Updated by the map widget.
+ * number of meters on the map/globe. Updated by the map widget.
* @property {GeoBoundingBox} viewExtent - The extent of the currently
* visible area in the map widget. Updated by the map widget.
* @property {Features} hoveredFeatures - The feature that the mouse is
@@ -121,7 +129,7 @@ define([
listener.stopListening();
listener.destroy();
}
- }
+ },
);
},
@@ -146,11 +154,11 @@ define([
listener.listenToOnce(model, "cameraChanged", function () {
listener.stopListening(model, "moveEnd");
model.trigger("moveStartAndChanged");
- })
+ });
listener.listenToOnce(model, "moveEnd", function () {
listener.stopListening(model, "cameraChanged");
- })
- })
+ });
+ });
},
/**
@@ -201,12 +209,12 @@ define([
* properties.
* @returns {GeoPoint} The corresponding position as a GeoPoint model.
*/
- setPosition: function(attributeName, position) {
+ setPosition: function (attributeName, position) {
let point = this.get(attributeName);
if (!point) {
point = new GeoPoint();
this.set(attributeName, point);
- }
+ }
point.set(position);
return point;
},
@@ -217,17 +225,17 @@ define([
* properties.
* @returns {GeoPoint} The clicked position as a GeoPoint model.
*/
- setClickedPosition: function(position) {
+ setClickedPosition: function (position) {
return this.setPosition("clickedPosition", position);
},
/**
- * Sets the position of the mouse on the map.
- * @param {Object} position - An object with 'longitude' and 'latitude'
- * properties.
- * @returns {GeoPoint} The mouse position as a GeoPoint model.
- */
- setMousePosition: function(position) {
+ * Sets the position of the mouse on the map.
+ * @param {Object} position - An object with 'longitude' and 'latitude'
+ * properties.
+ * @returns {GeoPoint} The mouse position as a GeoPoint model.
+ */
+ setMousePosition: function (position) {
return this.setPosition("mousePosition", position);
},
@@ -337,10 +345,14 @@ define([
return;
}
- const assets = _.reduce(this.get("mapModel")?.getLayerGroups(), (mapAssets, layers) => {
- mapAssets.add(layers.models);
- return mapAssets;
- }, new MapAssets());
+ const assets = _.reduce(
+ this.get("mapModel")?.getLayerGroups(),
+ (mapAssets, layers) => {
+ mapAssets.add(layers.models);
+ return mapAssets;
+ },
+ new MapAssets(),
+ );
const newAttrs = features.map((f) => ({ featureObject: f }));
@@ -351,7 +363,7 @@ define([
console.log("Failed to select a Feature in a Map model.", e);
}
},
- }
+ },
);
return MapInteraction;
diff --git a/src/js/models/maps/VectorFilter.js b/src/js/models/maps/VectorFilter.js
index fb6096d0d..1ba3935eb 100644
--- a/src/js/models/maps/VectorFilter.js
+++ b/src/js/models/maps/VectorFilter.js
@@ -1,235 +1,222 @@
-'use strict';
-
-define(
- [
- 'jquery',
- 'underscore',
- 'backbone'
- ],
- function (
- $,
- _,
- Backbone
- ) {
- /**
- * @classdesc A VectorFilter Model represents one condition used to show or hide
- * specific features of a vector layer on a map. The filter defines rules used to show
- * features conditionally based on properties of the feature. For example, it could
- * specify hiding all vectors for an asset that have an area greater than 10 km2.
- * @classcategory Models/Maps
- * @class VectorFilter
- * @name VectorFilter
- * @extends Backbone.Model
- * @since 2.18.0
- * @constructor
- */
- var VectorFilter = Backbone.Model.extend(
- /** @lends VectorFilter.prototype */ {
-
- /**
- * The name of this type of model
- * @type {string}
- */
- type: 'VectorFilter',
-
- /**
- * A VectorFilterConfig specifies conditions under which specific features of a
- * vector layer on a map should be visible. The filter defines rules used to show
- * features conditionally based on properties of the feature. For example, it
- * could specify hiding all vectors for an asset that have an area greater than
- * 10km2. This configuration is passed to the {@link VectorFilter} model.
- * @typedef {Object} VectorFilterConfig
- * @name MapConfig#VectorFilterConfig
- * @property {('categorical'|'numeric')} filterType If categorical, then a feature
- * will be visible when its property value exactly matches one of those listed in
- * the values attribute. If numeric, then a feature will be visible when its
- * property value is between the min and max.
- * @property {string} property The property (attribute) of the {@link MapAsset}
- * feature to filter on.
- * @property {(string[]|number[])} values Only used for categorical filters. If
- * the property matches one of the values listed, the feature will be displayed.
- * If the filter type is categorical and no values are set, then features will not
- * be filtered on this property.
- * @property {number} max Only used for numeric filters. The property's value must
- * be less than the value set here for the feature to be visible. If the filter
- * type is numeric, and max is set, then the max is infinite.
- * @property {number} min Only used for numeric filters. The property's value must
- * be greater than the value set here for the feature to be visible. If the filter
- * type is numeric, and min is set, then the min is minus infinity.
- *
- * @example
- * // Only show vectors with an 'area' property set to less than 10
- * {
- * filterType: 'numeric'
- * property: 'area'
- * max: 10
- * }
- *
- * @example
- * // Show only features that have the 'boreal' or 'tropical' property set on their 'forestType' attribute
- * {
- * filterType: 'categorical'
- * property: 'forestType'
- * values: ['boreal', 'tropical']
- * }
- */
-
- /**
- * Default attributes for VectorFilter models
- * @name VectorFilter#defaults
- * @type {Object}
- * @property {('categorical'|'numeric')} [filterType='categorical'] If
- * categorical, then a feature will be visible when its property value exactly
- * matches one of those listed in the values attribute. If numerical, then a
- * feature will be visible when its property value is between the min and max.
- * @property {string} property The property (attribute) of the feature to filter
- * on.
- * @property {(string[]|number[])} values Only used for categorical filters. If
- * the property matches one of the values listed, the feature will be displayed.
- * If the filter type is categorical and no values are set, then features will not
- * be filtered on this property.
- * @property {number} max Only used for numeric filters. The property's value must
- * be less than the value set here for the feature to be visible. If the filter
- * type is numeric, and max is set, then the max is infinite.
- * @property {number} min Only used for numeric filters. The property's value must
- * be greater than the value set here for the feature to be visible. If the filter
- * type is numeric, and min is set, then the min is minus infinity.
- *
- */
- defaults: function () {
- return {
- filterType: 'categorical',
- property: null,
- values: [],
- max: null,
- min: null
+"use strict";
+
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ /**
+ * @classdesc A VectorFilter Model represents one condition used to show or hide
+ * specific features of a vector layer on a map. The filter defines rules used to show
+ * features conditionally based on properties of the feature. For example, it could
+ * specify hiding all vectors for an asset that have an area greater than 10 km2.
+ * @classcategory Models/Maps
+ * @class VectorFilter
+ * @name VectorFilter
+ * @extends Backbone.Model
+ * @since 2.18.0
+ * @constructor
+ */
+ var VectorFilter = Backbone.Model.extend(
+ /** @lends VectorFilter.prototype */ {
+ /**
+ * The name of this type of model
+ * @type {string}
+ */
+ type: "VectorFilter",
+
+ /**
+ * A VectorFilterConfig specifies conditions under which specific features of a
+ * vector layer on a map should be visible. The filter defines rules used to show
+ * features conditionally based on properties of the feature. For example, it
+ * could specify hiding all vectors for an asset that have an area greater than
+ * 10km2. This configuration is passed to the {@link VectorFilter} model.
+ * @typedef {Object} VectorFilterConfig
+ * @name MapConfig#VectorFilterConfig
+ * @property {('categorical'|'numeric')} filterType If categorical, then a feature
+ * will be visible when its property value exactly matches one of those listed in
+ * the values attribute. If numeric, then a feature will be visible when its
+ * property value is between the min and max.
+ * @property {string} property The property (attribute) of the {@link MapAsset}
+ * feature to filter on.
+ * @property {(string[]|number[])} values Only used for categorical filters. If
+ * the property matches one of the values listed, the feature will be displayed.
+ * If the filter type is categorical and no values are set, then features will not
+ * be filtered on this property.
+ * @property {number} max Only used for numeric filters. The property's value must
+ * be less than the value set here for the feature to be visible. If the filter
+ * type is numeric, and max is set, then the max is infinite.
+ * @property {number} min Only used for numeric filters. The property's value must
+ * be greater than the value set here for the feature to be visible. If the filter
+ * type is numeric, and min is set, then the min is minus infinity.
+ *
+ * @example
+ * // Only show vectors with an 'area' property set to less than 10
+ * {
+ * filterType: 'numeric'
+ * property: 'area'
+ * max: 10
+ * }
+ *
+ * @example
+ * // Show only features that have the 'boreal' or 'tropical' property set on their 'forestType' attribute
+ * {
+ * filterType: 'categorical'
+ * property: 'forestType'
+ * values: ['boreal', 'tropical']
+ * }
+ */
+
+ /**
+ * Default attributes for VectorFilter models
+ * @name VectorFilter#defaults
+ * @type {Object}
+ * @property {('categorical'|'numeric')} [filterType='categorical'] If
+ * categorical, then a feature will be visible when its property value exactly
+ * matches one of those listed in the values attribute. If numerical, then a
+ * feature will be visible when its property value is between the min and max.
+ * @property {string} property The property (attribute) of the feature to filter
+ * on.
+ * @property {(string[]|number[])} values Only used for categorical filters. If
+ * the property matches one of the values listed, the feature will be displayed.
+ * If the filter type is categorical and no values are set, then features will not
+ * be filtered on this property.
+ * @property {number} max Only used for numeric filters. The property's value must
+ * be less than the value set here for the feature to be visible. If the filter
+ * type is numeric, and max is set, then the max is infinite.
+ * @property {number} min Only used for numeric filters. The property's value must
+ * be greater than the value set here for the feature to be visible. If the filter
+ * type is numeric, and min is set, then the min is minus infinity.
+ *
+ */
+ defaults: function () {
+ return {
+ filterType: "categorical",
+ property: null,
+ values: [],
+ max: null,
+ min: null,
+ };
+ },
+
+ /**
+ * This function checks if a feature is visible based on the filter's rules.
+ * @param {Object} properties The properties of the feature to be filtered. (See
+ * the 'properties' attribute of {@link Feature#defaults}.)
+ * @returns {boolean} Returns true if the feature properties pass this filter
+ */
+ featureIsVisible: function (properties) {
+ try {
+ if (!properties) {
+ properties = {};
}
- },
-
- /**
- * This function checks if a feature is visible based on the filter's rules.
- * @param {Object} properties The properties of the feature to be filtered. (See
- * the 'properties' attribute of {@link Feature#defaults}.)
- * @returns {boolean} Returns true if the feature properties pass this filter
- */
- featureIsVisible: function (properties) {
- try {
- if (!properties) {
- properties = {};
+ var visible = true;
+ if (this.get("filterType") === "categorical") {
+ var values = this.get("values");
+ if (values.length > 0) {
+ visible = _.contains(values, properties[this.get("property")]);
}
- var visible = true;
- if (this.get('filterType') === 'categorical') {
- var values = this.get('values');
- if (values.length > 0) {
- visible = _.contains(values, properties[this.get('property')]);
- }
- } else if (this.get('filterType') === 'numeric') {
- var max = this.get('max');
- var min = this.get('min');
- if (max !== null) {
- visible = properties[this.get('property')] < max;
- }
- if (min !== null) {
- visible = properties[this.get('property')] > min && visible;
- }
+ } else if (this.get("filterType") === "numeric") {
+ var max = this.get("max");
+ var min = this.get("min");
+ if (max !== null) {
+ visible = properties[this.get("property")] < max;
+ }
+ if (min !== null) {
+ visible = properties[this.get("property")] > min && visible;
}
- return visible;
- }
- catch (error) {
- console.log(
- 'There was an error checking feature visibility in a VectorFilter' +
- '. Error details: ' + error
- );
}
- },
-
- // /**
- // * Executed when a new VectorFilter model is created.
- // * @param {Object} [attributes] The initial values of the attributes, which will
- // * be set on the model.
- // * @param {Object} [options] Options for the initialize function.
- // */
- // initialize: function (attributes, options) {
- // try {
-
- // }
- // catch (error) {
- // console.log(
- // 'There was an error initializing a VectorFilter model' +
- // '. Error details: ' + error
- // );
- // }
- // },
-
- // /**
- // * Parses the given input into a JSON object to be set on the model.
- // *
- // * @param {TODO} input - The raw response object
- // * @return {TODO} - The JSON object of all the VectorFilter attributes
- // */
- // parse: function (input) {
-
- // try {
-
- // var modelJSON = {};
-
- // return modelJSON
-
- // }
- // catch (error) {
- // console.log(
- // 'There was an error parsing a VectorFilter model' +
- // '. Error details: ' + error
- // );
- // }
-
- // },
-
- // /**
- // * Overrides the default Backbone.Model.validate.function() to check if this if
- // * the values set on this model are valid.
- // *
- // * @param {Object} [attrs] - A literal object of model attributes to validate.
- // * @param {Object} [options] - A literal object of options for this validation
- // * process
- // *
- // * @return {Object} - Returns a literal object with the invalid attributes and
- // * their corresponding error message, if there are any. If there are no errors,
- // * returns nothing.
- // */
- // validate: function (attrs, options) {
- // try {
-
- // }
- // catch (error) {
- // console.log(
- // 'There was an error validating a VectorFilter model' +
- // '. Error details: ' + error
- // );
- // }
- // },
-
- // /**
- // * Creates a string using the values set on this model's attributes.
- // * @return {string} The VectorFilter string
- // */
- // serialize: function () {
- // try {
- // var serializedVectorFilter = '';
-
- // return serializedVectorFilter;
- // }
- // catch (error) {
- // console.log(
- // 'There was an error serializing a VectorFilter model' +
- // '. Error details: ' + error
- // );
- // }
- // },
-
- });
-
- return VectorFilter;
-
- }
-);
+ return visible;
+ } catch (error) {
+ console.log(
+ "There was an error checking feature visibility in a VectorFilter" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ // /**
+ // * Executed when a new VectorFilter model is created.
+ // * @param {Object} [attributes] The initial values of the attributes, which will
+ // * be set on the model.
+ // * @param {Object} [options] Options for the initialize function.
+ // */
+ // initialize: function (attributes, options) {
+ // try {
+
+ // }
+ // catch (error) {
+ // console.log(
+ // 'There was an error initializing a VectorFilter model' +
+ // '. Error details: ' + error
+ // );
+ // }
+ // },
+
+ // /**
+ // * Parses the given input into a JSON object to be set on the model.
+ // *
+ // * @param {TODO} input - The raw response object
+ // * @return {TODO} - The JSON object of all the VectorFilter attributes
+ // */
+ // parse: function (input) {
+
+ // try {
+
+ // var modelJSON = {};
+
+ // return modelJSON
+
+ // }
+ // catch (error) {
+ // console.log(
+ // 'There was an error parsing a VectorFilter model' +
+ // '. Error details: ' + error
+ // );
+ // }
+
+ // },
+
+ // /**
+ // * Overrides the default Backbone.Model.validate.function() to check if this if
+ // * the values set on this model are valid.
+ // *
+ // * @param {Object} [attrs] - A literal object of model attributes to validate.
+ // * @param {Object} [options] - A literal object of options for this validation
+ // * process
+ // *
+ // * @return {Object} - Returns a literal object with the invalid attributes and
+ // * their corresponding error message, if there are any. If there are no errors,
+ // * returns nothing.
+ // */
+ // validate: function (attrs, options) {
+ // try {
+
+ // }
+ // catch (error) {
+ // console.log(
+ // 'There was an error validating a VectorFilter model' +
+ // '. Error details: ' + error
+ // );
+ // }
+ // },
+
+ // /**
+ // * Creates a string using the values set on this model's attributes.
+ // * @return {string} The VectorFilter string
+ // */
+ // serialize: function () {
+ // try {
+ // var serializedVectorFilter = '';
+
+ // return serializedVectorFilter;
+ // }
+ // catch (error) {
+ // console.log(
+ // 'There was an error serializing a VectorFilter model' +
+ // '. Error details: ' + error
+ // );
+ // }
+ // },
+ },
+ );
+
+ return VectorFilter;
+});
diff --git a/src/js/models/maps/assets/Cesium3DTileset.js b/src/js/models/maps/assets/Cesium3DTileset.js
index df560048a..60e97f0cf 100644
--- a/src/js/models/maps/assets/Cesium3DTileset.js
+++ b/src/js/models/maps/assets/Cesium3DTileset.js
@@ -1,429 +1,423 @@
-'use strict';
-
-define(
- [
- 'cesium',
- 'models/maps/assets/MapAsset',
- 'models/maps/AssetColorPalette',
- 'collections/maps/VectorFilters'
- ],
- function (
- Cesium,
- MapAsset,
- AssetColorPalette,
- VectorFilters
- ) {
- /**
- * @classdesc A Cesium3DTileset Model is a special type of vector layer that can be used in
- * Cesium maps, and that follows the 3d-tiles specification. See
- * {@link https://github.com/CesiumGS/3d-tiles}
- * @classcategory Models/Maps/Assets
- * @class Cesium3DTileset
- * @name Cesium3DTileset
- * @extends MapAsset
- * @since 2.18.0
- * @constructor
- */
- var Cesium3DTileset = MapAsset.extend(
- /** @lends Cesium3DTileset.prototype */ {
-
- /**
- * The name of this type of model
- * @type {string}
- */
- type: 'Cesium3DTileset',
-
- /**
- * Options that are supported for creating 3D tilesets. The object will be passed
- * to `Cesium.Cesium3DTileset(options)` as options, so the properties listed in
- * the Cesium3DTileset documentation are also supported, see
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/Cesium3DTileset.html}
- * @typedef {Object} Cesium3DTileset#cesiumOptions
- * @property {string|number} ionAssetId - If this tileset is hosted by Cesium Ion,
- * then Ion asset ID.
- * @property {string} cesiumToken - If this tileset is hosted by Cesium Ion, then
- * the token needed to access this resource. If one is not set, then the default
- * set in the repository's {@link AppConfig#cesiumToken} will be used.
- */
-
- /**
- * Default attributes for Cesium3DTileset models
- * @name Cesium3DTileset#defaults
- * @extends MapAsset#defaults
- * @type {Object}
- * @property {'Cesium3DTileset'} type The format of the data. Must be
- * 'Cesium3DTileset'.
- * @property {VectorFilters} [filters=new VectorFilters()] A set of conditions
- * used to show or hide specific features of this tileset.
- * @property {AssetColorPalette} [colorPalette=new AssetColorPalette()] The color
- * or colors mapped to attributes of this asset. Used to style the features and to
- * make a legend.
- * @property {Cesium.Cesium3DTileset} cesiumModel A model created and used by
- * Cesium that organizes the data to display in the Cesium Widget. See
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/Cesium3DTileset.html}
- * @property {Cesium3DTileset#cesiumOptions} cesiumOptions options are passed
- * to the function that creates the Cesium model. The properties of options are
- * specific to each type of asset.
- */
- defaults: function () {
- return Object.assign(
- {},
- this.constructor.__super__.defaults(),
- {
- type: 'Cesium3DTileset',
- filters: new VectorFilters(),
- cesiumModel: null,
- cesiumOptions: {},
- colorPalette: new AssetColorPalette(),
- icon: ' ',
- featureType: Cesium.Cesium3DTileFeature
- }
- );
- },
-
- /**
- * Executed when a new Cesium3DTileset model is created.
- * @param {MapConfig#MapAssetConfig} [assetConfig] The initial values of the
- * attributes, which will be set on the model.
- */
- initialize: function (assetConfig) {
- try {
-
- MapAsset.prototype.initialize.call(this, assetConfig);
-
- if (assetConfig.filters) {
- this.set('filters', new VectorFilters(assetConfig.filters))
- }
-
- this.createCesiumModel();
+"use strict";
+
+define([
+ "cesium",
+ "models/maps/assets/MapAsset",
+ "models/maps/AssetColorPalette",
+ "collections/maps/VectorFilters",
+], function (Cesium, MapAsset, AssetColorPalette, VectorFilters) {
+ /**
+ * @classdesc A Cesium3DTileset Model is a special type of vector layer that can be used in
+ * Cesium maps, and that follows the 3d-tiles specification. See
+ * {@link https://github.com/CesiumGS/3d-tiles}
+ * @classcategory Models/Maps/Assets
+ * @class Cesium3DTileset
+ * @name Cesium3DTileset
+ * @extends MapAsset
+ * @since 2.18.0
+ * @constructor
+ */
+ var Cesium3DTileset = MapAsset.extend(
+ /** @lends Cesium3DTileset.prototype */ {
+ /**
+ * The name of this type of model
+ * @type {string}
+ */
+ type: "Cesium3DTileset",
+
+ /**
+ * Options that are supported for creating 3D tilesets. The object will be passed
+ * to `Cesium.Cesium3DTileset(options)` as options, so the properties listed in
+ * the Cesium3DTileset documentation are also supported, see
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/Cesium3DTileset.html}
+ * @typedef {Object} Cesium3DTileset#cesiumOptions
+ * @property {string|number} ionAssetId - If this tileset is hosted by Cesium Ion,
+ * then Ion asset ID.
+ * @property {string} cesiumToken - If this tileset is hosted by Cesium Ion, then
+ * the token needed to access this resource. If one is not set, then the default
+ * set in the repository's {@link AppConfig#cesiumToken} will be used.
+ */
+
+ /**
+ * Default attributes for Cesium3DTileset models
+ * @name Cesium3DTileset#defaults
+ * @extends MapAsset#defaults
+ * @type {Object}
+ * @property {'Cesium3DTileset'} type The format of the data. Must be
+ * 'Cesium3DTileset'.
+ * @property {VectorFilters} [filters=new VectorFilters()] A set of conditions
+ * used to show or hide specific features of this tileset.
+ * @property {AssetColorPalette} [colorPalette=new AssetColorPalette()] The color
+ * or colors mapped to attributes of this asset. Used to style the features and to
+ * make a legend.
+ * @property {Cesium.Cesium3DTileset} cesiumModel A model created and used by
+ * Cesium that organizes the data to display in the Cesium Widget. See
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/Cesium3DTileset.html}
+ * @property {Cesium3DTileset#cesiumOptions} cesiumOptions options are passed
+ * to the function that creates the Cesium model. The properties of options are
+ * specific to each type of asset.
+ */
+ defaults: function () {
+ return Object.assign({}, this.constructor.__super__.defaults(), {
+ type: "Cesium3DTileset",
+ filters: new VectorFilters(),
+ cesiumModel: null,
+ cesiumOptions: {},
+ colorPalette: new AssetColorPalette(),
+ icon: ' ',
+ featureType: Cesium.Cesium3DTileFeature,
+ });
+ },
+
+ /**
+ * Executed when a new Cesium3DTileset model is created.
+ * @param {MapConfig#MapAssetConfig} [assetConfig] The initial values of the
+ * attributes, which will be set on the model.
+ */
+ initialize: function (assetConfig) {
+ try {
+ MapAsset.prototype.initialize.call(this, assetConfig);
+
+ if (assetConfig.filters) {
+ this.set("filters", new VectorFilters(assetConfig.filters));
}
- catch (error) {
- console.log(
- 'There was an error initializing a 3DTileset model' +
- '. Error details: ' + error
- );
- }
- },
-
- /**
- * Creates a Cesium.Cesium3DTileset model and sets it to this model's
- * 'cesiumModel' attribute. This cesiumModel contains all the information required
- * for Cesium to render tiles. See
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/Cesium3DTileset.html?classFilter=3Dtiles}
- * @param {Boolean} recreate - Set recreate to true to force the function create
- * the Cesium Model again. Otherwise, if a cesium model already exists, that is
- * returned instead.
- */
- createCesiumModel: function (recreate = false) {
-
- try {
-
- // If the cesium model already exists, don't create it again unless specified
- const currentModel = this.get('cesiumModel')
- if (!recreate && currentModel) return currentModel
-
- const model = this;
- const cesiumOptions = this.getCesiumOptions();
- let cesiumModel = null
- if (!cesiumOptions) {
- model.set('status', 'error');
- model.set('statusDetails', 'No options were set for this tileset.');
- return;
- }
-
- model.resetStatus();
-
- // If this tileset is a Cesium Ion resource set the url from the asset Id
- cesiumOptions.url = this.getCesiumURL(cesiumOptions) || cesiumOptions.url;
-
- cesiumModel = new Cesium.Cesium3DTileset(cesiumOptions)
- model.set('cesiumModel', cesiumModel)
- cesiumModel.readyPromise
- .then(function () {
- // Let the map views know that the tileset is ready to render
- model.set('status', 'ready');
- // Listen to changes in the opacity, color, etc
- model.setListeners();
- // Set the initial visibility, opacity, filters, and colors
- model.updateAppearance();
- })
- .otherwise(function (error) {
- // See https://cesium.com/learn/cesiumjs/ref-doc/RequestErrorEvent.html
- let details = error;
- // Write a helpful error message
- switch (error.statusCode) {
- case 404:
- details = 'The resource was not found (error code 404).'
- break;
- case 500:
- details = 'There was a server error (error code 500).'
- break;
- }
- model.set('status', 'error');
- model.set('statusDetails', details)
- })
- }
- catch (error) {
- console.log(
- 'Failed to create a Cesium Model within a 3D Tileset model' +
- '. Error details: ' + error
- );
- }
- },
-
-
- /**
- * Checks whether there is an asset ID for a Cesium Ion resource and if
- * so, return the URL to the resource.
- * @returns {string} The URL to the Cesium Ion resource
- * @since 2.26.0
- */
- getCesiumURL: function () {
- try {
- const cesiumOptions = this.getCesiumOptions();
- if (!cesiumOptions || !cesiumOptions.ionAssetId) return null
- // The Cesium Ion ID of the resource to access
- const assetId = Number(cesiumOptions.ionAssetId)
- // Options to pass to Cesium's fromAssetId function. Access token
- // needs to be set before requesting cesium ion resources
- const ionResourceOptions = {
- accessToken: cesiumOptions.cesiumToken ||
- MetacatUI.appModel.get('cesiumToken')
- }
- // Create the new URL and set it on the model options
- return Cesium.IonResource.fromAssetId(assetId, ionResourceOptions);
- }
- catch (error) {
- console.log(
- 'There was an error settings a Cesium URL in a Cesium3DTileset' +
- '. Error details: ' + error
- );
- }
- },
-
- /**
- * Set listeners that update the cesium model when the backbone model is updated.
- */
- setListeners: function () {
- try {
-
- // call the super method
- this.constructor.__super__.setListeners.call(this);
-
- // When opacity, color, or visibility changes (will also update the filters)
- this.stopListening(this, 'change:opacity change:color change:visible')
- this.listenTo(
- this, 'change:opacity change:color change:visible', this.updateAppearance
- )
+ this.createCesiumModel();
+ } catch (error) {
+ console.log(
+ "There was an error initializing a 3DTileset model" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ /**
+ * Creates a Cesium.Cesium3DTileset model and sets it to this model's
+ * 'cesiumModel' attribute. This cesiumModel contains all the information required
+ * for Cesium to render tiles. See
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/Cesium3DTileset.html?classFilter=3Dtiles}
+ * @param {Boolean} recreate - Set recreate to true to force the function create
+ * the Cesium Model again. Otherwise, if a cesium model already exists, that is
+ * returned instead.
+ */
+ createCesiumModel: function (recreate = false) {
+ try {
+ // If the cesium model already exists, don't create it again unless specified
+ const currentModel = this.get("cesiumModel");
+ if (!recreate && currentModel) return currentModel;
- // When filters change
- this.stopListening(this.get('filters'), 'update')
- this.listenTo(this.get('filters'), 'update', this.updateFeatureVisibility)
+ const model = this;
+ const cesiumOptions = this.getCesiumOptions();
+ let cesiumModel = null;
+ if (!cesiumOptions) {
+ model.set("status", "error");
+ model.set("statusDetails", "No options were set for this tileset.");
+ return;
}
- catch (error) {
- console.log(
- 'There was an error setting listeners in a Cesium3DTileset' +
- '. Error details: ' + error
- );
- }
- },
-
- /**
- * Sets a new Cesium3DTileStyle on the Cesium 3D tileset model's style property,
- * based on the attributes set on this model.
- */
- updateAppearance: function () {
- try {
- const model = this;
- // The style set on the Cesium 3D tileset needs to be updated to show the
- // changes on a Cesium map.
- const cesiumModel = model.get('cesiumModel')
-
- if(!cesiumModel) return
- // If the layer isn't visible at all, don't bother setting up colors or
- // filters. Just set every feature to hidden.
- if (!model.isVisible()) {
- cesiumModel.style = new Cesium.Cesium3DTileStyle({
- show: false
- });
- // Indicate that the layer is hidden if the opacity is zero by updating the
- // visibility property
- if (model.get('opacity') === 0) {
- model.set('visible', false);
+ model.resetStatus();
+
+ // If this tileset is a Cesium Ion resource set the url from the asset Id
+ cesiumOptions.url =
+ this.getCesiumURL(cesiumOptions) || cesiumOptions.url;
+
+ cesiumModel = new Cesium.Cesium3DTileset(cesiumOptions);
+ model.set("cesiumModel", cesiumModel);
+ cesiumModel.readyPromise
+ .then(function () {
+ // Let the map views know that the tileset is ready to render
+ model.set("status", "ready");
+ // Listen to changes in the opacity, color, etc
+ model.setListeners();
+ // Set the initial visibility, opacity, filters, and colors
+ model.updateAppearance();
+ })
+ .otherwise(function (error) {
+ // See https://cesium.com/learn/cesiumjs/ref-doc/RequestErrorEvent.html
+ let details = error;
+ // Write a helpful error message
+ switch (error.statusCode) {
+ case 404:
+ details = "The resource was not found (error code 404).";
+ break;
+ case 500:
+ details = "There was a server error (error code 500).";
+ break;
}
+ model.set("status", "error");
+ model.set("statusDetails", details);
+ });
+ } catch (error) {
+ console.log(
+ "Failed to create a Cesium Model within a 3D Tileset model" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ /**
+ * Checks whether there is an asset ID for a Cesium Ion resource and if
+ * so, return the URL to the resource.
+ * @returns {string} The URL to the Cesium Ion resource
+ * @since 2.26.0
+ */
+ getCesiumURL: function () {
+ try {
+ const cesiumOptions = this.getCesiumOptions();
+ if (!cesiumOptions || !cesiumOptions.ionAssetId) return null;
+ // The Cesium Ion ID of the resource to access
+ const assetId = Number(cesiumOptions.ionAssetId);
+ // Options to pass to Cesium's fromAssetId function. Access token
+ // needs to be set before requesting cesium ion resources
+ const ionResourceOptions = {
+ accessToken:
+ cesiumOptions.cesiumToken ||
+ MetacatUI.appModel.get("cesiumToken"),
+ };
+ // Create the new URL and set it on the model options
+ return Cesium.IonResource.fromAssetId(assetId, ionResourceOptions);
+ } catch (error) {
+ console.log(
+ "There was an error settings a Cesium URL in a Cesium3DTileset" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ /**
+ * Set listeners that update the cesium model when the backbone model is updated.
+ */
+ setListeners: function () {
+ try {
+ // call the super method
+ this.constructor.__super__.setListeners.call(this);
+
+ // When opacity, color, or visibility changes (will also update the filters)
+ this.stopListening(
+ this,
+ "change:opacity change:color change:visible",
+ );
+ this.listenTo(
+ this,
+ "change:opacity change:color change:visible",
+ this.updateAppearance,
+ );
- // Let the map and/or other parent views know that a change has been made
- // that requires the map to be re-rendered
- model.trigger('appearanceChanged')
- } else {
- // Set a new 3D style with a function that Cesium will use to shade each
- // feature.
- cesiumModel.style = new Cesium.Cesium3DTileStyle({
- color: {
- evaluateColor: model.getColorFunction(),
- }
- });
- // Since the style has to be reset, re-add the filters expression. This also
- // triggers the appearanceChanged event
- model.updateFeatureVisibility()
- }
-
- }
- catch (error) {
- console.log(
- 'There was an error updating a 3D Tile style property in a Cesium3DTileset' +
- '. Error details: ' + error
- );
- }
- },
-
- /**
- * Updates the Cesium style set on this tileset. Adds a new expression to the
- * show property that will filter the features based on the filters set on this
- * model.
- */
- updateFeatureVisibility: function () {
- try {
-
- const model = this;
- const cesiumModel = this.get('cesiumModel')
- const filters = this.get('filters')
-
- // If there are no filters, just set the show property to true
- if (!filters || !filters.length) {
- cesiumModel.style.show = true
- } else {
- const expression = new Cesium.StyleExpression()
- expression.evaluate = function (feature) {
- const properties = model.getPropertiesFromFeature(feature)
- return model.featureIsVisible(properties)
- }
- cesiumModel.style.show = expression
+ // When filters change
+ this.stopListening(this.get("filters"), "update");
+ this.listenTo(
+ this.get("filters"),
+ "update",
+ this.updateFeatureVisibility,
+ );
+ } catch (error) {
+ console.log(
+ "There was an error setting listeners in a Cesium3DTileset" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ /**
+ * Sets a new Cesium3DTileStyle on the Cesium 3D tileset model's style property,
+ * based on the attributes set on this model.
+ */
+ updateAppearance: function () {
+ try {
+ const model = this;
+ // The style set on the Cesium 3D tileset needs to be updated to show the
+ // changes on a Cesium map.
+ const cesiumModel = model.get("cesiumModel");
+
+ if (!cesiumModel) return;
+
+ // If the layer isn't visible at all, don't bother setting up colors or
+ // filters. Just set every feature to hidden.
+ if (!model.isVisible()) {
+ cesiumModel.style = new Cesium.Cesium3DTileStyle({
+ show: false,
+ });
+ // Indicate that the layer is hidden if the opacity is zero by updating the
+ // visibility property
+ if (model.get("opacity") === 0) {
+ model.set("visible", false);
}
- model.trigger('appearanceChanged')
+ // Let the map and/or other parent views know that a change has been made
+ // that requires the map to be re-rendered
+ model.trigger("appearanceChanged");
+ } else {
+ // Set a new 3D style with a function that Cesium will use to shade each
+ // feature.
+ cesiumModel.style = new Cesium.Cesium3DTileStyle({
+ color: {
+ evaluateColor: model.getColorFunction(),
+ },
+ });
+ // Since the style has to be reset, re-add the filters expression. This also
+ // triggers the appearanceChanged event
+ model.updateFeatureVisibility();
}
- catch (error) {
- console.log(
- 'There was an error in a Cesium3DTileset' +
- '. Error details: ' + error
- );
- }
- },
-
- /**
- * Given a feature from a Cesium 3D tileset, returns any properties that are set
- * on the feature, similar to an attributes table.
- * @param {Cesium.Cesium3DTileFeature} feature A Cesium 3D Tile feature
- * @returns {Object} An object containing key-value mapping of property names to
- * properties.
- */
- getPropertiesFromFeature: function(feature) {
- if (!this.usesFeatureType(feature)) return null
- let properties = {};
- feature.getPropertyNames().forEach(function (propertyName) {
- properties[propertyName] = feature.getProperty(propertyName)
- })
- properties = this.addCustomProperties(properties)
- return properties
- },
-
- /**
- * Return the label for a feature from a Cesium 3D tileset
- * @param {Cesium.Cesium3DTileFeature} feature A Cesium 3D Tile feature
- * @returns {string} The label
- * @since 2.25.0
- */
- getLabelFromFeature: function (feature) {
- if (!this.usesFeatureType(feature)) return null
- return feature.getProperty('name') || feature.getProperty('label') || null
- },
-
- /**
- * Return the Cesium3DTileset model for a feature from a Cesium 3D tileset
- * @param {Cesium.Cesium3DTileFeature} feature A Cesium 3D Tile feature
- * @returns {Cesium3DTileset} The model
- * @since 2.25.0
- */
- getCesiumModelFromFeature: function (feature) {
- if (!this.usesFeatureType(feature)) return null
- return feature.primitive
- },
-
- /**
- * Return the ID used by Cesium for a feature from a Cesium 3D tileset
- * @param {Cesium.Cesium3DTileFeature} feature A Cesium 3D Tile feature
- * @returns {string} The ID
- * @since 2.25.0
- */
- getIDFromFeature: function (feature) {
- if (!this.usesFeatureType(feature)) return null
- return feature.pickId ? feature.pickId.key : null
- },
-
- /**
- * Creates a function that takes a Cesium3DTileFeature (see
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/Cesium3DTileFeature.html}) and
- * returns a Cesium color based on the colorPalette property set on this model.
- * The returned function is designed to be used as the evaluateColor function that
- * is set in the color property of a Cesium3DTileStyle StyleExpression. See
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/Cesium3DTileStyle.html#color}
- * @returns {function} A Cesium 3dTile evaluate color function
- */
- getColorFunction: function () {
- try {
- const model = this;
- // Opacity of the entire layer is set by using it as the alpha for each color
- const opacity = model.get('opacity')
-
- const evaluateColor = function (feature) {
+ } catch (error) {
+ console.log(
+ "There was an error updating a 3D Tile style property in a Cesium3DTileset" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ /**
+ * Updates the Cesium style set on this tileset. Adds a new expression to the
+ * show property that will filter the features based on the filters set on this
+ * model.
+ */
+ updateFeatureVisibility: function () {
+ try {
+ const model = this;
+ const cesiumModel = this.get("cesiumModel");
+ const filters = this.get("filters");
+
+ // If there are no filters, just set the show property to true
+ if (!filters || !filters.length) {
+ cesiumModel.style.show = true;
+ } else {
+ const expression = new Cesium.StyleExpression();
+ expression.evaluate = function (feature) {
const properties = model.getPropertiesFromFeature(feature);
- let featureOpacity = opacity;
- // If the feature is currently selected, set the opacity to max (otherwise the
- // 'silhouette' borders in the map do not show in the Cesium widget)
- if (model.featureIsSelected(feature)) {
- featureOpacity = 1
- }
- const rgb = model.getColor(properties)
- if (rgb) {
- return new Cesium.Color(rgb.red, rgb.green, rgb.blue, featureOpacity);
- } else {
- return new Cesium.Color();
- }
- }
- return evaluateColor
+ return model.featureIsVisible(properties);
+ };
+ cesiumModel.style.show = expression;
}
- catch (error) {
- console.log(
- 'There was an error creating a color function in a Cesium3DTileset model' +
- '. Error details: ' + error
- );
- }
- },
-
- /**
- * Gets a Cesium Bounding Sphere that can be used to navigate to view the full
- * extent of the tileset. See
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/BoundingSphere.html}.
- * @returns {Promise} Returns a promise that resolves to a Cesium Bounding Sphere
- * when ready
- */
- getBoundingSphere: function () {
+ model.trigger("appearanceChanged");
+ } catch (error) {
+ console.log(
+ "There was an error in a Cesium3DTileset" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ /**
+ * Given a feature from a Cesium 3D tileset, returns any properties that are set
+ * on the feature, similar to an attributes table.
+ * @param {Cesium.Cesium3DTileFeature} feature A Cesium 3D Tile feature
+ * @returns {Object} An object containing key-value mapping of property names to
+ * properties.
+ */
+ getPropertiesFromFeature: function (feature) {
+ if (!this.usesFeatureType(feature)) return null;
+ let properties = {};
+ feature.getPropertyNames().forEach(function (propertyName) {
+ properties[propertyName] = feature.getProperty(propertyName);
+ });
+ properties = this.addCustomProperties(properties);
+ return properties;
+ },
+
+ /**
+ * Return the label for a feature from a Cesium 3D tileset
+ * @param {Cesium.Cesium3DTileFeature} feature A Cesium 3D Tile feature
+ * @returns {string} The label
+ * @since 2.25.0
+ */
+ getLabelFromFeature: function (feature) {
+ if (!this.usesFeatureType(feature)) return null;
+ return (
+ feature.getProperty("name") || feature.getProperty("label") || null
+ );
+ },
+
+ /**
+ * Return the Cesium3DTileset model for a feature from a Cesium 3D tileset
+ * @param {Cesium.Cesium3DTileFeature} feature A Cesium 3D Tile feature
+ * @returns {Cesium3DTileset} The model
+ * @since 2.25.0
+ */
+ getCesiumModelFromFeature: function (feature) {
+ if (!this.usesFeatureType(feature)) return null;
+ return feature.primitive;
+ },
+
+ /**
+ * Return the ID used by Cesium for a feature from a Cesium 3D tileset
+ * @param {Cesium.Cesium3DTileFeature} feature A Cesium 3D Tile feature
+ * @returns {string} The ID
+ * @since 2.25.0
+ */
+ getIDFromFeature: function (feature) {
+ if (!this.usesFeatureType(feature)) return null;
+ return feature.pickId ? feature.pickId.key : null;
+ },
+
+ /**
+ * Creates a function that takes a Cesium3DTileFeature (see
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/Cesium3DTileFeature.html}) and
+ * returns a Cesium color based on the colorPalette property set on this model.
+ * The returned function is designed to be used as the evaluateColor function that
+ * is set in the color property of a Cesium3DTileStyle StyleExpression. See
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/Cesium3DTileStyle.html#color}
+ * @returns {function} A Cesium 3dTile evaluate color function
+ */
+ getColorFunction: function () {
+ try {
const model = this;
- return this.whenReady()
- .then(function (model) {
- const tileset = model.get('cesiumModel')
- const bSphere = Cesium.BoundingSphere.clone(tileset.boundingSphere);
- return bSphere
- })
+ // Opacity of the entire layer is set by using it as the alpha for each color
+ const opacity = model.get("opacity");
+
+ const evaluateColor = function (feature) {
+ const properties = model.getPropertiesFromFeature(feature);
+ let featureOpacity = opacity;
+ // If the feature is currently selected, set the opacity to max (otherwise the
+ // 'silhouette' borders in the map do not show in the Cesium widget)
+ if (model.featureIsSelected(feature)) {
+ featureOpacity = 1;
+ }
+ const rgb = model.getColor(properties);
+ if (rgb) {
+ return new Cesium.Color(
+ rgb.red,
+ rgb.green,
+ rgb.blue,
+ featureOpacity,
+ );
+ } else {
+ return new Cesium.Color();
+ }
+ };
+ return evaluateColor;
+ } catch (error) {
+ console.log(
+ "There was an error creating a color function in a Cesium3DTileset model" +
+ ". Error details: " +
+ error,
+ );
}
-
- });
-
- return Cesium3DTileset;
-
- }
-);
+ },
+
+ /**
+ * Gets a Cesium Bounding Sphere that can be used to navigate to view the full
+ * extent of the tileset. See
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/BoundingSphere.html}.
+ * @returns {Promise} Returns a promise that resolves to a Cesium Bounding Sphere
+ * when ready
+ */
+ getBoundingSphere: function () {
+ const model = this;
+ return this.whenReady().then(function (model) {
+ const tileset = model.get("cesiumModel");
+ const bSphere = Cesium.BoundingSphere.clone(tileset.boundingSphere);
+ return bSphere;
+ });
+ },
+ },
+ );
+
+ return Cesium3DTileset;
+});
diff --git a/src/js/models/maps/assets/CesiumGeohash.js b/src/js/models/maps/assets/CesiumGeohash.js
index ed4c0a4b6..5c2b5a6a5 100644
--- a/src/js/models/maps/assets/CesiumGeohash.js
+++ b/src/js/models/maps/assets/CesiumGeohash.js
@@ -19,7 +19,7 @@ define([
AssetColorPalette,
AssetColor,
Geohash,
- Geohashes
+ Geohashes,
) {
/**
* @classdesc A Geohash Model represents a geohash layer in a map.
@@ -127,7 +127,7 @@ define([
* For the property of interest (e.g. count) Get the min and max values
* from the geohashes collection and update the color palette.
* @since 2.25.0
- *
+ *
*/
updateColorRangeValues: function () {
const colorPalette = this.get("colorPalette");
@@ -152,7 +152,7 @@ define([
*/
getPrecision: function () {
const limit = this.get("maxGeoHashes");
- const geohashes = this.get("geohashes")
+ const geohashes = this.get("geohashes");
const area = this.getViewExtent().getArea();
return geohashes.getMaxPrecision(area, limit);
},
@@ -190,7 +190,7 @@ define([
function () {
this.updateColorRangeValues();
this.createCesiumModel(true);
- }
+ },
);
} catch (error) {
console.log("Failed to set listeners in CesiumGeohash", error);
@@ -205,7 +205,7 @@ define([
* to those that are within the current map extent.
* @returns {Geohashes} The geohashes to display.
*/
- getGeohashes: function(limitToExtent = true) {
+ getGeohashes: function (limitToExtent = true) {
let geohashes = this.get("geohashes");
if (limitToExtent) {
geohashes = this.getGeohashesForExtent();
@@ -229,7 +229,7 @@ define([
* @returns {GeoBoundingBox} The current map extent
*/
getViewExtent: function () {
- return this.get("mapModel")?.get("interactions")?.get("viewExtent")
+ return this.get("mapModel")?.get("interactions")?.get("viewExtent");
},
/**
@@ -277,7 +277,8 @@ define([
// Set the GeoJSON representing geohashes on the model
const cesiumOptions = this.getCesiumOptions();
const type = model.get("type");
- const data = type === "GeoJsonDataSource" ? this.getGeoJSON() : this.getCZML();
+ const data =
+ type === "GeoJsonDataSource" ? this.getGeoJSON() : this.getCZML();
cesiumOptions["data"] = data;
cesiumOptions["height"] = 0;
model.set("cesiumOptions", cesiumOptions);
@@ -296,10 +297,15 @@ define([
*/
selectGeohashes: function (geohashes) {
try {
- const toSelect = [...new Set(geohashes.map((geohash) => {
- const parent = this.get("geohashes").getContainingGeohash(geohash);
- return parent?.get("hashString");
- }, this))];
+ const toSelect = [
+ ...new Set(
+ geohashes.map((geohash) => {
+ const parent =
+ this.get("geohashes").getContainingGeohash(geohash);
+ return parent?.get("hashString");
+ }, this),
+ ),
+ ];
const entities = this.get("cesiumModel").entities.values;
const selected = entities.filter((entity) => {
const hashString = this.getPropertiesFromFeature(entity).hashString;
@@ -313,6 +319,6 @@ define([
console.log("Error selecting geohashes", e);
}
},
- }
+ },
);
});
diff --git a/src/js/models/maps/assets/CesiumImagery.js b/src/js/models/maps/assets/CesiumImagery.js
index 404100251..fae5b53f6 100644
--- a/src/js/models/maps/assets/CesiumImagery.js
+++ b/src/js/models/maps/assets/CesiumImagery.js
@@ -1,458 +1,469 @@
-'use strict';
-
-define(
- [
- 'jquery',
- 'underscore',
- 'backbone',
- 'cesium',
- 'models/maps/assets/MapAsset',
- ],
- function (
- $,
- _,
- Backbone,
- Cesium,
- MapAsset
- ) {
- /**
- * @classdesc A CesiumImagery Model contains the information required for Cesium to
- * request and draw high-resolution image tiles using several standards (Cesium
- * "imagery providers"), including Cesium Ion and Bing Maps. Imagery layers have
- * brightness, contrast, gamma, hue, and saturation properties that can be dynamically
- * changed.
- * @classcategory Models/Maps/Assets
- * @class CesiumImagery
- * @name CesiumImagery
- * @extends MapAsset
- * @since 2.18.0
- * @constructor
- */
- var CesiumImagery = MapAsset.extend(
- /** @lends CesiumImagery.prototype */ {
-
- /**
- * The name of this type of model
- * @type {string}
- */
- type: 'CesiumImagery',
-
- /**
- * Options that are supported for creating imagery tiles. Any properties provided
- * here are passed to the Cesium constructor function, so other properties that
- * are documented in Cesium are also supported. See
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/BingMapsImageryProvider.html#.ConstructorOptions}
- * and
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/IonImageryProvider.html#.ConstructorOptions}.
- * @typedef {Object} CesiumImagery#cesiumOptions
- * @property {string|number} ionAssetId - If this imagery is hosted by Cesium
- * Ion, then Ion asset ID.
- * @property {string|number} key - A key or token required to access the tiles.
- * For example, if this is a BingMapsImageryProvider, then the Bing maps key. If
- * one is required and not set, the model will look in the {@link AppModel} for a
- * key, for example, {@link AppModel#bingMapsKey}
- * @property {'GeographicTilingScheme'|'WebMercatorTilingScheme'} tilingScheme -
- * The tiling scheme to use when constructing an imagery provider. If not set,
- * Cesium uses WebMercatorTilingScheme by default.
- * @property {Number[]} rectangle - The rectangle covered by the layer. The list
- * of west, south, east, north bounding degree coordinates, respectively. This
- * will be passed to Cesium.Rectangle.fromDegrees to define the bounding box of
- * the imagery layer. If left undefined, the layer will cover the entire globe.
- */
-
- /**
- * Default attributes for CesiumImagery models
- * @name CesiumImagery#defaults
- * @extends MapAsset#defaults
- * @type {Object}
- * @property {'BingMapsImageryProvider'|'IonImageryProvider'|'TileMapServiceImageryProvider'|'WebMapTileServiceImageryProvider'} type
- * A string indicating a Cesium Imagery Provider type. See
- * {@link https://cesium.com/learn/cesiumjs-learn/cesiumjs-imagery/#more-imagery-providers}
- * @property {Cesium.ImageryLayer} cesiumModel A model created and used by Cesium
- * that organizes the data to display in the Cesium Widget. See
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/ImageryLayer.html?classFilter=ImageryLayer}
- * and
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/?classFilter=ImageryProvider}
- * @property {CesiumImagery#cesiumOptions} cesiumOptions options that are passed
- * to the function that creates the Cesium model. The properties of options are
- * specific to each type of asset.
- */
- defaults: function () {
- return _.extend(
- this.constructor.__super__.defaults(),
- {
- type: '',
- cesiumModel: null,
- cesiumOptions: {},
- // brightness: 1, contrast: 1, gamma: 1, hue: 0, saturation: 1,
- }
+"use strict";
+
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "cesium",
+ "models/maps/assets/MapAsset",
+], function ($, _, Backbone, Cesium, MapAsset) {
+ /**
+ * @classdesc A CesiumImagery Model contains the information required for Cesium to
+ * request and draw high-resolution image tiles using several standards (Cesium
+ * "imagery providers"), including Cesium Ion and Bing Maps. Imagery layers have
+ * brightness, contrast, gamma, hue, and saturation properties that can be dynamically
+ * changed.
+ * @classcategory Models/Maps/Assets
+ * @class CesiumImagery
+ * @name CesiumImagery
+ * @extends MapAsset
+ * @since 2.18.0
+ * @constructor
+ */
+ var CesiumImagery = MapAsset.extend(
+ /** @lends CesiumImagery.prototype */ {
+ /**
+ * The name of this type of model
+ * @type {string}
+ */
+ type: "CesiumImagery",
+
+ /**
+ * Options that are supported for creating imagery tiles. Any properties provided
+ * here are passed to the Cesium constructor function, so other properties that
+ * are documented in Cesium are also supported. See
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/BingMapsImageryProvider.html#.ConstructorOptions}
+ * and
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/IonImageryProvider.html#.ConstructorOptions}.
+ * @typedef {Object} CesiumImagery#cesiumOptions
+ * @property {string|number} ionAssetId - If this imagery is hosted by Cesium
+ * Ion, then Ion asset ID.
+ * @property {string|number} key - A key or token required to access the tiles.
+ * For example, if this is a BingMapsImageryProvider, then the Bing maps key. If
+ * one is required and not set, the model will look in the {@link AppModel} for a
+ * key, for example, {@link AppModel#bingMapsKey}
+ * @property {'GeographicTilingScheme'|'WebMercatorTilingScheme'} tilingScheme -
+ * The tiling scheme to use when constructing an imagery provider. If not set,
+ * Cesium uses WebMercatorTilingScheme by default.
+ * @property {Number[]} rectangle - The rectangle covered by the layer. The list
+ * of west, south, east, north bounding degree coordinates, respectively. This
+ * will be passed to Cesium.Rectangle.fromDegrees to define the bounding box of
+ * the imagery layer. If left undefined, the layer will cover the entire globe.
+ */
+
+ /**
+ * Default attributes for CesiumImagery models
+ * @name CesiumImagery#defaults
+ * @extends MapAsset#defaults
+ * @type {Object}
+ * @property {'BingMapsImageryProvider'|'IonImageryProvider'|'TileMapServiceImageryProvider'|'WebMapTileServiceImageryProvider'} type
+ * A string indicating a Cesium Imagery Provider type. See
+ * {@link https://cesium.com/learn/cesiumjs-learn/cesiumjs-imagery/#more-imagery-providers}
+ * @property {Cesium.ImageryLayer} cesiumModel A model created and used by Cesium
+ * that organizes the data to display in the Cesium Widget. See
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/ImageryLayer.html?classFilter=ImageryLayer}
+ * and
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/?classFilter=ImageryProvider}
+ * @property {CesiumImagery#cesiumOptions} cesiumOptions options that are passed
+ * to the function that creates the Cesium model. The properties of options are
+ * specific to each type of asset.
+ */
+ defaults: function () {
+ return _.extend(this.constructor.__super__.defaults(), {
+ type: "",
+ cesiumModel: null,
+ cesiumOptions: {},
+ // brightness: 1, contrast: 1, gamma: 1, hue: 0, saturation: 1,
+ });
+ },
+
+ /**
+ * Executed when a new CesiumImagery model is created.
+ * @param {MapConfig#MapAssetConfig} [assetConfig] The initial values of the
+ * attributes, which will be set on the model.
+ */
+ initialize: function (assetConfig) {
+ try {
+ MapAsset.prototype.initialize.call(this, assetConfig);
+
+ if (assetConfig.type == "NaturalEarthII") {
+ this.initNaturalEarthII(assetConfig);
+ } else if (assetConfig.type == "USGSImageryTopo") {
+ this.initUSGSImageryTopo(assetConfig);
+ }
+
+ this.createCesiumModel();
+
+ this.getThumbnail();
+ } catch (e) {
+ console.log("Error initializing a CesiumImagery model: ", e);
+ }
+ },
+
+ /**
+ * Initializes a CesiumImagery model for the Natural Earth II asset.
+ * @param {MapConfig#MapAssetConfig} [assetConfig] The initial values of the
+ * attributes, which will be set on the model.
+ */
+ initNaturalEarthII: function (assetConfig) {
+ try {
+ if (
+ !assetConfig.cesiumOptions ||
+ typeof assetConfig.cesiumOptions !== "object"
+ ) {
+ assetConfig.cesiumOptions = {};
+ }
+
+ assetConfig.cesiumOptions.url = Cesium.buildModuleUrl(
+ "Assets/Textures/NaturalEarthII",
+ );
+ this.set("type", "TileMapServiceImageryProvider");
+ this.set("cesiumOptions", assetConfig.cesiumOptions);
+ } catch (error) {
+ console.log(
+ "There was an error initializing NaturalEarthII in a CesiumImagery" +
+ ". Error details: " +
+ error,
);
- },
-
- /**
- * Executed when a new CesiumImagery model is created.
- * @param {MapConfig#MapAssetConfig} [assetConfig] The initial values of the
- * attributes, which will be set on the model.
- */
- initialize: function (assetConfig) {
- try {
- MapAsset.prototype.initialize.call(this, assetConfig);
-
- if (assetConfig.type == 'NaturalEarthII') {
- this.initNaturalEarthII(assetConfig);
- }
- else if (assetConfig.type == 'USGSImageryTopo') {
- this.initUSGSImageryTopo(assetConfig);
- }
-
- this.createCesiumModel();
-
- this.getThumbnail();
+ }
+ },
+
+ /**
+ * Initializes a CesiumImagery model for the USGS Imagery Topo asset.
+ * @param {MapConfig#MapAssetConfig} [assetConfig] The initial values of the
+ * attributes, which will be set on the model.
+ */
+ initUSGSImageryTopo: function (assetConfig) {
+ try {
+ if (
+ !assetConfig.cesiumOptions ||
+ typeof assetConfig.cesiumOptions !== "object"
+ ) {
+ assetConfig.cesiumOptions = {};
}
- catch (e) {
- console.log('Error initializing a CesiumImagery model: ', e);
+ this.set("type", "WebMapServiceImageryProvider");
+ assetConfig.cesiumOptions.url =
+ "https://basemap.nationalmap.gov:443/arcgis/services/USGSImageryTopo/MapServer/WmsServer";
+ assetConfig.cesiumOptions.layers = "0";
+ assetConfig.cesiumOptions.parameters = {
+ transparent: true,
+ format: "image/png",
+ };
+ this.set("cesiumOptions", assetConfig.cesiumOptions);
+ if (!assetConfig.moreInfoLink) {
+ this.set(
+ "moreInfoLink",
+ "https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer",
+ );
}
- },
-
- /**
- * Initializes a CesiumImagery model for the Natural Earth II asset.
- * @param {MapConfig#MapAssetConfig} [assetConfig] The initial values of the
- * attributes, which will be set on the model.
- */
- initNaturalEarthII: function (assetConfig) {
- try {
- if (
- !assetConfig.cesiumOptions || typeof assetConfig.cesiumOptions !== 'object'
- ) {
- assetConfig.cesiumOptions = {};
- }
-
- assetConfig.cesiumOptions.url = Cesium.buildModuleUrl(
- 'Assets/Textures/NaturalEarthII'
+ if (!assetConfig.attribution) {
+ this.set(
+ "attribution",
+ "USGS The National Map: Orthoimagery and US Topo. Data refreshed January, 2022.",
);
- this.set('type', 'TileMapServiceImageryProvider')
- this.set('cesiumOptions', assetConfig.cesiumOptions);
}
- catch (error) {
- console.log(
- 'There was an error initializing NaturalEarthII in a CesiumImagery' +
- '. Error details: ' + error
+ if (!assetConfig.description) {
+ this.set(
+ "description",
+ "USGS Imagery Topo is a tile cache base map of orthoimagery in The National Map and US Topo vectors visible to the 1:9,028 scale.",
);
}
- },
-
- /**
- * Initializes a CesiumImagery model for the USGS Imagery Topo asset.
- * @param {MapConfig#MapAssetConfig} [assetConfig] The initial values of the
- * attributes, which will be set on the model.
- */
- initUSGSImageryTopo: function (assetConfig) {
- try {
- if (
- !assetConfig.cesiumOptions || typeof assetConfig.cesiumOptions !== 'object'
- ) {
- assetConfig.cesiumOptions = {};
- }
- this.set('type', 'WebMapServiceImageryProvider')
- assetConfig.cesiumOptions.url = 'https://basemap.nationalmap.gov:443/arcgis/services/USGSImageryTopo/MapServer/WmsServer'
- assetConfig.cesiumOptions.layers = '0'
- assetConfig.cesiumOptions.parameters = {
- transparent: true,
- format: 'image/png',
- }
- this.set('cesiumOptions', assetConfig.cesiumOptions);
- if (!assetConfig.moreInfoLink) {
- this.set('moreInfoLink', 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer')
- }
- if (!assetConfig.attribution) {
- this.set('attribution', 'USGS The National Map: Orthoimagery and US Topo. Data refreshed January, 2022.')
- }
- if (!assetConfig.description) {
- this.set('description', 'USGS Imagery Topo is a tile cache base map of orthoimagery in The National Map and US Topo vectors visible to the 1:9,028 scale.')
- }
- if (!assetConfig.label) {
- this.set('label', 'USGS Imagery Topo')
- }
+ if (!assetConfig.label) {
+ this.set("label", "USGS Imagery Topo");
}
- catch (error) {
+ } catch (error) {
+ console.log(
+ "There was an error initializing USGSImageryTopo in a CesiumImagery" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ /**
+ * Creates a Cesium ImageryLayer that contains information about how the imagery
+ * should render in Cesium. See
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/ImageryLayer.html?classFilter=ImageryLay}
+ * @param {Boolean} recreate - Set recreate to true to force the function create
+ * the Cesium Model again. Otherwise, if a cesium model already exists, that is
+ * returned instead.
+ */
+ createCesiumModel: function (recreate = false) {
+ var model = this;
+ const cesiumOptions = this.getCesiumOptions();
+ var type = this.get("type");
+ var providerFunction = Cesium[type];
+
+ // If the cesium model already exists, don't create it again unless specified
+ if (!recreate && this.get("cesiumModel")) {
+ console.log("returning existing cesium model");
+ return this.get("cesiumModel");
+ }
+
+ model.resetStatus();
+
+ var initialAppearance = {
+ alpha: this.get("opacity"),
+ show: this.get("visible"),
+ saturation: this.get("saturation"),
+ // TODO: brightness, contrast, gamma, etc.
+ };
+
+ if (type === "BingMapsImageryProvider") {
+ cesiumOptions.key =
+ cesiumOptions.key || MetacatUI.AppConfig.bingMapsKey;
+ } else if (type === "IonImageryProvider") {
+ cesiumOptions.assetId = Number(cesiumOptions.ionAssetId);
+ delete cesiumOptions.ionAssetId;
+ cesiumOptions.accessToken =
+ cesiumOptions.cesiumToken || MetacatUI.appModel.get("cesiumToken");
+ } else if (type === "OpenStreetMapImageryProvider") {
+ cesiumOptions.url =
+ cesiumOptions.url || "https://a.tile.openstreetmap.org/";
+ }
+ if (cesiumOptions && cesiumOptions.tilingScheme) {
+ const ts = cesiumOptions.tilingScheme;
+ const availableTS = [
+ "GeographicTilingScheme",
+ "WebMercatorTilingScheme",
+ ];
+ if (availableTS.indexOf(ts) > -1) {
+ cesiumOptions.tilingScheme = new Cesium[ts]();
+ } else {
console.log(
- 'There was an error initializing USGSImageryTopo in a CesiumImagery' +
- '. Error details: ' + error
+ `${ts} is not a valid tiling scheme. Using WebMercatorTilingScheme`,
);
+ cesiumOptions.tilingScheme = new Cesium.WebMercatorTilingScheme();
}
- },
-
- /**
- * Creates a Cesium ImageryLayer that contains information about how the imagery
- * should render in Cesium. See
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/ImageryLayer.html?classFilter=ImageryLay}
- * @param {Boolean} recreate - Set recreate to true to force the function create
- * the Cesium Model again. Otherwise, if a cesium model already exists, that is
- * returned instead.
- */
- createCesiumModel: function (recreate = false) {
-
- var model = this;
- const cesiumOptions = this.getCesiumOptions();
- var type = this.get('type')
- var providerFunction = Cesium[type]
-
- // If the cesium model already exists, don't create it again unless specified
- if (!recreate && this.get('cesiumModel')) {
- console.log('returning existing cesium model');
- return this.get('cesiumModel')
- }
-
- model.resetStatus();
+ }
- var initialAppearance = {
- alpha: this.get('opacity'),
- show: this.get('visible'),
- saturation: this.get('saturation'),
- // TODO: brightness, contrast, gamma, etc.
- }
-
- if (type === 'BingMapsImageryProvider') {
- cesiumOptions.key = cesiumOptions.key || MetacatUI.AppConfig.bingMapsKey
- } else if (type === 'IonImageryProvider') {
- cesiumOptions.assetId = Number(cesiumOptions.ionAssetId)
- delete cesiumOptions.ionAssetId
- cesiumOptions.accessToken =
- cesiumOptions.cesiumToken || MetacatUI.appModel.get('cesiumToken');
- } else if (type === 'OpenStreetMapImageryProvider') {
- cesiumOptions.url = cesiumOptions.url || 'https://a.tile.openstreetmap.org/'
- }
- if (cesiumOptions && cesiumOptions.tilingScheme) {
- const ts = cesiumOptions.tilingScheme
- const availableTS = ['GeographicTilingScheme', 'WebMercatorTilingScheme']
- if (availableTS.indexOf(ts) > -1) {
- cesiumOptions.tilingScheme = new Cesium[ts]()
- } else {
- console.log(`${ts} is not a valid tiling scheme. Using WebMercatorTilingScheme`)
- cesiumOptions.tilingScheme = new Cesium.WebMercatorTilingScheme()
- }
- }
-
- if (cesiumOptions.rectangle) {
- cesiumOptions.rectangle = Cesium.Rectangle.fromDegrees(
- ...cesiumOptions.rectangle
- )
- }
-
- if (providerFunction && typeof providerFunction === 'function') {
- let provider = new providerFunction(cesiumOptions)
- provider.readyPromise
- .then(function () {
- // Imagery must be converted from a Cesium Imagery Provider to a Cesium
- // Imagery Layer. See
- // https://cesium.com/learn/cesiumjs-learn/cesiumjs-imagery/#imagery-providers-vs-layers
- model.set('cesiumModel', new Cesium.ImageryLayer(provider, initialAppearance))
- model.set('status', 'ready')
- model.setListeners()
- })
- .otherwise(function (error) {
- // See https://cesium.com/learn/cesiumjs/ref-doc/RequestErrorEvent.html
- let details = error;
- // Write a helpful error message
- switch (error.statusCode) {
- case 404:
- details = 'The resource was not found (error code 404).'
- break;
- case 500:
- details = 'There was a server error (error code 500).'
- break;
- }
- model.set('status', 'error');
- model.set('statusDetails', details)
- })
- } else {
- model.set('status', 'error')
- model.set('statusDetails', type + ' is not a supported imagery type.')
+ if (cesiumOptions.rectangle) {
+ cesiumOptions.rectangle = Cesium.Rectangle.fromDegrees(
+ ...cesiumOptions.rectangle,
+ );
+ }
+
+ if (providerFunction && typeof providerFunction === "function") {
+ let provider = new providerFunction(cesiumOptions);
+ provider.readyPromise
+ .then(function () {
+ // Imagery must be converted from a Cesium Imagery Provider to a Cesium
+ // Imagery Layer. See
+ // https://cesium.com/learn/cesiumjs-learn/cesiumjs-imagery/#imagery-providers-vs-layers
+ model.set(
+ "cesiumModel",
+ new Cesium.ImageryLayer(provider, initialAppearance),
+ );
+ model.set("status", "ready");
+ model.setListeners();
+ })
+ .otherwise(function (error) {
+ // See https://cesium.com/learn/cesiumjs/ref-doc/RequestErrorEvent.html
+ let details = error;
+ // Write a helpful error message
+ switch (error.statusCode) {
+ case 404:
+ details = "The resource was not found (error code 404).";
+ break;
+ case 500:
+ details = "There was a server error (error code 500).";
+ break;
+ }
+ model.set("status", "error");
+ model.set("statusDetails", details);
+ });
+ } else {
+ model.set("status", "error");
+ model.set(
+ "statusDetails",
+ type + " is not a supported imagery type.",
+ );
+ }
+ },
+
+ /**
+ * Set listeners that update the cesium model when the backbone model is updated.
+ */
+ setListeners: function () {
+ try {
+ var cesiumModel = this.get("cesiumModel");
+
+ // Make sure the listeners are only set once!
+ this.stopListening(this);
+
+ this.listenTo(this, "change:opacity", function (model, opacity) {
+ cesiumModel.alpha = opacity;
+ // Let the map and/or other parent views know that a change has been made
+ // that requires the map to be re-rendered
+ model.trigger("appearanceChanged");
+ });
+ this.listenTo(this, "change:visible", function (model, visible) {
+ cesiumModel.show = visible;
+ // Let the map and/or other parent views know that a change has been made
+ // that requires the map to be re-rendered
+ model.trigger("appearanceChanged");
+ });
+ } catch (error) {
+ console.log(
+ "There was an error setting listeners in a cesium Imagery model" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ /**
+ * Gets a Cesium Bounding Sphere that can be used to navigate to view the full
+ * extent of the imagery. See
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/BoundingSphere.html}
+ * @returns {Promise} Returns a promise that resolves to a Cesium Bounding Sphere
+ * when ready
+ */
+ getBoundingSphere: function () {
+ return this.whenReady()
+ .then(function (model) {
+ return model.get("cesiumModel").getViewableRectangle();
+ })
+ .then(function (rectangle) {
+ return Cesium.BoundingSphere.fromRectangle3D(rectangle);
+ });
+ },
+
+ /**
+ * Requests a tile from the imagery provider that is at the center of the layer's
+ * bounding box and at the minimum level. Once the image is fetched, sets its URL
+ * on the thumbnail property of this model. This function is first called when the
+ * layer initialized, but waits for the cesiumModel to be ready.
+ */
+ getThumbnail: function () {
+ try {
+ if (this.get("status") !== "ready") {
+ this.listenToOnce(this, "change:status", this.getThumbnail);
+ return;
}
- },
-
- /**
- * Set listeners that update the cesium model when the backbone model is updated.
- */
- setListeners: function () {
- try {
-
- var cesiumModel = this.get('cesiumModel')
+ const model = this;
+ const cesImageryLayer = this.get("cesiumModel");
+ const provider = cesImageryLayer.imageryProvider;
+ const rect = cesImageryLayer.rectangle;
+ var x = (rect.east + rect.west) / 2;
+ var y = (rect.north + rect.south) / 2;
+ var level = provider.minimumLevel;
- // Make sure the listeners are only set once!
- this.stopListening(this);
-
- this.listenTo(this, 'change:opacity', function (model, opacity) {
- cesiumModel.alpha = opacity
- // Let the map and/or other parent views know that a change has been made
- // that requires the map to be re-rendered
- model.trigger('appearanceChanged')
-
- })
- this.listenTo(this, 'change:visible', function (model, visible) {
- cesiumModel.show = visible
- // Let the map and/or other parent views know that a change has been made
- // that requires the map to be re-rendered
- model.trigger('appearanceChanged')
- })
- }
- catch (error) {
- console.log(
- 'There was an error setting listeners in a cesium Imagery model' +
- '. Error details: ' + error
- );
- }
- },
-
- /**
- * Gets a Cesium Bounding Sphere that can be used to navigate to view the full
- * extent of the imagery. See
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/BoundingSphere.html}
- * @returns {Promise} Returns a promise that resolves to a Cesium Bounding Sphere
- * when ready
- */
- getBoundingSphere: function () {
- return this.whenReady()
- .then(function (model) {
- return model.get('cesiumModel').getViewableRectangle()
- })
- .then(function (rectangle) {
- return Cesium.BoundingSphere.fromRectangle3D(rectangle)
- })
- },
-
- /**
- * Requests a tile from the imagery provider that is at the center of the layer's
- * bounding box and at the minimum level. Once the image is fetched, sets its URL
- * on the thumbnail property of this model. This function is first called when the
- * layer initialized, but waits for the cesiumModel to be ready.
- */
- getThumbnail: function () {
- try {
- if (this.get('status') !== 'ready') {
- this.listenToOnce(this, 'change:status', this.getThumbnail)
- return
- }
-
- const model = this
- const cesImageryLayer = this.get('cesiumModel');
- const provider = cesImageryLayer.imageryProvider
- const rect = cesImageryLayer.rectangle
- var x = (rect.east + rect.west) / 2
- var y = (rect.north + rect.south) / 2
- var level = provider.minimumLevel
-
- provider.requestImage(x, y, level).then(function (response) {
-
- let data = response.blob
- let objectURL = null
+ provider
+ .requestImage(x, y, level)
+ .then(function (response) {
+ let data = response.blob;
+ let objectURL = null;
if (!data && response instanceof ImageBitmap) {
- objectURL = model.getDataUriFromBitmap(response)
+ objectURL = model.getDataUriFromBitmap(response);
} else {
objectURL = URL.createObjectURL(data);
}
- model.set('thumbnail', objectURL)
- }).otherwise(function (e) {
- console.log('Error requesting an image tile to use as a thumbnail for an ' +
- 'Imagery Layer. Error message: ' + e);
+ model.set("thumbnail", objectURL);
})
- }
- catch (error) {
- console.log(
- 'There was an error getting a thumbnail for a CesiumImagery layer' +
- '. Error details: ' + error
- );
- }
- },
-
- /**
- * Gets a data URI from a bitmap image.
- * @param {ImageBitmap} bitmap The bitmap image to convert to a data URI
- * @returns {String} Returns a string containing the requested data URI.
- */
- getDataUriFromBitmap: function (imageBitmap) {
- try {
- const canvas = document.createElement('canvas');
- canvas.width = imageBitmap.width;
- canvas.height = imageBitmap.height;
- const ctx = canvas.getContext('2d')
- // y-flip the image - Natural Earth II bitmaps appear upside down otherwise
- // TODO: Test with other imagery layers
- ctx.translate(0, imageBitmap.height);
- ctx.scale(1, -1);
- ctx.drawImage(imageBitmap, 0, 0);
- return canvas.toDataURL();
- } catch (error) {
- console.log(
- 'There was an error converting an ImageBitmap to a data URL' +
- '. Error details: ' + error
- );
- }
- },
-
- // /**
- // * Parses the given input into a JSON object to be set on the model.
- // *
- // * @param {TODO} input - The raw response object
- // * @return {TODO} - The JSON object of all the Imagery attributes
- // */
- // parse: function (input) {
-
- // try {
-
- // var modelJSON = {};
-
- // return modelJSON
-
- // }
- // catch (error) {console.log('There was an error parsing a Imagery model' + '.
- // Error details: ' + error
- // );
- // }
-
- // },
-
- // /**
- // * Overrides the default Backbone.Model.validate.function() to check if this if
- // * the values set on this model are valid.
- // *
- // * @param {Object} [attrs] - A literal object of model attributes to validate.
- // * @param {Object} [options] - A literal object of options for this validation
- // * process
- // *
- // * @return {Object} - Returns a literal object with the invalid attributes and
- // * their corresponding error message, if there are any. If there are no errors,
- // * returns nothing.
- // */
- // validate: function (attrs, options) {try {
-
- // }
- // catch (error) {console.log('There was an error validating a CesiumImagery
- // model' + '. Error details: ' + error
- // );
- // }
- // },
-
- // /**
- // * Creates a string using the values set on this model's attributes.
- // * @return {string} The Imagery string
- // */
- // serialize: function () {try {var serializedImagery = "";
-
- // return serializedImagery;
- // }
- // catch (error) {console.log('There was an error serializing a CesiumImagery
- // model' + '. Error details: ' + error
- // );
- // }
- // },
-
- });
-
- return CesiumImagery;
-
- }
-);
+ .otherwise(function (e) {
+ console.log(
+ "Error requesting an image tile to use as a thumbnail for an " +
+ "Imagery Layer. Error message: " +
+ e,
+ );
+ });
+ } catch (error) {
+ console.log(
+ "There was an error getting a thumbnail for a CesiumImagery layer" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ /**
+ * Gets a data URI from a bitmap image.
+ * @param {ImageBitmap} bitmap The bitmap image to convert to a data URI
+ * @returns {String} Returns a string containing the requested data URI.
+ */
+ getDataUriFromBitmap: function (imageBitmap) {
+ try {
+ const canvas = document.createElement("canvas");
+ canvas.width = imageBitmap.width;
+ canvas.height = imageBitmap.height;
+ const ctx = canvas.getContext("2d");
+ // y-flip the image - Natural Earth II bitmaps appear upside down otherwise
+ // TODO: Test with other imagery layers
+ ctx.translate(0, imageBitmap.height);
+ ctx.scale(1, -1);
+ ctx.drawImage(imageBitmap, 0, 0);
+ return canvas.toDataURL();
+ } catch (error) {
+ console.log(
+ "There was an error converting an ImageBitmap to a data URL" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ // /**
+ // * Parses the given input into a JSON object to be set on the model.
+ // *
+ // * @param {TODO} input - The raw response object
+ // * @return {TODO} - The JSON object of all the Imagery attributes
+ // */
+ // parse: function (input) {
+
+ // try {
+
+ // var modelJSON = {};
+
+ // return modelJSON
+
+ // }
+ // catch (error) {console.log('There was an error parsing a Imagery model' + '.
+ // Error details: ' + error
+ // );
+ // }
+
+ // },
+
+ // /**
+ // * Overrides the default Backbone.Model.validate.function() to check if this if
+ // * the values set on this model are valid.
+ // *
+ // * @param {Object} [attrs] - A literal object of model attributes to validate.
+ // * @param {Object} [options] - A literal object of options for this validation
+ // * process
+ // *
+ // * @return {Object} - Returns a literal object with the invalid attributes and
+ // * their corresponding error message, if there are any. If there are no errors,
+ // * returns nothing.
+ // */
+ // validate: function (attrs, options) {try {
+
+ // }
+ // catch (error) {console.log('There was an error validating a CesiumImagery
+ // model' + '. Error details: ' + error
+ // );
+ // }
+ // },
+
+ // /**
+ // * Creates a string using the values set on this model's attributes.
+ // * @return {string} The Imagery string
+ // */
+ // serialize: function () {try {var serializedImagery = "";
+
+ // return serializedImagery;
+ // }
+ // catch (error) {console.log('There was an error serializing a CesiumImagery
+ // model' + '. Error details: ' + error
+ // );
+ // }
+ // },
+ },
+ );
+
+ return CesiumImagery;
+});
diff --git a/src/js/models/maps/assets/CesiumTerrain.js b/src/js/models/maps/assets/CesiumTerrain.js
index fdc641245..d54bc3066 100644
--- a/src/js/models/maps/assets/CesiumTerrain.js
+++ b/src/js/models/maps/assets/CesiumTerrain.js
@@ -1,240 +1,229 @@
-'use strict';
-
-define(
- [
- 'jquery',
- 'underscore',
- 'backbone',
- 'cesium',
- 'models/maps/assets/MapAsset'
- ],
- function (
- $,
- _,
- Backbone,
- Cesium,
- MapAsset
- ) {
- /**
- * @classdesc A CesiumTerrain Model comprises the information required to fetch 3D
- * terrain, such as mountain peaks and valleys, to display in Cesium. A terrain model
- * also contains metadata about the terrain source data, such as an attribution and a
- * description.
- * @classcategory Models/Maps/Assets
- * @class CesiumTerrain
- * @name CesiumTerrain
- * @extends MapAsset
- * @since 2.18.0
- * @constructor
- */
- var CesiumTerrain = MapAsset.extend(
- /** @lends CesiumTerrain.prototype */ {
-
- /**
- * The name of this type of model
- * @type {string}
- */
- type: 'CesiumTerrain',
-
- /**
- * Options that are supported for creating terrain in Cesium. Any properties
- * provided here are passed to the Cesium constructor function for the Terrain
- * Provider, so other properties that are documented in Cesium are also supported.
- * See `options` here:
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/CesiumTerrainProvider.html?classFilter=TerrainProvider}
- * @typedef {Object} CesiumTerrain#cesiumOptions
- * @property {string|number} ionAssetId - If this terrain is hosted by Cesium Ion,
- * then Ion asset ID.
- */
-
- /**
- * Default attributes for CesiumTerrain models
- * @name CesiumTerrain#defaults
- * @extends MapAsset#defaults
- * @type {Object}
- * @property {'CesiumTerrainProvider'} type A string indicating a Cesium Terrain
- * Provider, see
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/?classFilter=TerrainProvider}
- * @property {Cesium.TerrainProvider} cesiumModel A model created and used by
- * Cesium that organizes the data to display in the Cesium Widget. See
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/TerrainProvider.html}
- * @property {CesiumTerrain#cesiumOptions} cesiumOptions options are passed to the
- * function that creates the Cesium model. The properties of options are specific
- * to each type of asset
- */
- defaults: function () {
- return _.extend(
- this.constructor.__super__.defaults(),
- {
- type: 'CesiumTerrainProvider',
- cesiumModel: null,
- cesiumOptions: {},
- }
+"use strict";
+
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "cesium",
+ "models/maps/assets/MapAsset",
+], function ($, _, Backbone, Cesium, MapAsset) {
+ /**
+ * @classdesc A CesiumTerrain Model comprises the information required to fetch 3D
+ * terrain, such as mountain peaks and valleys, to display in Cesium. A terrain model
+ * also contains metadata about the terrain source data, such as an attribution and a
+ * description.
+ * @classcategory Models/Maps/Assets
+ * @class CesiumTerrain
+ * @name CesiumTerrain
+ * @extends MapAsset
+ * @since 2.18.0
+ * @constructor
+ */
+ var CesiumTerrain = MapAsset.extend(
+ /** @lends CesiumTerrain.prototype */ {
+ /**
+ * The name of this type of model
+ * @type {string}
+ */
+ type: "CesiumTerrain",
+
+ /**
+ * Options that are supported for creating terrain in Cesium. Any properties
+ * provided here are passed to the Cesium constructor function for the Terrain
+ * Provider, so other properties that are documented in Cesium are also supported.
+ * See `options` here:
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/CesiumTerrainProvider.html?classFilter=TerrainProvider}
+ * @typedef {Object} CesiumTerrain#cesiumOptions
+ * @property {string|number} ionAssetId - If this terrain is hosted by Cesium Ion,
+ * then Ion asset ID.
+ */
+
+ /**
+ * Default attributes for CesiumTerrain models
+ * @name CesiumTerrain#defaults
+ * @extends MapAsset#defaults
+ * @type {Object}
+ * @property {'CesiumTerrainProvider'} type A string indicating a Cesium Terrain
+ * Provider, see
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/?classFilter=TerrainProvider}
+ * @property {Cesium.TerrainProvider} cesiumModel A model created and used by
+ * Cesium that organizes the data to display in the Cesium Widget. See
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/TerrainProvider.html}
+ * @property {CesiumTerrain#cesiumOptions} cesiumOptions options are passed to the
+ * function that creates the Cesium model. The properties of options are specific
+ * to each type of asset
+ */
+ defaults: function () {
+ return _.extend(this.constructor.__super__.defaults(), {
+ type: "CesiumTerrainProvider",
+ cesiumModel: null,
+ cesiumOptions: {},
+ });
+ },
+
+ /**
+ * Executed when a new CesiumTerrain model is created.
+ * @param {Object} [attributes] The initial values of the attributes, which will
+ * be set on the model.
+ * @param {Object} [options] Options for the initialize function.
+ */
+ initialize: function (attributes, options) {
+ try {
+ MapAsset.prototype.initialize.call(this, attributes, options);
+
+ this.createCesiumModel();
+ } catch (error) {
+ console.log(
+ "There was an error initializing a CesiumTerrain model" +
+ ". Error details: " +
+ error,
);
- },
-
- /**
- * Executed when a new CesiumTerrain model is created.
- * @param {Object} [attributes] The initial values of the attributes, which will
- * be set on the model.
- * @param {Object} [options] Options for the initialize function.
- */
- initialize: function (attributes, options) {
- try {
- MapAsset.prototype.initialize.call(this, attributes, options);
-
- this.createCesiumModel();
- }
- catch (error) {
- console.log(
- 'There was an error initializing a CesiumTerrain model' +
- '. Error details: ' + error
- );
- }
- },
-
- /**
- * Creates a Cesium TerrainProvider that contains information about where the
- * terrain data should be requested from and how to render it in Cesium. See
- * {@link https://cesium.com/learn/cesiumjs/ref-doc/TerrainProvider.html?classFilter=terrain}
- * @param {Boolean} recreate - Set recreate to true to force the function create
- * the Cesium Model again. Otherwise, if a cesium model already exists, that is
- * returned instead.
- */
- createCesiumModel: function (recreate = false) {
-
- var model = this;
- var cesiumOptions = model.getCesiumOptions();
- var type = this.get('type')
- var terrainFunction = Cesium[type]
-
- // If the cesium model already exists, don't create it again unless specified
- if (!recreate && this.get('cesiumModel')) {
- return this.get('cesiumModel')
- }
-
- model.resetStatus();
-
- // If this tileset is a Cesium Ion resource, set the url from the
- // asset Id
- cesiumOptions.url = this.getCesiumURL(cesiumOptions) || cesiumOptions.url;
-
- if (terrainFunction && typeof terrainFunction === 'function') {
- let terrain = new terrainFunction(cesiumOptions)
- terrain.readyPromise
- .then(function () {
- model.set('cesiumModel', terrain)
- model.set('status', 'ready')
- })
- .otherwise(function (error) {
- model.set('status', 'error');
- model.set('statusDetails', error)
- })
- } else {
- model.set('status', 'error')
- model.set('statusDetails', type + ' is not a supported imagery type.')
- }
-
- },
-
- /**
- * Checks whether there is an asset ID for a Cesium Ion resource and if
- * so, return the URL to the resource.
- * @returns {string} The URL to the Cesium Ion resource
- * @since 2.26.0
- */
- getCesiumURL: function () {
- try {
- const cesiumOptions = this.getCesiumOptions();
- if (!cesiumOptions || !cesiumOptions.ionAssetId) return null
- // The Cesium Ion ID of the resource to access
- const assetId = Number(cesiumOptions.ionAssetId)
- // Options to pass to Cesium's fromAssetId function. Access token
- // needs to be set before requesting cesium ion resources
- const ionResourceOptions = {
- accessToken: cesiumOptions.cesiumToken ||
- MetacatUI.appModel.get('cesiumToken')
- }
- // Create the new URL and set it on the model options
- return Cesium.IonResource.fromAssetId(assetId, ionResourceOptions);
- }
- catch (error) {
- console.log(
- 'There was an error settings a Cesium URL in a Cesium3DTileset' +
- '. Error details: ' + error
- );
- }
- },
-
- // /**
- // * Parses the given input into a JSON object to be set on the model.
- // *
- // * @param {TODO} input - The raw response object
- // * @return {TODO} - The JSON object of all the CesiumTerrain attributes
- // */
- // parse: function (input) {
-
- // try {
-
- // var modelJSON = {};
-
- // return modelJSON
-
- // }
- // catch (error) {
- // console.log(
- // 'There was an error parsing a CesiumTerrain model' +
- // '. Error details: ' + error
- // );
- // }
-
- // },
-
- // /**
- // * Overrides the default Backbone.Model.validate.function() to check if this if
- // * the values set on this model are valid.
- // *
- // * @param {Object} [attrs] - A literal object of model attributes to validate.
- // * @param {Object} [options] - A literal object of options for this validation
- // * process
- // *
- // * @return {Object} - Returns a literal object with the invalid attributes and
- // * their corresponding error message, if there are any. If there are no errors,
- // * returns nothing.
- // */
- // validate: function (attrs, options) {
- // try {
-
- // }
- // catch (error) {
- // console.log(
- // 'There was an error validating a CesiumTerrain model' +
- // '. Error details: ' + error
- // );
- // }
- // },
-
- // /**
- // * Creates a string using the values set on this model's attributes.
- // * @return {string} The CesiumTerrain string
- // */
- // serialize: function () {
- // try {
- // var serializedTerrain = '';
-
- // return serializedTerrain;
- // }
- // catch (error) {
- // console.log(
- // 'There was an error serializing a CesiumTerrain model' +
- // '. Error details: ' + error
- // );
- // }
- // },
-
- });
-
- return CesiumTerrain;
-
- }
-);
+ }
+ },
+
+ /**
+ * Creates a Cesium TerrainProvider that contains information about where the
+ * terrain data should be requested from and how to render it in Cesium. See
+ * {@link https://cesium.com/learn/cesiumjs/ref-doc/TerrainProvider.html?classFilter=terrain}
+ * @param {Boolean} recreate - Set recreate to true to force the function create
+ * the Cesium Model again. Otherwise, if a cesium model already exists, that is
+ * returned instead.
+ */
+ createCesiumModel: function (recreate = false) {
+ var model = this;
+ var cesiumOptions = model.getCesiumOptions();
+ var type = this.get("type");
+ var terrainFunction = Cesium[type];
+
+ // If the cesium model already exists, don't create it again unless specified
+ if (!recreate && this.get("cesiumModel")) {
+ return this.get("cesiumModel");
+ }
+
+ model.resetStatus();
+
+ // If this tileset is a Cesium Ion resource, set the url from the
+ // asset Id
+ cesiumOptions.url =
+ this.getCesiumURL(cesiumOptions) || cesiumOptions.url;
+
+ if (terrainFunction && typeof terrainFunction === "function") {
+ let terrain = new terrainFunction(cesiumOptions);
+ terrain.readyPromise
+ .then(function () {
+ model.set("cesiumModel", terrain);
+ model.set("status", "ready");
+ })
+ .otherwise(function (error) {
+ model.set("status", "error");
+ model.set("statusDetails", error);
+ });
+ } else {
+ model.set("status", "error");
+ model.set(
+ "statusDetails",
+ type + " is not a supported imagery type.",
+ );
+ }
+ },
+
+ /**
+ * Checks whether there is an asset ID for a Cesium Ion resource and if
+ * so, return the URL to the resource.
+ * @returns {string} The URL to the Cesium Ion resource
+ * @since 2.26.0
+ */
+ getCesiumURL: function () {
+ try {
+ const cesiumOptions = this.getCesiumOptions();
+ if (!cesiumOptions || !cesiumOptions.ionAssetId) return null;
+ // The Cesium Ion ID of the resource to access
+ const assetId = Number(cesiumOptions.ionAssetId);
+ // Options to pass to Cesium's fromAssetId function. Access token
+ // needs to be set before requesting cesium ion resources
+ const ionResourceOptions = {
+ accessToken:
+ cesiumOptions.cesiumToken ||
+ MetacatUI.appModel.get("cesiumToken"),
+ };
+ // Create the new URL and set it on the model options
+ return Cesium.IonResource.fromAssetId(assetId, ionResourceOptions);
+ } catch (error) {
+ console.log(
+ "There was an error settings a Cesium URL in a Cesium3DTileset" +
+ ". Error details: " +
+ error,
+ );
+ }
+ },
+
+ // /**
+ // * Parses the given input into a JSON object to be set on the model.
+ // *
+ // * @param {TODO} input - The raw response object
+ // * @return {TODO} - The JSON object of all the CesiumTerrain attributes
+ // */
+ // parse: function (input) {
+
+ // try {
+
+ // var modelJSON = {};
+
+ // return modelJSON
+
+ // }
+ // catch (error) {
+ // console.log(
+ // 'There was an error parsing a CesiumTerrain model' +
+ // '. Error details: ' + error
+ // );
+ // }
+
+ // },
+
+ // /**
+ // * Overrides the default Backbone.Model.validate.function() to check if this if
+ // * the values set on this model are valid.
+ // *
+ // * @param {Object} [attrs] - A literal object of model attributes to validate.
+ // * @param {Object} [options] - A literal object of options for this validation
+ // * process
+ // *
+ // * @return {Object} - Returns a literal object with the invalid attributes and
+ // * their corresponding error message, if there are any. If there are no errors,
+ // * returns nothing.
+ // */
+ // validate: function (attrs, options) {
+ // try {
+
+ // }
+ // catch (error) {
+ // console.log(
+ // 'There was an error validating a CesiumTerrain model' +
+ // '. Error details: ' + error
+ // );
+ // }
+ // },
+
+ // /**
+ // * Creates a string using the values set on this model's attributes.
+ // * @return {string} The CesiumTerrain string
+ // */
+ // serialize: function () {
+ // try {
+ // var serializedTerrain = '';
+
+ // return serializedTerrain;
+ // }
+ // catch (error) {
+ // console.log(
+ // 'There was an error serializing a CesiumTerrain model' +
+ // '. Error details: ' + error
+ // );
+ // }
+ // },
+ },
+ );
+
+ return CesiumTerrain;
+});
diff --git a/src/js/models/maps/assets/CesiumVectorData.js b/src/js/models/maps/assets/CesiumVectorData.js
index 18ca4eb70..856345d9c 100644
--- a/src/js/models/maps/assets/CesiumVectorData.js
+++ b/src/js/models/maps/assets/CesiumVectorData.js
@@ -15,13 +15,18 @@ define([
AssetColor,
AssetColorPalette,
VectorFilters,
- IconUtilities
+ IconUtilities,
) {
// Source: https://fontawesome.com/v6/icons/location-dot?f=classic&s=solid
- const PIN_SVG_STRING = ' ';
+ const PIN_SVG_STRING =
+ ' ';
const PIN_OUTLINE_WIDTH = 30; // The width of the stroke around the pin is relative to the viewBox
const PIN_OUTLINE_COLOR = "white";
- const PIN_SVG = IconUtilities.formatSvgForCesiumBillboard(PIN_SVG_STRING, PIN_OUTLINE_WIDTH, PIN_OUTLINE_COLOR);
+ const PIN_SVG = IconUtilities.formatSvgForCesiumBillboard(
+ PIN_SVG_STRING,
+ PIN_OUTLINE_WIDTH,
+ PIN_OUTLINE_COLOR,
+ );
/**
* @classdesc A CesiumVectorData Model is a vector layer (excluding
@@ -122,7 +127,7 @@ define([
) {
this.set(
"outlineColor",
- new AssetColor({ color: assetConfig.outlineColor })
+ new AssetColor({ color: assetConfig.outlineColor }),
);
}
@@ -132,7 +137,7 @@ define([
) {
this.set(
"highlightColor",
- new AssetColor({ color: assetConfig.highlightColor })
+ new AssetColor({ color: assetConfig.highlightColor }),
);
}
@@ -199,7 +204,7 @@ define([
// For GeoJSON and CZML data sources
if (!cesiumOptions || !cesiumOptions.data) {
model.setError(
- "No data was provided to create a Cesium DataSource model."
+ "No data was provided to create a Cesium DataSource model.",
);
return;
}
@@ -409,7 +414,7 @@ define([
return;
}
const time = Cesium.JulianDate.now();
- let displayReadyNow = true
+ let displayReadyNow = true;
for (let x = 0; x < visualizers.length; x++) {
displayReadyNow = visualizers[x].update(time) && displayReadyNow;
}
@@ -620,7 +625,7 @@ define([
entity.billboard = {
image: IconUtilities.svgToBase64(PIN_SVG),
width: size,
- height: size
+ height: size,
};
// To convert the automatically created billboards to points instead:
// entity.billboard = undefined; entity.point = new
@@ -651,7 +656,7 @@ define([
color.red,
color.green,
color.blue,
- color.alpha
+ color.alpha,
);
},
@@ -680,7 +685,7 @@ define([
*/
getSelectedStyles: function (entity) {
const highlightColor = this.colorToCesiumColor(
- this.get("highlightColor")
+ this.get("highlightColor"),
);
return {
color: highlightColor || this.colorForEntity(entity),
@@ -709,7 +714,7 @@ define([
return null;
}
const outlineColor = this.colorToCesiumColor(
- this.get("outlineColor")?.get("color")
+ this.get("outlineColor")?.get("color"),
);
return {
color: color,
@@ -775,13 +780,13 @@ define([
state = dataSourceDisplay.getBoundingSphere(
entities[i],
false,
- boundingSphereScratch
+ boundingSphereScratch,
);
if (state === Cesium.BoundingSphereState.PENDING) {
return false;
} else if (state !== Cesium.BoundingSphereState.FAILED) {
boundingSpheres.push(
- Cesium.BoundingSphere.clone(boundingSphereScratch)
+ Cesium.BoundingSphere.clone(boundingSphereScratch),
);
}
}
@@ -794,7 +799,7 @@ define([
console.log("Error getting bounding sphere.", e);
});
},
- }
+ },
);
return CesiumVectorData;
diff --git a/src/js/models/maps/assets/MapAsset.js b/src/js/models/maps/assets/MapAsset.js
index e1b0b817a..0c108f8ed 100644
--- a/src/js/models/maps/assets/MapAsset.js
+++ b/src/js/models/maps/assets/MapAsset.js
@@ -7,7 +7,14 @@ define([
"models/maps/AssetColorPalette",
"common/IconUtilities",
MetacatUI.root + "/components/dayjs.min.js",
-], function (_, Backbone, PortalImage, AssetColorPalette, IconUtilities, dayjs) {
+], function (
+ _,
+ Backbone,
+ PortalImage,
+ AssetColorPalette,
+ IconUtilities,
+ dayjs,
+) {
/**
* @classdesc A MapAsset Model comprises information required to fetch source data for
* some asset or resource that is displayed in a map, such as imagery (raster) tiles,
@@ -325,7 +332,7 @@ define([
if (assetConfig.colorPalette) {
this.set(
"colorPalette",
- new AssetColorPalette(assetConfig.colorPalette)
+ new AssetColorPalette(assetConfig.colorPalette),
);
}
@@ -338,14 +345,15 @@ define([
model.set("iconStatus", "fetching");
// If the string is not an SVG then assume it is a PID and try to fetch
// the SVG file.
- IconUtilities.fetchIcon(assetConfig.icon)
- .then(icon => model.updateIcon(icon));
+ IconUtilities.fetchIcon(assetConfig.icon).then((icon) =>
+ model.updateIcon(icon),
+ );
}
} catch (error) {
console.log(
"Failed to fetch an icon for a MapAsset" +
". Error details: " +
- error
+ error,
);
model.set("iconStatus", "error");
}
@@ -370,14 +378,14 @@ define([
// Write a helpful error message
switch (error.statusCode) {
case 404:
- details = 'The resource was not found (error code 404).'
+ details = "The resource was not found (error code 404).";
break;
case 500:
- details = 'There was a server error (error code 500).'
+ details = "There was a server error (error code 500).";
break;
}
- this.set('status', 'error');
- this.set('statusDetails', details)
+ this.set("status", "error");
+ this.set("statusDetails", details);
},
/**
@@ -385,8 +393,8 @@ define([
* @since 2.27.0
*/
setReady: function () {
- this.set('status', 'ready')
- this.set('statusDetails', null)
+ this.set("status", "ready");
+ this.set("statusDetails", null);
},
/**
@@ -409,8 +417,8 @@ define([
this.handleError();
return;
} else {
- const vis = this.get("originalVisibility")
- if(typeof vis === "boolean"){
+ const vis = this.get("originalVisibility");
+ if (typeof vis === "boolean") {
this.set("visible", vis);
}
}
@@ -446,7 +454,7 @@ define([
this.listenToOnce(
this,
"change:mapModel",
- this.listenToSelectedFeatures
+ this.listenToSelectedFeatures,
);
return;
}
@@ -457,14 +465,14 @@ define([
this.listenToOnce(
mapModel,
"change:interactions",
- this.listenToSelectedFeatures
+ this.listenToSelectedFeatures,
);
return;
}
const selectedFeatures = mapModel.getSelectedFeatures();
- if(selectedFeatures){
+ if (selectedFeatures) {
this.stopListening(selectedFeatures, "update");
this.listenTo(selectedFeatures, "update", this.updateAppearance);
}
@@ -534,7 +542,7 @@ define([
usesFeatureType: function (feature) {
const ft = this.get("featureType");
if (!feature || !ft) return false;
- if (!feature instanceof ft) return false;
+ if ((!feature) instanceof ft) return false;
return true;
},
@@ -622,7 +630,7 @@ define([
console.log(
"There was an error adding custom properties. Returning properties " +
"unchanged. Error details: " +
- error
+ error,
);
return properties;
}
@@ -657,7 +665,7 @@ define([
console.log(
"There was an error formatting a date for a Feature model" +
". Error details: " +
- error
+ error,
);
return "";
}
@@ -691,7 +699,7 @@ define([
console.log(
"There was an error formatting a string for a Feature model" +
". Error details: " +
- error
+ error,
);
return "";
}
@@ -850,7 +858,7 @@ define([
this.set("visible", true);
}
},
- }
+ },
);
return MapAsset;
diff --git a/src/js/models/maps/viewfinder/ExpansionPanelsModel.js b/src/js/models/maps/viewfinder/ExpansionPanelsModel.js
index dd2fd38cc..3e710d776 100644
--- a/src/js/models/maps/viewfinder/ExpansionPanelsModel.js
+++ b/src/js/models/maps/viewfinder/ExpansionPanelsModel.js
@@ -1,54 +1,55 @@
-'use strict';
+"use strict";
define([], () => {
/**
- * @class ExpansionPanelsModel
- * @classdesc ExpansionPanelsModel maintains state for multiple
- * ExpansionPanelView instances so that only one is open at a time.
- * @classcategory Models/Maps
- */
+ * @class ExpansionPanelsModel
+ * @classdesc ExpansionPanelsModel maintains state for multiple
+ * ExpansionPanelView instances so that only one is open at a time.
+ * @classcategory Models/Maps
+ */
const ExpansionPanelsModel = Backbone.Model.extend(
- /** @lends ExpansionPanelsModel.prototype */{
+ /** @lends ExpansionPanelsModel.prototype */ {
/**
* @name ExpansionPanelsModel#defaults
* @type {Object}
* @property {ExpansionPanelView[]} panels The expansion panel views that
* are meant to have only a single panel open at a time.
* @property {boolean} isMulti Whether multiple panels can be open at the
- * same time when displayed in a group of panels.
- * @extends Backbone.Model
+ * same time when displayed in a group of panels.
+ * @extends Backbone.Model
*/
defaults() {
return {
panels: [],
isMulti: false,
- }
+ };
},
/**
* Register a panel to coordinate collapse state.
- * @property {ExpansionPanelView} panel The expansion panel view to be
+ * @property {ExpansionPanelView} panel The expansion panel view to be
* tracked.
*/
register(panel) {
- this.set('panels', [...this.get('panels'), panel]);
+ this.set("panels", [...this.get("panels"), panel]);
},
/**
* Collapse all panels except for the newly opened panel for certain open
* modes.
- * @property {ExpansionPanelView} openedPanel The expansion panel view that
+ * @property {ExpansionPanelView} openedPanel The expansion panel view that
* should remain open.
*/
maybeCollapseOthers(openedPanel) {
- const isSingleOpenMode = !this.get('isMulti');
- for (const panel of this.get('panels')) {
+ const isSingleOpenMode = !this.get("isMulti");
+ for (const panel of this.get("panels")) {
if (isSingleOpenMode && panel !== openedPanel) {
panel.collapse();
}
}
- }
- });
+ },
+ },
+ );
return ExpansionPanelsModel;
-});
\ No newline at end of file
+});
diff --git a/src/js/models/maps/viewfinder/ViewfinderModel.js b/src/js/models/maps/viewfinder/ViewfinderModel.js
index a64b4826e..bb364de19 100644
--- a/src/js/models/maps/viewfinder/ViewfinderModel.js
+++ b/src/js/models/maps/viewfinder/ViewfinderModel.js
@@ -1,242 +1,257 @@
-'use strict';
-
-define(
- [
- 'underscore',
- 'backbone',
- 'cesium',
- 'models/geocoder/GeocoderSearch',
- 'models/maps/GeoPoint'
- ],
- (_, Backbone, Cesium, GeocoderSearch, GeoPoint) => {
- const EMAIL = MetacatUI.appModel.get('emailContact');
- const NO_RESULTS_MESSAGE = 'No search results found, try using another place name.';
- const API_ERROR = 'We\'re having trouble identifying locations on the map right now. Please reach out to support for help with this issue' + (EMAIL ? `: ${EMAIL}` : '.');
- const PLACES_API_ERROR = API_ERROR;
- const GEOCODING_API_ERROR = API_ERROR;
-
- /**
- * @class ViewfinderModel
- * @classdesc ViewfinderModel maintains state for the ViewfinderView and
- * interfaces with location searching services.
- * @classcategory Models/Maps
- * @since 2.28.0
- * @extends Backbone.Model
- */
- const ViewfinderModel = Backbone.Model.extend(
- /** @lends ViewfinderModel.prototype */{
- /**
- * @name ViewfinderModel#defaults
- * @type {Object}
- * @property {string} error is the current error string to be displayed
- * in the UI.
- * @property {number} focusIndex is the index of the element
- * in the list of predictions that shoudl be highlighted as focus.
- * @property {Prediction[]} predictions a list of Predictions models that
- * correspond to the user's search query.
- * @property {string} query the user's search query.
- * @since 2.28.0
- */
- defaults() {
- return {
- error: '',
- focusIndex: -1,
- predictions: [],
- query: '',
- zoomPresets: [],
+"use strict";
+
+define([
+ "underscore",
+ "backbone",
+ "cesium",
+ "models/geocoder/GeocoderSearch",
+ "models/maps/GeoPoint",
+], (_, Backbone, Cesium, GeocoderSearch, GeoPoint) => {
+ const EMAIL = MetacatUI.appModel.get("emailContact");
+ const NO_RESULTS_MESSAGE =
+ "No search results found, try using another place name.";
+ const API_ERROR =
+ "We're having trouble identifying locations on the map right now. Please reach out to support for help with this issue" +
+ (EMAIL ? `: ${EMAIL}` : ".");
+ const PLACES_API_ERROR = API_ERROR;
+ const GEOCODING_API_ERROR = API_ERROR;
+
+ /**
+ * @class ViewfinderModel
+ * @classdesc ViewfinderModel maintains state for the ViewfinderView and
+ * interfaces with location searching services.
+ * @classcategory Models/Maps
+ * @since 2.28.0
+ * @extends Backbone.Model
+ */
+ const ViewfinderModel = Backbone.Model.extend(
+ /** @lends ViewfinderModel.prototype */ {
+ /**
+ * @name ViewfinderModel#defaults
+ * @type {Object}
+ * @property {string} error is the current error string to be displayed
+ * in the UI.
+ * @property {number} focusIndex is the index of the element
+ * in the list of predictions that shoudl be highlighted as focus.
+ * @property {Prediction[]} predictions a list of Predictions models that
+ * correspond to the user's search query.
+ * @property {string} query the user's search query.
+ * @since 2.28.0
+ */
+ defaults() {
+ return {
+ error: "",
+ focusIndex: -1,
+ predictions: [],
+ query: "",
+ zoomPresets: [],
+ };
+ },
+
+ /**
+ * @param {Map} mapModel is the Map model that the ViewfinderModel is
+ * managing for the corresponding ViewfinderView.
+ */
+ initialize({ mapModel }) {
+ this.geocoderSearch = new GeocoderSearch();
+ this.mapModel = mapModel;
+ this.allLayers = this.mapModel.getAllLayers();
+
+ this.set(
+ "zoomPresets",
+ mapModel.get("zoomPresetsCollection")?.models || [],
+ );
+ },
+
+ /**
+ * Get autocompletion predictions from the GeocoderSearch model.
+ * @param {string} rawQuery is the user's search query with spaces.
+ */
+ async autocompleteSearch(rawQuery) {
+ const query = rawQuery.trim();
+ if (this.get("query") === query) {
+ return;
+ } else if (!query) {
+ this.set({ error: "", predictions: [], query: "", focusIndex: -1 });
+ return;
+ } else if (GeoPoint.couldBeLatLong(query)) {
+ this.set({ predictions: [], query: "", focusIndex: -1 });
+ return;
+ }
+
+ // Unset error so the error will fire a change event even if it is the
+ // same error as already exists.
+ this.unset("error", { silent: true });
+
+ try {
+ // User is looking for autocompletions.
+ const predictions = await this.geocoderSearch.autocomplete(query);
+ const error = predictions.length === 0 ? NO_RESULTS_MESSAGE : "";
+ this.set({ error, focusIndex: -1, predictions, query });
+ } catch (e) {
+ if (
+ e.code === "REQUEST_DENIED" &&
+ e.endpoint === "PLACES_AUTOCOMPLETE"
+ ) {
+ this.set({
+ error: PLACES_API_ERROR,
+ focusIndex: -1,
+ predictions: [],
+ query,
+ });
+ } else {
+ this.set({
+ error: NO_RESULTS_MESSAGE,
+ focusIndex: -1,
+ predictions: [],
+ query,
+ });
}
- },
-
- /**
- * @param {Map} mapModel is the Map model that the ViewfinderModel is
- * managing for the corresponding ViewfinderView.
- */
- initialize({ mapModel }) {
- this.geocoderSearch = new GeocoderSearch();
- this.mapModel = mapModel;
- this.allLayers = this.mapModel.getAllLayers();
-
- this.set('zoomPresets', mapModel.get('zoomPresetsCollection')?.models || []);
- },
-
- /**
- * Get autocompletion predictions from the GeocoderSearch model.
- * @param {string} rawQuery is the user's search query with spaces.
- */
- async autocompleteSearch(rawQuery) {
- const query = rawQuery.trim();
- if (this.get('query') === query) {
- return;
- } else if (!query) {
- this.set({ error: '', predictions: [], query: '', focusIndex: -1, });
- return;
- } else if (GeoPoint.couldBeLatLong(query)) {
- this.set({ predictions: [], query: '', focusIndex: -1, });
+ }
+ },
+
+ /**
+ * Decrement the focused index with a minimum value of 0. This corresponds
+ * to an ArrowUp key down event.
+ * Note: An ArrowUp key press while the current index is -1 will
+ * result in highlighting the first element in the list.
+ */
+ decrementFocusIndex() {
+ const currentIndex = this.get("focusIndex");
+ this.set("focusIndex", Math.max(0, currentIndex - 1));
+ },
+
+ /**
+ * Increment the focused index with a maximum value of the last value in
+ * the list. This corresponds to an ArrowDown key down event.
+ */
+ incrementFocusIndex() {
+ const currentIndex = this.get("focusIndex");
+ this.set(
+ "focusIndex",
+ Math.min(currentIndex + 1, this.get("predictions").length - 1),
+ );
+ },
+
+ /**
+ * Reset the focused index back to the initial value so that no element
+ * in the UI is highlighted.
+ */
+ resetFocusIndex() {
+ this.set("focusIndex", -1);
+ },
+
+ /**
+ * Navigate to the GeocodedLocation.
+ * @param {GeocodedLocation} geocoding is the location that corresponds
+ * to the the selected prediction.
+ */
+ goToLocation(geocoding) {
+ if (!geocoding) return;
+
+ const coords = geocoding.get("box").getCoords();
+ this.mapModel.zoomTo({
+ destination: Cesium.Rectangle.fromDegrees(
+ coords.west,
+ coords.south,
+ coords.east,
+ coords.north,
+ ),
+ });
+ },
+
+ /**
+ * Select a ZoomPresetModel from the list of presets and navigate there.
+ * This function hides all layers that are not to be visible according to
+ * the ZoomPresetModel configuration.
+ * @param {ZoomPresetModel} preset A user selected preset for which to
+ * enable layers and navigate.
+ */
+ selectZoomPreset(preset) {
+ const enabledLayerIds = preset.get("enabledLayerIds");
+ for (const layer of this.allLayers) {
+ const isVisible = enabledLayerIds.includes(layer.get("layerId"));
+ // Show or hide the layer according to the preset.
+ layer.set("visible", isVisible);
+ }
+
+ this.mapModel.zoomTo(preset.get("geoPoint"));
+ },
+
+ /**
+ * Select a prediction from the list of predictions and navigate there.
+ * @param {Prediction} prediction is the user-selected Prediction that
+ * needs to be geocoded and navigated to.
+ */
+ async selectPrediction(prediction) {
+ if (!prediction) return;
+
+ try {
+ const geocodings = await this.geocoderSearch.geocode(prediction);
+
+ if (geocodings.length === 0) {
+ this.set("error", NO_RESULTS_MESSAGE);
return;
}
- // Unset error so the error will fire a change event even if it is the
- // same error as already exists.
- this.unset('error', { silent: true });
-
- try {
- // User is looking for autocompletions.
- const predictions = await this.geocoderSearch.autocomplete(query);
- const error = predictions.length === 0 ? NO_RESULTS_MESSAGE : '';
- this.set({ error, focusIndex: -1, predictions, query, });
- } catch (e) {
- if (e.code === 'REQUEST_DENIED' && e.endpoint === 'PLACES_AUTOCOMPLETE') {
- this.set({
- error: PLACES_API_ERROR,
- focusIndex: -1,
- predictions: [],
- query,
- });
- } else {
- this.set({
- error: NO_RESULTS_MESSAGE,
- focusIndex: -1,
- predictions: [],
- query,
- });
- }
- }
- },
-
- /**
- * Decrement the focused index with a minimum value of 0. This corresponds
- * to an ArrowUp key down event.
- * Note: An ArrowUp key press while the current index is -1 will
- * result in highlighting the first element in the list.
- */
- decrementFocusIndex() {
- const currentIndex = this.get('focusIndex');
- this.set('focusIndex', Math.max(0, currentIndex - 1));
- },
-
- /**
- * Increment the focused index with a maximum value of the last value in
- * the list. This corresponds to an ArrowDown key down event.
- */
- incrementFocusIndex() {
- const currentIndex = this.get('focusIndex');
- this.set(
- 'focusIndex',
- Math.min(currentIndex + 1, this.get('predictions').length - 1)
- );
- },
-
- /**
- * Reset the focused index back to the initial value so that no element
- * in the UI is highlighted.
- */
- resetFocusIndex() {
- this.set('focusIndex', -1);
- },
-
- /**
- * Navigate to the GeocodedLocation.
- * @param {GeocodedLocation} geocoding is the location that corresponds
- * to the the selected prediction.
- */
- goToLocation(geocoding) {
- if (!geocoding) return;
-
- const coords = geocoding.get('box').getCoords();
- this.mapModel.zoomTo({
- destination: Cesium.Rectangle.fromDegrees(
- coords.west,
- coords.south,
- coords.east,
- coords.north,
- )
- });
- },
-
- /**
- * Select a ZoomPresetModel from the list of presets and navigate there.
- * This function hides all layers that are not to be visible according to
- * the ZoomPresetModel configuration.
- * @param {ZoomPresetModel} preset A user selected preset for which to
- * enable layers and navigate.
- */
- selectZoomPreset(preset) {
- const enabledLayerIds = preset.get('enabledLayerIds');
- for (const layer of this.allLayers) {
- const isVisible = enabledLayerIds.includes(layer.get('layerId'));
- // Show or hide the layer according to the preset.
- layer.set('visible', isVisible);
+ this.trigger("selection-made", prediction.get("description"));
+ this.goToLocation(geocodings[0]);
+ } catch (e) {
+ if (
+ e.code === "REQUEST_DENIED" &&
+ e.endpoint === "GEOCODER_GEOCODE"
+ ) {
+ this.set({
+ error: GEOCODING_API_ERROR,
+ focusIndex: -1,
+ predictions: [],
+ });
+ } else {
+ this.set("error", NO_RESULTS_MESSAGE);
}
+ }
+ },
- this.mapModel.zoomTo(preset.get('geoPoint'));
- },
-
- /**
- * Select a prediction from the list of predictions and navigate there.
- * @param {Prediction} prediction is the user-selected Prediction that
- * needs to be geocoded and navigated to.
- */
- async selectPrediction(prediction) {
- if (!prediction) return;
-
- try {
- const geocodings = await this.geocoderSearch.geocode(prediction);
-
- if (geocodings.length === 0) {
- this.set('error', NO_RESULTS_MESSAGE)
- return;
- }
-
- this.trigger('selection-made', prediction.get('description'));
- this.goToLocation(geocodings[0]);
- } catch (e) {
- if (e.code === 'REQUEST_DENIED' && e.endpoint === 'GEOCODER_GEOCODE') {
- this.set({ error: GEOCODING_API_ERROR, focusIndex: -1, predictions: [] });
- } else {
- this.set('error', NO_RESULTS_MESSAGE)
- }
- }
- },
-
- /**
- * Event handler for Backbone.View configuration that is called whenever
- * the user clicks the search button or hits the Enter key.
- * @param {string} value is the query string.
- */
- async search(value) {
- if (!value) return;
-
- // This is not a lat,long value, so geocode the prediction instead.
- if (!GeoPoint.couldBeLatLong(value)) {
- const focusedIndex = Math.max(0, this.get("focusIndex"));
- this.selectPrediction(this.get('predictions')[focusedIndex]);
+ /**
+ * Event handler for Backbone.View configuration that is called whenever
+ * the user clicks the search button or hits the Enter key.
+ * @param {string} value is the query string.
+ */
+ async search(value) {
+ if (!value) return;
+
+ // This is not a lat,long value, so geocode the prediction instead.
+ if (!GeoPoint.couldBeLatLong(value)) {
+ const focusedIndex = Math.max(0, this.get("focusIndex"));
+ this.selectPrediction(this.get("predictions")[focusedIndex]);
+ return;
+ }
+
+ // Unset error so the error will fire a change event even if it is the
+ // same error as already exists.
+ this.unset("error", { silent: true });
+
+ try {
+ const geoPoint = new GeoPoint(value, { parse: true });
+ geoPoint.set("height", 10000 /* meters */);
+ if (geoPoint.isValid()) {
+ this.set("error", "");
+ this.mapModel.zoomTo(geoPoint);
return;
}
- // Unset error so the error will fire a change event even if it is the
- // same error as already exists.
- this.unset('error', { silent: true });
-
- try {
- const geoPoint = new GeoPoint(value, { parse: true });
- geoPoint.set("height", 10000 /* meters */);
- if (geoPoint.isValid()) {
- this.set('error', '');
- this.mapModel.zoomTo(geoPoint);
- return;
- }
-
- const errors = geoPoint.validationError;
- if (errors.latitude) {
- this.set('error', errors.latitude);
- } else if (errors.longitude) {
- this.set('error', errors.longitude);
- }
- } catch (e) {
- this.set('error', e.message);
+ const errors = geoPoint.validationError;
+ if (errors.latitude) {
+ this.set("error", errors.latitude);
+ } else if (errors.longitude) {
+ this.set("error", errors.longitude);
}
- },
- });
+ } catch (e) {
+ this.set("error", e.message);
+ }
+ },
+ },
+ );
- return ViewfinderModel;
- });
\ No newline at end of file
+ return ViewfinderModel;
+});
diff --git a/src/js/models/maps/viewfinder/ZoomPresetModel.js b/src/js/models/maps/viewfinder/ZoomPresetModel.js
index 0a7ad277c..13c30ad6a 100644
--- a/src/js/models/maps/viewfinder/ZoomPresetModel.js
+++ b/src/js/models/maps/viewfinder/ZoomPresetModel.js
@@ -1,60 +1,62 @@
-'use strict';
+"use strict";
-define(
- ['underscore', 'backbone', 'models/maps/GeoPoint'],
- (_, Backbone, GeoPoint) => {
- /**
- * @class ZoomPresetModel
- * @classdesc ZoomPresetModel represents a point of interest on a map that can
- * be configured within a MapView.
- * @classcategory Models/Maps
- * @extends Backbone.Model
- * @since 2.29.0
- */
- const ZoomPresetModel = Backbone.Model.extend(
+define(["underscore", "backbone", "models/maps/GeoPoint"], (
+ _,
+ Backbone,
+ GeoPoint,
+) => {
+ /**
+ * @class ZoomPresetModel
+ * @classdesc ZoomPresetModel represents a point of interest on a map that can
+ * be configured within a MapView.
+ * @classcategory Models/Maps
+ * @extends Backbone.Model
+ * @since 2.29.0
+ */
+ const ZoomPresetModel = Backbone.Model.extend(
/** @lends ZoomPresetModel.prototype */ {
+ /**
+ * @typedef {Object} ZoomPresetModelOptions
+ * @property {string} title The displayed title for the preset.
+ * @property {GeoPoint} geoPoint The location representing this preset,
+ * including height information.
+ * @property {string} description A brief description of the layers and
+ * location.
+ * @property {string[]} enabledLayerIds A list of layer IDs which are to
+ * be enabled for this preset.
+ * @property {string[]} enabledLayerLabels A list of layer labels which
+ * are enabled for this preset.
+ */
- /**
- * @typedef {Object} ZoomPresetModelOptions
- * @property {string} title The displayed title for the preset.
- * @property {GeoPoint} geoPoint The location representing this preset,
- * including height information.
- * @property {string} description A brief description of the layers and
- * location.
- * @property {string[]} enabledLayerIds A list of layer IDs which are to
- * be enabled for this preset.
- * @property {string[]} enabledLayerLabels A list of layer labels which
- * are enabled for this preset.
- */
+ /**
+ * @name ZoomPresetModel#defaults
+ * @type {ZoomPresetModelOptions}
+ */
+ defaults() {
+ return {
+ description: "",
+ enabledLayerIds: [],
+ enabledLayerLabels: [],
+ geoPoint: null,
+ title: "",
+ };
+ },
- /**
- * @name ZoomPresetModel#defaults
- * @type {ZoomPresetModelOptions}
- */
- defaults() {
- return {
- description: '',
- enabledLayerIds: [],
- enabledLayerLabels: [],
- geoPoint: null,
- title: '',
- }
- },
+ /**
+ * @param {Object} position The latitude, longitude, and height of this
+ * ZoomPresetModel's GeoPoint.
+ */
+ parse({ position, ...rest }) {
+ const geoPoint = new GeoPoint({
+ latitude: position.latitude,
+ longitude: position.longitude,
+ height: position.height,
+ });
- /**
- * @param {Object} position The latitude, longitude, and height of this
- * ZoomPresetModel's GeoPoint.
- */
- parse({ position, ...rest }) {
- const geoPoint = new GeoPoint({
- latitude: position.latitude,
- longitude: position.longitude,
- height: position.height
- });
+ return { geoPoint, ...rest };
+ },
+ },
+ );
- return { geoPoint, ...rest };
- },
- });
-
- return ZoomPresetModel;
- });
\ No newline at end of file
+ return ZoomPresetModel;
+});
diff --git a/src/js/models/metadata/ScienceMetadata.js b/src/js/models/metadata/ScienceMetadata.js
index af941c973..7a071c883 100644
--- a/src/js/models/metadata/ScienceMetadata.js
+++ b/src/js/models/metadata/ScienceMetadata.js
@@ -1,8 +1,11 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/DataONEObject'],
- function($, _, Backbone, DataONEObject){
-
- /**
+define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
+ $,
+ _,
+ Backbone,
+ DataONEObject,
+) {
+ /**
@class ScienceMetadata
@classdesc ScienceMetadata represents a generic science metadata document.
It's properties are limited to those shared across subclasses,
@@ -11,187 +14,198 @@ define(['jquery', 'underscore', 'backbone', 'models/DataONEObject'],
* @classcategory Models/Metadata
* @extends DataONEObject
*/
- var ScienceMetadata = DataONEObject.extend(
- /** @lends ScienceMetadata.prototype */{
-
- // Only add fields present in the Solr service to the defaults
- defaults: function(){ return _.extend(DataONEObject.prototype.defaults(), {
- abstract: [],
- attribute: [],
- attributeDescription: [],
- attributeLabel: [],
- attributeName: [],
- attributeUnit: [],
- author: null,
- authorGivenName: null,
- authoritativeMN: null,
- authorLastName: [],
- authorSurName: null,
- beginDate: null,
- changePermission: [],
- contactOrganization: [],
- datasource: null,
- dataUrl: null,
- dateModified: null,
- datePublished: null,
- dateUploaded: null,
- decade: null,
- edition: null,
- endDate: null,
- fileID: null,
- formatType: "METADATA",
- gcmdKeyword: [],
- investigator: [],
- isDocumentedBy: [],
- isPublic: null,
- keyConcept: [],
- keywords: [],
- mediaType: null,
- mediaTypeProperty: [],
- origin: [],
- originator: [],
- placeKey: [],
- presentationCat: null,
- project: null,
- pubDate: null,
- purpose: null,
- readPermission: [],
- relatedOrganizations: [],
- replicaMN: [],
- sensor: [],
- sensorText: [],
- source: [],
- scientificName: [],
- title: [],
- type: "Metadata",
- species: [],
- genus: [],
- family: [],
- class: [],
- phylum: [],
- order: [],
- kingdom: [],
- westBoundCoord: null,
- eastBoundCoord: null,
- northBoundCoord: null,
- southBoundCoord: null,
- site: [],
- namedLocation: [],
- noBoundingBox: null,
- geoform: null,
- isSpatial: null,
- sortOrder: 1,
- geohash_1: [],
- geohash_2: [],
- geohash_3: [],
- geohash_4: [],
- geohash_5: [],
- geohash_6: [],
- geohash_7: [],
- geohash_8: [],
- geohash_9: [],
- sem_annotated_by: [],
- sem_annotates: [],
- sem_annotation: [],
- sem_comment: []
- }) },
-
- type: "ScienceMetadata",
-
- nodeNameMap: function(){ return this.constructor.__super__.nodeNameMap(); },
-
- /* Initialize a ScienceMetadata object */
- initialize: function(attributes) {
- // Call initialize for the super class
- DataONEObject.prototype.initialize.call(this, attributes);
-
-
- // ScienceMetadata-specific init goes here
- this.listenTo(MetacatUI.rootDataPackage.packageModel, "change:changed", function(){
- if(MetacatUI.rootDataPackage.packageModel.get("changed"))
- this.set("uploadStatus", "q");
- });
-
- },
-
- /* Construct the Solr query URL to be called */
- url: function() {
-
- // Build the URL to include default fields in ScienceMetadata
- var fieldList = "*",//Object.keys(this.defaults),
- lastField = _.last(fieldList),
- searchFields = "",
- query = "q=",
- queryOptions = "&wt=json&fl=",
- url = "";
-
- // Make a list of the search fields
- _.each(fieldList, function(value, key, list) {
- if ( value === lastField ) {
- searchFields += value;
-
- } else {
- searchFields += value;
- searchFields += ",";
-
- }
- });
-
- queryOptions += searchFields;
- query += 'id:"' + encodeURIComponent(this.get("id")) + '"';
-
- url = MetacatUI.appModel.get("queryServiceUrl") + query + queryOptions;
- return url;
-
- },
-
- /* Fetch the ScienceMetadata from the MN Solr service */
- fetch: function(options) {
- if(!options)
- var options = {};
-
- //Add the authorization options
- _.extend(options, MetacatUI.appUserModel.createAjaxSettings());
-
- //Call Backbone.Model.fetch to retrieve the info
- return Backbone.Model.prototype.fetch.call(this, options);
-
- },
-
-
- /*
- * Updates the relationships with other models when this model has been updated
- */
- updateRelationships: function(){
- _.each(this.get("collections"), function(collection){
- //Get the old id for this model
- var oldId = this.get("oldPid");
-
- if(!oldId) return;
-
- //Find references to the old id in the documents relationship
- var outdatedModels = collection.filter(function(m){
- return _.contains(m.get("isDocumentedBy"), oldId);
- });
-
- //Update the documents array in each model
- _.each(outdatedModels, function(model){
- var updatedDocumentedBy = _.without(model.get("isDocumentedBy"), oldId);
- updatedDocumentedBy.push(this.get("id"));
-
- model.set("isDocumentedBy", updatedDocumentedBy);
- }, this);
-
- }, this);
-
- //Update the documents relationship
- if( _.contains(this.get("documents"), this.get("oldPid")) ){
- var updatedDocuments = _.without(this.get("documents"), this.get("oldPid"));
-
- this.set("documents", updatedDocuments);
- }
- }
+ var ScienceMetadata = DataONEObject.extend(
+ /** @lends ScienceMetadata.prototype */ {
+ // Only add fields present in the Solr service to the defaults
+ defaults: function () {
+ return _.extend(DataONEObject.prototype.defaults(), {
+ abstract: [],
+ attribute: [],
+ attributeDescription: [],
+ attributeLabel: [],
+ attributeName: [],
+ attributeUnit: [],
+ author: null,
+ authorGivenName: null,
+ authoritativeMN: null,
+ authorLastName: [],
+ authorSurName: null,
+ beginDate: null,
+ changePermission: [],
+ contactOrganization: [],
+ datasource: null,
+ dataUrl: null,
+ dateModified: null,
+ datePublished: null,
+ dateUploaded: null,
+ decade: null,
+ edition: null,
+ endDate: null,
+ fileID: null,
+ formatType: "METADATA",
+ gcmdKeyword: [],
+ investigator: [],
+ isDocumentedBy: [],
+ isPublic: null,
+ keyConcept: [],
+ keywords: [],
+ mediaType: null,
+ mediaTypeProperty: [],
+ origin: [],
+ originator: [],
+ placeKey: [],
+ presentationCat: null,
+ project: null,
+ pubDate: null,
+ purpose: null,
+ readPermission: [],
+ relatedOrganizations: [],
+ replicaMN: [],
+ sensor: [],
+ sensorText: [],
+ source: [],
+ scientificName: [],
+ title: [],
+ type: "Metadata",
+ species: [],
+ genus: [],
+ family: [],
+ class: [],
+ phylum: [],
+ order: [],
+ kingdom: [],
+ westBoundCoord: null,
+ eastBoundCoord: null,
+ northBoundCoord: null,
+ southBoundCoord: null,
+ site: [],
+ namedLocation: [],
+ noBoundingBox: null,
+ geoform: null,
+ isSpatial: null,
+ sortOrder: 1,
+ geohash_1: [],
+ geohash_2: [],
+ geohash_3: [],
+ geohash_4: [],
+ geohash_5: [],
+ geohash_6: [],
+ geohash_7: [],
+ geohash_8: [],
+ geohash_9: [],
+ sem_annotated_by: [],
+ sem_annotates: [],
+ sem_annotation: [],
+ sem_comment: [],
+ });
+ },
+
+ type: "ScienceMetadata",
+
+ nodeNameMap: function () {
+ return this.constructor.__super__.nodeNameMap();
+ },
+
+ /* Initialize a ScienceMetadata object */
+ initialize: function (attributes) {
+ // Call initialize for the super class
+ DataONEObject.prototype.initialize.call(this, attributes);
+
+ // ScienceMetadata-specific init goes here
+ this.listenTo(
+ MetacatUI.rootDataPackage.packageModel,
+ "change:changed",
+ function () {
+ if (MetacatUI.rootDataPackage.packageModel.get("changed"))
+ this.set("uploadStatus", "q");
+ },
+ );
+ },
+
+ /* Construct the Solr query URL to be called */
+ url: function () {
+ // Build the URL to include default fields in ScienceMetadata
+ var fieldList = "*", //Object.keys(this.defaults),
+ lastField = _.last(fieldList),
+ searchFields = "",
+ query = "q=",
+ queryOptions = "&wt=json&fl=",
+ url = "";
+
+ // Make a list of the search fields
+ _.each(fieldList, function (value, key, list) {
+ if (value === lastField) {
+ searchFields += value;
+ } else {
+ searchFields += value;
+ searchFields += ",";
+ }
});
- return ScienceMetadata;
- }
-);
+
+ queryOptions += searchFields;
+ query += 'id:"' + encodeURIComponent(this.get("id")) + '"';
+
+ url = MetacatUI.appModel.get("queryServiceUrl") + query + queryOptions;
+ return url;
+ },
+
+ /* Fetch the ScienceMetadata from the MN Solr service */
+ fetch: function (options) {
+ if (!options) var options = {};
+
+ //Add the authorization options
+ _.extend(options, MetacatUI.appUserModel.createAjaxSettings());
+
+ //Call Backbone.Model.fetch to retrieve the info
+ return Backbone.Model.prototype.fetch.call(this, options);
+ },
+
+ /*
+ * Updates the relationships with other models when this model has been updated
+ */
+ updateRelationships: function () {
+ _.each(
+ this.get("collections"),
+ function (collection) {
+ //Get the old id for this model
+ var oldId = this.get("oldPid");
+
+ if (!oldId) return;
+
+ //Find references to the old id in the documents relationship
+ var outdatedModels = collection.filter(function (m) {
+ return _.contains(m.get("isDocumentedBy"), oldId);
+ });
+
+ //Update the documents array in each model
+ _.each(
+ outdatedModels,
+ function (model) {
+ var updatedDocumentedBy = _.without(
+ model.get("isDocumentedBy"),
+ oldId,
+ );
+ updatedDocumentedBy.push(this.get("id"));
+
+ model.set("isDocumentedBy", updatedDocumentedBy);
+ },
+ this,
+ );
+ },
+ this,
+ );
+
+ //Update the documents relationship
+ if (_.contains(this.get("documents"), this.get("oldPid"))) {
+ var updatedDocuments = _.without(
+ this.get("documents"),
+ this.get("oldPid"),
+ );
+
+ this.set("documents", updatedDocuments);
+ }
+ },
+ },
+ );
+ return ScienceMetadata;
+});
diff --git a/src/js/models/metadata/eml/EMLMethodStep.js b/src/js/models/metadata/eml/EMLMethodStep.js
index b3ba84b76..68a8c0554 100644
--- a/src/js/models/metadata/eml/EMLMethodStep.js
+++ b/src/js/models/metadata/eml/EMLMethodStep.js
@@ -1,18 +1,20 @@
/* global define */
-var required = ['jquery',
- 'underscore',
- 'backbone',
- 'models/DataONEObject',
- 'models/metadata/eml220/EMLText']
-
-if( MetacatUI.appModel.get("customEMLMethods").length ){
+var required = [
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/DataONEObject",
+ "models/metadata/eml220/EMLText",
+];
+
+if (MetacatUI.appModel.get("customEMLMethods").length) {
required.push("models/metadata/eml/EMLSpecializedText");
}
-define(required,
- function($, _, Backbone, DataONEObject, EMLText, EMLSpecializedText) {
-
- /**
+define(
+ required,
+ function ($, _, Backbone, DataONEObject, EMLText, EMLSpecializedText) {
+ /**
* @class EMLMethodStep
* @classdesc Represents the EML Method Steps. The methodStep field allows for repeated sets of
elements that document a series of procedures followed to produce a
@@ -23,296 +25,308 @@ define(required,
* @extends Backbone.Model
* @since 2.19.0
*/
- var EMLMethodStep = Backbone.Model.extend(
- /** @lends EMLMethodStep.prototype */{
+ var EMLMethodStep = Backbone.Model.extend(
+ /** @lends EMLMethodStep.prototype */ {
+ /**
+ * Default attributes for EMLMethodSteps
+ * @returns {object}
+ * @property {string} objectXML The original XML snippet string from the EML XML
+ * @property {Element} objectDOM The original XML snippet as an Element
+ * @property {EMLText|EMLSpecializedText} description A textual description of this method step
+ * @property {string[]} instrumentation One or more instruments used for measurement and recording data
+ * @property {EMLMethodStep[]} subStep Nested additional method steps within this step. This is useful for hierarchical method descriptions. This is *not* fully supported in MetacatUI yet
+ * @property {string[]} customMethodID A unique identifier for this Custom Method Step type, which is defined in {@link AppConfig#customEMLMethods}
+ * @property {boolean} required If true, this method step is required in it's parent EML
+ */
+ defaults: function () {
+ return {
+ objectXML: null,
+ objectDOM: null,
+ description: null,
+ instrumentation: [],
+ subStep: [],
+ customMethodID: "",
+ required: false,
+ };
+ },
+
+ initialize: function (attributes) {
+ attributes = attributes || {};
+
+ if (attributes.objectDOM) {
+ this.set(this.parse(attributes.objectDOM));
+ } else if (attributes.customMethodID) {
+ try {
+ let customMethodConfig = MetacatUI.appModel
+ .get("customEMLMethods")
+ .find((config) => config.id == attributes.customMethodID);
+
+ this.set(
+ "description",
+ new EMLSpecializedText({
+ type: "description",
+ title: customMethodConfig.titleOptions[0],
+ titleOptions: customMethodConfig.titleOptions,
+ }),
+ );
+ } catch (e) {
+ console.error(e);
+ }
+ } else {
+ this.set(
+ "description",
+ new EMLText({
+ type: "description",
+ }),
+ );
+ }
- /**
- * Default attributes for EMLMethodSteps
- * @returns {object}
- * @property {string} objectXML The original XML snippet string from the EML XML
- * @property {Element} objectDOM The original XML snippet as an Element
- * @property {EMLText|EMLSpecializedText} description A textual description of this method step
- * @property {string[]} instrumentation One or more instruments used for measurement and recording data
- * @property {EMLMethodStep[]} subStep Nested additional method steps within this step. This is useful for hierarchical method descriptions. This is *not* fully supported in MetacatUI yet
- * @property {string[]} customMethodID A unique identifier for this Custom Method Step type, which is defined in {@link AppConfig#customEMLMethods}
- * @property {boolean} required If true, this method step is required in it's parent EML
- */
- defaults: function(){
- return {
- objectXML: null,
- objectDOM: null,
- description: null,
- instrumentation: [],
- subStep: [],
- customMethodID: "",
- required: false
- }
- },
-
- initialize: function(attributes){
- attributes = attributes || {};
-
- if(attributes.objectDOM){
- this.set(this.parse(attributes.objectDOM));
- }
- else if(attributes.customMethodID){
-
- try{
- let customMethodConfig = MetacatUI.appModel.get("customEMLMethods").find(config => config.id == attributes.customMethodID);
-
- this.set("description", new EMLSpecializedText({
- type: "description",
- title: customMethodConfig.titleOptions[0],
- titleOptions: customMethodConfig.titleOptions
- }));
- }
- catch(e){
- console.error(e);
- }
- }
- else{
- this.set("description", new EMLText({
- type: "description"
- }))
- }
-
- //Set the required attribute
- if( typeof attributes.required == "boolean" ){
- this.set("required", attributes.required);
- }
-
- //specific attributes to listen to
- this.on("change:instrumentation", this.trickleUpChange);
-
- },
+ //Set the required attribute
+ if (typeof attributes.required == "boolean") {
+ this.set("required", attributes.required);
+ }
- /**
- * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
- * Used during parse() and serialize()
- * @returns {object}
- */
- nodeNameMap: function(){
- return {
- "alternateidentifier" : "alternateIdentifier",
- "methodstep" : "methodStep",
- "substep" : "subStep",
- "datasource" : "dataSource",
- "referencedentityid" : "referencedEntityId",
- "qualitycontrol" : "qualityControl",
- "shortname" : "shortName"
- }
- },
-
- parse: function(objectDOM) {
- var modelJSON = {};
-
- if (!objectDOM) var objectDOM = this.get("objectDOM");
-
- let $objectDOM = $(objectDOM),
- description = $objectDOM.children("description");
-
- //Get the titles of all the custom method steps from the App Config
- let customMethodOptions = MetacatUI.appModel.get("customEMLMethods"),
- customMethodTitles = _.flatten(_.pluck(customMethodOptions, "titleOptions")),
- isCustom = false;
-
- try{
- //If there is at least one custom method configured, check if this description is one
- if( customMethodOptions && customMethodOptions.length ){
- let specializedTextAttr = EMLSpecializedText.prototype.parse(description[0]),
- matchingCustomMethod = customMethodOptions.find(options => options.titleOptions.includes(specializedTextAttr.title));
-
- if( matchingCustomMethod ){
- isCustom = true;
-
- //Use the EMLSpecializedText model for custom methods
- modelJSON.description = new EMLSpecializedText({
+ //specific attributes to listen to
+ this.on("change:instrumentation", this.trickleUpChange);
+ },
+
+ /**
+ * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
+ * Used during parse() and serialize()
+ * @returns {object}
+ */
+ nodeNameMap: function () {
+ return {
+ alternateidentifier: "alternateIdentifier",
+ methodstep: "methodStep",
+ substep: "subStep",
+ datasource: "dataSource",
+ referencedentityid: "referencedEntityId",
+ qualitycontrol: "qualityControl",
+ shortname: "shortName",
+ };
+ },
+
+ parse: function (objectDOM) {
+ var modelJSON = {};
+
+ if (!objectDOM) var objectDOM = this.get("objectDOM");
+
+ let $objectDOM = $(objectDOM),
+ description = $objectDOM.children("description");
+
+ //Get the titles of all the custom method steps from the App Config
+ let customMethodOptions = MetacatUI.appModel.get("customEMLMethods"),
+ customMethodTitles = _.flatten(
+ _.pluck(customMethodOptions, "titleOptions"),
+ ),
+ isCustom = false;
+
+ try {
+ //If there is at least one custom method configured, check if this description is one
+ if (customMethodOptions && customMethodOptions.length) {
+ let specializedTextAttr = EMLSpecializedText.prototype.parse(
+ description[0],
+ ),
+ matchingCustomMethod = customMethodOptions.find((options) =>
+ options.titleOptions.includes(specializedTextAttr.title),
+ );
+
+ if (matchingCustomMethod) {
+ isCustom = true;
+
+ //Use the EMLSpecializedText model for custom methods
+ modelJSON.description = new EMLSpecializedText({
+ objectDOM: description[0],
+ type: "description",
+ titleOptions: matchingCustomMethod.titleOptions,
+ parentModel: this,
+ });
+ //Save the other configurations of this custom method to this EMLMethodStep
+ modelJSON.customMethodID = matchingCustomMethod.id;
+ modelJSON.required = matchingCustomMethod.required;
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ }
+
+ //Create a regular EMLText description for non-custom methods
+ if (!isCustom) {
+ modelJSON.description = new EMLText({
objectDOM: description[0],
type: "description",
- titleOptions: matchingCustomMethod.titleOptions,
- parentModel: this
- });
- //Save the other configurations of this custom method to this EMLMethodStep
- modelJSON.customMethodID = matchingCustomMethod.id;
- modelJSON.required = matchingCustomMethod.required;
+ parentModel: this,
+ });
}
- }
- }
- catch(e){
- console.error(e);
- }
-
- //Create a regular EMLText description for non-custom methods
- if( !isCustom ){
- modelJSON.description = new EMLText({ objectDOM: description[0], type: "description", parentModel: this });
- }
- //Parse the instrumentation
- modelJSON.instrumentation = [];
- $objectDOM.children("instrumentation").each((i, el) => {
- modelJSON.instrumentation.push(el.textContent);
- });
+ //Parse the instrumentation
+ modelJSON.instrumentation = [];
+ $objectDOM.children("instrumentation").each((i, el) => {
+ modelJSON.instrumentation.push(el.textContent);
+ });
- /** @todo: Support parsing subSteps */
+ /** @todo: Support parsing subSteps */
- return modelJSON;
- },
+ return modelJSON;
+ },
- serialize: function(){
- var objectDOM = this.updateDOM();
+ serialize: function () {
+ var objectDOM = this.updateDOM();
- if(!objectDOM)
- return "";
+ if (!objectDOM) return "";
- var xmlString = objectDOM.outerHTML;
+ var xmlString = objectDOM.outerHTML;
- //Camel-case the XML
- xmlString = this.formatXML(xmlString);
+ //Camel-case the XML
+ xmlString = this.formatXML(xmlString);
- return xmlString;
- },
+ return xmlString;
+ },
- /**
- * Makes a copy of the original XML DOM and updates it with the new values from the model.
- */
- updateDOM: function(){
-
- //Return nothing if this model has only the default values
- if( this.isEmpty() ){
- return;
- }
-
- try{
- var objectDOM;
-
- if (this.get("objectDOM")) {
- objectDOM = this.get("objectDOM").cloneNode(true);
- } else {
- objectDOM = $(document.createElement("methodstep"));
- }
-
- let $objectDOM = $(objectDOM);
-
- //Update the description
- let description = this.get("description");
- if(description){
- let updatedDescription = description.updateDOM();
-
- //Descriptions are required for method steps, so if updating the DOM didn't work, don't serialize this method step.
- if( !updatedDescription ){
+ /**
+ * Makes a copy of the original XML DOM and updates it with the new values from the model.
+ */
+ updateDOM: function () {
+ //Return nothing if this model has only the default values
+ if (this.isEmpty()) {
return;
}
- //Add the description to the method step
- let existingDesc = $objectDOM.children("description");
- if( existingDesc.length ){
- existingDesc.replaceWith(updatedDescription);
- }
- else{
- $objectDOM.append(updatedDescription);
- }
- }
-
- try{
- //Update the instrumentation
- let instrumentation = this.get("instrumentation");
- $objectDOM.children("instrumentation").remove();
-
- if(instrumentation && instrumentation.length){
- instrumentation.reverse().each(i => {
- let updatedI = document.createElement("instrumentation");
- updatedI.textContent = i;
- $objectDOM.children("description").after(updatedI);
- })
- }
- }
- catch(e){
- console.error("Failed to serialize method step instrumentation, skipping. ", e);
- }
-
- /** @todo: Update software and subSteps */
-
- // Remove empty (zero-length or whitespace-only) nodes
- $objectDOM.find("*").filter(function() { return $.trim(this.innerHTML) === ""; } ).remove();
-
- return objectDOM;
- }
- catch(e){
- console.error("Failed to update the EMLMethodStep. Won't serialize. ", e);
- return;
- }
- },
-
- /**
- * function isEmpty() - Will check if there are any values set on this model
- * that are different than the default values and would be serialized to the EML.
- *
- * @return {boolean} - Returns true is this model is empty, false if not
- */
- isEmpty: function(){
-
- if( !this.get("description") || this.get("description").isEmpty() ){
- return true;
- }
-
- },
-
- /**
- * Returns whether or not this Method Step is a custom one, which currently only applies to the description
- * @returns {boolean}
- */
- isCustom: function(){
- return this.get("description")? this.get("description").type == "EMLSpecializedText" : false;
- },
-
- /**
- * Overloads Backbone.Model.validate() to check if this model has valid values set on it
- * @extends Backbone.Model.validate
- * @returns {object}
- */
- validate: function(){
-
- try{
-
- let validationErrors = {}
-
- if( this.isCustom() && this.get("required") ){
- let desc = this.get("description"),
- isMissing = false;
-
- //If there is a missing description, we need to show the required error
- if( !desc ){
- isMissing = true;
- }
- else if( !desc.get("text") ){
- isMissing = true;
+ try {
+ var objectDOM;
+
+ if (this.get("objectDOM")) {
+ objectDOM = this.get("objectDOM").cloneNode(true);
+ } else {
+ objectDOM = $(document.createElement("methodstep"));
+ }
+
+ let $objectDOM = $(objectDOM);
+
+ //Update the description
+ let description = this.get("description");
+ if (description) {
+ let updatedDescription = description.updateDOM();
+
+ //Descriptions are required for method steps, so if updating the DOM didn't work, don't serialize this method step.
+ if (!updatedDescription) {
+ return;
+ }
+
+ //Add the description to the method step
+ let existingDesc = $objectDOM.children("description");
+ if (existingDesc.length) {
+ existingDesc.replaceWith(updatedDescription);
+ } else {
+ $objectDOM.append(updatedDescription);
+ }
+ }
+
+ try {
+ //Update the instrumentation
+ let instrumentation = this.get("instrumentation");
+ $objectDOM.children("instrumentation").remove();
+
+ if (instrumentation && instrumentation.length) {
+ instrumentation.reverse().each((i) => {
+ let updatedI = document.createElement("instrumentation");
+ updatedI.textContent = i;
+ $objectDOM.children("description").after(updatedI);
+ });
+ }
+ } catch (e) {
+ console.error(
+ "Failed to serialize method step instrumentation, skipping. ",
+ e,
+ );
+ }
+
+ /** @todo: Update software and subSteps */
+
+ // Remove empty (zero-length or whitespace-only) nodes
+ $objectDOM
+ .find("*")
+ .filter(function () {
+ return $.trim(this.innerHTML) === "";
+ })
+ .remove();
+
+ return objectDOM;
+ } catch (e) {
+ console.error(
+ "Failed to update the EMLMethodStep. Won't serialize. ",
+ e,
+ );
+ return;
}
- else if( !_.compact(desc.get("text")).length ){
- isMissing = true;
+ },
+
+ /**
+ * function isEmpty() - Will check if there are any values set on this model
+ * that are different than the default values and would be serialized to the EML.
+ *
+ * @return {boolean} - Returns true is this model is empty, false if not
+ */
+ isEmpty: function () {
+ if (!this.get("description") || this.get("description").isEmpty()) {
+ return true;
}
-
- if( isMissing ){
- validationErrors.description = `${desc.get("title")} is required.`
- return validationErrors;
+ },
+
+ /**
+ * Returns whether or not this Method Step is a custom one, which currently only applies to the description
+ * @returns {boolean}
+ */
+ isCustom: function () {
+ return this.get("description")
+ ? this.get("description").type == "EMLSpecializedText"
+ : false;
+ },
+
+ /**
+ * Overloads Backbone.Model.validate() to check if this model has valid values set on it
+ * @extends Backbone.Model.validate
+ * @returns {object}
+ */
+ validate: function () {
+ try {
+ let validationErrors = {};
+
+ if (this.isCustom() && this.get("required")) {
+ let desc = this.get("description"),
+ isMissing = false;
+
+ //If there is a missing description, we need to show the required error
+ if (!desc) {
+ isMissing = true;
+ } else if (!desc.get("text")) {
+ isMissing = true;
+ } else if (!_.compact(desc.get("text")).length) {
+ isMissing = true;
+ }
+
+ if (isMissing) {
+ validationErrors.description = `${desc.get("title")} is required.`;
+ return validationErrors;
+ }
+ }
+ } catch (e) {
+ console.error("Error while validating the Methods: ", e);
+ return false;
}
- }
-
- }
- catch(e){
- console.error("Error while validating the Methods: ", e);
- return false;
- }
-
- },
+ },
- trickleUpChange: function(){
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- },
+ trickleUpChange: function () {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ },
- formatXML: function(xmlString){
- return DataONEObject.prototype.formatXML.call(this, xmlString);
- }
- });
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
+ },
+ );
- return EMLMethodStep;
-});
+ return EMLMethodStep;
+ },
+);
diff --git a/src/js/models/metadata/eml/EMLSpecializedText.js b/src/js/models/metadata/eml/EMLSpecializedText.js
index 0c46a92b5..fae78ae16 100644
--- a/src/js/models/metadata/eml/EMLSpecializedText.js
+++ b/src/js/models/metadata/eml/EMLSpecializedText.js
@@ -1,143 +1,143 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/metadata/eml220/EMLText'],
- function($, _, Backbone, EMLText) {
-
- /**
- * @class EMLSpecializedText
- * @classdesc An EMLSpecializedText is an EML 2.2.0 Text module that is treated differently in
- * the UI display. It is identified as "Specialized", by the title (either a `title` element in EMLText
- * or a Markdown Header 1). For example, you may want to display a custom EML Method Step specifically about
- * "Ethical Research Practices". An EMLSpecializedText would have a title of `Ethical Research Practices`, which
- * would be serialized in the EML XML as a section title or markdown header.
- * @classcategory Models/Metadata/EML
- * @since 2.19.0
- * @extends EMLText
- */
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/metadata/eml220/EMLText",
+], function ($, _, Backbone, EMLText) {
+ /**
+ * @class EMLSpecializedText
+ * @classdesc An EMLSpecializedText is an EML 2.2.0 Text module that is treated differently in
+ * the UI display. It is identified as "Specialized", by the title (either a `title` element in EMLText
+ * or a Markdown Header 1). For example, you may want to display a custom EML Method Step specifically about
+ * "Ethical Research Practices". An EMLSpecializedText would have a title of `Ethical Research Practices`, which
+ * would be serialized in the EML XML as a section title or markdown header.
+ * @classcategory Models/Metadata/EML
+ * @since 2.19.0
+ * @extends EMLText
+ */
var EMLSpecializedText = EMLText.extend(
- /** @lends EMLSpecializedText.prototype */{
-
- type: "EMLSpecializedText",
-
- defaults: function(){
- return _.extend(EMLText.prototype.defaults(), {
- id: null,
- title: "",
- titleOptions: []
- });
- },
+ /** @lends EMLSpecializedText.prototype */ {
+ type: "EMLSpecializedText",
- /**
- * Parses the XML objectDOM into a literal object to be set on the model.
- * It uses the EMLText 220 parse() method first and then performs additional
- * parsing for Specialized Texts. In particular, the first title in the text is
- * sorted out and used to identify as a Specialized text.
- *
- * @param {Element} objectDOM - XML Element to parse
- * @return {object} The literal object to be set on the model later
- */
- parse: function(objectDOM){
+ defaults: function () {
+ return _.extend(EMLText.prototype.defaults(), {
+ id: null,
+ title: "",
+ titleOptions: [],
+ });
+ },
- try{
- if(!objectDOM)
- var objectDOM = this.get("objectDOM").cloneNode(true);
-
- //Use the parent EMLText model parse() method
- let parsedAttributes = EMLText.prototype.parse.call(this, objectDOM);
-
- try{
- //Find all of the title nodes inside each section
- let titleNodes = $(objectDOM).children("section").children("title");
-
- //Get the title text from the first title node
- if( titleNodes.length ){
- let firstTitleNode = titleNodes[0];
- if( firstTitleNode ){
- //Save the title to the model attributes
- parsedAttributes.title = firstTitleNode.text;
- //Remove the title from the text attribute so that it doesn't get serialized twice
- parsedAttributes.text = _.without(parsedAttributes.text, parsedAttributes.title);
- parsedAttributes.originalText = _.without(parsedAttributes.originalText, parsedAttributes.title);
+ /**
+ * Parses the XML objectDOM into a literal object to be set on the model.
+ * It uses the EMLText 220 parse() method first and then performs additional
+ * parsing for Specialized Texts. In particular, the first title in the text is
+ * sorted out and used to identify as a Specialized text.
+ *
+ * @param {Element} objectDOM - XML Element to parse
+ * @return {object} The literal object to be set on the model later
+ */
+ parse: function (objectDOM) {
+ try {
+ if (!objectDOM) var objectDOM = this.get("objectDOM").cloneNode(true);
+
+ //Use the parent EMLText model parse() method
+ let parsedAttributes = EMLText.prototype.parse.call(this, objectDOM);
+
+ try {
+ //Find all of the title nodes inside each section
+ let titleNodes = $(objectDOM).children("section").children("title");
+
+ //Get the title text from the first title node
+ if (titleNodes.length) {
+ let firstTitleNode = titleNodes[0];
+ if (firstTitleNode) {
+ //Save the title to the model attributes
+ parsedAttributes.title = firstTitleNode.text;
+ //Remove the title from the text attribute so that it doesn't get serialized twice
+ parsedAttributes.text = _.without(
+ parsedAttributes.text,
+ parsedAttributes.title,
+ );
+ parsedAttributes.originalText = _.without(
+ parsedAttributes.originalText,
+ parsedAttributes.title,
+ );
+ }
+ } else if (parsedAttributes.markdown) {
+ /** @TODO: Support Markdown by looking for the first Header Level 1 (starting with #) */
}
+ } catch (e) {
+ console.error(
+ "Failed to parse the Specialized part of the EMLSpecializedText: ",
+ e,
+ );
+ } finally {
+ return parsedAttributes;
}
- else if( parsedAttributes.markdown ){
- /** @TODO: Support Markdown by looking for the first Header Level 1 (starting with #) */
- }
- }
- catch(e){
- console.error("Failed to parse the Specialized part of the EMLSpecializedText: ", e);
+ } catch (e) {
+ console.error("Failed to parse EMLSpecializedText: ", e);
+ return {};
}
- finally{
- return parsedAttributes;
- }
-
- }
- catch(e){
- console.error("Failed to parse EMLSpecializedText: ", e);
- return {}
- }
-
- },
-
- /**
- * Makes a copy of the original XML DOM and updates it with the new values from the model
- *
- * @param {string} textType - a string indicating the name for the outer xml element (i.e. content). Used in case there is no exisiting xmlDOM.
- * @return {XMLElement}
- */
- updateDOM: function(textType){
-
- try{
+ },
- //First update the DOM using the inherited updateDOM() method
- let updatedDOM = EMLText.prototype.updateDOM.call(this);
- let title = this.get("title");
-
- if(this.get("markdown")){
- /** @todo Support EMLSpecializedText for Markdown */
- }
- else if( updatedDOM && title ){
-
- //Get the section element
- let sectionEl = $(updatedDOM).children("section");
+ /**
+ * Makes a copy of the original XML DOM and updates it with the new values from the model
+ *
+ * @param {string} textType - a string indicating the name for the outer xml element (i.e. content). Used in case there is no exisiting xmlDOM.
+ * @return {XMLElement}
+ */
+ updateDOM: function (textType) {
+ try {
+ //First update the DOM using the inherited updateDOM() method
+ let updatedDOM = EMLText.prototype.updateDOM.call(this);
+ let title = this.get("title");
+
+ if (this.get("markdown")) {
+ /** @todo Support EMLSpecializedText for Markdown */
+ } else if (updatedDOM && title) {
+ //Get the section element
+ let sectionEl = $(updatedDOM).children("section");
+
+ //If there isn't a selection Element, create one and wrap it around the paras
+ if (!sectionEl.length) {
+ let allParas = $(updatedDOM).find("para");
+ allParas.wrapAll("");
+ sectionEl = $(updatedDOM).children("section");
+ }
- //If there isn't a selection Element, create one and wrap it around the paras
- if( !sectionEl.length ){
- let allParas = $(updatedDOM).find("para");
- allParas.wrapAll("");
- sectionEl = $(updatedDOM).children("section");
+ //Find the most up-to-date title from the AppConfig.
+ //The first title in the list gets used. All other titles in the list are
+ // considered legacy/alternative titles that may have been used in the past.
+ let titleOptions = this.get("titleOptions");
+ title = titleOptions.length ? titleOptions[0] : title;
+
+ //Get the title of the first section
+ let titleEl = sectionEl.children("title");
+ //If there isn't a title, create one
+ if (!titleEl.length) {
+ titleEl = $(document.createElement("title")).text(title);
+ sectionEl.prepend(titleEl);
+ }
+ //Otherwise update the title text content
+ else {
+ titleEl.text(title);
+ }
}
- //Find the most up-to-date title from the AppConfig.
- //The first title in the list gets used. All other titles in the list are
- // considered legacy/alternative titles that may have been used in the past.
- let titleOptions = this.get("titleOptions");
- title = titleOptions.length? titleOptions[0] : title;
-
- //Get the title of the first section
- let titleEl = sectionEl.children("title");
- //If there isn't a title, create one
- if( !titleEl.length ){
- titleEl = $(document.createElement("title")).text(title);
- sectionEl.prepend(titleEl);
- }
- //Otherwise update the title text content
- else{
- titleEl.text(title);
- }
+ return updatedDOM;
+ } catch (e) {
+ console.error(
+ "Failed to serialize the EMLSpecializedText. Will proceed using the original EML snippet. ",
+ e,
+ );
+ return this.get("objectDOM")
+ ? this.get("objectDOM").cloneNode(true)
+ : "";
}
-
- return updatedDOM;
-
- }
- catch(e){
- console.error("Failed to serialize the EMLSpecializedText. Will proceed using the original EML snippet. ", e);
- return this.get("objectDOM")? this.get("objectDOM").cloneNode(true) : "";
- }
-
- }
-
- });
+ },
+ },
+ );
return EMLSpecializedText;
-
});
diff --git a/src/js/models/metadata/eml211/EML211.js b/src/js/models/metadata/eml211/EML211.js
index 13af57d23..26b677d82 100644
--- a/src/js/models/metadata/eml211/EML211.js
+++ b/src/js/models/metadata/eml211/EML211.js
@@ -1,683 +1,738 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'uuid',
- 'collections/Units',
- 'models/metadata/ScienceMetadata',
- 'models/DataONEObject',
- 'models/metadata/eml211/EMLGeoCoverage',
- 'models/metadata/eml211/EMLKeywordSet',
- 'models/metadata/eml211/EMLTaxonCoverage',
- 'models/metadata/eml211/EMLTemporalCoverage',
- 'models/metadata/eml211/EMLDistribution',
- 'models/metadata/eml211/EMLEntity',
- 'models/metadata/eml211/EMLDataTable',
- 'models/metadata/eml211/EMLOtherEntity',
- 'models/metadata/eml211/EMLParty',
- 'models/metadata/eml211/EMLProject',
- 'models/metadata/eml211/EMLText',
- 'models/metadata/eml211/EMLMethods',
- 'collections/metadata/eml/EMLAnnotations',
- 'models/metadata/eml211/EMLAnnotation'],
- function($, _, Backbone, uuid, Units, ScienceMetadata, DataONEObject,
- EMLGeoCoverage, EMLKeywordSet, EMLTaxonCoverage, EMLTemporalCoverage,
- EMLDistribution, EMLEntity, EMLDataTable, EMLOtherEntity, EMLParty,
- EMLProject, EMLText, EMLMethods, EMLAnnotations, EMLAnnotation) {
-
- /**
- * @class EML211
- * @classdesc An EML211 object represents an Ecological Metadata Language
- * document, version 2.1.1
- * @classcategory Models/Metadata/EML211
- * @extends ScienceMetadata
- */
- var EML211 = ScienceMetadata.extend(
- /** @lends EML211.prototype */{
-
- type: "EML",
-
- defaults: function(){
- return _.extend(ScienceMetadata.prototype.defaults(), {
- id: "urn:uuid:" + uuid.v4(),
- formatId: "https://eml.ecoinformatics.org/eml-2.2.0",
- objectXML: null,
- isEditable: false,
- alternateIdentifier: [],
- shortName: null,
- title: [],
- creator: [], // array of EMLParty objects
- metadataProvider: [], // array of EMLParty objects
- associatedParty : [], // array of EMLParty objects
- contact: [], // array of EMLParty objects
- publisher: [], // array of EMLParty objects
- pubDate: null,
- language: null,
- series: null,
- abstract: [], //array of EMLText objects
- keywordSets: [], //array of EMLKeywordSet objects
- additionalInfo: [],
- intellectualRights: "This work is dedicated to the public domain under the Creative Commons Universal 1.0 Public Domain Dedication. To view a copy of this dedication, visit https://creativecommons.org/publicdomain/zero/1.0/.",
- distribution: [], // array of EMLDistribution objects
- geoCoverage : [], //an array for EMLGeoCoverages
- temporalCoverage : [], //an array of EMLTempCoverage models
- taxonCoverage : [], //an array of EMLTaxonCoverages
- purpose: [],
- entities: [], //An array of EMLEntities
- pubplace: null,
- methods: new EMLMethods(), // An EMLMethods objects
- project: null, // An EMLProject object,
- annotations: null, // Dataset-level annotations
- dataSensitivityPropertyURI: "http://purl.dataone.org/odo/SENSO_00000005",
- nodeOrder: [
- "alternateidentifier",
- "shortname",
- "title",
- "creator",
- "metadataprovider",
- "associatedparty",
- "pubdate",
- "language",
- "series",
- "abstract",
- "keywordset",
- "additionalinfo",
- "intellectualrights",
- "licensed",
- "distribution",
- "coverage",
- "annotation",
- "purpose",
- "introduction",
- "gettingstarted",
- "acknowledgements",
- "maintenance",
- "contact",
- "publisher",
- "pubplace",
- "methods",
- "project",
- "datatable",
- "spatialraster",
- "spatialvector",
- "storedprocedure",
- "view",
- "otherentity",
- "referencepublications",
- "usagecitations",
- "literaturecited",
- ]
- });
- },
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "uuid",
+ "collections/Units",
+ "models/metadata/ScienceMetadata",
+ "models/DataONEObject",
+ "models/metadata/eml211/EMLGeoCoverage",
+ "models/metadata/eml211/EMLKeywordSet",
+ "models/metadata/eml211/EMLTaxonCoverage",
+ "models/metadata/eml211/EMLTemporalCoverage",
+ "models/metadata/eml211/EMLDistribution",
+ "models/metadata/eml211/EMLEntity",
+ "models/metadata/eml211/EMLDataTable",
+ "models/metadata/eml211/EMLOtherEntity",
+ "models/metadata/eml211/EMLParty",
+ "models/metadata/eml211/EMLProject",
+ "models/metadata/eml211/EMLText",
+ "models/metadata/eml211/EMLMethods",
+ "collections/metadata/eml/EMLAnnotations",
+ "models/metadata/eml211/EMLAnnotation",
+], function (
+ $,
+ _,
+ Backbone,
+ uuid,
+ Units,
+ ScienceMetadata,
+ DataONEObject,
+ EMLGeoCoverage,
+ EMLKeywordSet,
+ EMLTaxonCoverage,
+ EMLTemporalCoverage,
+ EMLDistribution,
+ EMLEntity,
+ EMLDataTable,
+ EMLOtherEntity,
+ EMLParty,
+ EMLProject,
+ EMLText,
+ EMLMethods,
+ EMLAnnotations,
+ EMLAnnotation,
+) {
+ /**
+ * @class EML211
+ * @classdesc An EML211 object represents an Ecological Metadata Language
+ * document, version 2.1.1
+ * @classcategory Models/Metadata/EML211
+ * @extends ScienceMetadata
+ */
+ var EML211 = ScienceMetadata.extend(
+ /** @lends EML211.prototype */ {
+ type: "EML",
+
+ defaults: function () {
+ return _.extend(ScienceMetadata.prototype.defaults(), {
+ id: "urn:uuid:" + uuid.v4(),
+ formatId: "https://eml.ecoinformatics.org/eml-2.2.0",
+ objectXML: null,
+ isEditable: false,
+ alternateIdentifier: [],
+ shortName: null,
+ title: [],
+ creator: [], // array of EMLParty objects
+ metadataProvider: [], // array of EMLParty objects
+ associatedParty: [], // array of EMLParty objects
+ contact: [], // array of EMLParty objects
+ publisher: [], // array of EMLParty objects
+ pubDate: null,
+ language: null,
+ series: null,
+ abstract: [], //array of EMLText objects
+ keywordSets: [], //array of EMLKeywordSet objects
+ additionalInfo: [],
+ intellectualRights:
+ "This work is dedicated to the public domain under the Creative Commons Universal 1.0 Public Domain Dedication. To view a copy of this dedication, visit https://creativecommons.org/publicdomain/zero/1.0/.",
+ distribution: [], // array of EMLDistribution objects
+ geoCoverage: [], //an array for EMLGeoCoverages
+ temporalCoverage: [], //an array of EMLTempCoverage models
+ taxonCoverage: [], //an array of EMLTaxonCoverages
+ purpose: [],
+ entities: [], //An array of EMLEntities
+ pubplace: null,
+ methods: new EMLMethods(), // An EMLMethods objects
+ project: null, // An EMLProject object,
+ annotations: null, // Dataset-level annotations
+ dataSensitivityPropertyURI:
+ "http://purl.dataone.org/odo/SENSO_00000005",
+ nodeOrder: [
+ "alternateidentifier",
+ "shortname",
+ "title",
+ "creator",
+ "metadataprovider",
+ "associatedparty",
+ "pubdate",
+ "language",
+ "series",
+ "abstract",
+ "keywordset",
+ "additionalinfo",
+ "intellectualrights",
+ "licensed",
+ "distribution",
+ "coverage",
+ "annotation",
+ "purpose",
+ "introduction",
+ "gettingstarted",
+ "acknowledgements",
+ "maintenance",
+ "contact",
+ "publisher",
+ "pubplace",
+ "methods",
+ "project",
+ "datatable",
+ "spatialraster",
+ "spatialvector",
+ "storedprocedure",
+ "view",
+ "otherentity",
+ "referencepublications",
+ "usagecitations",
+ "literaturecited",
+ ],
+ });
+ },
- units: new Units(),
+ units: new Units(),
- initialize: function(attributes) {
- // Call initialize for the super class
- ScienceMetadata.prototype.initialize.call(this, attributes);
+ initialize: function (attributes) {
+ // Call initialize for the super class
+ ScienceMetadata.prototype.initialize.call(this, attributes);
- // EML211-specific init goes here
- // this.set("objectXML", this.createXML());
- this.parse(this.createXML());
+ // EML211-specific init goes here
+ // this.set("objectXML", this.createXML());
+ this.parse(this.createXML());
- this.on("sync", function(){
- this.set("synced", true);
- });
+ this.on("sync", function () {
+ this.set("synced", true);
+ });
- //Create a Unit collection
- if(!this.units.length)
- this.createUnits();
- },
+ //Create a Unit collection
+ if (!this.units.length) this.createUnits();
+ },
- url: function(options) {
- var identifier;
- if ( options && options.update ) {
- identifier = this.get("oldPid") || this.get("seriesid");
- } else {
- identifier = this.get("id") || this.get("seriesid");
- }
- return MetacatUI.appModel.get("objectServiceUrl") + encodeURIComponent(identifier);
- },
-
- /*
- * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
- * Used during parse() and serialize()
- */
- nodeNameMap: function(){
- return _.extend(
- this.constructor.__super__.nodeNameMap(),
- EMLDistribution.prototype.nodeNameMap(),
- EMLGeoCoverage.prototype.nodeNameMap(),
- EMLKeywordSet.prototype.nodeNameMap(),
- EMLParty.prototype.nodeNameMap(),
- EMLProject.prototype.nodeNameMap(),
- EMLTaxonCoverage.prototype.nodeNameMap(),
- EMLTemporalCoverage.prototype.nodeNameMap(),
- EMLMethods.prototype.nodeNameMap(),
- {
- "accuracyreport" : "accuracyReport",
- "actionlist" : "actionList",
- "additionalclassifications" : "additionalClassifications",
- "additionalinfo" : "additionalInfo",
- "additionallinks" : "additionalLinks",
- "additionalmetadata" : "additionalMetadata",
- "allowfirst" : "allowFirst",
- "alternateidentifier" : "alternateIdentifier",
- "altitudedatumname" : "altitudeDatumName",
- "altitudedistanceunits" : "altitudeDistanceUnits",
- "altituderesolution" : "altitudeResolution",
- "altitudeencodingmethod" : "altitudeEncodingMethod",
- "altitudesysdef" : "altitudeSysDef",
- "asneeded" : "asNeeded",
- "associatedparty" : "associatedParty",
- "attributeaccuracyexplanation" : "attributeAccuracyExplanation",
- "attributeaccuracyreport" : "attributeAccuracyReport",
- "attributeaccuracyvalue" : "attributeAccuracyValue",
- "attributedefinition" : "attributeDefinition",
- "attributelabel" : "attributeLabel",
- "attributelist" : "attributeList",
- "attributename" : "attributeName",
- "attributeorientation" : "attributeOrientation",
- "attributereference" : "attributeReference",
- "awardnumber" : "awardNumber",
- "awardurl" : "awardUrl",
- "audiovisual" : "audioVisual",
- "authsystem" : "authSystem",
- "banddescription" : "bandDescription",
- "bilinearfit" : "bilinearFit",
- "binaryrasterformat" : "binaryRasterFormat",
- "blockedmembernode" : "blockedMemberNode",
- "booktitle" : "bookTitle",
- "cameracalibrationinformationavailability" : "cameraCalibrationInformationAvailability",
- "casesensitive" : "caseSensitive",
- "cellgeometry" : "cellGeometry",
- "cellsizexdirection" : "cellSizeXDirection",
- "cellsizeydirection" : "cellSizeYDirection",
- "changehistory" : "changeHistory",
- "changedate" : "changeDate",
- "changescope" : "changeScope",
- "chapternumber" : "chapterNumber",
- "characterencoding" : "characterEncoding",
- "checkcondition" : "checkCondition",
- "checkconstraint" : "checkConstraint",
- "childoccurences" : "childOccurences",
- "citableclassificationsystem" : "citableClassificationSystem",
- "cloudcoverpercentage" : "cloudCoverPercentage",
- "codedefinition" : "codeDefinition",
- "codeexplanation" : "codeExplanation",
- "codesetname" : "codesetName",
- "codeseturl" : "codesetURL",
- "collapsedelimiters" : "collapseDelimiters",
- "communicationtype" : "communicationType",
- "compressiongenerationquality" : "compressionGenerationQuality",
- "compressionmethod" : "compressionMethod",
- "conferencedate" : "conferenceDate",
- "conferencelocation" : "conferenceLocation",
- "conferencename" : "conferenceName",
- "conferenceproceedings" : "conferenceProceedings",
- "constraintdescription" : "constraintDescription",
- "constraintname" : "constraintName",
- "constanttosi" : "constantToSI",
- "controlpoint" : "controlPoint",
- "cornerpoint" : "cornerPoint",
- "customunit" : "customUnit",
- "dataformat" : "dataFormat",
- "datasetgpolygon" : "datasetGPolygon",
- "datasetgpolygonoutergring" : "datasetGPolygonOuterGRing",
- "datasetgpolygonexclusiongring" : "datasetGPolygonExclusionGRing",
- "datatable" : "dataTable",
- "datatype" : "dataType",
- "datetime" : "dateTime",
- "datetimedomain" : "dateTimeDomain",
- "datetimeprecision" : "dateTimePrecision",
- "defaultvalue" : "defaultValue",
- "definitionattributereference" : "definitionAttributeReference",
- "denomflatratio" : "denomFlatRatio",
- "depthsysdef" : "depthSysDef",
- "depthdatumname" : "depthDatumName",
- "depthdistanceunits" : "depthDistanceUnits",
- "depthencodingmethod" : "depthEncodingMethod",
- "depthresolution" : "depthResolution",
- "descriptorvalue" : "descriptorValue",
- "dictref" : "dictRef",
- "diskusage" : "diskUsage",
- "domainDescription" : "domainDescription",
- "editedbook" : "editedBook",
- "encodingmethod" : "encodingMethod",
- "endcondition" : "endCondition",
- "entitycodelist" : "entityCodeList",
- "entitydescription" : "entityDescription",
- "entityname" : "entityName",
- "entityreference" : "entityReference",
- "entitytype" : "entityType",
- "enumerateddomain" : "enumeratedDomain",
- "errorbasis" : "errorBasis",
- "errorvalues" : "errorValues",
- "externalcodeset" : "externalCodeSet",
- "externallydefinedformat" : "externallyDefinedFormat",
- "fielddelimiter" : "fieldDelimiter",
- "fieldstartcolumn" : "fieldStartColumn",
- "fieldwidth" : "fieldWidth",
- "filmdistortioninformationavailability" : "filmDistortionInformationAvailability",
- "foreignkey" : "foreignKey",
- "formatname" : "formatName",
- "formatstring" : "formatString",
- "formatversion" : "formatVersion",
- "fractiondigits" : "fractionDigits",
- "fundername" : "funderName",
- "funderidentifier" : "funderIdentifier",
- "gettingstarted" : "gettingStarted",
- "gring" : "gRing",
- "gringpoint" : "gRingPoint",
- "gringlatitude" : "gRingLatitude",
- "gringlongitude" : "gRingLongitude",
- "geogcoordsys" : "geogCoordSys",
- "geometricobjectcount" : "geometricObjectCount",
- "georeferenceinfo" : "georeferenceInfo",
- "highwavelength" : "highWavelength",
- "horizontalaccuracy" : "horizontalAccuracy",
- "horizcoordsysdef" : "horizCoordSysDef",
- "horizcoordsysname" : "horizCoordSysName",
- "identifiername" : "identifierName",
- "illuminationazimuthangle" : "illuminationAzimuthAngle",
- "illuminationelevationangle" : "illuminationElevationAngle",
- "imagingcondition" : "imagingCondition",
- "imagequalitycode" : "imageQualityCode",
- "imageorientationangle" : "imageOrientationAngle",
- "intellectualrights" : "intellectualRights",
- "imagedescription" : "imageDescription",
- "isbn" : "ISBN",
- "issn" : "ISSN",
- "joincondition" : "joinCondition",
- "keywordtype" : "keywordType",
- "languagevalue" : "LanguageValue",
- "languagecodestandard" : "LanguageCodeStandard",
- "lensdistortioninformationavailability" : "lensDistortionInformationAvailability",
- "licensename" : "licenseName",
- "licenseurl" : "licenseURL",
- "linenumber" : "lineNumber",
- "literalcharacter" : "literalCharacter",
- "literallayout" : "literalLayout",
- "literaturecited" : "literatureCited",
- "lowwavelength" : "lowWaveLength",
- "machineprocessor" : "machineProcessor",
- "maintenanceupdatefrequency" : "maintenanceUpdateFrequency",
- "matrixtype" : "matrixType",
- "maxexclusive" : "maxExclusive",
- "maxinclusive" : "maxInclusive",
- "maxlength" : "maxLength",
- "maxrecordlength" : "maxRecordLength",
- "maxvalues" : "maxValues",
- "measurementscale" : "measurementScale",
- "metadatalist" : "metadataList",
- "methodstep" : "methodStep",
- "minexclusive" : "minExclusive",
- "mininclusive" : "minInclusive",
- "minlength" : "minLength",
- "minvalues" : "minValues",
- "missingvaluecode" : "missingValueCode",
- "moduledocs" : "moduleDocs",
- "modulename" : "moduleName",
- "moduledescription" : "moduleDescription",
- "multiband" : "multiBand",
- "multipliertosi" : "multiplierToSI",
- "nonnumericdomain" : "nonNumericDomain",
- "notnullconstraint" : "notNullConstraint",
- "notplanned" : "notPlanned",
- "numberofbands" : "numberOfBands",
- "numbertype" : "numberType",
- "numericdomain" : "numericDomain",
- "numfooterlines" : "numFooterLines",
- "numheaderlines" : "numHeaderLines",
- "numberofrecords" : "numberOfRecords",
- "numberofvolumes" : "numberOfVolumes",
- "numphysicallinesperrecord" : "numPhysicalLinesPerRecord",
- "objectname" : "objectName",
- "oldvalue" : "oldValue",
- "operatingsystem" : "operatingSystem",
- "orderattributereference" : "orderAttributeReference",
- "originalpublication" : "originalPublication",
- "otherentity" : "otherEntity",
- "othermaintenanceperiod" : "otherMaintenancePeriod",
- "parameterdefinition" : "parameterDefinition",
- "packageid" : "packageId",
- "pagerange" : "pageRange",
- "parentoccurences" : "parentOccurences",
- "parentsi" : "parentSI",
- "peakresponse" : "peakResponse",
- "personalcommunication" : "personalCommunication",
- "physicallinedelimiter" : "physicalLineDelimiter",
- "pointinpixel" : "pointInPixel",
- "preferredmembernode" : "preferredMemberNode",
- "preprocessingtypecode" : "preProcessingTypeCode",
- "primarykey" : "primaryKey",
- "primemeridian" : "primeMeridian",
- "proceduralstep" : "proceduralStep",
- "programminglanguage" : "programmingLanguage",
- "projcoordsys" : "projCoordSys",
- "projectionlist" : "projectionList",
- "propertyuri" : "propertyURI",
- "pubdate" : "pubDate",
- "pubplace" : "pubPlace",
- "publicationplace" : "publicationPlace",
- "quantitativeaccuracyreport" : "quantitativeAccuracyReport",
- "quantitativeaccuracyvalue" : "quantitativeAccuracyValue",
- "quantitativeaccuracymethod" : "quantitativeAccuracyMethod",
- "quantitativeattributeaccuracyassessment" : "quantitativeAttributeAccuracyAssessment",
- "querystatement" : "queryStatement",
- "quotecharacter" : "quoteCharacter",
- "radiometricdataavailability" : "radiometricDataAvailability",
- "rasterorigin" : "rasterOrigin",
- "recommendedunits" : "recommendedUnits",
- "recommendedusage" : "recommendedUsage",
- "referencedkey" : "referencedKey",
- "referencetype" : "referenceType",
- "relatedentry" : "relatedEntry",
- "relationshiptype" : "relationshipType",
- "reportnumber" : "reportNumber",
- "reprintedition" : "reprintEdition",
- "researchproject" : "researchProject",
- "researchtopic" : "researchTopic",
- "recorddelimiter" : "recordDelimiter",
- "referencepublication" : "referencePublication",
- "revieweditem" : "reviewedItem",
- "rowcolumnorientation" : "rowColumnOrientation",
- "runtimememoryusage" : "runtimeMemoryUsage",
- "samplingdescription" : "samplingDescription",
- "scalefactor" : "scaleFactor",
- "sequenceidentifier" : "sequenceIdentifier",
- "semiaxismajor" : "semiAxisMajor",
- "shortname" : "shortName",
- "simpledelimited" : "simpleDelimited",
- "spatialraster" : "spatialRaster",
- "spatialreference" : "spatialReference",
- "spatialvector" : "spatialVector",
- "standalone" : "standAlone",
- "standardunit" : "standardUnit",
- "startcondition" : "startCondition",
- "studyareadescription" : "studyAreaDescription",
- "storagetype" : "storageType",
- "studyextent" : "studyExtent",
- "studytype" : "studyType",
- "textdelimited" : "textDelimited",
- "textdomain" : "textDomain",
- "textfixed" : "textFixed",
- "textformat" : "textFormat",
- "topologylevel" : "topologyLevel",
- "tonegradation" : "toneGradation",
- "totaldigits" : "totalDigits",
- "totalfigures" : "totalFigures",
- "totalpages" : "totalPages",
- "totaltables" : "totalTables",
- "triangulationindicator" : "triangulationIndicator",
- "typesystem" : "typeSystem",
- "uniquekey" : "uniqueKey",
- "unittype" : "unitType",
- "unitlist" : "unitList",
- "usagecitation" : "usageCitation",
- "valueuri" : "valueURI",
- "valueattributereference" : "valueAttributeReference",
- "verticalaccuracy" : "verticalAccuracy",
- "vertcoordsys" : "vertCoordSys",
- "virtualmachine" : "virtualMachine",
- "wavelengthunits" : "waveLengthUnits",
- "whitespace" : "whiteSpace",
- "xintercept" : "xIntercept",
- "xcoordinate" : "xCoordinate",
- "xsi:schemalocation" : "xsi:schemaLocation",
- "xslope" : "xSlope",
- "ycoordinate" : "yCoordinate",
- "yintercept" : "yIntercept",
- "yslope" : "ySlope"
- }
- );
- },
+ url: function (options) {
+ var identifier;
+ if (options && options.update) {
+ identifier = this.get("oldPid") || this.get("seriesid");
+ } else {
+ identifier = this.get("id") || this.get("seriesid");
+ }
+ return (
+ MetacatUI.appModel.get("objectServiceUrl") +
+ encodeURIComponent(identifier)
+ );
+ },
- /**
- * Fetch the EML from the MN object service
- * @param {object} [options] - A set of options for this fetch()
- * @property {boolean} [options.systemMetadataOnly=false] - If true, only the system metadata will be fetched.
- * If false, the system metadata AND EML document will be fetched.
- */
- fetch: function(options) {
- if( ! options ) var options = {};
+ /*
+ * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
+ * Used during parse() and serialize()
+ */
+ nodeNameMap: function () {
+ return _.extend(
+ this.constructor.__super__.nodeNameMap(),
+ EMLDistribution.prototype.nodeNameMap(),
+ EMLGeoCoverage.prototype.nodeNameMap(),
+ EMLKeywordSet.prototype.nodeNameMap(),
+ EMLParty.prototype.nodeNameMap(),
+ EMLProject.prototype.nodeNameMap(),
+ EMLTaxonCoverage.prototype.nodeNameMap(),
+ EMLTemporalCoverage.prototype.nodeNameMap(),
+ EMLMethods.prototype.nodeNameMap(),
+ {
+ accuracyreport: "accuracyReport",
+ actionlist: "actionList",
+ additionalclassifications: "additionalClassifications",
+ additionalinfo: "additionalInfo",
+ additionallinks: "additionalLinks",
+ additionalmetadata: "additionalMetadata",
+ allowfirst: "allowFirst",
+ alternateidentifier: "alternateIdentifier",
+ altitudedatumname: "altitudeDatumName",
+ altitudedistanceunits: "altitudeDistanceUnits",
+ altituderesolution: "altitudeResolution",
+ altitudeencodingmethod: "altitudeEncodingMethod",
+ altitudesysdef: "altitudeSysDef",
+ asneeded: "asNeeded",
+ associatedparty: "associatedParty",
+ attributeaccuracyexplanation: "attributeAccuracyExplanation",
+ attributeaccuracyreport: "attributeAccuracyReport",
+ attributeaccuracyvalue: "attributeAccuracyValue",
+ attributedefinition: "attributeDefinition",
+ attributelabel: "attributeLabel",
+ attributelist: "attributeList",
+ attributename: "attributeName",
+ attributeorientation: "attributeOrientation",
+ attributereference: "attributeReference",
+ awardnumber: "awardNumber",
+ awardurl: "awardUrl",
+ audiovisual: "audioVisual",
+ authsystem: "authSystem",
+ banddescription: "bandDescription",
+ bilinearfit: "bilinearFit",
+ binaryrasterformat: "binaryRasterFormat",
+ blockedmembernode: "blockedMemberNode",
+ booktitle: "bookTitle",
+ cameracalibrationinformationavailability:
+ "cameraCalibrationInformationAvailability",
+ casesensitive: "caseSensitive",
+ cellgeometry: "cellGeometry",
+ cellsizexdirection: "cellSizeXDirection",
+ cellsizeydirection: "cellSizeYDirection",
+ changehistory: "changeHistory",
+ changedate: "changeDate",
+ changescope: "changeScope",
+ chapternumber: "chapterNumber",
+ characterencoding: "characterEncoding",
+ checkcondition: "checkCondition",
+ checkconstraint: "checkConstraint",
+ childoccurences: "childOccurences",
+ citableclassificationsystem: "citableClassificationSystem",
+ cloudcoverpercentage: "cloudCoverPercentage",
+ codedefinition: "codeDefinition",
+ codeexplanation: "codeExplanation",
+ codesetname: "codesetName",
+ codeseturl: "codesetURL",
+ collapsedelimiters: "collapseDelimiters",
+ communicationtype: "communicationType",
+ compressiongenerationquality: "compressionGenerationQuality",
+ compressionmethod: "compressionMethod",
+ conferencedate: "conferenceDate",
+ conferencelocation: "conferenceLocation",
+ conferencename: "conferenceName",
+ conferenceproceedings: "conferenceProceedings",
+ constraintdescription: "constraintDescription",
+ constraintname: "constraintName",
+ constanttosi: "constantToSI",
+ controlpoint: "controlPoint",
+ cornerpoint: "cornerPoint",
+ customunit: "customUnit",
+ dataformat: "dataFormat",
+ datasetgpolygon: "datasetGPolygon",
+ datasetgpolygonoutergring: "datasetGPolygonOuterGRing",
+ datasetgpolygonexclusiongring: "datasetGPolygonExclusionGRing",
+ datatable: "dataTable",
+ datatype: "dataType",
+ datetime: "dateTime",
+ datetimedomain: "dateTimeDomain",
+ datetimeprecision: "dateTimePrecision",
+ defaultvalue: "defaultValue",
+ definitionattributereference: "definitionAttributeReference",
+ denomflatratio: "denomFlatRatio",
+ depthsysdef: "depthSysDef",
+ depthdatumname: "depthDatumName",
+ depthdistanceunits: "depthDistanceUnits",
+ depthencodingmethod: "depthEncodingMethod",
+ depthresolution: "depthResolution",
+ descriptorvalue: "descriptorValue",
+ dictref: "dictRef",
+ diskusage: "diskUsage",
+ domainDescription: "domainDescription",
+ editedbook: "editedBook",
+ encodingmethod: "encodingMethod",
+ endcondition: "endCondition",
+ entitycodelist: "entityCodeList",
+ entitydescription: "entityDescription",
+ entityname: "entityName",
+ entityreference: "entityReference",
+ entitytype: "entityType",
+ enumerateddomain: "enumeratedDomain",
+ errorbasis: "errorBasis",
+ errorvalues: "errorValues",
+ externalcodeset: "externalCodeSet",
+ externallydefinedformat: "externallyDefinedFormat",
+ fielddelimiter: "fieldDelimiter",
+ fieldstartcolumn: "fieldStartColumn",
+ fieldwidth: "fieldWidth",
+ filmdistortioninformationavailability:
+ "filmDistortionInformationAvailability",
+ foreignkey: "foreignKey",
+ formatname: "formatName",
+ formatstring: "formatString",
+ formatversion: "formatVersion",
+ fractiondigits: "fractionDigits",
+ fundername: "funderName",
+ funderidentifier: "funderIdentifier",
+ gettingstarted: "gettingStarted",
+ gring: "gRing",
+ gringpoint: "gRingPoint",
+ gringlatitude: "gRingLatitude",
+ gringlongitude: "gRingLongitude",
+ geogcoordsys: "geogCoordSys",
+ geometricobjectcount: "geometricObjectCount",
+ georeferenceinfo: "georeferenceInfo",
+ highwavelength: "highWavelength",
+ horizontalaccuracy: "horizontalAccuracy",
+ horizcoordsysdef: "horizCoordSysDef",
+ horizcoordsysname: "horizCoordSysName",
+ identifiername: "identifierName",
+ illuminationazimuthangle: "illuminationAzimuthAngle",
+ illuminationelevationangle: "illuminationElevationAngle",
+ imagingcondition: "imagingCondition",
+ imagequalitycode: "imageQualityCode",
+ imageorientationangle: "imageOrientationAngle",
+ intellectualrights: "intellectualRights",
+ imagedescription: "imageDescription",
+ isbn: "ISBN",
+ issn: "ISSN",
+ joincondition: "joinCondition",
+ keywordtype: "keywordType",
+ languagevalue: "LanguageValue",
+ languagecodestandard: "LanguageCodeStandard",
+ lensdistortioninformationavailability:
+ "lensDistortionInformationAvailability",
+ licensename: "licenseName",
+ licenseurl: "licenseURL",
+ linenumber: "lineNumber",
+ literalcharacter: "literalCharacter",
+ literallayout: "literalLayout",
+ literaturecited: "literatureCited",
+ lowwavelength: "lowWaveLength",
+ machineprocessor: "machineProcessor",
+ maintenanceupdatefrequency: "maintenanceUpdateFrequency",
+ matrixtype: "matrixType",
+ maxexclusive: "maxExclusive",
+ maxinclusive: "maxInclusive",
+ maxlength: "maxLength",
+ maxrecordlength: "maxRecordLength",
+ maxvalues: "maxValues",
+ measurementscale: "measurementScale",
+ metadatalist: "metadataList",
+ methodstep: "methodStep",
+ minexclusive: "minExclusive",
+ mininclusive: "minInclusive",
+ minlength: "minLength",
+ minvalues: "minValues",
+ missingvaluecode: "missingValueCode",
+ moduledocs: "moduleDocs",
+ modulename: "moduleName",
+ moduledescription: "moduleDescription",
+ multiband: "multiBand",
+ multipliertosi: "multiplierToSI",
+ nonnumericdomain: "nonNumericDomain",
+ notnullconstraint: "notNullConstraint",
+ notplanned: "notPlanned",
+ numberofbands: "numberOfBands",
+ numbertype: "numberType",
+ numericdomain: "numericDomain",
+ numfooterlines: "numFooterLines",
+ numheaderlines: "numHeaderLines",
+ numberofrecords: "numberOfRecords",
+ numberofvolumes: "numberOfVolumes",
+ numphysicallinesperrecord: "numPhysicalLinesPerRecord",
+ objectname: "objectName",
+ oldvalue: "oldValue",
+ operatingsystem: "operatingSystem",
+ orderattributereference: "orderAttributeReference",
+ originalpublication: "originalPublication",
+ otherentity: "otherEntity",
+ othermaintenanceperiod: "otherMaintenancePeriod",
+ parameterdefinition: "parameterDefinition",
+ packageid: "packageId",
+ pagerange: "pageRange",
+ parentoccurences: "parentOccurences",
+ parentsi: "parentSI",
+ peakresponse: "peakResponse",
+ personalcommunication: "personalCommunication",
+ physicallinedelimiter: "physicalLineDelimiter",
+ pointinpixel: "pointInPixel",
+ preferredmembernode: "preferredMemberNode",
+ preprocessingtypecode: "preProcessingTypeCode",
+ primarykey: "primaryKey",
+ primemeridian: "primeMeridian",
+ proceduralstep: "proceduralStep",
+ programminglanguage: "programmingLanguage",
+ projcoordsys: "projCoordSys",
+ projectionlist: "projectionList",
+ propertyuri: "propertyURI",
+ pubdate: "pubDate",
+ pubplace: "pubPlace",
+ publicationplace: "publicationPlace",
+ quantitativeaccuracyreport: "quantitativeAccuracyReport",
+ quantitativeaccuracyvalue: "quantitativeAccuracyValue",
+ quantitativeaccuracymethod: "quantitativeAccuracyMethod",
+ quantitativeattributeaccuracyassessment:
+ "quantitativeAttributeAccuracyAssessment",
+ querystatement: "queryStatement",
+ quotecharacter: "quoteCharacter",
+ radiometricdataavailability: "radiometricDataAvailability",
+ rasterorigin: "rasterOrigin",
+ recommendedunits: "recommendedUnits",
+ recommendedusage: "recommendedUsage",
+ referencedkey: "referencedKey",
+ referencetype: "referenceType",
+ relatedentry: "relatedEntry",
+ relationshiptype: "relationshipType",
+ reportnumber: "reportNumber",
+ reprintedition: "reprintEdition",
+ researchproject: "researchProject",
+ researchtopic: "researchTopic",
+ recorddelimiter: "recordDelimiter",
+ referencepublication: "referencePublication",
+ revieweditem: "reviewedItem",
+ rowcolumnorientation: "rowColumnOrientation",
+ runtimememoryusage: "runtimeMemoryUsage",
+ samplingdescription: "samplingDescription",
+ scalefactor: "scaleFactor",
+ sequenceidentifier: "sequenceIdentifier",
+ semiaxismajor: "semiAxisMajor",
+ shortname: "shortName",
+ simpledelimited: "simpleDelimited",
+ spatialraster: "spatialRaster",
+ spatialreference: "spatialReference",
+ spatialvector: "spatialVector",
+ standalone: "standAlone",
+ standardunit: "standardUnit",
+ startcondition: "startCondition",
+ studyareadescription: "studyAreaDescription",
+ storagetype: "storageType",
+ studyextent: "studyExtent",
+ studytype: "studyType",
+ textdelimited: "textDelimited",
+ textdomain: "textDomain",
+ textfixed: "textFixed",
+ textformat: "textFormat",
+ topologylevel: "topologyLevel",
+ tonegradation: "toneGradation",
+ totaldigits: "totalDigits",
+ totalfigures: "totalFigures",
+ totalpages: "totalPages",
+ totaltables: "totalTables",
+ triangulationindicator: "triangulationIndicator",
+ typesystem: "typeSystem",
+ uniquekey: "uniqueKey",
+ unittype: "unitType",
+ unitlist: "unitList",
+ usagecitation: "usageCitation",
+ valueuri: "valueURI",
+ valueattributereference: "valueAttributeReference",
+ verticalaccuracy: "verticalAccuracy",
+ vertcoordsys: "vertCoordSys",
+ virtualmachine: "virtualMachine",
+ wavelengthunits: "waveLengthUnits",
+ whitespace: "whiteSpace",
+ xintercept: "xIntercept",
+ xcoordinate: "xCoordinate",
+ "xsi:schemalocation": "xsi:schemaLocation",
+ xslope: "xSlope",
+ ycoordinate: "yCoordinate",
+ yintercept: "yIntercept",
+ yslope: "ySlope",
+ },
+ );
+ },
- //Add the authorization header and other AJAX settings
- _.extend(options, MetacatUI.appUserModel.createAjaxSettings(), {dataType: "text"});
+ /**
+ * Fetch the EML from the MN object service
+ * @param {object} [options] - A set of options for this fetch()
+ * @property {boolean} [options.systemMetadataOnly=false] - If true, only the system metadata will be fetched.
+ * If false, the system metadata AND EML document will be fetched.
+ */
+ fetch: function (options) {
+ if (!options) var options = {};
- // Merge the system metadata into the object first
- _.extend(options, {merge: true});
- this.fetchSystemMetadata(options);
+ //Add the authorization header and other AJAX settings
+ _.extend(options, MetacatUI.appUserModel.createAjaxSettings(), {
+ dataType: "text",
+ });
- //If we are retrieving system metadata only, then exit now
- if(options.systemMetadataOnly)
- return;
+ // Merge the system metadata into the object first
+ _.extend(options, { merge: true });
+ this.fetchSystemMetadata(options);
- //Call Backbone.Model.fetch to retrieve the info
- return Backbone.Model.prototype.fetch.call(this, options);
+ //If we are retrieving system metadata only, then exit now
+ if (options.systemMetadataOnly) return;
- },
+ //Call Backbone.Model.fetch to retrieve the info
+ return Backbone.Model.prototype.fetch.call(this, options);
+ },
- /*
+ /*
Deserialize an EML 2.1.1 XML document
*/
- parse: function(response) {
- // Save a reference to this model for use in setting the
- // parentModel inside anonymous functions
- var model = this;
-
- //If the response is XML
- if((typeof response == "string") && response.indexOf("<") == 0){
- //Look for a system metadata tag and call DataONEObject parse instead
- if(response.indexOf("systemMetadata>") > -1)
- return DataONEObject.prototype.parse.call(this, response);
-
- response = this.cleanUpXML(response);
- response = this.dereference(response);
- this.set("objectXML", response);
- var emlElement = $($.parseHTML(response)).filter("eml\\:eml");
- }
-
- var datasetEl;
- if(emlElement[0])
- datasetEl = $(emlElement[0]).find("dataset");
-
- if(!datasetEl || !datasetEl.length)
- return {};
-
- var emlParties = ["metadataprovider", "associatedparty", "creator", "contact", "publisher"],
- emlDistribution = ["distribution"],
- emlEntities = ["datatable", "otherentity", "spatialvector", "spatialraster", "storedprocedure", "view"],
- emlText = ["abstract", "additionalinfo"],
- emlMethods = ["methods"];
-
- var nodes = datasetEl.children(),
- modelJSON = {};
-
- for(var i=0; i") > -1)
+ return DataONEObject.prototype.parse.call(this, response);
+
+ response = this.cleanUpXML(response);
+ response = this.dereference(response);
+ this.set("objectXML", response);
+ var emlElement = $($.parseHTML(response)).filter("eml\\:eml");
+ }
- modelJSON[attributeName].push(new EMLParty({
+ var datasetEl;
+ if (emlElement[0]) datasetEl = $(emlElement[0]).find("dataset");
+
+ if (!datasetEl || !datasetEl.length) return {};
+
+ var emlParties = [
+ "metadataprovider",
+ "associatedparty",
+ "creator",
+ "contact",
+ "publisher",
+ ],
+ emlDistribution = ["distribution"],
+ emlEntities = [
+ "datatable",
+ "otherentity",
+ "spatialvector",
+ "spatialraster",
+ "storedprocedure",
+ "view",
+ ],
+ emlText = ["abstract", "additionalinfo"],
+ emlMethods = ["methods"];
+
+ var nodes = datasetEl.children(),
+ modelJSON = {};
+
+ for (var i = 0; i < nodes.length; i++) {
+ var thisNode = nodes[i];
+ var convertedName =
+ this.nodeNameMap()[thisNode.localName] || thisNode.localName;
+
+ //EML Party modules are stored in EMLParty models
+ if (_.contains(emlParties, thisNode.localName)) {
+ if (thisNode.localName == "metadataprovider")
+ var attributeName = "metadataProvider";
+ else if (thisNode.localName == "associatedparty")
+ var attributeName = "associatedParty";
+ else var attributeName = thisNode.localName;
+
+ if (typeof modelJSON[attributeName] == "undefined")
+ modelJSON[attributeName] = [];
+
+ modelJSON[attributeName].push(
+ new EMLParty({
objectDOM: thisNode,
parentModel: model,
- type: attributeName
- }));
- }
- //EML Distribution modules are stored in EMLDistribution models
- else if(_.contains(emlDistribution, thisNode.localName)) {
- if(typeof modelJSON[thisNode.localName] == "undefined") modelJSON[thisNode.localName] = [];
-
- modelJSON[thisNode.localName].push(new EMLDistribution({
- objectDOM: thisNode,
- parentModel: model
- }, { parse: true }));
+ type: attributeName,
+ }),
+ );
+ }
+ //EML Distribution modules are stored in EMLDistribution models
+ else if (_.contains(emlDistribution, thisNode.localName)) {
+ if (typeof modelJSON[thisNode.localName] == "undefined")
+ modelJSON[thisNode.localName] = [];
+
+ modelJSON[thisNode.localName].push(
+ new EMLDistribution(
+ {
+ objectDOM: thisNode,
+ parentModel: model,
+ },
+ { parse: true },
+ ),
+ );
+ }
+ //The EML Project is stored in the EMLProject model
+ else if (thisNode.localName == "project") {
+ modelJSON.project = new EMLProject({
+ objectDOM: thisNode,
+ parentModel: model,
+ });
+ }
+ //EML Temporal, Taxonomic, and Geographic Coverage modules are stored in their own models
+ else if (thisNode.localName == "coverage") {
+ var temporal = $(thisNode).children("temporalcoverage"),
+ geo = $(thisNode).children("geographiccoverage"),
+ taxon = $(thisNode).children("taxonomiccoverage");
+
+ if (temporal.length) {
+ modelJSON.temporalCoverage = [];
+
+ _.each(temporal, function (t) {
+ modelJSON.temporalCoverage.push(
+ new EMLTemporalCoverage({
+ objectDOM: t,
+ parentModel: model,
+ }),
+ );
+ });
}
- //The EML Project is stored in the EMLProject model
- else if(thisNode.localName == "project"){
-
- modelJSON.project = new EMLProject({
- objectDOM: thisNode,
- parentModel: model
- });
+ if (geo.length) {
+ modelJSON.geoCoverage = [];
+ _.each(geo, function (g) {
+ modelJSON.geoCoverage.push(
+ new EMLGeoCoverage({
+ objectDOM: g,
+ parentModel: model,
+ }),
+ );
+ });
}
- //EML Temporal, Taxonomic, and Geographic Coverage modules are stored in their own models
- else if(thisNode.localName == "coverage"){
- var temporal = $(thisNode).children("temporalcoverage"),
- geo = $(thisNode).children("geographiccoverage"),
- taxon = $(thisNode).children("taxonomiccoverage");
-
- if(temporal.length){
- modelJSON.temporalCoverage = [];
-
- _.each(temporal, function(t){
- modelJSON.temporalCoverage.push(new EMLTemporalCoverage({
+ if (taxon.length) {
+ modelJSON.taxonCoverage = [];
+ _.each(taxon, function (t) {
+ modelJSON.taxonCoverage.push(
+ new EMLTaxonCoverage({
objectDOM: t,
- parentModel: model
- }));
- });
- }
-
- if(geo.length){
- modelJSON.geoCoverage = [];
- _.each(geo, function(g){
- modelJSON.geoCoverage.push(new EMLGeoCoverage({
- objectDOM: g,
- parentModel: model
- }));
- });
-
- }
-
- if(taxon.length){
- modelJSON.taxonCoverage = [];
- _.each(taxon, function(t){
- modelJSON.taxonCoverage.push(new EMLTaxonCoverage({
- objectDOM: t,
- parentModel: model
- }));
- });
-
- }
-
+ parentModel: model,
+ }),
+ );
+ });
}
- //Parse EMLText modules
- else if(_.contains(emlText, thisNode.localName)){
- if(typeof modelJSON[convertedName] == "undefined") modelJSON[convertedName] = [];
-
- modelJSON[convertedName].push(new EMLText({
- objectDOM: thisNode,
- parentModel: model
- }));
+ }
+ //Parse EMLText modules
+ else if (_.contains(emlText, thisNode.localName)) {
+ if (typeof modelJSON[convertedName] == "undefined")
+ modelJSON[convertedName] = [];
- }
- else if(_.contains(emlMethods, thisNode.localName)) {
- if(typeof modelJSON[thisNode.localName] === "undefined") modelJSON[thisNode.localName] = [];
+ modelJSON[convertedName].push(
+ new EMLText({
+ objectDOM: thisNode,
+ parentModel: model,
+ }),
+ );
+ } else if (_.contains(emlMethods, thisNode.localName)) {
+ if (typeof modelJSON[thisNode.localName] === "undefined")
+ modelJSON[thisNode.localName] = [];
- modelJSON[thisNode.localName] = new EMLMethods({
+ modelJSON[thisNode.localName] = new EMLMethods({
objectDOM: thisNode,
- parentModel: model
+ parentModel: model,
});
-
}
//Parse keywords
- else if(thisNode.localName == "keywordset"){
+ else if (thisNode.localName == "keywordset") {
//Start an array of keyword sets
- if(typeof modelJSON["keywordSets"] == "undefined") modelJSON["keywordSets"] = [];
+ if (typeof modelJSON["keywordSets"] == "undefined")
+ modelJSON["keywordSets"] = [];
- modelJSON["keywordSets"].push(new EMLKeywordSet({
- objectDOM: thisNode,
- parentModel: model
- }));
+ modelJSON["keywordSets"].push(
+ new EMLKeywordSet({
+ objectDOM: thisNode,
+ parentModel: model,
+ }),
+ );
}
//Parse intellectual rights
- else if(thisNode.localName == "intellectualrights"){
+ else if (thisNode.localName == "intellectualrights") {
var value = "";
- if($(thisNode).children("para").length == 1)
+ if ($(thisNode).children("para").length == 1)
value = $(thisNode).children("para").first().text().trim();
- else
- $(thisNode).text().trim();
+ else $(thisNode).text().trim();
//If the value is one of our pre-defined options, then add it to the model
//if(_.contains(this.get("intellRightsOptions"), value))
modelJSON["intellectualRights"] = value;
-
}
//Parse Entities
- else if(_.contains(emlEntities, thisNode.localName)){
-
+ else if (_.contains(emlEntities, thisNode.localName)) {
//Start an array of Entities
- if(typeof modelJSON["entities"] == "undefined")
+ if (typeof modelJSON["entities"] == "undefined")
modelJSON["entities"] = [];
//Create the model
var entityModel;
- if(thisNode.localName == "otherentity"){
- entityModel = new EMLOtherEntity({
+ if (thisNode.localName == "otherentity") {
+ entityModel = new EMLOtherEntity(
+ {
objectDOM: thisNode,
- parentModel: model
- }, {
- parse: true
- });
- } else if ( thisNode.localName == "datatable") {
- entityModel = new EMLDataTable({
- objectDOM: thisNode,
- parentModel: model
- }, {
- parse: true
- });
- }
- else {
- entityModel = new EMLEntity({
+ parentModel: model,
+ },
+ {
+ parse: true,
+ },
+ );
+ } else if (thisNode.localName == "datatable") {
+ entityModel = new EMLDataTable(
+ {
+ objectDOM: thisNode,
+ parentModel: model,
+ },
+ {
+ parse: true,
+ },
+ );
+ } else {
+ entityModel = new EMLEntity(
+ {
objectDOM: thisNode,
parentModel: model,
entityType: "application/octet-stream",
- type: thisNode.localName
- }, {
- parse: true
- });
+ type: thisNode.localName,
+ },
+ {
+ parse: true,
+ },
+ );
}
modelJSON["entities"].push(entityModel);
}
//Parse dataset-level annotations
else if (thisNode.localName === "annotation") {
- if( !modelJSON["annotations"] ) {
+ if (!modelJSON["annotations"]) {
modelJSON["annotations"] = new EMLAnnotations();
}
- var annotationModel = new EMLAnnotation({
- objectDOM: thisNode
- }, { parse: true });
+ var annotationModel = new EMLAnnotation(
+ {
+ objectDOM: thisNode,
+ },
+ { parse: true },
+ );
modelJSON["annotations"].add(annotationModel);
- }
- else{
+ } else {
//Is this a multi-valued field in EML?
- if(Array.isArray(this.get(convertedName))){
+ if (Array.isArray(this.get(convertedName))) {
//If we already have a value for this field, then add this value to the array
- if(Array.isArray(modelJSON[convertedName]))
+ if (Array.isArray(modelJSON[convertedName]))
modelJSON[convertedName].push(this.toJson(thisNode));
//If it's the first value for this field, then create a new array
- else
- modelJSON[convertedName] = [this.toJson(thisNode)];
- }
- else
- modelJSON[convertedName] = this.toJson(thisNode);
+ else modelJSON[convertedName] = [this.toJson(thisNode)];
+ } else modelJSON[convertedName] = this.toJson(thisNode);
}
-
}
return modelJSON;
@@ -687,12 +742,12 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
* Retireves the model attributes and serializes into EML XML, to produce the new or modified EML document.
* Returns the EML XML as a string.
*/
- serialize: function(){
+ serialize: function () {
//Get the EML document
- var xmlString = this.get("objectXML"),
- html = $.parseHTML(xmlString),
- eml = $(html).filter("eml\\:eml"),
- datasetNode = $(eml).find("dataset");
+ var xmlString = this.get("objectXML"),
+ html = $.parseHTML(xmlString),
+ eml = $(html).filter("eml\\:eml"),
+ datasetNode = $(eml).find("dataset");
//Update the packageId on the eml node with the EML id
$(eml).attr("packageId", this.get("id"));
@@ -703,14 +758,18 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
}
// Set schema version
- $(eml).attr("xmlns:eml",
+ $(eml).attr(
+ "xmlns:eml",
MetacatUI.appModel.get("editorSerializationFormat") ||
- "https://eml.ecoinformatics.org/eml-2.2.0");
+ "https://eml.ecoinformatics.org/eml-2.2.0",
+ );
// Set formatID
- this.set("formatId",
+ this.set(
+ "formatId",
MetacatUI.appModel.get("editorSerializationFormat") ||
- "https://eml.ecoinformatics.org/eml-2.2.0");
+ "https://eml.ecoinformatics.org/eml-2.2.0",
+ );
// Ensure xsi:schemaLocation has a value for the current format
eml = this.setSchemaLocation(eml);
@@ -719,510 +778,553 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
//Serialize the basic text fields
var basicText = ["alternateIdentifier", "title"];
- _.each(basicText, function(fieldName){
- var basicTextValues = this.get(fieldName);
-
- if(!Array.isArray(basicTextValues))
- basicTextValues = [basicTextValues];
+ _.each(
+ basicText,
+ function (fieldName) {
+ var basicTextValues = this.get(fieldName);
+
+ if (!Array.isArray(basicTextValues))
+ basicTextValues = [basicTextValues];
+
+ // Remove existing nodes
+ datasetNode.children(fieldName.toLowerCase()).remove();
+
+ // Create new nodes
+ var nodes = _.map(basicTextValues, function (value) {
+ if (value) {
+ var node = document.createElement(fieldName.toLowerCase());
+ $(node).text(value);
+ return node;
+ } else {
+ return "";
+ }
+ });
- // Remove existing nodes
- datasetNode.children(fieldName.toLowerCase()).remove();
+ var insertAfter = this.getEMLPosition(eml, fieldName.toLowerCase());
- // Create new nodes
- var nodes = _.map(basicTextValues, function(value) {
+ if (insertAfter) {
+ insertAfter.after(nodes);
+ } else {
+ datasetNode.prepend(nodes);
+ }
+ },
+ this,
+ );
- if(value){
+ // Serialize pubDate
+ // This one is special because it has a default behavior, unlike
+ // the others: When no pubDate is set, it should be set to
+ // the current year
+ var pubDate = this.get("pubDate");
- var node = document.createElement(fieldName.toLowerCase());
- $(node).text(value);
- return node;
+ datasetNode.find("pubdate").remove();
- }
- else{
- return "";
- }
- });
+ if (pubDate != null && pubDate.length > 0) {
+ var pubDateEl = document.createElement("pubdate");
- var insertAfter = this.getEMLPosition(eml, fieldName.toLowerCase());
+ $(pubDateEl).text(pubDate);
- if(insertAfter){
- insertAfter.after(nodes);
- }
- else{
- datasetNode.prepend(nodes);
+ this.getEMLPosition(eml, "pubdate").after(pubDateEl);
}
- }, this);
- // Serialize pubDate
- // This one is special because it has a default behavior, unlike
- // the others: When no pubDate is set, it should be set to
- // the current year
- var pubDate = this.get('pubDate');
-
- datasetNode.find('pubdate').remove();
-
- if (pubDate != null && pubDate.length > 0) {
-
- var pubDateEl = document.createElement('pubdate');
-
- $(pubDateEl).text(pubDate);
-
- this.getEMLPosition(eml, 'pubdate').after(pubDateEl);
- }
+ // Serialize the parts of EML that are eml-text modules
+ var textFields = ["abstract", "additionalInfo"];
+
+ _.each(
+ textFields,
+ function (field) {
+ var fieldName = this.nodeNameMap()[field] || field;
+
+ // Get the EMLText model
+ var emlTextModels = Array.isArray(this.get(field))
+ ? this.get(field)
+ : [this.get(field)];
+ if (!emlTextModels.length) return;
+
+ // Get the node from the EML doc
+ var nodes = datasetNode.find(fieldName);
+
+ // Update the DOMs for each model
+ _.each(
+ emlTextModels,
+ function (thisTextModel, i) {
+ //Don't serialize falsey values
+ if (!thisTextModel) return;
+
+ var node;
+
+ //Get the existing node or create a new one
+ if (nodes.length < i + 1) {
+ node = document.createElement(fieldName);
+ this.getEMLPosition(eml, fieldName).after(node);
+ } else {
+ node = nodes[i];
+ }
- // Serialize the parts of EML that are eml-text modules
- var textFields = ["abstract", "additionalInfo"];
+ $(node).html($(thisTextModel.updateDOM()).html());
+ },
+ this,
+ );
- _.each(textFields, function(field){
+ // Remove the extra nodes
+ this.removeExtraNodes(nodes, emlTextModels);
+ },
+ this,
+ );
+
+ //Create a XML node if there isn't one
+ if (datasetNode.children("coverage").length === 0) {
+ var coverageNode = $(document.createElement("coverage")),
+ coveragePosition = this.getEMLPosition(eml, "coverage");
+
+ if (coveragePosition) coveragePosition.after(coverageNode);
+ else datasetNode.append(coverageNode);
+ } else {
+ var coverageNode = datasetNode.children("coverage").first();
+ }
- var fieldName = this.nodeNameMap()[field] || field;
+ //Serialize the geographic coverage
+ if (
+ typeof this.get("geoCoverage") !== "undefined" &&
+ this.get("geoCoverage").length > 0
+ ) {
+ // Don't serialize if geoCoverage is invalid
+ var validCoverages = _.filter(
+ this.get("geoCoverage"),
+ function (cov) {
+ return cov.isValid();
+ },
+ );
- // Get the EMLText model
- var emlTextModels = Array.isArray(this.get(field)) ? this.get(field) : [this.get(field)];
- if( ! emlTextModels.length ) return;
+ //Get the existing geo coverage nodes from the EML
+ var existingGeoCov = datasetNode.find("geographiccoverage");
- // Get the node from the EML doc
- var nodes = datasetNode.find(fieldName);
+ //Update the DOM of each model
+ _.each(
+ validCoverages,
+ function (cov, position) {
+ //Update the existing node if it exists
+ if (existingGeoCov.length - 1 >= position) {
+ $(existingGeoCov[position]).replaceWith(cov.updateDOM());
+ }
+ //Or, append new nodes
+ else {
+ var insertAfter = existingGeoCov.length
+ ? datasetNode.find("geographiccoverage").last()
+ : null;
+
+ if (insertAfter) insertAfter.after(cov.updateDOM());
+ else coverageNode.append(cov.updateDOM());
+ }
+ },
+ this,
+ );
- // Update the DOMs for each model
- _.each(emlTextModels, function(thisTextModel, i){
- //Don't serialize falsey values
- if(!thisTextModel) return;
+ //Remove existing taxon coverage nodes that don't have an accompanying model
+ this.removeExtraNodes(
+ datasetNode.find("geographiccoverage"),
+ validCoverages,
+ );
+ } else {
+ //If there are no geographic coverages, remove the nodes
+ coverageNode.children("geographiccoverage").remove();
+ }
- var node;
+ //Serialize the taxonomic coverage
+ if (
+ typeof this.get("taxonCoverage") !== "undefined" &&
+ this.get("taxonCoverage").length > 0
+ ) {
+ // Group the taxonomic coverage models into empty and non-empty
+ var sortedTaxonModels = _.groupBy(
+ this.get("taxonCoverage"),
+ function (t) {
+ if (_.flatten(t.get("taxonomicClassification")).length > 0) {
+ return "notEmpty";
+ } else {
+ return "empty";
+ }
+ },
+ );
- //Get the existing node or create a new one
- if(nodes.length < i+1){
- node = document.createElement(fieldName);
- this.getEMLPosition(eml, fieldName).after(node);
+ //Get the existing taxon coverage nodes from the EML
+ var existingTaxonCov = coverageNode.children("taxonomiccoverage");
+
+ //Iterate over each taxon coverage and update it's DOM
+ if (
+ sortedTaxonModels["notEmpty"] &&
+ sortedTaxonModels["notEmpty"].length > 0
+ ) {
+ //Update the DOM of each model
+ _.each(
+ sortedTaxonModels["notEmpty"],
+ function (taxonCoverage, position) {
+ //Update the existing taxonCoverage node if it exists
+ if (existingTaxonCov.length - 1 >= position) {
+ $(existingTaxonCov[position]).replaceWith(
+ taxonCoverage.updateDOM(),
+ );
+ }
+ //Or, append new nodes
+ else {
+ coverageNode.append(taxonCoverage.updateDOM());
+ }
+ },
+ );
+ //Remove existing taxon coverage nodes that don't have an accompanying model
+ this.removeExtraNodes(existingTaxonCov, this.get("taxonCoverage"));
}
- else {
- node = nodes[i];
+ //If all the taxon coverages are empty, remove the parent taxonomicCoverage node
+ else if (
+ !sortedTaxonModels["notEmpty"] ||
+ sortedTaxonModels["notEmpty"].length == 0
+ ) {
+ existingTaxonCov.remove();
}
+ }
- $(node).html( $(thisTextModel.updateDOM() ).html());
-
- }, this);
-
- // Remove the extra nodes
- this.removeExtraNodes(nodes, emlTextModels);
-
- }, this);
-
- //Create a XML node if there isn't one
- if( datasetNode.children('coverage').length === 0 ) {
- var coverageNode = $(document.createElement('coverage')),
- coveragePosition = this.getEMLPosition(eml, 'coverage');
-
- if(coveragePosition)
- coveragePosition.after(coverageNode);
- else
- datasetNode.append(coverageNode);
- }
- else{
- var coverageNode = datasetNode.children("coverage").first();
- }
-
- //Serialize the geographic coverage
- if ( typeof this.get('geoCoverage') !== 'undefined' && this.get('geoCoverage').length > 0) {
-
- // Don't serialize if geoCoverage is invalid
- var validCoverages = _.filter(this.get('geoCoverage'), function(cov) {
- return cov.isValid();
- });
-
- //Get the existing geo coverage nodes from the EML
- var existingGeoCov = datasetNode.find("geographiccoverage");
+ //Serialize the temporal coverage
+ var existingTemporalCoverages = datasetNode.find("temporalcoverage");
//Update the DOM of each model
- _.each(validCoverages, function(cov, position){
-
- //Update the existing node if it exists
- if(existingGeoCov.length-1 >= position){
- $(existingGeoCov[position]).replaceWith(cov.updateDOM());
- }
- //Or, append new nodes
- else{
- var insertAfter = existingGeoCov.length? datasetNode.find("geographiccoverage").last() : null;
-
- if(insertAfter)
- insertAfter.after(cov.updateDOM());
- else
- coverageNode.append(cov.updateDOM());
- }
- }, this);
-
- //Remove existing taxon coverage nodes that don't have an accompanying model
- this.removeExtraNodes(datasetNode.find("geographiccoverage"), validCoverages);
- }
- else{
- //If there are no geographic coverages, remove the nodes
- coverageNode.children("geographiccoverage").remove();
- }
-
- //Serialize the taxonomic coverage
- if ( typeof this.get('taxonCoverage') !== 'undefined' && this.get('taxonCoverage').length > 0) {
-
- // Group the taxonomic coverage models into empty and non-empty
- var sortedTaxonModels = _.groupBy(this.get('taxonCoverage'), function(t) {
- if( _.flatten(t.get('taxonomicClassification')).length > 0 ){
- return "notEmpty";
- }
- else{
- return "empty";
- }
- });
-
- //Get the existing taxon coverage nodes from the EML
- var existingTaxonCov = coverageNode.children("taxonomiccoverage");
-
- //Iterate over each taxon coverage and update it's DOM
- if(sortedTaxonModels["notEmpty"] && sortedTaxonModels["notEmpty"].length > 0) {
-
- //Update the DOM of each model
- _.each(sortedTaxonModels["notEmpty"], function(taxonCoverage, position){
-
- //Update the existing taxonCoverage node if it exists
- if(existingTaxonCov.length-1 >= position){
- $(existingTaxonCov[position]).replaceWith(taxonCoverage.updateDOM());
+ _.each(
+ this.get("temporalCoverage"),
+ function (temporalCoverage, position) {
+ //Update the existing temporalCoverage node if it exists
+ if (existingTemporalCoverages.length - 1 >= position) {
+ $(existingTemporalCoverages[position]).replaceWith(
+ temporalCoverage.updateDOM(),
+ );
}
//Or, append new nodes
- else{
- coverageNode.append(taxonCoverage.updateDOM());
+ else {
+ coverageNode.append(temporalCoverage.updateDOM());
}
- });
+ },
+ );
- //Remove existing taxon coverage nodes that don't have an accompanying model
- this.removeExtraNodes(existingTaxonCov, this.get("taxonCoverage"));
-
- }
- //If all the taxon coverages are empty, remove the parent taxonomicCoverage node
- else if( !sortedTaxonModels["notEmpty"] || sortedTaxonModels["notEmpty"].length == 0 ){
- existingTaxonCov.remove();
+ //Remove existing taxon coverage nodes that don't have an accompanying model
+ this.removeExtraNodes(
+ existingTemporalCoverages,
+ this.get("temporalCoverage"),
+ );
+
+ //Remove the temporal coverage if it is empty
+ if (!coverageNode.children("temporalcoverage").children().length) {
+ coverageNode.children("temporalcoverage").remove();
}
- }
-
- //Serialize the temporal coverage
- var existingTemporalCoverages = datasetNode.find("temporalcoverage");
-
- //Update the DOM of each model
- _.each(this.get("temporalCoverage"), function(temporalCoverage, position){
-
- //Update the existing temporalCoverage node if it exists
- if(existingTemporalCoverages.length-1 >= position){
- $(existingTemporalCoverages[position]).replaceWith(temporalCoverage.updateDOM());
+ //Remove the node if it's empty
+ if (coverageNode.children().length == 0) {
+ coverageNode.remove();
}
- //Or, append new nodes
- else{
- coverageNode.append(temporalCoverage.updateDOM());
- }
- });
-
- //Remove existing taxon coverage nodes that don't have an accompanying model
- this.removeExtraNodes(existingTemporalCoverages, this.get("temporalCoverage"));
-
- //Remove the temporal coverage if it is empty
- if( !coverageNode.children("temporalcoverage").children().length ){
- coverageNode.children("temporalcoverage").remove();
- }
- //Remove the node if it's empty
- if(coverageNode.children().length == 0){
- coverageNode.remove();
- }
+ // Dataset-level annotations
+ datasetNode.children("annotation").remove();
- // Dataset-level annotations
- datasetNode.children("annotation").remove();
-
- if( this.get("annotations") ){
- this.get("annotations").each(function(annotation) {
- if (annotation.isEmpty()) {
- return;
- }
+ if (this.get("annotations")) {
+ this.get("annotations").each(function (annotation) {
+ if (annotation.isEmpty()) {
+ return;
+ }
- var after = this.getEMLPosition(eml, "annotation");
+ var after = this.getEMLPosition(eml, "annotation");
- $(after).after(annotation.updateDOM());
- }, this);
+ $(after).after(annotation.updateDOM());
+ }, this);
- //Since there is at least one annotation, the dataset node needs to have an id attribute.
- datasetNode.attr("id", this.getUniqueEntityId(this));
- }
+ //Since there is at least one annotation, the dataset node needs to have an id attribute.
+ datasetNode.attr("id", this.getUniqueEntityId(this));
+ }
- //If there is no creator, create one from the user
- if(!this.get("creator").length){
- var party = new EMLParty({ parentModel: this, type: "creator" });
+ //If there is no creator, create one from the user
+ if (!this.get("creator").length) {
+ var party = new EMLParty({ parentModel: this, type: "creator" });
- party.createFromUser();
+ party.createFromUser();
- this.set("creator", [party]);
- }
+ this.set("creator", [party]);
+ }
- //Serialize the creators
- this.serializeParties(eml, "creator");
+ //Serialize the creators
+ this.serializeParties(eml, "creator");
- //Serialize the metadata providers
- this.serializeParties(eml, "metadataProvider");
+ //Serialize the metadata providers
+ this.serializeParties(eml, "metadataProvider");
- //Serialize the associated parties
- this.serializeParties(eml, "associatedParty");
+ //Serialize the associated parties
+ this.serializeParties(eml, "associatedParty");
- //Serialize the contacts
- this.serializeParties(eml, "contact");
+ //Serialize the contacts
+ this.serializeParties(eml, "contact");
- //Serialize the publishers
- this.serializeParties(eml, "publisher");
+ //Serialize the publishers
+ this.serializeParties(eml, "publisher");
- // Serialize methods
- if(this.get('methods')) {
+ // Serialize methods
+ if (this.get("methods")) {
+ //If the methods model is empty, remove it from the EML
+ if (this.get("methods").isEmpty())
+ datasetNode.find("methods").remove();
+ else {
+ //Serialize the methods model
+ var methodsEl = this.get("methods").updateDOM();
- //If the methods model is empty, remove it from the EML
- if( this.get("methods").isEmpty() )
- datasetNode.find("methods").remove();
- else{
+ //If the methodsEl is an empty string or other falsey value, then remove the methods node
+ if (!methodsEl || !$(methodsEl).children().length) {
+ datasetNode.find("methods").remove();
+ } else {
+ //Add the node to the EML
+ datasetNode.find("methods").detach();
- //Serialize the methods model
- var methodsEl = this.get('methods').updateDOM();
+ var insertAfter = this.getEMLPosition(eml, "methods");
- //If the methodsEl is an empty string or other falsey value, then remove the methods node
- if( !methodsEl || !$(methodsEl).children().length ){
+ if (insertAfter) insertAfter.after(methodsEl);
+ else datasetNode.append(methodsEl);
+ }
+ }
+ }
+ //If there are no methods, then remove the methods nodes
+ else {
+ if (datasetNode.find("methods").length > 0) {
datasetNode.find("methods").remove();
}
- else{
-
- //Add the node to the EML
- datasetNode.find("methods").detach();
+ }
- var insertAfter = this.getEMLPosition(eml, "methods");
+ //Serialize the keywords
+ this.serializeKeywords(eml, "keywordSets");
- if(insertAfter)
- insertAfter.after(methodsEl);
- else
- datasetNode.append(methodsEl);
+ //Serialize the intellectual rights
+ if (this.get("intellectualRights")) {
+ if (datasetNode.find("intellectualRights").length)
+ datasetNode
+ .find("intellectualRights")
+ .html("" + this.get("intellectualRights") + " ");
+ else {
+ this.getEMLPosition(eml, "intellectualrights").after(
+ $(document.createElement("intellectualRights")).html(
+ "" + this.get("intellectualRights") + " ",
+ ),
+ );
}
}
- }
- //If there are no methods, then remove the methods nodes
- else{
- if( datasetNode.find("methods").length > 0 ){
- datasetNode.find("methods").remove();
+ // Serialize the distribution
+ const distributions = this.get("distribution");
+ if (distributions && distributions.length > 0) {
+ // Remove existing nodes
+ datasetNode.children("distribution").remove();
+ // Get the updated DOMs
+ const distributionDOMs = distributions.map((d) => d.updateDOM());
+ // Insert the updated DOMs in their correct positions
+ distributionDOMs.forEach((dom, i) => {
+ const insertAfter = this.getEMLPosition(eml, "distribution");
+ if (insertAfter) {
+ insertAfter.after(dom);
+ } else {
+ datasetNode.append(dom);
+ }
+ });
}
- }
-
- //Serialize the keywords
- this.serializeKeywords(eml, "keywordSets");
-
- //Serialize the intellectual rights
- if(this.get("intellectualRights")){
- if(datasetNode.find("intellectualRights").length)
- datasetNode.find("intellectualRights").html("" + this.get("intellectualRights") + " ")
- else{
-
- this.getEMLPosition(eml, "intellectualrights").after(
- $(document.createElement("intellectualRights"))
- .html("" + this.get("intellectualRights") + " "));
+ //Detach the project elements from the DOM
+ if (datasetNode.find("project").length) {
+ datasetNode.find("project").detach();
}
- }
-
- // Serialize the distribution
- const distributions = this.get('distribution');
- if (distributions && distributions.length > 0) {
- // Remove existing nodes
- datasetNode.children('distribution').remove();
- // Get the updated DOMs
- const distributionDOMs = distributions.map(d => d.updateDOM());
- // Insert the updated DOMs in their correct positions
- distributionDOMs.forEach((dom, i) => {
- const insertAfter = this.getEMLPosition(eml, 'distribution');
- if (insertAfter) {
- insertAfter.after(dom);
- } else {
- datasetNode.append(dom);
- }
- });
- }
-
- //Detach the project elements from the DOM
- if(datasetNode.find("project").length){
-
- datasetNode.find("project").detach();
-
- }
-
- //If there is an EMLProject, update its DOM
- if(this.get("project")){
-
- this.getEMLPosition(eml, "project").after(this.get("project").updateDOM());
- }
-
- //Get the existing taxon coverage nodes from the EML
- var existingEntities = datasetNode.find("otherEntity, dataTable, spatialRaster, spatialVector, storedProcedure, view");
-
- //Serialize the entities
- _.each(this.get("entities"), function(entity, position) {
-
- //Update the existing node if it exists
- if(existingEntities.length - 1 >= position) {
- //Remove the entity from the EML
- $(existingEntities[position]).detach();
- //Insert it into the correct position
- this.getEMLPosition(eml, entity.get("type").toLowerCase()).after(entity.updateDOM()); }
- //Or, append new nodes
- else {
- //Inser the entity into the correct position
- this.getEMLPosition(eml, entity.get("type").toLowerCase()).after(entity.updateDOM());
+ //If there is an EMLProject, update its DOM
+ if (this.get("project")) {
+ this.getEMLPosition(eml, "project").after(
+ this.get("project").updateDOM(),
+ );
}
- }, this);
-
- //Remove extra entities that have been removed
- var numExtraEntities = existingEntities.length - this.get("entities").length;
- for( var i = (existingEntities.length - numExtraEntities); i 1 ){
- //And if it is not a unit node, which we don't want to change,
- if( !$(el).is("unit") )
- //Then change the id attribute to a random uuid
- $(el).attr("id", "urn-uuid-" + uuid.v4());
+ //Get the existing taxon coverage nodes from the EML
+ var existingEntities = datasetNode.find(
+ "otherEntity, dataTable, spatialRaster, spatialVector, storedProcedure, view",
+ );
+
+ //Serialize the entities
+ _.each(
+ this.get("entities"),
+ function (entity, position) {
+ //Update the existing node if it exists
+ if (existingEntities.length - 1 >= position) {
+ //Remove the entity from the EML
+ $(existingEntities[position]).detach();
+ //Insert it into the correct position
+ this.getEMLPosition(eml, entity.get("type").toLowerCase()).after(
+ entity.updateDOM(),
+ );
+ }
+ //Or, append new nodes
+ else {
+ //Inser the entity into the correct position
+ this.getEMLPosition(eml, entity.get("type").toLowerCase()).after(
+ entity.updateDOM(),
+ );
}
+ },
+ this,
+ );
+
+ //Remove extra entities that have been removed
+ var numExtraEntities =
+ existingEntities.length - this.get("entities").length;
+ for (
+ var i = existingEntities.length - numExtraEntities;
+ i < existingEntities.length;
+ i++
+ ) {
+ $(existingEntities)[i].remove();
+ }
+ //Do a final check to make sure there are no duplicate ids in the EML
+ var elementsWithIDs = $(eml).find("[id]"),
+ //Get an array of all the ids in this EML doc
+ allIDs = _.map(elementsWithIDs, function (el) {
+ return $(el).attr("id");
});
+ //If there is at least one id in the EML...
+ if (allIDs && allIDs.length) {
+ //Boil the array down to just the unique values
+ var uniqueIDs = _.uniq(allIDs);
+
+ //If the unique array is shorter than the array of all ids,
+ // then there is a duplicate somewhere
+ if (uniqueIDs.length < allIDs.length) {
+ //For each element in the EML that has an id,
+ _.each(elementsWithIDs, function (el) {
+ //Get the id for this element
+ var id = $(el).attr("id");
+
+ //If there is more than one element in the EML with this id,
+ if ($(eml).find("[id='" + id + "']").length > 1) {
+ //And if it is not a unit node, which we don't want to change,
+ if (!$(el).is("unit"))
+ //Then change the id attribute to a random uuid
+ $(el).attr("id", "urn-uuid-" + uuid.v4());
+ }
+ });
+ }
}
- }
-
- //Camel-case the XML
- var emlString = "";
- _.each(html, function(rootEMLNode){ emlString += this.formatXML(rootEMLNode); }, this);
-
- return emlString;
- },
-
- /*
- * Given an EML DOM and party type, this function updated and/or adds the EMLParties to the EML
- */
- serializeParties: function(eml, type){
-
- //Remove the nodes from the EML for this party type
- $(eml).children("dataset").children(type.toLowerCase()).remove();
-
- //Serialize each party of this type
- _.each(this.get(type), function(party, i){
-
- //Get the last node of this type to insert after
- var insertAfter = $(eml).children("dataset").children(type.toLowerCase()).last();
-
- //If there isn't a node found, find the EML position to insert after
- if( !insertAfter.length ) {
- insertAfter = this.getEMLPosition(eml, type);
- }
- //Update the DOM of the EMLParty
- var emlPartyDOM = party.updateDOM();
+ //Camel-case the XML
+ var emlString = "";
+ _.each(
+ html,
+ function (rootEMLNode) {
+ emlString += this.formatXML(rootEMLNode);
+ },
+ this,
+ );
+
+ return emlString;
+ },
- //Make sure we don't insert empty EMLParty nodes into the EML
- if( $(emlPartyDOM).children().length ){
- //Insert the party DOM at the insert position
- if ( insertAfter && insertAfter.length )
- insertAfter.after(emlPartyDOM);
- //If an insert position still hasn't been found, then just append to the dataset node
- else
- $(eml).find("dataset").append(emlPartyDOM);
+ /*
+ * Given an EML DOM and party type, this function updated and/or adds the EMLParties to the EML
+ */
+ serializeParties: function (eml, type) {
+ //Remove the nodes from the EML for this party type
+ $(eml).children("dataset").children(type.toLowerCase()).remove();
+
+ //Serialize each party of this type
+ _.each(
+ this.get(type),
+ function (party, i) {
+ //Get the last node of this type to insert after
+ var insertAfter = $(eml)
+ .children("dataset")
+ .children(type.toLowerCase())
+ .last();
+
+ //If there isn't a node found, find the EML position to insert after
+ if (!insertAfter.length) {
+ insertAfter = this.getEMLPosition(eml, type);
}
- }, this);
-
- //Create a certain parties from the current app user if none is given
- if(type == "contact" && !this.get("contact").length){
- //Get the creators
- var creators = this.get("creator"),
- contacts = [];
+ //Update the DOM of the EMLParty
+ var emlPartyDOM = party.updateDOM();
- _.each(creators, function(creator){
- //Clone the creator model and add it to the contacts array
- var newModel = new EMLParty({ parentModel: this });
- newModel.set(creator.toJSON());
- newModel.set("type", type);
-
- contacts.push(newModel);
- }, this);
-
- this.set(type, contacts);
+ //Make sure we don't insert empty EMLParty nodes into the EML
+ if ($(emlPartyDOM).children().length) {
+ //Insert the party DOM at the insert position
+ if (insertAfter && insertAfter.length)
+ insertAfter.after(emlPartyDOM);
+ //If an insert position still hasn't been found, then just append to the dataset node
+ else $(eml).find("dataset").append(emlPartyDOM);
+ }
+ },
+ this,
+ );
+
+ //Create a certain parties from the current app user if none is given
+ if (type == "contact" && !this.get("contact").length) {
+ //Get the creators
+ var creators = this.get("creator"),
+ contacts = [];
+
+ _.each(
+ creators,
+ function (creator) {
+ //Clone the creator model and add it to the contacts array
+ var newModel = new EMLParty({ parentModel: this });
+ newModel.set(creator.toJSON());
+ newModel.set("type", type);
+
+ contacts.push(newModel);
+ },
+ this,
+ );
- //Call this function again to serialize the new models
- this.serializeParties(eml, type);
- }
- },
+ this.set(type, contacts);
+ //Call this function again to serialize the new models
+ this.serializeParties(eml, type);
+ }
+ },
- serializeKeywords: function(eml) {
+ serializeKeywords: function (eml) {
// Remove all existing keywordSets before appending
- $(eml).find('dataset').find('keywordset').remove();
+ $(eml).find("dataset").find("keywordset").remove();
- if (this.get('keywordSets').length == 0) return;
+ if (this.get("keywordSets").length == 0) return;
// Create the new keywordSets nodes
- var nodes = _.map(this.get('keywordSets'), function(kwd) {
+ var nodes = _.map(this.get("keywordSets"), function (kwd) {
return kwd.updateDOM();
});
- this.getEMLPosition(eml, "keywordset").after(nodes);
+ this.getEMLPosition(eml, "keywordset").after(nodes);
},
/*
* Remoes nodes from the EML that do not have an accompanying model
* (Were probably removed from the EML by the user during editing)
*/
- removeExtraNodes: function(nodes, models){
+ removeExtraNodes: function (nodes, models) {
// Remove the extra nodes
- var extraNodes = nodes.length - models.length;
- if(extraNodes > 0){
- for(var i = models.length; i < nodes.length; i++){
- $(nodes[i]).remove();
- }
- }
+ var extraNodes = nodes.length - models.length;
+ if (extraNodes > 0) {
+ for (var i = models.length; i < nodes.length; i++) {
+ $(nodes[i]).remove();
+ }
+ }
},
/*
* Saves the EML document to the server using the DataONE API
*/
- save: function(attributes, options){
-
+ save: function (attributes, options) {
//Validate before we try anything else
- if(!this.isValid()){
+ if (!this.isValid()) {
this.trigger("invalid");
this.trigger("cancelSave");
return false;
- }
- else{
+ } else {
this.trigger("valid");
}
@@ -1235,34 +1337,34 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
this.set("draftSaved", false);
//Create the creator from the current user if none is provided
- if(!this.get("creator").length){
- var party = new EMLParty({ parentModel: this, type: "creator" });
+ if (!this.get("creator").length) {
+ var party = new EMLParty({ parentModel: this, type: "creator" });
- party.createFromUser();
+ party.createFromUser();
- this.set("creator", [party]);
+ this.set("creator", [party]);
}
//Create the contact from the current user if none is provided
- if(!this.get("contact").length){
- var party = new EMLParty({ parentModel: this, type: "contact" });
+ if (!this.get("contact").length) {
+ var party = new EMLParty({ parentModel: this, type: "contact" });
- party.createFromUser();
+ party.createFromUser();
- this.set("contact", [party]);
+ this.set("contact", [party]);
}
//If this is an existing object and there is no system metadata, retrieve it
- if(!this.isNew() && !this.get("sysMetaXML")){
+ if (!this.isNew() && !this.get("sysMetaXML")) {
var model = this;
//When the system metadata is fetched, try saving again
var fetchOptions = {
- success: function(response){
- model.set(DataONEObject.prototype.parse.call(model, response));
- model.save(attributes, options);
- }
- }
+ success: function (response) {
+ model.set(DataONEObject.prototype.parse.call(model, response));
+ model.save(attributes, options);
+ },
+ };
//Fetch the system metadata now
this.fetchSystemMetadata(fetchOptions);
@@ -1270,185 +1372,198 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
return;
}
- //Create a FormData object to send data with our XHR
- var formData = new FormData();
-
- try{
-
- //Add the identifier to the XHR data
- if(this.isNew()){
- formData.append("pid", this.get("id"));
- }
- else{
- //Create a new ID
- this.updateID();
-
- //Add the ids to the form data
- formData.append("newPid", this.get("id"));
- formData.append("pid", this.get("oldPid"));
- }
-
- //Serialize the EML XML
- var xml = this.serialize();
- var xmlBlob = new Blob([xml], {type : 'application/xml'});
-
- //Get the size of the new EML XML
- this.set("size", xmlBlob.size);
-
- //Get the new checksum of the EML XML
- var checksum = md5(xml);
- this.set("checksum", checksum);
- this.set("checksumAlgorithm", "MD5");
-
- //Create the system metadata XML
- var sysMetaXML = this.serializeSysMeta();
-
- //Send the system metadata as a Blob
- var sysMetaXMLBlob = new Blob([sysMetaXML], {type : 'application/xml'});
-
- //Add the object XML and System Metadata XML to the form data
- //Append the system metadata first, so we can take advantage of Metacat's streaming multipart handler
- formData.append("sysmeta", sysMetaXMLBlob, "sysmeta");
- formData.append("object", xmlBlob);
- }
- catch(error){
- //Reset the identifier since we didn't actually update the object
- this.resetID();
-
- this.set("uploadStatus", "e");
- this.trigger("error");
- this.trigger("cancelSave");
- return false;
- }
-
- var model = this;
- var saveOptions = options || {};
- _.extend(saveOptions, {
- data : formData,
- cache: false,
- contentType: false,
- dataType: "text",
- processData: false,
- parse: false,
- //Use the URL function to determine the URL
- url: this.isNew() ? this.url() : this.url({update: true}),
- xhr: function(){
- var xhr = new window.XMLHttpRequest();
-
- //Upload progress
- xhr.upload.addEventListener("progress", function(evt){
- if (evt.lengthComputable) {
- var percentComplete = evt.loaded / evt.total * 100;
-
- model.set("uploadProgress", percentComplete);
- }
- }, false);
-
- return xhr;
- },
- success: function(model, response, xhr){
-
- model.set("numSaveAttempts", 0);
- model.set("uploadStatus", "c");
- model.set("sysMetaXML", model.serializeSysMeta());
- model.set("oldPid", null);
- model.fetch({merge: true, systemMetadataOnly: true});
- model.trigger("successSaving", model);
-
- },
- error: function(model, response, xhr){
-
- model.set("numSaveAttempts", model.get("numSaveAttempts") + 1);
- var numSaveAttempts = model.get("numSaveAttempts");
+ //Create a FormData object to send data with our XHR
+ var formData = new FormData();
- //Reset the identifier changes
- model.resetID();
-
- if( numSaveAttempts < 3 && (response.status == 408 || response.status == 0) ){
+ try {
+ //Add the identifier to the XHR data
+ if (this.isNew()) {
+ formData.append("pid", this.get("id"));
+ } else {
+ //Create a new ID
+ this.updateID();
- //Try saving again in 10, 40, and 90 seconds
- setTimeout(function(){
- model.save.call(model);
- },
- (numSaveAttempts * numSaveAttempts) * 10000);
+ //Add the ids to the form data
+ formData.append("newPid", this.get("id"));
+ formData.append("pid", this.get("oldPid"));
}
- else{
- model.set("numSaveAttempts", 0);
-
- //Get the error error information
- var errorDOM = $($.parseHTML(response.responseText)),
- errorContainer = errorDOM.filter("error"),
- msgContainer = errorContainer.length? errorContainer.find("description") : errorDOM.not("style, title"),
- errorMsg = msgContainer.length? msgContainer.text() : errorDOM;
-
- //When there is no network connection (status == 0), there will be no response text
- if(!errorMsg || (response.status == 408 || response.status == 0))
- errorMsg = "There was a network issue that prevented your metadata from uploading. " +
- "Make sure you are connected to a reliable internet connection.";
-
- //Save the error message in the model
- model.set("errorMessage", errorMsg);
-
- //Set the model status as e for error
- model.set("uploadStatus", "e");
- //Save the EML as a plain text file, until drafts are a supported feature
- var copy = model.createTextCopy();
+ //Serialize the EML XML
+ var xml = this.serialize();
+ var xmlBlob = new Blob([xml], { type: "application/xml" });
- //If the EML copy successfully saved, let the user know that there is a copy saved behind the scenes
- model.listenToOnce(copy, "successSaving", function(){
+ //Get the size of the new EML XML
+ this.set("size", xmlBlob.size);
- model.set("draftSaved", true);
+ //Get the new checksum of the EML XML
+ var checksum = md5(xml);
+ this.set("checksum", checksum);
+ this.set("checksumAlgorithm", "MD5");
- //Trigger the errorSaving event so other parts of the app know that the model failed to save
- //And send the error message with it
- model.trigger("errorSaving", errorMsg);
+ //Create the system metadata XML
+ var sysMetaXML = this.serializeSysMeta();
- });
+ //Send the system metadata as a Blob
+ var sysMetaXMLBlob = new Blob([sysMetaXML], {
+ type: "application/xml",
+ });
- //If the EML copy fails to save too, then just display the usual error message
- model.listenToOnce(copy, "errorSaving", function(){
+ //Add the object XML and System Metadata XML to the form data
+ //Append the system metadata first, so we can take advantage of Metacat's streaming multipart handler
+ formData.append("sysmeta", sysMetaXMLBlob, "sysmeta");
+ formData.append("object", xmlBlob);
+ } catch (error) {
+ //Reset the identifier since we didn't actually update the object
+ this.resetID();
- //Trigger the errorSaving event so other parts of the app know that the model failed to save
- //And send the error message with it
- model.trigger("errorSaving", errorMsg);
+ this.set("uploadStatus", "e");
+ this.trigger("error");
+ this.trigger("cancelSave");
+ return false;
+ }
- });
+ var model = this;
+ var saveOptions = options || {};
+ _.extend(
+ saveOptions,
+ {
+ data: formData,
+ cache: false,
+ contentType: false,
+ dataType: "text",
+ processData: false,
+ parse: false,
+ //Use the URL function to determine the URL
+ url: this.isNew() ? this.url() : this.url({ update: true }),
+ xhr: function () {
+ var xhr = new window.XMLHttpRequest();
+
+ //Upload progress
+ xhr.upload.addEventListener(
+ "progress",
+ function (evt) {
+ if (evt.lengthComputable) {
+ var percentComplete = (evt.loaded / evt.total) * 100;
+
+ model.set("uploadProgress", percentComplete);
+ }
+ },
+ false,
+ );
- //Save the EML plain text copy
- copy.save();
+ return xhr;
+ },
+ success: function (model, response, xhr) {
+ model.set("numSaveAttempts", 0);
+ model.set("uploadStatus", "c");
+ model.set("sysMetaXML", model.serializeSysMeta());
+ model.set("oldPid", null);
+ model.fetch({ merge: true, systemMetadataOnly: true });
+ model.trigger("successSaving", model);
+ },
+ error: function (model, response, xhr) {
+ model.set("numSaveAttempts", model.get("numSaveAttempts") + 1);
+ var numSaveAttempts = model.get("numSaveAttempts");
+
+ //Reset the identifier changes
+ model.resetID();
+
+ if (
+ numSaveAttempts < 3 &&
+ (response.status == 408 || response.status == 0)
+ ) {
+ //Try saving again in 10, 40, and 90 seconds
+ setTimeout(
+ function () {
+ model.save.call(model);
+ },
+ numSaveAttempts * numSaveAttempts * 10000,
+ );
+ } else {
+ model.set("numSaveAttempts", 0);
+
+ //Get the error error information
+ var errorDOM = $($.parseHTML(response.responseText)),
+ errorContainer = errorDOM.filter("error"),
+ msgContainer = errorContainer.length
+ ? errorContainer.find("description")
+ : errorDOM.not("style, title"),
+ errorMsg = msgContainer.length
+ ? msgContainer.text()
+ : errorDOM;
+
+ //When there is no network connection (status == 0), there will be no response text
+ if (!errorMsg || response.status == 408 || response.status == 0)
+ errorMsg =
+ "There was a network issue that prevented your metadata from uploading. " +
+ "Make sure you are connected to a reliable internet connection.";
+
+ //Save the error message in the model
+ model.set("errorMessage", errorMsg);
+
+ //Set the model status as e for error
+ model.set("uploadStatus", "e");
+
+ //Save the EML as a plain text file, until drafts are a supported feature
+ var copy = model.createTextCopy();
+
+ //If the EML copy successfully saved, let the user know that there is a copy saved behind the scenes
+ model.listenToOnce(copy, "successSaving", function () {
+ model.set("draftSaved", true);
+
+ //Trigger the errorSaving event so other parts of the app know that the model failed to save
+ //And send the error message with it
+ model.trigger("errorSaving", errorMsg);
+ });
- // Track the error
- MetacatUI.analytics?.trackException(
- `EML save error: ${errorMsg}, EML draft: ${copy.get("id")}`,
- model.get("id"),
- true
- );
- }
- }
- }, MetacatUI.appUserModel.createAjaxSettings());
+ //If the EML copy fails to save too, then just display the usual error message
+ model.listenToOnce(copy, "errorSaving", function () {
+ //Trigger the errorSaving event so other parts of the app know that the model failed to save
+ //And send the error message with it
+ model.trigger("errorSaving", errorMsg);
+ });
- return Backbone.Model.prototype.save.call(this, attributes, saveOptions);
- },
+ //Save the EML plain text copy
+ copy.save();
+ // Track the error
+ MetacatUI.analytics?.trackException(
+ `EML save error: ${errorMsg}, EML draft: ${copy.get("id")}`,
+ model.get("id"),
+ true,
+ );
+ }
+ },
+ },
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
+
+ return Backbone.Model.prototype.save.call(
+ this,
+ attributes,
+ saveOptions,
+ );
+ },
/*
* Checks if this EML model has all the required values necessary to save to the server
*/
- validate: function() {
+ validate: function () {
let errors = {};
//A title is always required by EML
- if( !this.get("title").length || !this.get("title")[0] ){
+ if (!this.get("title").length || !this.get("title")[0]) {
errors.title = "A title is required";
}
// Validate the publication date
if (this.get("pubDate") != null) {
if (!this.isValidYearDate(this.get("pubDate"))) {
- errors["pubDate"] = ["The value entered for publication date, '"
- + this.get("pubDate") +
- "' is not a valid value for this field. Enter with a year (e.g. 2017) or a date in the format YYYY-MM-DD."]
+ errors["pubDate"] = [
+ "The value entered for publication date, '" +
+ this.get("pubDate") +
+ "' is not a valid value for this field. Enter with a year (e.g. 2017) or a date in the format YYYY-MM-DD.",
+ ];
}
}
@@ -1456,103 +1571,114 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
errors.temporalCoverage = [];
//If temporal coverage is required and there aren't any, return an error
- if( MetacatUI.appModel.get("emlEditorRequiredFields").temporalCoverage &&
- !this.get("temporalCoverage").length ){
- errors.temporalCoverage = [{ beginDate: "Provide a begin date." }];
+ if (
+ MetacatUI.appModel.get("emlEditorRequiredFields").temporalCoverage &&
+ !this.get("temporalCoverage").length
+ ) {
+ errors.temporalCoverage = [{ beginDate: "Provide a begin date." }];
}
//If temporal coverage is required and they are all empty, return an error
- else if( MetacatUI.appModel.get("emlEditorRequiredFields").temporalCoverage &&
- _.every(this.get("temporalCoverage"), function(tc){
- return tc.isEmpty();
- }) ){
- errors.temporalCoverage = [{ beginDate: "Provide a begin date." }];
+ else if (
+ MetacatUI.appModel.get("emlEditorRequiredFields").temporalCoverage &&
+ _.every(this.get("temporalCoverage"), function (tc) {
+ return tc.isEmpty();
+ })
+ ) {
+ errors.temporalCoverage = [{ beginDate: "Provide a begin date." }];
}
//If temporal coverage is not required, validate each one
- else if( this.get("temporalCoverage").length ||
- ( MetacatUI.appModel.get("emlEditorRequiredFields").temporalCoverage &&
- _.every(this.get("temporalCoverage"), function(tc){
- return tc.isEmpty();
- }) )) {
+ else if (
+ this.get("temporalCoverage").length ||
+ (MetacatUI.appModel.get("emlEditorRequiredFields").temporalCoverage &&
+ _.every(this.get("temporalCoverage"), function (tc) {
+ return tc.isEmpty();
+ }))
+ ) {
//Iterate over each temporal coverage and add it's validation errors
- _.each(this.get("temporalCoverage"), function(temporalCoverage){
- if( !temporalCoverage.isValid() && !temporalCoverage.isEmpty() ){
+ _.each(this.get("temporalCoverage"), function (temporalCoverage) {
+ if (!temporalCoverage.isValid() && !temporalCoverage.isEmpty()) {
errors.temporalCoverage.push(temporalCoverage.validationError);
}
});
}
//Remove the temporalCoverage attribute if no errors were found
- if( errors.temporalCoverage.length == 0 ){
+ if (errors.temporalCoverage.length == 0) {
delete errors.temporalCoverage;
}
//Validate the EMLParty models
- var partyTypes = ["associatedParty", "contact", "creator", "metadataProvider", "publisher"];
- _.each(partyTypes, function(type){
-
- var people = this.get(type);
- _.each(people, function(person, i){
-
- if( !person.isValid() ){
- if( !errors[type] )
- errors[type] = [person.validationError];
- else
- errors[type].push(person.validationError);
- }
-
- }, this);
-
- }, this);
+ var partyTypes = [
+ "associatedParty",
+ "contact",
+ "creator",
+ "metadataProvider",
+ "publisher",
+ ];
+ _.each(
+ partyTypes,
+ function (type) {
+ var people = this.get(type);
+ _.each(
+ people,
+ function (person, i) {
+ if (!person.isValid()) {
+ if (!errors[type]) errors[type] = [person.validationError];
+ else errors[type].push(person.validationError);
+ }
+ },
+ this,
+ );
+ },
+ this,
+ );
//Validate the EMLGeoCoverage models
- _.each(this.get("geoCoverage"), function(geoCoverageModel, i){
-
- if( !geoCoverageModel.isValid() ){
- if( !errors.geoCoverage )
- errors.geoCoverage = [geoCoverageModel.validationError];
- else
- errors.geoCoverage.push(geoCoverageModel.validationError);
- }
-
- }, this);
+ _.each(
+ this.get("geoCoverage"),
+ function (geoCoverageModel, i) {
+ if (!geoCoverageModel.isValid()) {
+ if (!errors.geoCoverage)
+ errors.geoCoverage = [geoCoverageModel.validationError];
+ else errors.geoCoverage.push(geoCoverageModel.validationError);
+ }
+ },
+ this,
+ );
//Validate the EMLTaxonCoverage model
var taxonModel = this.get("taxonCoverage")[0];
- if( !taxonModel.isEmpty() && !taxonModel.isValid() ){
+ if (!taxonModel.isEmpty() && !taxonModel.isValid()) {
errors = _.extend(errors, taxonModel.validationError);
- }
- else if( taxonModel.isEmpty() &&
+ } else if (
+ taxonModel.isEmpty() &&
this.get("taxonCoverage").length == 1 &&
- MetacatUI.appModel.get("emlEditorRequiredFields").taxonCoverage ){
-
+ MetacatUI.appModel.get("emlEditorRequiredFields").taxonCoverage
+ ) {
taxonModel.isValid();
errors = _.extend(errors, taxonModel.validationError);
-
}
//Validate each EMLEntity model
- _.each( this.get("entities"), function(entityModel){
-
- if( !entityModel.isValid() ){
- if( !errors.entities )
+ _.each(this.get("entities"), function (entityModel) {
+ if (!entityModel.isValid()) {
+ if (!errors.entities)
errors.entities = [entityModel.validationError];
- else
- errors.entities.push(entityModel.validationError);
+ else errors.entities.push(entityModel.validationError);
}
-
});
//Validate the EML Methods
let emlMethods = this.get("methods");
- if( emlMethods ){
- if( !emlMethods.isValid() ){
+ if (emlMethods) {
+ if (!emlMethods.isValid()) {
errors.methods = emlMethods.validationError;
}
}
// Validate each EMLAnnotation model
- if( this.get("annotations") ){
+ if (this.get("annotations")) {
this.get("annotations").each(function (model) {
if (model.isValid()) {
return;
@@ -1567,96 +1693,125 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
}
//Check the required fields for this MetacatUI configuration
- for([field, isRequired] of Object.entries(MetacatUI.appModel.get("emlEditorRequiredFields"))){
-
+ for ([field, isRequired] of Object.entries(
+ MetacatUI.appModel.get("emlEditorRequiredFields"),
+ )) {
//If it's not required, then go to the next field
- if(!isRequired) continue;
-
- if(field == "alternateIdentifier"){
- if( !this.get("alternateIdentifier").length || _.every(this.get("alternateIdentifier"), function(altId){ return altId.trim() == "" }) )
- errors.alternateIdentifier = "At least one alternate identifier is required."
- }
- else if(field == "generalTaxonomicCoverage"){
- if( !this.get("taxonCoverage").length || !this.get("taxonCoverage")[0].get("generalTaxonomicCoverage") )
- errors.generalTaxonomicCoverage = "Provide a description of the general taxonomic coverage of this data set.";
- }
- else if(field == "geoCoverage"){
- if(!this.get("geoCoverage").length)
- errors.geoCoverage = "At least one location is required.";
- }
- else if(field == "intellectualRights"){
- if( !this.get("intellectualRights") )
- errors.intellectualRights = "Select usage rights for this data set.";
- }
- else if(field == "studyExtentDescription"){
- if( !this.get("methods") || !this.get("methods").get("studyExtentDescription") )
- errors.studyExtentDescription = "Provide a study extent description.";
- }
- else if(field == "samplingDescription"){
- if( !this.get("methods") || !this.get("methods").get("samplingDescription") )
- errors.samplingDescription = "Provide a sampling description.";
- }
- else if(field == "temporalCoverage"){
- if(!this.get("temporalCoverage").length)
- errors.temporalCoverage = "Provide the date(s) for this data set.";
- }
- else if(field == "taxonCoverage"){
- if(!this.get("taxonCoverage").length)
- errors.taxonCoverage = "At least one taxa rank and value is required.";
- }
- else if(field == "keywordSets"){
- if( !this.get("keywordSets").length )
- errors.keywordSets = "Provide at least one keyword.";
- }
- //The EMLMethods model will validate itself for required fields, but
- // this is a rudimentary check to make sure the EMLMethods model was created
- // in the first place
- else if(field == "methods"){
- if(!this.get("methods"))
- errors.methods = "At least one method step is required.";
- }
- else if(field == "funding"){
- // Note: Checks for either the funding or award element. award
- // element is checked by the project's objectDOM for now until
- // EMLProject fully supports the award element
- if(!this.get("project") ||
- !(this.get("project").get("funding").length ||
- (this.get("project").get("objectDOM") &&
- this.get("project").get("objectDOM").querySelectorAll &&
- this.get("project").get("objectDOM").querySelectorAll("award").length > 0)))
- errors.funding = "Provide at least one project funding number or name.";
- }
- else if(field == "abstract"){
- if(!this.get("abstract").length)
- errors["abstract"] = "Provide an abstract.";
- }
- else if(field == "dataSensitivity"){
- if( !this.getDataSensitivity() ){
- errors["dataSensitivity"] = "Pick the category that best describes the level of sensitivity or restriction of the data.";
- }
- }
+ if (!isRequired) continue;
+
+ if (field == "alternateIdentifier") {
+ if (
+ !this.get("alternateIdentifier").length ||
+ _.every(this.get("alternateIdentifier"), function (altId) {
+ return altId.trim() == "";
+ })
+ )
+ errors.alternateIdentifier =
+ "At least one alternate identifier is required.";
+ } else if (field == "generalTaxonomicCoverage") {
+ if (
+ !this.get("taxonCoverage").length ||
+ !this.get("taxonCoverage")[0].get("generalTaxonomicCoverage")
+ )
+ errors.generalTaxonomicCoverage =
+ "Provide a description of the general taxonomic coverage of this data set.";
+ } else if (field == "geoCoverage") {
+ if (!this.get("geoCoverage").length)
+ errors.geoCoverage = "At least one location is required.";
+ } else if (field == "intellectualRights") {
+ if (!this.get("intellectualRights"))
+ errors.intellectualRights =
+ "Select usage rights for this data set.";
+ } else if (field == "studyExtentDescription") {
+ if (
+ !this.get("methods") ||
+ !this.get("methods").get("studyExtentDescription")
+ )
+ errors.studyExtentDescription =
+ "Provide a study extent description.";
+ } else if (field == "samplingDescription") {
+ if (
+ !this.get("methods") ||
+ !this.get("methods").get("samplingDescription")
+ )
+ errors.samplingDescription = "Provide a sampling description.";
+ } else if (field == "temporalCoverage") {
+ if (!this.get("temporalCoverage").length)
+ errors.temporalCoverage =
+ "Provide the date(s) for this data set.";
+ } else if (field == "taxonCoverage") {
+ if (!this.get("taxonCoverage").length)
+ errors.taxonCoverage =
+ "At least one taxa rank and value is required.";
+ } else if (field == "keywordSets") {
+ if (!this.get("keywordSets").length)
+ errors.keywordSets = "Provide at least one keyword.";
+ }
+ //The EMLMethods model will validate itself for required fields, but
+ // this is a rudimentary check to make sure the EMLMethods model was created
+ // in the first place
+ else if (field == "methods") {
+ if (!this.get("methods"))
+ errors.methods = "At least one method step is required.";
+ } else if (field == "funding") {
+ // Note: Checks for either the funding or award element. award
+ // element is checked by the project's objectDOM for now until
+ // EMLProject fully supports the award element
+ if (
+ !this.get("project") ||
+ !(
+ this.get("project").get("funding").length ||
+ (this.get("project").get("objectDOM") &&
+ this.get("project").get("objectDOM").querySelectorAll &&
+ this.get("project").get("objectDOM").querySelectorAll("award")
+ .length > 0)
+ )
+ )
+ errors.funding =
+ "Provide at least one project funding number or name.";
+ } else if (field == "abstract") {
+ if (!this.get("abstract").length)
+ errors["abstract"] = "Provide an abstract.";
+ } else if (field == "dataSensitivity") {
+ if (!this.getDataSensitivity()) {
+ errors["dataSensitivity"] =
+ "Pick the category that best describes the level of sensitivity or restriction of the data.";
+ }
+ }
//If this is an EMLParty type, check that there is a party of this type in the model
- else if( EMLParty.prototype.partyTypes.map(t=>t.dataCategory).includes(field) ){
+ else if (
+ EMLParty.prototype.partyTypes
+ .map((t) => t.dataCategory)
+ .includes(field)
+ ) {
//If this is an associatedParty role
- if( EMLParty.prototype.defaults().roleOptions?.includes(field) ){
- if(!this.get("associatedParty")?.map(p=>p.get("roles")).flat().includes(field)){
- errors[field] = "Provide information about the people or organization(s) in the role: " +
- EMLParty.prototype.partyTypes.find(t=>t.dataCategory==field)?.label;
+ if (EMLParty.prototype.defaults().roleOptions?.includes(field)) {
+ if (
+ !this.get("associatedParty")
+ ?.map((p) => p.get("roles"))
+ .flat()
+ .includes(field)
+ ) {
+ errors[field] =
+ "Provide information about the people or organization(s) in the role: " +
+ EMLParty.prototype.partyTypes.find(
+ (t) => t.dataCategory == field,
+ )?.label;
}
+ } else if (!this.get(field)?.length) {
+ errors[field] =
+ "Provide information about the people or organization(s) in the role: " +
+ EMLParty.prototype.partyTypes.find(
+ (t) => t.dataCategory == field,
+ )?.label;
}
- else if( !this.get(field)?.length ){
- errors[field] = "Provide information about the people or organization(s) in the role: " +
- EMLParty.prototype.partyTypes.find(t=>t.dataCategory==field)?.label;
- }
- }
- else if( !this.get(field) || !this.get(field)?.length ){
+ } else if (!this.get(field) || !this.get(field)?.length) {
errors[field] = "Provide a " + field + ".";
}
}
- if( Object.keys(errors).length )
- return errors;
- else{
+ if (Object.keys(errors).length) return errors;
+ else {
return;
}
},
@@ -1667,38 +1822,46 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
Note that this method considers a zero-length String to be valid
because the EML211.serialize() method will properly handle a null
or zero-length String by serializing out the current year. */
- isValidYearDate: function(value) {
- return (value === "" || /^\d{4}$/.test(value) || /^\d{4}-\d{2}-\d{2}$/.test(value));
+ isValidYearDate: function (value) {
+ return (
+ value === "" ||
+ /^\d{4}$/.test(value) ||
+ /^\d{4}-\d{2}-\d{2}$/.test(value)
+ );
},
/*
* Sends an AJAX request to fetch the system metadata for this EML object.
* Will not trigger a sync event since it does not use Backbone.Model.fetch
*/
- fetchSystemMetadata: function(options){
-
- if(!options) var options = {};
+ fetchSystemMetadata: function (options) {
+ if (!options) var options = {};
else options = _.clone(options);
var model = this,
- fetchOptions = _.extend({
- url: MetacatUI.appModel.get("metaServiceUrl") + encodeURIComponent(this.get("id")),
- dataType: "text",
- success: function(response){
- model.set(DataONEObject.prototype.parse.call(model, response));
-
- //Trigger a custom event that the sys meta was updated
- model.trigger("sysMetaUpdated");
+ fetchOptions = _.extend(
+ {
+ url:
+ MetacatUI.appModel.get("metaServiceUrl") +
+ encodeURIComponent(this.get("id")),
+ dataType: "text",
+ success: function (response) {
+ model.set(DataONEObject.prototype.parse.call(model, response));
+
+ //Trigger a custom event that the sys meta was updated
+ model.trigger("sysMetaUpdated");
+ },
+ error: function () {
+ model.trigger("error");
+ },
},
- error: function(){
- model.trigger('error');
- }
- }, options);
+ options,
+ );
- //Add the authorization header and other AJAX settings
- _.extend(fetchOptions, MetacatUI.appUserModel.createAjaxSettings());
+ //Add the authorization header and other AJAX settings
+ _.extend(fetchOptions, MetacatUI.appUserModel.createAjaxSettings());
- $.ajax(fetchOptions);
+ $.ajax(fetchOptions);
},
/*
* Returns the nofde in the given EML document that the given node type
@@ -1707,7 +1870,7 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
* Returns false if either the node is not found in the and this should
* be handled by the caller.
*/
- getEMLPosition: function(eml, nodeName) {
+ getEMLPosition: function (eml, nodeName) {
var nodeOrder = this.get("nodeOrder");
var position = _.indexOf(nodeOrder, nodeName.toLowerCase());
@@ -1729,8 +1892,8 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
/*
* Checks if this model has updates that need to be synced with the server.
*/
- hasUpdates: function(){
- if(this.constructor.__super__.hasUpdates.call(this)) return true;
+ hasUpdates: function () {
+ if (this.constructor.__super__.hasUpdates.call(this)) return true;
//If nothing else has been changed, then this object hasn't had any updates
return false;
@@ -1739,15 +1902,14 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
/*
Add an entity into the EML 2.1.1 object
*/
- addEntity: function(emlEntity, position) {
+ addEntity: function (emlEntity, position) {
//Get the current list of entities
var currentEntities = this.get("entities");
- if( typeof position == "undefined" || position == -1)
+ if (typeof position == "undefined" || position == -1)
currentEntities.push(emlEntity);
- else
- //Add the entity model to the entity array
- currentEntities.splice(position, 0, emlEntity);
+ //Add the entity model to the entity array
+ else currentEntities.splice(position, 0, emlEntity);
this.trigger("change:entities");
@@ -1759,9 +1921,8 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
/*
Remove an entity from the EML 2.1.1 object
*/
- removeEntity: function(emlEntity) {
- if(!emlEntity || typeof emlEntity != "object")
- return;
+ removeEntity: function (emlEntity) {
+ if (!emlEntity || typeof emlEntity != "object") return;
//Get the current list of entities
var entities = this.get("entities");
@@ -1774,89 +1935,108 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
/*
* Find the entity model for a given DataONEObject
*/
- getEntity: function(dataONEObj){
-
+ getEntity: function (dataONEObj) {
//If an EMLEntity model has been found for this object before, then return it
- if( dataONEObj.get("metadataEntity") ){
+ if (dataONEObj.get("metadataEntity")) {
dataONEObj.get("metadataEntity").set("dataONEObject", dataONEObj);
return dataONEObj.get("metadataEntity");
}
- var entity = _.find(this.get("entities"), function(e){
-
- //Matches of the checksum or identifier are definite matches
- if( e.get("xmlID") == dataONEObj.getXMLSafeID() )
- return true;
- else if( e.get("physicalMD5Checksum") && (e.get("physicalMD5Checksum") == dataONEObj.get("checksum") && dataONEObj.get("checksumAlgorithm").toUpperCase() == "MD5"))
- return true;
- else if(e.get("downloadID") && e.get("downloadID") == dataONEObj.get("id"))
- return true;
+ var entity = _.find(
+ this.get("entities"),
+ function (e) {
+ //Matches of the checksum or identifier are definite matches
+ if (e.get("xmlID") == dataONEObj.getXMLSafeID()) return true;
+ else if (
+ e.get("physicalMD5Checksum") &&
+ e.get("physicalMD5Checksum") == dataONEObj.get("checksum") &&
+ dataONEObj.get("checksumAlgorithm").toUpperCase() == "MD5"
+ )
+ return true;
+ else if (
+ e.get("downloadID") &&
+ e.get("downloadID") == dataONEObj.get("id")
+ )
+ return true;
- // Get the file name from the EML for this entity
- var fileNameFromEML = e.get("physicalObjectName") || e.get("entityName");
+ // Get the file name from the EML for this entity
+ var fileNameFromEML =
+ e.get("physicalObjectName") || e.get("entityName");
- // If the EML file name matches the DataONEObject file name
- if (fileNameFromEML &&
+ // If the EML file name matches the DataONEObject file name
+ if (
+ fileNameFromEML &&
dataONEObj.get("fileName") &&
- ((fileNameFromEML.toLowerCase() == dataONEObj.get("fileName").toLowerCase()) ||
- (fileNameFromEML.replace(/ /g, "_").toLowerCase() == dataONEObj.get("fileName").toLowerCase()))) {
-
- //Get an array of all the other entities in this EML
- var otherEntities = _.without(this.get("entities"), e);
+ (fileNameFromEML.toLowerCase() ==
+ dataONEObj.get("fileName").toLowerCase() ||
+ fileNameFromEML.replace(/ /g, "_").toLowerCase() ==
+ dataONEObj.get("fileName").toLowerCase())
+ ) {
+ //Get an array of all the other entities in this EML
+ var otherEntities = _.without(this.get("entities"), e);
// If this entity name matches the dataone object file name, AND no other dataone object file name
// matches, then we can assume this is the entity element for this file.
- var otherMatchingEntity = _.find(otherEntities, function(otherE){
-
- // Get the file name from the EML for the other entities
- var otherFileNameFromEML = otherE.get("physicalObjectName") || otherE.get("entityName");
-
- // If the file names match, return true
- if( (otherFileNameFromEML == dataONEObj.get("fileName")) || (otherFileNameFromEML.replace(/ /g, "_") == dataONEObj.get("fileName")) )
- return true;
- });
-
- // If this entity's file name didn't match any other file names in the EML,
- // then this entity is a match for the given dataONEObject
- if( !otherMatchingEntity )
- return true;
- }
-
- }, this);
+ var otherMatchingEntity = _.find(
+ otherEntities,
+ function (otherE) {
+ // Get the file name from the EML for the other entities
+ var otherFileNameFromEML =
+ otherE.get("physicalObjectName") ||
+ otherE.get("entityName");
+
+ // If the file names match, return true
+ if (
+ otherFileNameFromEML == dataONEObj.get("fileName") ||
+ otherFileNameFromEML.replace(/ /g, "_") ==
+ dataONEObj.get("fileName")
+ )
+ return true;
+ },
+ );
+
+ // If this entity's file name didn't match any other file names in the EML,
+ // then this entity is a match for the given dataONEObject
+ if (!otherMatchingEntity) return true;
+ }
+ },
+ this,
+ );
//If we found an entity, give it an ID and return it
- if(entity){
-
+ if (entity) {
//If this entity has been matched to another DataONEObject already, then don't match it again
- if( entity.get("dataONEObject") == dataONEObj ){
+ if (entity.get("dataONEObject") == dataONEObj) {
return entity;
}
//If this entity has been matched to a different DataONEObject already, then don't match it again.
//i.e. We will not override existing entity<->DataONEObject pairings
- else if( entity.get("dataONEObject") ){
+ else if (entity.get("dataONEObject")) {
return;
- }
- else{
+ } else {
entity.set("dataONEObject", dataONEObj);
}
- //Create an XML-safe ID and set it on the Entity model
- var entityID = this.getUniqueEntityId(dataONEObj);
- entity.set("xmlID", entityID);
+ //Create an XML-safe ID and set it on the Entity model
+ var entityID = this.getUniqueEntityId(dataONEObj);
+ entity.set("xmlID", entityID);
- //Save a reference to this entity so we don't have to refind it later
- dataONEObj.set("metadataEntity", entity);
+ //Save a reference to this entity so we don't have to refind it later
+ dataONEObj.set("metadataEntity", entity);
return entity;
}
//See if one data object is of this type in the package
- var matchingTypes = _.filter(this.get("entities"), function(e){
- return (e.get("formatName") == (dataONEObj.get("formatId") || dataONEObj.get("mediaType")));
+ var matchingTypes = _.filter(this.get("entities"), function (e) {
+ return (
+ e.get("formatName") ==
+ (dataONEObj.get("formatId") || dataONEObj.get("mediaType"))
+ );
});
- if(matchingTypes.length == 1){
- //Create an XML-safe ID and set it on the Entity model
+ if (matchingTypes.length == 1) {
+ //Create an XML-safe ID and set it on the Entity model
matchingTypes[0].set("xmlID", dataONEObj.getXMLSafeID());
return matchingTypes[0];
@@ -1864,54 +2044,54 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
//If this EML is in a DataPackage with only one other DataONEObject,
// and there is only one entity in the EML, then we can assume they are the same entity
- if( this.get("entities").length == 1 ){
-
- if( this.get("collections")[0] && this.get("collections")[0].type == "DataPackage" &&
- this.get("collections")[0].length == 2 && _.contains(this.get("collections")[0].models, dataONEObj)){
- return this.get("entities")[0];
+ if (this.get("entities").length == 1) {
+ if (
+ this.get("collections")[0] &&
+ this.get("collections")[0].type == "DataPackage" &&
+ this.get("collections")[0].length == 2 &&
+ _.contains(this.get("collections")[0].models, dataONEObj)
+ ) {
+ return this.get("entities")[0];
}
-
}
return false;
-
},
- createEntity: function(dataONEObject){
+ createEntity: function (dataONEObject) {
// Add or append an entity to the parent's entity list
- var entityModel = new EMLOtherEntity({
- entityName : dataONEObject.get("fileName"),
- entityType : dataONEObject.get("formatId") ||
- dataONEObject.get("mediaType") ||
- "application/octet-stream",
- dataONEObject: dataONEObject,
- parentModel: this,
- xmlID: dataONEObject.getXMLSafeID()
- });
+ var entityModel = new EMLOtherEntity({
+ entityName: dataONEObject.get("fileName"),
+ entityType:
+ dataONEObject.get("formatId") ||
+ dataONEObject.get("mediaType") ||
+ "application/octet-stream",
+ dataONEObject: dataONEObject,
+ parentModel: this,
+ xmlID: dataONEObject.getXMLSafeID(),
+ });
- this.addEntity(entityModel);
+ this.addEntity(entityModel);
- //If this DataONEObject fails to upload, remove the EML entity
- this.listenTo(dataONEObject, "errorSaving", function(){
- this.removeEntity(dataONEObject.get("metadataEntity"));
+ //If this DataONEObject fails to upload, remove the EML entity
+ this.listenTo(dataONEObject, "errorSaving", function () {
+ this.removeEntity(dataONEObject.get("metadataEntity"));
- //Listen for a successful save so the entity can be added back
- this.listenToOnce(dataONEObject, "successSaving", function(){
- this.addEntity(dataONEObject.get("metadataEntity"))
- });
+ //Listen for a successful save so the entity can be added back
+ this.listenToOnce(dataONEObject, "successSaving", function () {
+ this.addEntity(dataONEObject.get("metadataEntity"));
});
-
+ });
},
/*
- * Creates an XML-safe identifier that is unique to this EML document,
- * based on the given DataONEObject model. It is intended for EML entity nodes in particular.
- *
- * @param {DataONEObject} - a DataONEObject model that this EML documents
- * @return {string} - an identifier string unique to this EML document
- */
- getUniqueEntityId: function(dataONEObject){
-
+ * Creates an XML-safe identifier that is unique to this EML document,
+ * based on the given DataONEObject model. It is intended for EML entity nodes in particular.
+ *
+ * @param {DataONEObject} - a DataONEObject model that this EML documents
+ * @return {string} - an identifier string unique to this EML document
+ */
+ getUniqueEntityId: function (dataONEObject) {
var uniqueId = "";
uniqueId = dataONEObject.getXMLSafeID();
@@ -1920,30 +2100,37 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
var emlString = this.get("objectXML");
//If this id already exists in the EML...
- if(emlString && emlString.indexOf(' id="' + uniqueId + '"')){
+ if (emlString && emlString.indexOf(' id="' + uniqueId + '"')) {
//Create a random uuid to use instead
uniqueId = "urn-uuid-" + uuid.v4();
}
return uniqueId;
-
},
/*
* removeParty - removes the given EMLParty model from this EML211 model's attributes
*/
- removeParty: function(partyModel){
+ removeParty: function (partyModel) {
//The list of attributes this EMLParty might be stored in
- var possibleAttr = ["creator", "contact", "metadataProvider", "publisher", "associatedParty"];
+ var possibleAttr = [
+ "creator",
+ "contact",
+ "metadataProvider",
+ "publisher",
+ "associatedParty",
+ ];
// Iterate over each possible attribute
- _.each(possibleAttr, function(attr){
-
- if( _.contains(this.get(attr), partyModel) ){
- this.set( attr, _.without(this.get(attr), partyModel) );
- }
-
- }, this);
+ _.each(
+ possibleAttr,
+ function (attr) {
+ if (_.contains(this.get(attr), partyModel)) {
+ this.set(attr, _.without(this.get(attr), partyModel));
+ }
+ },
+ this,
+ );
},
/**
@@ -1951,37 +2138,47 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
*
* @param {EMLParty} partyModel: The EMLParty model we're moving
*/
- movePartyUp: function(partyModel) {
- var possibleAttr = ["creator", "contact", "metadataProvider", "publisher", "associatedParty"];
+ movePartyUp: function (partyModel) {
+ var possibleAttr = [
+ "creator",
+ "contact",
+ "metadataProvider",
+ "publisher",
+ "associatedParty",
+ ];
// Iterate over each possible attribute
- _.each(possibleAttr, function(attr){
- if (!_.contains(this.get(attr), partyModel)) {
- return;
- }
- // Make a clone because we're going to use splice
- var models = _.clone(this.get(attr));
+ _.each(
+ possibleAttr,
+ function (attr) {
+ if (!_.contains(this.get(attr), partyModel)) {
+ return;
+ }
+ // Make a clone because we're going to use splice
+ var models = _.clone(this.get(attr));
- // Find the index of the model we're moving
- var index = _.findIndex(models, function(m) {
- return m === partyModel;
- });
+ // Find the index of the model we're moving
+ var index = _.findIndex(models, function (m) {
+ return m === partyModel;
+ });
- if (index === 0) {
- // Already first
- return;
- }
+ if (index === 0) {
+ // Already first
+ return;
+ }
- if (index === -1) {
- // Couldn't find the model
- return;
- }
+ if (index === -1) {
+ // Couldn't find the model
+ return;
+ }
- // Do the move using splice and update the model
- models.splice(index - 1, 0, models.splice(index, 1)[0])
- this.set(attr, models);
- this.trigger("change:" + attr);
- }, this);
+ // Do the move using splice and update the model
+ models.splice(index - 1, 0, models.splice(index, 1)[0]);
+ this.set(attr, models);
+ this.trigger("change:" + attr);
+ },
+ this,
+ );
},
/**
@@ -1989,64 +2186,70 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
*
* @param {EMLParty} partyModel: The EMLParty model we're moving
*/
- movePartyDown: function(partyModel) {
- var possibleAttr = ["creator", "contact", "metadataProvider", "publisher", "associatedParty"];
+ movePartyDown: function (partyModel) {
+ var possibleAttr = [
+ "creator",
+ "contact",
+ "metadataProvider",
+ "publisher",
+ "associatedParty",
+ ];
// Iterate over each possible attribute
- _.each(possibleAttr, function(attr){
- if (!_.contains(this.get(attr), partyModel)) {
- return;
- }
- // Make a clone because we're going to use splice
- var models = _.clone(this.get(attr));
-
- // Find the index of the model we're moving
- var index = _.findIndex(models, function(m) {
- return m === partyModel;
- });
+ _.each(
+ possibleAttr,
+ function (attr) {
+ if (!_.contains(this.get(attr), partyModel)) {
+ return;
+ }
+ // Make a clone because we're going to use splice
+ var models = _.clone(this.get(attr));
- if (index === -1) {
- // Couldn't find the model
- return;
- }
+ // Find the index of the model we're moving
+ var index = _.findIndex(models, function (m) {
+ return m === partyModel;
+ });
- // Figure out where to put the new model
- // Leave it in the same place if the next index doesn't exist
- // Move one forward if it does
- var newIndex = (models.length <= index + 1) ? index : index + 1;
+ if (index === -1) {
+ // Couldn't find the model
+ return;
+ }
- // Do the move using splice and update the model
- models.splice(newIndex, 0, models.splice(index, 1)[0])
- this.set(attr, models);
- this.trigger("change:" + attr);
- }, this);
+ // Figure out where to put the new model
+ // Leave it in the same place if the next index doesn't exist
+ // Move one forward if it does
+ var newIndex = models.length <= index + 1 ? index : index + 1;
+
+ // Do the move using splice and update the model
+ models.splice(newIndex, 0, models.splice(index, 1)[0]);
+ this.set(attr, models);
+ this.trigger("change:" + attr);
+ },
+ this,
+ );
},
/*
- * Adds the given EMLParty model to this EML211 model in the
- * appropriate role array in the given position
- *
- * @param {EMLParty} - The EMLParty model to add
- * @param {number} - The position in the role array in which to insert this EMLParty
- * @return {boolean} - Returns true if the EMLParty was successfully added, false if it was cancelled
- */
- addParty: function(partyModel, position){
-
+ * Adds the given EMLParty model to this EML211 model in the
+ * appropriate role array in the given position
+ *
+ * @param {EMLParty} - The EMLParty model to add
+ * @param {number} - The position in the role array in which to insert this EMLParty
+ * @return {boolean} - Returns true if the EMLParty was successfully added, false if it was cancelled
+ */
+ addParty: function (partyModel, position) {
//If the EMLParty model is empty, don't add it to the EML211 model
- if(partyModel.isEmpty())
- return false;
+ if (partyModel.isEmpty()) return false;
//Get the role of this EMLParty
var role = partyModel.get("type") || "associatedParty";
//If this model already contains this EMLParty, then exit
- if( _.contains(this.get(role), partyModel) )
- return false;
+ if (_.contains(this.get(role), partyModel)) return false;
- if( typeof position == "undefined" ){
+ if (typeof position == "undefined") {
this.get(role).push(partyModel);
- }
- else {
+ } else {
this.get(role).splice(position, 0, partyModel);
}
@@ -2060,62 +2263,72 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
* @param {string} partyType - A string that represents either the role or the party type. For example, "contact", "creator", "principalInvestigator", etc.
* @since 2.15.0
*/
- getPartiesByType: function(partyType){
-
+ getPartiesByType: function (partyType) {
try {
- if(!partyType){
- return false
+ if (!partyType) {
+ return false;
}
var associatedPartyTypes = new EMLParty().get("roleOptions"),
- isAssociatedParty = associatedPartyTypes.includes(partyType),
- parties = [];
+ isAssociatedParty = associatedPartyTypes.includes(partyType),
+ parties = [];
// For "contact", "creator", "metadataProvider", "publisher", each party type has it's own
// array in the EML model
- if(!isAssociatedParty){
+ if (!isAssociatedParty) {
parties = this.get(partyType);
- // For "custodianSteward", "principalInvestigator", "collaboratingPrincipalInvestigator", etc.,
- // party members are listed in the EML model's associated parties array. Each associated party's
- // party type is indicated in the role attribute.
+ // For "custodianSteward", "principalInvestigator", "collaboratingPrincipalInvestigator", etc.,
+ // party members are listed in the EML model's associated parties array. Each associated party's
+ // party type is indicated in the role attribute.
} else {
- parties = _.filter(this.get("associatedParty"), function (associatedParty) {
- return associatedParty.get("roles").includes(partyType) }
+ parties = _.filter(
+ this.get("associatedParty"),
+ function (associatedParty) {
+ return associatedParty.get("roles").includes(partyType);
+ },
);
}
return parties;
-
} catch (error) {
- console.log("Error trying to find a list of party members in an EML model by type. Error details: " + error);
+ console.log(
+ "Error trying to find a list of party members in an EML model by type. Error details: " +
+ error,
+ );
}
},
- createUnits: function(){
+ createUnits: function () {
this.units.fetch();
},
/* Initialize the object XML for brand spankin' new EML objects */
- createXML: function() {
-
- let emlSystem = MetacatUI.appModel.get("emlSystem");
- emlSystem = (!emlSystem || typeof emlSystem != "string") ? "knb" : emlSystem;
-
- var xml = " ",
- eml = $($.parseHTML(xml));
-
- // Set base attributes
- eml.attr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
- eml.attr("xmlns:stmml", "http://www.xml-cml.org/schema/stmml-1.1");
- eml.attr("xsi:schemaLocation", "https://eml.ecoinformatics.org/eml-2.2.0 https://eml.ecoinformatics.org/eml-2.2.0/eml.xsd");
- eml.attr("packageId", this.get("id"));
- eml.attr("system", emlSystem);
-
- // Add the dataset
- eml.append(document.createElement("dataset"));
- eml.find("dataset").append(document.createElement("title"));
-
- var emlString = $(document.createElement("div")).append(eml.clone()).html();
-
- return emlString;
+ createXML: function () {
+ let emlSystem = MetacatUI.appModel.get("emlSystem");
+ emlSystem =
+ !emlSystem || typeof emlSystem != "string" ? "knb" : emlSystem;
+
+ var xml =
+ ' ',
+ eml = $($.parseHTML(xml));
+
+ // Set base attributes
+ eml.attr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+ eml.attr("xmlns:stmml", "http://www.xml-cml.org/schema/stmml-1.1");
+ eml.attr(
+ "xsi:schemaLocation",
+ "https://eml.ecoinformatics.org/eml-2.2.0 https://eml.ecoinformatics.org/eml-2.2.0/eml.xsd",
+ );
+ eml.attr("packageId", this.get("id"));
+ eml.attr("system", emlSystem);
+
+ // Add the dataset
+ eml.append(document.createElement("dataset"));
+ eml.find("dataset").append(document.createElement("title"));
+
+ var emlString = $(document.createElement("div"))
+ .append(eml.clone())
+ .html();
+
+ return emlString;
},
/*
@@ -2124,69 +2337,77 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
@param xmlString The XML string to make the replacement in
*/
- cleanUpXML: function(xmlString){
+ cleanUpXML: function (xmlString) {
xmlString.replace("", "");
xmlString.replace(" ", "");
return xmlString;
},
- createTextCopy: function(){
- var emlDraftText = "EML draft for " + this.get("id") + "(" + this.get("title") + ") by " +
- MetacatUI.appUserModel.get("firstName") + " " + MetacatUI.appUserModel.get("lastName");
-
- if(this.get("uploadStatus") == "e" && this.get("errorMessage")){
- emlDraftText += ". This EML had the following save error: `" + this.get("errorMessage") + "` ";
- }
- else {
+ createTextCopy: function () {
+ var emlDraftText =
+ "EML draft for " +
+ this.get("id") +
+ "(" +
+ this.get("title") +
+ ") by " +
+ MetacatUI.appUserModel.get("firstName") +
+ " " +
+ MetacatUI.appUserModel.get("lastName");
+
+ if (this.get("uploadStatus") == "e" && this.get("errorMessage")) {
+ emlDraftText +=
+ ". This EML had the following save error: `" +
+ this.get("errorMessage") +
+ "` ";
+ } else {
emlDraftText += ": ";
}
emlDraftText += this.serialize();
var plainTextEML = new DataONEObject({
- formatId: "text/plain",
- fileName: "eml_draft_" + (MetacatUI.appUserModel.get("lastName") || "") + ".txt",
- uploadFile: new Blob([emlDraftText], {type : 'plain/text'}),
- synced: true
- });
+ formatId: "text/plain",
+ fileName:
+ "eml_draft_" +
+ (MetacatUI.appUserModel.get("lastName") || "") +
+ ".txt",
+ uploadFile: new Blob([emlDraftText], { type: "plain/text" }),
+ synced: true,
+ });
return plainTextEML;
},
/*
- * Cleans up the given text so that it is XML-valid by escaping reserved characters, trimming white space, etc.
- *
- * @param {string} textString - The string to clean up
- * @return {string} - The cleaned up string
- */
- cleanXMLText: function(textString){
-
- if( typeof textString != "string" )
- return;
+ * Cleans up the given text so that it is XML-valid by escaping reserved characters, trimming white space, etc.
+ *
+ * @param {string} textString - The string to clean up
+ * @return {string} - The cleaned up string
+ */
+ cleanXMLText: function (textString) {
+ if (typeof textString != "string") return;
textString = textString.trim();
//Check for XML/HTML elements
- _.each(textString.match(/<\s*[^>]*>/g), function(xmlNode){
-
+ _.each(textString.match(/<\s*[^>]*>/g), function (xmlNode) {
//Encode <, >, and substrings
var tagName = xmlNode.replace(/>/g, ">");
tagName = tagName.replace(/ -1 ){
- trimmedTitle = trimmedTitle.substr(0, Math.min(trimmedTitle.length, trimmedTitle.lastIndexOf(" ")));
+ if (trimmedTitle.indexOf(" ") > -1) {
+ trimmedTitle = trimmedTitle.substr(
+ 0,
+ Math.min(trimmedTitle.length, trimmedTitle.lastIndexOf(" ")),
+ );
}
//Replace all non alphanumeric characters with underscores
// and make sure there isn't more than one underscore in a row
- trimmedTitle = trimmedTitle.replace(/[^a-zA-Z0-9]/g, "_").replace(/_{2,}/g, "_");
+ trimmedTitle = trimmedTitle
+ .replace(/[^a-zA-Z0-9]/g, "_")
+ .replace(/_{2,}/g, "_");
//Set the fileName on the model
this.set("fileName", trimmedTitle + ".xml");
},
- trickleUpChange: function(){
- if( !MetacatUI.rootDataPackage || !MetacatUI.rootDataPackage.packageModel )
+ trickleUpChange: function () {
+ if (
+ !MetacatUI.rootDataPackage ||
+ !MetacatUI.rootDataPackage.packageModel
+ )
return;
//Mark the package as changed
@@ -2279,14 +2510,14 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
* @param {Element} eml: The root eml:eml element to modify
* @return {Element} The element, possibly modified
*/
- setSchemaLocation: function(eml) {
+ setSchemaLocation: function (eml) {
if (!MetacatUI || !MetacatUI.appModel) {
return eml;
}
var current = $(eml).attr("xsi:schemaLocation"),
- format = MetacatUI.appModel.get("editorSerializationFormat"),
- location = MetacatUI.appModel.get("editorSchemaLocation");
+ format = MetacatUI.appModel.get("editorSerializationFormat"),
+ location = MetacatUI.appModel.get("editorSchemaLocation");
// Return now if we can't do anything anyway
if (!format || !location) {
@@ -2310,7 +2541,7 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
return eml;
},
- createID: function() {
+ createID: function () {
this.set("xmlID", uuid.v4());
},
@@ -2324,19 +2555,17 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
* @property {Boolean} [annotationData.allowDuplicates] If false, this annotation will replace all annotations already set with the same propertyURI.
* By default, more than one annotation with a given propertyURI can be added (defaults to true)
*/
- addAnnotation: function(annotationData){
-
- try{
- if( !annotationData || typeof annotationData != "object" ){
+ addAnnotation: function (annotationData) {
+ try {
+ if (!annotationData || typeof annotationData != "object") {
return;
}
//If no element name is provided, default to the dataset element.
let elementName = "";
- if( !annotationData.elementName ){
- elementName = "dataset"
- }
- else{
+ if (!annotationData.elementName) {
+ elementName = "dataset";
+ } else {
elementName = annotationData.elementName;
}
//Remove the elementName property so it isn't set on the EMLAnnotation model later.
@@ -2350,68 +2579,60 @@ define(['jquery', 'underscore', 'backbone', 'uuid',
let annotation = new EMLAnnotation(annotationData);
//Update annotations set on the dataset element
- if( elementName == "dataset" ){
+ if (elementName == "dataset") {
let annotations = this.get("annotations");
//If the current annotations set on the EML model are not in Array form, change it to an array
- if( !annotations ){
+ if (!annotations) {
annotations = new EMLAnnotations();
}
- if( allowDuplicates === false ){
+ if (allowDuplicates === false) {
//Add the EMLAnnotation to the collection, making sure to remove duplicates first
annotations.replaceDuplicateWith(annotation);
- }
- else{
+ } else {
annotations.add(annotation);
}
//Set the annotations and force the change to be recognized by the model
- this.set("annotations", annotations, {silent: true});
+ this.set("annotations", annotations, { silent: true });
this.handleChange(this, { force: true });
-
- }
- else{
+ } else {
/** @todo Add annotation support for other EML Elements */
}
-
- }
- catch(e){
+ } catch (e) {
console.error("Could not add Annotation to the EML: ", e);
}
-
},
/**
- * Finds annotations that are of the `data sensitivity` property from the NCEAS SENSO ontology.
- * Returns undefined if none are found. This function returns EMLAnnotation models because the data
- * sensitivity is stored in the EML Model as EMLAnnotations and added to EML as semantic annotations.
- * @returns {EMLAnnotation[]|undefined}
- */
- getDataSensitivity: function(){
- try{
+ * Finds annotations that are of the `data sensitivity` property from the NCEAS SENSO ontology.
+ * Returns undefined if none are found. This function returns EMLAnnotation models because the data
+ * sensitivity is stored in the EML Model as EMLAnnotations and added to EML as semantic annotations.
+ * @returns {EMLAnnotation[]|undefined}
+ */
+ getDataSensitivity: function () {
+ try {
let annotations = this.get("annotations");
- if(annotations){
- let found = annotations.where({ propertyURI: this.get("dataSensitivityPropertyURI") });
- if( !found || !found.length ){
+ if (annotations) {
+ let found = annotations.where({
+ propertyURI: this.get("dataSensitivityPropertyURI"),
+ });
+ if (!found || !found.length) {
return;
- }
- else{
+ } else {
return found;
}
- }
- else{
+ } else {
return;
}
- }
- catch(e){
+ } catch (e) {
console.error("Failed to get Data Sensitivity from EML model: ", e);
return;
}
- }
-
- });
+ },
+ },
+ );
- return EML211;
- }
-);
+ return EML211;
+});
diff --git a/src/js/models/metadata/eml211/EMLAnnotation.js b/src/js/models/metadata/eml211/EMLAnnotation.js
index 78e7560c9..066af4687 100644
--- a/src/js/models/metadata/eml211/EMLAnnotation.js
+++ b/src/js/models/metadata/eml211/EMLAnnotation.js
@@ -1,163 +1,184 @@
-define(["jquery", "underscore", "backbone"],
- function ($, _, Backbone) {
-
- /**
- * @class EMLAnnotation
- * @classdesc Stores EML SemanticAnnotation elements.
- * @classcategory Models/Metadata/EML211
- * @see https://eml.ecoinformatics.org/eml-2.2.0/eml-semantics.xsd
- * @extends Backbone.Model
- */
- var EMLAnnotation = Backbone.Model.extend(
- /** @lends EMLAnnotation.prototype */{
-
- type: "EMLAnnotation",
-
- defaults: function () {
- return {
- isNew: true,
- propertyLabel: null,
- propertyURI: null,
- valueLabel: null,
- valueURI: null,
- objectDOM: null,
- objectXML: null
- }
- },
-
- initialize: function (attributes, opions) {
- this.on("change", this.trickleUpChange);
- },
-
- parse: function (attributes, options) {
- // If parsing, this is an existing annotation so it's not isNew
- attributes.isNew = false;
-
- var propertyURI = $(attributes.objectDOM).find("propertyuri");
- var valueURI = $(attributes.objectDOM).find("valueuri");
-
- if (propertyURI.length !== 1 || valueURI.length !== 1) {
- return;
- }
-
- attributes.propertyURI = $(propertyURI).text().trim();
-
- attributes.valueURI = $(valueURI).text().trim();
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ /**
+ * @class EMLAnnotation
+ * @classdesc Stores EML SemanticAnnotation elements.
+ * @classcategory Models/Metadata/EML211
+ * @see https://eml.ecoinformatics.org/eml-2.2.0/eml-semantics.xsd
+ * @extends Backbone.Model
+ */
+ var EMLAnnotation = Backbone.Model.extend(
+ /** @lends EMLAnnotation.prototype */ {
+ type: "EMLAnnotation",
+
+ defaults: function () {
+ return {
+ isNew: true,
+ propertyLabel: null,
+ propertyURI: null,
+ valueLabel: null,
+ valueURI: null,
+ objectDOM: null,
+ objectXML: null,
+ };
+ },
+
+ initialize: function (attributes, opions) {
+ this.on("change", this.trickleUpChange);
+ },
+
+ parse: function (attributes, options) {
+ // If parsing, this is an existing annotation so it's not isNew
+ attributes.isNew = false;
+
+ var propertyURI = $(attributes.objectDOM).find("propertyuri");
+ var valueURI = $(attributes.objectDOM).find("valueuri");
+
+ if (propertyURI.length !== 1 || valueURI.length !== 1) {
+ return;
+ }
- var propertyLabel = $(propertyURI).attr("label");
- var valueLabel = $(valueURI).attr("label");
+ attributes.propertyURI = $(propertyURI).text().trim();
- if (!propertyLabel || !valueLabel) {
- return;
- }
+ attributes.valueURI = $(valueURI).text().trim();
- attributes.propertyLabel = propertyLabel.trim();
- attributes.valueLabel = valueLabel.trim();
+ var propertyLabel = $(propertyURI).attr("label");
+ var valueLabel = $(valueURI).attr("label");
- return attributes;
- },
+ if (!propertyLabel || !valueLabel) {
+ return;
+ }
- validate: function () {
- var errors = [];
+ attributes.propertyLabel = propertyLabel.trim();
+ attributes.valueLabel = valueLabel.trim();
- if (this.isEmpty()) {
- this.trigger("valid");
+ return attributes;
+ },
- return;
- }
+ validate: function () {
+ var errors = [];
- var propertyURI = this.get("propertyURI");
+ if (this.isEmpty()) {
+ this.trigger("valid");
- if (!propertyURI || propertyURI.length <= 0) {
- errors.push({ category: "propertyURI", message: "Property URI must be set." });
- } else if (propertyURI.match(/http[s]?:\/\/.+/) === null) {
- errors.push({ category: "propertyURI", message: "Property URI should be an HTTP(S) URI." });
- }
-
- var propertyLabel = this.get("propertyLabel");
+ return;
+ }
- if (!propertyLabel || propertyLabel.length <= 0) {
- errors.push({ category: "propertyLabel", message: "Property Label must be set." });
- }
+ var propertyURI = this.get("propertyURI");
+
+ if (!propertyURI || propertyURI.length <= 0) {
+ errors.push({
+ category: "propertyURI",
+ message: "Property URI must be set.",
+ });
+ } else if (propertyURI.match(/http[s]?:\/\/.+/) === null) {
+ errors.push({
+ category: "propertyURI",
+ message: "Property URI should be an HTTP(S) URI.",
+ });
+ }
- var valueURI = this.get("valueURI");
+ var propertyLabel = this.get("propertyLabel");
- if (!valueURI || valueURI.length <= 0) {
- errors.push({ category: "valueURI", message: "Value URI must be set." });
- } else if (valueURI.match(/http[s]?:\/\/.+/) === null) {
- errors.push({ category: "valueURI", message: "Value URI should be an HTTP(S) URI." });
- }
+ if (!propertyLabel || propertyLabel.length <= 0) {
+ errors.push({
+ category: "propertyLabel",
+ message: "Property Label must be set.",
+ });
+ }
- var valueLabel = this.get("valueLabel");
+ var valueURI = this.get("valueURI");
+
+ if (!valueURI || valueURI.length <= 0) {
+ errors.push({
+ category: "valueURI",
+ message: "Value URI must be set.",
+ });
+ } else if (valueURI.match(/http[s]?:\/\/.+/) === null) {
+ errors.push({
+ category: "valueURI",
+ message: "Value URI should be an HTTP(S) URI.",
+ });
+ }
- if (!valueLabel || valueLabel.length <= 0) {
- errors.push({ category: "valueLabel", message: "Value Label must be set." });
- }
+ var valueLabel = this.get("valueLabel");
- if (errors.length === 0) {
- this.trigger("valid");
+ if (!valueLabel || valueLabel.length <= 0) {
+ errors.push({
+ category: "valueLabel",
+ message: "Value Label must be set.",
+ });
+ }
- return;
- }
+ if (errors.length === 0) {
+ this.trigger("valid");
- return errors;
- },
+ return;
+ }
- updateDOM: function (objectDOM) {
- objectDOM = document.createElement("annotation");
+ return errors;
+ },
- if (this.get("propertyURI")) {
- var propertyURIEl = document.createElement("propertyuri");
- $(propertyURIEl).html(this.get("propertyURI"));
+ updateDOM: function (objectDOM) {
+ objectDOM = document.createElement("annotation");
- if (this.get("propertyLabel")) {
- $(propertyURIEl).attr("label", this.get("propertyLabel"));
- }
+ if (this.get("propertyURI")) {
+ var propertyURIEl = document.createElement("propertyuri");
+ $(propertyURIEl).html(this.get("propertyURI"));
- $(objectDOM).append(propertyURIEl);
+ if (this.get("propertyLabel")) {
+ $(propertyURIEl).attr("label", this.get("propertyLabel"));
}
- if (this.get("valueURI")) {
- var valueURIEl = document.createElement("valueuri");
- $(valueURIEl).html(this.get("valueURI"));
+ $(objectDOM).append(propertyURIEl);
+ }
- if (this.get("valueLabel")) {
- $(valueURIEl).attr("label", this.get("valueLabel"));
- }
+ if (this.get("valueURI")) {
+ var valueURIEl = document.createElement("valueuri");
+ $(valueURIEl).html(this.get("valueURI"));
- $(objectDOM).append(valueURIEl);
+ if (this.get("valueLabel")) {
+ $(valueURIEl).attr("label", this.get("valueLabel"));
}
- return objectDOM;
- },
-
- formatXML: function (xmlString) {
- return DataONEObject.prototype.formatXML.call(this, xmlString);
- },
-
- /**
- * isEmpty
- *
- * Check whether the model's properties are all empty for the purpose
- * of skipping the model during serialization to avoid invalid EML
- * documents.
- *
- * @return {boolean} - Returns true if all child elements have no
- * content
- */
- isEmpty: function () {
- return (typeof this.get("propertyLabel") !== "string" || this.get("propertyLabel").length <= 0) &&
- (typeof this.get("propertyURI") !== "string" || this.get("propertyURI").length <= 0) &&
- (typeof this.get("valueLabel") !== "string" || this.get("valueLabel").length <= 0) &&
- (typeof this.get("valueURI") !== "string" || this.get("valueURI").length <= 0)
- },
-
- /* Let the top level package know of attribute changes from this object */
- trickleUpChange: function () {
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ $(objectDOM).append(valueURIEl);
}
- });
- return EMLAnnotation;
- }
-);
+ return objectDOM;
+ },
+
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
+
+ /**
+ * isEmpty
+ *
+ * Check whether the model's properties are all empty for the purpose
+ * of skipping the model during serialization to avoid invalid EML
+ * documents.
+ *
+ * @return {boolean} - Returns true if all child elements have no
+ * content
+ */
+ isEmpty: function () {
+ return (
+ (typeof this.get("propertyLabel") !== "string" ||
+ this.get("propertyLabel").length <= 0) &&
+ (typeof this.get("propertyURI") !== "string" ||
+ this.get("propertyURI").length <= 0) &&
+ (typeof this.get("valueLabel") !== "string" ||
+ this.get("valueLabel").length <= 0) &&
+ (typeof this.get("valueURI") !== "string" ||
+ this.get("valueURI").length <= 0)
+ );
+ },
+
+ /* Let the top level package know of attribute changes from this object */
+ trickleUpChange: function () {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ },
+ },
+ );
+
+ return EMLAnnotation;
+});
diff --git a/src/js/models/metadata/eml211/EMLAttribute.js b/src/js/models/metadata/eml211/EMLAttribute.js
index 4e0b1381a..69e78c698 100644
--- a/src/js/models/metadata/eml211/EMLAttribute.js
+++ b/src/js/models/metadata/eml211/EMLAttribute.js
@@ -1,560 +1,616 @@
-define(["jquery", "underscore", "backbone", "uuid",
- "models/metadata/eml211/EMLMeasurementScale", "models/metadata/eml211/EMLAnnotation",
- "collections/metadata/eml/EMLMissingValueCodes",
- "models/DataONEObject"],
- function ($, _, Backbone, uuid, EMLMeasurementScale, EMLAnnotation,
- EMLMissingValueCodes,
- DataONEObject) {
-
- /**
- * @class EMLAttribute
- * @classdesc EMLAttribute represents a data attribute within an entity, such as
- * a column variable in a data table, or a feature attribute in a shapefile.
- * @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html
- * @classcategory Models/Metadata/EML211
- */
- var EMLAttribute = Backbone.Model.extend(
- /** @lends EMLAttribute.prototype */{
-
- /* Attributes of an EML attribute object */
- defaults: function(){
- return {
- /* Attributes from EML */
- xmlID: null, // The XML id of the attribute
- attributeName: null,
- attributeLabel: [], // Zero or more human readable labels
- attributeDefinition: null,
- storageType: [], // Zero or more storage types
- typeSystem: [], // Zero or more system types for storage type
- measurementScale: null, // An EML{Non}NumericDomain or EMLDateTimeDomain object
- missingValueCodes: new EMLMissingValueCodes(), // An EMLMissingValueCodes collection
- accuracy: null, // An EMLAccuracy object
- coverage: null, // an EMLCoverage object
- methods: [], // Zero or more EMLMethods objects
- references: null, // A reference to another EMLAttribute by id (needs work)
- annotation: [], // Zero or more EMLAnnotation objects
-
- /* Attributes not from EML */
- type: "attribute", // The element type in the DOM
- parentModel: null, // The parent model this attribute belongs to
- objectXML: null, // The serialized XML of this EML attribute
- objectDOM: null, // The DOM of this EML attribute
- nodeOrder: [ // The order of the top level XML element nodes
- "attributeName",
- "attributeLabel",
- "attributeDefinition",
- "storageType",
- "measurementScale",
- "missingValueCode",
- "accuracy",
- "coverage",
- "methods",
- "annotation"
- ]
- }
- },
-
- /*
- * The map of lower case to camel case node names
- * needed to deal with parsing issues with $.parseHTML().
- * Use this until we can figure out issues with $.parseXML().
- */
- nodeNameMap: {
- "attributename": "attributeName",
- "attributelabel": "attributeLabel",
- "attributedefinition": "attributeDefinition",
- "sourced" : "source",
- "storagetype": "storageType",
- "typesystem": "typeSystem",
- "measurementscale": "measurementScale",
- "missingvaluecode": "missingValueCode",
- "propertyuri": "propertyURI",
- "valueuri" : "valueURI"
- },
-
- /* Initialize an EMLAttribute object */
- initialize: function(attributes, options) {
-
- if (!attributes) {
- var attributes = {};
- }
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "uuid",
+ "models/metadata/eml211/EMLMeasurementScale",
+ "models/metadata/eml211/EMLAnnotation",
+ "collections/metadata/eml/EMLMissingValueCodes",
+ "models/DataONEObject",
+], function (
+ $,
+ _,
+ Backbone,
+ uuid,
+ EMLMeasurementScale,
+ EMLAnnotation,
+ EMLMissingValueCodes,
+ DataONEObject,
+) {
+ /**
+ * @class EMLAttribute
+ * @classdesc EMLAttribute represents a data attribute within an entity, such as
+ * a column variable in a data table, or a feature attribute in a shapefile.
+ * @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html
+ * @classcategory Models/Metadata/EML211
+ */
+ var EMLAttribute = Backbone.Model.extend(
+ /** @lends EMLAttribute.prototype */ {
+ /* Attributes of an EML attribute object */
+ defaults: function () {
+ return {
+ /* Attributes from EML */
+ xmlID: null, // The XML id of the attribute
+ attributeName: null,
+ attributeLabel: [], // Zero or more human readable labels
+ attributeDefinition: null,
+ storageType: [], // Zero or more storage types
+ typeSystem: [], // Zero or more system types for storage type
+ measurementScale: null, // An EML{Non}NumericDomain or EMLDateTimeDomain object
+ missingValueCodes: new EMLMissingValueCodes(), // An EMLMissingValueCodes collection
+ accuracy: null, // An EMLAccuracy object
+ coverage: null, // an EMLCoverage object
+ methods: [], // Zero or more EMLMethods objects
+ references: null, // A reference to another EMLAttribute by id (needs work)
+ annotation: [], // Zero or more EMLAnnotation objects
+
+ /* Attributes not from EML */
+ type: "attribute", // The element type in the DOM
+ parentModel: null, // The parent model this attribute belongs to
+ objectXML: null, // The serialized XML of this EML attribute
+ objectDOM: null, // The DOM of this EML attribute
+ nodeOrder: [
+ // The order of the top level XML element nodes
+ "attributeName",
+ "attributeLabel",
+ "attributeDefinition",
+ "storageType",
+ "measurementScale",
+ "missingValueCode",
+ "accuracy",
+ "coverage",
+ "methods",
+ "annotation",
+ ],
+ };
+ },
+
+ /*
+ * The map of lower case to camel case node names
+ * needed to deal with parsing issues with $.parseHTML().
+ * Use this until we can figure out issues with $.parseXML().
+ */
+ nodeNameMap: {
+ attributename: "attributeName",
+ attributelabel: "attributeLabel",
+ attributedefinition: "attributeDefinition",
+ sourced: "source",
+ storagetype: "storageType",
+ typesystem: "typeSystem",
+ measurementscale: "measurementScale",
+ missingvaluecode: "missingValueCode",
+ propertyuri: "propertyURI",
+ valueuri: "valueURI",
+ },
+
+ /* Initialize an EMLAttribute object */
+ initialize: function (attributes, options) {
+ if (!attributes) {
+ var attributes = {};
+ }
+
+ // If initialized with missingValueCode as an array, convert it to a collection
+ if (
+ attributes.missingValueCodes &&
+ attributes.missingValueCodes instanceof Array
+ ) {
+ this.missingValueCodes = new EMLMissingValueCodes(
+ attributes.missingValueCode,
+ );
+ }
+
+ this.stopListening(this.get("missingValueCodes"));
+ this.listenTo(
+ this.get("missingValueCodes"),
+ "update",
+ this.trickleUpChange,
+ );
+ this.on(
+ "change:attributeName " +
+ "change:attributeLabel " +
+ "change:attributeDefinition " +
+ "change:storageType " +
+ "change:measurementScale " +
+ "change:missingValueCodes " +
+ "change:accuracy " +
+ "change:coverage " +
+ "change:methods " +
+ "change:references " +
+ "change:annotation",
+ this.trickleUpChange,
+ );
+ },
+
+ /*
+ * Parse the incoming attribute's XML elements
+ */
+ parse: function (attributes, options) {
+ var $objectDOM;
+
+ if (attributes.objectDOM) {
+ $objectDOM = $(attributes.objectDOM);
+ } else if (attributes.objectXML) {
+ $objectDOM = $(attributes.objectXML);
+ } else {
+ return {};
+ }
+
+ // Add the XML id
+ if (typeof $objectDOM.attr("id") !== "undefined") {
+ attributes.xmlID = $objectDOM.attr("id");
+ }
+
+ // Add the attributeName
+ attributes.attributeName = $objectDOM.children("attributename").text();
+
+ // Add the attributeLabel
+ attributes.attributeLabel = [];
+ var attributeLabels = $objectDOM.children("attributelabel");
+ _.each(attributeLabels, function (attributeLabel) {
+ attributes.attributeLabel.push(attributeLabel.textContent);
+ });
+
+ // Add the attributeDefinition
+ attributes.attributeDefinition = $objectDOM
+ .children("attributedefinition")
+ .text();
+
+ // Add the storageType
+ attributes.storageType = [];
+ attributes.typeSystem = [];
+ var storageTypes = $objectDOM.children("storagetype");
+ _.each(storageTypes, function (storageType) {
+ attributes.storageType.push(storageType.textContent);
+ var type = $(storageType).attr("typesystem");
+ attributes.typeSystem.push(type || null);
+ });
- // If initialized with missingValueCode as an array, convert it to a collection
+ var measurementScale = $objectDOM.find("measurementscale")[0];
+ if (measurementScale) {
+ attributes.measurementScale = EMLMeasurementScale.getInstance(
+ measurementScale.outerHTML,
+ );
+ attributes.measurementScale.set("parentModel", this);
+ }
+
+ // Add annotations
+ var annotations = $objectDOM.children("annotation");
+ attributes.annotation = [];
+
+ _.each(
+ annotations,
+ function (anno) {
+ annotation = new EMLAnnotation(
+ {
+ objectDOM: anno,
+ objectXML: anno.outerHTML,
+ },
+ { parse: true },
+ );
+
+ attributes.annotation.push(annotation);
+ },
+ this,
+ );
+
+ // Add the missingValueCodes as a collection
+ attributes.missingValueCodes = new EMLMissingValueCodes();
+ attributes.missingValueCodes.parse(
+ $objectDOM.children("missingvaluecode"),
+ );
+
+ attributes.objectDOM = $objectDOM[0];
+
+ return attributes;
+ },
+
+ serialize: function () {
+ var objectDOM = this.updateDOM(),
+ xmlString = objectDOM.outerHTML;
+
+ //Camel-case the XML
+ xmlString = this.formatXML(xmlString);
+
+ return xmlString;
+ },
+
+ /* Copy the original XML and update fields in a DOM object */
+ updateDOM: function (objectDOM) {
+ var nodeToInsertAfter;
+ var type = this.get("type") || "attribute";
+ if (!objectDOM) {
+ objectDOM = this.get("objectDOM");
+ }
+ var objectXML = this.get("objectXML");
+
+ // If present, use the cached DOM
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+
+ // otherwise, use the cached XML
+ } else if (objectXML) {
+ objectDOM = $(objectXML)[0].cloneNode(true);
+
+ // This is new, create it
+ } else {
+ objectDOM = document.createElement(type);
+ }
+
+ // update the id attribute
+ var xmlID = this.get("xmlID");
+ if (xmlID) {
+ $(objectDOM).attr("id", xmlID);
+ }
+
+ // Update the attributeName
+ if (
+ typeof this.get("attributeName") == "string" &&
+ this.get("attributeName").trim().length
+ ) {
+ if ($(objectDOM).find("attributename").length) {
+ $(objectDOM).find("attributename").text(this.get("attributeName"));
+ } else {
+ nodeToInsertAfter = this.getEMLPosition(objectDOM, "attributeName");
+
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(
+ $(document.createElement("attributename")).text(
+ this.get("attributeName"),
+ )[0],
+ );
+ } else {
+ $(nodeToInsertAfter).after(
+ $(document.createElement("attributename")).text(
+ this.get("attributeName"),
+ )[0],
+ );
+ }
+ }
+ }
+ //If there is no attribute name, return an empty string because it
+ // is invalid
+ else {
+ return "";
+ }
+
+ // Update the attributeLabels
+ nodeToInsertAfter = undefined;
+ var attributeLabels = this.get("attributeLabel");
+ if (attributeLabels) {
+ if (attributeLabels.length) {
+ // Copy and reverse the array for inserting
+ attributeLabels = Array.from(attributeLabels).reverse();
+ // Remove all current attributeLabels
+ $(objectDOM).find("attributelabel").remove();
+ nodeToInsertAfter = this.getEMLPosition(
+ objectDOM,
+ "attributeLabel",
+ );
+
+ if (!nodeToInsertAfter) {
+ // Add the new list back in
+ _.each(attributeLabels, function (attributeLabel) {
+ //If there is an empty string or falsey value in the label, don't add it to the XML
+ // We check purposefuly for falsey types (instead of just doing !attributeLabel) because
+ // it's ok to serialize labels that are the number 0.
if (
- attributes.missingValueCodes &&
- attributes.missingValueCodes instanceof Array
+ (typeof attributeLabel == "string" &&
+ !attributeLabel.trim().length) ||
+ attributeLabel === false ||
+ attributeLabel === null ||
+ typeof attributeLabel == "undefined"
) {
- this.missingValueCodes =
- new EMLMissingValueCodes(attributes.missingValueCode);
- }
-
- this.stopListening(this.get("missingValueCodes"));
- this.listenTo(
- this.get("missingValueCodes"),
- "update",
- this.trickleUpChange
- )
- this.on(
- "change:attributeName " +
- "change:attributeLabel " +
- "change:attributeDefinition " +
- "change:storageType " +
- "change:measurementScale " +
- "change:missingValueCodes " +
- "change:accuracy " +
- "change:coverage " +
- "change:methods " +
- "change:references " +
- "change:annotation",
- this.trickleUpChange);
- },
-
- /*
- * Parse the incoming attribute's XML elements
- */
- parse: function(attributes, options) {
- var $objectDOM;
-
- if ( attributes.objectDOM ) {
- $objectDOM = $(attributes.objectDOM);
- } else if ( attributes.objectXML ) {
- $objectDOM = $(attributes.objectXML);
- } else {
- return {};
- }
-
- // Add the XML id
- if ( typeof $objectDOM.attr("id") !== "undefined" ) {
- attributes.xmlID = $objectDOM.attr("id");
- }
-
- // Add the attributeName
- attributes.attributeName = $objectDOM.children("attributename").text();
-
- // Add the attributeLabel
- attributes.attributeLabel = [];
- var attributeLabels = $objectDOM.children("attributelabel");
- _.each(attributeLabels, function(attributeLabel) {
- attributes.attributeLabel.push(attributeLabel.textContent);
- });
-
- // Add the attributeDefinition
- attributes.attributeDefinition = $objectDOM.children("attributedefinition").text();
-
- // Add the storageType
- attributes.storageType = [];
- attributes.typeSystem = [];
- var storageTypes = $objectDOM.children("storagetype");
- _.each(storageTypes, function(storageType) {
- attributes.storageType.push(storageType.textContent);
- var type = $(storageType).attr("typesystem");
- attributes.typeSystem.push(type || null);
- });
-
- var measurementScale = $objectDOM.find("measurementscale")[0];
- if ( measurementScale ) {
- attributes.measurementScale =
- EMLMeasurementScale.getInstance(measurementScale.outerHTML);
- attributes.measurementScale.set("parentModel", this);
+ return;
}
- // Add annotations
- var annotations = $objectDOM.children("annotation");
- attributes.annotation = [];
-
- _.each(annotations, function(anno) {
- annotation = new EMLAnnotation({
- objectDOM: anno,
- objectXML: anno.outerHTML
- }, { parse: true });
-
- attributes.annotation.push(annotation);
- }, this);
-
- // Add the missingValueCodes as a collection
- attributes.missingValueCodes = new EMLMissingValueCodes();
- attributes.missingValueCodes.parse(
- $objectDOM.children("missingvaluecode")
+ $(objectDOM).append(
+ $(document.createElement("attributelabel")).text(
+ attributeLabel,
+ )[0],
);
-
- attributes.objectDOM = $objectDOM[0];
-
- return attributes;
- },
-
- serialize: function(){
- var objectDOM = this.updateDOM(),
- xmlString = objectDOM.outerHTML;
-
- //Camel-case the XML
- xmlString = this.formatXML(xmlString);
-
- return xmlString;
- },
-
- /* Copy the original XML and update fields in a DOM object */
- updateDOM: function(objectDOM){
-
- var nodeToInsertAfter;
- var type = this.get("type") || "attribute";
- if ( ! objectDOM ) {
- objectDOM = this.get("objectDOM");
- }
- var objectXML = this.get("objectXML");
-
- // If present, use the cached DOM
- if ( objectDOM ) {
- objectDOM = objectDOM.cloneNode(true);
-
- // otherwise, use the cached XML
- } else if ( objectXML ){
- objectDOM = $(objectXML)[0].cloneNode(true);
-
- // This is new, create it
- } else {
- objectDOM = document.createElement(type);
- }
-
- // update the id attribute
- var xmlID = this.get("xmlID");
- if ( xmlID ) {
- $(objectDOM).attr("id", xmlID);
- }
-
- // Update the attributeName
- if ( typeof this.get("attributeName") == "string" && this.get("attributeName").trim().length ) {
- if ( $(objectDOM).find("attributename").length ) {
- $(objectDOM).find("attributename").text(this.get("attributeName"));
- } else {
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "attributeName");
-
- if( ! nodeToInsertAfter ) {
- $(objectDOM).append($(document.createElement("attributename"))
- .text(this.get("attributeName"))[0]);
- } else {
- $(nodeToInsertAfter).after(
- $(document.createElement("attributename")).text(this.get("attributeName"))[0]
- );
- }
- }
- }
- //If there is no attribute name, return an empty string because it
- // is invalid
- else{
- return "";
- }
-
- // Update the attributeLabels
- nodeToInsertAfter = undefined;
- var attributeLabels = this.get("attributeLabel");
- if ( attributeLabels ) {
- if ( attributeLabels.length ) {
- // Copy and reverse the array for inserting
- attributeLabels = Array.from(attributeLabels).reverse();
- // Remove all current attributeLabels
- $(objectDOM).find("attributelabel").remove();
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "attributeLabel");
-
- if( ! nodeToInsertAfter ) {
- // Add the new list back in
- _.each(attributeLabels, function(attributeLabel) {
-
- //If there is an empty string or falsey value in the label, don't add it to the XML
- // We check purposefuly for falsey types (instead of just doing !attributeLabel) because
- // it's ok to serialize labels that are the number 0.
- if( (typeof attributeLabel == "string" && !attributeLabel.trim().length) ||
- attributeLabel === false || attributeLabel === null || typeof attributeLabel == "undefined"){
- return;
- }
-
- $(objectDOM).append(
- $(document.createElement("attributelabel"))
- .text(attributeLabel)[0]);
- });
- } else {
- // Add the new list back in after its previous sibling
- _.each(attributeLabels, function(attributeLabel) {
-
- //If there is an empty string or falsey value in the label, don't add it to the XML
- // We check purposefuly for falsey types (instead of just doing !attributeLabel) because
- // it's ok to serialize labels that are the number 0.
- if( (typeof attributeLabel == "string" && !attributeLabel.trim().length) ||
- attributeLabel === false || attributeLabel === null || typeof attributeLabel == "undefined"){
- return;
- }
-
- $(nodeToInsertAfter).after(
- $(document.createElement("attributelabel"))
- .text(attributeLabel)[0]);
- });
- }
- }
- //If the label array is empty, remove all the labels from the DOM
- else{
- $(objectDOM).find("attributelabel").remove();
- }
- }
- //If there is no attribute label, remove them from the DOM
- else{
- $(objectDOM).find("attributelabel").remove();
- }
-
- // Update the attributeDefinition
- nodeToInsertAfter = undefined;
- if ( this.get("attributeDefinition") ) {
- if ( $(objectDOM).find("attributedefinition").length ) {
- $(objectDOM).find("attributedefinition").text(this.get("attributeDefinition"));
- } else {
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "attributeDefinition");
-
- if( ! nodeToInsertAfter ) {
- $(objectDOM).append($(document.createElement("attributedefinition"))
- .text(this.get("attributeDefinition"))[0]);
- } else {
- $(nodeToInsertAfter).after($(document.createElement("attributedefinition"))
- .text(this.get("attributeDefinition"))[0]);
- }
- }
- }
- // If there is no attribute definition, then return an empty String
- // because it is invalid
- else{
- return "";
+ });
+ } else {
+ // Add the new list back in after its previous sibling
+ _.each(attributeLabels, function (attributeLabel) {
+ //If there is an empty string or falsey value in the label, don't add it to the XML
+ // We check purposefuly for falsey types (instead of just doing !attributeLabel) because
+ // it's ok to serialize labels that are the number 0.
+ if (
+ (typeof attributeLabel == "string" &&
+ !attributeLabel.trim().length) ||
+ attributeLabel === false ||
+ attributeLabel === null ||
+ typeof attributeLabel == "undefined"
+ ) {
+ return;
}
- // Update the storageTypes
- nodeToInsertAfter = undefined;
- var storageTypes = this.get("storageTypes");
- if ( storageTypes ) {
- if ( storageTypes.length ) {
- // Copy and reverse the array for inserting
- storageTypes = Array.from(storageTypes).reverse();
- // Remove all current attributeLabels
- $(objectDOM).find("storagetype").remove();
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "storageType");
-
- if( ! nodeToInsertAfter ) {
- // Add the new list back in
- _.each(storageTypes, function(storageType) {
-
- if(!storageType)
- return;
-
- $(objectDOM).append(
- $(document.createElement("storagetype"))
- .text(storageType)[0]);
- });
- } else {
- // Add the new list back in after its previous sibling
- _.each(storageTypes, function(storageType) {
-
- if(!storageType)
- return;
-
- $(nodeToInsertAfter).after(
- $(document.createElement("storagetype"))
- .text(storageType)[0]);
- });
- }
- }
- }
- /*If there are no storage types, remove them all from the DOM.
+ $(nodeToInsertAfter).after(
+ $(document.createElement("attributelabel")).text(
+ attributeLabel,
+ )[0],
+ );
+ });
+ }
+ }
+ //If the label array is empty, remove all the labels from the DOM
+ else {
+ $(objectDOM).find("attributelabel").remove();
+ }
+ }
+ //If there is no attribute label, remove them from the DOM
+ else {
+ $(objectDOM).find("attributelabel").remove();
+ }
+
+ // Update the attributeDefinition
+ nodeToInsertAfter = undefined;
+ if (this.get("attributeDefinition")) {
+ if ($(objectDOM).find("attributedefinition").length) {
+ $(objectDOM)
+ .find("attributedefinition")
+ .text(this.get("attributeDefinition"));
+ } else {
+ nodeToInsertAfter = this.getEMLPosition(
+ objectDOM,
+ "attributeDefinition",
+ );
+
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(
+ $(document.createElement("attributedefinition")).text(
+ this.get("attributeDefinition"),
+ )[0],
+ );
+ } else {
+ $(nodeToInsertAfter).after(
+ $(document.createElement("attributedefinition")).text(
+ this.get("attributeDefinition"),
+ )[0],
+ );
+ }
+ }
+ }
+ // If there is no attribute definition, then return an empty String
+ // because it is invalid
+ else {
+ return "";
+ }
+
+ // Update the storageTypes
+ nodeToInsertAfter = undefined;
+ var storageTypes = this.get("storageTypes");
+ if (storageTypes) {
+ if (storageTypes.length) {
+ // Copy and reverse the array for inserting
+ storageTypes = Array.from(storageTypes).reverse();
+ // Remove all current attributeLabels
+ $(objectDOM).find("storagetype").remove();
+ nodeToInsertAfter = this.getEMLPosition(objectDOM, "storageType");
+
+ if (!nodeToInsertAfter) {
+ // Add the new list back in
+ _.each(storageTypes, function (storageType) {
+ if (!storageType) return;
+
+ $(objectDOM).append(
+ $(document.createElement("storagetype")).text(storageType)[0],
+ );
+ });
+ } else {
+ // Add the new list back in after its previous sibling
+ _.each(storageTypes, function (storageType) {
+ if (!storageType) return;
+
+ $(nodeToInsertAfter).after(
+ $(document.createElement("storagetype")).text(storageType)[0],
+ );
+ });
+ }
+ }
+ }
+ /*If there are no storage types, remove them all from the DOM.
TODO: Uncomment this out when storage type is supported in editor
else{
$(objectDOM).find("storagetype").remove();
}
*/
- // Update the measurementScale
- nodeToInsertAfter = undefined;
- var measurementScale = this.get("measurementScale");
- var measurementScaleNodes;
- var measurementScaleNode;
- var domainNode;
- if ( typeof measurementScale !== "undefined" && measurementScale) {
-
- // Find the measurementScale child or create a new one
- measurementScaleNodes = $(objectDOM).children("measurementscale");
- if ( measurementScaleNodes.length ) {
- measurementScaleNode = measurementScaleNodes[0];
-
- } else {
- measurementScaleNode = document.createElement("measurementscale");
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "measurementScale");
-
- if ( typeof nodeToInsertAfter === "undefined" ) {
- $(objectDOM).append(measurementScaleNode);
- } else {
- $(nodeToInsertAfter).after(measurementScaleNode);
- }
- }
-
- // Append the measurementScale domain content
- domainNode = measurementScale.updateDOM();
- if (typeof domainNode !== "undefined" ) {
- $(measurementScaleNode).children().remove();
- $(measurementScaleNode).append(domainNode);
- }
-
- } else {
- console.log("No measurementScale object has been defined.");
- }
-
- // Update annotations
- var annotation = this.get("annotation");
-
- // Always remove all annotations to start with
- $(objectDOM).children("annotation").remove();
-
- _.each(annotation, function(anno) {
- if (anno.isEmpty()) {
- return;
- }
-
- var after = this.getEMLPosition(objectDOM, "annotation");
- $(after).after(anno.updateDOM());
- }, this);
-
- // Update the missingValueCodes
- nodeToInsertAfter = undefined;
- var missingValueCodes = this.get("missingValueCodes");
- $(objectDOM).children("missingvaluecode").remove();
- if (missingValueCodes) {
- var missingValueCodeNodes = missingValueCodes.updateDOM();
- if (missingValueCodeNodes) {
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "missingValueCode");
- if (typeof nodeToInsertAfter === "undefined") {
- $(objectDOM).append(missingValueCodeNodes);
- } else {
- $(nodeToInsertAfter).after(missingValueCodeNodes);
- }
- }
- }
-
- return objectDOM;
- },
-
- /*
- * Get the DOM node preceding the given nodeName
- * to find what position in the EML document
- * the named node should be appended
- */
- getEMLPosition: function(objectDOM, nodeName) {
- var nodeOrder = this.get("nodeOrder");
-
- var position = _.indexOf(nodeOrder, nodeName);
-
- // Append to the bottom if not found
- if ( position == -1 ) {
- return $(objectDOM).children().last()[0];
- }
-
- // Otherwise, go through each node in the node list and find the
- // position where this node will be inserted after
- for ( var i = position - 1; i >= 0; i-- ) {
- if ( $(objectDOM).find(nodeOrder[i].toLowerCase()).length ) {
- return $(objectDOM).find(nodeOrder[i].toLowerCase()).last()[0];
- }
- }
- },
-
- formatXML: function(xmlString){
- return DataONEObject.prototype.formatXML.call(this, xmlString);
- },
-
- validate: function(){
- var errors = {};
-
- //If there is no attribute name, add that error message
- if(!this.get("attributeName"))
- errors.attributeName = "Provide a name for this attribute.";
-
- //If there is no attribute definition, add that error message
- if(!this.get("attributeDefinition"))
- errors.attributeDefinition = "Provide a definition for this attribute.";
-
- //Get the EML measurement scale model
- var measurementScaleModel = this.get("measurementScale");
-
- // If there is no measurement scale model, then add that error message
- if( !measurementScaleModel ){
- errors.measurementScale = "Choose a measurement scale category for this attribute.";
- }
- else{
- if( !measurementScaleModel.isValid() ){
- errors.measurementScale = "More information is needed.";
- }
- }
-
- // Validate the missing value codes
- var missingValueCodesErrors = this.get("missingValueCodes")?.validate();
- if (missingValueCodesErrors) {
- // Just display the first error message
- errors.missingValueCodes = Object.values(missingValueCodesErrors)[0]
- }
+ // Update the measurementScale
+ nodeToInsertAfter = undefined;
+ var measurementScale = this.get("measurementScale");
+ var measurementScaleNodes;
+ var measurementScaleNode;
+ var domainNode;
+ if (typeof measurementScale !== "undefined" && measurementScale) {
+ // Find the measurementScale child or create a new one
+ measurementScaleNodes = $(objectDOM).children("measurementscale");
+ if (measurementScaleNodes.length) {
+ measurementScaleNode = measurementScaleNodes[0];
+ } else {
+ measurementScaleNode = document.createElement("measurementscale");
+ nodeToInsertAfter = this.getEMLPosition(
+ objectDOM,
+ "measurementScale",
+ );
+
+ if (typeof nodeToInsertAfter === "undefined") {
+ $(objectDOM).append(measurementScaleNode);
+ } else {
+ $(nodeToInsertAfter).after(measurementScaleNode);
+ }
+ }
+
+ // Append the measurementScale domain content
+ domainNode = measurementScale.updateDOM();
+ if (typeof domainNode !== "undefined") {
+ $(measurementScaleNode).children().remove();
+ $(measurementScaleNode).append(domainNode);
+ }
+ } else {
+ console.log("No measurementScale object has been defined.");
+ }
+
+ // Update annotations
+ var annotation = this.get("annotation");
+
+ // Always remove all annotations to start with
+ $(objectDOM).children("annotation").remove();
+
+ _.each(
+ annotation,
+ function (anno) {
+ if (anno.isEmpty()) {
+ return;
+ }
- // If there is a measurement scale model and it is valid and there are no other
- // errors, then trigger this model as valid and exit.
- if (!Object.keys(errors).length) {
- this.trigger("valid", this);
- return;
- } else {
- //If there is at least one error, then return the errors object
- return errors;
- }
- },
-
- /*
- * Validates each of the EMLAnnotation models on this model
- *
- * @return {Array} - Returns an array of error messages for all the EMLAnnotation models
- */
- validateAnnotations: function(){
- var errors = [];
-
- //Validate each of the EMLAttributes
- _.each(this.get("annotation"), function (anno) {
- if (anno.isValid()) {
- return;
- }
-
- errors.push(anno.validationError);
- });
-
- return errors;
- },
-
- /*
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211 or false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
- var emlModel = this.get("parentModel"),
- tries = 0;
-
- while (emlModel.type !== "EML" && tries < 6){
- emlModel = emlModel.get("parentModel");
- tries++;
- }
-
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
-
- },
-
- /* Let the top level package know of attribute changes from this object */
- trickleUpChange: function(){
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- },
-
- createID: function() {
- this.set("xmlID", uuid.v4());
+ var after = this.getEMLPosition(objectDOM, "annotation");
+ $(after).after(anno.updateDOM());
+ },
+ this,
+ );
+
+ // Update the missingValueCodes
+ nodeToInsertAfter = undefined;
+ var missingValueCodes = this.get("missingValueCodes");
+ $(objectDOM).children("missingvaluecode").remove();
+ if (missingValueCodes) {
+ var missingValueCodeNodes = missingValueCodes.updateDOM();
+ if (missingValueCodeNodes) {
+ nodeToInsertAfter = this.getEMLPosition(
+ objectDOM,
+ "missingValueCode",
+ );
+ if (typeof nodeToInsertAfter === "undefined") {
+ $(objectDOM).append(missingValueCodeNodes);
+ } else {
+ $(nodeToInsertAfter).after(missingValueCodeNodes);
}
+ }
+ }
+
+ return objectDOM;
+ },
+
+ /*
+ * Get the DOM node preceding the given nodeName
+ * to find what position in the EML document
+ * the named node should be appended
+ */
+ getEMLPosition: function (objectDOM, nodeName) {
+ var nodeOrder = this.get("nodeOrder");
+
+ var position = _.indexOf(nodeOrder, nodeName);
+
+ // Append to the bottom if not found
+ if (position == -1) {
+ return $(objectDOM).children().last()[0];
+ }
+
+ // Otherwise, go through each node in the node list and find the
+ // position where this node will be inserted after
+ for (var i = position - 1; i >= 0; i--) {
+ if ($(objectDOM).find(nodeOrder[i].toLowerCase()).length) {
+ return $(objectDOM).find(nodeOrder[i].toLowerCase()).last()[0];
+ }
+ }
+ },
+
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
+
+ validate: function () {
+ var errors = {};
+
+ //If there is no attribute name, add that error message
+ if (!this.get("attributeName"))
+ errors.attributeName = "Provide a name for this attribute.";
+
+ //If there is no attribute definition, add that error message
+ if (!this.get("attributeDefinition"))
+ errors.attributeDefinition =
+ "Provide a definition for this attribute.";
+
+ //Get the EML measurement scale model
+ var measurementScaleModel = this.get("measurementScale");
+
+ // If there is no measurement scale model, then add that error message
+ if (!measurementScaleModel) {
+ errors.measurementScale =
+ "Choose a measurement scale category for this attribute.";
+ } else {
+ if (!measurementScaleModel.isValid()) {
+ errors.measurementScale = "More information is needed.";
+ }
+ }
+
+ // Validate the missing value codes
+ var missingValueCodesErrors = this.get("missingValueCodes")?.validate();
+ if (missingValueCodesErrors) {
+ // Just display the first error message
+ errors.missingValueCodes = Object.values(missingValueCodesErrors)[0];
+ }
+
+ // If there is a measurement scale model and it is valid and there are no other
+ // errors, then trigger this model as valid and exit.
+ if (!Object.keys(errors).length) {
+ this.trigger("valid", this);
+ return;
+ } else {
+ //If there is at least one error, then return the errors object
+ return errors;
+ }
+ },
+
+ /*
+ * Validates each of the EMLAnnotation models on this model
+ *
+ * @return {Array} - Returns an array of error messages for all the EMLAnnotation models
+ */
+ validateAnnotations: function () {
+ var errors = [];
+
+ //Validate each of the EMLAttributes
+ _.each(this.get("annotation"), function (anno) {
+ if (anno.isValid()) {
+ return;
+ }
+
+ errors.push(anno.validationError);
});
- return EMLAttribute;
- }
-);
+ return errors;
+ },
+
+ /*
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211 or false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
+ var emlModel = this.get("parentModel"),
+ tries = 0;
+
+ while (emlModel.type !== "EML" && tries < 6) {
+ emlModel = emlModel.get("parentModel");
+ tries++;
+ }
+
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
+ },
+
+ /* Let the top level package know of attribute changes from this object */
+ trickleUpChange: function () {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ },
+
+ createID: function () {
+ this.set("xmlID", uuid.v4());
+ },
+ },
+ );
+
+ return EMLAttribute;
+});
diff --git a/src/js/models/metadata/eml211/EMLDataTable.js b/src/js/models/metadata/eml211/EMLDataTable.js
index a240d4be1..5833b1178 100644
--- a/src/js/models/metadata/eml211/EMLDataTable.js
+++ b/src/js/models/metadata/eml211/EMLDataTable.js
@@ -1,235 +1,253 @@
-define(["jquery", "underscore", "backbone", "models/metadata/eml211/EMLEntity"],
- function($, _, Backbone, EMLEntity) {
-
- /**
- * @class EMLDataTable
- * @classdesc EMLDataTable represents a tabular data entity, corresponding
- * with the EML dataTable module.
- * @classcategory Models/Metadata/EML211
- * @see https://eml.ecoinformatics.org/schema/eml-datatable_xsd
- * @extends EMLEntity
- */
- var EMLDataTable = EMLEntity.extend(
- /** @lends EMLDataTable.prototype */{
-
- //The class name for this model
- type: "EMLDataTable",
-
- /* Attributes of any entity */
- defaults: function(){
- return _.extend({
-
- /* Attributes from EML */
- caseSensitive: null, // The case sensitivity of the table records
- numberOfRecords: null, // the number of records in the table
- type: "dataTable",
-
- /* Attributes not from EML */
- nodeOrder: [ // The order of the top level XML element nodes
- "caseSensitive",
- "numberOfRecords",
- "references"
- ],
-
- }, EMLEntity.prototype.defaults());
- },
-
- /*
- * The map of lower case to camel case node names
- * needed to deal with parsing issues with $.parseHTML().
- * Use this until we can figure out issues with $.parseXML().
- */
- nodeNameMap: _.extend({
- "casesensitive" : "caseSensitive",
- "numberofrecords": "numberOfRecords"
-
- }, EMLEntity.prototype.nodeNameMap),
-
- /* Initialize an EMLDataTable object */
- initialize: function(attributes) {
-
- // if options.parse = true, Backbone will call parse()
-
- // Call super() first
- this.constructor.__super__.initialize.apply(this, [attributes]);
-
- // EMLDataTable-specific work
- this.set("type", "dataTable", {silent: true});
-
- // Register change events
- this.on( "change:caseSensitive change:numberOfRecords", EMLEntity.trickleUpChange);
-
- },
-
- /*
- * Parse the incoming other entity's XML elements
- */
- parse: function(attributes, options) {
-
- var attributes = attributes || {};
-
- // Call super() first
- attributes = this.constructor.__super__.parse.apply(this, [attributes, options]);
-
- // EMLDataTable-specific work
- var objectXML = attributes.objectXML; // The dataTable XML fragment
- var objectDOM; // The W3C DOM of the object XML fragment
- var $objectDOM; // The JQuery object of the XML fragment
-
- // Use the updated objectDOM if we have it
- if ( attributes.objectDOM ) {
- $objectDOM = $(attributes.objectDOM);
- } else {
- // Hmm, oddly not there, start from scratch =/
- $objectDOM = $(objectXML);
- }
-
- // Add the caseSensitive
- attributes.caseSensitive = $objectDOM.children("caseSensitive").text();
-
- // Add the numberOfRecords
- attributes.numberOfRecords = $objectDOM.children("numberOfRecords").text();
-
- // Add the references value
- attributes.references = $objectDOM.children("references").text();
-
- return attributes;
- },
-
- /* Copy the original XML and update fields in a DOM object */
- updateDOM: function(objectDOM) {
- var nodeToInsertAfter;
- var type = this.get("type") || "dataTable";
- if ( ! objectDOM ) {
- objectDOM = this.get("objectDOM");
- }
- var objectXML = this.get("objectXML");
-
- // If present, use the cached DOM
- if ( objectDOM ) {
- objectDOM = objectDOM.cloneNode(true);
-
- // otherwise, use the cached XML
- } else if ( objectXML ){
- objectDOM = $(objectXML)[0].cloneNode(true);
-
- // This is new, create it
- } else {
- objectDOM = document.createElement(type);
-
- }
-
- // Now call the superclass
- objectDOM = this.constructor.__super__.updateDOM.apply(this, [objectDOM]);
-
- // And then update the EMLDataTable-specific fields
- // Update the caseSensitive field
- if ( this.get("caseSensitive") ) {
- if ( $(objectDOM).find("caseSensitive").length ) {
- $(objectDOM).find("caseSensitive").text(this.get("caseSensitive"));
-
- } else {
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "caseSensitive");
-
- if ( ! nodeToInsertAfter ) {
- $(objectDOM).append($(document.createElement("casesensitive"))
- .text(this.get("caseSensitive"))[0]);
- } else {
- $(nodeToInsertAfter).after($(document.createElement("casesensitive"))
- .text(this.get("caseSensitive"))[0]);
- }
- }
- }
-
- // Update the numberOfRecords field
- if ( this.get("numberOfRecords") ) {
- if ( $(objectDOM).find("numberOfRecords").length ) {
- $(objectDOM).find("numberOfRecords").text(this.get("numberOfRecords"));
-
- } else {
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "numberOfRecords");
-
- if ( ! nodeToInsertAfter ) {
- $(objectDOM).append($(document.createElement("numberofrecords"))
- .text(this.get("numberOfRecords"))[0]);
- } else {
- $(nodeToInsertAfter).after($(document.createElement("numberofrecords"))
- .text(this.get("numberOfRecords"))[0]);
- }
- }
- }
-
- return objectDOM;
- },
-
- /* Serialize the EML DOM to XML */
- serialize: function() {
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/metadata/eml211/EMLEntity",
+], function ($, _, Backbone, EMLEntity) {
+ /**
+ * @class EMLDataTable
+ * @classdesc EMLDataTable represents a tabular data entity, corresponding
+ * with the EML dataTable module.
+ * @classcategory Models/Metadata/EML211
+ * @see https://eml.ecoinformatics.org/schema/eml-datatable_xsd
+ * @extends EMLEntity
+ */
+ var EMLDataTable = EMLEntity.extend(
+ /** @lends EMLDataTable.prototype */ {
+ //The class name for this model
+ type: "EMLDataTable",
+
+ /* Attributes of any entity */
+ defaults: function () {
+ return _.extend(
+ {
+ /* Attributes from EML */
+ caseSensitive: null, // The case sensitivity of the table records
+ numberOfRecords: null, // the number of records in the table
+ type: "dataTable",
+
+ /* Attributes not from EML */
+ nodeOrder: [
+ // The order of the top level XML element nodes
+ "caseSensitive",
+ "numberOfRecords",
+ "references",
+ ],
+ },
+ EMLEntity.prototype.defaults(),
+ );
+ },
+
+ /*
+ * The map of lower case to camel case node names
+ * needed to deal with parsing issues with $.parseHTML().
+ * Use this until we can figure out issues with $.parseXML().
+ */
+ nodeNameMap: _.extend(
+ {
+ casesensitive: "caseSensitive",
+ numberofrecords: "numberOfRecords",
+ },
+ EMLEntity.prototype.nodeNameMap,
+ ),
+
+ /* Initialize an EMLDataTable object */
+ initialize: function (attributes) {
+ // if options.parse = true, Backbone will call parse()
+
+ // Call super() first
+ this.constructor.__super__.initialize.apply(this, [attributes]);
+
+ // EMLDataTable-specific work
+ this.set("type", "dataTable", { silent: true });
+
+ // Register change events
+ this.on(
+ "change:caseSensitive change:numberOfRecords",
+ EMLEntity.trickleUpChange,
+ );
+ },
+
+ /*
+ * Parse the incoming other entity's XML elements
+ */
+ parse: function (attributes, options) {
+ var attributes = attributes || {};
+
+ // Call super() first
+ attributes = this.constructor.__super__.parse.apply(this, [
+ attributes,
+ options,
+ ]);
+
+ // EMLDataTable-specific work
+ var objectXML = attributes.objectXML; // The dataTable XML fragment
+ var objectDOM; // The W3C DOM of the object XML fragment
+ var $objectDOM; // The JQuery object of the XML fragment
+
+ // Use the updated objectDOM if we have it
+ if (attributes.objectDOM) {
+ $objectDOM = $(attributes.objectDOM);
+ } else {
+ // Hmm, oddly not there, start from scratch =/
+ $objectDOM = $(objectXML);
+ }
+
+ // Add the caseSensitive
+ attributes.caseSensitive = $objectDOM.children("caseSensitive").text();
+
+ // Add the numberOfRecords
+ attributes.numberOfRecords = $objectDOM
+ .children("numberOfRecords")
+ .text();
+
+ // Add the references value
+ attributes.references = $objectDOM.children("references").text();
+
+ return attributes;
+ },
+
+ /* Copy the original XML and update fields in a DOM object */
+ updateDOM: function (objectDOM) {
+ var nodeToInsertAfter;
+ var type = this.get("type") || "dataTable";
+ if (!objectDOM) {
+ objectDOM = this.get("objectDOM");
+ }
+ var objectXML = this.get("objectXML");
+
+ // If present, use the cached DOM
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+
+ // otherwise, use the cached XML
+ } else if (objectXML) {
+ objectDOM = $(objectXML)[0].cloneNode(true);
+
+ // This is new, create it
+ } else {
+ objectDOM = document.createElement(type);
+ }
+
+ // Now call the superclass
+ objectDOM = this.constructor.__super__.updateDOM.apply(this, [
+ objectDOM,
+ ]);
+
+ // And then update the EMLDataTable-specific fields
+ // Update the caseSensitive field
+ if (this.get("caseSensitive")) {
+ if ($(objectDOM).find("caseSensitive").length) {
+ $(objectDOM).find("caseSensitive").text(this.get("caseSensitive"));
+ } else {
+ nodeToInsertAfter = this.getEMLPosition(objectDOM, "caseSensitive");
+
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(
+ $(document.createElement("casesensitive")).text(
+ this.get("caseSensitive"),
+ )[0],
+ );
+ } else {
+ $(nodeToInsertAfter).after(
+ $(document.createElement("casesensitive")).text(
+ this.get("caseSensitive"),
+ )[0],
+ );
+ }
+ }
+ }
+
+ // Update the numberOfRecords field
+ if (this.get("numberOfRecords")) {
+ if ($(objectDOM).find("numberOfRecords").length) {
+ $(objectDOM)
+ .find("numberOfRecords")
+ .text(this.get("numberOfRecords"));
+ } else {
+ nodeToInsertAfter = this.getEMLPosition(
+ objectDOM,
+ "numberOfRecords",
+ );
+
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(
+ $(document.createElement("numberofrecords")).text(
+ this.get("numberOfRecords"),
+ )[0],
+ );
+ } else {
+ $(nodeToInsertAfter).after(
+ $(document.createElement("numberofrecords")).text(
+ this.get("numberOfRecords"),
+ )[0],
+ );
+ }
+ }
+ }
- var xmlString = "";
+ return objectDOM;
+ },
- // Update the superclass fields in the objectDOM first
- var objectDOM = this.constructor.__super__.updateDOM.apply(this, []);
-
- // Then update the subclass fields in the objectDOM
- // TODO
+ /* Serialize the EML DOM to XML */
+ serialize: function () {
+ var xmlString = "";
+ // Update the superclass fields in the objectDOM first
+ var objectDOM = this.constructor.__super__.updateDOM.apply(this, []);
- this.set("objectXML", xmlString);
+ // Then update the subclass fields in the objectDOM
+ // TODO
- return xmlString;
- },
+ this.set("objectXML", xmlString);
- /* Validate the datable's required fields */
- validate: function(){
+ return xmlString;
+ },
- var errors = {};
+ /* Validate the datable's required fields */
+ validate: function () {
+ var errors = {};
- // Require the entity name
- if( !this.get("entityName") ) {
- errors.entityName = "Please specify an data table name.";
- }
+ // Require the entity name
+ if (!this.get("entityName")) {
+ errors.entityName = "Please specify an data table name.";
+ }
- //Validate the attributes
- var attributeErrors = this.validateAttributes();
- if(attributeErrors.length)
- errors.attributeList = errors;
+ //Validate the attributes
+ var attributeErrors = this.validateAttributes();
+ if (attributeErrors.length) errors.attributeList = errors;
- // Require the attribute list
- /*if( !this.get("attributeList").length ) {
+ // Require the attribute list
+ /*if( !this.get("attributeList").length ) {
errors.attributeList = "Please describe the table attributes (columns).";
}*/
- if( Object.keys(errors).length ){
- return errors;
- }
- else{
- return false;
- }
- },
-
- /*
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211 or false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
- var emlModel = this.get("parentModel"),
- tries = 0;
-
- while (emlModel.type !== "EML" && tries < 6){
- emlModel = emlModel.get("parentModel");
- tries++;
- }
-
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
-
- }
-
- });
-
- return EMLDataTable;
- }
-);
+ if (Object.keys(errors).length) {
+ return errors;
+ } else {
+ return false;
+ }
+ },
+
+ /*
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211 or false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
+ var emlModel = this.get("parentModel"),
+ tries = 0;
+
+ while (emlModel.type !== "EML" && tries < 6) {
+ emlModel = emlModel.get("parentModel");
+ tries++;
+ }
+
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
+ },
+ },
+ );
+
+ return EMLDataTable;
+});
diff --git a/src/js/models/metadata/eml211/EMLDateTimeDomain.js b/src/js/models/metadata/eml211/EMLDateTimeDomain.js
index e503a285f..96e2cf2b9 100644
--- a/src/js/models/metadata/eml211/EMLDateTimeDomain.js
+++ b/src/js/models/metadata/eml211/EMLDateTimeDomain.js
@@ -1,371 +1,382 @@
-define(["jquery", "underscore", "backbone",
- "models/DataONEObject"],
- function($, _, Backbone, DataONEObject) {
-
- /**
- * @classdesc EMLDateTimeDomain represents the measurement scale of a date/time
- * attribute.
- * @classcategory Models/Metadata/EML211
- * @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html#AttributeType_AttributeType_measurementScale_dateTime
- * @extends Backbone.Model
- */
- var EMLDateTimeDomain = Backbone.Model.extend(
- /** @lends EMLDateTimeDomain.prototype */{
-
- type: "EMLDateTimeDomain",
-
- /* Attributes of an EMLDateTimeDomain object */
- el: "datetime",
-
- defaults: function(){
- return {
- /* Attributes from EML */
- formatString: null, // Required format string (e.g. YYYY)
- dateTimePrecision: null, // The precision of the date time value
- dateTimeDomain: null, // Zero or more bounds, or a references object
- /* Attributes not from EML */
- type: "dateTime",
- parentModel: null, // The parent model this attribute belongs to
- objectXML: null, // The serialized XML of this EML measurement scale
- objectDOM: null // The DOM of this EML measurement scale
- }
- },
-
- /*
- * The map of lower case to camel case node names
- * needed to deal with parsing issues with $.parseHTML().
- * Use this until we can figure out issues with $.parseXML().
- */
- nodeNameMap: {
- "alternativetimescale" : "alternativeTimeScale",
- "datetime": "dateTime",
- "formatstring": "formatString",
- "datetimeprecision": "dateTimePrecision",
- "datetimedomain": "dateTimeDomain",
- "timescalename" : "timeScaleName",
- "timescaleageestimate" : "timeScaleAgeEstimate",
- "timescaleageuncertainty" : "timeScaleAgeUncertainty",
- "timescaleageexplanation" : "timeScaleAgeExplanation",
- "timescalecitation" : "timeScaleCitation"
- },
-
- /* Initialize an EMLDateTimeDomain object */
- initialize: function(attributes, options) {
-
- this.on(
- "change:formatString " +
- "change:dateTimePrecision " +
- "change:dateTimeDomain",
- this.trickleUpChange);
- },
-
- /*
- * Parse the incoming measurementScale's XML elements
- */
- parse: function(attributes, options) {
- var $objectDOM;
- var measurementScale;
- var rootNodeName;
-
- if ( attributes.objectDOM ) {
- rootNodeName = $(attributes.objectDOM)[0].localName;
- $objectDOM = $(attributes.objectDOM);
- } else if ( attributes.objectXML ) {
- rootNodeName = $(attributes.objectXML)[0].localName;
- $objectDOM = $($(attributes.objectXML)[0]);
- } else {
- return {};
- }
-
- // If measurementScale is present, add it
- if ( rootNodeName == "measurementscale" ) {
- attributes.measurementScale = $objectDOM.children().first()[0].localName;
- $objectDOM = $objectDOM.children().first();
- } else {
- attributes.measurementScale = $objectDOM.localName;
- }
-
- // Add the formatString
- attributes.formatString = $objectDOM.find("formatstring").text();
-
- // Add the dateTimePrecision
- attributes.dateTimePrecision = $objectDOM.find("datetimeprecision").text();
-
- // Add in the dateTimeDomain
- var dateTimeDomain = $objectDOM.find("datetimedomain");
- if ( dateTimeDomain.length ) {
- attributes.dateTimeDomain = this.parseDateTimeDomain(dateTimeDomain);
-
- }
- attributes.objectDOM = $objectDOM[0];
-
- return attributes;
- },
-
- /*
- * Parse the attribute/measurementScale/dateTime/dateTimeDomain fragment
- * returning a domain object with a bounds attribute consisting of an array
- * of objects with optional minimum and maximum attributes
- * For example:
- * {
- * bounds: [
- * {minimum: 2015, maximum: 2016},
- * {minimum: 2017, maximum: 2018}
- * ]
- * }
- * TODO: Support the references element
- */
- parseDateTimeDomain: function(dateTimeDomainXML) {
- var domain = {
- bounds: []
- }
- var bounds = $(dateTimeDomainXML).find("bounds");
-
- _.each(bounds, function(bound) {
- var bnd = {};
- // Get the minimum if available
- var min = $(bound).find("minimum").text();
- if ( min ) {
- bnd.minimum = min;
- bnd.exclusive = $(bound).find("minimum").attr("exclusive");
- }
- // Get the maximum if available
- var max = $(bound).find("maximum").text();
- if ( max ) {
- bnd.maximum = max;
- bnd.exclusive = $(bound).find("maximum").attr("exclusive");
- }
- domain.bounds.push(bnd);
-
- }, domain);
-
- return domain;
- },
-
- /* Serialize the model to XML */
- serialize: function() {
- var objectDOM = this.updateDOM();
- var xmlString = objectDOM.outerHTML;
-
- // Camel-case the XML
- xmlString = this.formatXML(xmlString);
-
- return xmlString;
- },
-
- /* Copy the original XML DOM and update it with new values from the model */
- updateDOM: function(objectDOM) {
-
- var nodeToInsertAfter;
- var type = this.get("type") || "datetime";
- if ( ! objectDOM ) {
- objectDOM = this.get("objectDOM");
- }
- var objectXML = this.get("objectXML");
-
- // If present, use the cached DOM
- if ( objectDOM ) {
- objectDOM = objectDOM.cloneNode(true);
-
- // otherwise, use the cached XML
- } else if ( objectXML ){
- objectDOM = $(objectXML)[0].cloneNode(true);
-
- // This is new, create it
- } else {
- objectDOM = document.createElement(type);
-
- }
-
- // Update the formatString
- if ( this.get("formatString") ) {
- if ( $(objectDOM).find("formatstring").length ) {
- $(objectDOM).find("formatstring").text(this.get("formatString"));
- } else {
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "formatString");
-
- if( ! nodeToInsertAfter ) {
- $(objectDOM).append($(document.createElement("formatstring"))
- .text(this.get("formatString"))[0]);
- } else {
- $(nodeToInsertAfter).after(
- $(document.createElement("formatstring"))
- .text(this.get("formatString"))[0]
- );
- }
- }
- }
- /* TODO: Uncomment out when formatstrings are better supported
+define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
+ $,
+ _,
+ Backbone,
+ DataONEObject,
+) {
+ /**
+ * @classdesc EMLDateTimeDomain represents the measurement scale of a date/time
+ * attribute.
+ * @classcategory Models/Metadata/EML211
+ * @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html#AttributeType_AttributeType_measurementScale_dateTime
+ * @extends Backbone.Model
+ */
+ var EMLDateTimeDomain = Backbone.Model.extend(
+ /** @lends EMLDateTimeDomain.prototype */ {
+ type: "EMLDateTimeDomain",
+
+ /* Attributes of an EMLDateTimeDomain object */
+ el: "datetime",
+
+ defaults: function () {
+ return {
+ /* Attributes from EML */
+ formatString: null, // Required format string (e.g. YYYY)
+ dateTimePrecision: null, // The precision of the date time value
+ dateTimeDomain: null, // Zero or more bounds, or a references object
+ /* Attributes not from EML */
+ type: "dateTime",
+ parentModel: null, // The parent model this attribute belongs to
+ objectXML: null, // The serialized XML of this EML measurement scale
+ objectDOM: null, // The DOM of this EML measurement scale
+ };
+ },
+
+ /*
+ * The map of lower case to camel case node names
+ * needed to deal with parsing issues with $.parseHTML().
+ * Use this until we can figure out issues with $.parseXML().
+ */
+ nodeNameMap: {
+ alternativetimescale: "alternativeTimeScale",
+ datetime: "dateTime",
+ formatstring: "formatString",
+ datetimeprecision: "dateTimePrecision",
+ datetimedomain: "dateTimeDomain",
+ timescalename: "timeScaleName",
+ timescaleageestimate: "timeScaleAgeEstimate",
+ timescaleageuncertainty: "timeScaleAgeUncertainty",
+ timescaleageexplanation: "timeScaleAgeExplanation",
+ timescalecitation: "timeScaleCitation",
+ },
+
+ /* Initialize an EMLDateTimeDomain object */
+ initialize: function (attributes, options) {
+ this.on(
+ "change:formatString " +
+ "change:dateTimePrecision " +
+ "change:dateTimeDomain",
+ this.trickleUpChange,
+ );
+ },
+
+ /*
+ * Parse the incoming measurementScale's XML elements
+ */
+ parse: function (attributes, options) {
+ var $objectDOM;
+ var measurementScale;
+ var rootNodeName;
+
+ if (attributes.objectDOM) {
+ rootNodeName = $(attributes.objectDOM)[0].localName;
+ $objectDOM = $(attributes.objectDOM);
+ } else if (attributes.objectXML) {
+ rootNodeName = $(attributes.objectXML)[0].localName;
+ $objectDOM = $($(attributes.objectXML)[0]);
+ } else {
+ return {};
+ }
+
+ // If measurementScale is present, add it
+ if (rootNodeName == "measurementscale") {
+ attributes.measurementScale = $objectDOM
+ .children()
+ .first()[0].localName;
+ $objectDOM = $objectDOM.children().first();
+ } else {
+ attributes.measurementScale = $objectDOM.localName;
+ }
+
+ // Add the formatString
+ attributes.formatString = $objectDOM.find("formatstring").text();
+
+ // Add the dateTimePrecision
+ attributes.dateTimePrecision = $objectDOM
+ .find("datetimeprecision")
+ .text();
+
+ // Add in the dateTimeDomain
+ var dateTimeDomain = $objectDOM.find("datetimedomain");
+ if (dateTimeDomain.length) {
+ attributes.dateTimeDomain = this.parseDateTimeDomain(dateTimeDomain);
+ }
+ attributes.objectDOM = $objectDOM[0];
+
+ return attributes;
+ },
+
+ /*
+ * Parse the attribute/measurementScale/dateTime/dateTimeDomain fragment
+ * returning a domain object with a bounds attribute consisting of an array
+ * of objects with optional minimum and maximum attributes
+ * For example:
+ * {
+ * bounds: [
+ * {minimum: 2015, maximum: 2016},
+ * {minimum: 2017, maximum: 2018}
+ * ]
+ * }
+ * TODO: Support the references element
+ */
+ parseDateTimeDomain: function (dateTimeDomainXML) {
+ var domain = {
+ bounds: [],
+ };
+ var bounds = $(dateTimeDomainXML).find("bounds");
+
+ _.each(
+ bounds,
+ function (bound) {
+ var bnd = {};
+ // Get the minimum if available
+ var min = $(bound).find("minimum").text();
+ if (min) {
+ bnd.minimum = min;
+ bnd.exclusive = $(bound).find("minimum").attr("exclusive");
+ }
+ // Get the maximum if available
+ var max = $(bound).find("maximum").text();
+ if (max) {
+ bnd.maximum = max;
+ bnd.exclusive = $(bound).find("maximum").attr("exclusive");
+ }
+ domain.bounds.push(bnd);
+ },
+ domain,
+ );
+
+ return domain;
+ },
+
+ /* Serialize the model to XML */
+ serialize: function () {
+ var objectDOM = this.updateDOM();
+ var xmlString = objectDOM.outerHTML;
+
+ // Camel-case the XML
+ xmlString = this.formatXML(xmlString);
+
+ return xmlString;
+ },
+
+ /* Copy the original XML DOM and update it with new values from the model */
+ updateDOM: function (objectDOM) {
+ var nodeToInsertAfter;
+ var type = this.get("type") || "datetime";
+ if (!objectDOM) {
+ objectDOM = this.get("objectDOM");
+ }
+ var objectXML = this.get("objectXML");
+
+ // If present, use the cached DOM
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+
+ // otherwise, use the cached XML
+ } else if (objectXML) {
+ objectDOM = $(objectXML)[0].cloneNode(true);
+
+ // This is new, create it
+ } else {
+ objectDOM = document.createElement(type);
+ }
+
+ // Update the formatString
+ if (this.get("formatString")) {
+ if ($(objectDOM).find("formatstring").length) {
+ $(objectDOM).find("formatstring").text(this.get("formatString"));
+ } else {
+ nodeToInsertAfter = this.getEMLPosition(objectDOM, "formatString");
+
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(
+ $(document.createElement("formatstring")).text(
+ this.get("formatString"),
+ )[0],
+ );
+ } else {
+ $(nodeToInsertAfter).after(
+ $(document.createElement("formatstring")).text(
+ this.get("formatString"),
+ )[0],
+ );
+ }
+ }
+ }
+ /* TODO: Uncomment out when formatstrings are better supported
else{
$(objectDOM).find("formatstring").remove();
}
*/
- // Update the dateTimePrecision
- if ( this.get("dateTimePrecision") ) {
- if ( $(objectDOM).find("datetimeprecision").length ) {
- $(objectDOM).find("datetimeprecision").text(this.get("dateTimePrecision"));
- } else {
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "dateTimePrecision");
-
- if( ! nodeToInsertAfter ) {
- $(objectDOM).append($(document.createElement("datetimeprecision"))
- .text(this.get("dateTimePrecision"))[0]);
- } else {
- $(nodeToInsertAfter).after(
- $(document.createElement("datetimeprecision"))
- .text(this.get("dateTimePrecision"))[0]
- );
- }
- }
- }
- /* TODO: Uncomment out when datetimeprecision if better supported
+ // Update the dateTimePrecision
+ if (this.get("dateTimePrecision")) {
+ if ($(objectDOM).find("datetimeprecision").length) {
+ $(objectDOM)
+ .find("datetimeprecision")
+ .text(this.get("dateTimePrecision"));
+ } else {
+ nodeToInsertAfter = this.getEMLPosition(
+ objectDOM,
+ "dateTimePrecision",
+ );
+
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(
+ $(document.createElement("datetimeprecision")).text(
+ this.get("dateTimePrecision"),
+ )[0],
+ );
+ } else {
+ $(nodeToInsertAfter).after(
+ $(document.createElement("datetimeprecision")).text(
+ this.get("dateTimePrecision"),
+ )[0],
+ );
+ }
+ }
+ }
+ /* TODO: Uncomment out when datetimeprecision if better supported
else{
$(objectDOM).find("datetimeprecision").remove();
}
*/
- // Update the dateTimeDomain
- var dateTimeDomain = this.get("dateTimeDomain");
- var dateTimeDomainNode = $(objectDOM).find("datetimedomain")[0];
- var minBound;
- var maxBound;
- var boundsNode;
- var minBoundNode;
- var maxBoundNode;
- if ( dateTimeDomain ) {
-
- // Remove the existing dateTimeDomain node
- if ( typeof dateTimeDomainNode !== "undefined" ) {
- dateTimeDomainNode.remove();
- }
-
- // Do we have bounds?
- if ( typeof dateTimeDomain.bounds !== "undefined" &&
- dateTimeDomain.bounds.length ) {
- // Build the new dateTimeDomain node
- dateTimeDomainNode = document.createElement("datetimedomain");
-
- _.each(dateTimeDomain.bounds, function(bound) {
- minBound = bound.minimum;
- maxBound = bound.maximum;
- boundsNode = document.createElement("bounds");
- var hasBounds = typeof minBound !== "undefined" || typeof maxBound !== "undefined";
- if ( hasBounds ) {
- // Populate the minimum element
- if ( typeof minBound !== "undefined" ) {
- minBoundNode = $(document.createElement("minimum"));
- minBoundNode.text(minBound);
-
- if(bound.exclusive === true || bound.exclusive == "true")
- minBoundNode.attr("exclusive", "true");
- else
- minBoundNode.attr("exclusive", "false");
-
- $(boundsNode).append(minBoundNode);
- }
-
- // Populate the maximum element
- if ( typeof maxBound !== "undefined" ) {
- maxBoundNode = $(document.createElement("maximum"));
- maxBoundNode.text(maxBound);
-
- if(bound.exclusive === true || bound.exclusive == "true")
- maxBoundNode.attr("exclusive", "true");
- else
- maxBoundNode.attr("exclusive", "false");
-
- $(boundsNode).append(maxBoundNode);
- }
-
- //If the bounds are populated, append it to the date time domain node
- if( $(boundsNode).children().length > 0 )
- $(dateTimeDomainNode).append(boundsNode);
-
- } else {
- // Do nothing. Content is missing, don't append the node
- }
- });
- } else {
- // Basically do nothing. Don't append the dateTimeDomain element
- // TODO: handle dateTimeDomain.references
-
- }
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "dateTimeDomain");
-
- if( ! nodeToInsertAfter ) {
- $(objectDOM).append(dateTimeDomainNode);
- } else {
- $(nodeToInsertAfter).after(dateTimeDomainNode);
- }
+ // Update the dateTimeDomain
+ var dateTimeDomain = this.get("dateTimeDomain");
+ var dateTimeDomainNode = $(objectDOM).find("datetimedomain")[0];
+ var minBound;
+ var maxBound;
+ var boundsNode;
+ var minBoundNode;
+ var maxBoundNode;
+ if (dateTimeDomain) {
+ // Remove the existing dateTimeDomain node
+ if (typeof dateTimeDomainNode !== "undefined") {
+ dateTimeDomainNode.remove();
+ }
+
+ // Do we have bounds?
+ if (
+ typeof dateTimeDomain.bounds !== "undefined" &&
+ dateTimeDomain.bounds.length
+ ) {
+ // Build the new dateTimeDomain node
+ dateTimeDomainNode = document.createElement("datetimedomain");
+
+ _.each(dateTimeDomain.bounds, function (bound) {
+ minBound = bound.minimum;
+ maxBound = bound.maximum;
+ boundsNode = document.createElement("bounds");
+ var hasBounds =
+ typeof minBound !== "undefined" ||
+ typeof maxBound !== "undefined";
+ if (hasBounds) {
+ // Populate the minimum element
+ if (typeof minBound !== "undefined") {
+ minBoundNode = $(document.createElement("minimum"));
+ minBoundNode.text(minBound);
+
+ if (bound.exclusive === true || bound.exclusive == "true")
+ minBoundNode.attr("exclusive", "true");
+ else minBoundNode.attr("exclusive", "false");
+
+ $(boundsNode).append(minBoundNode);
}
- return objectDOM;
- },
-
- formatXML: function(xmlString){
- return DataONEObject.prototype.formatXML.call(this, xmlString);
- },
- /**/
- getEMLPosition: function(objectDOM, nodeName) {
- var nodeOrder = ["formatString", "dateTimePrecision", "dateTimeDomain"];
+ // Populate the maximum element
+ if (typeof maxBound !== "undefined") {
+ maxBoundNode = $(document.createElement("maximum"));
+ maxBoundNode.text(maxBound);
- var position = _.indexOf(nodeOrder, nodeName);
+ if (bound.exclusive === true || bound.exclusive == "true")
+ maxBoundNode.attr("exclusive", "true");
+ else maxBoundNode.attr("exclusive", "false");
- // Append to the bottom if not found
- if ( position == -1 ) {
- return $(objectDOM).children().last()[0];
+ $(boundsNode).append(maxBoundNode);
}
- // Otherwise, go through each node in the node list and find the
- // position where this node will be inserted after
- for ( var i = position - 1; i >= 0; i-- ) {
- if ( $(objectDOM).find(nodeOrder[i]).length ) {
- return $(objectDOM).find(nodeOrder[i]).last()[0];
- }
- }
- },
-
- /*
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211 or false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
- var emlModel = this.get("parentModel"),
- tries = 0;
-
- while (emlModel.type !== "EML" && tries < 6){
- emlModel = emlModel.get("parentModel");
- tries++;
+ //If the bounds are populated, append it to the date time domain node
+ if ($(boundsNode).children().length > 0)
+ $(dateTimeDomainNode).append(boundsNode);
+ } else {
+ // Do nothing. Content is missing, don't append the node
}
-
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
-
- },
-
- /* Let the top level package know of attribute changes from this object */
- trickleUpChange: function(){
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- },
-
- /* Validate the values of this model */
- validate: function(){
- if( !this.get("formatString") )
- return { formatString: "Choose a date-time format." }
- else{
-
- this.trigger("valid");
- return;
-
- }
- }
-
- });
-
- return EMLDateTimeDomain;
- }
-);
+ });
+ } else {
+ // Basically do nothing. Don't append the dateTimeDomain element
+ // TODO: handle dateTimeDomain.references
+ }
+ nodeToInsertAfter = this.getEMLPosition(objectDOM, "dateTimeDomain");
+
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(dateTimeDomainNode);
+ } else {
+ $(nodeToInsertAfter).after(dateTimeDomainNode);
+ }
+ }
+ return objectDOM;
+ },
+
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
+
+ /**/
+ getEMLPosition: function (objectDOM, nodeName) {
+ var nodeOrder = ["formatString", "dateTimePrecision", "dateTimeDomain"];
+
+ var position = _.indexOf(nodeOrder, nodeName);
+
+ // Append to the bottom if not found
+ if (position == -1) {
+ return $(objectDOM).children().last()[0];
+ }
+
+ // Otherwise, go through each node in the node list and find the
+ // position where this node will be inserted after
+ for (var i = position - 1; i >= 0; i--) {
+ if ($(objectDOM).find(nodeOrder[i]).length) {
+ return $(objectDOM).find(nodeOrder[i]).last()[0];
+ }
+ }
+ },
+
+ /*
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211 or false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
+ var emlModel = this.get("parentModel"),
+ tries = 0;
+
+ while (emlModel.type !== "EML" && tries < 6) {
+ emlModel = emlModel.get("parentModel");
+ tries++;
+ }
+
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
+ },
+
+ /* Let the top level package know of attribute changes from this object */
+ trickleUpChange: function () {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ },
+
+ /* Validate the values of this model */
+ validate: function () {
+ if (!this.get("formatString"))
+ return { formatString: "Choose a date-time format." };
+ else {
+ this.trigger("valid");
+ return;
+ }
+ },
+ },
+ );
+
+ return EMLDateTimeDomain;
+});
diff --git a/src/js/models/metadata/eml211/EMLDistribution.js b/src/js/models/metadata/eml211/EMLDistribution.js
index 29ccf2753..848c6d725 100644
--- a/src/js/models/metadata/eml211/EMLDistribution.js
+++ b/src/js/models/metadata/eml211/EMLDistribution.js
@@ -3,7 +3,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
$,
_,
Backbone,
- DataONEObject
+ DataONEObject,
) {
/**
* @class EMLDistribution
@@ -16,7 +16,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
* @constructor
*/
var EMLDistribution = Backbone.Model.extend(
- /** @lends EMLDistribution.prototype */{
+ /** @lends EMLDistribution.prototype */ {
/**
* Default values for an EML 211 Distribution model. This is essentially a
* flattened version of the EML 2.1.1 DistributionType, including nodes and
@@ -73,7 +73,12 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
* @type {string[]}
* @since 2.26.0
*/
- offlineNodes: ["mediumname", "mediumvolume", "mediumformat", "mediumnote"],
+ offlineNodes: [
+ "mediumname",
+ "mediumvolume",
+ "mediumformat",
+ "mediumnote",
+ ],
/**
* lower-case EML node names that belong within the node. These
@@ -100,7 +105,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
this.listenTo(
this,
"change:" + nodeAttr.join(" change:"),
- this.trickleUpChange
+ this.trickleUpChange,
);
},
@@ -250,7 +255,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
// Add the urlFunction attribute if one is set in the model. Remove it if
// it's not set.
- const url = $objectDOM.find("url")
+ const url = $objectDOM.find("url");
if (url) {
const urlFunction = this.get("urlFunction");
if (urlFunction) {
@@ -260,7 +265,6 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
}
}
-
return objectDOM;
},
@@ -324,7 +328,8 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
formatXML: function (xmlString) {
return DataONEObject.prototype.formatXML.call(this, xmlString);
},
- });
+ },
+ );
return EMLDistribution;
});
diff --git a/src/js/models/metadata/eml211/EMLEntity.js b/src/js/models/metadata/eml211/EMLEntity.js
index 2d1182b30..7e4c0e79d 100644
--- a/src/js/models/metadata/eml211/EMLEntity.js
+++ b/src/js/models/metadata/eml211/EMLEntity.js
@@ -1,532 +1,561 @@
-define(["jquery", "underscore", "backbone", "uuid", "models/DataONEObject",
- "models/metadata/eml211/EMLAttribute"],
- function($, _, Backbone, uuid, DataONEObject, EMLAttribute) {
-
- /**
- * @class EMLEntity
- * @classdesc EMLEntity represents an abstract data entity, corresponding
- * with the EML EntityGroup and other elements common to all
- * entity types, including otherEntity, dataTable, spatialVector,
- * spatialRaster, and storedProcedure
- * @classcategory Models/Metadata/EML211
- * @see https://eml.ecoinformatics.org/schema/eml-entity_xsd
- * @extends Backbone.Model
- */
- var EMLEntity = Backbone.Model.extend(
- /** @lends EMLEntity.prototype */{
-
- //The class name for this model
- type: "EMLEntity",
-
- /* Attributes of any entity */
- defaults: function(){
- return {
- /* Attributes from EML */
- xmlID: null, // The XML id of the entity
- alternateIdentifier: [], // Zero or more alt ids
- entityName: null, // Required, the name of the entity
- entityDescription: null, // Description of the entity
- physical: [], // Zero to many EMLPhysical objects
- physicalMD5Checksum: null,
- physicalSize: null,
- physicalObjectName: null,
- coverage: [], // Zero to many EML{Geo|Taxon|Temporal}Coverage objects
- methods: null, // Zero or one EMLMethod object
- additionalInfo: [], // Zero to many EMLText objects
- attributeList: [], // Zero to many EMLAttribute objects
- constraint: [], // Zero to many EMLConstraint objects
- references: null, // A reference to another EMLEntity by id (needs work)
-
- //Temporary attribute until we implement the eml-physical module
- downloadID: null,
- formatName: null,
-
- /* Attributes not from EML */
- nodeOrder: [ // The order of the top level XML element nodes
- "alternateIdentifier",
- "entityName",
- "entityDescription",
- "physical",
- "coverage",
- "methods",
- "additionalInfo",
- "annotation",
- "attributeList",
- "constraint"
- ],
- parentModel: null, // The parent model this entity belongs to
- dataONEObject: null, //Reference to the DataONEObject this EMLEntity describes
- objectXML: null, // The serialized XML of this EML entity
- objectDOM: null, // The DOM of this EML entity
- type: "otherentity"
- }
- },
-
- /*
- * The map of lower case to camel case node names
- * needed to deal with parsing issues with $.parseHTML().
- * Use this until we can figure out issues with $.parseXML().
- */
- nodeNameMap: {
- "alternateidentifier": "alternateIdentifier",
- "entityname": "entityName",
- "entitydescription": "entityDescription",
- "additionalinfo": "additionalInfo",
- "attributelist": "attributeList"
- },
-
- /* Initialize an EMLEntity object */
- initialize: function(attributes, options) {
-
- // if options.parse = true, Backbone will call parse()
-
- // Register change events
- this.on(
- "change:alternateIdentifier " +
- "change:entityName " +
- "change:entityDescription " +
- "change:physical " +
- "change:coverage " +
- "change:methods " +
- "change:additionalInfo " +
- "change:attributeList " +
- "change:constraint " +
- "change:references",
- EMLEntity.trickleUpChange);
-
- //Listen to changes on the DataONEObject file name
- if(this.get("dataONEObject")){
- this.listenTo(this.get("dataONEObject"), "change:fileName", this.updateFileName);
- }
-
- //Listen to changes on the DataONEObject to reset the listener
- this.on("change:dataONEObject", function(entity, dataONEObj){
-
- //Stop listening to the old DataONEObject
- if(this.previous("dataONEObject")){
- this.stopListening(this.previous("dataONEObject"), "change:fileName");
- }
-
- //Listen to changes on the file name
- this.listenTo(dataONEObj, "change:fileName", this.updateFileName);
- });
-
- },
-
- /*
- * Parse the incoming entity's common XML elements
- * Content example:
- *
- * file-alt.1.1.txt
- * file-again.1.1.txt
- * file.1.1.txt
- * A file summary
- *
- */
- parse: function(attributes, options) {
- var $objectDOM;
- var objectDOM = attributes.objectDOM;
- var objectXML = attributes.objectXML;
-
- // Use the cached object if we have it
- if ( objectDOM ) {
- $objectDOM = $(objectDOM);
- } else if ( objectXML ) {
- $objectDOM = $(objectXML);
- }
-
- // Add the XML id
- attributes.xmlID = $objectDOM.attr("id");
-
- // Add the alternateIdentifiers
- attributes.alternateIdentifier = [];
- var alternateIds = $objectDOM.children("alternateidentifier");
- _.each(alternateIds, function(alternateId) {
- attributes.alternateIdentifier.push(alternateId.textContent);
- });
-
- // Add the entityName
- attributes.entityName = $objectDOM.children("entityname").text();
-
- // Add the entityDescription
- attributes.entityDescription = $objectDOM.children("entitydescription").text();
-
- //Get some physical attributes from the EMLPhysical module
- var physical = $objectDOM.find("physical");
- if(physical){
- attributes.physicalSize = physical.find("size").text();
- attributes.physicalObjectName = physical.find("objectname").text();
-
- var checksumType = physical.find("authentication").attr("method");
- if(checksumType == "MD5")
- attributes.physicalMD5Checksum = physical.find("authentication").text();
- }
-
- attributes.objectXML = objectXML;
- attributes.objectDOM = $objectDOM[0];
-
- //Find the id from the download distribution URL
- var urlNode = $objectDOM.find("url");
- if(urlNode.length){
- var downloadURL = urlNode.text(),
- downloadID = "";
-
- if( downloadURL.indexOf("/resolve/") > -1 )
- downloadID = downloadURL.substring( downloadURL.indexOf("/resolve/") + 9 );
- else if( downloadURL.indexOf("/object/") > -1 )
- downloadID = downloadURL.substring( downloadURL.indexOf("/object/") + 8 );
- else if( downloadURL.indexOf("ecogrid") > -1 ){
- var withoutEcoGridPrefix = downloadURL.substring( downloadURL.indexOf("ecogrid://") + 10 ),
- downloadID = withoutEcoGridPrefix.substring( withoutEcoGridPrefix.indexOf("/")+1 );
- }
-
-
- if(downloadID.length)
- attributes.downloadID = downloadID;
- }
-
- //Find the format name
- var formatNode = $objectDOM.find("formatName");
- if(formatNode.length){
- attributes.formatName = formatNode.text();
- }
-
- // Add the attributeList
- var attributeList = $objectDOM.find("attributelist");
- var attribute; // An individual EML attribute
- var options = {parse: true};
- attributes.attributeList = [];
- if ( attributeList.length ) {
- _.each(attributeList[0].children, function(attr) {
- attribute = new EMLAttribute(
- {
- objectDOM: attr,
- objectXML: attr.outerHTML,
- parentModel: this
- }, options);
- // Can't use this.addAttribute() here (no this yet)
- attributes.attributeList.push(attribute);
- }, this);
-
- }
- return attributes;
- },
-
- /*
- * Add an attribute to the attributeList, inserting it
- * at the zero-based index
- */
- addAttribute: function(attribute, index) {
- if ( typeof index == "undefined" ) {
- this.get("attributeList").push(attribute);
- } else {
- this.get("attributeList").splice(index, attribute);
- }
-
- this.trigger("change:attributeList");
- },
-
- /*
- * Remove an EMLAttribute model from the attributeList array
- *
- * @param {EMLAttribute} - The EMLAttribute model to remove from this model's attributeList
- */
- removeAttribute: function(attribute) {
-
- //Get the index of the EMLAttribute in the array
- var attrIndex = this.get("attributeList").indexOf(attribute);
-
- //If this attribute model does not exist in the attribute list, don't do anything
- if( attrIndex == -1 ){
- return;
- }
-
- //Remove that index from the array
- this.get("attributeList").splice(attrIndex, 1);
-
- //Trickle the change up the model chain
- this.trickleUpChange();
- },
-
- /* Validate the top level EMLEntity fields */
- validate: function() {
- var errors = {};
-
- // will be run by calls to isValid()
- if ( ! this.get("entityName") ) {
- errors.entityName = "An entity name is required.";
- }
-
- //Validate the attributes
- var attributeErrors = this.validateAttributes();
- if(attributeErrors.length)
- errors.attributeList = attributeErrors;
-
- if( Object.keys(errors).length )
- return errors;
- else{
- this.trigger("valid");
- return false;
- }
-
- },
-
- /*
- * Validates each of the EMLAttribute models in the attributeList
- *
- * @return {Array} - Returns an array of error messages for all the EMlAttribute models
- */
- validateAttributes: function(){
- var errors = [];
-
- //Validate each of the EMLAttributes
- _.each( this.get("attributeList"), function(attribute){
-
- if( !attribute.isValid() ){
- errors.push(attribute.validationError);
- }
-
- });
-
- return errors;
- },
-
- /* Copy the original XML and update fields in a DOM object */
- updateDOM: function(objectDOM) {
- var nodeToInsertAfter;
- var type = this.get("type") || "otherEntity";
- if ( ! objectDOM ) {
- objectDOM = this.get("objectDOM");
- }
- var objectXML = this.get("objectXML");
-
- // If present, use the cached DOM
- if ( objectDOM ) {
- objectDOM = objectDOM.cloneNode(true);
-
- // otherwise, use the cached XML
- } else if ( objectXML ){
- objectDOM = $(objectXML)[0].cloneNode(true);
-
- // This is new, create it
- } else {
- objectDOM = document.createElement(type);
- }
-
- //Update the id attribute on this XML node
- // update the id attribute
- if( this.get("dataONEObject") ){
- //Ideally, the EMLEntity will use the object's id in it's id attribute, so we wil switch them
- var xmlID = this.get("dataONEObject").getXMLSafeID();
-
- //Set the xml-safe id on the model and use it as the id attribute
- $(objectDOM).attr("id", xmlID);
- this.set("xmlID", xmlID);
- }
- //If there isn't a matching DataONEObject but there is an id set on this model, use that id
- else if(this.get("xmlID")){
- $(objectDOM).attr("id", this.get("xmlID"));
- }
-
- // Update the alternateIdentifiers
- var altIDs = this.get("alternateIdentifier");
- if ( altIDs ) {
- if ( altIDs.length ) {
- // Copy and reverse the array for prepending
- altIDs = Array.from(altIDs).reverse();
- // Remove all current alternateIdentifiers
- $(objectDOM).find("alternateIdentifier").remove();
- // Add the new list back in
- _.each(altIDs, function(altID) {
- $(objectDOM).prepend(
- $(document.createElement("alternateIdentifier"))
- .text(altID));
- });
- }
- }
- else{
-
- // Remove all current alternateIdentifiers
- $(objectDOM).find("alternateIdentifier").remove();
-
- }
-
- // Update the entityName
- if ( this.get("entityName") ) {
- if ( $(objectDOM).find("entityName").length ) {
- $(objectDOM).find("entityName").text(this.get("entityName"));
-
- } else {
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "entityName");
- if ( ! nodeToInsertAfter ) {
- $(objectDOM).append($(document.createElement("entityName"))
- .text(this.get("entityName"))[0]);
- } else {
- $(nodeToInsertAfter).after($(document.createElement("entityName"))
- .text(this.get("entityName"))[0]);
- }
- }
- }
-
- // Update the entityDescription
- if ( this.get("entityDescription") ) {
- if ( $(objectDOM).find("entityDescription").length ) {
- $(objectDOM).find("entityDescription").text(this.get("entityDescription"));
-
- } else {
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "entityDescription");
- if ( ! nodeToInsertAfter ) {
- $(objectDOM).append($(document.createElement("entityDescription"))
- .text(this.get("entityDescription"))[0]);
- } else {
- $(nodeToInsertAfter).after($(document.createElement("entityDescription"))
- .text(this.get("entityDescription"))[0]);
- }
- }
- }
- //If there is no entity description
- else{
-
- //If there is an entity description node in the XML, remove it
- $(objectDOM).find("entityDescription").remove();
-
- }
-
- // TODO: Update the physical section
-
- // TODO: Update the coverage section
-
- // TODO: Update the methods section
-
-
- // Update the additionalInfo
- var addInfos = this.get("additionalInfo");
- if ( addInfos ) {
- if ( addInfos.length ) {
- // Copy and reverse the array for prepending
- addInfos = Array.from(addInfos).reverse();
- // Remove all current alternateIdentifiers
- $(objectDOM).find("additionalInfo").remove();
- // Add the new list back in
- _.each(addInfos, function(additionalInfo) {
- $(objectDOM).prepend(
- document.createElement("additionalInfo")
- .text(additionalInfo));
- });
- }
- }
-
- // Update the attributeList section
- let attributeList = this.get("attributeList");
- let attributeListInDOM = $(objectDOM).children("attributelist");
- let attributeListNode;
- if ( attributeListInDOM.length ) {
- attributeListNode = attributeListInDOM[0];
- $(attributeListNode).children().remove(); // Each attr will be replaced
- } else {
- attributeListNode = document.createElement("attributeList");
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "attributeList");
- if( ! nodeToInsertAfter ) {
- $(objectDOM).append(attributeListNode);
- } else {
- $(nodeToInsertAfter).after(attributeListNode);
- }
- }
-
- var updatedAttrDOM;
- if ( attributeList.length ) {
- // Add each attribute
- _.each(attributeList, function(attribute) {
- updatedAttrDOM = attribute.updateDOM();
- $(attributeListNode).append(updatedAttrDOM);
- }, this);
- } else {
- // Attributes are not defined, remove them from the DOM
- attributeListNode.remove();
- }
-
- // TODO: Update the constraint section
-
- return objectDOM;
- },
-
- /**
- * Update the file name in the EML
- */
- updateFileName: function(){
-
- var dataONEObj = this.get("dataONEObject");
-
- //Get the DataONEObject model associated with this EML Entity
- if(dataONEObj){
- //If the last file name matched the EML entity name, then update it
- if( dataONEObj.previous("fileName") == this.get("entityName") ){
- this.set("entityName", dataONEObj.get("fileName"));
- }
- //If the DataONEObject doesn't have an old file name or entity name, then update it
- else if( !dataONEObj.previous("fileName") || !this.get("entityName") ){
- this.set("entityName", dataONEObj.get("fileName"));
- }
- }
-
- },
-
- /*
- * Get the DOM node preceding the given nodeName
- * to find what position in the EML document
- * the named node should be appended
- */
- getEMLPosition: function(objectDOM, nodeName) {
- var nodeOrder = this.get("nodeOrder");
-
- var position = _.indexOf(nodeOrder, nodeName);
-
- // Append to the bottom if not found
- if ( position == -1 ) {
- return $(objectDOM).children().last()[0];
- }
-
- // Otherwise, go through each node in the node list and find the
- // position where this node will be inserted after
- for ( var i = position - 1; i >= 0; i-- ) {
- if ( $(objectDOM).find( nodeOrder[i].toLowerCase() ).length ) {
- return $(objectDOM).find(nodeOrder[i].toLowerCase()).last()[0];
- }
- }
- },
-
- /*
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211 or false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
- var emlModel = this.get("parentModel"),
- tries = 0;
-
- while (emlModel.type !== "EML" && tries < 6){
- emlModel = emlModel.get("parentModel");
- tries++;
- }
-
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
-
- },
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "uuid",
+ "models/DataONEObject",
+ "models/metadata/eml211/EMLAttribute",
+], function ($, _, Backbone, uuid, DataONEObject, EMLAttribute) {
+ /**
+ * @class EMLEntity
+ * @classdesc EMLEntity represents an abstract data entity, corresponding
+ * with the EML EntityGroup and other elements common to all
+ * entity types, including otherEntity, dataTable, spatialVector,
+ * spatialRaster, and storedProcedure
+ * @classcategory Models/Metadata/EML211
+ * @see https://eml.ecoinformatics.org/schema/eml-entity_xsd
+ * @extends Backbone.Model
+ */
+ var EMLEntity = Backbone.Model.extend(
+ /** @lends EMLEntity.prototype */ {
+ //The class name for this model
+ type: "EMLEntity",
+
+ /* Attributes of any entity */
+ defaults: function () {
+ return {
+ /* Attributes from EML */
+ xmlID: null, // The XML id of the entity
+ alternateIdentifier: [], // Zero or more alt ids
+ entityName: null, // Required, the name of the entity
+ entityDescription: null, // Description of the entity
+ physical: [], // Zero to many EMLPhysical objects
+ physicalMD5Checksum: null,
+ physicalSize: null,
+ physicalObjectName: null,
+ coverage: [], // Zero to many EML{Geo|Taxon|Temporal}Coverage objects
+ methods: null, // Zero or one EMLMethod object
+ additionalInfo: [], // Zero to many EMLText objects
+ attributeList: [], // Zero to many EMLAttribute objects
+ constraint: [], // Zero to many EMLConstraint objects
+ references: null, // A reference to another EMLEntity by id (needs work)
+
+ //Temporary attribute until we implement the eml-physical module
+ downloadID: null,
+ formatName: null,
+
+ /* Attributes not from EML */
+ nodeOrder: [
+ // The order of the top level XML element nodes
+ "alternateIdentifier",
+ "entityName",
+ "entityDescription",
+ "physical",
+ "coverage",
+ "methods",
+ "additionalInfo",
+ "annotation",
+ "attributeList",
+ "constraint",
+ ],
+ parentModel: null, // The parent model this entity belongs to
+ dataONEObject: null, //Reference to the DataONEObject this EMLEntity describes
+ objectXML: null, // The serialized XML of this EML entity
+ objectDOM: null, // The DOM of this EML entity
+ type: "otherentity",
+ };
+ },
+
+ /*
+ * The map of lower case to camel case node names
+ * needed to deal with parsing issues with $.parseHTML().
+ * Use this until we can figure out issues with $.parseXML().
+ */
+ nodeNameMap: {
+ alternateidentifier: "alternateIdentifier",
+ entityname: "entityName",
+ entitydescription: "entityDescription",
+ additionalinfo: "additionalInfo",
+ attributelist: "attributeList",
+ },
+
+ /* Initialize an EMLEntity object */
+ initialize: function (attributes, options) {
+ // if options.parse = true, Backbone will call parse()
+
+ // Register change events
+ this.on(
+ "change:alternateIdentifier " +
+ "change:entityName " +
+ "change:entityDescription " +
+ "change:physical " +
+ "change:coverage " +
+ "change:methods " +
+ "change:additionalInfo " +
+ "change:attributeList " +
+ "change:constraint " +
+ "change:references",
+ EMLEntity.trickleUpChange,
+ );
+
+ //Listen to changes on the DataONEObject file name
+ if (this.get("dataONEObject")) {
+ this.listenTo(
+ this.get("dataONEObject"),
+ "change:fileName",
+ this.updateFileName,
+ );
+ }
+
+ //Listen to changes on the DataONEObject to reset the listener
+ this.on("change:dataONEObject", function (entity, dataONEObj) {
+ //Stop listening to the old DataONEObject
+ if (this.previous("dataONEObject")) {
+ this.stopListening(
+ this.previous("dataONEObject"),
+ "change:fileName",
+ );
+ }
+
+ //Listen to changes on the file name
+ this.listenTo(dataONEObj, "change:fileName", this.updateFileName);
+ });
+ },
+
+ /*
+ * Parse the incoming entity's common XML elements
+ * Content example:
+ *
+ * file-alt.1.1.txt
+ * file-again.1.1.txt
+ * file.1.1.txt
+ * A file summary
+ *
+ */
+ parse: function (attributes, options) {
+ var $objectDOM;
+ var objectDOM = attributes.objectDOM;
+ var objectXML = attributes.objectXML;
+
+ // Use the cached object if we have it
+ if (objectDOM) {
+ $objectDOM = $(objectDOM);
+ } else if (objectXML) {
+ $objectDOM = $(objectXML);
+ }
+
+ // Add the XML id
+ attributes.xmlID = $objectDOM.attr("id");
+
+ // Add the alternateIdentifiers
+ attributes.alternateIdentifier = [];
+ var alternateIds = $objectDOM.children("alternateidentifier");
+ _.each(alternateIds, function (alternateId) {
+ attributes.alternateIdentifier.push(alternateId.textContent);
+ });
- /*Format the EML XML for entities*/
- formatXML: function(xmlString){
- return DataONEObject.prototype.formatXML.call(this, xmlString);
+ // Add the entityName
+ attributes.entityName = $objectDOM.children("entityname").text();
+
+ // Add the entityDescription
+ attributes.entityDescription = $objectDOM
+ .children("entitydescription")
+ .text();
+
+ //Get some physical attributes from the EMLPhysical module
+ var physical = $objectDOM.find("physical");
+ if (physical) {
+ attributes.physicalSize = physical.find("size").text();
+ attributes.physicalObjectName = physical.find("objectname").text();
+
+ var checksumType = physical.find("authentication").attr("method");
+ if (checksumType == "MD5")
+ attributes.physicalMD5Checksum = physical
+ .find("authentication")
+ .text();
+ }
+
+ attributes.objectXML = objectXML;
+ attributes.objectDOM = $objectDOM[0];
+
+ //Find the id from the download distribution URL
+ var urlNode = $objectDOM.find("url");
+ if (urlNode.length) {
+ var downloadURL = urlNode.text(),
+ downloadID = "";
+
+ if (downloadURL.indexOf("/resolve/") > -1)
+ downloadID = downloadURL.substring(
+ downloadURL.indexOf("/resolve/") + 9,
+ );
+ else if (downloadURL.indexOf("/object/") > -1)
+ downloadID = downloadURL.substring(
+ downloadURL.indexOf("/object/") + 8,
+ );
+ else if (downloadURL.indexOf("ecogrid") > -1) {
+ var withoutEcoGridPrefix = downloadURL.substring(
+ downloadURL.indexOf("ecogrid://") + 10,
+ ),
+ downloadID = withoutEcoGridPrefix.substring(
+ withoutEcoGridPrefix.indexOf("/") + 1,
+ );
+ }
+
+ if (downloadID.length) attributes.downloadID = downloadID;
+ }
+
+ //Find the format name
+ var formatNode = $objectDOM.find("formatName");
+ if (formatNode.length) {
+ attributes.formatName = formatNode.text();
+ }
+
+ // Add the attributeList
+ var attributeList = $objectDOM.find("attributelist");
+ var attribute; // An individual EML attribute
+ var options = { parse: true };
+ attributes.attributeList = [];
+ if (attributeList.length) {
+ _.each(
+ attributeList[0].children,
+ function (attr) {
+ attribute = new EMLAttribute(
+ {
+ objectDOM: attr,
+ objectXML: attr.outerHTML,
+ parentModel: this,
+ },
+ options,
+ );
+ // Can't use this.addAttribute() here (no this yet)
+ attributes.attributeList.push(attribute);
},
-
- /* Let the top level package know of attribute changes from this object */
- trickleUpChange: function(){
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- }
+ this,
+ );
+ }
+ return attributes;
+ },
+
+ /*
+ * Add an attribute to the attributeList, inserting it
+ * at the zero-based index
+ */
+ addAttribute: function (attribute, index) {
+ if (typeof index == "undefined") {
+ this.get("attributeList").push(attribute);
+ } else {
+ this.get("attributeList").splice(index, attribute);
+ }
+
+ this.trigger("change:attributeList");
+ },
+
+ /*
+ * Remove an EMLAttribute model from the attributeList array
+ *
+ * @param {EMLAttribute} - The EMLAttribute model to remove from this model's attributeList
+ */
+ removeAttribute: function (attribute) {
+ //Get the index of the EMLAttribute in the array
+ var attrIndex = this.get("attributeList").indexOf(attribute);
+
+ //If this attribute model does not exist in the attribute list, don't do anything
+ if (attrIndex == -1) {
+ return;
+ }
+
+ //Remove that index from the array
+ this.get("attributeList").splice(attrIndex, 1);
+
+ //Trickle the change up the model chain
+ this.trickleUpChange();
+ },
+
+ /* Validate the top level EMLEntity fields */
+ validate: function () {
+ var errors = {};
+
+ // will be run by calls to isValid()
+ if (!this.get("entityName")) {
+ errors.entityName = "An entity name is required.";
+ }
+
+ //Validate the attributes
+ var attributeErrors = this.validateAttributes();
+ if (attributeErrors.length) errors.attributeList = attributeErrors;
+
+ if (Object.keys(errors).length) return errors;
+ else {
+ this.trigger("valid");
+ return false;
+ }
+ },
+
+ /*
+ * Validates each of the EMLAttribute models in the attributeList
+ *
+ * @return {Array} - Returns an array of error messages for all the EMlAttribute models
+ */
+ validateAttributes: function () {
+ var errors = [];
+
+ //Validate each of the EMLAttributes
+ _.each(this.get("attributeList"), function (attribute) {
+ if (!attribute.isValid()) {
+ errors.push(attribute.validationError);
+ }
});
- return EMLEntity;
- }
-);
+ return errors;
+ },
+
+ /* Copy the original XML and update fields in a DOM object */
+ updateDOM: function (objectDOM) {
+ var nodeToInsertAfter;
+ var type = this.get("type") || "otherEntity";
+ if (!objectDOM) {
+ objectDOM = this.get("objectDOM");
+ }
+ var objectXML = this.get("objectXML");
+
+ // If present, use the cached DOM
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+
+ // otherwise, use the cached XML
+ } else if (objectXML) {
+ objectDOM = $(objectXML)[0].cloneNode(true);
+
+ // This is new, create it
+ } else {
+ objectDOM = document.createElement(type);
+ }
+
+ //Update the id attribute on this XML node
+ // update the id attribute
+ if (this.get("dataONEObject")) {
+ //Ideally, the EMLEntity will use the object's id in it's id attribute, so we wil switch them
+ var xmlID = this.get("dataONEObject").getXMLSafeID();
+
+ //Set the xml-safe id on the model and use it as the id attribute
+ $(objectDOM).attr("id", xmlID);
+ this.set("xmlID", xmlID);
+ }
+ //If there isn't a matching DataONEObject but there is an id set on this model, use that id
+ else if (this.get("xmlID")) {
+ $(objectDOM).attr("id", this.get("xmlID"));
+ }
+
+ // Update the alternateIdentifiers
+ var altIDs = this.get("alternateIdentifier");
+ if (altIDs) {
+ if (altIDs.length) {
+ // Copy and reverse the array for prepending
+ altIDs = Array.from(altIDs).reverse();
+ // Remove all current alternateIdentifiers
+ $(objectDOM).find("alternateIdentifier").remove();
+ // Add the new list back in
+ _.each(altIDs, function (altID) {
+ $(objectDOM).prepend(
+ $(document.createElement("alternateIdentifier")).text(altID),
+ );
+ });
+ }
+ } else {
+ // Remove all current alternateIdentifiers
+ $(objectDOM).find("alternateIdentifier").remove();
+ }
+
+ // Update the entityName
+ if (this.get("entityName")) {
+ if ($(objectDOM).find("entityName").length) {
+ $(objectDOM).find("entityName").text(this.get("entityName"));
+ } else {
+ nodeToInsertAfter = this.getEMLPosition(objectDOM, "entityName");
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(
+ $(document.createElement("entityName")).text(
+ this.get("entityName"),
+ )[0],
+ );
+ } else {
+ $(nodeToInsertAfter).after(
+ $(document.createElement("entityName")).text(
+ this.get("entityName"),
+ )[0],
+ );
+ }
+ }
+ }
+
+ // Update the entityDescription
+ if (this.get("entityDescription")) {
+ if ($(objectDOM).find("entityDescription").length) {
+ $(objectDOM)
+ .find("entityDescription")
+ .text(this.get("entityDescription"));
+ } else {
+ nodeToInsertAfter = this.getEMLPosition(
+ objectDOM,
+ "entityDescription",
+ );
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(
+ $(document.createElement("entityDescription")).text(
+ this.get("entityDescription"),
+ )[0],
+ );
+ } else {
+ $(nodeToInsertAfter).after(
+ $(document.createElement("entityDescription")).text(
+ this.get("entityDescription"),
+ )[0],
+ );
+ }
+ }
+ }
+ //If there is no entity description
+ else {
+ //If there is an entity description node in the XML, remove it
+ $(objectDOM).find("entityDescription").remove();
+ }
+
+ // TODO: Update the physical section
+
+ // TODO: Update the coverage section
+
+ // TODO: Update the methods section
+
+ // Update the additionalInfo
+ var addInfos = this.get("additionalInfo");
+ if (addInfos) {
+ if (addInfos.length) {
+ // Copy and reverse the array for prepending
+ addInfos = Array.from(addInfos).reverse();
+ // Remove all current alternateIdentifiers
+ $(objectDOM).find("additionalInfo").remove();
+ // Add the new list back in
+ _.each(addInfos, function (additionalInfo) {
+ $(objectDOM).prepend(
+ document.createElement("additionalInfo").text(additionalInfo),
+ );
+ });
+ }
+ }
+
+ // Update the attributeList section
+ let attributeList = this.get("attributeList");
+ let attributeListInDOM = $(objectDOM).children("attributelist");
+ let attributeListNode;
+ if (attributeListInDOM.length) {
+ attributeListNode = attributeListInDOM[0];
+ $(attributeListNode).children().remove(); // Each attr will be replaced
+ } else {
+ attributeListNode = document.createElement("attributeList");
+ nodeToInsertAfter = this.getEMLPosition(objectDOM, "attributeList");
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(attributeListNode);
+ } else {
+ $(nodeToInsertAfter).after(attributeListNode);
+ }
+ }
+
+ var updatedAttrDOM;
+ if (attributeList.length) {
+ // Add each attribute
+ _.each(
+ attributeList,
+ function (attribute) {
+ updatedAttrDOM = attribute.updateDOM();
+ $(attributeListNode).append(updatedAttrDOM);
+ },
+ this,
+ );
+ } else {
+ // Attributes are not defined, remove them from the DOM
+ attributeListNode.remove();
+ }
+
+ // TODO: Update the constraint section
+
+ return objectDOM;
+ },
+
+ /**
+ * Update the file name in the EML
+ */
+ updateFileName: function () {
+ var dataONEObj = this.get("dataONEObject");
+
+ //Get the DataONEObject model associated with this EML Entity
+ if (dataONEObj) {
+ //If the last file name matched the EML entity name, then update it
+ if (dataONEObj.previous("fileName") == this.get("entityName")) {
+ this.set("entityName", dataONEObj.get("fileName"));
+ }
+ //If the DataONEObject doesn't have an old file name or entity name, then update it
+ else if (
+ !dataONEObj.previous("fileName") ||
+ !this.get("entityName")
+ ) {
+ this.set("entityName", dataONEObj.get("fileName"));
+ }
+ }
+ },
+
+ /*
+ * Get the DOM node preceding the given nodeName
+ * to find what position in the EML document
+ * the named node should be appended
+ */
+ getEMLPosition: function (objectDOM, nodeName) {
+ var nodeOrder = this.get("nodeOrder");
+
+ var position = _.indexOf(nodeOrder, nodeName);
+
+ // Append to the bottom if not found
+ if (position == -1) {
+ return $(objectDOM).children().last()[0];
+ }
+
+ // Otherwise, go through each node in the node list and find the
+ // position where this node will be inserted after
+ for (var i = position - 1; i >= 0; i--) {
+ if ($(objectDOM).find(nodeOrder[i].toLowerCase()).length) {
+ return $(objectDOM).find(nodeOrder[i].toLowerCase()).last()[0];
+ }
+ }
+ },
+
+ /*
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211 or false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
+ var emlModel = this.get("parentModel"),
+ tries = 0;
+
+ while (emlModel.type !== "EML" && tries < 6) {
+ emlModel = emlModel.get("parentModel");
+ tries++;
+ }
+
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
+ },
+
+ /*Format the EML XML for entities*/
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
+
+ /* Let the top level package know of attribute changes from this object */
+ trickleUpChange: function () {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ },
+ },
+ );
+
+ return EMLEntity;
+});
diff --git a/src/js/models/metadata/eml211/EMLGeoCoverage.js b/src/js/models/metadata/eml211/EMLGeoCoverage.js
index 589d0b99d..56ab37b28 100644
--- a/src/js/models/metadata/eml211/EMLGeoCoverage.js
+++ b/src/js/models/metadata/eml211/EMLGeoCoverage.js
@@ -3,7 +3,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
$,
_,
Backbone,
- DataONEObject
+ DataONEObject,
) {
/**
* @class EMLGeoCoverage
@@ -37,7 +37,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
"change:west " +
"change:south " +
"change:north",
- this.trickleUpChange
+ this.trickleUpChange,
);
},
@@ -85,16 +85,23 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
* @since 2.27.0
*/
errorMessages: {
- "default": "Please correct the geographic coverage.",
- "north": "Northwest latitude out of range, must be >-90 and <90. Please correct the latitude.",
- "east": "Southeast longitude out of range (-180 to 180). Please adjust the longitude.",
- "south": "Southeast latitude out of range, must be >-90 and <90. Please correct the latitude.",
- "west": "Northwest longitude out of range (-180 to 180). Check and correct the longitude.",
- "missing": "Latitude and longitude are required for each coordinate. Please complete all fields.",
- "description": "Missing location description. Please add a brief description.",
- "needPair": "Location requires at least one coordinate pair. Please add coordinates.",
- "northSouthReversed": "North latitude should be greater than South. Please swap the values.",
- "crossesAntiMeridian": "Bounding box crosses the anti-meridian. Please use multiple boxes that meet at the anti-meridian instead.",
+ default: "Please correct the geographic coverage.",
+ north:
+ "Northwest latitude out of range, must be >-90 and <90. Please correct the latitude.",
+ east: "Southeast longitude out of range (-180 to 180). Please adjust the longitude.",
+ south:
+ "Southeast latitude out of range, must be >-90 and <90. Please correct the latitude.",
+ west: "Northwest longitude out of range (-180 to 180). Check and correct the longitude.",
+ missing:
+ "Latitude and longitude are required for each coordinate. Please complete all fields.",
+ description:
+ "Missing location description. Please add a brief description.",
+ needPair:
+ "Location requires at least one coordinate pair. Please add coordinates.",
+ northSouthReversed:
+ "North latitude should be greater than South. Please swap the values.",
+ crossesAntiMeridian:
+ "Bounding box crosses the anti-meridian. Please use multiple boxes that meet at the anti-meridian instead.",
},
/**
@@ -208,8 +215,8 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
if (!objectDOM.children("geographicdescription").length)
objectDOM.append(
$(document.createElement("geographicdescription")).text(
- this.get("description")
- )
+ this.get("description"),
+ ),
);
else
objectDOM
@@ -229,17 +236,17 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
//Add the four coordinate values
$(boundingCoordinates).append(
$(document.createElement("westboundingcoordinate")).text(
- this.get("west")
+ this.get("west"),
),
$(document.createElement("eastboundingcoordinate")).text(
- this.get("east")
+ this.get("east"),
),
$(document.createElement("northboundingcoordinate")).text(
- this.get("north")
+ this.get("north"),
),
$(document.createElement("southboundingcoordinate")).text(
- this.get("south")
- )
+ this.get("south"),
+ ),
);
return objectDOM;
@@ -544,7 +551,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
formatXML: function (xmlString) {
return DataONEObject.prototype.formatXML.call(this, xmlString);
},
- }
+ },
);
return EMLGeoCoverage;
diff --git a/src/js/models/metadata/eml211/EMLKeywordSet.js b/src/js/models/metadata/eml211/EMLKeywordSet.js
index fdb5067e4..3750a1fc0 100644
--- a/src/js/models/metadata/eml211/EMLKeywordSet.js
+++ b/src/js/models/metadata/eml211/EMLKeywordSet.js
@@ -1,122 +1,132 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/DataONEObject'],
- function($, _, Backbone, DataONEObject) {
-
- var EMLKeywordSet = Backbone.Model.extend({
-
- type: "EMLKeywordSet",
-
- defaults: {
- objectXML: null,
- objectDOM: null,
- parentModel: null,
- thesaurus: "None",
- keywords: [] //The keyword values
- },
-
- initialize: function(attributes){
- if(attributes && attributes.objectDOM) this.set(this.parse(attributes.objectDOM));
-
- this.on("change:thesaurus change:keywords", this.trickleUpChange);
- },
-
- /*
- * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
- * Used during parse() and serialize()
- */
- nodeNameMap: function(){
- return{
- "keywordset" : "keywordSet",
- "keywordthesaurus" : "keywordThesaurus"
- }
- },
-
- parse: function(objectDOM){
- if(!objectDOM)
- var objectDOM = this.get("objectDOM").cloneNode(true);
-
- var modelJSON = {
- keywords: []
- };
+define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
+ $,
+ _,
+ Backbone,
+ DataONEObject,
+) {
+ var EMLKeywordSet = Backbone.Model.extend({
+ type: "EMLKeywordSet",
+
+ defaults: {
+ objectXML: null,
+ objectDOM: null,
+ parentModel: null,
+ thesaurus: "None",
+ keywords: [], //The keyword values
+ },
- //Get the list of keywords
- _.each($(objectDOM).find("keyword"), function(keyword){
- modelJSON.keywords.push($(keyword).text());
- });
+ initialize: function (attributes) {
+ if (attributes && attributes.objectDOM)
+ this.set(this.parse(attributes.objectDOM));
- //Get the thesaurus
- modelJSON.thesaurus = $(objectDOM).find("keywordthesaurus").text();
+ this.on("change:thesaurus change:keywords", this.trickleUpChange);
+ },
- return modelJSON;
- },
+ /*
+ * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
+ * Used during parse() and serialize()
+ */
+ nodeNameMap: function () {
+ return {
+ keywordset: "keywordSet",
+ keywordthesaurus: "keywordThesaurus",
+ };
+ },
- serialize: function(){
- var objectDOM = this.updateDOM(),
- xmlString = objectDOM.outerHTML;
+ parse: function (objectDOM) {
+ if (!objectDOM) var objectDOM = this.get("objectDOM").cloneNode(true);
- //Camel-case the XML
- xmlString = this.formatXML(xmlString);
+ var modelJSON = {
+ keywords: [],
+ };
- return xmlString;
- },
+ //Get the list of keywords
+ _.each($(objectDOM).find("keyword"), function (keyword) {
+ modelJSON.keywords.push($(keyword).text());
+ });
- /*
- * Makes a copy of the original XML DOM and updates it with the new values from the model.
- */
- updateDOM: function(){
- var objectDOM = this.get("objectDOM") ? this.get("objectDOM").cloneNode(true) : document.createElement("keywordset");
+ //Get the thesaurus
+ modelJSON.thesaurus = $(objectDOM).find("keywordthesaurus").text();
- //Return an empty string if there are no keywords
- if( !this.get("keywords") || this.get("keywords").length == 0 ){
- return "";
- }
+ return modelJSON;
+ },
- //Remove the keywords and thesaurus
- $(objectDOM).empty();
+ serialize: function () {
+ var objectDOM = this.updateDOM(),
+ xmlString = objectDOM.outerHTML;
- //Add the keywords
- _.each(this.get("keywords"), function(keyword){
- $(objectDOM).append($(document.createElement("keyword")).text(keyword));
- });
+ //Camel-case the XML
+ xmlString = this.formatXML(xmlString);
- //Add the thesaurus
- $(objectDOM).append($(document.createElement("keywordthesaurus")).text(this.get("thesaurus")));
+ return xmlString;
+ },
- // Remove empty (zero-length or whitespace-only) nodes
- $(objectDOM).find("*").filter(function() { return $.trim(this.innerHTML) === ""; } ).remove();
+ /*
+ * Makes a copy of the original XML DOM and updates it with the new values from the model.
+ */
+ updateDOM: function () {
+ var objectDOM = this.get("objectDOM")
+ ? this.get("objectDOM").cloneNode(true)
+ : document.createElement("keywordset");
+
+ //Return an empty string if there are no keywords
+ if (!this.get("keywords") || this.get("keywords").length == 0) {
+ return "";
+ }
- return objectDOM;
- },
+ //Remove the keywords and thesaurus
+ $(objectDOM).empty();
+
+ //Add the keywords
+ _.each(this.get("keywords"), function (keyword) {
+ $(objectDOM).append($(document.createElement("keyword")).text(keyword));
+ });
+
+ //Add the thesaurus
+ $(objectDOM).append(
+ $(document.createElement("keywordthesaurus")).text(
+ this.get("thesaurus"),
+ ),
+ );
+
+ // Remove empty (zero-length or whitespace-only) nodes
+ $(objectDOM)
+ .find("*")
+ .filter(function () {
+ return $.trim(this.innerHTML) === "";
+ })
+ .remove();
+
+ return objectDOM;
+ },
/*
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211 or false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211 or false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
var emlModel = this.get("parentModel"),
- tries = 0;
+ tries = 0;
- while (emlModel.type !== "EML" && tries < 6){
+ while (emlModel.type !== "EML" && tries < 6) {
emlModel = emlModel.get("parentModel");
tries++;
}
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
-
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
},
- trickleUpChange: function(){
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- },
+ trickleUpChange: function () {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ },
- formatXML: function(xmlString){
- return DataONEObject.prototype.formatXML.call(this, xmlString);
- }
- });
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
+ });
- return EMLKeywordSet;
+ return EMLKeywordSet;
});
diff --git a/src/js/models/metadata/eml211/EMLMeasurementScale.js b/src/js/models/metadata/eml211/EMLMeasurementScale.js
index a77db84fa..85e46f962 100644
--- a/src/js/models/metadata/eml211/EMLMeasurementScale.js
+++ b/src/js/models/metadata/eml211/EMLMeasurementScale.js
@@ -1,83 +1,114 @@
-define(["jquery", "underscore", "backbone",
- "models/metadata/eml211/EMLNonNumericDomain",
- "models/metadata/eml211/EMLNumericDomain",
- "models/metadata/eml211/EMLDateTimeDomain"],
- function($, _, Backbone, EMLNonNumericDomain, EMLNumericDomain, EMLDateTimeDomain) {
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/metadata/eml211/EMLNonNumericDomain",
+ "models/metadata/eml211/EMLNumericDomain",
+ "models/metadata/eml211/EMLDateTimeDomain",
+], function (
+ $,
+ _,
+ Backbone,
+ EMLNonNumericDomain,
+ EMLNumericDomain,
+ EMLDateTimeDomain,
+) {
+ /**
+ * @class EMLMeasurementScale
+ * @classdesc EMLMeasurementScale is a measurement scale factory that returns
+ * an EMLMeasurementScale subclass of either EMLNonNumericDomain,
+ * EMLNumericDomain, or EMLDateTimeDomain, depending on the
+ * domain name found in the given measurementScaleXML
+ * @classcategory Models/Metadata/EML211
+ * @extends Backbone.Model
+ */
+ var EMLMeasurementScale = Backbone.Model.extend(
+ {},
+ /** @lends EMLMeasurementScale.prototype */
+ {
+ /*
+ * Get an instance of an EMLMeasurementScale subclass
+ * given the measurementScaleXML fragment
+ */
+ getInstance: function (measurementScaleXML) {
+ var instance = {};
- /**
- * @class EMLMeasurementScale
- * @classdesc EMLMeasurementScale is a measurement scale factory that returns
- * an EMLMeasurementScale subclass of either EMLNonNumericDomain,
- * EMLNumericDomain, or EMLDateTimeDomain, depending on the
- * domain name found in the given measurementScaleXML
- * @classcategory Models/Metadata/EML211
- * @extends Backbone.Model
- */
- var EMLMeasurementScale = Backbone.Model.extend({},
- /** @lends EMLMeasurementScale.prototype */
- {
- /*
- * Get an instance of an EMLMeasurementScale subclass
- * given the measurementScaleXML fragment
- */
- getInstance: function(measurementScaleXML) {
- var instance = {};
+ if (measurementScaleXML && measurementScaleXML.indexOf("<") > -1) {
+ var objectDOM = $(measurementScaleXML)[0];
+ var domainName = $(objectDOM).children()[0].localName;
+ var options = { parse: true };
+ }
+ //If it's not an XML string, then it must be the domainName itself
+ else if (
+ measurementScaleXML &&
+ measurementScaleXML.indexOf("<") == -1
+ ) {
+ var domainName = measurementScaleXML;
+ var options = {};
+ measurementScaleXML = null;
+ }
- if(measurementScaleXML && measurementScaleXML.indexOf("<") > -1){
- var objectDOM = $(measurementScaleXML)[0];
- var domainName = $(objectDOM).children()[0].localName;
- var options = {parse: true};
- }
- //If it's not an XML string, then it must be the domainName itself
- else if(measurementScaleXML && measurementScaleXML.indexOf("<") == -1){
- var domainName = measurementScaleXML;
- var options = {};
- measurementScaleXML = null;
- }
+ // Return the appropriate sub class of EMLMeasurementScale
+ switch (domainName) {
+ case "nominal":
+ instance = new EMLNonNumericDomain(
+ {
+ measurementScale: domainName,
+ objectDOM: $(measurementScaleXML)[0],
+ },
+ options,
+ );
+ break;
+ case "ordinal":
+ instance = new EMLNonNumericDomain(
+ {
+ measurementScale: domainName,
+ objectDOM: $(measurementScaleXML)[0],
+ },
+ options,
+ );
+ break;
+ case "interval":
+ instance = new EMLNumericDomain(
+ {
+ measurementScale: domainName,
+ objectDOM: $(measurementScaleXML)[0],
+ },
+ options,
+ );
+ break;
+ case "ratio":
+ instance = new EMLNumericDomain(
+ {
+ measurementScale: domainName,
+ objectDOM: $(measurementScaleXML)[0],
+ },
+ options,
+ );
+ break;
+ case "datetime":
+ instance = new EMLDateTimeDomain(
+ {
+ measurementScale: domainName,
+ objectDOM: $(measurementScaleXML)[0],
+ },
+ options,
+ );
+ break;
+ default:
+ instance = new EMLNonNumericDomain(
+ {
+ measurementScale: domainName,
+ objectDOM: $(measurementScaleXML)[0],
+ },
+ options,
+ );
+ }
- // Return the appropriate sub class of EMLMeasurementScale
- switch ( domainName ) {
- case "nominal":
- instance = new EMLNonNumericDomain({
- "measurementScale": domainName,
- "objectDOM": $(measurementScaleXML)[0]
- }, options);
- break;
- case "ordinal":
- instance = new EMLNonNumericDomain({
- "measurementScale": domainName,
- "objectDOM": $(measurementScaleXML)[0]
- }, options);
- break;
- case "interval":
- instance = new EMLNumericDomain({
- "measurementScale": domainName,
- "objectDOM": $(measurementScaleXML)[0]
- }, options);
- break;
- case "ratio":
- instance = new EMLNumericDomain({
- "measurementScale": domainName,
- "objectDOM": $(measurementScaleXML)[0]
- }, options);
- break;
- case "datetime":
- instance = new EMLDateTimeDomain({
- "measurementScale": domainName,
- "objectDOM": $(measurementScaleXML)[0]
- }, options);
- break;
- default:
- instance = new EMLNonNumericDomain({
- "measurementScale": domainName,
- "objectDOM": $(measurementScaleXML)[0]
- }, options);
- }
+ return instance;
+ },
+ },
+ );
- return instance;
- }
- });
-
- return EMLMeasurementScale;
- }
-);
+ return EMLMeasurementScale;
+});
diff --git a/src/js/models/metadata/eml211/EMLMethods.js b/src/js/models/metadata/eml211/EMLMethods.js
index 86db9de21..c1be2b7bd 100644
--- a/src/js/models/metadata/eml211/EMLMethods.js
+++ b/src/js/models/metadata/eml211/EMLMethods.js
@@ -1,12 +1,12 @@
/* global define */
-define(['jquery',
- 'underscore',
- 'backbone',
- 'models/DataONEObject',
- 'models/metadata/eml/EMLMethodStep',
- 'models/metadata/eml211/EMLText'],
- function($, _, Backbone, DataONEObject, EMLMethodStep, EMLText) {
-
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/DataONEObject",
+ "models/metadata/eml/EMLMethodStep",
+ "models/metadata/eml211/EMLText",
+], function ($, _, Backbone, DataONEObject, EMLMethodStep, EMLText) {
/**
* @class EMLMethods
* @classdesc Represents the EML Methods module. The methods field documents scientific methods
@@ -17,9 +17,8 @@ define(['jquery',
* @extends Backbone.Model
*/
var EMLMethods = Backbone.Model.extend(
- /** @lends EMLMethods.prototype */{
-
- /**
+ /** @lends EMLMethods.prototype */ {
+ /**
* The default values of this model that are get() or set()
* @returns {object}
* @property {string} objectXML The original XML snippet string from the EML XML
@@ -32,508 +31,571 @@ define(['jquery',
text-based/human readable description of the sampling
procedures used in the research project.
*/
- defaults: function(){
- return {
- objectXML: null,
- objectDOM: null,
- methodSteps: [],
- studyExtentDescription: null,
- samplingDescription: null
- }
- },
-
- initialize: function(attributes){
- attributes = attributes || {};
-
- if(attributes.objectDOM){
- this.set(this.parse(attributes.objectDOM))
- }
- else if(attributes.objectXML){
- let objectDOM = $.parseHTML(attributes.objectXML)[0];
- this.set("objectDOM", objectDOM);
- this.set(this.parse(objectDOM))
- }
- else{
- //Create the custom method steps and add to the step list
- let customMethodSteps = this.createCustomMethodSteps();
- this.set("methodSteps", customMethodSteps);
- }
-
- //specific attributes to listen to
- this.on("change:methodStepDescription change:studyExtentDescription change:samplingDescription",
- this.trickleUpChange);
-
- },
+ defaults: function () {
+ return {
+ objectXML: null,
+ objectDOM: null,
+ methodSteps: [],
+ studyExtentDescription: null,
+ samplingDescription: null,
+ };
+ },
+
+ initialize: function (attributes) {
+ attributes = attributes || {};
+
+ if (attributes.objectDOM) {
+ this.set(this.parse(attributes.objectDOM));
+ } else if (attributes.objectXML) {
+ let objectDOM = $.parseHTML(attributes.objectXML)[0];
+ this.set("objectDOM", objectDOM);
+ this.set(this.parse(objectDOM));
+ } else {
+ //Create the custom method steps and add to the step list
+ let customMethodSteps = this.createCustomMethodSteps();
+ this.set("methodSteps", customMethodSteps);
+ }
- /**
- * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
- * Used during parse() and serialize()
- * @returns {object}
- */
- nodeNameMap: function(){
- return _.extend(EMLMethodStep.prototype.nodeNameMap(), {
- "methodstep" : "methodStep",
- "datasource" : "dataSource",
- "studyextent" : "studyExtent",
- "spatialsamplingunits" : "spatialSamplingUnits",
- "referencedentityid" : "referencedEntityId",
- "qualitycontrol" : "qualityControl"
- })
- },
+ //specific attributes to listen to
+ this.on(
+ "change:methodStepDescription change:studyExtentDescription change:samplingDescription",
+ this.trickleUpChange,
+ );
+ },
+
+ /**
+ * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
+ * Used during parse() and serialize()
+ * @returns {object}
+ */
+ nodeNameMap: function () {
+ return _.extend(EMLMethodStep.prototype.nodeNameMap(), {
+ methodstep: "methodStep",
+ datasource: "dataSource",
+ studyextent: "studyExtent",
+ spatialsamplingunits: "spatialSamplingUnits",
+ referencedentityid: "referencedEntityId",
+ qualitycontrol: "qualityControl",
+ });
+ },
- parse: function(objectDOM) {
- var modelJSON = {};
+ parse: function (objectDOM) {
+ var modelJSON = {};
- if (!objectDOM) var objectDOM = this.get("objectDOM");
+ if (!objectDOM) var objectDOM = this.get("objectDOM");
- var model = this;
+ var model = this;
- //Create the custom method steps
- let customMethodSteps = this.createCustomMethodSteps();
+ //Create the custom method steps
+ let customMethodSteps = this.createCustomMethodSteps();
- //Create new EMLMethodStep models for the method steps
- let allMethodSteps = _.map($(objectDOM).find('methodstep'), function(el, i) {
- return new EMLMethodStep({
- objectDOM: el
- });
- }),
+ //Create new EMLMethodStep models for the method steps
+ let allMethodSteps = _.map(
+ $(objectDOM).find("methodstep"),
+ function (el, i) {
+ return new EMLMethodStep({
+ objectDOM: el,
+ });
+ },
+ ),
//Get the custom IDs for each method step, if there any
- allMethodStepIDs = _.compact(allMethodSteps.map(step => { return step.get("customMethodID") }));
+ allMethodStepIDs = _.compact(
+ allMethodSteps.map((step) => {
+ return step.get("customMethodID");
+ }),
+ );
+
+ //Filter out any custom method steps that we already created from the DOM
+ customMethodSteps = customMethodSteps.filter((step) => {
+ return !allMethodStepIDs.includes(step.get("customMethodID"));
+ });
- //Filter out any custom method steps that we already created from the DOM
- customMethodSteps = customMethodSteps.filter(step => { return !allMethodStepIDs.includes(step.get("customMethodID")) });
+ //Combine the parsed method steps and the default custom method steps
+ allMethodSteps = allMethodSteps.concat(customMethodSteps);
- //Combine the parsed method steps and the default custom method steps
- allMethodSteps = allMethodSteps.concat(customMethodSteps);
+ //Save the method steps to this model
+ modelJSON.methodSteps = allMethodSteps;
- //Save the method steps to this model
- modelJSON.methodSteps = allMethodSteps;
+ if ($(objectDOM).find("sampling studyextent description").length > 0) {
+ modelJSON.studyExtentDescription = new EMLText({
+ objectDOM: $(objectDOM)
+ .find("sampling studyextent description")
+ .get(0),
+ type: "description",
+ parentModel: model,
+ });
+ }
- if ($(objectDOM).find('sampling studyextent description').length > 0) {
- modelJSON.studyExtentDescription = new EMLText({
- objectDOM: $(objectDOM).find('sampling studyextent description').get(0),
- type: 'description',
- parentModel: model
- });
- }
+ if ($(objectDOM).find("sampling samplingdescription").length > 0) {
+ modelJSON.samplingDescription = new EMLText({
+ objectDOM: $(objectDOM).find("sampling samplingdescription").get(0),
+ type: "samplingDescription",
+ parentModel: model,
+ });
+ }
- if ($(objectDOM).find('sampling samplingdescription').length > 0) {
- modelJSON.samplingDescription = new EMLText({
- objectDOM: $(objectDOM).find('sampling samplingdescription').get(0),
- type: 'samplingDescription',
- parentModel: model
- });
- }
+ return modelJSON;
+ },
- return modelJSON;
- },
+ serialize: function () {
+ var objectDOM = this.updateDOM();
- serialize: function(){
- var objectDOM = this.updateDOM();
+ if (!objectDOM) return "";
- if(!objectDOM)
- return "";
+ var xmlString = objectDOM.outerHTML;
- var xmlString = objectDOM.outerHTML;
+ //Camel-case the XML
+ xmlString = this.formatXML(xmlString);
- //Camel-case the XML
- xmlString = this.formatXML(xmlString);
+ return xmlString;
+ },
- return xmlString;
- },
-
- /**
- * Makes a copy of the original XML DOM and updates it with the new values from the model.
- */
- updateDOM: function(){
- var objectDOM;
+ /**
+ * Makes a copy of the original XML DOM and updates it with the new values from the model.
+ */
+ updateDOM: function () {
+ var objectDOM;
- if (this.get("objectDOM")) {
- objectDOM = this.get("objectDOM").cloneNode(true);
- } else {
- objectDOM = $(document.createElement("methods"));
- }
+ if (this.get("objectDOM")) {
+ objectDOM = this.get("objectDOM").cloneNode(true);
+ } else {
+ objectDOM = $(document.createElement("methods"));
+ }
- objectDOM = $(objectDOM);
+ objectDOM = $(objectDOM);
- try{
- var methodStepsFromModel = this.get('methodSteps'),
+ try {
+ var methodStepsFromModel = this.get("methodSteps"),
regularMethodSteps = this.getNonCustomSteps(),
- customMethodSteps = _.difference(methodStepsFromModel, regularMethodSteps),
+ customMethodSteps = _.difference(
+ methodStepsFromModel,
+ regularMethodSteps,
+ ),
sortedCustomMethodSteps = [],
- methodStepsFromDOM = $(objectDOM).find("methodstep");
-
- //Detach the existing method steps from the DOM first
- methodStepsFromDOM.detach();
-
- try{
+ methodStepsFromDOM = $(objectDOM).find("methodstep");
+
+ //Detach the existing method steps from the DOM first
+ methodStepsFromDOM.detach();
+
+ try {
+ //Sort the custom method steps to match the app config order
+ let configCustomMethods = _.clone(
+ MetacatUI.appModel.get("customEMLMethods") || [],
+ );
+ if (configCustomMethods.length) {
+ configCustomMethods.forEach((customOptions) => {
+ let matchingStep = customMethodSteps.find((step) => {
+ return customOptions.titleOptions.includes(
+ step.get("description").get("title"),
+ );
+ });
+ if (matchingStep) {
+ sortedCustomMethodSteps.push(matchingStep);
+ }
+ });
+ }
+ } catch (e) {
+ console.error(
+ "Could not sort the custom methods during serialization. Will proceed without sorting the custom method steps: ",
+ e,
+ );
+ sortedCustomMethodSteps = customMethodSteps;
+ }
- //Sort the custom method steps to match the app config order
- let configCustomMethods = _.clone(MetacatUI.appModel.get("customEMLMethods") || []);
- if( configCustomMethods.length ){
- configCustomMethods.forEach( customOptions => {
- let matchingStep = customMethodSteps.find( step => { return customOptions.titleOptions.includes(step.get("description").get("title")) });
- if( matchingStep ){
- sortedCustomMethodSteps.push(matchingStep);
- }
+ //Update each method step and prepend to the top of the methods (reverse arrays first to keep the right order)
+ regularMethodSteps
+ .reverse()
+ .concat(sortedCustomMethodSteps.reverse())
+ .forEach((step) => {
+ objectDOM.prepend(step.updateDOM());
});
- }
- }
- catch(e){
- console.error("Could not sort the custom methods during serialization. Will proceed without sorting the custom method steps: ", e);
- sortedCustomMethodSteps = customMethodSteps;
+ } catch (e) {
+ console.error(
+ "Failed to serialize the method steps. Proceeding without updating. ",
+ e,
+ );
}
- //Update each method step and prepend to the top of the methods (reverse arrays first to keep the right order)
- regularMethodSteps.reverse().concat(sortedCustomMethodSteps.reverse()).forEach(step => {
- objectDOM.prepend(step.updateDOM());
- });
- }
- catch(e){
- console.error("Failed to serialize the method steps. Proceeding without updating. ", e);
- }
-
- try{
- // Update the sampling metadata
- if (this.get('samplingDescription') || this.get('studyExtentDescription')) {
-
- var samplingEl = $(document.createElement('sampling')),
- studyExtentEl = $(document.createElement('studyExtent')),
+ try {
+ // Update the sampling metadata
+ if (
+ this.get("samplingDescription") ||
+ this.get("studyExtentDescription")
+ ) {
+ var samplingEl = $(document.createElement("sampling")),
+ studyExtentEl = $(document.createElement("studyExtent")),
missingStudyExtent = false,
missingDescription = false;
- //If there is a study extent description, then create a DOM element for it and append it to the parent node
- if (this.get('studyExtentDescription') && !this.get('studyExtentDescription').isEmpty()) {
- $(studyExtentEl).append(this.get('studyExtentDescription').updateDOM());
-
- //If the text matches the default "filler" text, then mark it as missing
- if( this.get('studyExtentDescription').get("text")[0] == "No study extent description provided."){
+ //If there is a study extent description, then create a DOM element for it and append it to the parent node
+ if (
+ this.get("studyExtentDescription") &&
+ !this.get("studyExtentDescription").isEmpty()
+ ) {
+ $(studyExtentEl).append(
+ this.get("studyExtentDescription").updateDOM(),
+ );
+
+ //If the text matches the default "filler" text, then mark it as missing
+ if (
+ this.get("studyExtentDescription").get("text")[0] ==
+ "No study extent description provided."
+ ) {
+ missingStudyExtent = true;
+ }
+ }
+ //If there isn't a study extent description, then mark it as missing and append the default "filler" text
+ else {
missingStudyExtent = true;
+ $(studyExtentEl).append(
+ $(document.createElement("description")).html(
+ "No study extent description provided. ",
+ ),
+ );
}
- }
- //If there isn't a study extent description, then mark it as missing and append the default "filler" text
- else {
- missingStudyExtent = true;
- $(studyExtentEl).append($(document.createElement('description')).html("No study extent description provided. "));
- }
-
- //Add the study extent element to the sampling element
- $(samplingEl).append(studyExtentEl);
-
- //If there is a sampling description, then create a DOM element for it and append it to the parent node
- if (this.get('samplingDescription') && !this.get('samplingDescription').isEmpty()) {
- $(samplingEl).append(this.get('samplingDescription').updateDOM());
-
- //If the text matches the default "filler" text, then mark it as missing
- if( this.get('samplingDescription').get("text")[0] == "No sampling description provided."){
+ //Add the study extent element to the sampling element
+ $(samplingEl).append(studyExtentEl);
+
+ //If there is a sampling description, then create a DOM element for it and append it to the parent node
+ if (
+ this.get("samplingDescription") &&
+ !this.get("samplingDescription").isEmpty()
+ ) {
+ $(samplingEl).append(this.get("samplingDescription").updateDOM());
+
+ //If the text matches the default "filler" text, then mark it as missing
+ if (
+ this.get("samplingDescription").get("text")[0] ==
+ "No sampling description provided."
+ ) {
+ missingDescription = true;
+ }
+ }
+ //If there isn't a study extent description, then mark it as missing and append the default "filler" text
+ else {
missingDescription = true;
+ $(samplingEl).append(
+ $(document.createElement("samplingDescription")).html(
+ "No sampling description provided. ",
+ ),
+ );
}
- }
- //If there isn't a study extent description, then mark it as missing and append the default "filler" text
- else {
- missingDescription = true;
- $(samplingEl).append($(document.createElement('samplingDescription')).html("No sampling description provided. "));
- }
-
- //Find the existing element
- var existingSampling = objectDOM.find("sampling");
+ //Find the existing element
+ var existingSampling = objectDOM.find("sampling");
- //Remove all the sampling nodes if there is no study extent and no description
- if(missingStudyExtent && missingDescription){
- existingSampling.remove();
- }
- //Replace the existing sampling element, if it exists
- else if( existingSampling.length > 0 ){
- existingSampling.replaceWith(samplingEl);
- }
- //Or append a new one
- else{
- objectDOM.append(samplingEl);
+ //Remove all the sampling nodes if there is no study extent and no description
+ if (missingStudyExtent && missingDescription) {
+ existingSampling.remove();
+ }
+ //Replace the existing sampling element, if it exists
+ else if (existingSampling.length > 0) {
+ existingSampling.replaceWith(samplingEl);
+ }
+ //Or append a new one
+ else {
+ objectDOM.append(samplingEl);
+ }
}
+ } catch (e) {
+ console.error(
+ "Error while serializing the study extent and sampling. Won't update. ",
+ e,
+ );
}
- }
- catch(e){
- console.error("Error while serializing the study extent and sampling. Won't update. ", e);
- }
-
- // Remove empty (zero-length or whitespace-only) nodes
- objectDOM.find("*").filter(function() { return $.trim(this.innerHTML) === ""; } ).remove();
-
- //Check if all the content is filler content. This means there are no method steps, no sampling description, and
- // no study extent description.
- if( objectDOM.find("samplingdescription").length == 1 &&
- objectDOM.find("samplingdescription para").text() == "No sampling description provided." &&
+ // Remove empty (zero-length or whitespace-only) nodes
+ objectDOM
+ .find("*")
+ .filter(function () {
+ return $.trim(this.innerHTML) === "";
+ })
+ .remove();
+
+ //Check if all the content is filler content. This means there are no method steps, no sampling description, and
+ // no study extent description.
+ if (
+ objectDOM.find("samplingdescription").length == 1 &&
+ objectDOM.find("samplingdescription para").text() ==
+ "No sampling description provided." &&
objectDOM.find("studyextent").length == 1 &&
- objectDOM.find("studyextent description para").text() == "No study extent description provided." ){
-
+ objectDOM.find("studyextent description para").text() ==
+ "No study extent description provided."
+ ) {
//If it is all empty / filler content, then totally remove the methods
return "";
+ }
- }
-
- //If there are sampling nodes listed before methodStep nodes, then reorder them
- if( objectDOM.children().index(objectDOM.find("methodstep").last()) >
- objectDOM.children().index(objectDOM.find("sampling").last()) ){
-
- //Detach all the sampling nodes and append them to the parent node
- objectDOM.append( objectDOM.children("sampling").detach() );
-
- }
-
- //If there are sampling nodes but no method nodes, make method nodes
- if( objectDOM.find("samplingdescription").length > 0 &&
- objectDOM.find("studyextent").length > 0){
- //Make a filler method node
- if(!objectDOM.find("methodstep").length){
- objectDOM.prepend("No method step description provided. ");
+ //If there are sampling nodes listed before methodStep nodes, then reorder them
+ if (
+ objectDOM.children().index(objectDOM.find("methodstep").last()) >
+ objectDOM.children().index(objectDOM.find("sampling").last())
+ ) {
+ //Detach all the sampling nodes and append them to the parent node
+ objectDOM.append(objectDOM.children("sampling").detach());
}
- else if(objectDOM.find("methodstep").length > 1){
- //If there is more than one method step, remove any that have the default filler text
- objectDOM.find("methodstep:contains('No method step description provided.')").remove();
- //Double check that there is always at least one method step (or there will be an EML validation error)
- if(!objectDOM.find("methodstep").length){
- objectDOM.prepend("No method step description provided. ");
+
+ //If there are sampling nodes but no method nodes, make method nodes
+ if (
+ objectDOM.find("samplingdescription").length > 0 &&
+ objectDOM.find("studyextent").length > 0
+ ) {
+ //Make a filler method node
+ if (!objectDOM.find("methodstep").length) {
+ objectDOM.prepend(
+ "No method step description provided. ",
+ );
+ } else if (objectDOM.find("methodstep").length > 1) {
+ //If there is more than one method step, remove any that have the default filler text
+ objectDOM
+ .find(
+ "methodstep:contains('No method step description provided.')",
+ )
+ .remove();
+ //Double check that there is always at least one method step (or there will be an EML validation error)
+ if (!objectDOM.find("methodstep").length) {
+ objectDOM.prepend(
+ "No method step description provided. ",
+ );
+ }
}
}
- }
-
- return objectDOM.length? objectDOM[0] : objectDOM;
- },
-
- /**
- * Creates a new EMLMethodStep model and adds it to this model
- * @param {object} [attr] A literal object of attributes to set on the EMLMethodStep
- * @since 2.19.0
- */
- addMethodStep: function(attr){
-
- try{
+ return objectDOM.length ? objectDOM[0] : objectDOM;
+ },
+
+ /**
+ * Creates a new EMLMethodStep model and adds it to this model
+ * @param {object} [attr] A literal object of attributes to set on the EMLMethodStep
+ * @since 2.19.0
+ */
+ addMethodStep: function (attr) {
+ try {
+ if (!attr) {
+ let attr = {};
+ }
- if(!attr){
- let attr = {}
+ let newStep = new EMLMethodStep(attr);
+ this.get("methodSteps").push(newStep);
+ this.set("methodSteps", this.get("methodSteps"));
+ return newStep;
+ } catch (e) {
+ console.error(e);
}
-
- let newStep = new EMLMethodStep(attr);
- this.get("methodSteps").push(newStep);
- this.set("methodSteps", this.get("methodSteps"));
- return newStep;
-
- }
- catch(e){
- console.error(e);
- }
-
- },
-
- /**
- * Removes the given EMLMethodStep from the overall EMLMethods
- * @param {EMLMethodStep} step The EMLMethodStep to remove
- * @since 2.19.0
- */
- removeMethodStep: function(step){
- try{
-
- if( !step ) return;
-
- //Remove the EMLMethodStep from the steps list
- this.set("methodSteps", _.without(this.get("methodSteps"), step));
-
- //If this was the last step to be removed, and the rest of the EMLMethods
- // model is empty, then remove the model from the parent EML model
- if( this.isEmpty() ){
- //Get the parent EML model
- var parentEML = this.getParentEML();
-
- //Make sure this model type is EML211
- if( parentEML && parentEML.type == "EML" ){
-
- //If the methods are an array,
- if( Array.isArray(parentEML.get("methods")) ){
- //remove this EMLMethods model from the array
- parentEML.set( "methods", _.without(parentEML.get("methods"), this) );
- }
- else{
- //If the methods attribute is set to this EMLMethods model,
- // then just set it back to it's default
- if( parentEML.get("methods") == this )
- parentEML.set("methods", parentEML.defaults().methods);
+ },
+
+ /**
+ * Removes the given EMLMethodStep from the overall EMLMethods
+ * @param {EMLMethodStep} step The EMLMethodStep to remove
+ * @since 2.19.0
+ */
+ removeMethodStep: function (step) {
+ try {
+ if (!step) return;
+
+ //Remove the EMLMethodStep from the steps list
+ this.set("methodSteps", _.without(this.get("methodSteps"), step));
+
+ //If this was the last step to be removed, and the rest of the EMLMethods
+ // model is empty, then remove the model from the parent EML model
+ if (this.isEmpty()) {
+ //Get the parent EML model
+ var parentEML = this.getParentEML();
+
+ //Make sure this model type is EML211
+ if (parentEML && parentEML.type == "EML") {
+ //If the methods are an array,
+ if (Array.isArray(parentEML.get("methods"))) {
+ //remove this EMLMethods model from the array
+ parentEML.set(
+ "methods",
+ _.without(parentEML.get("methods"), this),
+ );
+ } else {
+ //If the methods attribute is set to this EMLMethods model,
+ // then just set it back to it's default
+ if (parentEML.get("methods") == this)
+ parentEML.set("methods", parentEML.defaults().methods);
+ }
}
}
+ this.trickleUpChange();
+ } catch (e) {
+ console.error("Error while trying to remove a method step: ", e);
}
-
- this.trickleUpChange();
-
- }
- catch(e){
- console.error("Error while trying to remove a method step: ", e);
- }
- },
-
- /**
- * Returns the EMLMethodSteps that are not custom methods, as configured in {@link AppConfig#customEMLMethods}
- * @returns {EMLMethodStep[]}
- * @since 2.19.0
- */
- getNonCustomSteps: function(){
- return this.get("methodSteps").filter(step => !step.isCustom());
- },
-
- /**
- * Returns the EMLMethodSteps that are custom methods, as configured in {@link AppConfig#customEMLMethods}
- * @returns {EMLMethodStep[]}
- * @since 2.19.0
- */
- getCustomSteps: function(){
- return this.get("methodSteps").filter(step => step.isCustom());
- },
-
- /**
- * function isEmpty() - Will check if there are any values set on this model
- * that are different than the default values and would be serialized to the EML.
- *
- * @return {boolean} - Returns true is this model is empty, false if not
- */
- isEmpty: function(){
-
- var methodsStepsEmpty = false,
+ },
+
+ /**
+ * Returns the EMLMethodSteps that are not custom methods, as configured in {@link AppConfig#customEMLMethods}
+ * @returns {EMLMethodStep[]}
+ * @since 2.19.0
+ */
+ getNonCustomSteps: function () {
+ return this.get("methodSteps").filter((step) => !step.isCustom());
+ },
+
+ /**
+ * Returns the EMLMethodSteps that are custom methods, as configured in {@link AppConfig#customEMLMethods}
+ * @returns {EMLMethodStep[]}
+ * @since 2.19.0
+ */
+ getCustomSteps: function () {
+ return this.get("methodSteps").filter((step) => step.isCustom());
+ },
+
+ /**
+ * function isEmpty() - Will check if there are any values set on this model
+ * that are different than the default values and would be serialized to the EML.
+ *
+ * @return {boolean} - Returns true is this model is empty, false if not
+ */
+ isEmpty: function () {
+ var methodsStepsEmpty = false,
studyExtentEmpty = false,
samplingEmpty = false;
- if( !this.get("methodSteps").length || !this.get("methodSteps") || this.get("methodSteps").every(step => step.isEmpty()) ){
- methodsStepsEmpty = true;
- }
+ if (
+ !this.get("methodSteps").length ||
+ !this.get("methodSteps") ||
+ this.get("methodSteps").every((step) => step.isEmpty())
+ ) {
+ methodsStepsEmpty = true;
+ }
- if( this.get("studyExtentDescription") == this.defaults().studyExtentDescription ||
+ if (
+ this.get("studyExtentDescription") ==
+ this.defaults().studyExtentDescription ||
!this.get("studyExtentDescription") ||
- (this.get("studyExtentDescription").isEmpty && this.get("studyExtentDescription").isEmpty()) ||
- (Array.isArray(this.get("studyExtentDescription")) && !this.get("studyExtentDescription").length ) ||
+ (this.get("studyExtentDescription").isEmpty &&
+ this.get("studyExtentDescription").isEmpty()) ||
(Array.isArray(this.get("studyExtentDescription")) &&
- this.get("studyExtentDescription").length == 1 &&
- this.get("studyExtentDescription")[0].get("text").length == 1 &&
- this.get("studyExtentDescription")[0].get("text")[0] == "No study extent description provided.") ){
-
+ !this.get("studyExtentDescription").length) ||
+ (Array.isArray(this.get("studyExtentDescription")) &&
+ this.get("studyExtentDescription").length == 1 &&
+ this.get("studyExtentDescription")[0].get("text").length == 1 &&
+ this.get("studyExtentDescription")[0].get("text")[0] ==
+ "No study extent description provided.")
+ ) {
studyExtentEmpty = true;
+ }
- }
-
- if( this.get("samplingDescription") == this.defaults().samplingDescription ||
+ if (
+ this.get("samplingDescription") ==
+ this.defaults().samplingDescription ||
!this.get("samplingDescription") ||
- (this.get("samplingDescription").isEmpty && this.get("samplingDescription").isEmpty()) ||
- (Array.isArray(this.get("samplingDescription")) && !this.get("samplingDescription").length ) ||
+ (this.get("samplingDescription").isEmpty &&
+ this.get("samplingDescription").isEmpty()) ||
(Array.isArray(this.get("samplingDescription")) &&
- this.get("samplingDescription").length == 1 &&
- this.get("samplingDescription")[0].get("text").length == 1 &&
- this.get("samplingDescription")[0].get("text")[0] == "No sampling description provided.") ){
-
+ !this.get("samplingDescription").length) ||
+ (Array.isArray(this.get("samplingDescription")) &&
+ this.get("samplingDescription").length == 1 &&
+ this.get("samplingDescription")[0].get("text").length == 1 &&
+ this.get("samplingDescription")[0].get("text")[0] ==
+ "No sampling description provided.")
+ ) {
samplingEmpty = true;
+ }
- }
-
- if( methodsStepsEmpty && studyExtentEmpty && samplingEmpty )
- return true;
-
- },
-
- /**
- * Overloads Backbone.Model.validate() to check if this model has valid values set on it.
- * For now, only the custom method steps are validated, because they could be required.
- * @extends Backbone.Model.validate
- * @returns {object}
- */
- validate: function(){
-
- try{
-
- let validationErrors = {}
-
- //Validate each custom Method Step
- let customSteps = this.getCustomSteps(),
+ if (methodsStepsEmpty && studyExtentEmpty && samplingEmpty) return true;
+ },
+
+ /**
+ * Overloads Backbone.Model.validate() to check if this model has valid values set on it.
+ * For now, only the custom method steps are validated, because they could be required.
+ * @extends Backbone.Model.validate
+ * @returns {object}
+ */
+ validate: function () {
+ try {
+ let validationErrors = {};
+
+ //Validate each custom Method Step
+ let customSteps = this.getCustomSteps(),
methodStepValidationErrors = {};
- customSteps.forEach(step => {
- if( !step.isValid() ){
- methodStepValidationErrors[step.get("customMethodID")] = step.validationError;
- }
- });
-
- if( Object.keys(methodStepValidationErrors).length ){
- validationErrors.methodSteps = methodStepValidationErrors;
- }
+ customSteps.forEach((step) => {
+ if (!step.isValid()) {
+ methodStepValidationErrors[step.get("customMethodID")] =
+ step.validationError;
+ }
+ });
- //Check for the required fields
- let isRequired = MetacatUI.appModel.get("emlEditorRequiredFields").methods === true;
- if( isRequired ){
- let steps = this.getNonCustomSteps();
- if( !steps || !steps.length){
- validationErrors.methodSteps = "At least one method step is required.";
+ if (Object.keys(methodStepValidationErrors).length) {
+ validationErrors.methodSteps = methodStepValidationErrors;
}
- }
-
- return Object.keys(validationErrors).length? validationErrors : false;
- }
- catch(e){
- console.error("Error while validating the Methods: ", e);
- return false;
- }
-
- },
+ //Check for the required fields
+ let isRequired =
+ MetacatUI.appModel.get("emlEditorRequiredFields").methods === true;
+ if (isRequired) {
+ let steps = this.getNonCustomSteps();
+ if (!steps || !steps.length) {
+ validationErrors.methodSteps =
+ "At least one method step is required.";
+ }
+ }
- /**
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211|false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
- var emlModel = this.get("parentModel"),
+ return Object.keys(validationErrors).length
+ ? validationErrors
+ : false;
+ } catch (e) {
+ console.error("Error while validating the Methods: ", e);
+ return false;
+ }
+ },
+
+ /**
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211|false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
+ var emlModel = this.get("parentModel"),
tries = 0;
- while (emlModel.type !== "EML" && tries < 6){
- emlModel = emlModel.get("parentModel");
- tries++;
- }
-
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
+ while (emlModel.type !== "EML" && tries < 6) {
+ emlModel = emlModel.get("parentModel");
+ tries++;
+ }
- },
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
+ },
+
+ trickleUpChange: function () {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ },
+
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
+
+ /**
+ * Creates and returns the custom Method Step models, as configured in the {@link AppConfig}
+ * @returns {EMLMethodStep[]}
+ * @since 2.19.0
+ */
+ createCustomMethodSteps: function () {
+ //Get the custom methods configured in the app
+ let configCustomMethods = MetacatUI.appModel.get("customEMLMethods"),
+ customMethods = [];
- trickleUpChange: function(){
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- },
+ //If there is at least one
+ configCustomMethods.forEach((config) => {
+ customMethods.push(
+ new EMLMethodStep({
+ customMethodID: config.id,
+ required: config.required,
+ }),
+ );
+ });
- formatXML: function(xmlString){
- return DataONEObject.prototype.formatXML.call(this, xmlString);
+ return customMethods;
+ },
},
-
- /**
- * Creates and returns the custom Method Step models, as configured in the {@link AppConfig}
- * @returns {EMLMethodStep[]}
- * @since 2.19.0
- */
- createCustomMethodSteps: function(){
- //Get the custom methods configured in the app
- let configCustomMethods = MetacatUI.appModel.get("customEMLMethods"),
- customMethods = [];
-
- //If there is at least one
- configCustomMethods.forEach(config => {
- customMethods.push(new EMLMethodStep({
- customMethodID: config.id,
- required: config.required
- }))
- });
-
- return customMethods;
- }
- });
+ );
return EMLMethods;
});
diff --git a/src/js/models/metadata/eml211/EMLMissingValueCode.js b/src/js/models/metadata/eml211/EMLMissingValueCode.js
index 9ecfae121..b0ac3d8cf 100644
--- a/src/js/models/metadata/eml211/EMLMissingValueCode.js
+++ b/src/js/models/metadata/eml211/EMLMissingValueCode.js
@@ -128,7 +128,7 @@ define(["backbone"], function (Backbone) {
return Object.keys(errors).length > 0 ? errors : undefined;
},
- }
+ },
);
return EMLMissingValueCode;
diff --git a/src/js/models/metadata/eml211/EMLNonNumericDomain.js b/src/js/models/metadata/eml211/EMLNonNumericDomain.js
index 1bfa1e3ed..4ff236241 100644
--- a/src/js/models/metadata/eml211/EMLNonNumericDomain.js
+++ b/src/js/models/metadata/eml211/EMLNonNumericDomain.js
@@ -1,890 +1,937 @@
-define(["jquery", "underscore", "backbone",
- "models/DataONEObject"],
- function($, _, Backbone, DataONEObject) {
-
- /**
- * @class EMLNonNumericDomain
- * @classdesc EMLNonNumericDomain represents the measurement scale of a nominal
- * or ordinal measurement scale attribute, and is an extension of
- * EMLMeasurementScale.
- * @classcategory Models/Metadata/EML211
- * @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html#AttributeType_AttributeType_measurementScale_AttributeType_AttributeType_measurementScale_nominal_nonNumericDomain
- * @extends Backbone.Model
- * @constructor
- */
- var EMLNonNumericDomain = Backbone.Model.extend(
- /** @lends EMLNonNumericDomain.prototype */{
-
- type: "EMLNonNumericDomain",
-
- /* Attributes of an EMLNonNumericDomain object */
- defaults: function(){
- return {
- /* Attributes from EML, extends attributes from EMLMeasurementScale */
- measurementScale: null, // the name of this measurement scale
- nonNumericDomain: [] // One or more of enumeratedDomain, textDomain, references
+define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
+ $,
+ _,
+ Backbone,
+ DataONEObject,
+) {
+ /**
+ * @class EMLNonNumericDomain
+ * @classdesc EMLNonNumericDomain represents the measurement scale of a nominal
+ * or ordinal measurement scale attribute, and is an extension of
+ * EMLMeasurementScale.
+ * @classcategory Models/Metadata/EML211
+ * @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html#AttributeType_AttributeType_measurementScale_AttributeType_AttributeType_measurementScale_nominal_nonNumericDomain
+ * @extends Backbone.Model
+ * @constructor
+ */
+ var EMLNonNumericDomain = Backbone.Model.extend(
+ /** @lends EMLNonNumericDomain.prototype */ {
+ type: "EMLNonNumericDomain",
+
+ /* Attributes of an EMLNonNumericDomain object */
+ defaults: function () {
+ return {
+ /* Attributes from EML, extends attributes from EMLMeasurementScale */
+ measurementScale: null, // the name of this measurement scale
+ nonNumericDomain: [], // One or more of enumeratedDomain, textDomain, references
+ };
+ },
+
+ /**
+ * The map of lower case to camel case node names
+ * needed to deal with parsing issues with $.parseHTML().
+ * Use this until we can figure out issues with $.parseXML().
+ * @type {object}
+ */
+ nodeNameMap: {
+ nonnumericdomain: "nonNumericDomain",
+ enumerateddomain: "enumeratedDomain",
+ textdomain: "textDomain",
+ externalcodeset: "externalCodeSet",
+ codesetname: "codesetName",
+ codeseturl: "codesetURL",
+ entityCodeList: "entityCodeList",
+ entityreference: "entityReference",
+ valueattributereference: "valueAttributeReference",
+ definitionattributereference: "definitionAttributeReference",
+ orderattributereference: "orderAttributeReference",
+ sourced: "source",
+ },
+
+ /* Initialize an EMLNonNumericDomain object */
+ initialize: function (attributes, options) {
+ this.on("change:nonNumericDomain", this.trickleUpChange);
+ },
+
+ /**
+ * Parse the incoming measurementScale's XML elements
+ */
+ parse: function (attributes, options) {
+ var $objectDOM;
+ var nonNumericDomainNodeList;
+ var domainNodeList; // the list of domain elements
+ var domain; // the text or enumerated domain to parse
+ var domainObject; // The parsed domain object to be added to attributes.nonNumericDomain
+ var rootNodeName; // Name of the fragment root elements
+
+ if (attributes.objectDOM) {
+ rootNodeName = $(attributes.objectDOM)[0].localName;
+ $objectDOM = $(attributes.objectDOM);
+ } else if (attributes.objectXML) {
+ rootNodeName = $(attributes.objectXML)[0].localName;
+ $objectDOM = $($(attributes.objectXML)[0]);
+ } else {
+ return {};
+ }
+
+ // do we have an appropriate measurementScale tree?
+ var index = _.indexOf(
+ ["measurementscale", "nominal", "ordinal"],
+ rootNodeName,
+ );
+ if (index == -1) {
+ throw new Error(
+ "The measurement scale XML does not have a root " +
+ "node of 'measurementScale', 'nominal', or 'ordinal'.",
+ );
+ }
+
+ // If measurementScale is present, add it
+ if (rootNodeName == "measurementscale") {
+ attributes.measurementScale = $objectDOM
+ .children()
+ .first()[0].localName;
+ $objectDOM = $objectDOM.children().first();
+ } else {
+ attributes.measurementScale = $objectDOM.localName;
+ }
+
+ nonNumericDomainNodeList = $objectDOM.find("nonnumericdomain");
+
+ if (nonNumericDomainNodeList && nonNumericDomainNodeList.length > 0) {
+ domainNodeList = nonNumericDomainNodeList[0].children;
+ } else {
+ // No content is available, return
+ return attributes;
+ }
+
+ // Initialize an array of nonNumericDomain objects
+ attributes.nonNumericDomain = [];
+
+ // Set each domain if we have it
+ if (domainNodeList && domainNodeList.length > 0) {
+ _.each(
+ domainNodeList,
+ function (domain) {
+ if (domain) {
+ // match the camelCase name since DOMParser() is XML-aware
+ switch (domain.localName) {
+ case "textdomain":
+ domainObject = this.parseTextDomain(domain);
+ break;
+ case "enumerateddomain":
+ domainObject = this.parseEnumeratedDomain(domain);
+ break;
+ case "references":
+ // TODO: Support references
+ console.log(
+ "In EMLNonNumericDomain.parse()" +
+ "We don't support references yet ",
+ );
+ default:
+ console.log(
+ "Unrecognized nonNumericDomain: " + domain.nodeName,
+ );
}
+ }
+ attributes.nonNumericDomain.push(domainObject);
},
+ this,
+ );
+ }
+
+ // Add in the textDomain content if present
+ // TODO
+
+ attributes.objectDOM = $objectDOM[0];
+
+ return attributes;
+ },
+
+ /* Parse the nonNumericDomain/textDomain fragment
+ * returning an object with a textDomain attribute, like:
+ * {
+ * textDomain: {
+ * definition: "Some definition",
+ * pattern: ["*", "\w", "[0-9]"],
+ * source: "Some source reference"
+ * }
+ * }
+ */
+ parseTextDomain: function (domain) {
+ var domainObject = {};
+ domainObject.textDomain = {};
+ var xmlID;
+ var definition;
+ let patterns = [];
+ var source;
+
+ // Add the XML id attribute
+ if ($(domain).attr("id")) {
+ xmlID = $(domain).attr("id");
+ } else {
+ // Generate an id if it's not found
+ xmlID = DataONEObject.generateId();
+ }
+ domainObject.textDomain.xmlID = xmlID;
+
+ // Add the definition
+ definition = $(domain).children("definition").text();
+ domainObject.textDomain.definition = definition;
+
+ // Add the pattern
+ _.each($(domain).children("pattern"), function (pattern) {
+ patterns.push(pattern.textContent);
+ });
+ domainObject.textDomain.pattern = patterns;
+
+ // Add the source
+ source = $(domain).children("sourced").text();
+ domainObject.textDomain.source = source;
+
+ return domainObject;
+ },
+
+ /* Parse the nonNumericDomain/enumeratedDomain fragment
+ * returning an object with an enumeratedDomain attribute, like:
+ * var emlCitation = {};
+ * var nonNumericDomain = [
+ * {
+ * enumeratedDomain: {
+ * codeDefinition: [
+ * {
+ * code: "Some code", // required
+ * definition: "Some definition", // required
+ * source: "Some source"
+ * } // repeatable
+ * ]
+ * }
+ * }, // or
+ * {
+ * enumeratedDomain: {
+ * externalCodeSet: [
+ * {
+ * codesetName: "Some code", // required
+ * citation: [emlCitation], // one of citation or codesetURL
+ * codesetURL: ["Some URL"] // is required, both repeatable
+ * } // repeatable
+ * ]
+ * }
+ * }, // or
+ * {
+ * enumeratedDomain: {
+ * entityCodeList: {
+ * entityReference: "Some reference", // required
+ * valueAttributeReference: "Some attr reference", // required
+ * definitionAttributeReference: "Some definition attr reference", // required
+ * orderAttributeReference: "Some order attr reference"
+ * }
+ * }
+ * }
+ * ]
+ */
+ parseEnumeratedDomain: function (domain) {
+ var domainObject = {};
+ domainObject.enumeratedDomain = {};
+ var codeDefinition = {};
+ var externalCodeSet = {};
+ var entityCodeList = {};
+ var xmlID;
+
+ // Add the XML id attribute
+ if ($(domain).attr("id")) {
+ xmlID = $(domain).attr("id");
+ } else {
+ // Generate an id if it's not found
+ xmlID = DataONEObject.generateId();
+ }
+ domainObject.enumeratedDomain.xmlID = xmlID;
+
+ // Add the codeDefinitions if present
+ var codeDefinitions = $(domain).children("codedefinition");
+
+ if (codeDefinitions.length) {
+ domainObject.enumeratedDomain.codeDefinition = [];
+ _.each(codeDefinitions, function (codeDef) {
+ var code = $(codeDef).children("code").text();
+ var definition = $(codeDef).children("definition").text();
+ var source = $(codeDef).children("sourced").text() || undefined;
+ domainObject.enumeratedDomain.codeDefinition.push({
+ code: code,
+ definition: definition,
+ source: source,
+ });
+ });
+ }
+ return domainObject;
+ },
+
+ /* Serialize the model to XML */
+ serialize: function () {
+ var objectDOM = this.updateDOM();
+ var xmlString = objectDOM.outerHTML;
+
+ // Camel-case the XML
+ xmlString = this.formatXML(xmlString);
+
+ return xmlString;
+ },
+
+ /* Copy the original XML DOM and update it with new values from the model */
+ updateDOM: function (objectDOM) {
+ var objectDOM;
+ var xmlID; // The id of the textDomain or enumeratedDomain fragment
+ var nonNumericDomainNode;
+ var domainType; // Either textDomain or enumeratedDomain
+ var $domainInDOM; // The jQuery object of the text or enumerated domain from the DOM
+ var nodeToInsertAfter;
+ var domainNode; // Either a textDomain or enumeratedDomain node
+ var definitionNode;
+ var patternNode;
+ var sourceNode;
+ var enumeratedDomainNode;
+ var codeDefinitions;
+ var codeDefinitionNode;
+ var codeNode;
+
+ var type = this.get("measurementScale");
+ if (typeof type === "undefined") {
+ console.warn("Defaulting to an nominal measurementScale.");
+ type = "nominal";
+ }
+ if (!objectDOM) {
+ objectDOM = this.get("objectDOM");
+ }
+ var objectXML = this.get("objectXML");
+
+ // If present, use the cached DOM
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+
+ // otherwise, use the cached XML
+ } else if (objectXML) {
+ objectDOM = $(objectXML)[0].cloneNode(true);
+
+ // This is new, create it
+ } else {
+ objectDOM = document.createElement(type);
+ }
+
+ if (this.get("nonNumericDomain").length) {
+ // Update each nonNumericDomain in the DOM
+ _.each(
+ this.get("nonNumericDomain"),
+ function (domain, i) {
+ // Is this a textDomain or enumeratedDomain?
+ if (typeof domain.textDomain === "object") {
+ domainType = "textDomain";
+ xmlID = domain.textDomain.xmlID;
+ } else if (typeof domain.enumeratedDomain === "object") {
+ domainType = "enumeratedDomain";
+ xmlID = domain.enumeratedDomain.xmlID;
+ } else {
+ console.log("Unrecognized NonNumericDomain type. Skipping.");
+ // TODO: Handle references here
+ }
- /**
- * The map of lower case to camel case node names
- * needed to deal with parsing issues with $.parseHTML().
- * Use this until we can figure out issues with $.parseXML().
- * @type {object}
- */
- nodeNameMap: {
- "nonnumericdomain": "nonNumericDomain",
- "enumerateddomain": "enumeratedDomain",
- "textdomain": "textDomain",
- "externalcodeset": "externalCodeSet",
- "codesetname": "codesetName",
- "codeseturl": "codesetURL",
- "entityCodeList": "entityCodeList",
- "entityreference": "entityReference",
- "valueattributereference": "valueAttributeReference",
- "definitionattributereference": "definitionAttributeReference",
- "orderattributereference": "orderAttributeReference",
- "sourced": "source"
- },
-
- /* Initialize an EMLNonNumericDomain object */
- initialize: function(attributes, options) {
-
- this.on("change:nonNumericDomain", this.trickleUpChange);
- },
-
- /**
- * Parse the incoming measurementScale's XML elements
- */
- parse: function(attributes, options) {
-
- var $objectDOM;
- var nonNumericDomainNodeList;
- var domainNodeList; // the list of domain elements
- var domain; // the text or enumerated domain to parse
- var domainObject; // The parsed domain object to be added to attributes.nonNumericDomain
- var rootNodeName; // Name of the fragment root elements
-
- if ( attributes.objectDOM ) {
- rootNodeName = $(attributes.objectDOM)[0].localName;
- $objectDOM = $(attributes.objectDOM);
- } else if ( attributes.objectXML ) {
- rootNodeName = $(attributes.objectXML)[0].localName;
- $objectDOM = $($(attributes.objectXML)[0]);
- } else {
- return {};
- }
-
- // do we have an appropriate measurementScale tree?
- var index = _.indexOf(["measurementscale", "nominal", "ordinal"], rootNodeName);
- if ( index == -1 ) {
- throw new Error("The measurement scale XML does not have a root " +
- "node of 'measurementScale', 'nominal', or 'ordinal'.");
- }
-
- // If measurementScale is present, add it
- if ( rootNodeName == "measurementscale" ) {
- attributes.measurementScale = $objectDOM.children().first()[0].localName;
- $objectDOM = $objectDOM.children().first();
- } else {
- attributes.measurementScale = $objectDOM.localName;
- }
-
- nonNumericDomainNodeList = $objectDOM.find("nonnumericdomain");
-
- if ( nonNumericDomainNodeList && nonNumericDomainNodeList.length > 0 ) {
- domainNodeList = nonNumericDomainNodeList[0].children;
-
- } else {
- // No content is available, return
- return attributes;
- }
-
- // Initialize an array of nonNumericDomain objects
- attributes.nonNumericDomain = [];
-
- // Set each domain if we have it
- if ( domainNodeList && domainNodeList.length > 0 ) {
-
- _.each(domainNodeList, function(domain) {
- if ( domain ) {
- // match the camelCase name since DOMParser() is XML-aware
- switch ( domain.localName ) {
- case "textdomain":
- domainObject = this.parseTextDomain(domain);
- break;
- case "enumerateddomain":
- domainObject = this.parseEnumeratedDomain(domain);
- break;
- case "references":
- // TODO: Support references
- console.log("In EMLNonNumericDomain.parse()" +
- "We don't support references yet ");
- default:
- console.log("Unrecognized nonNumericDomain: " + domain.nodeName);
- }
- }
- attributes.nonNumericDomain.push(domainObject);
- }, this);
-
+ // Update the existing DOM node by id
+ if (xmlID && $(objectDOM).find("#" + xmlID).length) {
+ if (domainType === "textDomain") {
+ let originalTextDomain = $(objectDOM)
+ .find("#" + xmlID)
+ .find("textdomain");
+
+ //If there are existing textDomain nodes in the DOM, update them
+ if (originalTextDomain.length) {
+ let updatedTextDomain = this.updateTextDomain(
+ domain.textDomain,
+ originalTextDomain,
+ );
+ originalTextDomain.replaceWith(updatedTextDomain);
+ }
+ //If there are no textDomain nodes in the DOM, create new ones
+ else {
+ //Create new textDomain nodes
+ let newTextDomain = this.createTextDomain(
+ domain.textDomain,
+ );
+
+ //Insert the new textDomain nodes into the nonNumericDomain node
+ $($(objectDOM).children("nonnumericdomain")[i]).html(
+ newTextDomain,
+ );
+ }
+ } else if (domainType === "enumeratedDomain") {
+ this.updateEnumeratedDomainDOM(
+ domain.enumeratedDomain,
+ $domainInDOM,
+ );
}
- // Add in the textDomain content if present
- // TODO
-
- attributes.objectDOM = $objectDOM[0];
-
- return attributes;
- },
+ //If there is no XML ID but there are the same number of nonNumericDomains in the model and DOM
+ } else if (
+ this.get("nonNumericDomain").length ==
+ $(objectDOM).children("nonnumericdomain").length &&
+ $(objectDOM).children("nonnumericdomain").length >= i
+ ) {
+ //If this is a text domain,
+ if (typeof domain.textDomain === "object") {
+ let originalTextDomain = $(
+ $(objectDOM).children("nonnumericdomain")[i],
+ ).find("textdomain");
+
+ //If there are existing textDomain nodes in the DOM, update them
+ if (originalTextDomain.length) {
+ let updatedTextDomain = this.updateTextDomain(
+ domain.textDomain,
+ originalTextDomain,
+ );
+ originalTextDomain.replaceWith(updatedTextDomain);
+ }
+ //If there are no textDomain nodes in the DOM, create new ones
+ else {
+ //Create new textDomain nodes
+ var newTextDomain = this.createTextDomain(
+ domain.textDomain,
+ );
+
+ //Insert the new textDomain nodes into the nonNumericDomain node
+ $($(objectDOM).children("nonnumericdomain")[i]).html(
+ newTextDomain,
+ );
+ }
+ } else if (typeof domain.enumeratedDomain === "object") {
+ //Get the nonNumericDomain node from the DOM
+ var nonNumericDomainNode =
+ $(objectDOM).children("nonnumericdomain")[i],
+ enumeratedDomain =
+ $(nonNumericDomainNode).children("enumerateddomain");
+
+ if (enumeratedDomain.length) {
+ this.updateEnumeratedDomainDOM(
+ domain.enumeratedDomain,
+ enumeratedDomain,
+ );
+ } else {
+ //Remove the textDomain node and replace it with an enumeratedDomain node
+ var textDomainToReplace = $(objectDOM).find("textdomain");
- /* Parse the nonNumericDomain/textDomain fragment
- * returning an object with a textDomain attribute, like:
- * {
- * textDomain: {
- * definition: "Some definition",
- * pattern: ["*", "\w", "[0-9]"],
- * source: "Some source reference"
- * }
- * }
- */
- parseTextDomain: function(domain) {
- var domainObject = {};
- domainObject.textDomain = {};
- var xmlID;
- var definition;
- let patterns = [];
- var source;
-
- // Add the XML id attribute
- if ( $(domain).attr("id") ) {
- xmlID = $(domain).attr("id");
- } else {
- // Generate an id if it's not found
- xmlID = DataONEObject.generateId();
+ if (textDomainToReplace.length > 0) {
+ $(textDomainToReplace[i]).replaceWith(
+ this.createEnumeratedDomainDOM(domain.enumeratedDomain),
+ );
+ } else {
+ nonNumericDomainNode.html(
+ this.createEnumeratedDomainDOM(
+ domain.enumeratedDomain,
+ document.createElement("enumerateddomain"),
+ ),
+ );
+ }
+ }
}
- domainObject.textDomain.xmlID = xmlID;
- // Add the definition
- definition = $(domain).children("definition").text();
- domainObject.textDomain.definition = definition;
-
- // Add the pattern
- _.each($(domain).children("pattern"), function(pattern) {
- patterns.push(pattern.textContent);
- });
- domainObject.textDomain.pattern = patterns;
-
- // Add the source
- source = $(domain).children("sourced").text();
- domainObject.textDomain.source = source;
+ // Otherwise append to the DOM
+ } else {
+ // Add the nonNumericDomain element
+ nonNumericDomainNode =
+ document.createElement("nonnumericdomain");
+
+ if (domainType === "textDomain") {
+ // Add the definiton element
+ domainNode = document.createElement("textdomain");
+ if (domain.textDomain.definition) {
+ definitionNode = document.createElement("definition");
+ $(definitionNode).text(domain.textDomain.definition);
+ $(domainNode).append(definitionNode);
+ }
- return domainObject;
- },
+ // Add the pattern element(s)
+ if (domain.textDomain.pattern.length) {
+ _.each(
+ domain.textDomain.pattern,
+ function (pattern) {
+ patternNode = document.createElement("pattern");
+ $(patternNode).text(pattern);
+ $(domainNode).append(patternNode);
+ },
+ this,
+ );
+ }
- /* Parse the nonNumericDomain/enumeratedDomain fragment
- * returning an object with an enumeratedDomain attribute, like:
- * var emlCitation = {};
- * var nonNumericDomain = [
- * {
- * enumeratedDomain: {
- * codeDefinition: [
- * {
- * code: "Some code", // required
- * definition: "Some definition", // required
- * source: "Some source"
- * } // repeatable
- * ]
- * }
- * }, // or
- * {
- * enumeratedDomain: {
- * externalCodeSet: [
- * {
- * codesetName: "Some code", // required
- * citation: [emlCitation], // one of citation or codesetURL
- * codesetURL: ["Some URL"] // is required, both repeatable
- * } // repeatable
- * ]
- * }
- * }, // or
- * {
- * enumeratedDomain: {
- * entityCodeList: {
- * entityReference: "Some reference", // required
- * valueAttributeReference: "Some attr reference", // required
- * definitionAttributeReference: "Some definition attr reference", // required
- * orderAttributeReference: "Some order attr reference"
- * }
- * }
- * }
- * ]
- */
- parseEnumeratedDomain: function(domain) {
- var domainObject = {};
- domainObject.enumeratedDomain = {};
- var codeDefinition = {};
- var externalCodeSet = {};
- var entityCodeList = {};
- var xmlID;
-
- // Add the XML id attribute
- if ( $(domain).attr("id") ) {
- xmlID = $(domain).attr("id");
+ // Add the source element
+ if (domain.textDomain.source) {
+ sourceNode = document.createElement("sourced"); // Accommodate parseHTML() with "d"
+ $(sourceNode).text(domain.textDomain.source);
+ $(domainNode).append(sourceNode);
+ }
+ } else if (domainType === "enumeratedDomain") {
+ nonNumericDomainNode.append(
+ this.createEnumeratedDomainDOM(domain.enumeratedDomain),
+ );
} else {
- // Generate an id if it's not found
- xmlID = DataONEObject.generateId();
+ console.log(
+ "The domainType: " + domainType + " is not recognized.",
+ );
}
- domainObject.enumeratedDomain.xmlID = xmlID;
-
- // Add the codeDefinitions if present
- var codeDefinitions = $(domain).children("codedefinition");
-
- if ( codeDefinitions.length ) {
- domainObject.enumeratedDomain.codeDefinition = [];
- _.each(codeDefinitions, function(codeDef) {
- var code = $(codeDef).children("code").text();
- var definition = $(codeDef).children("definition").text();
- var source = $(codeDef).children("sourced").text() || undefined;
- domainObject.enumeratedDomain.codeDefinition.push({
- code: code,
- definition: definition,
- source: source
- });
- })
- }
- return domainObject;
+ $(nonNumericDomainNode).append(domainNode);
+ $(objectDOM).append(nonNumericDomainNode);
+ }
},
-
- /* Serialize the model to XML */
- serialize: function() {
- var objectDOM = this.updateDOM();
- var xmlString = objectDOM.outerHTML;
-
- // Camel-case the XML
- xmlString = this.formatXML(xmlString);
-
- return xmlString;
+ this,
+ );
+ } else {
+ // We have no content, so can't create a valid domain
+ console.log(
+ "In EMLNonNumericDomain.updateDOM(),\n" +
+ "references are not handled yet. Returning undefined.",
+ );
+ // TODO: handle references here
+ return undefined;
+ }
+ return objectDOM;
+ },
+
+ /*
+ * Update the codeDefinitionList in the first enumeratedDomain
+ * found in the nonNumericDomain array.
+ * TODO: Refactor this to support externalCodeSet and entityCodeList
+ * TODO: Support the source field
+ * TODO: Support repeatable enumeratedDomains
+ * var nonNumericDomain = [
+ * {
+ * enumeratedDomain: {
+ * codeDefinition: [
+ * {
+ * code: "Some code", // required
+ * definition: "Some definition", // required
+ * source: "Some source"
+ * } // repeatable
+ * ]
+ * }
+ * }
+ * ]
+ */
+ updateEnumeratedDomain: function (code, definition, index) {
+ var nonNumericDomain = this.get("nonNumericDomain");
+ var enumeratedDomain = {};
+ var codeDefinitions;
+
+ if (typeof code == "string" && !code.trim().length) {
+ code = "";
+ }
+
+ if (typeof definition == "string" && !definition.trim().length) {
+ definition = "";
+ }
+
+ // Create from scratch
+ if (
+ !nonNumericDomain.length ||
+ !nonNumericDomain[0] ||
+ !nonNumericDomain[0].enumeratedDomain
+ ) {
+ nonNumericDomain[0] = {
+ enumeratedDomain: {
+ codeDefinition: [
+ {
+ code: code,
+ definition: definition,
+ },
+ ],
},
+ };
+ }
+ // Update existing
+ else {
+ enumeratedDomain = this.get("nonNumericDomain")[0].enumeratedDomain;
+
+ if (typeof enumeratedDomain !== "undefined") {
+ //If there is no code or definition, then remove it from the code list
+ if (!code && code !== 0 && !definition && definition !== 0) {
+ this.removeCode(index);
+ } else if (enumeratedDomain.codeDefinition.length >= index) {
+ //Create a new code object and insert it into the array
+ enumeratedDomain.codeDefinition[index] = {
+ code: code,
+ definition: definition,
+ };
+ } else {
+ //Create a new code object and append it to the end of the array
+ enumeratedDomain.codeDefinition.push({
+ code: code,
+ definition: definition,
+ });
+ }
+ }
+ }
+
+ //Manually trigger the change event since we're updating an array on the model
+ this.trigger("change:nonNumericDomain");
+ },
+
+ /*
+ * Given a `codeDefinition` HTML node and an enumeratedDomain list,
+ * this function will update the HTML node code definitions with
+ * all the code definitions listed in the enumeratedDomain
+ *
+ * @param {object} enumeratedDomain - A literal object with an array of codeDefinitions
+ * @param {DOM Element or jQuery Object} - A DOM Element or jQuery selection that represents the node
+ */
+ updateEnumeratedDomainDOM: function (
+ enumeratedDomain,
+ enumeratedDomainNode,
+ ) {
+ if (enumeratedDomain.codeDefinition.length) {
+ // Update each codeDefinition
+ _.each(
+ enumeratedDomain.codeDefinition,
+ function (codeDef, i) {
+ var codeDefNode = $(
+ $(enumeratedDomainNode).children("codedefinition")[i],
+ );
+
+ if (!codeDefNode.length) {
+ codeDefNode = $(document.createElement("codedefinition"));
+ $(enumeratedDomainNode).append(codeDefNode);
+ }
- /* Copy the original XML DOM and update it with new values from the model */
- updateDOM: function(objectDOM) {
- var objectDOM;
- var xmlID; // The id of the textDomain or enumeratedDomain fragment
- var nonNumericDomainNode;
- var domainType; // Either textDomain or enumeratedDomain
- var $domainInDOM; // The jQuery object of the text or enumerated domain from the DOM
- var nodeToInsertAfter;
- var domainNode; // Either a textDomain or enumeratedDomain node
- var definitionNode;
- var patternNode;
- var sourceNode;
- var enumeratedDomainNode;
- var codeDefinitions;
- var codeDefinitionNode;
- var codeNode;
-
- var type = this.get("measurementScale");
- if ( typeof type === "undefined") {
- console.warn("Defaulting to an nominal measurementScale.");
- type = "nominal";
- }
- if ( ! objectDOM ) {
- objectDOM = this.get("objectDOM");
- }
- var objectXML = this.get("objectXML");
-
- // If present, use the cached DOM
- if ( objectDOM ) {
- objectDOM = objectDOM.cloneNode(true);
-
- // otherwise, use the cached XML
- } else if ( objectXML ){
- objectDOM = $(objectXML)[0].cloneNode(true);
-
- // This is new, create it
- } else {
- objectDOM = document.createElement(type);
+ // Update the required code element
+ if (codeDef.code) {
+ var codeNode = codeDefNode.children("code");
+ //If there is no XML node, make one
+ if (!codeNode.length) {
+ codeNode = $(document.createElement("code"));
+ codeDefNode.append(codeNode);
}
- if ( this.get("nonNumericDomain").length ) {
-
- // Update each nonNumericDomain in the DOM
- _.each(this.get("nonNumericDomain"), function(domain, i) {
-
- // Is this a textDomain or enumeratedDomain?
- if ( typeof domain.textDomain === "object" ) {
- domainType = "textDomain";
- xmlID = domain.textDomain.xmlID;
-
- } else if ( typeof domain.enumeratedDomain === "object" ) {
- domainType = "enumeratedDomain";
- xmlID = domain.enumeratedDomain.xmlID;
- } else {
- console.log("Unrecognized NonNumericDomain type. Skipping.");
- // TODO: Handle references here
- }
-
- // Update the existing DOM node by id
- if ( xmlID && $(objectDOM).find("#" + xmlID).length ) {
-
- if ( domainType === "textDomain" ) {
-
- let originalTextDomain = $(objectDOM).find("#" + xmlID).find("textdomain");
-
- //If there are existing textDomain nodes in the DOM, update them
- if( originalTextDomain.length ){
- let updatedTextDomain = this.updateTextDomain(domain.textDomain, originalTextDomain);
- originalTextDomain.replaceWith(updatedTextDomain);
- }
- //If there are no textDomain nodes in the DOM, create new ones
- else{
- //Create new textDomain nodes
- let newTextDomain = this.createTextDomain(domain.textDomain);
-
- //Insert the new textDomain nodes into the nonNumericDomain node
- $( $(objectDOM).children("nonnumericdomain")[i] ).html( newTextDomain );
- }
-
- } else if ( domainType === "enumeratedDomain") {
-
- this.updateEnumeratedDomainDOM(domain.enumeratedDomain, $domainInDOM);
- }
-
-
- //If there is no XML ID but there are the same number of nonNumericDomains in the model and DOM
- } else if( this.get("nonNumericDomain").length == $(objectDOM).children("nonnumericdomain").length
- && $(objectDOM).children("nonnumericdomain").length >= i){
-
- //If this is a text domain,
- if( typeof domain.textDomain === "object" ){
-
- let originalTextDomain = $($(objectDOM).children("nonnumericdomain")[i]).find("textdomain");
-
- //If there are existing textDomain nodes in the DOM, update them
- if( originalTextDomain.length ){
- let updatedTextDomain = this.updateTextDomain(domain.textDomain, originalTextDomain);
- originalTextDomain.replaceWith(updatedTextDomain);
- }
- //If there are no textDomain nodes in the DOM, create new ones
- else{
- //Create new textDomain nodes
- var newTextDomain = this.createTextDomain(domain.textDomain);
-
- //Insert the new textDomain nodes into the nonNumericDomain node
- $( $(objectDOM).children("nonnumericdomain")[i] ).html( newTextDomain );
- }
-
- }
- else if(typeof domain.enumeratedDomain === "object"){
- //Get the nonNumericDomain node from the DOM
- var nonNumericDomainNode = $(objectDOM).children("nonnumericdomain")[i],
- enumeratedDomain = $(nonNumericDomainNode).children("enumerateddomain");
-
- if( enumeratedDomain.length ){
- this.updateEnumeratedDomainDOM(domain.enumeratedDomain, enumeratedDomain);
- }
- else{
- //Remove the textDomain node and replace it with an enumeratedDomain node
- var textDomainToReplace = $(objectDOM).find("textdomain");
-
- if( textDomainToReplace.length > 0 ){
- $(textDomainToReplace[i]).replaceWith(this.createEnumeratedDomainDOM(domain.enumeratedDomain));
- }
- else{
- nonNumericDomainNode.html(this.createEnumeratedDomainDOM(domain.enumeratedDomain, document.createElement("enumerateddomain")));
- }
-
-
- }
- }
-
- // Otherwise append to the DOM
- } else {
-
- // Add the nonNumericDomain element
- nonNumericDomainNode = document.createElement("nonnumericdomain");
-
- if ( domainType === "textDomain" ) {
-
- // Add the definiton element
- domainNode = document.createElement("textdomain");
- if ( domain.textDomain.definition ) {
- definitionNode = document.createElement("definition");
- $(definitionNode).text(domain.textDomain.definition);
- $(domainNode).append(definitionNode);
- }
-
- // Add the pattern element(s)
- if ( domain.textDomain.pattern.length ) {
- _.each(domain.textDomain.pattern, function(pattern) {
- patternNode = document.createElement("pattern");
- $(patternNode).text(pattern);
- $(domainNode).append(patternNode);
- }, this);
- }
-
- // Add the source element
- if ( domain.textDomain.source ) {
- sourceNode = document.createElement("sourced"); // Accommodate parseHTML() with "d"
- $(sourceNode).text(domain.textDomain.source);
- $(domainNode).append(sourceNode);
- }
-
- } else if ( domainType === "enumeratedDomain" ) {
-
- nonNumericDomainNode.append(this.createEnumeratedDomainDOM(domain.enumeratedDomain));
-
- } else {
- console.log("The domainType: " + domainType + " is not recognized.");
- }
- $(nonNumericDomainNode).append(domainNode);
- $(objectDOM).append(nonNumericDomainNode);
- }
- }, this);
-
- } else {
- // We have no content, so can't create a valid domain
- console.log("In EMLNonNumericDomain.updateDOM(),\n" +
- "references are not handled yet. Returning undefined.");
- // TODO: handle references here
- return undefined;
- }
- return objectDOM;
- },
-
- /*
- * Update the codeDefinitionList in the first enumeratedDomain
- * found in the nonNumericDomain array.
- * TODO: Refactor this to support externalCodeSet and entityCodeList
- * TODO: Support the source field
- * TODO: Support repeatable enumeratedDomains
- * var nonNumericDomain = [
- * {
- * enumeratedDomain: {
- * codeDefinition: [
- * {
- * code: "Some code", // required
- * definition: "Some definition", // required
- * source: "Some source"
- * } // repeatable
- * ]
- * }
- * }
- * ]
- */
- updateEnumeratedDomain: function(code, definition, index) {
- var nonNumericDomain = this.get("nonNumericDomain");
- var enumeratedDomain = {};
- var codeDefinitions;
-
- if( typeof code == "string" && !code.trim().length ){
- code = "";
- }
+ //Add the code text to the node
+ codeNode.text(codeDef.code);
+ } else {
+ codeDefNode.children("code").remove();
+ }
- if( typeof definition == "string" && !definition.trim().length ){
- definition = "";
- }
+ // Update the required definition element
+ if (codeDef.definition) {
+ var defNode = codeDefNode.children("definition");
- // Create from scratch
- if ( !nonNumericDomain.length || !nonNumericDomain[0] || !nonNumericDomain[0].enumeratedDomain) {
- nonNumericDomain[0] = {
- enumeratedDomain: {
- codeDefinition: [
- {
- code: code,
- definition: definition
- }
- ]
- }
- }
- }
- // Update existing
- else {
- enumeratedDomain = this.get("nonNumericDomain")[0].enumeratedDomain;
-
- if ( typeof enumeratedDomain !== "undefined" ) {
-
- //If there is no code or definition, then remove it from the code list
- if( !code && code !== 0 && !definition && definition !== 0 ){
- this.removeCode(index);
- }
- else if ( enumeratedDomain.codeDefinition.length >= index ) {
- //Create a new code object and insert it into the array
- enumeratedDomain.codeDefinition[index] = {
- code: code,
- definition: definition
- }
- }
- else {
- //Create a new code object and append it to the end of the array
- enumeratedDomain.codeDefinition.push({
- code: code,
- definition: definition
- });
- }
- }
+ //If there is no XML node, make one
+ if (!defNode.length) {
+ defNode = $(document.createElement("definition"));
+ codeDefNode.append(defNode);
}
- //Manually trigger the change event since we're updating an array on the model
- this.trigger("change:nonNumericDomain");
- },
-
- /*
- * Given a `codeDefinition` HTML node and an enumeratedDomain list,
- * this function will update the HTML node code definitions with
- * all the code definitions listed in the enumeratedDomain
- *
- * @param {object} enumeratedDomain - A literal object with an array of codeDefinitions
- * @param {DOM Element or jQuery Object} - A DOM Element or jQuery selection that represents the node
- */
- updateEnumeratedDomainDOM: function( enumeratedDomain, enumeratedDomainNode ){
-
- if ( enumeratedDomain.codeDefinition.length ) {
-
- // Update each codeDefinition
- _.each(enumeratedDomain.codeDefinition, function(codeDef, i) {
-
- var codeDefNode = $($(enumeratedDomainNode).children("codedefinition")[i]);
-
- if( !codeDefNode.length ){
- codeDefNode = $(document.createElement("codedefinition"));
- $(enumeratedDomainNode).append(codeDefNode);
- }
-
- // Update the required code element
- if ( codeDef.code ) {
- var codeNode = codeDefNode.children("code");
-
- //If there is no XML node, make one
- if( !codeNode.length ){
- codeNode = $(document.createElement("code"));
- codeDefNode.append(codeNode);
- }
-
- //Add the code text to the node
- codeNode.text(codeDef.code);
- }
- else{
- codeDefNode.children("code").remove();
- }
-
- // Update the required definition element
- if ( codeDef.definition ) {
- var defNode = codeDefNode.children("definition");
-
- //If there is no XML node, make one
- if( !defNode.length ){
- defNode = $(document.createElement("definition"));
- codeDefNode.append(defNode);
- }
-
- //Add the definition text to the node
- defNode.text(codeDef.definition);
- }
- else{
- codeDefNode.children("definition").remove();
- }
-
- // Update the optional source element
- if ( codeDef.source ) {
- // Accommodate parseHTML() with source"d"
- var sourceNode = codeDefNode.children("sourced");
-
- //If there is no XML node, make one
- if( !sourceNode.length ){
- sourceNode = $(document.createElement("sourced"));
- codeDefNode.append(sourceNode);
- }
-
- sourceNode.text(codeDef.source);
- }
- else{
- codeDefNode.children("sourced").remove();
- }
-
- }, this);
-
- // If there are more codeDefinition nodes than there are codeDefinitions
- // in the model, then we need to remove the extraneous nodes
- var numNodes = $(enumeratedDomainNode).children("codedefinition").length,
- numCodes = enumeratedDomain.codeDefinition.length;
-
- if( numNodes > numCodes ){
- //Get the extraneous nodes by selecting the last X child elements
- var nodesToRemove = $(enumeratedDomainNode).children("codedefinition").slice( (numNodes - numCodes) * -1 );
- //Remove them from the DOM
- nodesToRemove.remove();
- }
-
- } else if ( domain.enumeratedDomain.externalCodeSet ) {
- // TODO Handle externalCodeSet
-
- } else if ( domain.enumeratedDomain.entityCodeList ) {
- // TODO Handle entityCodeList
+ //Add the definition text to the node
+ defNode.text(codeDef.definition);
+ } else {
+ codeDefNode.children("definition").remove();
}
- return enumeratedDomainNode;
+ // Update the optional source element
+ if (codeDef.source) {
+ // Accommodate parseHTML() with source"d"
+ var sourceNode = codeDefNode.children("sourced");
- },
+ //If there is no XML node, make one
+ if (!sourceNode.length) {
+ sourceNode = $(document.createElement("sourced"));
+ codeDefNode.append(sourceNode);
+ }
- /*
- * Given an enumeratedDomain list, this function will create an
- * HTML element with all the code definitions
- * listed in the enumeratedDomain object
- *
- * @param {object} enumeratedDomain - A literal object with an array of codeDefinitions
- * @return {DOM Element} - An DOM element tree with code definitions
- */
- createEnumeratedDomainDOM: function( enumeratedDomain ){
-
- var enumeratedDomainNode = document.createElement("enumerateddomain");
-
- if ( enumeratedDomain.codeDefinition.length ) {
-
- // Add each codeDefinition
- _.each(enumeratedDomain.codeDefinition, function(codeDef) {
-
- var codeDefinitionNode = document.createElement("codedefinition");
-
- // Add the required code element
- if ( codeDef.code ) {
- var codeNode = document.createElement("code");
- $(codeNode).text(codeDef.code);
- $(codeDefinitionNode).append(codeNode);
- }
-
- // Add the required definition element
- if ( codeDef.definition ) {
- var definitionNode = document.createElement("definition");
- $(definitionNode).text(codeDef.definition);
- $(codeDefinitionNode).append(definitionNode);
- }
-
- // Add the optional source element
- if ( codeDef.source ) {
- var sourceNode = document.createElement("sourced"); // Accommodate parseHTML() with "d"
- $(sourceNode).text(codeDef.source);
- $(codeDefinitionNode).append(sourceNode);
- }
- $(enumeratedDomainNode).append(codeDefinitionNode);
-
- }, this);
-
- } else if ( domain.enumeratedDomain.externalCodeSet ) {
- // TODO Handle externalCodeSet
-
- } else if ( domain.enumeratedDomain.entityCodeList ) {
- // TODO Handle entityCodeList
+ sourceNode.text(codeDef.source);
+ } else {
+ codeDefNode.children("sourced").remove();
}
-
- return enumeratedDomainNode;
-
},
-
- /*
- * Given a textDomain object, and textDomain DOM object, this function
- * will update all the DOM elements with the textDomain object values
- *
- * @param {object} textDomain - A literal object representing an EML text domain
- * @param {DOM Element} textDomainEl - The DOM Element to update
- * @return {DOM Element} - An DOM element tree to update
- */
- updateTextDomain: function(textDomain, textDomainEl){
-
- if( typeof textDomainEl === "undefined" || (typeof textDomainEl == "object" && textDomainEl.length == 0) )
- var textDomainEl = document.createElement("textdomain");
-
- //Create a shortcut to the jQuery object of the text domain element
- var $textDomainEl = $(textDomainEl);
-
- var definitionEl = $textDomainEl.find("definition");
-
- //Update the definition element text
- if( definitionEl.length > 0 )
- definitionEl.text(textDomain.definition);
- else {
- $textDomainEl.prepend( $(document.createElement("definition")).text(textDomain.definition) );
+ this,
+ );
+
+ // If there are more codeDefinition nodes than there are codeDefinitions
+ // in the model, then we need to remove the extraneous nodes
+ var numNodes =
+ $(enumeratedDomainNode).children("codedefinition").length,
+ numCodes = enumeratedDomain.codeDefinition.length;
+
+ if (numNodes > numCodes) {
+ //Get the extraneous nodes by selecting the last X child elements
+ var nodesToRemove = $(enumeratedDomainNode)
+ .children("codedefinition")
+ .slice((numNodes - numCodes) * -1);
+ //Remove them from the DOM
+ nodesToRemove.remove();
+ }
+ } else if (domain.enumeratedDomain.externalCodeSet) {
+ // TODO Handle externalCodeSet
+ } else if (domain.enumeratedDomain.entityCodeList) {
+ // TODO Handle entityCodeList
+ }
+
+ return enumeratedDomainNode;
+ },
+
+ /*
+ * Given an enumeratedDomain list, this function will create an
+ * HTML element with all the code definitions
+ * listed in the enumeratedDomain object
+ *
+ * @param {object} enumeratedDomain - A literal object with an array of codeDefinitions
+ * @return {DOM Element} - An DOM element tree with code definitions
+ */
+ createEnumeratedDomainDOM: function (enumeratedDomain) {
+ var enumeratedDomainNode = document.createElement("enumerateddomain");
+
+ if (enumeratedDomain.codeDefinition.length) {
+ // Add each codeDefinition
+ _.each(
+ enumeratedDomain.codeDefinition,
+ function (codeDef) {
+ var codeDefinitionNode = document.createElement("codedefinition");
+
+ // Add the required code element
+ if (codeDef.code) {
+ var codeNode = document.createElement("code");
+ $(codeNode).text(codeDef.code);
+ $(codeDefinitionNode).append(codeNode);
}
- // Remove existing patterns
- $textDomainEl.find("pattern").remove();
-
- // Add any new patterns
- if ( textDomain.pattern && textDomain.pattern.length ) {
-
- let patterns = Array.from(textDomain.pattern).reverse();
-
- _.each(patterns, function(pattern) {
-
- //Don't serialize strings with only empty characters
- if( typeof pattern == "string" && !pattern.trim().length )
- return;
-
- var patternNode = document.createElement("pattern");
-
- $(patternNode).text(pattern);
-
- // Prepend before the sourced element if present
- if ( $textDomainEl.find("sourced").length ) {
- $textDomainEl.find("sourced").before(patternNode);
- } else {
- $textDomainEl.append(patternNode);
- }
- });
+ // Add the required definition element
+ if (codeDef.definition) {
+ var definitionNode = document.createElement("definition");
+ $(definitionNode).text(codeDef.definition);
+ $(codeDefinitionNode).append(definitionNode);
}
- // Update any new source
- if ( textDomain.source ) {
- if ( $textDomainEl.find("sourced").length ) {
- $textDomainEl.find("sourced").text(textDomain.source);
- } else {
- //
- var src = document.createElement("sourced");
- src.textContent = textDomain.source;
- $textDomainEl.find("textDomain").append(src);
- }
- } else {
- // Remove the source in the DOM not present in the textDomain
- // TODO: Uncomment this when we support "source" in the UI
- // $domainInDOM.children("source").remove();
-
+ // Add the optional source element
+ if (codeDef.source) {
+ var sourceNode = document.createElement("sourced"); // Accommodate parseHTML() with "d"
+ $(sourceNode).text(codeDef.source);
+ $(codeDefinitionNode).append(sourceNode);
}
-
- return textDomainEl;
+ $(enumeratedDomainNode).append(codeDefinitionNode);
},
-
- /*
- * Creates a textDomain DOM object with the textDomain object values
- *
- * @param {object} textDomain - A literal object representing an EML text domain
- * @return {DOM Element} - An DOM element tree to update
- */
- createTextDomain: function(textDomain){
- var textDomainEl = document.createElement("textdomain");
-
- this.updateTextDomain(textDomain, textDomainEl);
-
- return textDomainEl;
-
- },
-
- /*
- * Get the DOM node preceding the given nodeName
- * to find what position in the EML document
- * the named node should be appended
- */
- getEMLPosition: function(objectDOM, nodeName) {
- // TODO: set the node order
- var nodeOrder = ["enumerateddomain", "textdomain"];
-
- var position = _.indexOf(nodeOrder, nodeName);
-
- // Append to the bottom if not found
- if ( position == -1 ) {
- return $(objectDOM).children().last()[0];
- }
-
- // Otherwise, go through each node in the node list and find the
- // position where this node will be inserted after
- for ( var i = position - 1; i >= 0; i-- ) {
- if ( $(objectDOM).find(nodeOrder[i]).length ) {
- return $(objectDOM).find(nodeOrder[i]).last()[0];
- }
+ this,
+ );
+ } else if (domain.enumeratedDomain.externalCodeSet) {
+ // TODO Handle externalCodeSet
+ } else if (domain.enumeratedDomain.entityCodeList) {
+ // TODO Handle entityCodeList
+ }
+
+ return enumeratedDomainNode;
+ },
+
+ /*
+ * Given a textDomain object, and textDomain DOM object, this function
+ * will update all the DOM elements with the textDomain object values
+ *
+ * @param {object} textDomain - A literal object representing an EML text domain
+ * @param {DOM Element} textDomainEl - The DOM Element to update
+ * @return {DOM Element} - An DOM element tree to update
+ */
+ updateTextDomain: function (textDomain, textDomainEl) {
+ if (
+ typeof textDomainEl === "undefined" ||
+ (typeof textDomainEl == "object" && textDomainEl.length == 0)
+ )
+ var textDomainEl = document.createElement("textdomain");
+
+ //Create a shortcut to the jQuery object of the text domain element
+ var $textDomainEl = $(textDomainEl);
+
+ var definitionEl = $textDomainEl.find("definition");
+
+ //Update the definition element text
+ if (definitionEl.length > 0) definitionEl.text(textDomain.definition);
+ else {
+ $textDomainEl.prepend(
+ $(document.createElement("definition")).text(textDomain.definition),
+ );
+ }
+
+ // Remove existing patterns
+ $textDomainEl.find("pattern").remove();
+
+ // Add any new patterns
+ if (textDomain.pattern && textDomain.pattern.length) {
+ let patterns = Array.from(textDomain.pattern).reverse();
+
+ _.each(patterns, function (pattern) {
+ //Don't serialize strings with only empty characters
+ if (typeof pattern == "string" && !pattern.trim().length) return;
+
+ var patternNode = document.createElement("pattern");
+
+ $(patternNode).text(pattern);
+
+ // Prepend before the sourced element if present
+ if ($textDomainEl.find("sourced").length) {
+ $textDomainEl.find("sourced").before(patternNode);
+ } else {
+ $textDomainEl.append(patternNode);
+ }
+ });
+ }
+
+ // Update any new source
+ if (textDomain.source) {
+ if ($textDomainEl.find("sourced").length) {
+ $textDomainEl.find("sourced").text(textDomain.source);
+ } else {
+ //
+ var src = document.createElement("sourced");
+ src.textContent = textDomain.source;
+ $textDomainEl.find("textDomain").append(src);
+ }
+ } else {
+ // Remove the source in the DOM not present in the textDomain
+ // TODO: Uncomment this when we support "source" in the UI
+ // $domainInDOM.children("source").remove();
+ }
+
+ return textDomainEl;
+ },
+
+ /*
+ * Creates a textDomain DOM object with the textDomain object values
+ *
+ * @param {object} textDomain - A literal object representing an EML text domain
+ * @return {DOM Element} - An DOM element tree to update
+ */
+ createTextDomain: function (textDomain) {
+ var textDomainEl = document.createElement("textdomain");
+
+ this.updateTextDomain(textDomain, textDomainEl);
+
+ return textDomainEl;
+ },
+
+ /*
+ * Get the DOM node preceding the given nodeName
+ * to find what position in the EML document
+ * the named node should be appended
+ */
+ getEMLPosition: function (objectDOM, nodeName) {
+ // TODO: set the node order
+ var nodeOrder = ["enumerateddomain", "textdomain"];
+
+ var position = _.indexOf(nodeOrder, nodeName);
+
+ // Append to the bottom if not found
+ if (position == -1) {
+ return $(objectDOM).children().last()[0];
+ }
+
+ // Otherwise, go through each node in the node list and find the
+ // position where this node will be inserted after
+ for (var i = position - 1; i >= 0; i--) {
+ if ($(objectDOM).find(nodeOrder[i]).length) {
+ return $(objectDOM).find(nodeOrder[i]).last()[0];
+ }
+ }
+ },
+
+ /* Let the top level package know of attribute changes from this object */
+ trickleUpChange: function () {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ },
+
+ validate: function () {
+ var errors = {};
+
+ if (!this.get("nonNumericDomain").length)
+ errors.nonNumericDomain = "Choose a possible value type.";
+ else {
+ var domain = this.get("nonNumericDomain")[0];
+
+ _.each(
+ Object.keys(domain),
+ function (key) {
+ //For enumerated domain types
+ if (key == "enumeratedDomain" && domain[key].codeDefinition) {
+ var isEmpty =
+ domain[key].codeDefinition.length == 0 ? true : false;
+
+ //Validate the list of codes
+ for (var i = 0; i < domain[key].codeDefinition.length; i++) {
+ var codeDef = domain[key].codeDefinition[i];
+
+ //If either the code or definition is missing in at least one codeDefinition set,
+ //then this model is invalid
+ if (
+ (codeDef.code && !codeDef.definition) ||
+ (!codeDef.code && codeDef.definition)
+ ) {
+ errors.enumeratedDomain =
+ "Provide both a code and definition in each row.";
+ i = domain[key].codeDefinition.length;
+ } else if (
+ domain[key].codeDefinition.length == 1 &&
+ !codeDef.code &&
+ !codeDef.definition
+ )
+ isEmpty = true;
}
- },
-
- /* Let the top level package know of attribute changes from this object */
- trickleUpChange: function(){
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- },
-
- validate: function(){
- var errors = {};
-
- if( !this.get("nonNumericDomain").length )
- errors.nonNumericDomain = "Choose a possible value type.";
- else{
- var domain = this.get("nonNumericDomain")[0];
-
- _.each(Object.keys(domain), function(key){
-
- //For enumerated domain types
- if(key == "enumeratedDomain" && domain[key].codeDefinition){
-
- var isEmpty = (domain[key].codeDefinition.length == 0) ? true : false;
-
- //Validate the list of codes
- for(var i=0; i < domain[key].codeDefinition.length; i++){
-
- var codeDef = domain[key].codeDefinition[i];
-
- //If either the code or definition is missing in at least one codeDefinition set,
- //then this model is invalid
- if((codeDef.code && !codeDef.definition) || (!codeDef.code && codeDef.definition)){
- errors.enumeratedDomain = "Provide both a code and definition in each row.";
- i = domain[key].codeDefinition.length;
- }
- else if(domain[key].codeDefinition.length == 1 && !codeDef.code && !codeDef.definition)
- isEmpty = true;
-
- }
-
- if(isEmpty)
- errors.enumeratedDomain = "Define at least one code and definition.";
- }
- else if(key == "textDomain" && (typeof domain[key] != "object" || !domain[key].definition)){
- errors.definition = "Provide a description of the kind of text allowed.";
- }
-
- }, this);
-
- }
-
- if(Object.keys(errors).length)
- return errors;
- else{
-
- this.trigger("valid");
-
- return;
- }
- },
-
- /*
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211 or false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
- var emlModel = this.get("parentModel"),
- tries = 0;
-
- while (emlModel.type !== "EML" && tries < 6){
- emlModel = emlModel.get("parentModel");
- tries++;
+ if (isEmpty)
+ errors.enumeratedDomain =
+ "Define at least one code and definition.";
+ } else if (
+ key == "textDomain" &&
+ (typeof domain[key] != "object" || !domain[key].definition)
+ ) {
+ errors.definition =
+ "Provide a description of the kind of text allowed.";
}
-
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
-
},
-
- removeCode: function(index){
- var codeToRemove = this.get("nonNumericDomain")[0].enumeratedDomain.codeDefinition[index];
-
- var newCodeList = _.without(this.get("nonNumericDomain")[0].enumeratedDomain.codeDefinition, codeToRemove);
-
- this.get("nonNumericDomain")[0].enumeratedDomain.codeDefinition = newCodeList;
-
- this.trigger("change:nonNumericDomain");
- }
-
- });
-
- return EMLNonNumericDomain;
- }
-);
+ this,
+ );
+ }
+
+ if (Object.keys(errors).length) return errors;
+ else {
+ this.trigger("valid");
+
+ return;
+ }
+ },
+
+ /*
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211 or false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
+ var emlModel = this.get("parentModel"),
+ tries = 0;
+
+ while (emlModel.type !== "EML" && tries < 6) {
+ emlModel = emlModel.get("parentModel");
+ tries++;
+ }
+
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
+ },
+
+ removeCode: function (index) {
+ var codeToRemove =
+ this.get("nonNumericDomain")[0].enumeratedDomain.codeDefinition[
+ index
+ ];
+
+ var newCodeList = _.without(
+ this.get("nonNumericDomain")[0].enumeratedDomain.codeDefinition,
+ codeToRemove,
+ );
+
+ this.get("nonNumericDomain")[0].enumeratedDomain.codeDefinition =
+ newCodeList;
+
+ this.trigger("change:nonNumericDomain");
+ },
+ },
+ );
+
+ return EMLNonNumericDomain;
+});
diff --git a/src/js/models/metadata/eml211/EMLNumericDomain.js b/src/js/models/metadata/eml211/EMLNumericDomain.js
index 93c7e9c6b..a90715f2e 100644
--- a/src/js/models/metadata/eml211/EMLNumericDomain.js
+++ b/src/js/models/metadata/eml211/EMLNumericDomain.js
@@ -1,433 +1,431 @@
-define(["jquery", "underscore", "backbone",
- "models/DataONEObject"],
- function($, _, Backbone, DataONEObject) {
-
- /**
- * @class EMLNumericDomain
- * @classdesc EMLNumericDomain represents the measurement scale of an interval
- * or ratio measurement scale attribute, and is an extension of
- * EMLMeasurementScale.
- * @classcategory Models/Metadata/EML211
- * @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html#AttributeType_measurementScale
- * @extends Backbone.Model
- */
- var EMLNumericDomain = Backbone.Model.extend(
- /** @lends EMLNumericDomain.prototype */{
-
- type: "EMLNumericDomain",
-
- /* Attributes of an EMLNonNumericDomain object */
- defaults: function(){
- return {
- /* Attributes from EML, extends attributes from EMLMeasurementScale */
- measurementScale: null, // the required name of this measurement scale
- unit: null, // the required standard or custom unit definition
- precision: null, // the precision of the observed number
- numericDomain: {} // a required numeric domain object or its reference
- }
- },
-
- /**
- * The map of lower case to camel case node names
- * needed to deal with parsing issues with $.parseHTML().
- * Use this until we can figure out issues with $.parseXML().
- */
- nodeNameMap: {
- "standardunit": "standardUnit",
- "customunit": "customUnit",
- "numericdomain": "numericDomain",
- "numbertype": "numberType"
- },
-
- /* Initialize an EMLNonNumericDomain object */
- initialize: function(attributes, options) {
-
- this.on("change:numericDomain", this.trickleUpChange);
-
- },
-
- /**
- * Parse the incoming measurementScale's XML elements
- */
- parse: function(attributes, options) {
-
- var $objectDOM;
- var measurementScale;
- var rootNodeName;
-
- if ( attributes.objectDOM ) {
- rootNodeName = $(attributes.objectDOM)[0].localName;
- $objectDOM = $(attributes.objectDOM);
- } else if ( attributes.objectXML ) {
- rootNodeName = $(attributes.objectXML)[0].localName;
- $objectDOM = $($(attributes.objectXML)[0]);
- } else {
- return {};
- }
-
- // do we have an appropriate measurementScale tree?
- var index = _.indexOf(["measurementscale","interval", "ratio"], rootNodeName);
- if ( index == -1 ) {
- throw new Error("The measurement scale XML does not have a root " +
- "node of 'measurementScale', 'interval', or 'ratio'.");
- }
-
- // If measurementScale is present, add it
- if ( rootNodeName == "measurementscale" ) {
- attributes.measurementScale = $objectDOM.children().first()[0].localName;
- $objectDOM = $objectDOM.children().first();
- } else {
- attributes.measurementScale = $objectDOM.localName;
- }
-
-
- // Add the unit
- var unitObject = {};
- var unit = $objectDOM.children("unit");
- var standardUnitNodes = unit.children("standardunit"),
- customUnitNodes = unit.children("customunit"),
- standardUnit,
- customUnit;
-
- if ( standardUnitNodes.length ) {
- standardUnit = standardUnitNodes.text();
-
- if( standardUnit )
- unitObject.standardUnit = standardUnit;
+define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
+ $,
+ _,
+ Backbone,
+ DataONEObject,
+) {
+ /**
+ * @class EMLNumericDomain
+ * @classdesc EMLNumericDomain represents the measurement scale of an interval
+ * or ratio measurement scale attribute, and is an extension of
+ * EMLMeasurementScale.
+ * @classcategory Models/Metadata/EML211
+ * @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html#AttributeType_measurementScale
+ * @extends Backbone.Model
+ */
+ var EMLNumericDomain = Backbone.Model.extend(
+ /** @lends EMLNumericDomain.prototype */ {
+ type: "EMLNumericDomain",
+
+ /* Attributes of an EMLNonNumericDomain object */
+ defaults: function () {
+ return {
+ /* Attributes from EML, extends attributes from EMLMeasurementScale */
+ measurementScale: null, // the required name of this measurement scale
+ unit: null, // the required standard or custom unit definition
+ precision: null, // the precision of the observed number
+ numericDomain: {}, // a required numeric domain object or its reference
+ };
+ },
+
+ /**
+ * The map of lower case to camel case node names
+ * needed to deal with parsing issues with $.parseHTML().
+ * Use this until we can figure out issues with $.parseXML().
+ */
+ nodeNameMap: {
+ standardunit: "standardUnit",
+ customunit: "customUnit",
+ numericdomain: "numericDomain",
+ numbertype: "numberType",
+ },
+
+ /* Initialize an EMLNonNumericDomain object */
+ initialize: function (attributes, options) {
+ this.on("change:numericDomain", this.trickleUpChange);
+ },
+
+ /**
+ * Parse the incoming measurementScale's XML elements
+ */
+ parse: function (attributes, options) {
+ var $objectDOM;
+ var measurementScale;
+ var rootNodeName;
+
+ if (attributes.objectDOM) {
+ rootNodeName = $(attributes.objectDOM)[0].localName;
+ $objectDOM = $(attributes.objectDOM);
+ } else if (attributes.objectXML) {
+ rootNodeName = $(attributes.objectXML)[0].localName;
+ $objectDOM = $($(attributes.objectXML)[0]);
+ } else {
+ return {};
+ }
+
+ // do we have an appropriate measurementScale tree?
+ var index = _.indexOf(
+ ["measurementscale", "interval", "ratio"],
+ rootNodeName,
+ );
+ if (index == -1) {
+ throw new Error(
+ "The measurement scale XML does not have a root " +
+ "node of 'measurementScale', 'interval', or 'ratio'.",
+ );
+ }
+
+ // If measurementScale is present, add it
+ if (rootNodeName == "measurementscale") {
+ attributes.measurementScale = $objectDOM
+ .children()
+ .first()[0].localName;
+ $objectDOM = $objectDOM.children().first();
+ } else {
+ attributes.measurementScale = $objectDOM.localName;
+ }
+
+ // Add the unit
+ var unitObject = {};
+ var unit = $objectDOM.children("unit");
+ var standardUnitNodes = unit.children("standardunit"),
+ customUnitNodes = unit.children("customunit"),
+ standardUnit,
+ customUnit;
+
+ if (standardUnitNodes.length) {
+ standardUnit = standardUnitNodes.text();
+
+ if (standardUnit) unitObject.standardUnit = standardUnit;
+ } else if (customUnitNodes.length) {
+ customUnit = customUnitNodes.text();
+
+ if (customUnit) unitObject.customUnit = customUnit;
+ }
+
+ attributes.unit = unitObject;
+
+ // Add the precision
+ var precision = $objectDOM.children("precision").text();
+ if (precision) {
+ attributes.precision = precision;
+ }
+
+ // Add the numericDomain
+ var numericDomainObject = {};
+ var numericDomain = $objectDOM.children("numericdomain");
+ var numberType;
+ var boundsArray = [];
+ var boundsObject;
+ var bounds;
+ var minimum;
+ var maximum;
+ var references;
+ if (numericDomain) {
+ // Add the XML id of the numeric domain
+ if ($(numericDomain).attr("id")) {
+ numericDomainObject.xmlID = $(numericDomain).attr("id");
+ }
+
+ // Add the numberType
+ numberType = $(numericDomain).children("numbertype");
+
+ if (numberType) {
+ numericDomainObject.numberType = numberType.text();
+
+ // Add optional bounds
+ bounds = $(numericDomain).children("bounds");
+ if (bounds.length) {
+ _.each(bounds, function (bound) {
+ boundsObject = {}; // initialize on each
+ minimum = $(bound).children("minimum").text();
+ maximum = $(bound).children("maximum").text();
+ if (minimum && maximum) {
+ boundsObject.minimum = minimum;
+ boundsObject.maximum = maximum;
+ } else if (minimum) {
+ boundsObject.minimum = minimum;
+ } else if (maximum) {
+ boundsObject.maximum = maximum;
}
- else if( customUnitNodes.length ){
- customUnit = customUnitNodes.text();
-
- if( customUnit )
- unitObject.customUnit = customUnit;
- }
-
- attributes.unit = unitObject;
-
- // Add the precision
- var precision = $objectDOM.children("precision").text();
- if ( precision ) {
- attributes.precision = precision;
+ // If one of min or max is defined, add to the bounds array
+ if (boundsObject.minimum || boundsObject.maximum) {
+ boundsArray.push(boundsObject);
}
-
- // Add the numericDomain
- var numericDomainObject = {};
- var numericDomain = $objectDOM.children("numericdomain");
- var numberType;
- var boundsArray = [];
- var boundsObject;
- var bounds;
- var minimum;
- var maximum;
- var references;
- if ( numericDomain ) {
- // Add the XML id of the numeric domain
- if ( $(numericDomain).attr("id") ) {
- numericDomainObject.xmlID = $(numericDomain).attr("id");
- }
-
- // Add the numberType
- numberType = $(numericDomain).children("numbertype");
-
- if ( numberType ) {
- numericDomainObject.numberType = numberType.text();
-
- // Add optional bounds
- bounds = $(numericDomain).children("bounds");
- if ( bounds.length ) {
- _.each(bounds, function(bound) {
- boundsObject = {}; // initialize on each
- minimum = $(bound).children("minimum").text();
- maximum = $(bound).children("maximum").text();
- if ( minimum && maximum ) {
- boundsObject.minimum = minimum;
- boundsObject.maximum = maximum;
- } else if ( minimum ) {
- boundsObject.minimum = minimum;
- } else if ( maximum ) {
- boundsObject.maximum = maximum;
- }
- // If one of min or max is defined, add to the bounds array
- if ( boundsObject.minimum || boundsObject.maximum ) {
- boundsArray.push(boundsObject);
- }
- });
- }
- numericDomainObject.bounds = boundsArray;
-
- } else {
- // Otherwise look for references
- references = $(numericDomain).children("references");
- if ( references ) {
- numericDomainObject.references = references.text();
- }
- }
- attributes.numericDomain = numericDomainObject;
- }
- attributes.objectDOM = $objectDOM[0];
-
- return attributes;
- },
-
- /* Serialize the model to XML */
- serialize: function() {
- var objectDOM = this.updateDOM();
- var xmlString = objectDOM.outerHTML;
-
- // Camel-case the XML
- xmlString = this.formatXML(xmlString);
-
- return xmlString;
- },
-
- /* Copy the original XML DOM and update it with new values from the model */
- updateDOM: function(objectDOM) {
- var nodeToInsertAfter;
- var type = this.get("measurementScale");
- if ( typeof type === "undefined") {
- console.warn("Defaulting to an interval measurementScale.");
- type = "interval";
- }
- if ( ! objectDOM ) {
- objectDOM = this.get("objectDOM");
- }
- var objectXML = this.get("objectXML");
-
- // If present, use the cached DOM
- if ( objectDOM ) {
- objectDOM = objectDOM.cloneNode(true);
-
- // otherwise, use the cached XML
- } else if ( objectXML ){
- objectDOM = $(objectXML)[0].cloneNode(true);
-
- // This is new, create it
- } else {
- objectDOM = document.createElement(type);
-
- }
-
- // Update the unit
- var unit = this.get("unit");
- var unitNode;
- var unitTypeNode;
- if ( unit ) {
- // Remove any existing unit
- $(objectDOM).find("unit").remove();
-
- // Build a unit element, and populate a standard or custom child
- unitNode = document.createElement("unit");
-
- if ( typeof unit.standardUnit !== "undefined") {
- unitTypeNode = document.createElement("standardUnit");
- $(unitTypeNode).text(unit.standardUnit);
- } else if ( typeof unit.customUnit !== "undefined" ) {
- unitTypeNode = document.createElement("customUnit");
- $(unitTypeNode).text(unit.customUnit);
- } else {
- // Hmm, unit isn't an object?
- // Default to a standard unit
- unitTypeNode = document.createElement("standardUnit");
- if ( typeof unit === "string") {
- $(unitTypeNode).text(unit);
- console.warn("EMLNumericDomain.unit should be an object.");
- } else {
- // We're really striking out. Default to dimensionless.
- $(unitTypeNode).text("dimensionless");
- console.warn("Defaulting EMLNumericDomain.unit to dimensionless.");
- }
- }
- $(unitNode).append(unitTypeNode);
-
- // Add the unit to the DOM
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "unit");
-
- if( ! nodeToInsertAfter ) {
- $(objectDOM).prepend(unitNode);
- } else {
- $(nodeToInsertAfter).after(unitNode);
- }
- }
-
- // Update the precision
- if ( this.get("precision") ) {
- if ( $(objectDOM).find("precision").length ) {
- $(objectDOM).find("precision").text(this.get("precision"));
- } else {
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "precision");
-
- if( ! nodeToInsertAfter ) {
- $(objectDOM).append($(document.createElement("precision"))
- .text(this.get("precision"))[0]);
- } else {
- $(nodeToInsertAfter).after(
- $(document.createElement("precision"))
- .text(this.get("precision"))[0]
- );
- }
- }
- }
-
- // Update the numericDomain
- var numericDomain = this.get("numericDomain");
- var numericDomainNode = $(objectDOM).find("numericdomain")[0];
- var numberType;
- var numberTypeNode;
- var minBound;
- var maxBound;
- var boundsNode;
- var minBoundNode;
- var maxBoundNode;
- if ( numericDomain ) {
-
- var oldNumericDomainNode = $(numericDomainNode).clone();
-
- // Remove the existing numericDomainNode node
- if ( typeof numericDomainNode !== "undefined" ) {
- numericDomainNode.remove();
- }
-
- // Build the new numericDomain node
- numericDomainNode = document.createElement("numericdomain");
-
- // Do we have numberType?
- if ( typeof numericDomain.numberType !== "undefined" ) {
- numberTypeNode = document.createElement("numbertype");
- $(numberTypeNode).text(numericDomain.numberType);
- $(numericDomainNode).append(numberTypeNode);
- }
-
- // Do we have bounds?
- if ( typeof numericDomain.bounds !== "undefined" &&
- numericDomain.bounds.length ) {
-
- _.each(numericDomain.bounds, function(bound) {
- minBound = bound.minimum;
- maxBound = bound.maximum;
- boundsNode = document.createElement("bounds");
-
- var hasBounds = typeof minBound !== "undefined" || typeof maxBound !== "undefined";
-
- if ( hasBounds ) {
- // Populate the minimum element
- if ( typeof minBound !== "undefined" ) {
- minBoundNode = $(document.createElement("minimum"));
- minBoundNode.text(minBound);
-
- var existingExclusive = oldNumericDomainNode.find("minimum").attr("exclusive");
-
- if( !existingExclusive || existingExclusive === "false" )
- minBoundNode.attr("exclusive", "false");
- else
- minBoundNode.attr("exclusive", "true");
- }
-
- // Populate the maximum element
- if ( typeof maxBound !== "undefined" ) {
- maxBoundNode = $(document.createElement("maximum"));
- maxBoundNode.text(maxBound);
-
- var existingExclusive = oldNumericDomainNode.find("maximum").attr("exclusive");
-
- if( !existingExclusive || existingExclusive === "false" )
- maxBoundNode.attr("exclusive", "false");
- else
- maxBoundNode.attr("exclusive", "true");
- }
-
- $(boundsNode).append(minBoundNode, maxBoundNode);
- $(numericDomainNode).append(boundsNode);
-
- } else {
- // Do nothing. Content is missing, don't append the node
- }
- });
- } else {
- // Basically do nothing. Don't append the numericDomain element
- // TODO: handle numericDomain.references
-
- }
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "numericDomain");
-
- if( ! nodeToInsertAfter ) {
- $(objectDOM).append(numericDomainNode);
- } else {
- $(nodeToInsertAfter).after(numericDomainNode);
- }
+ });
+ }
+ numericDomainObject.bounds = boundsArray;
+ } else {
+ // Otherwise look for references
+ references = $(numericDomain).children("references");
+ if (references) {
+ numericDomainObject.references = references.text();
+ }
+ }
+ attributes.numericDomain = numericDomainObject;
+ }
+ attributes.objectDOM = $objectDOM[0];
+
+ return attributes;
+ },
+
+ /* Serialize the model to XML */
+ serialize: function () {
+ var objectDOM = this.updateDOM();
+ var xmlString = objectDOM.outerHTML;
+
+ // Camel-case the XML
+ xmlString = this.formatXML(xmlString);
+
+ return xmlString;
+ },
+
+ /* Copy the original XML DOM and update it with new values from the model */
+ updateDOM: function (objectDOM) {
+ var nodeToInsertAfter;
+ var type = this.get("measurementScale");
+ if (typeof type === "undefined") {
+ console.warn("Defaulting to an interval measurementScale.");
+ type = "interval";
+ }
+ if (!objectDOM) {
+ objectDOM = this.get("objectDOM");
+ }
+ var objectXML = this.get("objectXML");
+
+ // If present, use the cached DOM
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+
+ // otherwise, use the cached XML
+ } else if (objectXML) {
+ objectDOM = $(objectXML)[0].cloneNode(true);
+
+ // This is new, create it
+ } else {
+ objectDOM = document.createElement(type);
+ }
+
+ // Update the unit
+ var unit = this.get("unit");
+ var unitNode;
+ var unitTypeNode;
+ if (unit) {
+ // Remove any existing unit
+ $(objectDOM).find("unit").remove();
+
+ // Build a unit element, and populate a standard or custom child
+ unitNode = document.createElement("unit");
+
+ if (typeof unit.standardUnit !== "undefined") {
+ unitTypeNode = document.createElement("standardUnit");
+ $(unitTypeNode).text(unit.standardUnit);
+ } else if (typeof unit.customUnit !== "undefined") {
+ unitTypeNode = document.createElement("customUnit");
+ $(unitTypeNode).text(unit.customUnit);
+ } else {
+ // Hmm, unit isn't an object?
+ // Default to a standard unit
+ unitTypeNode = document.createElement("standardUnit");
+ if (typeof unit === "string") {
+ $(unitTypeNode).text(unit);
+ console.warn("EMLNumericDomain.unit should be an object.");
+ } else {
+ // We're really striking out. Default to dimensionless.
+ $(unitTypeNode).text("dimensionless");
+ console.warn(
+ "Defaulting EMLNumericDomain.unit to dimensionless.",
+ );
+ }
+ }
+ $(unitNode).append(unitTypeNode);
+
+ // Add the unit to the DOM
+ nodeToInsertAfter = this.getEMLPosition(objectDOM, "unit");
+
+ if (!nodeToInsertAfter) {
+ $(objectDOM).prepend(unitNode);
+ } else {
+ $(nodeToInsertAfter).after(unitNode);
+ }
+ }
+
+ // Update the precision
+ if (this.get("precision")) {
+ if ($(objectDOM).find("precision").length) {
+ $(objectDOM).find("precision").text(this.get("precision"));
+ } else {
+ nodeToInsertAfter = this.getEMLPosition(objectDOM, "precision");
+
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(
+ $(document.createElement("precision")).text(
+ this.get("precision"),
+ )[0],
+ );
+ } else {
+ $(nodeToInsertAfter).after(
+ $(document.createElement("precision")).text(
+ this.get("precision"),
+ )[0],
+ );
+ }
+ }
+ }
+
+ // Update the numericDomain
+ var numericDomain = this.get("numericDomain");
+ var numericDomainNode = $(objectDOM).find("numericdomain")[0];
+ var numberType;
+ var numberTypeNode;
+ var minBound;
+ var maxBound;
+ var boundsNode;
+ var minBoundNode;
+ var maxBoundNode;
+ if (numericDomain) {
+ var oldNumericDomainNode = $(numericDomainNode).clone();
+
+ // Remove the existing numericDomainNode node
+ if (typeof numericDomainNode !== "undefined") {
+ numericDomainNode.remove();
+ }
+
+ // Build the new numericDomain node
+ numericDomainNode = document.createElement("numericdomain");
+
+ // Do we have numberType?
+ if (typeof numericDomain.numberType !== "undefined") {
+ numberTypeNode = document.createElement("numbertype");
+ $(numberTypeNode).text(numericDomain.numberType);
+ $(numericDomainNode).append(numberTypeNode);
+ }
+
+ // Do we have bounds?
+ if (
+ typeof numericDomain.bounds !== "undefined" &&
+ numericDomain.bounds.length
+ ) {
+ _.each(numericDomain.bounds, function (bound) {
+ minBound = bound.minimum;
+ maxBound = bound.maximum;
+ boundsNode = document.createElement("bounds");
+
+ var hasBounds =
+ typeof minBound !== "undefined" ||
+ typeof maxBound !== "undefined";
+
+ if (hasBounds) {
+ // Populate the minimum element
+ if (typeof minBound !== "undefined") {
+ minBoundNode = $(document.createElement("minimum"));
+ minBoundNode.text(minBound);
+
+ var existingExclusive = oldNumericDomainNode
+ .find("minimum")
+ .attr("exclusive");
+
+ if (!existingExclusive || existingExclusive === "false")
+ minBoundNode.attr("exclusive", "false");
+ else minBoundNode.attr("exclusive", "true");
}
- return objectDOM;
- },
- formatXML: function(xmlString){
- return DataONEObject.prototype.formatXML.call(this, xmlString);
- },
+ // Populate the maximum element
+ if (typeof maxBound !== "undefined") {
+ maxBoundNode = $(document.createElement("maximum"));
+ maxBoundNode.text(maxBound);
- /**/
- getEMLPosition: function(objectDOM, nodeName) {
- // TODO: set the node order
- var nodeOrder = ["unit", "precision", "numericDomain"];
-
- var position = _.indexOf(nodeOrder, nodeName);
-
- // Append to the bottom if not found
- if ( position == -1 ) {
- return $(objectDOM).children().last()[0];
- }
+ var existingExclusive = oldNumericDomainNode
+ .find("maximum")
+ .attr("exclusive");
- // Otherwise, go through each node in the node list and find the
- // position where this node will be inserted after
- for ( var i = position - 1; i >= 0; i-- ) {
- if ( $(objectDOM).find(nodeOrder[i]).length ) {
- return $(objectDOM).find(nodeOrder[i]).last()[0];
- }
+ if (!existingExclusive || existingExclusive === "false")
+ maxBoundNode.attr("exclusive", "false");
+ else maxBoundNode.attr("exclusive", "true");
}
- },
- validate: function(){
- var errors = {};
-
- if(!this.get("unit"))
- errors.unit = "Choose a unit.";
-
- if( Object.keys(errors).length )
- return errors;
- else{
-
- this.trigger("valid");
- return;
-
- }
-
- },
-
- /*
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211 or false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
- var emlModel = this.get("parentModel"),
- tries = 0;
-
- while (emlModel.type !== "EML" && tries < 6){
- emlModel = emlModel.get("parentModel");
- tries++;
+ $(boundsNode).append(minBoundNode, maxBoundNode);
+ $(numericDomainNode).append(boundsNode);
+ } else {
+ // Do nothing. Content is missing, don't append the node
}
-
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
-
- },
-
- /* Let the top level package know of attribute changes from this object */
- trickleUpChange: function(){
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- }
-
- });
-
- return EMLNumericDomain;
- }
-);
+ });
+ } else {
+ // Basically do nothing. Don't append the numericDomain element
+ // TODO: handle numericDomain.references
+ }
+ nodeToInsertAfter = this.getEMLPosition(objectDOM, "numericDomain");
+
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(numericDomainNode);
+ } else {
+ $(nodeToInsertAfter).after(numericDomainNode);
+ }
+ }
+ return objectDOM;
+ },
+
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
+
+ /**/
+ getEMLPosition: function (objectDOM, nodeName) {
+ // TODO: set the node order
+ var nodeOrder = ["unit", "precision", "numericDomain"];
+
+ var position = _.indexOf(nodeOrder, nodeName);
+
+ // Append to the bottom if not found
+ if (position == -1) {
+ return $(objectDOM).children().last()[0];
+ }
+
+ // Otherwise, go through each node in the node list and find the
+ // position where this node will be inserted after
+ for (var i = position - 1; i >= 0; i--) {
+ if ($(objectDOM).find(nodeOrder[i]).length) {
+ return $(objectDOM).find(nodeOrder[i]).last()[0];
+ }
+ }
+ },
+
+ validate: function () {
+ var errors = {};
+
+ if (!this.get("unit")) errors.unit = "Choose a unit.";
+
+ if (Object.keys(errors).length) return errors;
+ else {
+ this.trigger("valid");
+ return;
+ }
+ },
+
+ /*
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211 or false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
+ var emlModel = this.get("parentModel"),
+ tries = 0;
+
+ while (emlModel.type !== "EML" && tries < 6) {
+ emlModel = emlModel.get("parentModel");
+ tries++;
+ }
+
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
+ },
+
+ /* Let the top level package know of attribute changes from this object */
+ trickleUpChange: function () {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ },
+ },
+ );
+
+ return EMLNumericDomain;
+});
diff --git a/src/js/models/metadata/eml211/EMLOtherEntity.js b/src/js/models/metadata/eml211/EMLOtherEntity.js
index 2dfde7c62..9e1915a87 100644
--- a/src/js/models/metadata/eml211/EMLOtherEntity.js
+++ b/src/js/models/metadata/eml211/EMLOtherEntity.js
@@ -1,187 +1,193 @@
-define(["jquery", "underscore", "backbone", "models/metadata/eml211/EMLEntity"],
- function($, _, Backbone, EMLEntity) {
-
- /**
- * @class EMLOtherEntity
- * @classdesc EMLOtherEntity represents a generic data entity, corresponding
- * with the EML otherEntity module.
- * @classcategory Models/Metadata/EML211
- * @see https://eml.ecoinformatics.org/schema/eml-dataset_xsd.html#DatasetType_otherEntity
- * @extends EMLEntity
- */
- var EMLOtherEntity = EMLEntity.extend(
- /** @lends EMLOtherEntity.prototype */{
-
- //The class name for this model
- type: "EMLOtherEntity",
-
- /* Attributes of any entity */
- defaults: function(){
- return _.extend({
-
- /* Attributes from EML */
- entityType: "data entity",
-
- /* Attributes not from EML */
- nodeOrder: [ // The order of the top level XML element nodes
- "alternateIdentifier",
- "entityName",
- "entityDescription",
- "physical",
- "coverage",
- "methods",
- "additionalInfo",
- "attributeList",
- "constraint",
- "entityType"
- ],
-
- }, EMLEntity.prototype.defaults());
- },
-
- /*
- * The map of lower case to camel case node names
- * needed to deal with parsing issues with $.parseHTML().
- * Use this until we can figure out issues with $.parseXML().
- */
- nodeNameMap: _.extend({
- "entitytype": "entityType"
-
- }, EMLEntity.prototype.nodeNameMap),
-
- /* Initialize an EMLOtherEntity object */
- initialize: function(attributes) {
-
- // if options.parse = true, Backbone will call parse()
-
- // Call super() first
- this.constructor.__super__.initialize.apply(this, [attributes]);
-
- // EMLOtherEntity-specific work
- this.set("type", "otherEntity", {silent: true});
-
- // Register change events
- this.on( "change:entityType", EMLEntity.trickleUpChange);
-
- },
-
- /*
- * Parse the incoming other entity's XML elements
- */
- parse: function(attributes, options) {
-
- var attributes = attributes || {};
-
- // Call super() first
- attributes = this.constructor.__super__.parse.apply(this, [attributes, options]);
-
- // EMLOtherEntity-specific work
- var objectXML = attributes.objectXML; // The otherEntity XML fragment
- var objectDOM; // The W3C DOM of the object XML fragment
- var $objectDOM; // The JQuery object of the XML fragment
-
- // Use the updated objectDOM if we have it
- if ( attributes.objectDOM ) {
- $objectDOM = $(attributes.objectDOM);
- } else {
- // Hmm, oddly not there, start from scratch =/
- $objectDOM = $(objectXML);
- }
-
- // Add the entityType
- attributes.entityType = $objectDOM.children("entitytype").text();
-
- return attributes;
- },
-
- /* Copy the original XML and update fields in a DOM object */
- updateDOM: function(objectDOM) {
- var nodeToInsertAfter;
- var type = this.get("type") || "otherEntity";
- if ( ! objectDOM ) {
- objectDOM = this.get("objectDOM");
- }
- var objectXML = this.get("objectXML");
-
- // If present, use the cached DOM
- if ( objectDOM ) {
- objectDOM = objectDOM.cloneNode(true);
-
- // otherwise, use the cached XML
- } else if ( objectXML ){
- objectDOM = $(objectXML)[0].cloneNode(true);
-
- // This is new, create it
- } else {
- objectDOM = document.createElement(type);
-
- }
-
- // Now call the superclass
- objectDOM = this.constructor.__super__.updateDOM.apply(this, [objectDOM]);
-
- // And then update the EMLOtherEntity-specific fields
- // Update the entityName
- if ( this.get("entityType") ) {
- if ( $(objectDOM).find("entityType").length ) {
- $(objectDOM).find("entityType").text(this.get("entityType"));
-
- } else {
- nodeToInsertAfter = this.getEMLPosition(objectDOM, "entityType");
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/metadata/eml211/EMLEntity",
+], function ($, _, Backbone, EMLEntity) {
+ /**
+ * @class EMLOtherEntity
+ * @classdesc EMLOtherEntity represents a generic data entity, corresponding
+ * with the EML otherEntity module.
+ * @classcategory Models/Metadata/EML211
+ * @see https://eml.ecoinformatics.org/schema/eml-dataset_xsd.html#DatasetType_otherEntity
+ * @extends EMLEntity
+ */
+ var EMLOtherEntity = EMLEntity.extend(
+ /** @lends EMLOtherEntity.prototype */ {
+ //The class name for this model
+ type: "EMLOtherEntity",
+
+ /* Attributes of any entity */
+ defaults: function () {
+ return _.extend(
+ {
+ /* Attributes from EML */
+ entityType: "data entity",
+
+ /* Attributes not from EML */
+ nodeOrder: [
+ // The order of the top level XML element nodes
+ "alternateIdentifier",
+ "entityName",
+ "entityDescription",
+ "physical",
+ "coverage",
+ "methods",
+ "additionalInfo",
+ "attributeList",
+ "constraint",
+ "entityType",
+ ],
+ },
+ EMLEntity.prototype.defaults(),
+ );
+ },
+
+ /*
+ * The map of lower case to camel case node names
+ * needed to deal with parsing issues with $.parseHTML().
+ * Use this until we can figure out issues with $.parseXML().
+ */
+ nodeNameMap: _.extend(
+ {
+ entitytype: "entityType",
+ },
+ EMLEntity.prototype.nodeNameMap,
+ ),
+
+ /* Initialize an EMLOtherEntity object */
+ initialize: function (attributes) {
+ // if options.parse = true, Backbone will call parse()
+
+ // Call super() first
+ this.constructor.__super__.initialize.apply(this, [attributes]);
+
+ // EMLOtherEntity-specific work
+ this.set("type", "otherEntity", { silent: true });
+
+ // Register change events
+ this.on("change:entityType", EMLEntity.trickleUpChange);
+ },
+
+ /*
+ * Parse the incoming other entity's XML elements
+ */
+ parse: function (attributes, options) {
+ var attributes = attributes || {};
+
+ // Call super() first
+ attributes = this.constructor.__super__.parse.apply(this, [
+ attributes,
+ options,
+ ]);
+
+ // EMLOtherEntity-specific work
+ var objectXML = attributes.objectXML; // The otherEntity XML fragment
+ var objectDOM; // The W3C DOM of the object XML fragment
+ var $objectDOM; // The JQuery object of the XML fragment
+
+ // Use the updated objectDOM if we have it
+ if (attributes.objectDOM) {
+ $objectDOM = $(attributes.objectDOM);
+ } else {
+ // Hmm, oddly not there, start from scratch =/
+ $objectDOM = $(objectXML);
+ }
+
+ // Add the entityType
+ attributes.entityType = $objectDOM.children("entitytype").text();
+
+ return attributes;
+ },
+
+ /* Copy the original XML and update fields in a DOM object */
+ updateDOM: function (objectDOM) {
+ var nodeToInsertAfter;
+ var type = this.get("type") || "otherEntity";
+ if (!objectDOM) {
+ objectDOM = this.get("objectDOM");
+ }
+ var objectXML = this.get("objectXML");
+
+ // If present, use the cached DOM
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+
+ // otherwise, use the cached XML
+ } else if (objectXML) {
+ objectDOM = $(objectXML)[0].cloneNode(true);
+
+ // This is new, create it
+ } else {
+ objectDOM = document.createElement(type);
+ }
+
+ // Now call the superclass
+ objectDOM = this.constructor.__super__.updateDOM.apply(this, [
+ objectDOM,
+ ]);
+
+ // And then update the EMLOtherEntity-specific fields
+ // Update the entityName
+ if (this.get("entityType")) {
+ if ($(objectDOM).find("entityType").length) {
+ $(objectDOM).find("entityType").text(this.get("entityType"));
+ } else {
+ nodeToInsertAfter = this.getEMLPosition(objectDOM, "entityType");
+
+ if (!nodeToInsertAfter) {
+ $(objectDOM).append(
+ $(document.createElement("entitytype")).text(
+ this.get("entityType"),
+ )[0],
+ );
+ } else {
+ $(nodeToInsertAfter).after(
+ $(document.createElement("entitytype")).text(
+ this.get("entityType"),
+ )[0],
+ );
+ }
+ }
+ }
- if ( ! nodeToInsertAfter ) {
- $(objectDOM).append($(document.createElement("entitytype"))
- .text(this.get("entityType"))[0]);
- } else {
- $(nodeToInsertAfter).after($(document.createElement("entitytype"))
- .text(this.get("entityType"))[0]);
- }
- }
- }
+ return objectDOM;
+ },
- return objectDOM;
- },
+ /*
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211 or false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
+ var emlModel = this.get("parentModel"),
+ tries = 0;
- /*
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211 or false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
- var emlModel = this.get("parentModel"),
- tries = 0;
+ while (emlModel.type !== "EML" && tries < 6) {
+ emlModel = emlModel.get("parentModel");
+ tries++;
+ }
- while (emlModel.type !== "EML" && tries < 6){
- emlModel = emlModel.get("parentModel");
- tries++;
- }
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
+ },
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
+ /* Serialize the EML DOM to XML */
+ serialize: function () {
+ var xmlString = "";
- },
+ // Update the superclass fields in the objectDOM first
+ var objectDOM = this.constructor.__super__.updateDOM.apply(this, []);
- /* Serialize the EML DOM to XML */
- serialize: function() {
-
- var xmlString = "";
+ // Then update the subclass fields in the objectDOM
+ // TODO
- // Update the superclass fields in the objectDOM first
- var objectDOM = this.constructor.__super__.updateDOM.apply(this, []);
-
- // Then update the subclass fields in the objectDOM
- // TODO
-
-
- this.set("objectXML", xmlString);
-
- return xmlString;
- }
+ this.set("objectXML", xmlString);
- });
+ return xmlString;
+ },
+ },
+ );
- return EMLOtherEntity;
- }
-);
+ return EMLOtherEntity;
+});
diff --git a/src/js/models/metadata/eml211/EMLParty.js b/src/js/models/metadata/eml211/EMLParty.js
index 2acdfe596..0bb38fad7 100644
--- a/src/js/models/metadata/eml211/EMLParty.js
+++ b/src/js/models/metadata/eml211/EMLParty.js
@@ -1,642 +1,690 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/DataONEObject'],
- function($, _, Backbone, DataONEObject) {
-
- /**
- * @class EMLParty
- * @classcategory Models/Metadata/EML211
- * @classdesc EMLParty represents a single Party from the EML 2.1.1 and 2.2.0
- * metadata schema. This can be a person or organization.
- * @see https://eml.ecoinformatics.org/schema/eml-party_xsd.html#ResponsibleParty
- * @extends Backbone.Model
- * @constructor
- */
+define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
+ $,
+ _,
+ Backbone,
+ DataONEObject,
+) {
+ /**
+ * @class EMLParty
+ * @classcategory Models/Metadata/EML211
+ * @classdesc EMLParty represents a single Party from the EML 2.1.1 and 2.2.0
+ * metadata schema. This can be a person or organization.
+ * @see https://eml.ecoinformatics.org/schema/eml-party_xsd.html#ResponsibleParty
+ * @extends Backbone.Model
+ * @constructor
+ */
var EMLParty = Backbone.Model.extend(
- /** @lends EMLParty.prototype */{
-
- defaults: function(){
- return {
- objectXML: null,
- objectDOM: null,
- individualName: null,
- organizationName: null,
- positionName: null,
- address: [],
- phone: [],
- fax: [],
- email: [],
- onlineUrl: [],
- roles: [],
- references: null,
- userId: [],
- xmlID: null,
- type: null,
- typeOptions: ["associatedParty", "contact", "creator", "metadataProvider", "publisher"],
- roleOptions: ["custodianSteward", "principalInvestigator", "collaboratingPrincipalInvestigator",
- "coPrincipalInvestigator", "user"],
- parentModel: null,
- removed: false //Indicates whether this model has been removed from the parent model
- }
- },
+ /** @lends EMLParty.prototype */ {
+ defaults: function () {
+ return {
+ objectXML: null,
+ objectDOM: null,
+ individualName: null,
+ organizationName: null,
+ positionName: null,
+ address: [],
+ phone: [],
+ fax: [],
+ email: [],
+ onlineUrl: [],
+ roles: [],
+ references: null,
+ userId: [],
+ xmlID: null,
+ type: null,
+ typeOptions: [
+ "associatedParty",
+ "contact",
+ "creator",
+ "metadataProvider",
+ "publisher",
+ ],
+ roleOptions: [
+ "custodianSteward",
+ "principalInvestigator",
+ "collaboratingPrincipalInvestigator",
+ "coPrincipalInvestigator",
+ "user",
+ ],
+ parentModel: null,
+ removed: false, //Indicates whether this model has been removed from the parent model
+ };
+ },
- initialize: function(options){
- if(options && options.objectDOM)
- this.set(this.parse(options.objectDOM));
+ initialize: function (options) {
+ if (options && options.objectDOM)
+ this.set(this.parse(options.objectDOM));
- if(!this.get("xmlID"))
- this.createID();
- this.on("change:roles", this.setType);
- },
+ if (!this.get("xmlID")) this.createID();
+ this.on("change:roles", this.setType);
+ },
- /*
- * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
- * Used during parse() and serialize()
- */
- nodeNameMap: function(){
- return {
- "administrativearea" : "administrativeArea",
- "associatedparty" : "associatedParty",
- "deliverypoint" : "deliveryPoint",
- "electronicmailaddress" : "electronicMailAddress",
- "givenname" : "givenName",
- "individualname" : "individualName",
- "metadataprovider" : "metadataProvider",
- "onlineurl" : "onlineUrl",
- "organizationname" : "organizationName",
- "positionname" : "positionName",
- "postalcode" : "postalCode",
- "surname" : "surName",
- "userid" : "userId"
- }
- },
+ /*
+ * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
+ * Used during parse() and serialize()
+ */
+ nodeNameMap: function () {
+ return {
+ administrativearea: "administrativeArea",
+ associatedparty: "associatedParty",
+ deliverypoint: "deliveryPoint",
+ electronicmailaddress: "electronicMailAddress",
+ givenname: "givenName",
+ individualname: "individualName",
+ metadataprovider: "metadataProvider",
+ onlineurl: "onlineUrl",
+ organizationname: "organizationName",
+ positionname: "positionName",
+ postalcode: "postalCode",
+ surname: "surName",
+ userid: "userId",
+ };
+ },
- /*
+ /*
Parse the object DOM to create the model
@param objectDOM the XML DOM to parse
@return modelJSON the resulting model attributes object
*/
- parse: function(objectDOM){
- if(!objectDOM)
- var objectDOM = this.get("objectDOM");
-
- var model = this,
- modelJSON = {};
-
- //Set the name
- var person = $(objectDOM).children("individualname, individualName");
-
- if(person.length)
- modelJSON.individualName = this.parsePerson(person);
-
- //Set the phone and fax numbers
- var phones = $(objectDOM).children("phone"),
- phoneNums = [],
- faxNums = [];
-
- phones.each(function(i, phone){
- if($(phone).attr("phonetype") == "voice")
- phoneNums.push($(phone).text());
- else if($(phone).attr("phonetype") == "facsimile")
- faxNums.push($(phone).text());
- });
-
- modelJSON.phone = phoneNums;
- modelJSON.fax = faxNums;
-
- //Set the address
- var addresses = $(objectDOM).children("address") || [],
- addressesJSON = [];
-
- addresses.each(function(i, address){
- addressesJSON.push(model.parseAddress(address));
- });
-
- modelJSON.address = addressesJSON;
-
- //Set the text fields
- modelJSON.organizationName = $(objectDOM).children("organizationname, organizationName").text() || null;
- modelJSON.positionName = $(objectDOM).children("positionname, positionName").text() || null;
- // roles
- modelJSON.roles = [];
- $(objectDOM).find("role").each(function(i,role){
- modelJSON.roles.push($(role).text());
- });
-
- //Set the id attribute
- modelJSON.xmlID = $(objectDOM).attr("id");
-
- //Email - only set it on the JSON if it exists (we want to avoid an empty string value in the array)
- if( $(objectDOM).children("electronicmailaddress, electronicMailAddress").length ){
- modelJSON.email = _.map($(objectDOM).children("electronicmailaddress, electronicMailAddress"), function(email){
- return $(email).text();
- });
- }
-
- //Online URL - only set it on the JSON if it exists (we want to avoid an empty string value in the array)
- if( $(objectDOM).find("onlineurl, onlineUrl").length ){
- // modelJSON.onlineUrl = [$(objectDOM).find("onlineurl, onlineUrl").first().text()];
- modelJSON.onlineUrl = $(objectDOM).find("onlineurl, onlineUrl").map(function(i,v) {
- return $(v).text();
- }).get();
- }
-
- //User ID - only set it on the JSON if it exists (we want to avoid an empty string value in the array)
- if( $(objectDOM).find("userid, userId").length ){
- modelJSON.userId = [$(objectDOM).find("userid, userId").first().text()];
- }
-
- return modelJSON;
- },
+ parse: function (objectDOM) {
+ if (!objectDOM) var objectDOM = this.get("objectDOM");
- parseNode: function(node){
- if(!node || (Array.isArray(node) && !node.length))
- return;
+ var model = this,
+ modelJSON = {};
- this.set($(node)[0].localName, $(node).text());
- },
+ //Set the name
+ var person = $(objectDOM).children("individualname, individualName");
- parsePerson: function(personXML){
- var person = {
- givenName: [],
- surName: "",
- salutation: []
- },
- givenName = $(personXML).find("givenname, givenName"),
- surName = $(personXML).find("surname, surName"),
- salutations = $(personXML).find("salutation");
+ if (person.length) modelJSON.individualName = this.parsePerson(person);
+
+ //Set the phone and fax numbers
+ var phones = $(objectDOM).children("phone"),
+ phoneNums = [],
+ faxNums = [];
- //Concatenate all the given names into one, for now
- //TODO: Support multiple given names
- givenName.each(function(i, name){
- if(i==0)
- person.givenName[0] = "";
+ phones.each(function (i, phone) {
+ if ($(phone).attr("phonetype") == "voice")
+ phoneNums.push($(phone).text());
+ else if ($(phone).attr("phonetype") == "facsimile")
+ faxNums.push($(phone).text());
+ });
+
+ modelJSON.phone = phoneNums;
+ modelJSON.fax = faxNums;
- person.givenName[0] += $(name).text() + " ";
+ //Set the address
+ var addresses = $(objectDOM).children("address") || [],
+ addressesJSON = [];
- if(i==givenName.length-1)
- person.givenName[0] = person.givenName[0].trim();
- });
+ addresses.each(function (i, address) {
+ addressesJSON.push(model.parseAddress(address));
+ });
- person.surName = surName.text();
+ modelJSON.address = addressesJSON;
+
+ //Set the text fields
+ modelJSON.organizationName =
+ $(objectDOM).children("organizationname, organizationName").text() ||
+ null;
+ modelJSON.positionName =
+ $(objectDOM).children("positionname, positionName").text() || null;
+ // roles
+ modelJSON.roles = [];
+ $(objectDOM)
+ .find("role")
+ .each(function (i, role) {
+ modelJSON.roles.push($(role).text());
+ });
+
+ //Set the id attribute
+ modelJSON.xmlID = $(objectDOM).attr("id");
+
+ //Email - only set it on the JSON if it exists (we want to avoid an empty string value in the array)
+ if (
+ $(objectDOM).children("electronicmailaddress, electronicMailAddress")
+ .length
+ ) {
+ modelJSON.email = _.map(
+ $(objectDOM).children(
+ "electronicmailaddress, electronicMailAddress",
+ ),
+ function (email) {
+ return $(email).text();
+ },
+ );
+ }
- salutations.each(function(i, name){
- person.salutation.push($(name).text());
- });
+ //Online URL - only set it on the JSON if it exists (we want to avoid an empty string value in the array)
+ if ($(objectDOM).find("onlineurl, onlineUrl").length) {
+ // modelJSON.onlineUrl = [$(objectDOM).find("onlineurl, onlineUrl").first().text()];
+ modelJSON.onlineUrl = $(objectDOM)
+ .find("onlineurl, onlineUrl")
+ .map(function (i, v) {
+ return $(v).text();
+ })
+ .get();
+ }
- return person;
- },
+ //User ID - only set it on the JSON if it exists (we want to avoid an empty string value in the array)
+ if ($(objectDOM).find("userid, userId").length) {
+ modelJSON.userId = [
+ $(objectDOM).find("userid, userId").first().text(),
+ ];
+ }
- parseAddress: function(addressXML){
- var address = {},
- delPoint = $(addressXML).find("deliverypoint, deliveryPoint"),
- city = $(addressXML).find("city"),
- adminArea = $(addressXML).find("administrativearea, administrativeArea"),
- postalCode = $(addressXML).find("postalcode, postalCode"),
- country = $(addressXML).find("country");
+ return modelJSON;
+ },
- address.city = city.length? city.text() : "";
- address.administrativeArea = adminArea.length? adminArea.text() : "";
- address.postalCode = postalCode.length? postalCode.text() : "";
- address.country = country.length? country.text() : "";
+ parseNode: function (node) {
+ if (!node || (Array.isArray(node) && !node.length)) return;
- //Get an array of all the address line (or delivery point) values
- var addressLines = [];
- _.each(delPoint, function(addressLine, i){
- addressLines.push($(addressLine).text());
- }, this);
+ this.set($(node)[0].localName, $(node).text());
+ },
- address.deliveryPoint = addressLines;
+ parsePerson: function (personXML) {
+ var person = {
+ givenName: [],
+ surName: "",
+ salutation: [],
+ },
+ givenName = $(personXML).find("givenname, givenName"),
+ surName = $(personXML).find("surname, surName"),
+ salutations = $(personXML).find("salutation");
+
+ //Concatenate all the given names into one, for now
+ //TODO: Support multiple given names
+ givenName.each(function (i, name) {
+ if (i == 0) person.givenName[0] = "";
+
+ person.givenName[0] += $(name).text() + " ";
+
+ if (i == givenName.length - 1)
+ person.givenName[0] = person.givenName[0].trim();
+ });
- return address;
- },
+ person.surName = surName.text();
- serialize: function(){
- var objectDOM = this.updateDOM(),
- xmlString = objectDOM.outerHTML;
+ salutations.each(function (i, name) {
+ person.salutation.push($(name).text());
+ });
- //Camel-case the XML
+ return person;
+ },
+
+ parseAddress: function (addressXML) {
+ var address = {},
+ delPoint = $(addressXML).find("deliverypoint, deliveryPoint"),
+ city = $(addressXML).find("city"),
+ adminArea = $(addressXML).find(
+ "administrativearea, administrativeArea",
+ ),
+ postalCode = $(addressXML).find("postalcode, postalCode"),
+ country = $(addressXML).find("country");
+
+ address.city = city.length ? city.text() : "";
+ address.administrativeArea = adminArea.length ? adminArea.text() : "";
+ address.postalCode = postalCode.length ? postalCode.text() : "";
+ address.country = country.length ? country.text() : "";
+
+ //Get an array of all the address line (or delivery point) values
+ var addressLines = [];
+ _.each(
+ delPoint,
+ function (addressLine, i) {
+ addressLines.push($(addressLine).text());
+ },
+ this,
+ );
+
+ address.deliveryPoint = addressLines;
+
+ return address;
+ },
+
+ serialize: function () {
+ var objectDOM = this.updateDOM(),
+ xmlString = objectDOM.outerHTML;
+
+ //Camel-case the XML
xmlString = this.formatXML(xmlString);
return xmlString;
- },
+ },
- /*
- * Updates the attributes on this model based on the application user (the app UserModel)
- */
- createFromUser: function(){
- //Create the name from the user
- var name = this.get("individualName") || {};
- name.givenName = [MetacatUI.appUserModel.get("firstName")];
- name.surName = MetacatUI.appUserModel.get("lastName");
- this.set("individualName", name);
-
- //Get the email and username
- if(MetacatUI.appUserModel.get("email"))
- this.set("email", [MetacatUI.appUserModel.get("email")]);
-
- this.set("userId", [MetacatUI.appUserModel.get("username")]);
- },
+ /*
+ * Updates the attributes on this model based on the application user (the app UserModel)
+ */
+ createFromUser: function () {
+ //Create the name from the user
+ var name = this.get("individualName") || {};
+ name.givenName = [MetacatUI.appUserModel.get("firstName")];
+ name.surName = MetacatUI.appUserModel.get("lastName");
+ this.set("individualName", name);
+
+ //Get the email and username
+ if (MetacatUI.appUserModel.get("email"))
+ this.set("email", [MetacatUI.appUserModel.get("email")]);
+
+ this.set("userId", [MetacatUI.appUserModel.get("username")]);
+ },
- /*
- * Makes a copy of the original XML DOM and updates it with the new values from the model.
- */
- updateDOM: function(){
- var type = this.get("type") || "associatedParty",
- objectDOM = this.get("objectDOM");
-
- // If there is already an XML node for this model and it is the wrong type,
- // then replace the XML node contents
- if(objectDOM && objectDOM.nodeName != type.toUpperCase()){
- objectDOM = $(document.createElement(type)).html( objectDOM.innerHTML );
- }
- // If there is already an XML node for this model and it is the correct type,
- // then simply clone the XML node
- else if(objectDOM){
- objectDOM = objectDOM.cloneNode(true);
- }
- // Otherwise, create a new XML node
- else{
- objectDOM = document.createElement(type);
- }
-
- //There needs to be at least one individual name, organization name, or position name
- if( this.nameIsEmpty() && !this.get("organizationName") && !this.get("positionName"))
- return "";
-
- var name = this.get("individualName");
- if(name){
- //Get the individualName node
- var nameNode = $(objectDOM).find("individualname");
- if(!nameNode.length){
- nameNode = document.createElement("individualname");
- $(objectDOM).prepend(nameNode);
+ /*
+ * Makes a copy of the original XML DOM and updates it with the new values from the model.
+ */
+ updateDOM: function () {
+ var type = this.get("type") || "associatedParty",
+ objectDOM = this.get("objectDOM");
+
+ // If there is already an XML node for this model and it is the wrong type,
+ // then replace the XML node contents
+ if (objectDOM && objectDOM.nodeName != type.toUpperCase()) {
+ objectDOM = $(document.createElement(type)).html(objectDOM.innerHTML);
}
+ // If there is already an XML node for this model and it is the correct type,
+ // then simply clone the XML node
+ else if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+ }
+ // Otherwise, create a new XML node
+ else {
+ objectDOM = document.createElement(type);
+ }
+
+ //There needs to be at least one individual name, organization name, or position name
+ if (
+ this.nameIsEmpty() &&
+ !this.get("organizationName") &&
+ !this.get("positionName")
+ )
+ return "";
+
+ var name = this.get("individualName");
+ if (name) {
+ //Get the individualName node
+ var nameNode = $(objectDOM).find("individualname");
+ if (!nameNode.length) {
+ nameNode = document.createElement("individualname");
+ $(objectDOM).prepend(nameNode);
+ }
+
+ //Empty the individualName node
+ $(nameNode).empty();
- //Empty the individualName node
- $(nameNode).empty();
+ // salutation[s]
+ if (!Array.isArray(name.salutation) && name.salutation)
+ name.salutation = [name.salutation];
- // salutation[s]
- if(!Array.isArray(name.salutation) && name.salutation)
- name.salutation = [name.salutation];
+ _.each(name.salutation, function (salutation) {
+ $(nameNode).prepend("" + salutation + " ");
+ });
- _.each(name.salutation, function(salutation) {
- $(nameNode).prepend("" + salutation + " ");
- });
+ //Given name
+ if (!Array.isArray(name.givenName) && name.givenName)
+ name.givenName = [name.givenName];
+ _.each(name.givenName, function (givenName) {
+ //If there is a given name string, create a givenName node
+ if (typeof givenName == "string" && givenName) {
+ $(nameNode).append("" + givenName + " ");
+ }
+ });
- //Given name
- if(!Array.isArray(name.givenName) && name.givenName) name.givenName = [name.givenName];
- _.each(name.givenName, function(givenName) {
+ // surname
+ if (name.surName)
+ $(nameNode).append("" + name.surName + " ");
+ }
+ //If there is no name set on the model, remove it from the DOM
+ else {
+ $(objectDOM).find("individualname").remove();
+ }
- //If there is a given name string, create a givenName node
- if(typeof givenName == "string" && givenName){
- $(nameNode).append("" + givenName + " ");
+ // organizationName
+ if (this.get("organizationName")) {
+ //Get the organization name node
+ if ($(objectDOM).find("organizationname").length)
+ var orgNameNode = $(objectDOM).find("organizationname").detach();
+ else var orgNameNode = document.createElement("organizationname");
+
+ //Insert the text
+ $(orgNameNode).text(this.get("organizationName"));
+
+ //If the DOM is empty, append it
+ if (!$(objectDOM).children().length) $(objectDOM).append(orgNameNode);
+ else {
+ var insertAfter = this.getEMLPosition(
+ objectDOM,
+ "organizationname",
+ );
+
+ if (insertAfter && insertAfter.length)
+ insertAfter.after(orgNameNode);
+ else $(objectDOM).prepend(orgNameNode);
}
+ }
+ //Remove the organization name node if there is no organization name
+ else {
+ var orgNameNode = $(objectDOM).find("organizationname").remove();
+ }
- });
-
- // surname
- if(name.surName)
- $(nameNode).append("" + name.surName + " ");
- }
- //If there is no name set on the model, remove it from the DOM
- else{
- $(objectDOM).find("individualname").remove();
- }
-
- // organizationName
- if(this.get("organizationName")){
- //Get the organization name node
- if($(objectDOM).find("organizationname").length)
- var orgNameNode = $(objectDOM).find("organizationname").detach();
- else
- var orgNameNode = document.createElement("organizationname");
-
- //Insert the text
- $(orgNameNode).text(this.get("organizationName"));
-
- //If the DOM is empty, append it
- if( !$(objectDOM).children().length )
- $(objectDOM).append(orgNameNode);
- else{
- var insertAfter = this.getEMLPosition(objectDOM, "organizationname");
-
- if(insertAfter && insertAfter.length)
- insertAfter.after(orgNameNode);
- else
- $(objectDOM).prepend(orgNameNode);
+ // positionName
+ if (this.get("positionName")) {
+ //Get the name node
+ if ($(objectDOM).find("positionname").length)
+ var posNameNode = $(objectDOM).find("positionname").detach();
+ else var posNameNode = document.createElement("positionname");
+
+ //Insert the text
+ $(posNameNode).text(this.get("positionName"));
+
+ //If the DOM is empty, append it
+ if (!$(objectDOM).children().length) $(objectDOM).append(posNameNode);
+ else {
+ let insertAfter = this.getEMLPosition(objectDOM, "positionname");
+ if (insertAfter) insertAfter.after(posNameNode);
+ else $(objectDOM).prepend(posNameNode);
+ }
}
- }
- //Remove the organization name node if there is no organization name
- else{
- var orgNameNode = $(objectDOM).find("organizationname").remove();
- }
-
- // positionName
- if(this.get("positionName")){
- //Get the name node
- if($(objectDOM).find("positionname").length)
- var posNameNode = $(objectDOM).find("positionname").detach();
- else
- var posNameNode = document.createElement("positionname");
-
- //Insert the text
- $(posNameNode).text(this.get("positionName"));
-
- //If the DOM is empty, append it
- if( !$(objectDOM).children().length )
- $(objectDOM).append(posNameNode);
- else{
- let insertAfter = this.getEMLPosition(objectDOM, "positionname")
- if(insertAfter)
- insertAfter.after(posNameNode);
- else
- $(objectDOM).prepend(posNameNode);
+ //Remove the position name node if there is no position name
+ else {
+ $(objectDOM).find("positionname").remove();
}
- }
- //Remove the position name node if there is no position name
- else{
- $(objectDOM).find("positionname").remove();
- }
-
- // address
- _.each(this.get("address"), function(address, i) {
-
- var addressNode = $(objectDOM).find("address")[i];
-
- if(!addressNode){
- addressNode = document.createElement("address");
- this.getEMLPosition(objectDOM, "address").after(addressNode);
- }
-
- //Remove all the delivery points since they'll be reserialized
- $(addressNode).find("deliverypoint").remove();
-
- _.each(address.deliveryPoint, function(deliveryPoint, ii){
- if(!deliveryPoint) return;
-
- var delPointNode = $(addressNode).find("deliverypoint")[ii];
-
- if(!delPointNode){
- delPointNode = document.createElement("deliverypoint");
-
- //Add the deliveryPoint node to the address node
- //Insert after the last deliveryPoint node
- var appendAfter = $(addressNode).find("deliverypoint")[ii-1];
- if(appendAfter)
- $(appendAfter).after(delPointNode);
- //Or just prepend to the beginning
- else
- $(addressNode).prepend(delPointNode);
- }
-
- $(delPointNode).text(deliveryPoint);
- });
-
- if(address.city){
- var cityNode = $(addressNode).find("city");
- if(!cityNode.length){
- cityNode = document.createElement("city");
+ // address
+ _.each(
+ this.get("address"),
+ function (address, i) {
+ var addressNode = $(objectDOM).find("address")[i];
- if(this.getEMLPosition(addressNode, "city")){
- this.getEMLPosition(addressNode, "city").after(cityNode);
- }
- else{
- $(addressNode).append(cityNode);
- }
- }
+ if (!addressNode) {
+ addressNode = document.createElement("address");
+ this.getEMLPosition(objectDOM, "address").after(addressNode);
+ }
- $(cityNode).text(address.city);
- }
- else{
- $(addressNode).find("city").remove();
- }
+ //Remove all the delivery points since they'll be reserialized
+ $(addressNode).find("deliverypoint").remove();
- if(address.administrativeArea){
- var adminAreaNode = $(addressNode).find("administrativearea");
+ _.each(address.deliveryPoint, function (deliveryPoint, ii) {
+ if (!deliveryPoint) return;
- if(!adminAreaNode.length){
- adminAreaNode = document.createElement("administrativearea");
+ var delPointNode = $(addressNode).find("deliverypoint")[ii];
- if(this.getEMLPosition(addressNode, "administrativearea")){
- this.getEMLPosition(addressNode, "administrativearea").after(adminAreaNode);
- }
- else{
- $(addressNode).append(adminAreaNode);
- }
+ if (!delPointNode) {
+ delPointNode = document.createElement("deliverypoint");
- }
+ //Add the deliveryPoint node to the address node
+ //Insert after the last deliveryPoint node
+ var appendAfter = $(addressNode).find("deliverypoint")[ii - 1];
+ if (appendAfter) $(appendAfter).after(delPointNode);
+ //Or just prepend to the beginning
+ else $(addressNode).prepend(delPointNode);
+ }
- $(adminAreaNode).text(address.administrativeArea);
- }
- else{
- $(addressNode).find("administrativearea").remove();
- }
+ $(delPointNode).text(deliveryPoint);
+ });
- if(address.postalCode){
- var postalcodeNode = $(addressNode).find("postalcode");
+ if (address.city) {
+ var cityNode = $(addressNode).find("city");
- if(!postalcodeNode.length){
- postalcodeNode = document.createElement("postalcode");
+ if (!cityNode.length) {
+ cityNode = document.createElement("city");
- if(this.getEMLPosition(addressNode, "postalcode")){
- this.getEMLPosition(addressNode, "postalcode").after(postalcodeNode);
- }
- else{
- $(addressNode).append(postalcodeNode);
- }
+ if (this.getEMLPosition(addressNode, "city")) {
+ this.getEMLPosition(addressNode, "city").after(cityNode);
+ } else {
+ $(addressNode).append(cityNode);
+ }
+ }
- }
+ $(cityNode).text(address.city);
+ } else {
+ $(addressNode).find("city").remove();
+ }
- $(postalcodeNode).text(address.postalCode);
- }
- else{
- $(addressNode).find("postalcode").remove();
- }
+ if (address.administrativeArea) {
+ var adminAreaNode = $(addressNode).find("administrativearea");
- if(address.country){
- var countryNode = $(addressNode).find("country");
+ if (!adminAreaNode.length) {
+ adminAreaNode = document.createElement("administrativearea");
- if(!countryNode.length){
- countryNode = document.createElement("country");
+ if (this.getEMLPosition(addressNode, "administrativearea")) {
+ this.getEMLPosition(addressNode, "administrativearea").after(
+ adminAreaNode,
+ );
+ } else {
+ $(addressNode).append(adminAreaNode);
+ }
+ }
- if(this.getEMLPosition(addressNode, "country")){
- this.getEMLPosition(addressNode, "country").after(countryNode);
- }
- else{
- $(addressNode).append(countryNode);
- }
+ $(adminAreaNode).text(address.administrativeArea);
+ } else {
+ $(addressNode).find("administrativearea").remove();
+ }
- }
+ if (address.postalCode) {
+ var postalcodeNode = $(addressNode).find("postalcode");
- $(countryNode).text(address.country);
- }
- else{
- $(addressNode).find("country").remove();
- }
+ if (!postalcodeNode.length) {
+ postalcodeNode = document.createElement("postalcode");
- }, this);
+ if (this.getEMLPosition(addressNode, "postalcode")) {
+ this.getEMLPosition(addressNode, "postalcode").after(
+ postalcodeNode,
+ );
+ } else {
+ $(addressNode).append(postalcodeNode);
+ }
+ }
- if( this.get("address").length == 0 ){
- $(objectDOM).find("address").remove();
- }
-
- // phone[s]
- $(objectDOM).find("phone[phonetype='voice']").remove();
- _.each(this.get("phone"), function(phone) {
+ $(postalcodeNode).text(address.postalCode);
+ } else {
+ $(addressNode).find("postalcode").remove();
+ }
+
+ if (address.country) {
+ var countryNode = $(addressNode).find("country");
+
+ if (!countryNode.length) {
+ countryNode = document.createElement("country");
+
+ if (this.getEMLPosition(addressNode, "country")) {
+ this.getEMLPosition(addressNode, "country").after(
+ countryNode,
+ );
+ } else {
+ $(addressNode).append(countryNode);
+ }
+ }
+
+ $(countryNode).text(address.country);
+ } else {
+ $(addressNode).find("country").remove();
+ }
+ },
+ this,
+ );
- var phoneNode = $(document.createElement("phone")).attr("phonetype", "voice").text(phone);
- this.getEMLPosition(objectDOM, "phone").after(phoneNode);
+ if (this.get("address").length == 0) {
+ $(objectDOM).find("address").remove();
+ }
- }, this);
-
- // fax[es]
- $(objectDOM).find("phone[phonetype='facsimile']").remove();
- _.each(this.get("fax"), function(fax) {
-
- var faxNode = $(document.createElement("phone")).attr("phonetype", "facsimile").text(fax);
- this.getEMLPosition(objectDOM, "phone").after(faxNode);
-
- }, this);
-
- // electronicMailAddress[es]
- $(objectDOM).find("electronicmailaddress").remove();
- _.each(this.get("email"), function(email) {
-
- var emailNode = document.createElement("electronicmailaddress");
- this.getEMLPosition(objectDOM, "electronicmailaddress").after(emailNode);
-
- $(emailNode).text(email);
-
- }, this);
-
- // online URL[es]
- $(objectDOM).find("onlineurl").remove();
- _.each(this.get("onlineUrl"), function(onlineUrl, i) {
-
- var urlNode = document.createElement("onlineurl");
- this.getEMLPosition(objectDOM, "onlineurl").after(urlNode);
-
- $(urlNode).text(onlineUrl);
-
- }, this);
-
- //user ID
- var userId = Array.isArray(this.get("userId")) ? this.get("userId") : [this.get("userId")];
- _.each(userId, function(id) {
- if(!id) return;
-
- var idNode = $(objectDOM).find("userid");
-
- //Create the userid node
- if(!idNode.length){
-
- idNode = $(document.createElement("userid"));
+ // phone[s]
+ $(objectDOM).find("phone[phonetype='voice']").remove();
+ _.each(
+ this.get("phone"),
+ function (phone) {
+ var phoneNode = $(document.createElement("phone"))
+ .attr("phonetype", "voice")
+ .text(phone);
+ this.getEMLPosition(objectDOM, "phone").after(phoneNode);
+ },
+ this,
+ );
+
+ // fax[es]
+ $(objectDOM).find("phone[phonetype='facsimile']").remove();
+ _.each(
+ this.get("fax"),
+ function (fax) {
+ var faxNode = $(document.createElement("phone"))
+ .attr("phonetype", "facsimile")
+ .text(fax);
+ this.getEMLPosition(objectDOM, "phone").after(faxNode);
+ },
+ this,
+ );
+
+ // electronicMailAddress[es]
+ $(objectDOM).find("electronicmailaddress").remove();
+ _.each(
+ this.get("email"),
+ function (email) {
+ var emailNode = document.createElement("electronicmailaddress");
+ this.getEMLPosition(objectDOM, "electronicmailaddress").after(
+ emailNode,
+ );
+
+ $(emailNode).text(email);
+ },
+ this,
+ );
+
+ // online URL[es]
+ $(objectDOM).find("onlineurl").remove();
+ _.each(
+ this.get("onlineUrl"),
+ function (onlineUrl, i) {
+ var urlNode = document.createElement("onlineurl");
+ this.getEMLPosition(objectDOM, "onlineurl").after(urlNode);
+
+ $(urlNode).text(onlineUrl);
+ },
+ this,
+ );
+
+ //user ID
+ var userId = Array.isArray(this.get("userId"))
+ ? this.get("userId")
+ : [this.get("userId")];
+ _.each(
+ userId,
+ function (id) {
+ if (!id) return;
+
+ var idNode = $(objectDOM).find("userid");
+
+ //Create the userid node
+ if (!idNode.length) {
+ idNode = $(document.createElement("userid"));
+
+ this.getEMLPosition(objectDOM, "userid").after(idNode);
+ }
- this.getEMLPosition(objectDOM, "userid").after(idNode);
- }
+ //If this is an orcid identifier, format it correctly
+ if (this.isOrcid(id)) {
+ // Add the directory attribute
+ idNode.attr("directory", "https://orcid.org");
+
+ //If this ORCID does not start with "http"
+ if (id.indexOf("http") == -1) {
+ //If this is an ORCID with just the 16-digit numbers and hyphens, then add
+ // the https://orcid.org/ prefix to it
+ if (id.length == 19) {
+ id = "https://orcid.org/" + id;
+ }
+ //If it starts with "orcid.org", then add the "https://" prefix
+ else if (id.indexOf("orcid.org") == 0) {
+ id = "https://" + id;
+ }
+ //If it starts with "www.orcid.org", then add the "https" prefix and remove the "www"
+ else if (id.indexOf("www.orcid.org") == 0) {
+ id = "https://" + id.replace("www.orcid.org", "orcid.org");
+ }
+ }
- //If this is an orcid identifier, format it correctly
- if(this.isOrcid(id)){
- // Add the directory attribute
- idNode.attr("directory", "https://orcid.org");
-
- //If this ORCID does not start with "http"
- if( id.indexOf("http") == -1 ){
- //If this is an ORCID with just the 16-digit numbers and hyphens, then add
- // the https://orcid.org/ prefix to it
- if( id.length == 19){
- id = "https://orcid.org/" + id;
- }
- //If it starts with "orcid.org", then add the "https://" prefix
- else if( id.indexOf("orcid.org") == 0 ){
- id = "https://" + id;
- }
- //If it starts with "www.orcid.org", then add the "https" prefix and remove the "www"
- else if( id.indexOf("www.orcid.org") == 0 ){
- id = "https://" + id.replace("www.orcid.org", "orcid.org");
- }
- }
-
- //If there is a "www", remove it
- if( id.indexOf("www.orcid.org") > -1 ){
- id = id.replace("www.orcid.org", "orcid.org");
- }
-
- //If it has the http:// prefix, add the 's' for secure protocol
- if( id.indexOf("http://") == 0){
- id = id.replace("http", "https");
- }
+ //If there is a "www", remove it
+ if (id.indexOf("www.orcid.org") > -1) {
+ id = id.replace("www.orcid.org", "orcid.org");
+ }
- }
- else{
- idNode.attr("directory", "unknown");
- }
+ //If it has the http:// prefix, add the 's' for secure protocol
+ if (id.indexOf("http://") == 0) {
+ id = id.replace("http", "https");
+ }
+ } else {
+ idNode.attr("directory", "unknown");
+ }
- $(idNode).text(id);
+ $(idNode).text(id);
+ },
+ this,
+ );
- }, this);
+ //Remove all the user id's if there aren't any in the model
+ if (userId.length == 0) {
+ $(objectDOM).find("userid").remove();
+ }
- //Remove all the user id's if there aren't any in the model
- if( userId.length == 0 ){
- $(objectDOM).find("userid").remove();
- }
-
- // role
- //If this party type is not an associated party, then remove the role element
- if( type != "associatedParty" && type != "personnel" ){
- $(objectDOM).find("role").remove();
- }
- //Otherwise, change the value of the role element
- else {
- // If for some reason there is no role, create a default role
- if( !this.get("roles").length ){
- var roles = ["Associated Party"];
- } else {
- var roles = this.get("roles");
+ // role
+ //If this party type is not an associated party, then remove the role element
+ if (type != "associatedParty" && type != "personnel") {
+ $(objectDOM).find("role").remove();
}
- _.each(roles, function(role, i){
- var roleSerialized = $(objectDOM).find("role");
- if(roleSerialized.length){
- $(roleSerialized[i]).text(role)
+ //Otherwise, change the value of the role element
+ else {
+ // If for some reason there is no role, create a default role
+ if (!this.get("roles").length) {
+ var roles = ["Associated Party"];
} else {
- roleSerialized = $(document.createElement("role")).text(role);
- this.getEMLPosition(objectDOM, "role").after( roleSerialized );
+ var roles = this.get("roles");
}
- }, this);
-
- }
+ _.each(
+ roles,
+ function (role, i) {
+ var roleSerialized = $(objectDOM).find("role");
+ if (roleSerialized.length) {
+ $(roleSerialized[i]).text(role);
+ } else {
+ roleSerialized = $(document.createElement("role")).text(role);
+ this.getEMLPosition(objectDOM, "role").after(roleSerialized);
+ }
+ },
+ this,
+ );
+ }
- //XML id attribute
- this.createID();
- //if(this.get("xmlID"))
+ //XML id attribute
+ this.createID();
+ //if(this.get("xmlID"))
$(objectDOM).attr("id", this.get("xmlID"));
- //else
- // $(objectDOM).removeAttr("id");
-
- // Remove empty (zero-length or whitespace-only) nodes
- $(objectDOM).find("*").filter(function() { return $.trim(this.innerHTML) === ""; } ).remove();
-
- return objectDOM;
- },
+ //else
+ // $(objectDOM).removeAttr("id");
+
+ // Remove empty (zero-length or whitespace-only) nodes
+ $(objectDOM)
+ .find("*")
+ .filter(function () {
+ return $.trim(this.innerHTML) === "";
+ })
+ .remove();
+
+ return objectDOM;
+ },
/*
- * Adds this EMLParty model to it's parent EML211 model in the appropriate role array
- *
- * @return {boolean} - Returns true if the merge was successful, false if the merge was cancelled
- */
- mergeIntoParent: function(){
+ * Adds this EMLParty model to it's parent EML211 model in the appropriate role array
+ *
+ * @return {boolean} - Returns true if the merge was successful, false if the merge was cancelled
+ */
+ mergeIntoParent: function () {
//Get the type of EML Party, in relation to the parent model
- if(this.get("type") && this.get("type") != "associatedParty")
+ if (this.get("type") && this.get("type") != "associatedParty")
var type = this.get("type");
- else
- var type = "associatedParty";
+ else var type = "associatedParty";
//Update the list of EMLParty models in the parent model
var parentEML = this.getParentEML();
- if(parentEML.type != "EML")
- return false;
+ if (parentEML.type != "EML") return false;
//Add this model to the EML model
var successfulAdd = parentEML.addParty(this);
@@ -647,397 +695,451 @@ define(['jquery', 'underscore', 'backbone', 'models/DataONEObject'],
return successfulAdd;
},
- isEmpty: function(){
+ isEmpty: function () {
// If we add any new fields, be sure to add the attribute here
- var attributes = ["userId", "fax", "phone", "onlineUrl",
- "email", "positionName", "organizationName"];
-
- //Check each value in the model that gets serialized to see if there is a value
- for(var i in attributes) {
- //Get the value from the model for this attribute
- var modelValue = this.get(attributes[i]);
-
- //If this is an array, then we want to check if there are any values in it
- if( Array.isArray(modelValue) ){
- if( modelValue.length > 0 )
- return false;
- }
- //Otherwise, check if this value differs from the default value
- else if(this.get(attributes[i]) !== this.defaults()[attributes[i]]){
- return false;
- }
- }
+ var attributes = [
+ "userId",
+ "fax",
+ "phone",
+ "onlineUrl",
+ "email",
+ "positionName",
+ "organizationName",
+ ];
+
+ //Check each value in the model that gets serialized to see if there is a value
+ for (var i in attributes) {
+ //Get the value from the model for this attribute
+ var modelValue = this.get(attributes[i]);
+
+ //If this is an array, then we want to check if there are any values in it
+ if (Array.isArray(modelValue)) {
+ if (modelValue.length > 0) return false;
+ }
+ //Otherwise, check if this value differs from the default value
+ else if (this.get(attributes[i]) !== this.defaults()[attributes[i]]) {
+ return false;
+ }
+ }
- //Check for a first and last name
- if( this.get("individualName") && (this.get("individualName").givenName || this.get("individualName").surName) )
+ //Check for a first and last name
+ if (
+ this.get("individualName") &&
+ (this.get("individualName").givenName ||
+ this.get("individualName").surName)
+ )
return false;
- //Check for addresses
- var isAddress = false;
-
- if( this.get("address") ){
-
- //Checks if there are any values anywhere in the address
- _.each(this.get("address"), function(address){
- //Delivery point is an array so we need to check the first and second
- //values of that array
- if( address.administrativeArea || address.city ||
- address.country || address.postalCode ||
- (address.deliveryPoint && address.deliveryPoint.length &&
- (address.deliveryPoint[0] || address.deliveryPoint[1]) ) ){
- isAddress = true;
- }
- });
-
- }
+ //Check for addresses
+ var isAddress = false;
+
+ if (this.get("address")) {
+ //Checks if there are any values anywhere in the address
+ _.each(this.get("address"), function (address) {
+ //Delivery point is an array so we need to check the first and second
+ //values of that array
+ if (
+ address.administrativeArea ||
+ address.city ||
+ address.country ||
+ address.postalCode ||
+ (address.deliveryPoint &&
+ address.deliveryPoint.length &&
+ (address.deliveryPoint[0] || address.deliveryPoint[1]))
+ ) {
+ isAddress = true;
+ }
+ });
+ }
- //If we found an address value anywhere, then it is not empty
- if(isAddress)
- return false;
+ //If we found an address value anywhere, then it is not empty
+ if (isAddress) return false;
- //If we never found a value, then return true because this model is empty
- return true;
+ //If we never found a value, then return true because this model is empty
+ return true;
},
- /*
- * Returns the node in the given EML snippet that the given node type should be inserted after
- */
- getEMLPosition: function(objectDOM, nodeName){
- var nodeOrder = [ "individualname", "organizationname", "positionname", "address", "phone",
- "electronicmailaddress", "onlineurl", "userid", "role"];
- var addressOrder = ["deliverypoint", "city", "administrativearea", "postalcode", "country"];
-
- //If this is an address node, find the position within the address
- if( _.contains(addressOrder, nodeName) ){
- nodeOrder = addressOrder;
- }
+ /*
+ * Returns the node in the given EML snippet that the given node type should be inserted after
+ */
+ getEMLPosition: function (objectDOM, nodeName) {
+ var nodeOrder = [
+ "individualname",
+ "organizationname",
+ "positionname",
+ "address",
+ "phone",
+ "electronicmailaddress",
+ "onlineurl",
+ "userid",
+ "role",
+ ];
+ var addressOrder = [
+ "deliverypoint",
+ "city",
+ "administrativearea",
+ "postalcode",
+ "country",
+ ];
+
+ //If this is an address node, find the position within the address
+ if (_.contains(addressOrder, nodeName)) {
+ nodeOrder = addressOrder;
+ }
- var position = _.indexOf(nodeOrder, nodeName);
- if(position == -1)
- return $(objectDOM).children().last();
+ var position = _.indexOf(nodeOrder, nodeName);
+ if (position == -1) return $(objectDOM).children().last();
- //Go through each node in the node list and find the position where this node will be inserted after
- for(var i=position-1; i>=0; i--){
- if($(objectDOM).find(nodeOrder[i]).length)
- return $(objectDOM).find(nodeOrder[i]).last();
- }
+ //Go through each node in the node list and find the position where this node will be inserted after
+ for (var i = position - 1; i >= 0; i--) {
+ if ($(objectDOM).find(nodeOrder[i]).length)
+ return $(objectDOM).find(nodeOrder[i]).last();
+ }
- return false;
- },
+ return false;
+ },
- createID: function(){
- this.set("xmlID", Math.ceil(Math.random() * (9999999999999999 - 1000000000000000) + 1000000000000000));
- },
+ createID: function () {
+ this.set(
+ "xmlID",
+ Math.ceil(
+ Math.random() * (9999999999999999 - 1000000000000000) +
+ 1000000000000000,
+ ),
+ );
+ },
- setType: function(){
- if(this.get("roles")){
- if(this.get("roles").length && !this.get("type")){
- this.set("type", "associatedParty");
+ setType: function () {
+ if (this.get("roles")) {
+ if (this.get("roles").length && !this.get("type")) {
+ this.set("type", "associatedParty");
+ }
}
- }
- },
+ },
- trickleUpChange: function(){
- if ( this.get("parentModel") ) {
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ trickleUpChange: function () {
+ if (this.get("parentModel")) {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
}
- },
+ },
- removeFromParent: function(){
- if( !this.get("parentModel") )
- return;
- else if( typeof this.get("parentModel").removeParty != "function" )
- return;
+ removeFromParent: function () {
+ if (!this.get("parentModel")) return;
+ else if (typeof this.get("parentModel").removeParty != "function")
+ return;
this.get("parentModel").removeParty(this);
this.set("removed", true);
- },
-
- /*
- * Checks the values of the model to determine if it is EML-valid
- */
- validate: function(){
-
- var individualName = this.get("individualName") || {},
- givenName = individualName.givenName || [],
- surName = individualName.surName || null,
- errors = {};
-
- //If there are no values in this model that would be serialized, then the model is valid
- if( !this.get("organizationName") && !this.get("positionName") && !givenName[0]?.length && !surName
- && !this.get("address").length && !this.get("phone").length && !this.get("fax").length
- && !this.get("email").length && !this.get("onlineUrl").length && !this.get("userId").length){
-
- return;
-
- }
+ },
- //The EMLParty must have either an organization name, position name, or surname.
- // It must ALSO have a type or role.
- if ( !this.get("organizationName") && !this.get("positionName") &&
- (!this.get("individualName") || !surName ) ){
+ /*
+ * Checks the values of the model to determine if it is EML-valid
+ */
+ validate: function () {
+ var individualName = this.get("individualName") || {},
+ givenName = individualName.givenName || [],
+ surName = individualName.surName || null,
+ errors = {};
+
+ //If there are no values in this model that would be serialized, then the model is valid
+ if (
+ !this.get("organizationName") &&
+ !this.get("positionName") &&
+ !givenName[0]?.length &&
+ !surName &&
+ !this.get("address").length &&
+ !this.get("phone").length &&
+ !this.get("fax").length &&
+ !this.get("email").length &&
+ !this.get("onlineUrl").length &&
+ !this.get("userId").length
+ ) {
+ return;
+ }
- errors = {
- surName: "Either a last name, position name, or organization name is required.",
- positionName: "",
- organizationName: ""
+ //The EMLParty must have either an organization name, position name, or surname.
+ // It must ALSO have a type or role.
+ if (
+ !this.get("organizationName") &&
+ !this.get("positionName") &&
+ (!this.get("individualName") || !surName)
+ ) {
+ errors = {
+ surName:
+ "Either a last name, position name, or organization name is required.",
+ positionName: "",
+ organizationName: "",
+ };
+ }
+ //If there is a first name and no last name, then this is not a valid individualName
+ else if (
+ givenName[0]?.length &&
+ !surName &&
+ this.get("organizationName") &&
+ this.get("positionName")
+ ) {
+ errors = { surName: "Provide a last name." };
}
- }
- //If there is a first name and no last name, then this is not a valid individualName
- else if( (givenName[0]?.length && !surName) && this.get("organizationName") && this.get("positionName") ){
+ //Check that each required field has a value. Required fields are configured in the {@link AppConfig}
+ let roles =
+ this.get("type") == "associatedParty"
+ ? this.get("roles")
+ : [this.get("type")];
+ for (role of roles) {
+ let requiredFields = MetacatUI.appModel.get(
+ "emlEditorRequiredFields_EMLParty",
+ )[role];
+ requiredFields?.forEach((field) => {
+ let currentVal = this.get(field);
+ if (!currentVal || !currentVal?.length) {
+ errors[field] =
+ `Provide a${["a", "e", "i", "o", "u"].includes(field.charAt(0)) ? "n " : " "} ${field}. `;
+ }
+ });
+ }
- errors = { surName: "Provide a last name." }
+ return Object.keys(errors)?.length ? errors : false;
+ },
- }
+ isOrcid: function (username) {
+ if (!username) return false;
- //Check that each required field has a value. Required fields are configured in the {@link AppConfig}
- let roles = this.get("type") == "associatedParty"? this.get("roles") : [this.get("type")];
- for( role of roles ){
- let requiredFields = MetacatUI.appModel.get("emlEditorRequiredFields_EMLParty")[role];
- requiredFields?.forEach(field=>{
- let currentVal = this.get(field);
- if( !currentVal || !currentVal?.length ){
- errors[field] = `Provide a${(["a","e","i","o","u"].includes(field.charAt(0))? "n " : " ")} ${field}. `;
- }
- });
- }
+ //If the ORCID URL is anywhere in this username string, it is an ORCID
+ if (username.indexOf("orcid.org") > -1) {
+ return true;
+ }
-
- return Object.keys(errors)?.length? errors : false;
+ /* The ORCID checksum algorithm to determine is a character string is an ORCiD
+ * http://support.orcid.org/knowledgebase/articles/116780-structure-of-the-orcid-identifier
+ */
+ var total = 0,
+ baseDigits = username.replace(/-/g, "").substr(0, 15);
-
- },
+ for (var i = 0; i < baseDigits.length; i++) {
+ var digit = parseInt(baseDigits.charAt(i));
+ total = (total + digit) * 2;
+ }
- isOrcid: function(username){
- if(!username) return false;
+ var remainder = total % 11,
+ result = (12 - remainder) % 11,
+ checkDigit = result == 10 ? "X" : result.toString(),
+ isOrcid = checkDigit == username.charAt(username.length - 1);
- //If the ORCID URL is anywhere in this username string, it is an ORCID
- if(username.indexOf("orcid.org") > -1){
- return true;
- }
+ return isOrcid;
+ },
- /* The ORCID checksum algorithm to determine is a character string is an ORCiD
- * http://support.orcid.org/knowledgebase/articles/116780-structure-of-the-orcid-identifier
+ /*
+ * Clones all the values of this array into a new JS Object.
+ * Special care is needed for nested objects and arrays
+ * This is helpful when copying this EMLParty to another role in the EML
*/
- var total = 0,
- baseDigits = username.replace(/-/g, "").substr(0, 15);
-
- for(var i=0; i 0) {
+ // Serialize funding (if needed)
+ var fundingNode = $(objectDOM).children("funding");
+ if (this.get("funding") && this.get("funding").length > 0) {
// Create the funding element if needed
- if (fundingNode.length == 0) {
-
- fundingNode = document.createElement("funding");
- this.getEMLPosition(objectDOM, "funding").after(fundingNode);
-
- } else {
-
- // Clear the funding node out of all child elements
- // We only replace because is an EMLText module
- // instance and can contain other content we don't want to remove
- // when serializing
- $(fundingNode).children("para").remove();
-
- }
+ if (fundingNode.length == 0) {
+ fundingNode = document.createElement("funding");
+ this.getEMLPosition(objectDOM, "funding").after(fundingNode);
+ } else {
+ // Clear the funding node out of all child elements
+ // We only replace because is an EMLText module
+ // instance and can contain other content we don't want to remove
+ // when serializing
+ $(fundingNode).children("para").remove();
+ }
//Add a element with the text for each funding info
- _.each(this.get('funding'), function(f) {
- $(fundingNode).append( $(document.createElement("para")).text(f) );
- });
-
- } else if ( (!this.get('funding') || !this.get('funding').length) && fundingNode.length > 0) {
-
- // Remove all funding elements
- $(fundingNode).remove();
-
- }
+ _.each(this.get("funding"), function (f) {
+ $(fundingNode).append($(document.createElement("para")).text(f));
+ });
+ } else if (
+ (!this.get("funding") || !this.get("funding").length) &&
+ fundingNode.length > 0
+ ) {
+ // Remove all funding elements
+ $(fundingNode).remove();
+ }
- // Remove empty (zero-length or whitespace-only) nodes
- $(objectDOM).find("*").filter(function() { return $.trim(this.innerHTML) === ""; } ).remove();
+ // Remove empty (zero-length or whitespace-only) nodes
+ $(objectDOM)
+ .find("*")
+ .filter(function () {
+ return $.trim(this.innerHTML) === "";
+ })
+ .remove();
- return objectDOM;
- },
+ return objectDOM;
+ },
- getEMLPosition: function(objectDOM, nodeName) {
- var nodeOrder = this.get("nodeOrder");
- var position = _.indexOf(nodeOrder, nodeName);
+ getEMLPosition: function (objectDOM, nodeName) {
+ var nodeOrder = this.get("nodeOrder");
+ var position = _.indexOf(nodeOrder, nodeName);
- // Append to the bottom if not found
- if ( position == -1 ) {
- return $(objectDOM).children().last()[0];
- }
+ // Append to the bottom if not found
+ if (position == -1) {
+ return $(objectDOM).children().last()[0];
+ }
- // Otherwise, go through each node in the node list and find the
- // position where this node will be inserted after
- for ( var i = position - 1; i >= 0; i-- ) {
- if ( $(objectDOM).find( nodeOrder[i].toLowerCase() ).length ) {
- return $(objectDOM).find(nodeOrder[i].toLowerCase()).last()[0];
- }
- }
+ // Otherwise, go through each node in the node list and find the
+ // position where this node will be inserted after
+ for (var i = position - 1; i >= 0; i--) {
+ if ($(objectDOM).find(nodeOrder[i].toLowerCase()).length) {
+ return $(objectDOM).find(nodeOrder[i].toLowerCase()).last()[0];
+ }
+ }
- // Always return something so calling code can be cleaner
- return $(objectDOM).children().last()[0];
- },
+ // Always return something so calling code can be cleaner
+ return $(objectDOM).children().last()[0];
+ },
/*
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211 or false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211 or false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
var emlModel = this.get("parentModel"),
- tries = 0;
+ tries = 0;
- while (emlModel.type !== "EML" && tries < 6){
+ while (emlModel.type !== "EML" && tries < 6) {
emlModel = emlModel.get("parentModel");
tries++;
}
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
-
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
},
- trickleUpChange: function(){
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- },
+ trickleUpChange: function () {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ },
- formatXML: function(xmlString){
- return DataONEObject.prototype.formatXML.call(this, xmlString);
- }
- });
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
+ });
- return EMLProject;
+ return EMLProject;
});
diff --git a/src/js/models/metadata/eml211/EMLTaxonCoverage.js b/src/js/models/metadata/eml211/EMLTaxonCoverage.js
index efbe29cfd..eaad42673 100644
--- a/src/js/models/metadata/eml211/EMLTaxonCoverage.js
+++ b/src/js/models/metadata/eml211/EMLTaxonCoverage.js
@@ -3,7 +3,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
$,
_,
Backbone,
- DataONEObject
+ DataONEObject,
) {
/**
* @name taxonomicClassification
@@ -32,9 +32,9 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
* @classcategory Models/Metadata/EML
* @extends Backbone.Model
* @constructor
- */
+ */
var EMLTaxonCoverage = Backbone.Model.extend(
- /** @lends EMLTaxonCoverage.prototype */{
+ /** @lends EMLTaxonCoverage.prototype */ {
/**
* Returns the default properties for this model. Defined here.
* @type {Object}
@@ -76,7 +76,8 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
taxonomicsystem: "taxonomicSystem",
classificationsystem: "classificationSystem",
classificationsystemcitation: "classificationSystemCitation",
- classificationsystemmodifications: "classificationSystemModifications",
+ classificationsystemmodifications:
+ "classificationSystemModifications",
identificationreference: "identificationReference",
identifiername: "identifierName",
taxonomicprocedures: "taxonomicProcedures",
@@ -91,14 +92,14 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
var model = this,
taxonomicClassifications = $(objectDOM).children(
- "taxonomicclassification"
+ "taxonomicclassification",
),
modelJSON = {
taxonomicClassification: _.map(
taxonomicClassifications,
function (tc) {
return model.parseTaxonomicClassification(tc);
- }
+ },
),
generalTaxonomicCoverage: $(objectDOM)
.children("generaltaxonomiccoverage")
@@ -116,7 +117,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
var commonName = $(classification).children("commonname");
var taxonId = $(classification).children("taxonId");
var taxonomicClassification = $(classification).children(
- "taxonomicclassification"
+ "taxonomicclassification",
);
var model = this,
@@ -137,7 +138,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
taxonomicClassification,
function (tc) {
return model.parseTaxonomicClassification(tc);
- }
+ },
),
};
@@ -176,8 +177,8 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
if (_.isString(generalCoverage) && generalCoverage.length > 0) {
$(objectDOM).append(
$(document.createElement("generaltaxonomiccoverage")).text(
- this.get("generalTaxonomicCoverage")
- )
+ this.get("generalTaxonomicCoverage"),
+ ),
);
}
@@ -193,7 +194,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
for (var i = 0; i < classifications.length; i++) {
$(objectDOM).append(
- this.createTaxonomicClassificationDOM(classifications[i])
+ this.createTaxonomicClassificationDOM(classifications[i]),
);
}
@@ -230,19 +231,19 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
if (taxonRankName && taxonRankName.length > 0) {
$(finishedEl).append(
- $(document.createElement("taxonrankname")).text(taxonRankName)
+ $(document.createElement("taxonrankname")).text(taxonRankName),
);
}
if (taxonRankValue && taxonRankValue.length > 0) {
$(finishedEl).append(
- $(document.createElement("taxonrankvalue")).text(taxonRankValue)
+ $(document.createElement("taxonrankvalue")).text(taxonRankValue),
);
}
if (commonName && commonName.length > 0) {
$(finishedEl).append(
- $(document.createElement("commonname")).text(commonName)
+ $(document.createElement("commonname")).text(commonName),
);
}
@@ -265,7 +266,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
function (tc) {
$(finishedEl).append(this.createTaxonomicClassificationDOM(tc));
},
- this
+ this,
);
}
@@ -299,7 +300,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
!_.every(
this.get("taxonomicClassification"),
this.isClassificationValid,
- this
+ this,
)
)
errors.taxonomicClassification =
@@ -337,7 +338,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
if (taxonomicClassification.taxonomicClassification)
return this.isClassificationValid(
- taxonomicClassification.taxonomicClassification
+ taxonomicClassification.taxonomicClassification,
);
else return true;
},
@@ -368,7 +369,7 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
if (c.taxonId) stringified.taxonId = c.taxonId;
if (c.taxonomicClassification) {
stringified.taxonomicClassification = stringifyClassification(
- c.taxonomicClassification
+ c.taxonomicClassification,
);
}
const st = JSON.stringify(stringified);
@@ -398,7 +399,9 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
const classifications = this.get("taxonomicClassification");
for (let i = 0; i < classifications.length; i++) {
if (typeof indexToSkip === "number" && i === indexToSkip) continue;
- if (this.classificationsAreEqual(classifications[i], classification)) {
+ if (
+ this.classificationsAreEqual(classifications[i], classification)
+ ) {
return true;
}
}
@@ -457,7 +460,8 @@ define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
formatXML: function (xmlString) {
return DataONEObject.prototype.formatXML.call(this, xmlString);
},
- });
+ },
+ );
return EMLTaxonCoverage;
});
diff --git a/src/js/models/metadata/eml211/EMLTemporalCoverage.js b/src/js/models/metadata/eml211/EMLTemporalCoverage.js
index 697be18c8..a623dcf88 100644
--- a/src/js/models/metadata/eml211/EMLTemporalCoverage.js
+++ b/src/js/models/metadata/eml211/EMLTemporalCoverage.js
@@ -1,502 +1,527 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/DataONEObject'],
- function($, _, Backbone, DataONEObject) {
-
+define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
+ $,
+ _,
+ Backbone,
+ DataONEObject,
+) {
/**
- * @class EMLTemporalCoverage
- * @classcategory Models/Metadata/EML211
- * @extends Backbone.Model
- */
- var EMLTemporalCoverage = Backbone.Model.extend(
- /** @lends EMLTemporalCoverage.prototype */{
-
- defaults: {
- objectXML: null,
- objectDOM: null,
- beginDate: null,
- beginTime: null,
- endDate: null,
- endTime: null
- },
-
- initialize: function(attributes){
- if(attributes && attributes.objectDOM)
- this.set(this.parse(attributes.objectDOM));
-
- this.on("change:beginDate change:beginTime change:endDate change:endTime", this.trickleUpChange);
- },
-
- /*
- * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
- * Used during parse() and serialize()
- */
- nodeNameMap: function(){
- return {
- "begindate" : "beginDate",
- "calendardate" : "calendarDate",
- "enddate" : "endDate",
- "rangeofdates" : "rangeOfDates",
- "singledatetime" : "singleDateTime",
- "spatialraster" : "spatialRaster",
- "spatialvector" : "spatialVector",
- "storedprocedure" : "storedProcedure",
- "temporalcoverage" : "temporalCoverage"
+ * @class EMLTemporalCoverage
+ * @classcategory Models/Metadata/EML211
+ * @extends Backbone.Model
+ */
+ var EMLTemporalCoverage = Backbone.Model.extend(
+ /** @lends EMLTemporalCoverage.prototype */ {
+ defaults: {
+ objectXML: null,
+ objectDOM: null,
+ beginDate: null,
+ beginTime: null,
+ endDate: null,
+ endTime: null,
+ },
+
+ initialize: function (attributes) {
+ if (attributes && attributes.objectDOM)
+ this.set(this.parse(attributes.objectDOM));
+
+ this.on(
+ "change:beginDate change:beginTime change:endDate change:endTime",
+ this.trickleUpChange,
+ );
+ },
+
+ /*
+ * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
+ * Used during parse() and serialize()
+ */
+ nodeNameMap: function () {
+ return {
+ begindate: "beginDate",
+ calendardate: "calendarDate",
+ enddate: "endDate",
+ rangeofdates: "rangeOfDates",
+ singledatetime: "singleDateTime",
+ spatialraster: "spatialRaster",
+ spatialvector: "spatialVector",
+ storedprocedure: "storedProcedure",
+ temporalcoverage: "temporalCoverage",
+ };
+ },
+
+ parse: function (objectDOM) {
+ if (!objectDOM) var objectDOM = this.get("objectDOM");
+
+ var rangeOfDates = $(objectDOM).children("rangeofdates"),
+ singleDateTime = $(objectDOM).children("singledatetime");
+
+ // If the temporalCoverage element has both a rangeOfDates and a
+ // singleDateTime (invalid EML), the rangeOfDates is preferred.
+ if (rangeOfDates.length) {
+ return this.parseRangeOfDates(rangeOfDates);
+ } else if (singleDateTime.length) {
+ return this.parseSingleDateTime(singleDateTime);
+ }
+ },
+
+ parseRangeOfDates: function (rangeOfDates) {
+ var beginDate = $(rangeOfDates).find("beginDate"),
+ endDate = $(rangeOfDates).find("endDate"),
+ properties = {};
+
+ if (beginDate.length > 0) {
+ if ($(beginDate).find("calendardate")) {
+ properties.beginDate = $(beginDate)
+ .find("calendardate")
+ .first()
+ .text();
+ }
+
+ if ($(beginDate).find("time").length > 0) {
+ properties.beginTime = $(beginDate).find("time").first().text();
+ }
+ }
+
+ if (endDate.length > 0) {
+ if ($(endDate).find("calendardate").length > 0) {
+ properties.endDate = $(endDate).find("calendardate").first().text();
+ }
+
+ if ($(endDate).find("time").length > 0) {
+ properties.endTime = $(endDate).find("time").first().text();
+ }
+ }
+
+ return properties;
+ },
+
+ parseSingleDateTime: function (singleDateTime) {
+ var calendarDate = $(singleDateTime).find("calendardate"),
+ time = $(singleDateTime).find("time");
+
+ return {
+ beginDate:
+ calendarDate.length > 0 ? calendarDate.first().text() : null,
+ beginTime: time.length > 0 ? time.first().text() : null,
+ };
+ },
+
+ serialize: function () {
+ var objectDOM = this.updateDOM(),
+ xmlString = objectDOM.outerHTML;
+
+ //Camel-case the XML
+ xmlString = this.formatXML(xmlString);
+
+ return xmlString;
+ },
+
+ /*
+ * Makes a copy of the original XML DOM and updates it with the new values from the model.
+ */
+ updateDOM: function () {
+ var objectDOM;
+
+ if (this.get("objectDOM")) {
+ objectDOM = this.get("objectDOM").cloneNode(true);
+ //Empty the DOM
+ $(objectDOM).empty();
+ } else {
+ objectDOM = $(" ");
+ }
+
+ if (this.get("beginDate") && this.get("endDate")) {
+ $(objectDOM).append(this.serializeRangeOfDates());
+ } else if (!this.get("endDate")) {
+ $(objectDOM).append(this.serializeSingleDateTime());
+ } else if (this.get("singleDateTime")) {
+ var singleDateTime = $(objectDOM).find("singledatetime");
+ if (!singleDateTime.length) {
+ singleDateTime = document.createElement("singledatetime");
+ $(objectDOM).append(singleDateTime);
+ }
+
+ if (this.get("singleDateTime").calendarDate)
+ $(singleDateTime).html(
+ this.serializeSingleDateTime(
+ this.get("singleDateTime").calendarDate,
+ ),
+ );
+ }
+
+ // Remove empty (zero-length or whitespace-only) nodes
+ $(objectDOM)
+ .find("*")
+ .filter(function () {
+ return $.trim(this.innerHTML) === "";
+ })
+ .remove();
+
+ return objectDOM;
+ },
+
+ serializeRangeOfDates: function () {
+ var objectDOM = $(document.createElement("rangeofdates")),
+ beginDateEl = $(document.createElement("begindate")),
+ endDateEl = $(document.createElement("enddate"));
+
+ if (this.get("beginDate")) {
+ $(beginDateEl).append(
+ this.serializeCalendarDate(this.get("beginDate")),
+ );
+
+ if (this.get("beginTime")) {
+ $(beginDateEl).append(this.serializeTime(this.get("beginTime")));
+ }
+
+ objectDOM.append(beginDateEl);
+ }
+
+ if (this.get("endDate")) {
+ $(endDateEl).append(this.serializeCalendarDate(this.get("endDate")));
+
+ if (this.get("endTime")) {
+ $(endDateEl).append(this.serializeTime(this.get("endTime")));
+ }
+ objectDOM.append(endDateEl);
+ }
+
+ return objectDOM;
+ },
+
+ serializeSingleDateTime: function () {
+ var objectDOM = $(document.createElement("singleDateTime"));
+
+ if (this.get("beginDate")) {
+ $(objectDOM).append(
+ this.serializeCalendarDate(this.get("beginDate")),
+ );
+
+ if (this.get("beginTime")) {
+ $(objectDOM).append(this.serializeTime(this.get("beginTime")));
+ }
+ }
+
+ return objectDOM;
+ },
+
+ serializeCalendarDate: function (date) {
+ return $(document.createElement("calendarDate")).html(date);
+ },
+
+ serializeTime: function (time) {
+ return $(document.createElement("time")).html(time);
+ },
+
+ trickleUpChange: function () {
+ if (
+ _.contains(MetacatUI.rootDataPackage.models, this.get("parentModel"))
+ )
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ },
+
+ mergeIntoParent: function () {
+ if (
+ this.get("parentModel") &&
+ this.get("parentModel").type == "EML" &&
+ !_.contains(this.get("parentModel").get("temporalCoverage"), this)
+ )
+ this.get("parentModel").get("temporalCoverage").push(this);
+ },
+
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
+
+ // Checks the values of this model and determines whether they are valid according the the EML 2.1.1 schema.
+ // Returns a hash of error messages
+ validate: function () {
+ var beginDate = this.get("beginDate"),
+ beginTime = this.get("beginTime"),
+ endDate = this.get("endDate"),
+ endTime = this.get("endTime"),
+ errors = {};
+
+ // A valid temporal coverage at least needs a start date
+ if (!beginDate) {
+ errors.beginDate = "Provide a begin date.";
+ }
+ // endTime is set but endDate is not
+ else if (
+ endTime &&
+ endTime.length > 0 &&
+ (!endDate || endDate.length == 0)
+ ) {
+ errors.endDate = "Provide an end date.";
+ }
+
+ //Check the validity of the date format
+ if (beginDate && !this.isDateFormatValid(beginDate)) {
+ errors.beginDate =
+ "The begin date must be formatted as YYYY-MM-DD or YYYY.";
+ }
+
+ //Check the validity of the date format
+ if (endDate && !this.isDateFormatValid(endDate)) {
+ errors.endDate =
+ "The end date must be formatted as YYYY-MM-DD or YYYY.";
+ }
+
+ if (typeof endDate == "string" && endDate.length && beginDate <= 0) {
+ errors.beginDate = "The begin date must be greater than zero.";
+ }
+
+ if (typeof endDate == "string" && endDate.length && endDate <= 0) {
+ errors.endDate = "The end date must be greater than zero.";
+ }
+
+ //Check the validity of the begin time format
+ if (beginTime) {
+ var timeErrorMessage = this.validateTimeFormat(beginTime);
+
+ if (typeof timeErrorMessage == "string")
+ errors.beginTime = timeErrorMessage;
+ }
+
+ //Check the validity of the end time format
+ if (endTime) {
+ var timeErrorMessage = this.validateTimeFormat(endTime);
+
+ if (typeof timeErrorMessage == "string")
+ errors.endTime = timeErrorMessage;
+ }
+
+ // Check if begin date greater than end date for the temporalCoverage
+ if (this.isGreaterDate(beginDate, endDate))
+ errors.beginDate = "The begin date must be before the end date.";
+
+ // Check if begin time greater than end time for the temporalCoverage in case of equal dates.
+ if (this.isGreaterTime(beginDate, endDate, beginTime, endTime))
+ errors.beginTime = "The begin time must be before the end time.";
+
+ if (Object.keys(errors).length) return errors;
+ else return;
+ },
+
+ isDateFormatValid: function (dateString) {
+ //Date strings that are four characters should be a full year. Make sure all characters are numbers
+ if (dateString.length == 4) {
+ var digits = dateString.match(/[0-9]/g);
+ return digits.length == 4;
+ }
+ //Date strings that are 10 characters long should be a valid date
+ else {
+ var dateParts = dateString.split("-");
+
+ if (
+ dateParts.length != 3 ||
+ dateParts[0].length != 4 ||
+ dateParts[1].length != 2 ||
+ dateParts[2].length != 2
+ )
+ return false;
+
+ dateYear = dateParts[0];
+ dateMonth = dateParts[1];
+ dateDay = dateParts[2];
+
+ // Validating the values for the date and month if in YYYY-MM-DD format.
+ if (dateMonth < 1 || dateMonth > 12) return false;
+ else if (dateDay < 1 || dateDay > 31) return false;
+ else if (
+ (dateMonth == 4 ||
+ dateMonth == 6 ||
+ dateMonth == 9 ||
+ dateMonth == 11) &&
+ dateDay == 31
+ )
+ return false;
+ else if (dateMonth == 2) {
+ // Validation for leap year dates.
+ var isleap =
+ dateYear % 4 == 0 && (dateYear % 100 != 0 || dateYear % 400 == 0);
+ if (dateDay > 29 || (dateDay == 29 && !isleap)) return false;
+ }
+
+ var digits = _.filter(dateParts, function (part) {
+ return part.match(/[0-9]/g).length == part.length;
+ });
+
+ return digits.length == 3;
+ }
+ },
+
+ validateTimeFormat: function (timeString) {
+ //If the last character is a "Z", then remove it for now
+ if (
+ timeString.substring(timeString.length - 1, timeString.length) == "Z"
+ ) {
+ timeString = timeString.replace("Z", "", "g");
+ }
+
+ if (timeString.length == 8) {
+ var timeParts = timeString.split(":");
+
+ if (timeParts.length != 3) {
+ return "Time must be formatted as HH:MM:SS";
+ }
+
+ // Validation pattern for HH:MM:SS values.
+ // Range for HH validation : 00-24
+ // Range for MM validation : 00-59
+ // Range for SS validation : 00-59
+ // Leading 0's are must in case of single digit values.
+ var timePattern = /^(?:2[0-4]|[01][0-9]):[0-5][0-9]:[0-5][0-9]$/,
+ validTimePattern = timeString.match(timePattern);
+
+ //If the hour is 24, only accept 00:00 for MM:SS. Any minutes or seconds in the midnight hour should be
+ //formatted as 00:XX:XX not 24:XX:XX
+ if (
+ validTimePattern &&
+ timeParts[0] == "24" &&
+ (timeParts[1] != "00" || timeParts[2] != "00")
+ ) {
+ return "The midnight hour starts at 00:00:00 and ends at 00:59:59.";
+ } else if (!validTimePattern && parseInt(timeParts[0]) > "24") {
+ return "Time of the day starts at 00:00 and ends at 23:59.";
+ } else if (!validTimePattern && parseInt(timeParts[1]) > "59") {
+ return "Minutes should be between 00 and 59.";
+ } else if (!validTimePattern && parseInt(timeParts[2]) > "59") {
+ return "Seconds should be between 00 and 59.";
+ } else return true;
+ } else return "Time must be formatted as HH:MM:SS";
+ },
+
+ /**
+ * This function checks whether the begin date is greater than the end date.
+ *
+ * @param {string} beginDate the begin date string
+ * @param {string} endDate the end date string
+ * @return {boolean}
+ */
+ isGreaterDate: function (beginDate, endDate) {
+ if (typeof beginDate == "undefined" || !beginDate) return false;
+
+ if (typeof endDate == "undefined" || !endDate) return false;
+
+ //Making sure that beginDate year is smaller than endDate year
+ if (beginDate.length == 4 && endDate.length == 4) {
+ if (beginDate > endDate) {
+ return true;
+ }
+ }
+
+ //Checking equality for either dateStrings that are greater than 4 characters
+ else {
+ beginDateParts = beginDate.split("-");
+ endDateParts = endDate.split("-");
+
+ if (beginDateParts.length == endDateParts.length) {
+ if (beginDateParts[0] > endDateParts[0]) {
+ return true;
+ } else if (beginDateParts[0] == endDateParts[0]) {
+ if (beginDateParts[1] > endDateParts[1]) {
+ return true;
+ } else if (beginDateParts[1] == endDateParts[1]) {
+ if (beginDateParts[2] > endDateParts[2]) {
+ return true;
+ }
+ }
}
- },
-
- parse: function(objectDOM){
- if(!objectDOM) var objectDOM = this.get("objectDOM");
-
- var rangeOfDates = $(objectDOM).children('rangeofdates'),
- singleDateTime = $(objectDOM).children('singledatetime');
-
- // If the temporalCoverage element has both a rangeOfDates and a
- // singleDateTime (invalid EML), the rangeOfDates is preferred.
- if (rangeOfDates.length) {
- return this.parseRangeOfDates(rangeOfDates);
- } else if (singleDateTime.length) {
- return this.parseSingleDateTime(singleDateTime);
- }
- },
-
- parseRangeOfDates: function(rangeOfDates) {
- var beginDate = $(rangeOfDates).find('beginDate'),
- endDate = $(rangeOfDates).find('endDate'),
- properties = {};
-
- if (beginDate.length > 0) {
- if ($(beginDate).find('calendardate')) {
- properties.beginDate = $(beginDate).find('calendardate').first().text();
- }
-
- if ($(beginDate).find('time').length > 0) {
- properties.beginTime = $(beginDate).find('time').first().text();
- }
- }
-
- if (endDate.length > 0) {
- if ($(endDate).find('calendardate').length > 0) {
- properties.endDate = $(endDate).find('calendardate').first().text();
- }
-
- if ($(endDate).find('time').length > 0) {
- properties.endTime = $(endDate).find('time').first().text();
- }
- }
-
- return properties;
- },
-
- parseSingleDateTime: function(singleDateTime) {
- var calendarDate = $(singleDateTime).find("calendardate"),
- time = $(singleDateTime).find("time");
-
- return {
- beginDate: calendarDate.length > 0 ? calendarDate.first().text() : null,
- beginTime: time.length > 0 ? time.first().text() : null
- };
- },
-
- serialize: function(){
- var objectDOM = this.updateDOM(),
- xmlString = objectDOM.outerHTML;
-
- //Camel-case the XML
- xmlString = this.formatXML(xmlString);
-
- return xmlString;
- },
-
- /*
- * Makes a copy of the original XML DOM and updates it with the new values from the model.
- */
- updateDOM: function(){
- var objectDOM;
-
- if (this.get("objectDOM")) {
- objectDOM = this.get("objectDOM").cloneNode(true);
- //Empty the DOM
- $(objectDOM).empty();
- } else {
- objectDOM = $(" ");
- }
-
- if (this.get('beginDate') && this.get('endDate')) {
- $(objectDOM).append(this.serializeRangeOfDates());
- } else if (!this.get('endDate')) {
- $(objectDOM).append(this.serializeSingleDateTime());
- }
- else if(this.get("singleDateTime")){
- var singleDateTime = $(objectDOM).find("singledatetime");
- if(!singleDateTime.length){
- singleDateTime = document.createElement("singledatetime");
- $(objectDOM).append(singleDateTime);
- }
-
- if(this.get("singleDateTime").calendarDate)
- $(singleDateTime).html(this.serializeSingleDateTime( this.get("singleDateTime").calendarDate ));
- }
-
- // Remove empty (zero-length or whitespace-only) nodes
- $(objectDOM).find("*").filter(function() { return $.trim(this.innerHTML) === ""; } ).remove();
-
- return objectDOM;
- },
-
- serializeRangeOfDates: function() {
- var objectDOM = $(document.createElement('rangeofdates')),
- beginDateEl = $(document.createElement('begindate')),
- endDateEl = $(document.createElement('enddate'));
-
- if (this.get('beginDate')) {
- $(beginDateEl).append(this.serializeCalendarDate(this.get('beginDate')));
-
- if (this.get('beginTime')) {
- $(beginDateEl).append(this.serializeTime(this.get('beginTime')));
- }
-
- objectDOM.append(beginDateEl);
- }
-
- if (this.get('endDate')) {
- $(endDateEl).append(this.serializeCalendarDate(this.get('endDate')));
-
- if (this.get('endTime')) {
- $(endDateEl).append(this.serializeTime(this.get('endTime')));
- }
- objectDOM.append(endDateEl);
- }
-
- return objectDOM;
- },
-
- serializeSingleDateTime: function() {
- var objectDOM = $(document.createElement('singleDateTime'));
-
- if (this.get('beginDate')) {
- $(objectDOM).append(this.serializeCalendarDate(this.get('beginDate')));
-
- if (this.get('beginTime')) {
- $(objectDOM).append(this.serializeTime(this.get('beginTime')));
- }
- }
-
- return objectDOM;
- },
-
- serializeCalendarDate: function(date) {
- return $(document.createElement('calendarDate')).html(date);
- },
-
- serializeTime: function(time) {
- return $(document.createElement('time')).html(time);
- },
-
- trickleUpChange: function(){
- if(_.contains(MetacatUI.rootDataPackage.models, this.get("parentModel")))
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- },
-
- mergeIntoParent: function(){
- if(this.get("parentModel") && this.get("parentModel").type == "EML" && !_.contains(this.get("parentModel").get("temporalCoverage"), this))
- this.get("parentModel").get("temporalCoverage").push(this);
- },
-
- formatXML: function(xmlString){
- return DataONEObject.prototype.formatXML.call(this, xmlString);
- },
-
- // Checks the values of this model and determines whether they are valid according the the EML 2.1.1 schema.
- // Returns a hash of error messages
- validate: function() {
- var beginDate = this.get('beginDate'),
- beginTime = this.get('beginTime'),
- endDate = this.get('endDate'),
- endTime = this.get('endTime'),
- errors = {};
-
- // A valid temporal coverage at least needs a start date
- if (!beginDate) {
- errors.beginDate = "Provide a begin date.";
- }
- // endTime is set but endDate is not
- else if (endTime && endTime.length > 0 && (!endDate || endDate.length == 0)) {
- errors.endDate = "Provide an end date."
- }
-
- //Check the validity of the date format
- if(beginDate && !this.isDateFormatValid(beginDate)){
- errors.beginDate = "The begin date must be formatted as YYYY-MM-DD or YYYY.";
- }
-
- //Check the validity of the date format
- if(endDate && !this.isDateFormatValid(endDate)){
- errors.endDate = "The end date must be formatted as YYYY-MM-DD or YYYY.";
- }
-
- if( typeof endDate == "string" && endDate.length && beginDate <= 0 ){
- errors.beginDate = "The begin date must be greater than zero.";
- }
-
- if( typeof endDate == "string" && endDate.length && endDate <= 0 ){
- errors.endDate = "The end date must be greater than zero.";
- }
-
- //Check the validity of the begin time format
- if(beginTime){
- var timeErrorMessage = this.validateTimeFormat(beginTime);
-
- if( typeof timeErrorMessage == "string" )
- errors.beginTime = timeErrorMessage;
- }
-
- //Check the validity of the end time format
- if(endTime){
- var timeErrorMessage = this.validateTimeFormat(endTime);
-
- if( typeof timeErrorMessage == "string" )
- errors.endTime = timeErrorMessage;
- }
-
- // Check if begin date greater than end date for the temporalCoverage
- if (this.isGreaterDate(beginDate, endDate))
- errors.beginDate = "The begin date must be before the end date."
-
- // Check if begin time greater than end time for the temporalCoverage in case of equal dates.
- if (this.isGreaterTime(beginDate, endDate, beginTime, endTime))
- errors.beginTime = "The begin time must be before the end time."
-
- if(Object.keys(errors).length)
- return errors;
- else
- return;
- },
-
- isDateFormatValid: function(dateString){
-
- //Date strings that are four characters should be a full year. Make sure all characters are numbers
- if(dateString.length == 4){
- var digits = dateString.match( /[0-9]/g );
- return (digits.length == 4)
- }
- //Date strings that are 10 characters long should be a valid date
- else{
- var dateParts = dateString.split("-");
-
- if(dateParts.length != 3 || dateParts[0].length != 4 || dateParts[1].length != 2 || dateParts[2].length != 2)
- return false;
-
- dateYear = dateParts[0];
- dateMonth = dateParts[1];
- dateDay = dateParts[2];
-
- // Validating the values for the date and month if in YYYY-MM-DD format.
- if (dateMonth < 1 || dateMonth > 12)
- return false;
- else if (dateDay < 1 || dateDay > 31)
- return false;
- else if ((dateMonth == 4 || dateMonth == 6 || dateMonth == 9 || dateMonth == 11) && dateDay == 31)
- return false;
- else if (dateMonth == 2) {
- // Validation for leap year dates.
- var isleap = (dateYear % 4 == 0 && (dateYear % 100 != 0 || dateYear % 400 == 0));
- if ((dateDay > 29) || (dateDay == 29 && !isleap))
- return false;
- }
-
- var digits = _.filter(dateParts, function(part){
- return (part.match( /[0-9]/g ).length == part.length);
- });
-
- return (digits.length == 3);
- }
- },
-
- validateTimeFormat: function(timeString){
-
- //If the last character is a "Z", then remove it for now
- if( timeString.substring(timeString.length-1, timeString.length) == "Z"){
- timeString = timeString.replace("Z", "", "g");
- }
-
- if(timeString.length == 8){
- var timeParts = timeString.split(":");
-
- if(timeParts.length != 3){
- return "Time must be formatted as HH:MM:SS";
- }
-
- // Validation pattern for HH:MM:SS values.
- // Range for HH validation : 00-24
- // Range for MM validation : 00-59
- // Range for SS validation : 00-59
- // Leading 0's are must in case of single digit values.
- var timePattern = /^(?:2[0-4]|[01][0-9]):[0-5][0-9]:[0-5][0-9]$/,
- validTimePattern = timeString.match(timePattern);
-
- //If the hour is 24, only accept 00:00 for MM:SS. Any minutes or seconds in the midnight hour should be
- //formatted as 00:XX:XX not 24:XX:XX
- if(validTimePattern && timeParts[0] == "24" && (timeParts[1] != "00" || timeParts[2] != "00")){
- return "The midnight hour starts at 00:00:00 and ends at 00:59:59.";
- }
- else if(!validTimePattern && parseInt(timeParts[0]) > "24"){
- return "Time of the day starts at 00:00 and ends at 23:59.";
- }
- else if(!validTimePattern && parseInt(timeParts[1]) > "59"){
- return "Minutes should be between 00 and 59.";
- }
- else if(!validTimePattern && parseInt(timeParts[2]) > "59"){
- return "Seconds should be between 00 and 59.";
- }
- else
- return true;
-
- }
- else
- return "Time must be formatted as HH:MM:SS";
- },
-
- /**
- * This function checks whether the begin date is greater than the end date.
- *
- * @param {string} beginDate the begin date string
- * @param {string} endDate the end date string
- * @return {boolean}
- */
- isGreaterDate: function(beginDate, endDate) {
-
- if(typeof beginDate == "undefined" || !beginDate)
- return false;
-
- if(typeof endDate == "undefined" || !endDate)
- return false;
-
- //Making sure that beginDate year is smaller than endDate year
- if (beginDate.length == 4 && endDate.length == 4) {
- if (beginDate > endDate) {
- return true;
- }
- }
-
- //Checking equality for either dateStrings that are greater than 4 characters
- else {
- beginDateParts = beginDate.split("-");
- endDateParts = endDate.split("-");
-
- if (beginDateParts.length == endDateParts.length) {
- if (beginDateParts[0] > endDateParts[0]) {
- return true;
- }
- else if (beginDateParts[0] == endDateParts[0]) {
- if (beginDateParts[1] > endDateParts[1]) {
- return true;
- }
- else if (beginDateParts[1] == endDateParts[1]) {
- if (beginDateParts[2] > endDateParts[2]) {
- return true;
- }
- }
- }
- }
- else {
- if (beginDateParts[0] > endDateParts[0]) {
- return true;
- }
- }
- }
- return false;
- },
-
- /**
- * This function checks whether the begin time is greater than the end time.
- *
- * @param {string} beginDate the begin date string
- * @param {string} endDate the end date string
- * @param {string} beginTime the begin time string
- * @param {string} endTime the end time string
- * @return {boolean}
- */
- isGreaterTime: function (beginDate, endDate, beginTime, endTime) {
- if(!beginTime || !endTime)
- return false;
-
- var equalDates = false;
-
- //Making sure that beginDate year is smaller than endDate year
- if (beginDate.length == 4 && endDate.length == 4) {
- if (beginDate == endDate) {
- equalDates = true;
- }
- }
-
- else {
- beginDateParts = beginDate.split("-");
- endDateParts = endDate.split("-");
-
- if (beginDateParts.length == endDateParts.length) {
- if (beginDateParts[0] == endDateParts[0]) {
- if (beginDateParts[1] == endDateParts[1]) {
- if (beginDateParts[2] == endDateParts[2]) {
- equalDates = true;
- }
- }
- }
- }
- }
-
- // If the dates are equal, check for validity of time frame.
- if (equalDates) {
- beginTimeParts = beginTime.split(":");
- endTimeParts = endTime.split(":");
- if (beginTimeParts[0] > endTimeParts[0]) {
- return true;
- }
- else if (beginTimeParts[0] == endTimeParts[0]) {
- if (beginTimeParts[1] > endTimeParts[1]) {
- return true;
- }
- else if (beginTimeParts[1] == endTimeParts[1]) {
- if (beginTimeParts[2] > endTimeParts[2]) {
- return true;
- }
- }
- }
- }
- return false;
- },
-
- /**
- * Checks if this model has no values set on it
- * @return {boolean}
- */
- isEmpty: function(){
-
- return (!this.get('beginDate') && !this.get('beginTime') && !this.get('endDate')
- && !this.get('endTime'));
-
- },
-
- /*
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211 or false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
- var emlModel = this.get("parentModel"),
+ } else {
+ if (beginDateParts[0] > endDateParts[0]) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+
+ /**
+ * This function checks whether the begin time is greater than the end time.
+ *
+ * @param {string} beginDate the begin date string
+ * @param {string} endDate the end date string
+ * @param {string} beginTime the begin time string
+ * @param {string} endTime the end time string
+ * @return {boolean}
+ */
+ isGreaterTime: function (beginDate, endDate, beginTime, endTime) {
+ if (!beginTime || !endTime) return false;
+
+ var equalDates = false;
+
+ //Making sure that beginDate year is smaller than endDate year
+ if (beginDate.length == 4 && endDate.length == 4) {
+ if (beginDate == endDate) {
+ equalDates = true;
+ }
+ } else {
+ beginDateParts = beginDate.split("-");
+ endDateParts = endDate.split("-");
+
+ if (beginDateParts.length == endDateParts.length) {
+ if (beginDateParts[0] == endDateParts[0]) {
+ if (beginDateParts[1] == endDateParts[1]) {
+ if (beginDateParts[2] == endDateParts[2]) {
+ equalDates = true;
+ }
+ }
+ }
+ }
+ }
+
+ // If the dates are equal, check for validity of time frame.
+ if (equalDates) {
+ beginTimeParts = beginTime.split(":");
+ endTimeParts = endTime.split(":");
+ if (beginTimeParts[0] > endTimeParts[0]) {
+ return true;
+ } else if (beginTimeParts[0] == endTimeParts[0]) {
+ if (beginTimeParts[1] > endTimeParts[1]) {
+ return true;
+ } else if (beginTimeParts[1] == endTimeParts[1]) {
+ if (beginTimeParts[2] > endTimeParts[2]) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Checks if this model has no values set on it
+ * @return {boolean}
+ */
+ isEmpty: function () {
+ return (
+ !this.get("beginDate") &&
+ !this.get("beginTime") &&
+ !this.get("endDate") &&
+ !this.get("endTime")
+ );
+ },
+
+ /*
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211 or false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
+ var emlModel = this.get("parentModel"),
tries = 0;
- while (emlModel.type !== "EML" && tries < 6){
- emlModel = emlModel.get("parentModel");
- tries++;
- }
+ while (emlModel.type !== "EML" && tries < 6) {
+ emlModel = emlModel.get("parentModel");
+ tries++;
+ }
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
-
- }
- });
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
+ },
+ },
+ );
- return EMLTemporalCoverage;
+ return EMLTemporalCoverage;
});
diff --git a/src/js/models/metadata/eml211/EMLText.js b/src/js/models/metadata/eml211/EMLText.js
index 37b702a56..926cdb068 100644
--- a/src/js/models/metadata/eml211/EMLText.js
+++ b/src/js/models/metadata/eml211/EMLText.js
@@ -1,266 +1,258 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/DataONEObject'],
- function($, _, Backbone, DataONEObject) {
-
+define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
+ $,
+ _,
+ Backbone,
+ DataONEObject,
+) {
/**
- * @class EMLText211
- * @classdesc A model that represents the EML 2.1.1 Text module
- * @classcategory Models/Metadata/EML211
- * @extends Backbone.Model
- */
+ * @class EMLText211
+ * @classdesc A model that represents the EML 2.1.1 Text module
+ * @classcategory Models/Metadata/EML211
+ * @extends Backbone.Model
+ */
var EMLText = Backbone.Model.extend(
- /** @lends EMLText211.prototype */{
-
- type: "EMLText",
-
- defaults: function(){
- return {
- objectXML: null,
- objectDOM: null,
- parentModel: null,
- originalText: [],
- text: [] //The text content
- }
- },
-
- initialize: function(attributes){
- var attributes = attributes || {}
-
- if(attributes.objectDOM)
- this.set(this.parse(attributes.objectDOM));
-
- if(attributes.text) {
- if (_.isArray(attributes.text)) {
- this.text = attributes.text
- } else {
- this.text = [attributes.text]
+ /** @lends EMLText211.prototype */ {
+ type: "EMLText",
+
+ defaults: function () {
+ return {
+ objectXML: null,
+ objectDOM: null,
+ parentModel: null,
+ originalText: [],
+ text: [], //The text content
+ };
+ },
+
+ initialize: function (attributes) {
+ var attributes = attributes || {};
+
+ if (attributes.objectDOM) this.set(this.parse(attributes.objectDOM));
+
+ if (attributes.text) {
+ if (_.isArray(attributes.text)) {
+ this.text = attributes.text;
+ } else {
+ this.text = [attributes.text];
+ }
}
- }
-
- this.on("change:text", this.trickleUpChange);
- },
-
- /**
- * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
- * Used during parse() and serialize()
- */
- nodeNameMap: function(){
- return{
-
- }
- },
-
- /**
- * function setText
- *
- * @param text {string} - The text, usually taken directly from an HTML textarea
- * value, to parse and set on this model
- */
- setText: function(text){
-
- if( typeof text !== "string" )
- return "";
-
- let model = this;
-
- require(["models/metadata/eml211/EML211"], function(EMLModel){
- //Get the EML model and use the cleanXMLText function to clean up the text
- text = EMLModel.prototype.cleanXMLText(text);
-
- //Get the list of paragraphs - checking for carriage returns and line feeds
- var paragraphsCR = text.split(String.fromCharCode(13));
- var paragraphsLF = text.split(String.fromCharCode(10));
-
- //Use the paragraph list that has the most
- var paragraphs = (paragraphsCR > paragraphsLF)? paragraphsCR : paragraphsLF;
- paragraphs = _.map(paragraphs, function(p){ return p.trim() });
-
- model.set("text", paragraphs);
- });
-
- },
-
- parse: function(objectDOM){
- if(!objectDOM)
- var objectDOM = this.get("objectDOM").cloneNode(true);
-
- //Start a list of paragraphs
- var paragraphs = [];
-
- //Get all the child nodes of this text element
- var $objectDOM = $(objectDOM);
-
- // Save all the contained nodes as paragraphs
- // ignore any nested formatting elements for now
- //TODO: Support more detailed text formatting
- if( $objectDOM.children().length ){
-
- paragraphs = this.parseNestedElements($objectDOM);
-
- }
- else if( objectDOM.textContent ){
- paragraphs[0] = objectDOM.textContent;
- }
+ this.on("change:text", this.trickleUpChange);
+ },
+
+ /**
+ * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
+ * Used during parse() and serialize()
+ */
+ nodeNameMap: function () {
+ return {};
+ },
+
+ /**
+ * function setText
+ *
+ * @param text {string} - The text, usually taken directly from an HTML textarea
+ * value, to parse and set on this model
+ */
+ setText: function (text) {
+ if (typeof text !== "string") return "";
+
+ let model = this;
+
+ require(["models/metadata/eml211/EML211"], function (EMLModel) {
+ //Get the EML model and use the cleanXMLText function to clean up the text
+ text = EMLModel.prototype.cleanXMLText(text);
+
+ //Get the list of paragraphs - checking for carriage returns and line feeds
+ var paragraphsCR = text.split(String.fromCharCode(13));
+ var paragraphsLF = text.split(String.fromCharCode(10));
+
+ //Use the paragraph list that has the most
+ var paragraphs =
+ paragraphsCR > paragraphsLF ? paragraphsCR : paragraphsLF;
+
+ paragraphs = _.map(paragraphs, function (p) {
+ return p.trim();
+ });
+
+ model.set("text", paragraphs);
+ });
+ },
- return {
- text: paragraphs,
- originalText: paragraphs.slice(0) //The slice function will effectively clone the array
- }
- },
+ parse: function (objectDOM) {
+ if (!objectDOM) var objectDOM = this.get("objectDOM").cloneNode(true);
- parseNestedElements: function(nodeEl){
+ //Start a list of paragraphs
+ var paragraphs = [];
- let children = $(nodeEl).children(),
- paragraphs = [];
+ //Get all the child nodes of this text element
+ var $objectDOM = $(objectDOM);
- children.each((i, childNode) => {
- if( $(childNode).children().length ){
- paragraphs = paragraphs.concat(this.parseNestedElements(childNode));
+ // Save all the contained nodes as paragraphs
+ // ignore any nested formatting elements for now
+ //TODO: Support more detailed text formatting
+ if ($objectDOM.children().length) {
+ paragraphs = this.parseNestedElements($objectDOM);
+ } else if (objectDOM.textContent) {
+ paragraphs[0] = objectDOM.textContent;
}
- else{
- paragraphs = paragraphs.concat(this.parseParagraphs(childNode));
- }
- })
-
- return paragraphs;
- },
- parseParagraphs: function(nodeEl){
- if( nodeEl.textContent ){
+ return {
+ text: paragraphs,
+ originalText: paragraphs.slice(0), //The slice function will effectively clone the array
+ };
+ },
- //Get the list of paragraphs - checking for carriage returns and line feeds
- var paragraphsCR = nodeEl.textContent.split(String.fromCharCode(13));
- var paragraphsLF = nodeEl.textContent.split(String.fromCharCode(10));
-
- //Use the paragraph list that has the most
- var paragraphs = (paragraphsCR > paragraphsLF)? paragraphsCR : paragraphsLF;
+ parseNestedElements: function (nodeEl) {
+ let children = $(nodeEl).children(),
+ paragraphs = [];
- //Trim extra whitespace off each paragraph to get rid of the line break characters
- paragraphs = _.map(paragraphs, function(text){
- if(typeof text == "string")
- return text.trim();
- else
- return text;
+ children.each((i, childNode) => {
+ if ($(childNode).children().length) {
+ paragraphs = paragraphs.concat(this.parseNestedElements(childNode));
+ } else {
+ paragraphs = paragraphs.concat(this.parseParagraphs(childNode));
+ }
});
- //Remove all falsey values - primarily empty strings
- paragraphs = _.compact(paragraphs);
-
return paragraphs;
+ },
- }
- },
-
- serialize: function(){
- var objectDOM = this.updateDOM(),
- xmlString = objectDOM.outerHTML;
+ parseParagraphs: function (nodeEl) {
+ if (nodeEl.textContent) {
+ //Get the list of paragraphs - checking for carriage returns and line feeds
+ var paragraphsCR = nodeEl.textContent.split(String.fromCharCode(13));
+ var paragraphsLF = nodeEl.textContent.split(String.fromCharCode(10));
- //Camel-case the XML
- xmlString = this.formatXML(xmlString);
+ //Use the paragraph list that has the most
+ var paragraphs =
+ paragraphsCR > paragraphsLF ? paragraphsCR : paragraphsLF;
- return xmlString;
- },
+ //Trim extra whitespace off each paragraph to get rid of the line break characters
+ paragraphs = _.map(paragraphs, function (text) {
+ if (typeof text == "string") return text.trim();
+ else return text;
+ });
- /**
- * Makes a copy of the original XML DOM and updates it with the new values from the model.
- */
- updateDOM: function(){
- var type = this.get("type") || this.get("parentAttribute") || 'text',
- objectDOM = this.get("objectDOM") ? this.get("objectDOM").cloneNode(true) : document.createElement(type);
+ //Remove all falsey values - primarily empty strings
+ paragraphs = _.compact(paragraphs);
- //FIrst check if any of the text in this model has changed since it was originally parsed
- if( _.intersection(this.get("text"), this.get("originalText")).length == this.get("text").length
- && this.get("objectDOM")){
- return objectDOM;
- }
+ return paragraphs;
+ }
+ },
- //If there is no text, return an empty string
- if( this.isEmpty() ){
- return "";
- }
+ serialize: function () {
+ var objectDOM = this.updateDOM(),
+ xmlString = objectDOM.outerHTML;
- //Empty the DOM
- $(objectDOM).empty();
+ //Camel-case the XML
+ xmlString = this.formatXML(xmlString);
- //Format the text
- var paragraphs = this.get("text");
- _.each(paragraphs, function(p){
+ return xmlString;
+ },
+
+ /**
+ * Makes a copy of the original XML DOM and updates it with the new values from the model.
+ */
+ updateDOM: function () {
+ var type = this.get("type") || this.get("parentAttribute") || "text",
+ objectDOM = this.get("objectDOM")
+ ? this.get("objectDOM").cloneNode(true)
+ : document.createElement(type);
+
+ //FIrst check if any of the text in this model has changed since it was originally parsed
+ if (
+ _.intersection(this.get("text"), this.get("originalText")).length ==
+ this.get("text").length &&
+ this.get("objectDOM")
+ ) {
+ return objectDOM;
+ }
- //If this paragraph text is a string, add a node with that text
- if( typeof p == "string" && p.trim().length )
- $(objectDOM).append("" + p + " ");
+ //If there is no text, return an empty string
+ if (this.isEmpty()) {
+ return "";
+ }
- });
+ //Empty the DOM
+ $(objectDOM).empty();
- return objectDOM;
- },
+ //Format the text
+ var paragraphs = this.get("text");
+ _.each(paragraphs, function (p) {
+ //If this paragraph text is a string, add a node with that text
+ if (typeof p == "string" && p.trim().length)
+ $(objectDOM).append("" + p + " ");
+ });
- /**
- * Climbs up the model heirarchy until it finds the EML model
- *
- * @return {EML211|false} - Returns the EML 211 Model or false if not found
- */
- getParentEML: function(){
- var emlModel = this.get("parentModel"),
+ return objectDOM;
+ },
+
+ /**
+ * Climbs up the model heirarchy until it finds the EML model
+ *
+ * @return {EML211|false} - Returns the EML 211 Model or false if not found
+ */
+ getParentEML: function () {
+ var emlModel = this.get("parentModel"),
tries = 0;
- while (emlModel.type !== "EML" && tries < 6){
- emlModel = emlModel.get("parentModel");
- tries++;
- }
+ while (emlModel.type !== "EML" && tries < 6) {
+ emlModel = emlModel.get("parentModel");
+ tries++;
+ }
- if( emlModel && emlModel.type == "EML")
- return emlModel;
- else
- return false;
+ if (emlModel && emlModel.type == "EML") return emlModel;
+ else return false;
+ },
- },
+ trickleUpChange: function () {
+ if (
+ MetacatUI.rootDataPackage &&
+ MetacatUI.rootDataPackage.packageModel
+ ) {
+ MetacatUI.rootDataPackage.packageModel.set("changed", true);
+ }
+ },
- trickleUpChange: function(){
- if( MetacatUI.rootDataPackage && MetacatUI.rootDataPackage.packageModel ){
- MetacatUI.rootDataPackage.packageModel.set("changed", true);
- }
- },
+ formatXML: function (xmlString) {
+ return DataONEObject.prototype.formatXML.call(this, xmlString);
+ },
- formatXML: function(xmlString){
- return DataONEObject.prototype.formatXML.call(this, xmlString);
- },
+ isEmpty: function () {
+ //If the text is an empty array, this is empty
+ if (Array.isArray(this.get("text")) && this.get("text").length == 0) {
+ return true;
+ }
+ //If the text is a falsey value, it is empty
+ else if (!this.get("text")) {
+ return true;
+ }
- isEmpty: function() {
+ //Iterate over each paragraph in the text array and check if it's an empty string
+ for (var i = 0; i < this.get("text").length; i++) {
+ if (this.get("text")[i].trim().length > 0) return false;
+ }
- //If the text is an empty array, this is empty
- if( Array.isArray(this.get("text")) && this.get("text").length == 0 ){
- return true;
- }
- //If the text is a falsey value, it is empty
- else if( !this.get("text") ){
return true;
- }
+ },
- //Iterate over each paragraph in the text array and check if it's an empty string
- for (var i = 0; i < this.get('text').length; i++) {
- if (this.get('text')[i].trim().length > 0)
- return false;
- }
-
- return true;
- },
+ /**
+ * Returns the EML Text paragraphs as a string, with each paragraph on a new line.
+ * @returns {string}
+ */
+ toString: function () {
+ var value = [];
- /**
- * Returns the EML Text paragraphs as a string, with each paragraph on a new line.
- * @returns {string}
- */
- toString: function() {
- var value = [];
-
- if (_.isArray(this.get('text'))) {
- value = this.get('text');
- }
+ if (_.isArray(this.get("text"))) {
+ value = this.get("text");
+ }
- return value.join('\n\n');
- }
- });
+ return value.join("\n\n");
+ },
+ },
+ );
return EMLText;
});
diff --git a/src/js/models/metadata/eml211/EMLUnit.js b/src/js/models/metadata/eml211/EMLUnit.js
index f22bf04c4..38594e92c 100644
--- a/src/js/models/metadata/eml211/EMLUnit.js
+++ b/src/js/models/metadata/eml211/EMLUnit.js
@@ -1,42 +1,40 @@
"use strict";
-define(["jquery", "underscore", "backbone"], function($, _, Backbone) {
+define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
+ /**
+ * @class EMLUnit
+ * @classdesc An EMLUnit represents a single unit defined in the EML Unit Dictionary
+ * @classcategory Models/Metadata/EML211
+ * @extends Backbone.Model
+ */
+ var EMLUnit = Backbone.Model.extend(
+ /** @lends EMLUnit.prototype */ {
+ /* The default unit fields */
+ defaults: function () {
+ return {
+ /* With X2JS, attributes are prefixed with _ */
+ _id: null,
+ _name: null,
+ _parentSI: null,
+ _multiplierToSI: null,
+ _abbreviation: null,
+ _unitType: null,
+ /* Child elements are not */
+ description: null,
+ };
+ },
- /**
- * @class EMLUnit
- * @classdesc An EMLUnit represents a single unit defined in the EML Unit Dictionary
- * @classcategory Models/Metadata/EML211
- * @extends Backbone.Model
- */
- var EMLUnit = Backbone.Model.extend(
- /** @lends EMLUnit.prototype */{
+ /* Constructs a new instance */
+ initialize: function (attrs, options) {},
- /* The default unit fields */
- defaults: function() {
- return {
- /* With X2JS, attributes are prefixed with _ */
- _id: null,
- _name: null,
- _parentSI: null,
- _multiplierToSI: null,
- _abbreviation: null,
- _unitType: null,
- /* Child elements are not */
- description: null,
- };
- },
+ /* No op - Units are read only */
+ save: function () {
+ console.log("EMLUnit is read only. Not implemented.");
- /* Constructs a new instance */
- initialize: function(attrs, options) {
- },
+ return false;
+ },
+ },
+ );
- /* No op - Units are read only */
- save: function() {
- console.log("EMLUnit is read only. Not implemented.");
-
- return false;
- }
- });
-
- return EMLUnit;
+ return EMLUnit;
});
diff --git a/src/js/models/metadata/eml220/EMLText.js b/src/js/models/metadata/eml220/EMLText.js
index bf3f7f963..e310b0433 100644
--- a/src/js/models/metadata/eml220/EMLText.js
+++ b/src/js/models/metadata/eml220/EMLText.js
@@ -1,145 +1,144 @@
/* global define */
-define(['jquery', 'underscore', 'backbone', 'models/metadata/eml211/EMLText',
- "text!templates/portals/editor/MarkdownExample.md"],
- function($, _, Backbone, EMLText211, MarkdownExample) {
-
- /**
- * @class EMLText
- * @classdesc A model that represents the EML 2.2.0 Text module
- * @classcategory Models/Metadata/EML220
- * @extends EMLText211
- */
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "models/metadata/eml211/EMLText",
+ "text!templates/portals/editor/MarkdownExample.md",
+], function ($, _, Backbone, EMLText211, MarkdownExample) {
+ /**
+ * @class EMLText
+ * @classdesc A model that represents the EML 2.2.0 Text module
+ * @classcategory Models/Metadata/EML220
+ * @extends EMLText211
+ */
var EMLText = EMLText211.extend(
- /** @lends EMLText.prototype */{
-
- defaults: function(){
- return _.extend(EMLText211.prototype.defaults(), {
- markdown: null,
- markdownExample: MarkdownExample
- });
- },
-
- /**
- * Parses the XML objectDOM into a JSON object to be set on the model.
- * If this EMLText element contains markdown, then parse it. Otherwise, use
- * the EMLText 211 parse() method.
- *
- * @param {Element} objectDOM - XML Element to parse
- * @return {JSON} The literal object to be set on the model later
- */
- parse: function(objectDOM){
- if(!objectDOM)
- var objectDOM = this.get("objectDOM").cloneNode(true);
+ /** @lends EMLText.prototype */ {
+ defaults: function () {
+ return _.extend(EMLText211.prototype.defaults(), {
+ markdown: null,
+ markdownExample: MarkdownExample,
+ });
+ },
- // Get the markdown elements inside this EMLText element
- var markdownElements = $(objectDOM).children("markdown"),
+ /**
+ * Parses the XML objectDOM into a JSON object to be set on the model.
+ * If this EMLText element contains markdown, then parse it. Otherwise, use
+ * the EMLText 211 parse() method.
+ *
+ * @param {Element} objectDOM - XML Element to parse
+ * @return {JSON} The literal object to be set on the model later
+ */
+ parse: function (objectDOM) {
+ if (!objectDOM) var objectDOM = this.get("objectDOM").cloneNode(true);
+
+ // Get the markdown elements inside this EMLText element
+ var markdownElements = $(objectDOM).children("markdown"),
modelJSON = {};
- //Grab the contents of each markdown element and add it to the JSON
- if( markdownElements.length ){
+ //Grab the contents of each markdown element and add it to the JSON
+ if (markdownElements.length) {
+ modelJSON.markdown = "";
+
+ //Get the text content of the markdown element
+ _.each(markdownElements, function (markdownElement) {
+ // Concatenate markdown elements with a space.
+ if (modelJSON.markdown === "") {
+ modelJSON.markdown = markdownElement.textContent;
+ } else {
+ modelJSON.markdown += " " + markdownElement.textContent;
+ }
+ });
+
+ //Return the JSON
+ return modelJSON;
+ }
+ //If there is no markdown, parse as the same as EML 2.1.1
+ else {
+ return EMLText211.prototype.parse(objectDOM);
+ }
+ },
- modelJSON.markdown = "";
+ /**
+ * Makes a copy of the original XML DOM and updates it with the new values from the model
+ *
+ * @param {string} textType - a string indicating the name for the outer xml element (i.e. content). Used in case there is no exisiting xmlDOM.
+ * @return {XMLElement}
+ */
+ updateDOM: function (textType) {
+ var markdown = this.get("markdown");
+
+ //If there is no markdown, parse as the same as EML 2.1.1
+ if (!markdown) {
+ return EMLText211.prototype.updateDOM.call(this);
+ } else {
+ var objectDOM = this.get("objectDOM");
- //Get the text content of the markdown element
- _.each(markdownElements, function(markdownElement){
- // Concatenate markdown elements with a space.
- if(modelJSON.markdown === ""){
- modelJSON.markdown = markdownElement.textContent;
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+ $(objectDOM).empty();
} else {
- modelJSON.markdown += " " + markdownElement.textContent;
+ // create an XML section element from scratch
+ var xmlText = "<" + textType + ">" + textType + ">",
+ objectDOM = new DOMParser().parseFromString(xmlText, "text/xml"),
+ objectDOM = $(objectDOM).children()[0];
}
- });
-
- //Return the JSON
- return modelJSON;
- }
- //If there is no markdown, parse as the same as EML 2.1.1
- else{
- return EMLText211.prototype.parse(objectDOM);
- }
-
- },
-
- /**
- * Makes a copy of the original XML DOM and updates it with the new values from the model
- *
- * @param {string} textType - a string indicating the name for the outer xml element (i.e. content). Used in case there is no exisiting xmlDOM.
- * @return {XMLElement}
- */
- updateDOM: function(textType){
- var markdown = this.get("markdown");
-
- //If there is no markdown, parse as the same as EML 2.1.1
- if(!markdown){
- return EMLText211.prototype.updateDOM.call(this);
- }
- else{
+ // There could be multiple markdown elements, or markdown could be a string
+ if (typeof markdown == "string") {
+ markdown = [markdown];
+ }
- var objectDOM = this.get("objectDOM");
+ _.each(
+ markdown,
+ function (markdownElement) {
+ // Create markdown element with content wrapped in CDATA tags
+ var markdownSerialized =
+ objectDOM.ownerDocument.createElement("markdown");
+ var cdataMarkdown =
+ objectDOM.ownerDocument.createCDATASection(markdownElement);
+ $(markdownSerialized).append(cdataMarkdown);
+ $(objectDOM).append(markdownSerialized);
+ },
+ this,
+ );
+
+ return objectDOM;
+ }
+ },
- if (objectDOM) {
- objectDOM = objectDOM.cloneNode(true);
- $(objectDOM).empty();
+ /**
+ * @overrides EML211.setText
+ * If there is markdown, then the markdown gets updated. Otherwise, we default to the EML211.setText() functionality
+ */
+ setText: function (text) {
+ if (this.get("markdown")) {
+ this.set("markdown", text);
} else {
- // create an XML section element from scratch
- var xmlText = "<" + textType + ">" + textType + ">",
- objectDOM = new DOMParser().parseFromString(xmlText, "text/xml"),
- objectDOM = $(objectDOM).children()[0];
+ return EMLText211.prototype.setText.call(this, text);
}
+ },
- // There could be multiple markdown elements, or markdown could be a string
- if(typeof markdown == "string"){
- markdown = [markdown]
+ /**
+ * @overrides EML211.toString
+ * Returns the markdown string or EML Text paragraphs as a string, if there is no markdown. (For
+ * compatability with EML 2.1.1). Returns an empty string if neither are set on the model.
+ * @returns {string}
+ */
+ toString: function () {
+ try {
+ return (
+ this.get("markdown") ||
+ EMLText211.prototype.toString.call(this) ||
+ ""
+ );
+ } catch (e) {
+ console.error("Failed to convert EMLText toString(): ", e);
+ return "";
}
-
- _.each(markdown, function(markdownElement){
- // Create markdown element with content wrapped in CDATA tags
- var markdownSerialized = objectDOM.ownerDocument.createElement("markdown");
- var cdataMarkdown = objectDOM.ownerDocument.createCDATASection(markdownElement);
- $(markdownSerialized).append(cdataMarkdown);
- $(objectDOM).append(markdownSerialized)
- }, this);
-
- return objectDOM
-
- }
-
+ },
},
-
- /**
- * @overrides EML211.setText
- * If there is markdown, then the markdown gets updated. Otherwise, we default to the EML211.setText() functionality
- */
- setText: function(text){
-
- if( this.get("markdown") ){
- this.set("markdown", text);
- }
- else{
- return EMLText211.prototype.setText.call(this, text);
- }
-
- },
-
- /**
- * @overrides EML211.toString
- * Returns the markdown string or EML Text paragraphs as a string, if there is no markdown. (For
- * compatability with EML 2.1.1). Returns an empty string if neither are set on the model.
- * @returns {string}
- */
- toString: function(){
- try{
- return this.get("markdown") || EMLText211.prototype.toString.call(this) || "";
- }
- catch(e){
- console.error("Failed to convert EMLText toString(): ", e);
- return "";
- }
- }
-
- });
+ );
return EMLText;
-
});
diff --git a/src/js/models/portals/PortalImage.js b/src/js/models/portals/PortalImage.js
index 6e9a3b7dd..b52132652 100644
--- a/src/js/models/portals/PortalImage.js
+++ b/src/js/models/portals/PortalImage.js
@@ -1,311 +1,318 @@
/* global define */
-define(["jquery",
- "underscore",
- "backbone",
- "models/DataONEObject"
- ],
- function($, _, Backbone, DataONEObject) {
-
+define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
+ $,
+ _,
+ Backbone,
+ DataONEObject,
+) {
+ /**
+ * @class PortalImage
+ * @classdesc A Portal Image model represents a single image used in a Portal
+ * @classcategory Models/Portals
+ * @extends Backbone.Model
+ */
+ var PortalImageModel = DataONEObject.extend(
+ /** @lends PortalImage.prototype */ {
/**
- * @class PortalImage
- * @classdesc A Portal Image model represents a single image used in a Portal
- * @classcategory Models/Portals
- * @extends Backbone.Model
+ * @inheritdoc
*/
- var PortalImageModel = DataONEObject.extend(
- /** @lends PortalImage.prototype */{
-
- /**
- * @inheritdoc
- */
- type: "PortalImage",
-
- defaults: function(){
- return _.extend(DataONEObject.prototype.defaults(), {
- identifier: "",
- imageURL: "",
- label: "",
- associatedURL: "",
- objectDOM: null,
- nodeName: "image",
- portalModel: null
- });
- },
-
- initialize: function(attrs){
-
- // Call the super class initialize function
- DataONEObject.prototype.initialize.call(this, attrs);
-
- // If the image model is initialized with an identifier but no image URL,
- // create the full image URL
- if(this.get("identifier") && !this.get("imageURL")){
-
- var baseURL = this.getBaseURL(),
- imageURL = baseURL + this.get("identifier");
- this.set("imageURL", imageURL);
- }
-
- },
-
- /**
- * Parses an ImageType XML element from a portal document
- *
- * @param {XMLElement} objectDOM - An ImageType XML element from a portal document
- * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
- */
- parse: function(objectDOM){
+ type: "PortalImage",
+
+ defaults: function () {
+ return _.extend(DataONEObject.prototype.defaults(), {
+ identifier: "",
+ imageURL: "",
+ label: "",
+ associatedURL: "",
+ objectDOM: null,
+ nodeName: "image",
+ portalModel: null,
+ });
+ },
+
+ initialize: function (attrs) {
+ // Call the super class initialize function
+ DataONEObject.prototype.initialize.call(this, attrs);
+
+ // If the image model is initialized with an identifier but no image URL,
+ // create the full image URL
+ if (this.get("identifier") && !this.get("imageURL")) {
+ var baseURL = this.getBaseURL(),
+ imageURL = baseURL + this.get("identifier");
+ this.set("imageURL", imageURL);
+ }
+ },
- if(!objectDOM){
- objectDOM = this.get("objectDOM");
+ /**
+ * Parses an ImageType XML element from a portal document
+ *
+ * @param {XMLElement} objectDOM - An ImageType XML element from a portal document
+ * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
+ */
+ parse: function (objectDOM) {
+ if (!objectDOM) {
+ objectDOM = this.get("objectDOM");
- if(!objectDOM){
- return {};
- }
+ if (!objectDOM) {
+ return {};
}
+ }
- var portalModel = this.get("portalModel"),
- $objectDOM = $(objectDOM),
- modelJSON = {};
+ var portalModel = this.get("portalModel"),
+ $objectDOM = $(objectDOM),
+ modelJSON = {};
+
+ if (portalModel) {
+ modelJSON.datasource = portalModel.get("datasource");
+ modelJSON.submitter = portalModel.get("submitter");
+ modelJSON.rightsHolder = portalModel.get("rightsHolder");
+ modelJSON.originMemberNode = portalModel.get("originMemberNode");
+ modelJSON.authoritativeMemberNode = portalModel.get(
+ "authoritativeMemberNode",
+ );
+ }
- if( portalModel ){
- modelJSON.datasource = portalModel.get("datasource");
- modelJSON.submitter = portalModel.get("submitter");
- modelJSON.rightsHolder = portalModel.get("rightsHolder");
- modelJSON.originMemberNode = portalModel.get("originMemberNode");
- modelJSON.authoritativeMemberNode = portalModel.get("authoritativeMemberNode");
- }
+ modelJSON.nodeName = objectDOM.nodeName;
- modelJSON.nodeName = objectDOM.nodeName;
+ //Parse all the simple string elements
+ modelJSON.label = $objectDOM.children("label").text();
+ modelJSON.associatedURL = $objectDOM.children("associatedURL").text();
- //Parse all the simple string elements
- modelJSON.label = $objectDOM.children("label").text();
- modelJSON.associatedURL = $objectDOM.children("associatedURL").text();
+ // Parse the image URL or identifier
+ modelJSON.identifier = $objectDOM.children("identifier").text();
+ if (modelJSON.identifier) {
+ if (modelJSON.identifier.substring(0, 4) !== "http") {
+ var baseURL = this.getBaseURL();
+ modelJSON.imageURL = baseURL + modelJSON.identifier;
+ } else {
+ modelJSON.imageURL = modelJSON.identifier;
+ }
+ }
- // Parse the image URL or identifier
- modelJSON.identifier = $objectDOM.children("identifier").text();
- if( modelJSON.identifier ){
- if( modelJSON.identifier.substring(0, 4) !== "http" ){
+ return modelJSON;
+ },
- var baseURL = this.getBaseURL();
- modelJSON.imageURL = baseURL + modelJSON.identifier;
+ /**
+ * imageExists - Check if an image exists with the given
+ * url, or if no url provided, with the baseURL + identifier
+ *
+ * @param {string} imageURL The image URL to check
+ * @return {boolean} Returns true if an HTTP request returns anything but 404
+ */
+ imageExists: function (imageURL) {
+ if (!imageURL) {
+ this.get("imageURL");
+ }
- }
- else{
- modelJSON.imageURL = modelJSON.identifier;
- }
- }
+ if (!imageURL && this.get("identifier")) {
+ var baseURL = this.getBaseURL(),
+ imageURL = baseURL + this.get("identifier");
+ }
- return modelJSON;
+ if (!imageURL) {
+ return false;
+ }
- },
+ var http = new XMLHttpRequest();
+ http.open("HEAD", imageURL, false);
+ http.send();
- /**
- * imageExists - Check if an image exists with the given
- * url, or if no url provided, with the baseURL + identifier
- *
- * @param {string} imageURL The image URL to check
- * @return {boolean} Returns true if an HTTP request returns anything but 404
- */
- imageExists: function (imageURL){
+ return http.status != 404;
+ },
- if(!imageURL){
- this.get("imageURL")
+ /**
+ * getBaseURL - Get the base URL to use with an image identifier
+ *
+ * @return {string} The image base URL, or an empty string if not found
+ */
+ getBaseURL: function () {
+ var url = "",
+ portalModel = this.get("portalModel"),
+ // datasource = portalModel ? portalModel.get("datasource") : false;
+ datasource = this.get("datasource"),
+ datasource =
+ portalModel && !datasource
+ ? portalModel.get("datasource")
+ : datasource;
+
+ if (MetacatUI.appModel.get("isCN")) {
+ var sourceRepo;
+
+ //Use the object service URL from the origin MN/datasource
+ if (datasource) {
+ sourceRepo = MetacatUI.nodeModel.getMember(datasource);
}
-
- if(!imageURL && this.get("identifier")){
- var baseURL = this.getBaseURL(),
- imageURL = baseURL + this.get("identifier");
+ // Use the object service URL from the alt repo
+ if (!sourceRepo) {
+ sourceRepo = MetacatUI.appModel.getActiveAltRepo();
}
-
- if(!imageURL){
- return false
+ if (sourceRepo) {
+ url = sourceRepo.objectServiceUrl;
}
+ }
- var http = new XMLHttpRequest();
- http.open('HEAD', imageURL, false);
- http.send();
-
- return http.status != 404;
-
- },
-
- /**
- * getBaseURL - Get the base URL to use with an image identifier
- *
- * @return {string} The image base URL, or an empty string if not found
- */
- getBaseURL: function(){
-
- var url = "",
- portalModel = this.get("portalModel"),
- // datasource = portalModel ? portalModel.get("datasource") : false;
- datasource = this.get("datasource"),
- datasource = (portalModel && !datasource) ? portalModel.get("datasource") : datasource;
-
- if( MetacatUI.appModel.get("isCN") ){
- var sourceRepo;
-
- //Use the object service URL from the origin MN/datasource
- if( datasource ){
- sourceRepo = MetacatUI.nodeModel.getMember(datasource);
- }
- // Use the object service URL from the alt repo
- if( !sourceRepo ){
- sourceRepo = MetacatUI.appModel.getActiveAltRepo();
- }
- if( sourceRepo ){
- url = sourceRepo.objectServiceUrl;
- }
+ if (!url && datasource) {
+ var imageMN = _.findWhere(
+ MetacatUI.appModel.get("alternateRepositories"),
+ { identifier: datasource },
+ );
+ if (imageMN) {
+ url = imageMN.objectServiceUrl;
}
+ }
- if(!url && datasource){
- var imageMN = _.findWhere(MetacatUI.appModel.get("alternateRepositories"), { identifier: datasource });
- if(imageMN){
- url = imageMN.objectServiceUrl;
- }
- }
+ //If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel
+ if (!url) {
+ url =
+ MetacatUI.appModel.get("objectServiceUrl") ||
+ MetacatUI.appModel.get("resolveServiceUrl");
+ }
+ return url;
+ },
- //If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel
- if( !url ){
- url = MetacatUI.appModel.get("objectServiceUrl") || MetacatUI.appModel.get("resolveServiceUrl");
- }
- return url;
- },
-
- /**
- * Makes a copy of the original XML DOM and updates it with the new values from the model
- *
- * @return {XMLElement} An updated ImageType XML element from a portal document
- */
- updateDOM: function() {
-
- //If there is no identifier, don't serialize anything
- if( !this.get("identifier") ){
- return "";
- }
+ /**
+ * Makes a copy of the original XML DOM and updates it with the new values from the model
+ *
+ * @return {XMLElement} An updated ImageType XML element from a portal document
+ */
+ updateDOM: function () {
+ //If there is no identifier, don't serialize anything
+ if (!this.get("identifier")) {
+ return "";
+ }
- var objectDOM = this.get("objectDOM");
+ var objectDOM = this.get("objectDOM");
+
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+ $(objectDOM).empty();
+ } else {
+ // create an XML image element from scratch
+ var xmlText =
+ "<" + this.get("nodeName") + ">" + this.get("nodeName") + ">",
+ objectDOM = new DOMParser().parseFromString(xmlText, "text/xml"),
+ objectDOM = $(objectDOM).children()[0];
+ }
- if (objectDOM) {
- objectDOM = objectDOM.cloneNode(true);
- $(objectDOM).empty();
- } else {
- // create an XML image element from scratch
- var xmlText = "<" + this.get("nodeName") + ">" + this.get("nodeName") + ">",
- objectDOM = new DOMParser().parseFromString(xmlText, "text/xml"),
- objectDOM = $(objectDOM).children()[0];
- }
-
- // get new image data
- var imageData = {
- identifier: this.get("identifier"),
- label: this.get("label"),
- associatedURL: this.get("associatedURL")
+ // get new image data
+ var imageData = {
+ identifier: this.get("identifier"),
+ label: this.get("label"),
+ associatedURL: this.get("associatedURL"),
+ };
+
+ _.map(imageData, function (value, nodeName) {
+ // Don't serialize falsey values
+ if (value) {
+ // Make new sub-node
+ var imageSubnodeSerialized =
+ objectDOM.ownerDocument.createElement(nodeName);
+ $(imageSubnodeSerialized).text(value);
+ // Append new sub-node to objectDOM
+ $(objectDOM).append(imageSubnodeSerialized);
}
+ });
- _.map(imageData, function(value, nodeName){
-
- // Don't serialize falsey values
- if(value){
- // Make new sub-node
- var imageSubnodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
- $(imageSubnodeSerialized).text(value);
- // Append new sub-node to objectDOM
- $(objectDOM).append(imageSubnodeSerialized);
-
- }
-
- });
-
- return objectDOM;
- },
-
- /**
- * Overrides the default Backbone.Model.validate.function() to
- * check if this PortalImage model has all the required values necessary
- * to save to the server.
- *
- * @return {Object} If there are errors, an object comprising error
- * messages. If no errors, returns nothing.
- */
- validate: function(){
- try {
-
- var errors = {},
- requiredFields = MetacatUI.appModel.get("portalEditorRequiredFields"),
- label = this.get("label"),
- url = this.get("associatedURL"),
- id = this.get("identifier"),
- genericLabels = ["logo", "image"], // not set by the user
- hasLabel = (label && typeof label == "string" && !genericLabels.includes(label)) ? true : false,
- hasURL = (url && typeof url == "string") ? true : false,
- hasId = (id && typeof id == "string") ? true : false,
- urlRegex = new RegExp(/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/);
-
- // If it's a logo, check whether it's a required image
- if(this.get("nodeName") === "logo" && requiredFields.logo && !hasId){
- errors["identifier"] = "An image is required."
- return errors
- }
- // If it's a section image, check whether it's a required image
- else if(this.get("nodeName") === "image" && requiredFields.sectionImage && !hasId){
- errors["identifier"] = "An image is required."
- return errors
- }
- // If none of the fields have values, the portalImage won't be serialized
- else if(!hasId && !hasURL && !hasLabel){
- return
- }
-
- // If the URL isn't a valid format, add an error message
- if(hasURL && !urlRegex.test(url)){
- errors["associatedURL"] = "Enter a valid URL."
- }
- //If the URL is valid, check if there is an http or https protocol
- else if(hasURL && url.substring(0,4) != "http"){
- //If not, add the https protocol
- this.set("associatedURL", "https://" + url);
- }
+ return objectDOM;
+ },
+ /**
+ * Overrides the default Backbone.Model.validate.function() to
+ * check if this PortalImage model has all the required values necessary
+ * to save to the server.
+ *
+ * @return {Object} If there are errors, an object comprising error
+ * messages. If no errors, returns nothing.
+ */
+ validate: function () {
+ try {
+ var errors = {},
+ requiredFields = MetacatUI.appModel.get(
+ "portalEditorRequiredFields",
+ ),
+ label = this.get("label"),
+ url = this.get("associatedURL"),
+ id = this.get("identifier"),
+ genericLabels = ["logo", "image"], // not set by the user
+ hasLabel =
+ label &&
+ typeof label == "string" &&
+ !genericLabels.includes(label)
+ ? true
+ : false,
+ hasURL = url && typeof url == "string" ? true : false,
+ hasId = id && typeof id == "string" ? true : false,
+ urlRegex = new RegExp(
+ /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/,
+ );
+
+ // If it's a logo, check whether it's a required image
+ if (
+ this.get("nodeName") === "logo" &&
+ requiredFields.logo &&
+ !hasId
+ ) {
+ errors["identifier"] = "An image is required.";
+ return errors;
+ }
+ // If it's a section image, check whether it's a required image
+ else if (
+ this.get("nodeName") === "image" &&
+ requiredFields.sectionImage &&
+ !hasId
+ ) {
+ errors["identifier"] = "An image is required.";
return errors;
-
}
- catch(e){
- console.error("Error validating a portal image. Error message:" + e);
+ // If none of the fields have values, the portalImage won't be serialized
+ else if (!hasId && !hasURL && !hasLabel) {
return;
}
- },
-
-
- /**
- * isEmpty - Returns true if the PortalImage model has no label, no associatedURL, and no identifier
- *
- * @return {boolean} true if the model is empty, false if it has at least a label, url, or id
- */
- isEmpty: function(){
- return (
- !this.get("label") &&
- !this.get("associatedURL") &&
- !this.get("identifier")
- ) ;
- },
-
- /**
- * Returns true if this PortalImage hasn't been saved to a Portal yet, so it is a new object.
- * For now, all PortalImages will be considered new objects since we will not be performing updates on them.
- * @return {boolean}
- */
- isNew: function(){
- if( this.get("identifier") ){
- return false;
+
+ // If the URL isn't a valid format, add an error message
+ if (hasURL && !urlRegex.test(url)) {
+ errors["associatedURL"] = "Enter a valid URL.";
}
- else{
- return true;
+ //If the URL is valid, check if there is an http or https protocol
+ else if (hasURL && url.substring(0, 4) != "http") {
+ //If not, add the https protocol
+ this.set("associatedURL", "https://" + url);
}
+
+ return errors;
+ } catch (e) {
+ console.error("Error validating a portal image. Error message:" + e);
+ return;
}
+ },
- });
+ /**
+ * isEmpty - Returns true if the PortalImage model has no label, no associatedURL, and no identifier
+ *
+ * @return {boolean} true if the model is empty, false if it has at least a label, url, or id
+ */
+ isEmpty: function () {
+ return (
+ !this.get("label") &&
+ !this.get("associatedURL") &&
+ !this.get("identifier")
+ );
+ },
+
+ /**
+ * Returns true if this PortalImage hasn't been saved to a Portal yet, so it is a new object.
+ * For now, all PortalImages will be considered new objects since we will not be performing updates on them.
+ * @return {boolean}
+ */
+ isNew: function () {
+ if (this.get("identifier")) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+ },
+ );
- return PortalImageModel;
+ return PortalImageModel;
});
diff --git a/src/js/models/portals/PortalModel.js b/src/js/models/portals/PortalModel.js
index c9bfe4ba8..e06bd27e9 100644
--- a/src/js/models/portals/PortalModel.js
+++ b/src/js/models/portals/PortalModel.js
@@ -2,2171 +2,2279 @@
* @exports PortalModel
*/
/* global define */
-define(["jquery",
- "underscore",
- "backbone",
- "gmaps",
- "uuid",
- "collections/Filters",
- "collections/SolrResults",
- "models/filters/Filter",
- "models/portals/PortalSectionModel",
- "models/portals/PortalVizSectionModel",
- "models/portals/PortalImage",
- "models/metadata/eml211/EMLParty",
- "models/metadata/eml220/EMLText",
- "models/CollectionModel",
- "models/Search",
- "models/filters/FilterGroup",
- "models/Map",
- ],
- function($, _, Backbone, gmaps, uuid, Filters, SolrResults, FilterModel,
- PortalSectionModel, PortalVizSectionModel, PortalImage,
- EMLParty, EMLText, CollectionModel, SearchModel, FilterGroup, MapModel ) {
- /**
- * @classdesc A PortalModel is a specialized collection that represents a portal,
- * including the associated data, people, portal descriptions, results and
- * visualizations. It also includes settings for customized filtering of the
- * associated data, and properties used to customized the map display and the
- * overall branding of the portal.
- *
- * @class PortalModel
- * @classcategory Models/Portals
- * @extends CollectionModel
- * @module models/PortalModel
- * @name PortalModel
- * @constructor
- */
- var PortalModel = CollectionModel.extend(
- /** @lends PortalModel.prototype */{
-
- /**
- * The name of this type of model
- * @type {string}
- */
- type: "Portal",
-
- /**
- * Overrides the default Backbone.Model.defaults() function to
- * specify default attributes for the portal model
- * @type {object}
- */
- defaults: function() {
- return _.extend(CollectionModel.prototype.defaults(), {
- id: null,
- objectXML: null,
- formatId: MetacatUI.appModel.get("portalEditorSerializationFormat"),
- formatType: "METADATA",
- type: "portal",
- //Is true if the last fetch was sent with user credentials. False if not.
- fetchedWithAuth: null,
- logo: null,
- sections: [],
- associatedParties: [],
- acknowledgments: null,
- acknowledgmentsLogos: [],
- awards: [],
- checkedNodeLabels: false,
- labelDoubleChecked: false,
- literatureCited: [],
- filterGroups: [],
- createSeriesId: true, //If true, a seriesId will be created when this object is saved.
- // The portal document options may specify section to hide
- edit: false, // Set to true if this model is being used in a portal editor view
- hideMetrics: null,
- hideData: null,
- hideMembers: null,
- hideMap: null,
- // List of section labels indicating the order in which to display the sections.
- // Labels must exactly match the labels set on sections, or the values set on the
- // metricsLabel, dataLabel, and membersLabel options.
- pageOrder: null,
- //Options for the custom section labels
- //NOTE: This are not fully supported yet.
- metricsLabel: "Metrics",
- dataLabel: "Data",
- membersLabel: "Members",
- // Map options, as specified in the portal document options
- mapZoomLevel: 3,
- mapCenterLatitude: null,
- mapCenterLongitude: null,
- mapShapeHue: 200,
- // The MapModel
- mapModel: gmaps ? new MapModel() : null,
- optionNames: ["primaryColor", "secondaryColor", "accentColor",
- "mapZoomLevel", "mapCenterLatitude", "mapCenterLongitude",
- "mapShapeHue", "hideData", "hideMetrics", "hideMembers",
- "pageOrder", "layout", "theme"
- ],
- // Portal view colors, as specified in the portal document options
- primaryColor: MetacatUI.appModel.get("portalDefaults").primaryColor || "#006699",
- secondaryColor: MetacatUI.appModel.get("portalDefaults").secondaryColor || "#009299",
- accentColor: MetacatUI.appModel.get("portalDefaults").accentColor || "#f89406",
- primaryColorRGB: null,
- secondaryColorRGB: null,
- accentColorRGB: null,
- primaryColorTransparent: MetacatUI.appModel.get("portalDefaults").primaryColorTransparent || "rgba(0, 102, 153, .7)",
- secondaryColorTransparent: MetacatUI.appModel.get("portalDefaults").secondaryColorTransparent || "rgba(0, 146, 153, .7)",
- accentColorTransparent: MetacatUI.appModel.get("portalDefaults").accentColorTransparent || "rgba(248, 148, 6, .7)",
- theme: null,
- layout: null
+define([
+ "jquery",
+ "underscore",
+ "backbone",
+ "gmaps",
+ "uuid",
+ "collections/Filters",
+ "collections/SolrResults",
+ "models/filters/Filter",
+ "models/portals/PortalSectionModel",
+ "models/portals/PortalVizSectionModel",
+ "models/portals/PortalImage",
+ "models/metadata/eml211/EMLParty",
+ "models/metadata/eml220/EMLText",
+ "models/CollectionModel",
+ "models/Search",
+ "models/filters/FilterGroup",
+ "models/Map",
+], function (
+ $,
+ _,
+ Backbone,
+ gmaps,
+ uuid,
+ Filters,
+ SolrResults,
+ FilterModel,
+ PortalSectionModel,
+ PortalVizSectionModel,
+ PortalImage,
+ EMLParty,
+ EMLText,
+ CollectionModel,
+ SearchModel,
+ FilterGroup,
+ MapModel,
+) {
+ /**
+ * @classdesc A PortalModel is a specialized collection that represents a portal,
+ * including the associated data, people, portal descriptions, results and
+ * visualizations. It also includes settings for customized filtering of the
+ * associated data, and properties used to customized the map display and the
+ * overall branding of the portal.
+ *
+ * @class PortalModel
+ * @classcategory Models/Portals
+ * @extends CollectionModel
+ * @module models/PortalModel
+ * @name PortalModel
+ * @constructor
+ */
+ var PortalModel = CollectionModel.extend(
+ /** @lends PortalModel.prototype */ {
+ /**
+ * The name of this type of model
+ * @type {string}
+ */
+ type: "Portal",
+
+ /**
+ * Overrides the default Backbone.Model.defaults() function to
+ * specify default attributes for the portal model
+ * @type {object}
+ */
+ defaults: function () {
+ return _.extend(CollectionModel.prototype.defaults(), {
+ id: null,
+ objectXML: null,
+ formatId: MetacatUI.appModel.get("portalEditorSerializationFormat"),
+ formatType: "METADATA",
+ type: "portal",
+ //Is true if the last fetch was sent with user credentials. False if not.
+ fetchedWithAuth: null,
+ logo: null,
+ sections: [],
+ associatedParties: [],
+ acknowledgments: null,
+ acknowledgmentsLogos: [],
+ awards: [],
+ checkedNodeLabels: false,
+ labelDoubleChecked: false,
+ literatureCited: [],
+ filterGroups: [],
+ createSeriesId: true, //If true, a seriesId will be created when this object is saved.
+ // The portal document options may specify section to hide
+ edit: false, // Set to true if this model is being used in a portal editor view
+ hideMetrics: null,
+ hideData: null,
+ hideMembers: null,
+ hideMap: null,
+ // List of section labels indicating the order in which to display the sections.
+ // Labels must exactly match the labels set on sections, or the values set on the
+ // metricsLabel, dataLabel, and membersLabel options.
+ pageOrder: null,
+ //Options for the custom section labels
+ //NOTE: This are not fully supported yet.
+ metricsLabel: "Metrics",
+ dataLabel: "Data",
+ membersLabel: "Members",
+ // Map options, as specified in the portal document options
+ mapZoomLevel: 3,
+ mapCenterLatitude: null,
+ mapCenterLongitude: null,
+ mapShapeHue: 200,
+ // The MapModel
+ mapModel: gmaps ? new MapModel() : null,
+ optionNames: [
+ "primaryColor",
+ "secondaryColor",
+ "accentColor",
+ "mapZoomLevel",
+ "mapCenterLatitude",
+ "mapCenterLongitude",
+ "mapShapeHue",
+ "hideData",
+ "hideMetrics",
+ "hideMembers",
+ "pageOrder",
+ "layout",
+ "theme",
+ ],
+ // Portal view colors, as specified in the portal document options
+ primaryColor:
+ MetacatUI.appModel.get("portalDefaults").primaryColor || "#006699",
+ secondaryColor:
+ MetacatUI.appModel.get("portalDefaults").secondaryColor ||
+ "#009299",
+ accentColor:
+ MetacatUI.appModel.get("portalDefaults").accentColor || "#f89406",
+ primaryColorRGB: null,
+ secondaryColorRGB: null,
+ accentColorRGB: null,
+ primaryColorTransparent:
+ MetacatUI.appModel.get("portalDefaults").primaryColorTransparent ||
+ "rgba(0, 102, 153, .7)",
+ secondaryColorTransparent:
+ MetacatUI.appModel.get("portalDefaults")
+ .secondaryColorTransparent || "rgba(0, 146, 153, .7)",
+ accentColorTransparent:
+ MetacatUI.appModel.get("portalDefaults").accentColorTransparent ||
+ "rgba(248, 148, 6, .7)",
+ theme: null,
+ layout: null,
+ });
+ },
+
+ /**
+ * The default text to use for a new section label added by the user
+ * @type {string}
+ */
+ newSectionLabel: "Untitled",
+
+ /**
+ * Overrides the default Backbone.Model.initialize() function to
+ * provide some custom initialize options
+ *
+ * @param {} options -
+ */
+ initialize: function (attrs) {
+ //Call the super class initialize function
+ CollectionModel.prototype.initialize.call(this, attrs);
+
+ // Generate transparent colours from the primary, secondary, and accent colors
+ // TODO
+
+ if (attrs.isNew) {
+ this.set("synced", true);
+ //Create an isPartOf filter for this new Portal
+ this.addIsPartOfFilter();
+
+ var model = this;
+
+ // Insert new sections if any are set in the appModel
+
+ var portalDefaults = MetacatUI.appModel.get("portalDefaults"),
+ defaultSections = portalDefaults ? portalDefaults.sections : [];
+
+ if (
+ defaultSections &&
+ defaultSections.length &&
+ Array.isArray(defaultSections)
+ ) {
+ defaultSections.forEach(function (section, index) {
+ // If there is at least one section default set...
+ if (section.title || section.label) {
+ var newDefaultSection = new PortalSectionModel({
+ title: section.title || "",
+ label: section.label || this.newSectionLabel,
+ // Set a default image on new markdown sections
+ image: model.getRandomSectionImage(),
+ portalModel: model,
});
- },
-
- /**
- * The default text to use for a new section label added by the user
- * @type {string}
- */
- newSectionLabel: "Untitled",
-
- /**
- * Overrides the default Backbone.Model.initialize() function to
- * provide some custom initialize options
- *
- * @param {} options -
- */
- initialize: function(attrs) {
-
- //Call the super class initialize function
- CollectionModel.prototype.initialize.call(this, attrs);
-
- // Generate transparent colours from the primary, secondary, and accent colors
- // TODO
-
- if( attrs.isNew ){
- this.set("synced", true);
- //Create an isPartOf filter for this new Portal
- this.addIsPartOfFilter();
-
- var model = this;
-
- // Insert new sections if any are set in the appModel
-
- var portalDefaults = MetacatUI.appModel.get("portalDefaults"),
- defaultSections = portalDefaults ? portalDefaults.sections : [];
-
- if(defaultSections && defaultSections.length && Array.isArray(defaultSections)){
- defaultSections.forEach(function(section, index){
- // If there is at least one section default set...
- if(section.title || section.label){
- var newDefaultSection = new PortalSectionModel({
- title: section.title || "",
- label: section.label || this.newSectionLabel,
- // Set a default image on new markdown sections
- image: model.getRandomSectionImage(),
- portalModel: model
- });
- model.addSection(newDefaultSection);
- }
- });
- }
+ model.addSection(newDefaultSection);
}
-
- // check for info received from Bookkeeper
- if( MetacatUI.appModel.get("enableBookkeeperServices") ){
-
- this.listenTo( MetacatUI.appUserModel, "change:dataoneSubscription", function(){
- if(MetacatUI.appUserModel.get("dataoneSubscription").isTrialing()) {
- this.setRandomLabel();
- }
- });
-
- //Fetch the user subscription info
- MetacatUI.appUserModel.fetchSubscription();
+ });
+ }
+ }
+
+ // check for info received from Bookkeeper
+ if (MetacatUI.appModel.get("enableBookkeeperServices")) {
+ this.listenTo(
+ MetacatUI.appUserModel,
+ "change:dataoneSubscription",
+ function () {
+ if (
+ MetacatUI.appUserModel.get("dataoneSubscription").isTrialing()
+ ) {
+ this.setRandomLabel();
}
-
- // Cache this model for later use
- this.cachePortal();
-
},
+ );
+
+ //Fetch the user subscription info
+ MetacatUI.appUserModel.fetchSubscription();
+ }
+
+ // Cache this model for later use
+ this.cachePortal();
+ },
+
+ /**
+ * getRandomSectionImage - Using the list of image identifiers set
+ * in the app config, select an image to use for a portal section.
+ * The function will not return the same image until all the images
+ * have been returned at least once. If an image would return a 404
+ * error, it is skipped. If all images give 404s, an empty string
+ * is returned.
+ *
+ * @return {PortalImage} A portal image model to use in a section model
+ */
+ getRandomSectionImage: function () {
+ // This variable will hold the section image to return, if any
+ var newSectionImage = "",
+ // The default portal values set in the config
+ portalDefaults = MetacatUI.appModel.get("portalDefaults"),
+ // Check if default images are set on the model already
+ defaultImageIds = this.get("defaultSectionImageIds"),
+ // Keep track of where we are in the list of default images,
+ // so there's not too much repetition
+ runningNumber = this.get("defaultImageRunningNumber") || 0;
+
+ // If none are set, get the configured default image IDs,
+ // shuffle them, and set them on the model.
+ if (!defaultImageIds || !defaultImageIds.length) {
+ // Get the list of default section image IDs from the appModel
+ defaultImageIds = portalDefaults
+ ? portalDefaults.sectionImageIdentifiers
+ : false;
+
+ // If some are configured...
+ if (defaultImageIds && defaultImageIds.length) {
+ // ...Shuffle the images...
+ for (let i = defaultImageIds.length - 1; i > 0; i--) {
+ let j = Math.floor(Math.random() * (i + 1));
+ [defaultImageIds[i], defaultImageIds[j]] = [
+ defaultImageIds[j],
+ defaultImageIds[i],
+ ];
+ }
+ // ... and save the shuffled list to the portal model
+ this.set("defaultSectionImageIds", defaultImageIds);
+ }
+ }
+
+ // Can't get a random image if none are configured
+ if (!defaultImageIds) {
+ console.log(
+ "Can't set a default image on new markdown sections because there are no default image IDs set. Check portalDefaults.sectionImageIdentifiers in the config file.",
+ );
+ return;
+ }
+
+ // Select one of the image IDs
+ if (defaultImageIds && defaultImageIds.length > 0) {
+ if (runningNumber >= defaultImageIds.length) {
+ runningNumber = 0;
+ }
+
+ // Go through the shuffled array of image IDs in order
+ for (i = runningNumber; i < defaultImageIds.length; i++) {
+ // Skip images that have already returned 404 errors
+ if (defaultImageIds[i] == "NOT FOUND") {
+ continue;
+ }
- /**
- * getRandomSectionImage - Using the list of image identifiers set
- * in the app config, select an image to use for a portal section.
- * The function will not return the same image until all the images
- * have been returned at least once. If an image would return a 404
- * error, it is skipped. If all images give 404s, an empty string
- * is returned.
- *
- * @return {PortalImage} A portal image model to use in a section model
- */
- getRandomSectionImage: function(){
-
- // This variable will hold the section image to return, if any
- var newSectionImage = "",
- // The default portal values set in the config
- portalDefaults = MetacatUI.appModel.get("portalDefaults"),
- // Check if default images are set on the model already
- defaultImageIds = this.get("defaultSectionImageIds"),
- // Keep track of where we are in the list of default images,
- // so there's not too much repetition
- runningNumber = this.get("defaultImageRunningNumber") || 0;
-
- // If none are set, get the configured default image IDs,
- // shuffle them, and set them on the model.
- if(!defaultImageIds || !defaultImageIds.length){
-
- // Get the list of default section image IDs from the appModel
- defaultImageIds = portalDefaults ? portalDefaults.sectionImageIdentifiers : false;
-
- // If some are configured...
- if(defaultImageIds && defaultImageIds.length){
- // ...Shuffle the images...
- for (let i = defaultImageIds.length - 1; i > 0; i--) {
- let j = Math.floor(Math.random() * (i + 1));
- [defaultImageIds[i], defaultImageIds[j]] = [defaultImageIds[j], defaultImageIds[i]];
- }
- // ... and save the shuffled list to the portal model
- this.set("defaultSectionImageIds", defaultImageIds);
- }
+ // Section images are PortalImage models
+ var newSectionImage = new PortalImage({
+ identifier: defaultImageIds[i],
+ portalModel: this.get("portalModel"),
+ });
+
+ // Skip adding an image if it doesn't exist given the identifer and baseUrl found in the image model
+ if (newSectionImage.imageExists()) {
+ break;
+ // If the image doesn't exist, mark it so we don't have to
+ // check again next time
+ } else {
+ defaultImageIds[i] = "NOT FOUND";
+ newSectionImage = "";
+ }
+ }
+ }
+
+ this.set("defaultImageRunningNumber", i + 1);
+ this.set("defaultSectionImageIds", defaultImageIds);
+
+ return newSectionImage;
+ },
+
+ /**
+ * Returns the portal URL
+ *
+ * @return {string} The portal URL
+ */
+ url: function () {
+ //Start the base URL string
+ // use the resolve service if there is no object service url
+ // (e.g. in DataONE theme)
+ var urlBase =
+ MetacatUI.appModel.get("objectServiceUrl") ||
+ MetacatUI.appModel.get("resolveServiceUrl");
+
+ //Get the active alternative repository, if one is configured
+ var activeAltRepo = MetacatUI.appModel.getActiveAltRepo();
+
+ if (activeAltRepo) {
+ urlBase = activeAltRepo.objectServiceUrl;
+ }
+
+ //If this object is being updated, use the old pid in the URL
+ if (!this.isNew() && this.get("oldPid")) {
+ return urlBase + encodeURIComponent(this.get("oldPid"));
+ }
+ //If this object is new, use the new pid in the URL
+ else {
+ return (
+ urlBase + encodeURIComponent(this.get("seriesId") || this.get("id"))
+ );
+ }
+ },
+
+ /**
+ * Overrides the default Backbone.Model.fetch() function to provide some custom
+ * fetch options
+ * @param [options] {object} - Options for this fetch
+ * @property [options.objectOnly] {Boolean} - If true, only the object will be retrieved and not the system metadata
+ * @property [options.systemMetadataOnly] {Boolean} - If true, only the system metadata will be retrieved
+ * @return {XMLDocument} The XMLDocument returned from the fetch() AJAX call
+ */
+ fetch: function (options) {
+ if (!options) var options = {};
+ else var options = _.clone(options);
+
+ //If the seriesId has not been found yet, get it from Solr
+ if (!this.get("id") && !this.get("seriesId") && this.get("label")) {
+ this.once("change:seriesId", function () {
+ this.fetch(options);
+ });
+ this.once("latestVersionFound", function () {
+ this.fetch(options);
+ });
+
+ //Get the series ID of this object
+ this.getSeriesIdByLabel();
+
+ return;
+ }
+ //If we found the latest version in this pid version chain,
+ else if (this.get("id") && this.get("latestVersion")) {
+ //Set it as the id of this model
+ this.set("id", this.get("latestVersion"));
+
+ //Stop listening to the change of seriesId and the latest version found
+ this.stopListening("change:seriesId", this.fetch);
+ this.stopListening("latestVersionFound", this.fetch);
+ }
+
+ //If this MetacatUI instance is pointing to a CN, use the origin MN
+ // to fetch the Portal, if available as an alt repo.
+ if (MetacatUI.appModel.get("isCN") && this.get("datasource")) {
+ //Check if the origin MN (datasource) is an alt repo option
+ var altRepo = _.findWhere(
+ MetacatUI.appModel.get("alternateRepositories"),
+ { identifier: this.get("datasource") },
+ );
+
+ if (altRepo) {
+ //Set the origin MN (datasource) as the active alt repo
+ MetacatUI.appModel.set(
+ "activeAlternateRepositoryId",
+ this.get("datasource"),
+ );
+ }
+ }
+
+ //Fetch the system metadata
+ if (!options.objectOnly || options.systemMetadataOnly) {
+ this.fetchSystemMetadata();
+
+ if (options.systemMetadataOnly) {
+ return;
+ }
+ }
+
+ var requestSettings = {
+ dataType: "xml",
+ error: function (model, response) {
+ model.trigger("error", model, response);
+
+ if (response && response.status == 404) {
+ model.trigger("notFound");
+ }
+ },
+ };
+
+ //Save a boolean flag for whether or not this fetch was done with user authentication.
+ //This is helpful when the app is dealing with potentially private data
+ this.set("fetchedWithAuth", MetacatUI.appUserModel.get("loggedIn"));
+
+ // Add the user settings to the fetch settings
+ requestSettings = _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
+
+ // Call Backbone.Model.fetch()
+ return Backbone.Model.prototype.fetch.call(this, requestSettings);
+ },
+
+ /**
+ * Get the portal seriesId by searching for the portal by its label in Solr
+ */
+ getSeriesIdByLabel: function () {
+ //Exit if there is no portal name set
+ if (!this.get("label")) return;
+
+ var model = this;
+
+ //Start the base URL for the query service
+ var baseUrl = "";
+
+ try {
+ //If this app instance is pointing to the CN, find the Portal series ID on the MN
+ if (MetacatUI.appModel.get("alternateRepositories").length) {
+ //Get the array of possible authoritative MNs
+ var possibleAuthMNs = this.get("possibleAuthMNs");
+
+ //If there are no possible authoritative MNs, use the CN query service
+ if (!possibleAuthMNs.length) {
+ baseUrl = MetacatUI.appModel.get("queryServiceUrl");
+ } else {
+ baseUrl = possibleAuthMNs[0].queryServiceUrl;
+ }
+ } else {
+ //Get the query service URL
+ baseUrl = MetacatUI.appModel.get("queryServiceUrl");
+ }
+ } catch (e) {
+ console.error(
+ "Error in trying to determine the query service URL. Going to try to use the AppModel setting. ",
+ e,
+ );
+ } finally {
+ //Default to the query service URL configured in the AppModel, if one wasn't set earlier
+ if (!baseUrl) {
+ baseUrl = MetacatUI.appModel.get("queryServiceUrl");
+ //If there isn't a query service URL, trigger a "not found" error and exit
+ if (!baseUrl) {
+ this.trigger("notFound");
+ return;
+ }
+ }
+ }
+
+ var requestSettings = {
+ url:
+ baseUrl +
+ 'q=label:"' +
+ this.get("label") +
+ '" OR ' +
+ 'seriesId:"' +
+ this.get("label") +
+ '"' +
+ "&fl=seriesId,id,label,datasource" +
+ "&sort=dateUploaded%20asc" +
+ "&rows=1" +
+ "&wt=json",
+ dataType: "json",
+ error: function (response) {
+ model.trigger("error", model, response);
+
+ if (response.status == 404) {
+ model.trigger("notFound");
+ }
+ },
+ success: function (response) {
+ if (response.response.numFound > 0) {
+ //Set the label and datasource
+ model.set("label", response.response.docs[0].label);
+ model.set("datasource", response.response.docs[0].datasource);
+
+ //Save the seriesId, if one is found
+ if (response.response.docs[0].seriesId) {
+ model.set("seriesId", response.response.docs[0].seriesId);
}
-
- // Can't get a random image if none are configured
- if(!defaultImageIds){
- console.log("Can't set a default image on new markdown sections because there are no default image IDs set. Check portalDefaults.sectionImageIdentifiers in the config file.");
- return
+ //If this portal doesn't have a seriesId,
+ //but id has been found
+ else if (response.response.docs[0].id) {
+ //Save the id
+ model.set("id", response.response.docs[0].id);
+
+ //Find the latest version in this version chain
+ model.findLatestVersion(response.response.docs[0].id);
}
-
- // Select one of the image IDs
- if(defaultImageIds && defaultImageIds.length > 0){
-
- if(runningNumber >= defaultImageIds.length){
- runningNumber = 0
- }
-
- // Go through the shuffled array of image IDs in order
- for (i = runningNumber; i < defaultImageIds.length; i++) {
-
- // Skip images that have already returned 404 errors
- if(defaultImageIds[i] == "NOT FOUND"){
- continue;
- }
-
- // Section images are PortalImage models
- var newSectionImage = new PortalImage({
- identifier: defaultImageIds[i],
- portalModel: this.get("portalModel")
- });
-
- // Skip adding an image if it doesn't exist given the identifer and baseUrl found in the image model
- if(newSectionImage.imageExists()){
- break;
- // If the image doesn't exist, mark it so we don't have to
- // check again next time
- } else {
- defaultImageIds[i] = "NOT FOUND";
- newSectionImage = "";
- }
- }
+ // if we don't have Id or SeriesId
+ else {
+ model.trigger("notFound");
}
-
- this.set("defaultImageRunningNumber", i + 1);
- this.set("defaultSectionImageIds", defaultImageIds);
-
- return newSectionImage
- },
-
- /**
- * Returns the portal URL
- *
- * @return {string} The portal URL
- */
- url: function() {
-
- //Start the base URL string
- // use the resolve service if there is no object service url
- // (e.g. in DataONE theme)
- var urlBase = MetacatUI.appModel.get("objectServiceUrl") ||
- MetacatUI.appModel.get("resolveServiceUrl");
-
- //Get the active alternative repository, if one is configured
- var activeAltRepo = MetacatUI.appModel.getActiveAltRepo();
-
- if( activeAltRepo ){
- urlBase = activeAltRepo.objectServiceUrl;
+ } else {
+ var possibleAuthMNs = model.get("possibleAuthMNs");
+ if (possibleAuthMNs.length) {
+ //Remove the first MN from the array, since it didn't contain the Portal, so it's not the auth MN
+ possibleAuthMNs.shift();
}
- //If this object is being updated, use the old pid in the URL
- if ( !this.isNew() && this.get("oldPid") ) {
- return urlBase +
- encodeURIComponent(this.get("oldPid"));
+ //If there are no other possible auth MNs to check, trigger this Portal as Not Found.
+ if (possibleAuthMNs.length == 0 || !possibleAuthMNs) {
+ model.trigger("notFound");
}
- //If this object is new, use the new pid in the URL
+ //If there's more MNs to check, try again
else {
- return urlBase +
- encodeURIComponent(this.get("seriesId") || this.get("id"));
- }
- },
-
- /**
- * Overrides the default Backbone.Model.fetch() function to provide some custom
- * fetch options
- * @param [options] {object} - Options for this fetch
- * @property [options.objectOnly] {Boolean} - If true, only the object will be retrieved and not the system metadata
- * @property [options.systemMetadataOnly] {Boolean} - If true, only the system metadata will be retrieved
- * @return {XMLDocument} The XMLDocument returned from the fetch() AJAX call
- */
- fetch: function(options) {
-
- if ( ! options ) var options = {};
- else var options = _.clone(options);
-
- //If the seriesId has not been found yet, get it from Solr
- if( !this.get("id") && !this.get("seriesId") && this.get("label") ){
-
- this.once("change:seriesId", function(){
- this.fetch(options)
- });
- this.once("latestVersionFound", function(){
- this.fetch(options)
- });
-
- //Get the series ID of this object
- this.getSeriesIdByLabel();
-
- return;
+ model.getSeriesIdByLabel();
}
- //If we found the latest version in this pid version chain,
- else if( this.get("id") && this.get("latestVersion") ){
- //Set it as the id of this model
- this.set("id", this.get("latestVersion"));
-
- //Stop listening to the change of seriesId and the latest version found
- this.stopListening("change:seriesId", this.fetch);
- this.stopListening("latestVersionFound", this.fetch);
+ }
+ },
+ };
+
+ //Save a boolean flag for whether or not this fetch was done with user authentication.
+ //This is helpful when the app is dealing with potentially private data
+ this.set("fetchedWithAuth", MetacatUI.appUserModel.get("loggedIn"));
+
+ requestSettings = _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
+
+ $.ajax(requestSettings);
+ },
+
+ /**
+ * This function has been renamed `getSeriesIdByLabel` and may be removed in future releases.
+ * @deprecated This function has been renamed `getSeriesIdByLabel` and may be removed in future releases.
+ * @see PortalModel#getSeriesIdByLabel
+ */
+ getSeriesIdByName: function () {
+ this.getSeriesIdByLabel();
+ },
+
+ /**
+ * Overrides the default Backbone.Model.parse() function to parse the custom
+ * portal XML document
+ *
+ * @param {XMLDocument} response - The XMLDocument returned from the fetch() AJAX call
+ * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
+ */
+ parse: function (response) {
+ //Start the empty JSON object
+ var modelJSON = {},
+ modelRef = this,
+ portalNode;
+
+ // Iterate over each root XML node to find the portal node
+ $(response)
+ .children()
+ .each(function (i, el) {
+ if (el.tagName.indexOf("portal") > -1) {
+ portalNode = el;
+ return false;
+ }
+ });
+
+ // If a portal XML node wasn't found, return an empty JSON object
+ if (typeof portalNode == "undefined" || !portalNode) {
+ return {};
+ }
+
+ // Parse the collection elements
+ modelJSON = this.parseCollectionXML(portalNode);
+
+ // Save the xml for serialize
+ modelJSON.objectXML = response;
+
+ // Parse the portal logo
+ var portLogo = $(portalNode).children("logo")[0];
+ if (portLogo) {
+ var portImageModel = new PortalImage({
+ objectDOM: portLogo,
+ portalModel: this,
+ });
+ portImageModel.set(portImageModel.parse());
+ modelJSON.logo = portImageModel;
+ }
+
+ // Parse acknowledgement logos into urls
+ var logos = $(portalNode).children("acknowledgmentsLogo");
+ modelJSON.acknowledgmentsLogos = [];
+ _.each(
+ logos,
+ function (logo, i) {
+ if (!logo) return;
+
+ var imageModel = new PortalImage({
+ objectDOM: logo,
+ portalModel: this,
+ });
+ imageModel.set(imageModel.parse());
+
+ if (imageModel.get("imageURL")) {
+ modelJSON.acknowledgmentsLogos.push(imageModel);
+ }
+ },
+ this,
+ );
+
+ // Parse the literature cited
+ // This will only work for bibtex at the moment
+ var bibtex = $(portalNode)
+ .children("literatureCited")
+ .children("bibtex");
+ if (bibtex.length > 0) {
+ modelJSON.literatureCited = this.parseTextNode(
+ portalNode,
+ "literatureCited",
+ );
+ }
+
+ // Parse the portal content sections
+ modelJSON.sections = [];
+ $(portalNode)
+ .children("section")
+ .each(function (i, section) {
+ //Get the section type, if there is one
+ var sectionTypeNode = $(section).find(
+ "optionName:contains(sectionType)",
+ ),
+ sectionType = "";
+
+ if (sectionTypeNode.length) {
+ var optionValueNode = sectionTypeNode
+ .first()
+ .siblings("optionValue");
+ if (optionValueNode.length) {
+ sectionType = optionValueNode[0].textContent;
}
+ }
- //If this MetacatUI instance is pointing to a CN, use the origin MN
- // to fetch the Portal, if available as an alt repo.
- if( MetacatUI.appModel.get("isCN") && this.get("datasource") ){
- //Check if the origin MN (datasource) is an alt repo option
- var altRepo = _.findWhere(MetacatUI.appModel.get("alternateRepositories"), { identifier: this.get("datasource") });
+ if (sectionType == "visualization") {
+ // Create a new PortalVizSectionModel
+ modelJSON.sections.push(
+ new PortalVizSectionModel({
+ objectDOM: section,
+ literatureCited: modelJSON.literatureCited,
+ }),
+ );
+ } else {
+ // Create a new PortalSectionModel
+ modelJSON.sections.push(
+ new PortalSectionModel({
+ objectDOM: section,
+ literatureCited: modelJSON.literatureCited,
+ portalModel: modelRef,
+ }),
+ );
+ }
- if( altRepo ){
- //Set the origin MN (datasource) as the active alt repo
- MetacatUI.appModel.set("activeAlternateRepositoryId", this.get("datasource"));
+ //Parse the PortalSectionModel
+ modelJSON.sections[i].set(modelJSON.sections[i].parse(section));
+ });
+
+ // Parse the EMLText elements
+ modelJSON.acknowledgments = this.parseEMLTextNode(
+ portalNode,
+ "acknowledgments",
+ );
+
+ // Parse the awards
+ modelJSON.awards = [];
+ var parse_it = this.parseTextNode;
+ $(portalNode)
+ .children("award")
+ .each(function (i, award) {
+ var award_parsed = {};
+ $(award)
+ .children()
+ .each(function (i, award_attr) {
+ if (award_attr.nodeName != "funderLogo") {
+ // parse the text nodes
+ award_parsed[award_attr.nodeName] = parse_it(
+ award,
+ award_attr.nodeName,
+ );
+ } else {
+ // parse funderLogo which is type ImageType
+ var imageModel = new PortalImage({ objectDOM: award_attr });
+ imageModel.set(imageModel.parse());
+ award_parsed[award_attr.nodeName] = imageModel;
}
+ });
+ modelJSON.awards.push(award_parsed);
+ });
+
+ // Parse the associatedParties
+ modelJSON.associatedParties = [];
+ $(portalNode)
+ .children("associatedParty")
+ .each(function (i, associatedParty) {
+ modelJSON.associatedParties.push(
+ new EMLParty({
+ objectDOM: associatedParty,
+ }),
+ );
+ });
+
+ // Parse the options. Use children() and not find() because we only want
+ // option nodes that are direct children of the portal node. Option nodes
+ // can also be found within section nodes.
+ $(portalNode)
+ .children("option")
+ .each(function (i, option) {
+ var optionName = $(option).find("optionName")[0].textContent,
+ optionValue = $(option).find("optionValue")[0].textContent;
+
+ if (optionValue === "true") {
+ optionValue = true;
+ } else if (optionValue === "false") {
+ optionValue = false;
+ }
- }
-
- //Fetch the system metadata
- if( !options.objectOnly || options.systemMetadataOnly ){
- this.fetchSystemMetadata();
+ // TODO: keep a list of optionNames so that in the case of
+ // custom options, we can serialize them in serialize()
+ // otherwise it's not saved in the model which attributes
+ // are s
+
+ // Convert the comma separated list of pages into an array
+ if (
+ optionName === "pageOrder" &&
+ optionValue &&
+ optionValue.length
+ ) {
+ optionValue = optionValue.split(",");
+ }
- if( options.systemMetadataOnly ){
- return;
+ if (!_.has(modelJSON, optionName)) {
+ modelJSON[optionName] = optionValue;
+ }
+ });
+
+ // Convert all the hex colors to rgb
+ if (modelJSON.primaryColor) {
+ modelJSON.primaryColorRGB = this.hexToRGB(modelJSON.primaryColor);
+ modelJSON.primaryColorTransparent =
+ "rgba(" +
+ modelJSON.primaryColorRGB.r +
+ "," +
+ modelJSON.primaryColorRGB.g +
+ "," +
+ modelJSON.primaryColorRGB.b +
+ ", .7)";
+ }
+ if (modelJSON.secondaryColor) {
+ modelJSON.secondaryColorRGB = this.hexToRGB(modelJSON.secondaryColor);
+ modelJSON.secondaryColorTransparent =
+ "rgba(" +
+ modelJSON.secondaryColorRGB.r +
+ "," +
+ modelJSON.secondaryColorRGB.g +
+ "," +
+ modelJSON.secondaryColorRGB.b +
+ ", .5)";
+ }
+ if (modelJSON.accentColor) {
+ modelJSON.accentColorRGB = this.hexToRGB(modelJSON.accentColor);
+ modelJSON.accentColorTransparent =
+ "rgba(" +
+ modelJSON.accentColorRGB.r +
+ "," +
+ modelJSON.accentColorRGB.g +
+ "," +
+ modelJSON.accentColorRGB.b +
+ ", .5)";
+ }
+
+ if (gmaps) {
+ // Create a MapModel with all the map options
+ modelJSON.mapModel = new MapModel();
+ var mapOptions = modelJSON.mapModel.get("mapOptions");
+
+ if (modelJSON.mapZoomLevel) {
+ mapOptions.zoom = parseInt(modelJSON.mapZoomLevel);
+ mapOptions.minZoom = parseInt(modelJSON.mapZoomLevel);
+ }
+ if (
+ (modelJSON.mapCenterLatitude ||
+ modelJSON.mapCenterLatitude === 0) &&
+ (modelJSON.mapCenterLongitude || modelJSON.mapCenterLongitude === 0)
+ ) {
+ mapOptions.center = modelJSON.mapModel.createLatLng(
+ modelJSON.mapCenterLatitude,
+ modelJSON.mapCenterLongitude,
+ );
+ }
+ if (modelJSON.mapShapeHue) {
+ modelJSON.mapModel.set("tileHue", modelJSON.mapShapeHue);
+ }
+ }
+
+ // Parse the UIFilterGroups
+ modelJSON.filterGroups = [];
+ var allFilters = modelJSON.searchModel.get("filters");
+ $(portalNode)
+ .children("filterGroup")
+ .each(function (i, filterGroup) {
+ // Create a FilterGroup model
+ var filterGroupModel = new FilterGroup({
+ objectDOM: filterGroup,
+ isUIFilterType: true,
+ });
+ modelJSON.filterGroups.push(filterGroupModel);
+
+ // Add the Filters from this FilterGroup to the portal's Search model,
+ // unless this portal model is being edited. Then we only want the
+ // definition filters to be included in the search model.
+ if (!modelRef.get("edit")) {
+ allFilters.add(filterGroupModel.get("filters").models);
+ }
+ });
+
+ return modelJSON;
+ },
+
+ /**
+ * Parses the XML nodes that are of type EMLText
+ *
+ * @param {Element} parentNode - The XML Element that contains all the EMLText nodes
+ * @param {string} nodeName - The name of the XML node to parse
+ * @param {boolean} isMultiple - If true, parses the nodes into an array
+ * @return {(string|Array)} A string or array of strings comprising the text content
+ */
+ parseEMLTextNode: function (parentNode, nodeName, isMultiple) {
+ var node = $(parentNode).children(nodeName);
+
+ // If no matching nodes were found, return falsey values
+ if (!node || !node.length) {
+ // Return an empty array if the isMultiple flag is true
+ if (isMultiple) return [];
+ // Return null if the isMultiple flag is false
+ else return null;
+ }
+ // If exactly one node is found and we are only expecting one, return the text content
+ else if (node.length == 1 && !isMultiple) {
+ return new EMLText({
+ objectDOM: node[0],
+ });
+ } else {
+ // If more than one node is found, parse into an array
+ return _.map(node, function (node) {
+ return new EMLText({
+ objectDOM: node,
+ });
+ });
+ }
+ },
+
+ /**
+ * Sets the fileName attribute on this model using the portal label
+ * @override
+ */
+ setMissingFileName: function () {
+ var fileName = this.get("label");
+
+ if (!fileName) {
+ fileName = "portal.xml";
+ } else {
+ fileName = fileName.replace(/[^a-zA-Z0-9]/g, "_") + ".xml";
+ }
+
+ this.set("fileName", fileName);
+ },
+
+ /**
+ * @typedef {Object} PortalModel#rgb - An RGB color value
+ * @property {number} r - A value between 0 and 255 defining the intensity of red
+ * @property {number} g - A value between 0 and 255 defining the intensity of green
+ * @property {number} b - A value between 0 and 255 defining the intensity of blue
+ */
+
+ /**
+ * Converts hex color values to RGB
+ *
+ * @param {string} hex - a color in hexadecimal format
+ * @return {rgb} a color in RGB format
+ */
+ hexToRGB: function (hex) {
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return result
+ ? {
+ r: parseInt(result[1], 16),
+ g: parseInt(result[2], 16),
+ b: parseInt(result[3], 16),
+ }
+ : null;
+ },
+
+ /**
+ * Finds the node in the given portal XML document afterwhich the
+ * given node type should be inserted
+ *
+ * @param {Element} portalNode - The portal element of an XML document
+ * @param {string} nodeName - The name of the node to be inserted
+ * into xml
+ * @return {(jQuery|boolean)} A jQuery object indicating a position,
+ * or false when nodeName is not in the
+ * portal schema
+ */
+ getXMLPosition: function (portalNode, nodeName) {
+ var nodeOrder = [
+ "label",
+ "name",
+ "description",
+ "definition",
+ "logo",
+ "section",
+ "associatedParty",
+ "acknowledgments",
+ "acknowledgmentsLogo",
+ "award",
+ "literatureCited",
+ "filterGroup",
+ "option",
+ ];
+
+ var position = _.indexOf(nodeOrder, nodeName);
+
+ // First check that nodeName is in the list of nodes
+ if (position == -1) {
+ return false;
+ }
+
+ // If there's already an occurence of nodeName...
+ if ($(portalNode).children(nodeName).length > 0) {
+ // ...insert it after the last occurence
+ return $(portalNode).children(nodeName).last();
+ } else {
+ // Go through each node in the node list and find the position
+ // after which this node will be inserted
+ for (var i = position - 1; i >= 0; i--) {
+ if ($(portalNode).children(nodeOrder[i]).length) {
+ return $(portalNode).children(nodeOrder[i]).last();
+ }
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Retrieves the model attributes and serializes into portal XML,
+ * to produce the new or modified portal document.
+ *
+ * @return {string} - Returns the portal XML as a string.
+ */
+ serialize: function () {
+ try {
+ // So we can call getXMLPosition() from within if{}
+ var model = this;
+
+ var xmlDoc, portalNode, xmlString;
+
+ xmlDoc = this.get("objectXML");
+
+ // Check if there is a portal doc already
+ if (xmlDoc == null) {
+ // If not create one
+ xmlDoc = this.createXML();
+ } else {
+ // If yes, clone it
+ xmlDoc = xmlDoc.cloneNode(true);
+ }
+
+ // Iterate over each root XML node to find the portal node
+ $(xmlDoc)
+ .children()
+ .each(function (i, el) {
+ if (el.tagName.indexOf("portal") > -1) {
+ portalNode = el;
+ }
+ });
+
+ // Serialize the collection elements
+ // ("name", "label", "description", "definition")
+ portalNode = this.updateCollectionDOM(portalNode);
+ xmlDoc = portalNode.getRootNode();
+ var $portalNode = $(portalNode);
+
+ // Set formatID
+ this.set(
+ "formatId",
+ MetacatUI.appModel.get("portalEditorSerializationFormat") ||
+ "https://purl.dataone.org/portals-1.1.0",
+ );
+
+ /* ==== Serialize portal logo ==== */
+
+ // Remove node if it exists already
+ $(xmlDoc).find("logo").remove();
+
+ // Get new values
+ var logo = this.get("logo");
+
+ // Don't serialize falsey values or empty logos
+ if (logo && logo.get("identifier")) {
+ // Make new node
+ var logoSerialized = logo.updateDOM("logo");
+
+ //Add the logo node to the XMLDocument
+ xmlDoc.adoptNode(logoSerialized);
+
+ // Insert new node at correct position
+ var insertAfter = this.getXMLPosition(portalNode, "logo");
+ if (insertAfter) {
+ insertAfter.after(logoSerialized);
+ } else {
+ portalNode.appendChild(logoSerialized);
+ }
+ }
+
+ /* ==== Serialize acknowledgment logos ==== */
+
+ // Remove element if it exists already
+ $(xmlDoc).find("acknowledgmentsLogo").remove();
+
+ var acknowledgmentsLogos = this.get("acknowledgmentsLogos");
+
+ // Don't serialize falsey values
+ if (acknowledgmentsLogos) {
+ _.each(acknowledgmentsLogos, function (imageModel) {
+ // Don't serialize empty imageModels
+ if (
+ imageModel.get("identifier") ||
+ imageModel.get("label") ||
+ imageModel.get("associatedURL")
+ ) {
+ var ackLogosSerialized = imageModel.updateDOM();
+
+ //Add the logo node to the XMLDocument
+ xmlDoc.adoptNode(ackLogosSerialized);
+
+ // Insert new node at correct position
+ var insertAfter = model.getXMLPosition(
+ portalNode,
+ "acknowledgmentsLogo",
+ );
+ if (insertAfter) {
+ insertAfter.after(ackLogosSerialized);
+ } else {
+ portalNode.appendChild(ackLogosSerialized);
}
}
+ });
+ }
- var requestSettings = {
- dataType: "xml",
- error: function(model, response) {
+ /* ==== Serialize literature cited ==== */
+ // Assumes the value of literatureCited is a block of bibtex text
- model.trigger("error", model, response);
+ // Remove node if it exists already
+ $(xmlDoc).find("literatureCited").remove();
- if( response && response.status == 404 ){
- model.trigger("notFound");
- }
- }
- };
-
- //Save a boolean flag for whether or not this fetch was done with user authentication.
- //This is helpful when the app is dealing with potentially private data
- this.set("fetchedWithAuth", MetacatUI.appUserModel.get("loggedIn"));
-
- // Add the user settings to the fetch settings
- requestSettings = _.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings());
+ // Get new values
+ var litCit = this.get("literatureCited");
- // Call Backbone.Model.fetch()
- return Backbone.Model.prototype.fetch.call(this, requestSettings);
+ // Don't serialize falsey values
+ if (litCit.length) {
+ // If there's only one element in litCited, it will be a string
+ // turn it into an array so that we can use _.each
+ if (typeof litCit == "string") {
+ litCit = [litCit];
+ }
- },
+ // Make new element
+ var litCitSerialized = xmlDoc.createElement("literatureCited");
+
+ _.each(litCit, function (bibtex) {
+ // Wrap in literature cited in cdata tags
+ var cdataLitCit = xmlDoc.createCDATASection(bibtex);
+ var bibtexSerialized = xmlDoc.createElement("bibtex");
+ // wrap in CDATA tags so that bibtex characters aren't escaped
+ bibtexSerialized.appendChild(cdataLitCit);
+ // is a subelement of
+ litCitSerialized.appendChild(bibtexSerialized);
+ });
+
+ // Insert new element at correct position
+ var insertAfter = this.getXMLPosition(
+ portalNode,
+ "literatureCited",
+ );
+ if (insertAfter) {
+ insertAfter.after(litCitSerialized);
+ } else {
+ portalNode.appendChild(litCitSerialized);
+ }
+ }
- /**
- * Get the portal seriesId by searching for the portal by its label in Solr
- */
- getSeriesIdByLabel: function(){
+ /* ==== Serialize portal content sections ==== */
- //Exit if there is no portal name set
- if( !this.get("label") )
- return;
+ // Remove node if it exists already
+ $portalNode.children("section").remove();
- var model = this;
+ var sections = this.get("sections");
- //Start the base URL for the query service
- var baseUrl = "";
+ // Don't serialize falsey values
+ if (sections) {
+ _.each(
+ sections,
+ function (sectionModel) {
+ // Don't serialize sections with default values
+ if (!this.sectionIsDefault(sectionModel)) {
+ var sectionSerialized = sectionModel.updateDOM();
- try{
- //If this app instance is pointing to the CN, find the Portal series ID on the MN
- if( MetacatUI.appModel.get("alternateRepositories").length ){
+ //If there was an error serializing this section, or if
+ // nothing was returned, don't do anythiing further
+ if (!sectionSerialized) {
+ return;
+ }
- //Get the array of possible authoritative MNs
- var possibleAuthMNs = this.get("possibleAuthMNs");
+ //Add the section node to the XMLDocument
+ xmlDoc.adoptNode(sectionSerialized);
- //If there are no possible authoritative MNs, use the CN query service
- if( !possibleAuthMNs.length ){
- baseUrl = MetacatUI.appModel.get("queryServiceUrl");
+ // Remove sections entirely if the content is blank
+ var newMD = $(sectionSerialized).find("markdown")[0];
+ if (!newMD || newMD.textContent == "") {
+ $(sectionSerialized).find("markdown").remove();
}
- else{
- baseUrl = possibleAuthMNs[0].queryServiceUrl;
+
+ // Remove the element if it's empty.
+ // This will trigger a validation error, prompting user to
+ // enter content.
+ if ($(sectionSerialized).find("content").is(":empty")) {
+ $(sectionSerialized).find("content").remove();
}
- }
- else{
- //Get the query service URL
- baseUrl = MetacatUI.appModel.get("queryServiceUrl");
- }
- }
- catch(e){
- console.error("Error in trying to determine the query service URL. Going to try to use the AppModel setting. ", e);
- }
- finally{
- //Default to the query service URL configured in the AppModel, if one wasn't set earlier
- if( !baseUrl ){
- baseUrl = MetacatUI.appModel.get("queryServiceUrl");
- //If there isn't a query service URL, trigger a "not found" error and exit
- if( !baseUrl ){
- this.trigger("notFound");
- return;
+ // Insert new node at correct position
+ var insertAfter = model.getXMLPosition(portalNode, "section");
+ if (insertAfter) {
+ insertAfter.after(sectionSerialized);
+ } else {
+ portalNode.appendChild(sectionSerialized);
}
}
- }
+ },
+ this,
+ );
+ }
- var requestSettings = {
- url: baseUrl +
- "q=label:\"" + this.get("label") + "\" OR " +
- "seriesId:\"" + this.get("label") + "\"" +
- "&fl=seriesId,id,label,datasource" +
- "&sort=dateUploaded%20asc" +
- "&rows=1" +
- "&wt=json",
- dataType: "json",
- error: function(response) {
- model.trigger("error", model, response);
-
- if( response.status == 404 ){
- model.trigger("notFound");
- }
- },
- success: function(response){
- if( response.response.numFound > 0 ){
+ /* ==== Serialize the EMLText elements ("acknowledgments") ==== */
- //Set the label and datasource
- model.set("label", response.response.docs[0].label);
- model.set("datasource", response.response.docs[0].datasource);
+ var textFields = ["acknowledgments"];
- //Save the seriesId, if one is found
- if( response.response.docs[0].seriesId ){
- model.set("seriesId", response.response.docs[0].seriesId);
- }
- //If this portal doesn't have a seriesId,
- //but id has been found
- else if ( response.response.docs[0].id ){
- //Save the id
- model.set("id", response.response.docs[0].id);
-
- //Find the latest version in this version chain
- model.findLatestVersion(response.response.docs[0].id);
- }
- // if we don't have Id or SeriesId
- else {
- model.trigger("notFound");
- }
+ _.each(
+ textFields,
+ function (field) {
+ var fieldName = field;
- }
- else{
+ // Get the EMLText model
+ var emlTextModels = Array.isArray(this.get(field))
+ ? this.get(field)
+ : [this.get(field)];
+ if (!emlTextModels.length) return;
- var possibleAuthMNs = model.get("possibleAuthMNs");
- if( possibleAuthMNs.length ){
- //Remove the first MN from the array, since it didn't contain the Portal, so it's not the auth MN
- possibleAuthMNs.shift();
- }
+ // Get the node from the XML doc
+ var nodes = $portalNode.children(fieldName);
- //If there are no other possible auth MNs to check, trigger this Portal as Not Found.
- if( possibleAuthMNs.length == 0 || !possibleAuthMNs ){
- model.trigger("notFound");
- }
- //If there's more MNs to check, try again
- else{
- model.getSeriesIdByLabel();
- }
+ // Update the DOMs for each model
+ _.each(
+ emlTextModels,
+ function (thisTextModel, i) {
+ //Don't serialize falsey values
+ if (!thisTextModel) return;
- }
- }
- }
+ var node;
- //Save a boolean flag for whether or not this fetch was done with user authentication.
- //This is helpful when the app is dealing with potentially private data
- this.set("fetchedWithAuth", MetacatUI.appUserModel.get("loggedIn"));
+ //Get the existing node or create a new one
+ if (nodes.length < i + 1) {
+ node = xmlDoc.createElement(fieldName);
+ this.getXMLPosition(portalNode, fieldName).after(node);
+ } else {
+ node = nodes[i];
+ }
- requestSettings = _.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings());
+ var textModelSerialized = thisTextModel.updateDOM();
- $.ajax(requestSettings);
+ //If the text model wasn't serialized correctly or resulted in nothing
+ if (
+ typeof textModelSerialized == "undefined" ||
+ !textModelSerialized
+ ) {
+ //Remove the existing node
+ $(node).remove();
+ } else {
+ xmlDoc.adoptNode(textModelSerialized);
+ $(node).replaceWith(textModelSerialized);
+ }
+ },
+ this,
+ );
+ // Remove the extra nodes
+ this.removeExtraNodes(nodes, emlTextModels);
},
-
- /**
- * This function has been renamed `getSeriesIdByLabel` and may be removed in future releases.
- * @deprecated This function has been renamed `getSeriesIdByLabel` and may be removed in future releases.
- * @see PortalModel#getSeriesIdByLabel
- */
- getSeriesIdByName: function(){ this.getSeriesIdByLabel() },
-
- /**
- * Overrides the default Backbone.Model.parse() function to parse the custom
- * portal XML document
- *
- * @param {XMLDocument} response - The XMLDocument returned from the fetch() AJAX call
- * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
- */
- parse: function(response) {
-
- //Start the empty JSON object
- var modelJSON = {},
- modelRef = this,
- portalNode;
-
- // Iterate over each root XML node to find the portal node
- $(response).children().each(function(i, el) {
- if (el.tagName.indexOf("portal") > -1) {
- portalNode = el;
- return false;
- }
- });
-
- // If a portal XML node wasn't found, return an empty JSON object
- if (typeof portalNode == "undefined" || !portalNode) {
- return {};
+ this,
+ );
+
+ /* ==== Serialize awards ==== */
+
+ // Remove award node if it exists already
+ $portalNode.children("award").remove();
+
+ // Get new values
+ var awards = this.get("awards");
+
+ // Don't serialize falsey values
+ if (awards && awards.length > 0) {
+ _.each(awards, function (award) {
+ // Make new node
+ var awardSerialized = xmlDoc.createElement("award");
+
+ // create the subnodes
+ _.map(award, function (value, nodeName) {
+ // serialize the simple text nodes
+ if (nodeName != "funderLogo") {
+ // Don't serialize falsey values
+ if (value) {
+ // Make new sub-nodes
+ var awardSubnodeSerialized = xmlDoc.createElement(nodeName);
+ $(awardSubnodeSerialized).text(value);
+ $(awardSerialized).append(awardSubnodeSerialized);
+ }
+ } else {
+ // serialize "funderLogo" which is ImageType
+ var funderLogoSerialized = value.updateDOM();
+ xmlDoc.adoptNode(funderLogoSerialized);
+ $(awardSerialized).append(funderLogoSerialized);
}
+ });
- // Parse the collection elements
- modelJSON = this.parseCollectionXML(portalNode);
-
- // Save the xml for serialize
- modelJSON.objectXML = response;
-
- // Parse the portal logo
- var portLogo = $(portalNode).children("logo")[0];
- if (portLogo) {
- var portImageModel = new PortalImage({
- objectDOM: portLogo,
- portalModel: this
- });
- portImageModel.set(portImageModel.parse());
- modelJSON.logo = portImageModel
- };
-
- // Parse acknowledgement logos into urls
- var logos = $(portalNode).children("acknowledgmentsLogo");
- modelJSON.acknowledgmentsLogos = [];
- _.each(logos, function(logo, i) {
- if ( !logo ) return;
-
- var imageModel = new PortalImage({
- objectDOM: logo,
- portalModel: this
- });
- imageModel.set(imageModel.parse());
-
- if( imageModel.get("imageURL") ){
- modelJSON.acknowledgmentsLogos.push( imageModel );
- }
- }, this);
-
- // Parse the literature cited
- // This will only work for bibtex at the moment
- var bibtex = $(portalNode).children("literatureCited").children("bibtex");
- if (bibtex.length > 0) {
- modelJSON.literatureCited = this.parseTextNode(portalNode, "literatureCited");
+ // Insert new node at correct position
+ var insertAfter = model.getXMLPosition(portalNode, "award");
+ if (insertAfter) {
+ insertAfter.after(awardSerialized);
+ } else {
+ portalNode.appendChild(awardSerialized);
+ }
+ });
+ }
+
+ /* ==== Serialize associatedParties ==== */
+
+ // Remove element if it exists already
+ $portalNode.children("associatedParty").remove();
+
+ // Get new values
+ var parties = this.get("associatedParties");
+
+ // Don't serialize falsey values
+ if (parties) {
+ // Serialize each associatedParty
+ _.each(parties, function (party) {
+ // Update the DOM of the EMLParty
+ var partyEl = party.updateDOM();
+ partyDoc = $.parseXML(party.formatXML($(partyEl)[0]));
+
+ // Make sure we don't insert empty EMLParty nodes into the EML
+ if (partyDoc.childNodes.length) {
+ //Save a reference to the associated party element in the NodeList
+ var assocPartyEl = partyDoc.childNodes[0];
+ //Add the associated part element to the portal XML doc
+ xmlDoc.adoptNode(assocPartyEl);
+
+ // Get the last node of this type to insert after
+ var insertAfter = $portalNode
+ .children("associatedParty")
+ .last();
+
+ // If there isn't a node found, find the EML position to insert after
+ if (!insertAfter.length) {
+ insertAfter = model.getXMLPosition(
+ portalNode,
+ "associatedParty",
+ );
}
- // Parse the portal content sections
- modelJSON.sections = [];
- $(portalNode).children("section").each(function(i, section){
-
- //Get the section type, if there is one
- var sectionTypeNode = $(section).find("optionName:contains(sectionType)"),
- sectionType = "";
-
- if( sectionTypeNode.length ){
- var optionValueNode = sectionTypeNode.first().siblings("optionValue");
- if( optionValueNode.length ){
- sectionType = optionValueNode[0].textContent;
- }
- }
-
- if( sectionType == "visualization" ){
- // Create a new PortalVizSectionModel
- modelJSON.sections.push( new PortalVizSectionModel({
- objectDOM: section,
- literatureCited: modelJSON.literatureCited
- }) );
- }
- else{
- // Create a new PortalSectionModel
- modelJSON.sections.push( new PortalSectionModel({
- objectDOM: section,
- literatureCited: modelJSON.literatureCited,
- portalModel: modelRef
- }) );
- }
-
- //Parse the PortalSectionModel
- modelJSON.sections[i].set( modelJSON.sections[i].parse(section) );
- });
-
- // Parse the EMLText elements
- modelJSON.acknowledgments = this.parseEMLTextNode(portalNode, "acknowledgments");
-
- // Parse the awards
- modelJSON.awards = [];
- var parse_it = this.parseTextNode;
- $(portalNode).children("award").each(function(i, award) {
- var award_parsed = {};
- $(award).children().each(function(i, award_attr) {
- if(award_attr.nodeName != "funderLogo"){
- // parse the text nodes
- award_parsed[award_attr.nodeName] = parse_it(award, award_attr.nodeName);
- } else {
- // parse funderLogo which is type ImageType
- var imageModel = new PortalImage({ objectDOM: award_attr });
- imageModel.set(imageModel.parse());
- award_parsed[award_attr.nodeName] = imageModel;
- }
- });
- modelJSON.awards.push(award_parsed);
- });
-
- // Parse the associatedParties
- modelJSON.associatedParties = [];
- $(portalNode).children("associatedParty").each(function(i, associatedParty) {
-
- modelJSON.associatedParties.push(new EMLParty({
- objectDOM: associatedParty
- }));
-
- });
-
- // Parse the options. Use children() and not find() because we only want
- // option nodes that are direct children of the portal node. Option nodes
- // can also be found within section nodes.
- $(portalNode).children("option").each(function(i, option) {
-
- var optionName = $(option).find("optionName")[0].textContent,
- optionValue = $(option).find("optionValue")[0].textContent;
-
- if (optionValue === "true") {
- optionValue = true;
- } else if (optionValue === "false") {
- optionValue = false;
- }
+ //Insert the party DOM at the insert position
+ if (insertAfter && insertAfter.length) {
+ insertAfter.after(assocPartyEl);
+ } else {
+ portalNode.appendChild(assocPartyEl);
+ }
+ }
+ });
+ }
+
+ try {
+ /* ==== Serialize options (including map options) ==== */
+ // This will only serialize the options named in `optNames` (below)
+ // Functionality needed in order to serialize new or custom options
+
+ // The standard list of options used in portals
+ var optNames = this.get("optionNames");
+
+ _.each(optNames, function (optName) {
+ //Get the value on the model
+ var optValue = model.get(optName),
+ existingValue;
+
+ //Get the existing optionName element
+ var matchingOption = $portalNode
+ .children("option")
+ .find("optionName:contains('" + optName + "')");
+
+ //
+ if (
+ !matchingOption.length ||
+ matchingOption.first().text() != optName
+ ) {
+ matchingOption = false;
+ } else {
+ //Get the value for this option from the Portal doc
+ existingValue = matchingOption.siblings("optionValue").text();
+ }
- // TODO: keep a list of optionNames so that in the case of
- // custom options, we can serialize them in serialize()
- // otherwise it's not saved in the model which attributes
- // are s
+ // Don't serialize null or undefined values. Also don't serialize values that match the default model value
+ if (
+ (optValue || optValue === 0 || optValue === false) &&
+ optValue != model.defaults()[optName]
+ ) {
+ //Replace the existing option, if it exists
+ if (matchingOption) {
+ matchingOption.siblings("optionValue").text(optValue);
+ } else {
+ // Make new node
+ // and are subelements of
+ var optionSerialized = xmlDoc.createElement("option"),
+ optNameSerialized = xmlDoc.createElement("optionName"),
+ optValueSerialized = xmlDoc.createElement("optionValue");
- // Convert the comma separated list of pages into an array
- if(optionName === "pageOrder" && optionValue && optionValue.length){
- optionValue = optionValue.split(',');
- }
+ $(optNameSerialized).text(optName);
+ $(optValueSerialized).text(optValue);
- if( !_.has(modelJSON, optionName) ){
- modelJSON[optionName] = optionValue;
- }
+ $(optionSerialized).append(
+ optNameSerialized,
+ optValueSerialized,
+ );
- });
+ // Insert new node at correct position
+ var insertAfter = model.getXMLPosition(portalNode, "option");
- // Convert all the hex colors to rgb
- if(modelJSON.primaryColor){
- modelJSON.primaryColorRGB = this.hexToRGB(modelJSON.primaryColor);
- modelJSON.primaryColorTransparent = "rgba(" + modelJSON.primaryColorRGB.r +
- "," + modelJSON.primaryColorRGB.g + "," + modelJSON.primaryColorRGB.b +
- ", .7)";
- }
- if(modelJSON.secondaryColor){
- modelJSON.secondaryColorRGB = this.hexToRGB(modelJSON.secondaryColor);
- modelJSON.secondaryColorTransparent = "rgba(" + modelJSON.secondaryColorRGB.r +
- "," + modelJSON.secondaryColorRGB.g + "," + modelJSON.secondaryColorRGB.b +
- ", .5)";
+ if (insertAfter) {
+ insertAfter.after(optionSerialized);
+ }
}
- if(modelJSON.accentColor){
- modelJSON.accentColorRGB = this.hexToRGB(modelJSON.accentColor);
- modelJSON.accentColorTransparent = "rgba(" + modelJSON.accentColorRGB.r +
- "," + modelJSON.accentColorRGB.g + "," + modelJSON.accentColorRGB.b +
- ", .5)";
+ } else {
+ //Remove the elements from the portal XML when the value is invalid
+ if (matchingOption) {
+ matchingOption.parent("option").remove();
}
+ }
+ });
+ } catch (e) {
+ console.error(e);
+ }
- if (gmaps) {
- // Create a MapModel with all the map options
- modelJSON.mapModel = new MapModel();
- var mapOptions = modelJSON.mapModel.get("mapOptions");
-
- if (modelJSON.mapZoomLevel) {
- mapOptions.zoom = parseInt(modelJSON.mapZoomLevel);
- mapOptions.minZoom = parseInt(modelJSON.mapZoomLevel);
- }
- if ((modelJSON.mapCenterLatitude || modelJSON.mapCenterLatitude === 0) &&
- (modelJSON.mapCenterLongitude || modelJSON.mapCenterLongitude === 0)) {
- mapOptions.center = modelJSON.mapModel.createLatLng(modelJSON.mapCenterLatitude, modelJSON.mapCenterLongitude);
- }
- if (modelJSON.mapShapeHue) {
- modelJSON.mapModel.set("tileHue", modelJSON.mapShapeHue);
- }
- }
+ /* ==== Serialize UI FilterGroups (aka custom search filters) ==== */
- // Parse the UIFilterGroups
- modelJSON.filterGroups = [];
- var allFilters = modelJSON.searchModel.get("filters");
- $(portalNode).children("filterGroup").each(function(i, filterGroup) {
+ // Get new filter group values
+ var filterGroups = this.get("filterGroups");
- // Create a FilterGroup model
- var filterGroupModel = new FilterGroup({
- objectDOM: filterGroup,
- isUIFilterType: true
- });
- modelJSON.filterGroups.push(filterGroupModel);
+ // Remove filter groups in the current objectDOM that are at the portal
+ // level. (don't use .find("filterGroup") as that would remove
+ // filterGroups that are nested in the definition
+ $portalNode.children("filterGroup").remove();
- // Add the Filters from this FilterGroup to the portal's Search model,
- // unless this portal model is being edited. Then we only want the
- // definition filters to be included in the search model.
- if (!modelRef.get("edit")){
- allFilters.add(filterGroupModel.get("filters").models);
- }
-
+ // Make a new node for each filter group in the model
+ _.each(filterGroups, function (filterGroup) {
+ filterGroupSerialized = filterGroup.updateDOM();
- });
+ if (filterGroupSerialized) {
+ //Add the new element to the XMLDocument
+ xmlDoc.adoptNode(filterGroupSerialized);
- return modelJSON;
- },
+ // Insert new node at correct position
+ var insertAfter = model.getXMLPosition(portalNode, "filterGroup");
- /**
- * Parses the XML nodes that are of type EMLText
- *
- * @param {Element} parentNode - The XML Element that contains all the EMLText nodes
- * @param {string} nodeName - The name of the XML node to parse
- * @param {boolean} isMultiple - If true, parses the nodes into an array
- * @return {(string|Array)} A string or array of strings comprising the text content
- */
- parseEMLTextNode: function(parentNode, nodeName, isMultiple) {
-
- var node = $(parentNode).children(nodeName);
-
- // If no matching nodes were found, return falsey values
- if (!node || !node.length) {
-
- // Return an empty array if the isMultiple flag is true
- if (isMultiple)
- return [];
- // Return null if the isMultiple flag is false
- else
- return null;
+ if (insertAfter) {
+ insertAfter.after(filterGroupSerialized);
+ } else {
+ portalNode.appendChild(filterGroupSerialized);
+ }
+ }
+ });
+
+ /* ==== Remove duplicates ==== */
+
+ //Do a final check to make sure there are no duplicate ids in the XML
+ var elementsWithIDs = $(xmlDoc).find("[id]"),
+ //Get an array of all the ids in this EML doc
+ allIDs = _.map(elementsWithIDs, function (el) {
+ return $(el).attr("id");
+ });
+
+ //If there is at least one id in the EML...
+ if (allIDs && allIDs.length) {
+ //Boil the array down to just the unique values
+ var uniqueIDs = _.uniq(allIDs);
+
+ //If the unique array is shorter than the array of all ids,
+ // then there is a duplicate somewhere
+ if (uniqueIDs.length < allIDs.length) {
+ //For each element in the EML that has an id,
+ _.each(elementsWithIDs, function (el) {
+ //Get the id for this element
+ var id = $(el).attr("id");
+
+ //If there is more than one element in the EML with this id,
+ if ($(xmlDoc).find("[id='" + id + "']").length > 1) {
+ //And if it is not a unit node, which we don't want to change,
+ if (!$(el).is("unit"))
+ //Then change the id attribute to a random uuid
+ $(el).attr("id", "urn-uuid-" + uuid.v4());
}
- // If exactly one node is found and we are only expecting one, return the text content
- else if (node.length == 1 && !isMultiple) {
- return new EMLText({
- objectDOM: node[0]
- });
- } else {
- // If more than one node is found, parse into an array
- return _.map(node, function(node) {
- return new EMLText({
- objectDOM: node
- });
- });
-
+ });
+ }
+ }
+
+ // Convert xml to xmlString and return xmlString
+ xmlString = new XMLSerializer().serializeToString(xmlDoc);
+
+ //If there isn't an XML declaration, add one
+ if (xmlString.indexOf("' + xmlString;
+ }
+
+ return xmlString;
+ } catch (e) {
+ console.error("Error while serializing the Portal XML document: ", e);
+ this.set("errorMessage", e.stack);
+ this.trigger(
+ "errorSaving",
+ MetacatUI.appModel.get("portalEditSaveErrorMsg"),
+ );
+ return;
+ }
+ },
+
+ /**
+ * Checks whether the given sectionModel has been updated by the
+ * user, or whether all attributes match their default values.
+ * For a section's markdown, the default value is either an empty
+ * string or null. For a section's label, the default
+ * value is either an empty string or a string that begins with the
+ * value set to PortalModel.newSectionLabel. For all other attributes,
+ * the defaults are set in PortalSectionModel.defaults.
+ * @param {PortalSectionModel} sectionModel - The model to check against a default model
+ * @return {boolean} returns true if the sectionModel matches a default model, and false when at least one attribute differs
+ */
+ sectionIsDefault: function (sectionModel) {
+ try {
+ var defaults = sectionModel.defaults(),
+ currentMarkdown = sectionModel.get("content").get("markdown"),
+ labelRegex = new RegExp("^" + this.newSectionLabel, "i");
+
+ // For each attribute, check whether it matches the default
+ if (
+ // Check whether markdown matches the content that's
+ // auto-filled or whether it's empty
+ //currentMarkdown === this.markdownExample ||
+ (currentMarkdown == "" || currentMarkdown == null) &&
+ sectionModel.get("image") === defaults.image &&
+ sectionModel.get("introduction") === defaults.introduction &&
+ // Check whether label starts with the default new page name,
+ // or whether it's empty
+ (labelRegex.test(sectionModel.get("label")) ||
+ sectionModel.get("label") == "" ||
+ sectionModel.get("label") == null) &&
+ sectionModel.get("literatureCited") === defaults.literatureCited &&
+ sectionModel.get("title") === defaults.title
+ ) {
+ // All elements of the section match the default
+ return true;
+ } else {
+ // At least one attribute of the section has been updated
+ return false;
+ }
+ } catch (e) {
+ // If there's a problem with this function for some reason,
+ // return false so that the section is serialized to avoid
+ // losing information
+ console.log(
+ "Failed to check whether section model is default. Serializing it anyway. Error message:" +
+ e,
+ );
+ return false;
+ }
+ },
+
+ /**
+ * Initialize the object XML for a brand spankin' new portal
+ * @inheritdoc
+ *
+ */
+ createXML: function () {
+ var format =
+ MetacatUI.appModel.get("portalEditorSerializationFormat") ||
+ "https://purl.dataone.org/portals-1.1.0";
+ var xmlString = ' ';
+ var xmlNew = $.parseXML(xmlString);
+ var portalNode = xmlNew.getElementsByTagName("por:portal")[0];
+
+ this.set("ownerDocument", portalNode.ownerDocument);
+ return xmlNew;
+ },
+
+ /**
+ * Overrides the default Backbone.Model.validate.function() to
+ * check if this portal model has all the required values necessary
+ * to save to the server.
+ *
+ * @param {Object} [attrs] - A literal object of model attributes to validate.
+ * @param {Object} [options] - A literal object of options for this validation process
+ * @return {Object} If there are errors, an object comprising error
+ * messages. If no errors, returns nothing.
+ */
+ validate: function (attrs, options) {
+ try {
+ var errors = {},
+ requiredFields =
+ MetacatUI.appModel.get("portalEditorRequiredFields") || {};
+
+ //Execute the superclass validate() function
+ var collectionErrors = this.constructor.__super__.validate.call(this);
+ if (
+ typeof collectionErrors == "object" &&
+ Object.keys(collectionErrors).length
+ ) {
+ //Use the errors messages from the CollectionModel for this PortalModel
+ errors = collectionErrors;
+ }
+
+ // ---- Validate the description and name ----
+ //Map the model attributes to the user-facing attribute name
+ var textFields = {
+ description: "description",
+ name: "title",
+ };
+ //Iterate over each text field
+ _.each(
+ Object.keys(textFields),
+ function (field) {
+ //If this field is required, and it is a string
+ if (requiredFields[field] && typeof this.get(field) == "string") {
+ //If this is an empty string, set an error message
+ if (!this.get(field).trim().length) {
+ errors[field] = "A " + textFields[field] + " is required.";
}
-
- },
-
- /**
- * Sets the fileName attribute on this model using the portal label
- * @override
- */
- setMissingFileName: function(){
-
- var fileName = this.get("label");
-
- if( !fileName ){
- fileName = "portal.xml";
}
- else{
- fileName = fileName.replace(/[^a-zA-Z0-9]/g, "_") + ".xml";
+ //If this field is required, and it's not a string at all, set an error message
+ else if (requiredFields[field]) {
+ errors[field] = "A " + textFields[field] + " is required.";
}
-
- this.set("fileName", fileName);
-
},
-
- /**
- * @typedef {Object} PortalModel#rgb - An RGB color value
- * @property {number} r - A value between 0 and 255 defining the intensity of red
- * @property {number} g - A value between 0 and 255 defining the intensity of green
- * @property {number} b - A value between 0 and 255 defining the intensity of blue
- */
-
- /**
- * Converts hex color values to RGB
- *
- * @param {string} hex - a color in hexadecimal format
- * @return {rgb} a color in RGB format
- */
- hexToRGB: function(hex){
- var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
- return result ? {
- r: parseInt(result[1], 16),
- g: parseInt(result[2], 16),
- b: parseInt(result[3], 16)
- } : null;
- },
-
- /**
- * Finds the node in the given portal XML document afterwhich the
- * given node type should be inserted
- *
- * @param {Element} portalNode - The portal element of an XML document
- * @param {string} nodeName - The name of the node to be inserted
- * into xml
- * @return {(jQuery|boolean)} A jQuery object indicating a position,
- * or false when nodeName is not in the
- * portal schema
- */
- getXMLPosition: function(portalNode, nodeName){
-
- var nodeOrder = [ "label", "name", "description", "definition",
- "logo", "section", "associatedParty",
- "acknowledgments", "acknowledgmentsLogo",
- "award", "literatureCited", "filterGroup",
- "option"];
-
- var position = _.indexOf(nodeOrder, nodeName);
-
- // First check that nodeName is in the list of nodes
- if ( position == -1 ) {
- return false;
- };
-
- // If there's already an occurence of nodeName...
- if($(portalNode).children(nodeName).length > 0){
- // ...insert it after the last occurence
- return $(portalNode).children(nodeName).last();
- } else {
- // Go through each node in the node list and find the position
- // after which this node will be inserted
- for (var i = position - 1; i >= 0; i--) {
- if ( $(portalNode).children(nodeOrder[i]).length ) {
- return $(portalNode).children(nodeOrder[i]).last();
- }
- }
+ this,
+ );
+
+ //---Validate the sections---
+ //Iterate over each section model
+ _.each(
+ this.get("sections"),
+ function (section) {
+ //Validate the section model
+ var sectionErrors = section.validate();
+
+ //If there is at least one error, then add an error to the PortalModel error list
+ if (sectionErrors && Object.keys(sectionErrors).length) {
+ errors.sections = "At least one section has an error";
}
-
- return false;
},
+ this,
+ );
+
+ //----Validate the logo----
+ if (
+ requiredFields.logo &&
+ (!this.get("logo") || !this.get("logo").get("identifier"))
+ ) {
+ errors.logo = "A logo image is required";
+ } else if (this.get("logo")) {
+ logoErrors = this.get("logo").validate();
+ if (logoErrors && Object.keys(logoErrors).length) {
+ errors.logo = "A logo image is required";
+ }
+ }
- /**
- * Retrieves the model attributes and serializes into portal XML,
- * to produce the new or modified portal document.
- *
- * @return {string} - Returns the portal XML as a string.
- */
- serialize: function(){
-
- try{
-
- // So we can call getXMLPosition() from within if{}
- var model = this;
-
- var xmlDoc,
- portalNode,
- xmlString;
-
- xmlDoc = this.get("objectXML");
-
- // Check if there is a portal doc already
- if (xmlDoc == null){
- // If not create one
- xmlDoc = this.createXML();
- } else {
- // If yes, clone it
- xmlDoc = xmlDoc.cloneNode(true);
- };
-
- // Iterate over each root XML node to find the portal node
- $(xmlDoc).children().each(function(i, el) {
- if (el.tagName.indexOf("portal") > -1) {
- portalNode = el;
- }
- });
-
- // Serialize the collection elements
- // ("name", "label", "description", "definition")
- portalNode = this.updateCollectionDOM(portalNode);
- xmlDoc = portalNode.getRootNode();
- var $portalNode = $(portalNode);
-
- // Set formatID
- this.set("formatId",
- MetacatUI.appModel.get("portalEditorSerializationFormat") ||
- "https://purl.dataone.org/portals-1.1.0");
-
- /* ==== Serialize portal logo ==== */
-
- // Remove node if it exists already
- $(xmlDoc).find("logo").remove();
-
- // Get new values
- var logo = this.get("logo");
-
- // Don't serialize falsey values or empty logos
- if(logo && logo.get("identifier")){
-
- // Make new node
- var logoSerialized = logo.updateDOM("logo");
-
- //Add the logo node to the XMLDocument
- xmlDoc.adoptNode(logoSerialized);
-
- // Insert new node at correct position
- var insertAfter = this.getXMLPosition(portalNode, "logo");
- if(insertAfter){
- insertAfter.after(logoSerialized);
- }
- else{
- portalNode.appendChild(logoSerialized);
- }
-
- };
-
- /* ==== Serialize acknowledgment logos ==== */
-
- // Remove element if it exists already
- $(xmlDoc).find("acknowledgmentsLogo").remove();
-
- var acknowledgmentsLogos = this.get("acknowledgmentsLogos");
-
- // Don't serialize falsey values
- if(acknowledgmentsLogos){
-
- _.each(acknowledgmentsLogos, function(imageModel) {
+ //---Validate the acknowledgmentsLogo---
- // Don't serialize empty imageModels
- if(
- imageModel.get("identifier") ||
- imageModel.get("label") ||
- imageModel.get("associatedURL")
- ){
+ var nonEmptyAckLogos = this.get("acknowledgmentsLogos").filter(
+ function (portalImage) {
+ return !portalImage.isEmpty();
+ },
+ );
+
+ if (requiredFields.acknowledgmentsLogos && !nonEmptyAckLogos.length) {
+ errors.acknowledgmentsLogos =
+ "At least one partner logo image is required.";
+ } else if (nonEmptyAckLogos && nonEmptyAckLogos.length) {
+ _.each(
+ nonEmptyAckLogos,
+ function (ackLogo) {
+ // Validate the portal image model
+ var ackLogoErrors = ackLogo.validate();
+
+ // If there is at least one error, then add an error to the PortalModel error list
+ if (ackLogoErrors && Object.keys(ackLogoErrors).length) {
+ errors.acknowledgmentsLogosImages =
+ "At least one acknowledgment logo has an error";
+ }
+ },
+ this,
+ );
+ }
+
+ //TODO: Validate these other elements, listed below, as they are added to the portal editor
+
+ //---Validate the associatedParties---
+
+ //---Validate the acknowledgments---
+
+ //---Validate the award---
+
+ //---Validate the literatureCited---
+
+ //---Validate the filterGroups---
+
+ //Return the errors object
+ if (Object.keys(errors).length) return errors;
+ else {
+ return;
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ },
+
+ /**
+ * Checks for the existing block list for repository labels
+ * If at least one other Portal has the same label, then it is not available.
+ * @param {string} label - The label to query for
+ */
+ checkLabelAvailability: function (label) {
+ //Validate the label set on the model if one isn't given
+ if (!label || typeof label != "string") {
+ var label = this.get("label");
+ if (!label || typeof label != "string") {
+ //Trigger an error event
+ this.trigger("errorValidatingLabel");
+ console.error("error validating label, no label provided");
+ return;
+ }
+ }
+
+ var model = this;
+
+ if (!this.get("checkedNodeLabels")) {
+ // query CN to fetch the latest node data
+ model.updateNodeBlockList();
+
+ this.listenTo(this, "change:checkedNodeLabels", function () {
+ this.checkPortalLabelAvailability(label);
+ });
+ } else {
+ this.checkPortalLabelAvailability(label);
+ }
+ },
+
+ /**
+ * Queries the Solr discovery index for other Portal objects with this same label.
+ * Also, checks for the existing block list for repository labels
+ * If at least one other Portal has the same label, then it is not available.
+ * @param {string} label - The label to query for
+ */
+ checkPortalLabelAvailability: function (label) {
+ var model = this;
+
+ // Stop Listening to the node model. We only need to retrieve this node label once.
+ this.stopListening(this, "change:checkedNodeLabels", function () {
+ this.checkPortalLabelAvailability(label);
+ });
- var ackLogosSerialized = imageModel.updateDOM();
+ // Convert the block list to lower case for case insensitive match
+ var lowerCaseBlockList = this.get("labelBlockList").map(
+ function (value) {
+ return value.toLowerCase();
+ },
+ );
+
+ // Check the existing blockList before making a Solr call
+ if (lowerCaseBlockList.indexOf(label.toLowerCase()) > -1) {
+ model.trigger("labelTaken");
+ return;
+ }
+
+ // Query solr to see if other portals already use this label
+ var requestSettings = {
+ url:
+ MetacatUI.appModel.get("queryServiceUrl") +
+ 'q=label:"' +
+ label +
+ '"' +
+ ' AND formatId:"' +
+ this.get("formatId") +
+ '"' +
+ "&rows=0" +
+ "&wt=json",
+ error: function (response) {
+ model.trigger("errorValidatingLabel");
+ },
+ success: function (response) {
+ if (response.response.numFound > 0) {
+ //Add this label to the blockList so we don't have to query for it later
+ var blockList = model.get("labelBlockList");
+ if (Array.isArray(blockList)) {
+ blockList.push(label);
+ }
- //Add the logo node to the XMLDocument
- xmlDoc.adoptNode(ackLogosSerialized);
+ model.trigger("labelTaken");
+ } else {
+ if (MetacatUI.appModel.get("alternateRepositories").length) {
+ MetacatUI.appModel.setActiveAltRepo();
+ var activeAltRepo = MetacatUI.appModel.getActiveAltRepo();
+ if (activeAltRepo) {
+ var requestSettings = {
+ url:
+ activeAltRepo.queryServiceUrl +
+ 'q=label:"' +
+ label +
+ '"' +
+ ' AND formatId:"' +
+ model.get("formatId") +
+ '"' +
+ "&rows=0" +
+ "&wt=json",
+ error: function (response) {
+ model.trigger("errorValidatingLabel");
+ },
+ success: function (response) {
+ if (response.response.numFound > 0) {
+ //Add this label to the blockList so we don't have to query for it later
+ var blockList = model.get("labelBlockList");
+ if (Array.isArray(blockList)) {
+ blockList.push(label);
+ }
- // Insert new node at correct position
- var insertAfter = model.getXMLPosition(portalNode, "acknowledgmentsLogo");
- if(insertAfter){
- insertAfter.after(ackLogosSerialized);
- }
- else {
- portalNode.appendChild(ackLogosSerialized);
+ model.trigger("labelTaken");
+ } else {
+ model.trigger("labelAvailable");
}
- }
- })
- };
-
- /* ==== Serialize literature cited ==== */
- // Assumes the value of literatureCited is a block of bibtex text
-
- // Remove node if it exists already
- $(xmlDoc).find("literatureCited").remove();
-
- // Get new values
- var litCit = this.get("literatureCited");
-
- // Don't serialize falsey values
- if( litCit.length ){
+ },
+ };
+ //Attach the User auth info and send the request
+ requestSettings = _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
+ $.ajax(requestSettings);
+ }
+ } else {
+ model.trigger("labelAvailable");
+ }
+ }
+ },
+ };
+ //Attach the User auth info and send the request
+ requestSettings = _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings(),
+ );
+ $.ajax(requestSettings);
+ },
+
+ /**
+ * Queries the CN Solr to retrieve the updated BlockList
+ */
+ updateNodeBlockList: function () {
+ var model = this;
+
+ $.ajax({
+ url: MetacatUI.appModel.get("nodeServiceUrl"),
+ dataType: "text",
+ error: function (data, textStatus, xhr) {
+ // if there is an error in retrieving the node list;
+ // proceed with the existing node list to perform the checks
+ model.checkPortalLabelAvailability();
+ },
+ success: function (data, textStatus, xhr) {
+ var xmlResponse = $.parseXML(data) || null;
+ if (!xmlResponse) return;
+
+ // update the node block list on success
+ model.saveNodeBlockList(xmlResponse);
+ },
+ });
+ },
+
+ /**
+ * Parses the retrieved XML document and saves the node information to the BlockList
+ *
+ * @param {XMLDocument} The XMLDocument returned from the fetch() AJAX call
+ */
+ saveNodeBlockList: function (xml) {
+ var model = this,
+ children = xml.children || xml.childNodes;
+
+ //Traverse the XML response to get the MN info
+ _.each(children, function (d1NodeList) {
+ var d1NodeListChildren = d1NodeList.children || d1NodeList.childNodes;
+
+ //The first (and only) child should be the d1NodeList
+ _.each(d1NodeListChildren, function (thisNode) {
+ //Ignore parts of the XML that is not MN info
+ if (!thisNode.attributes) return;
+
+ //'node' will be a single node
+ var node = {},
+ nodeProperties = thisNode.children || thisNode.childNodes;
+
+ //Grab information about this node from XML nodes
+ _.each(nodeProperties, function (nodeProperty) {
+ if (nodeProperty.nodeName == "property")
+ node[$(nodeProperty).attr("key")] = nodeProperty.textContent;
+ else node[nodeProperty.nodeName] = nodeProperty.textContent;
+
+ //Check if this member node has v2 read capabilities - important for the Package service
+ if (
+ nodeProperty.nodeName == "services" &&
+ nodeProperty.childNodes.length
+ ) {
+ var v2 = $(nodeProperty).find(
+ "service[name='MNRead'][version='v2'][available='true']",
+ ).length;
+ node["readv2"] = v2;
+ }
+ });
+
+ //Grab information about this node from XLM attributes
+ _.each(thisNode.attributes, function (attribute) {
+ node[attribute.nodeName] = attribute.nodeValue;
+ });
+
+ // Append Node name, node identifier and node short identifier to the array.
+ // node identifier
+ if (
+ Array.isArray(model.get("labelBlockList")) &&
+ model.get("labelBlockList").indexOf(node.identifier) < 0
+ ) {
+ model.get("labelBlockList").push(node.identifier);
+ }
- // If there's only one element in litCited, it will be a string
- // turn it into an array so that we can use _.each
- if(typeof litCit == "string"){
- litCit = [litCit]
- }
+ // node name
+ if (node.CN_node_name) {
+ node.name = node.CN_node_name;
+ if (
+ Array.isArray(model.get("labelBlockList")) &&
+ model.get("labelBlockList").indexOf(node.name) < 0
+ ) {
+ model.get("labelBlockList").push(node.name);
+ }
+ }
- // Make new element
- var litCitSerialized = xmlDoc.createElement("literatureCited");
+ // node short identifier
+ node.shortIdentifier = node.identifier.substring(
+ node.identifier.lastIndexOf(":") + 1,
+ );
+ if (
+ Array.isArray(model.get("labelBlockList")) &&
+ model.get("labelBlockList").indexOf(node.shortIdentifier) < 0
+ ) {
+ model.get("labelBlockList").push(node.shortIdentifier);
+ }
+ });
+ });
- _.each(litCit, function(bibtex){
+ this.set("checkedNodeLabels", "true");
+ },
+
+ /**
+ * Removes nodes from the XML that do not have an accompanying model
+ * (i.e. nodes which were probably removed by the user during editing)
+ *
+ * @param {jQuery} nodes - The nodes to potentially remove
+ * @param {Model[]} models - The model to compare to
+ */
+ removeExtraNodes: function (nodes, models) {
+ // Remove the extra nodes
+ var extraNodes = nodes.length - models.length;
+ if (extraNodes > 0) {
+ for (var i = models.length; i < nodes.length; i++) {
+ $(nodes[i]).remove();
+ }
+ }
+ },
+
+ /**
+ * Saves the portal XML document to the server using the DataONE API
+ */
+ save: function () {
+ var model = this;
+
+ // Remove empty filters from the custom portal search filters.
+ this.get("filterGroups").forEach(function (filterGroupModel) {
+ filterGroupModel.get("filters").removeEmptyFilters();
+ }, this);
+
+ // Ensure empty filters (rule groups) are removed, including from
+ // within any nested filter groups
+ this.get("definitionFilters").removeEmptyFilters(true);
+
+ // Validate before we try anything else
+ if (!this.isValid()) {
+ //Trigger the invalid and cancelSave events
+ this.trigger("invalid");
+ this.trigger("cancelSave");
+ //Don't save the model since it's invalid
+ return false;
+ } else {
+ //Double-check that the label is available, if it was changed
+ if (
+ (this.isNew() || this.get("originalLabel") != this.get("label")) &&
+ !this.get("labelDoubleChecked")
+ ) {
+ //If the label is taken
+ this.once("labelTaken", function () {
+ //Stop listening to the label availability
+ this.stopListening("labelAvailable");
+
+ //Set that the label has been double-checked
+ this.set("labelDoubleChecked", true);
+
+ //If this portal is in a free trial of DataONE Plus, generate a new random label
+ // and start the save process again
+ if (MetacatUI.appModel.get("enableBookkeeperServices")) {
+ var subscription = MetacatUI.appUserModel.get(
+ "dataoneSubscription",
+ );
+ if (subscription && subscription.isTrialing()) {
+ this.setRandomLabel();
+
+ this.set("labelDoubleChecked", true);
+
+ // Start the save process again
+ this.save();
- // Wrap in literature cited in cdata tags
- var cdataLitCit = xmlDoc.createCDATASection(bibtex);
- var bibtexSerialized = xmlDoc.createElement("bibtex");
- // wrap in CDATA tags so that bibtex characters aren't escaped
- bibtexSerialized.appendChild(cdataLitCit);
- // is a subelement of
- litCitSerialized.appendChild(bibtexSerialized);
+ return;
+ }
+ } else {
+ //If the label is taken, trigger an invalid event
+ this.trigger("invalid");
+ //Trigger a cancellation of the save event
+ this.trigger("cancelSave");
+ }
+ });
+
+ this.once("labelAvailable", function () {
+ this.stopListening("labelTaken");
+ this.set("labelDoubleChecked", true);
+ this.save();
+ });
+
+ // Check label availability
+ this.checkLabelAvailability(this.get("label"));
+
+ // console.log("Double checking label");
+
+ //Don't proceed with the rest of the save
+ return;
+ } else {
+ this.trigger("valid");
+ }
+ }
+
+ //Check if the checksum has been calculated yet.
+ if (!this.get("checksum")) {
+ // Serialize the XML
+ var xml = this.serialize();
+
+ //If there is no xml returned from the serialize() function, then there
+ // was an error, so don't save.
+ if (typeof xml === "undefined" || !xml) {
+ //If no error message is set on the model, trigger an error now.
+ // If there is an error message already, it means the error has already
+ // been triggered inside the serialize() function.
+ if (!this.get("errorMessage")) {
+ this.trigger(
+ "errorSaving",
+ MetacatUI.appModel.get("portalEditSaveErrorMsg"),
+ );
+ }
+ return;
+ }
+
+ var xmlBlob = new Blob([xml], { type: "application/xml" });
+
+ //Set the Blob as the upload file
+ this.set("uploadFile", xmlBlob);
+
+ //When it is calculated, restart this function
+ this.off("checksumCalculated", this.save);
+ this.on("checksumCalculated", this.save);
+ //Calculate the checksum for this file
+ this.calculateChecksum();
+
+ //Exit this function until the checksum is done
+ return;
+ }
+
+ this.constructor.__super__.save.call(this);
+ },
+
+ /**
+ * Removes or hides the given section from this Portal
+ * @param {PortalSectionModel|string} section - Either the PortalSectionModel
+ * to remove, or the name of the section to remove. Some sections in the portals
+ * are not tied to PortalSectionModels, because they are created from other parts of the Portal
+ * document. For example, the Data, Metrics, and Members sections.
+ */
+ removeSection: function (section) {
+ try {
+ //If this section is a string, remove it by adding custom options
+ if (typeof section == "string") {
+ switch (section.toLowerCase()) {
+ case "data":
+ this.set("hideData", true);
+ break;
+ case "metrics":
+ this.set("hideMetrics", true);
+ break;
+ case "members":
+ this.set("hideMembers", true);
+ break;
+ }
+ }
+ //If this section is a section model, delete it from this Portal
+ else if (PortalSectionModel.prototype.isPrototypeOf(section)) {
+ // Remove the section from the model's sections array object.
+ // Use clone() to create new array reference and ensure change
+ // event is tirggered.
+ var sectionModels = _.clone(this.get("sections"));
+ sectionModels.splice($.inArray(section, sectionModels), 1);
+ this.set({ sections: sectionModels });
+ } else {
+ return;
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ },
+
+ /**
+ * Adds the given section to this Portal
+ * @param {PortalSectionModel|string} section - Either the PortalSectionModel
+ * to add, or the name of the section to add. Some sections in the portals
+ * are not tied to PortalSectionModels, because they are created from other parts of the Portal
+ * document. For example, the Data, Metrics, and Members sections.
+ */
+ addSection: function (section) {
+ try {
+ //If this section is a string, add it by adding custom options
+ if (typeof section == "string") {
+ switch (section.toLowerCase()) {
+ case "data":
+ this.set("hideData", null);
+ break;
+ case "metrics":
+ this.set("hideMetrics", null);
+ break;
+ case "members":
+ this.set("hideMembers", null);
+ break;
+ case "freeform":
+ // Add a new, blank markdown section with a default image
+ var sectionModels = _.clone(this.get("sections")),
+ newSection = new PortalSectionModel({
+ portalModel: this,
+ // Include a default image if some are configured.
+ image: this.getRandomSectionImage(),
});
- // Insert new element at correct position
- var insertAfter = this.getXMLPosition(portalNode, "literatureCited");
- if(insertAfter){
- insertAfter.after(litCitSerialized);
- }
- else{
- portalNode.appendChild(litCitSerialized);
- }
+ sectionModels.push(newSection);
+ this.set("sections", sectionModels);
+ // Trigger event manually so we can just pass newSection
+ this.trigger("addSection", newSection);
+ break;
+ }
+ }
+ // If this section is a section model, add it to this Portal
+ else if (PortalSectionModel.prototype.isPrototypeOf(section)) {
+ var sectionModels = _.clone(this.get("sections"));
+ sectionModels.push(section);
+ this.set({ sections: sectionModels });
+ // trigger event manually so we can just pass newSection
+ this.trigger("addSection", section);
+ } else {
+ return;
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ },
+
+ /**
+ * removePortalImage - remove a PortalImage model from either the
+ * logo, sections, or acknowledgmentsLogos node of the portal model.
+ *
+ * @param {Image} portalImage the portalImage model to remove
+ */
+ removePortalImage: function (portalImage) {
+ try {
+ // find the portalImage to remove
+ switch (portalImage.get("nodeName")) {
+ case "logo":
+ if (portalImage === this.get("logo")) {
+ this.set("logo", this.defaults().logo);
+ }
+ break;
+ case "image":
+ _.each(this.get("sections"), function (section, i) {
+ if (portalImage === section.get("image")) {
+ section.set("image", section.defaults().image);
}
+ });
+ break;
+ case "acknowledgmentsLogo":
+ var ackLogos = _.clone(this.get("acknowledgmentsLogos"));
+ ackLogos.splice($.inArray(portalImage, ackLogos), 1);
+ this.set({ acknowledgmentsLogos: ackLogos });
+ break;
+ }
+ } catch (e) {
+ console.log(
+ "Failed to remove a portalImage model, error message: " + e,
+ );
+ }
+ },
+
+ /**
+ * Saves a reference to this Portal on the MetacatUI global object
+ */
+ cachePortal: function () {
+ if (this.get("id")) {
+ MetacatUI.portals = MetacatUI.portals || {};
+ MetacatUI.portals[this.get("id")] = this;
+ }
+
+ this.on("change:id", this.cachePortal);
+ },
+
+ /**
+ * Creates a URL for viewing more information about this object
+ * @return {string}
+ */
+ createViewURL: function () {
+ return (
+ MetacatUI.root +
+ "/" +
+ MetacatUI.appModel.get("portalTermPlural") +
+ "/" +
+ encodeURIComponent(
+ this.get("label") || this.get("seriesId") || this.get("id"),
+ )
+ );
+ },
+
+ /**
+ * Sets attributes on this Portal using the given Member Node data
+ * @param {object} nodeInfoObject - A literal object taken from the NodeModel 'members' array
+ */
+ createNodeAttributes: function (nodeInfoObject) {
+ var nodePortalModel = {};
+
+ if (nodeInfoObject === undefined) {
+ nodeInfoObject = {};
+ }
+
+ //TODO - check for undefined for each of the nodeInfo properties
+
+ // Setting basic properties from the node info object
+ this.set("name", nodeInfoObject.name);
+ this.set("logo", nodeInfoObject.logo);
+ this.set("description", nodeInfoObject.description);
+
+ // Creating repo specific Filters
+ var nodeFilterModel = new FilterModel({
+ fields: ["datasource"],
+ values: [nodeInfoObject.identifier],
+ label: "Datasets for a repository",
+ matchSubstring: false,
+ operator: "OR",
+ });
- /* ==== Serialize portal content sections ==== */
-
- // Remove node if it exists already
- $portalNode.children("section").remove();
-
- var sections = this.get("sections");
-
- // Don't serialize falsey values
- if(sections){
-
- _.each(sections, function(sectionModel) {
-
- // Don't serialize sections with default values
- if(!this.sectionIsDefault(sectionModel)){
-
- var sectionSerialized = sectionModel.updateDOM();
-
- //If there was an error serializing this section, or if
- // nothing was returned, don't do anythiing further
- if( !sectionSerialized ){
- return;
- }
-
- //Add the section node to the XMLDocument
- xmlDoc.adoptNode(sectionSerialized);
-
- // Remove sections entirely if the content is blank
- var newMD = $(sectionSerialized).find("markdown")[0];
- if( !newMD || newMD.textContent == "" ){
- $(sectionSerialized).find("markdown").remove();
- }
+ // adding the filter in the node model
+ this.get("definitionFilters").add(nodeFilterModel);
- // Remove the element if it's empty.
- // This will trigger a validation error, prompting user to
- // enter content.
- if($(sectionSerialized).find("content").is(':empty')){
- $(sectionSerialized).find("content").remove();
- }
+ // Set up the search model
+ this.get("searchModel").get("filters").add(nodeFilterModel);
+ },
- // Insert new node at correct position
- var insertAfter = model.getXMLPosition(portalNode, "section");
- if(insertAfter){
- insertAfter.after(sectionSerialized);
- }
- else {
- portalNode.appendChild(sectionSerialized);
- }
+ /**
+ * Cleans up the given text so that it is XML-valid by escaping reserved characters, trimming white space, etc.
+ *
+ * @param {string} textString - The string to clean up
+ * @return {string} - The cleaned up string
+ */
+ cleanXMLText: function (textString) {
+ if (typeof textString != "string") return;
- }
+ textString = textString.trim();
- }, this)
- };
-
- /* ==== Serialize the EMLText elements ("acknowledgments") ==== */
-
- var textFields = ["acknowledgments"];
-
- _.each(textFields, function(field){
-
- var fieldName = field;
-
- // Get the EMLText model
- var emlTextModels = Array.isArray(this.get(field)) ? this.get(field) : [this.get(field)];
- if( ! emlTextModels.length ) return;
-
- // Get the node from the XML doc
- var nodes = $portalNode.children(fieldName);
-
- // Update the DOMs for each model
- _.each(emlTextModels, function(thisTextModel, i){
- //Don't serialize falsey values
- if(!thisTextModel) return;
-
- var node;
-
- //Get the existing node or create a new one
- if(nodes.length < i+1){
- node = xmlDoc.createElement(fieldName);
- this.getXMLPosition(portalNode, fieldName).after(node);
- }
- else {
- node = nodes[i];
- }
-
- var textModelSerialized = thisTextModel.updateDOM();
-
- //If the text model wasn't serialized correctly or resulted in nothing
- if(typeof textModelSerialized == "undefined" || !textModelSerialized){
- //Remove the existing node
- $(node).remove();
- }
- else{
- xmlDoc.adoptNode(textModelSerialized);
- $(node).replaceWith(textModelSerialized);
- }
-
- }, this);
-
- // Remove the extra nodes
- this.removeExtraNodes(nodes, emlTextModels);
-
- }, this);
-
- /* ==== Serialize awards ==== */
-
- // Remove award node if it exists already
- $portalNode.children("award").remove();
-
- // Get new values
- var awards = this.get("awards");
-
- // Don't serialize falsey values
- if(awards && awards.length>0){
-
- _.each(awards, function(award){
-
- // Make new node
- var awardSerialized = xmlDoc.createElement("award");
-
- // create the subnodes
- _.map(award, function(value, nodeName){
-
- // serialize the simple text nodes
- if(nodeName != "funderLogo"){
- // Don't serialize falsey values
- if(value){
- // Make new sub-nodes
- var awardSubnodeSerialized = xmlDoc.createElement(nodeName);
- $(awardSubnodeSerialized).text(value);
- $(awardSerialized).append(awardSubnodeSerialized);
- }
- } else {
- // serialize "funderLogo" which is ImageType
- var funderLogoSerialized = value.updateDOM();
- xmlDoc.adoptNode(funderLogoSerialized);
- $(awardSerialized).append(funderLogoSerialized);
- }
-
- });
-
- // Insert new node at correct position
- var insertAfter = model.getXMLPosition(portalNode, "award");
- if(insertAfter){
- insertAfter.after(awardSerialized);
- }
- else{
- portalNode.appendChild(awardSerialized);
- }
-
- });
-
- }
-
- /* ==== Serialize associatedParties ==== */
-
- // Remove element if it exists already
- $portalNode.children("associatedParty").remove();
-
- // Get new values
- var parties = this.get("associatedParties");
-
- // Don't serialize falsey values
- if(parties){
-
- // Serialize each associatedParty
- _.each(parties, function(party){
-
- // Update the DOM of the EMLParty
- var partyEl = party.updateDOM();
- partyDoc = $.parseXML(party.formatXML( $(partyEl)[0] ));
-
- // Make sure we don't insert empty EMLParty nodes into the EML
- if(partyDoc.childNodes.length){
- //Save a reference to the associated party element in the NodeList
- var assocPartyEl = partyDoc.childNodes[0];
- //Add the associated part element to the portal XML doc
- xmlDoc.adoptNode(assocPartyEl);
-
- // Get the last node of this type to insert after
- var insertAfter = $portalNode.children("associatedParty").last();
-
- // If there isn't a node found, find the EML position to insert after
- if( !insertAfter.length ) {
- insertAfter = model.getXMLPosition(portalNode, "associatedParty");
- }
-
- //Insert the party DOM at the insert position
- if ( insertAfter && insertAfter.length ){
- insertAfter.after(assocPartyEl);
- } else {
- portalNode.appendChild(assocPartyEl);
- }
- }
- });
- }
-
- try{
- /* ==== Serialize options (including map options) ==== */
- // This will only serialize the options named in `optNames` (below)
- // Functionality needed in order to serialize new or custom options
-
- // The standard list of options used in portals
- var optNames = this.get("optionNames");
-
- _.each(optNames, function(optName){
-
- //Get the value on the model
- var optValue = model.get(optName),
- existingValue;
-
- //Get the existing optionName element
- var matchingOption = $portalNode.children("option")
- .find("optionName:contains('" + optName + "')");
-
- //
- if( !matchingOption.length || matchingOption.first().text() != optName ){
- matchingOption = false;
- }
- else{
- //Get the value for this option from the Portal doc
- existingValue = matchingOption.siblings("optionValue").text();
- }
-
- // Don't serialize null or undefined values. Also don't serialize values that match the default model value
- if( (optValue || optValue === 0 || optValue === false) &&
- (optValue != model.defaults()[optName]) ){
-
- //Replace the existing option, if it exists
- if( matchingOption ){
- matchingOption.siblings("optionValue").text(optValue);
- }
- else{
- // Make new node
- // and are subelements of
- var optionSerialized = xmlDoc.createElement("option"),
- optNameSerialized = xmlDoc.createElement("optionName"),
- optValueSerialized = xmlDoc.createElement("optionValue");
-
- $(optNameSerialized).text(optName);
- $(optValueSerialized).text(optValue);
-
- $(optionSerialized).append(optNameSerialized, optValueSerialized);
-
- // Insert new node at correct position
- var insertAfter = model.getXMLPosition(portalNode, "option");
-
- if(insertAfter){
- insertAfter.after(optionSerialized);
- }
-
- }
-
- }
- else{
- //Remove the elements from the portal XML when the value is invalid
- if( matchingOption ){
- matchingOption.parent("option").remove();
- }
- }
- });
- }
- catch(e){
- console.error(e);
- }
-
- /* ==== Serialize UI FilterGroups (aka custom search filters) ==== */
-
- // Get new filter group values
- var filterGroups = this.get("filterGroups");
-
- // Remove filter groups in the current objectDOM that are at the portal
- // level. (don't use .find("filterGroup") as that would remove
- // filterGroups that are nested in the definition
- $portalNode.children("filterGroup").remove();
-
- // Make a new node for each filter group in the model
- _.each(filterGroups, function(filterGroup){
-
- filterGroupSerialized = filterGroup.updateDOM();
-
- if (filterGroupSerialized){
- //Add the new element to the XMLDocument
- xmlDoc.adoptNode(filterGroupSerialized);
-
- // Insert new node at correct position
- var insertAfter = model.getXMLPosition(portalNode, "filterGroup");
-
- if (insertAfter) {
- insertAfter.after(filterGroupSerialized);
- }
- else {
- portalNode.appendChild(filterGroupSerialized);
- }
- }
-
-
- });
-
- /* ==== Remove duplicates ==== */
-
- //Do a final check to make sure there are no duplicate ids in the XML
- var elementsWithIDs = $(xmlDoc).find("[id]"),
- //Get an array of all the ids in this EML doc
- allIDs = _.map(elementsWithIDs, function(el){ return $(el).attr("id") });
-
- //If there is at least one id in the EML...
- if(allIDs && allIDs.length){
- //Boil the array down to just the unique values
- var uniqueIDs = _.uniq(allIDs);
-
- //If the unique array is shorter than the array of all ids,
- // then there is a duplicate somewhere
- if(uniqueIDs.length < allIDs.length){
-
- //For each element in the EML that has an id,
- _.each(elementsWithIDs, function(el){
-
- //Get the id for this element
- var id = $(el).attr("id");
-
- //If there is more than one element in the EML with this id,
- if( $(xmlDoc).find("[id='" + id + "']").length > 1 ){
- //And if it is not a unit node, which we don't want to change,
- if( !$(el).is("unit") )
- //Then change the id attribute to a random uuid
- $(el).attr("id", "urn-uuid-" + uuid.v4());
- }
-
- });
-
- }
- }
-
- // Convert xml to xmlString and return xmlString
- xmlString = new XMLSerializer().serializeToString(xmlDoc);
-
- //If there isn't an XML declaration, add one
- if( xmlString.indexOf("' + xmlString;
- }
-
- return xmlString;
- }
- catch(e){
- console.error("Error while serializing the Portal XML document: ", e);
- this.set("errorMessage", e.stack);
- this.trigger("errorSaving", MetacatUI.appModel.get("portalEditSaveErrorMsg"));
- return;
- }
- },
-
- /**
- * Checks whether the given sectionModel has been updated by the
- * user, or whether all attributes match their default values.
- * For a section's markdown, the default value is either an empty
- * string or null. For a section's label, the default
- * value is either an empty string or a string that begins with the
- * value set to PortalModel.newSectionLabel. For all other attributes,
- * the defaults are set in PortalSectionModel.defaults.
- * @param {PortalSectionModel} sectionModel - The model to check against a default model
- * @return {boolean} returns true if the sectionModel matches a default model, and false when at least one attribute differs
- */
- sectionIsDefault: function(sectionModel){
-
- try{
-
- var defaults = sectionModel.defaults(),
- currentMarkdown = sectionModel.get("content").get("markdown"),
- labelRegex = new RegExp("^" + this.newSectionLabel, "i");
-
- // For each attribute, check whether it matches the default
- if(
- // Check whether markdown matches the content that's
- // auto-filled or whether it's empty
- ( //currentMarkdown === this.markdownExample ||
- currentMarkdown == "" ||
- currentMarkdown == null
- ) &&
- ( sectionModel.get("image") === defaults.image ) &&
- ( sectionModel.get("introduction") === defaults.introduction ) &&
- // Check whether label starts with the default new page name,
- // or whether it's empty
- (
- labelRegex.test( sectionModel.get("label") ) ||
- sectionModel.get("label") == "" ||
- sectionModel.get("label") == null
- ) &&
- ( sectionModel.get("literatureCited") === defaults.literatureCited ) &&
- ( sectionModel.get("title") === defaults.title )
- ){
- // All elements of the section match the default
- return true
- } else {
- // At least one attribute of the section has been updated
- return false
- }
-
- }
- catch(e){
- // If there's a problem with this function for some reason,
- // return false so that the section is serialized to avoid
- // losing information
- console.log("Failed to check whether section model is default. Serializing it anyway. Error message:" + e);
- return false
- }
-
- },
-
- /**
- * Initialize the object XML for a brand spankin' new portal
- * @inheritdoc
- *
- */
- createXML: function() {
- var format = MetacatUI.appModel.get("portalEditorSerializationFormat") ||
- "https://purl.dataone.org/portals-1.1.0";
- var xmlString = " ";
- var xmlNew = $.parseXML(xmlString);
- var portalNode = xmlNew.getElementsByTagName("por:portal")[0];
-
- this.set("ownerDocument", portalNode.ownerDocument);
- return(xmlNew);
- },
-
- /**
- * Overrides the default Backbone.Model.validate.function() to
- * check if this portal model has all the required values necessary
- * to save to the server.
- *
- * @param {Object} [attrs] - A literal object of model attributes to validate.
- * @param {Object} [options] - A literal object of options for this validation process
- * @return {Object} If there are errors, an object comprising error
- * messages. If no errors, returns nothing.
- */
- validate: function(attrs, options) {
-
- try{
-
- var errors = {},
- requiredFields = MetacatUI.appModel.get("portalEditorRequiredFields") || {};
-
- //Execute the superclass validate() function
- var collectionErrors = this.constructor.__super__.validate.call(this);
- if( typeof collectionErrors == "object" && Object.keys(collectionErrors).length ){
- //Use the errors messages from the CollectionModel for this PortalModel
- errors = collectionErrors;
- }
-
- // ---- Validate the description and name ----
- //Map the model attributes to the user-facing attribute name
- var textFields = {
- description: "description",
- name: "title"
- }
- //Iterate over each text field
- _.each( Object.keys(textFields), function(field){
- //If this field is required, and it is a string
- if( requiredFields[field] && typeof this.get(field) == "string" ){
- //If this is an empty string, set an error message
- if( !this.get(field).trim().length ){
- errors[field] = "A " + textFields[field] + " is required.";
- }
- }
- //If this field is required, and it's not a string at all, set an error message
- else if( requiredFields[field] ){
- errors[field] = "A " + textFields[field] + " is required.";
- }
- }, this);
-
- //---Validate the sections---
- //Iterate over each section model
- _.each( this.get("sections"), function(section){
-
- //Validate the section model
- var sectionErrors = section.validate();
-
- //If there is at least one error, then add an error to the PortalModel error list
- if( sectionErrors && Object.keys(sectionErrors).length ){
- errors.sections = "At least one section has an error";
- }
-
- }, this);
-
- //----Validate the logo----
- if(requiredFields.logo && (!this.get("logo") ||
- !this.get("logo").get("identifier")))
- {
- errors.logo = "A logo image is required";
- } else if(this.get("logo")){
- logoErrors = this.get("logo").validate();
- if(logoErrors && Object.keys(logoErrors).length ){
- errors.logo = "A logo image is required";
- }
- }
-
- //---Validate the acknowledgmentsLogo---
-
- var nonEmptyAckLogos = this.get("acknowledgmentsLogos").filter(function(portalImage){
- return(!portalImage.isEmpty())
- });
-
- if(
- requiredFields.acknowledgmentsLogos &&
- !nonEmptyAckLogos.length
- ){
- errors.acknowledgmentsLogos = "At least one partner logo image is required.";
- }
- else if (
- nonEmptyAckLogos &&
- nonEmptyAckLogos.length
- ){
-
- _.each( nonEmptyAckLogos, function(ackLogo){
-
- // Validate the portal image model
- var ackLogoErrors = ackLogo.validate();
-
- // If there is at least one error, then add an error to the PortalModel error list
- if( ackLogoErrors && Object.keys(ackLogoErrors).length ){
- errors.acknowledgmentsLogosImages = "At least one acknowledgment logo has an error";
- }
-
- }, this);
-
- }
-
- //TODO: Validate these other elements, listed below, as they are added to the portal editor
-
- //---Validate the associatedParties---
-
- //---Validate the acknowledgments---
-
- //---Validate the award---
-
- //---Validate the literatureCited---
-
- //---Validate the filterGroups---
-
- //Return the errors object
- if( Object.keys(errors).length )
- return errors;
- else{
- return;
- }
-
- }
- catch(e){
- console.error(e);
- }
-
- },
-
- /**
- * Checks for the existing block list for repository labels
- * If at least one other Portal has the same label, then it is not available.
- * @param {string} label - The label to query for
- */
- checkLabelAvailability: function(label){
-
- //Validate the label set on the model if one isn't given
- if(!label || typeof label != "string" ){
- var label = this.get("label");
- if(!label || typeof label != "string" ){
- //Trigger an error event
- this.trigger("errorValidatingLabel");
- console.error("error validating label, no label provided");
- return
- }
- }
-
- var model = this;
-
- if (!this.get("checkedNodeLabels")) {
- // query CN to fetch the latest node data
- model.updateNodeBlockList();
-
- this.listenTo(this, "change:checkedNodeLabels", function(){
- this.checkPortalLabelAvailability(label);
- });
- }
- else {
- this.checkPortalLabelAvailability(label);
- }
-
- },
-
- /**
- * Queries the Solr discovery index for other Portal objects with this same label.
- * Also, checks for the existing block list for repository labels
- * If at least one other Portal has the same label, then it is not available.
- * @param {string} label - The label to query for
- */
- checkPortalLabelAvailability: function(label) {
- var model = this;
-
- // Stop Listening to the node model. We only need to retrieve this node label once.
- this.stopListening(this, "change:checkedNodeLabels", function(){
- this.checkPortalLabelAvailability(label);
- });
-
- // Convert the block list to lower case for case insensitive match
- var lowerCaseBlockList = this.get("labelBlockList").map(function(value) {
- return value.toLowerCase();
- });
-
- // Check the existing blockList before making a Solr call
- if (lowerCaseBlockList.indexOf(label.toLowerCase()) > -1) {
- model.trigger("labelTaken");
- return
- }
-
- // Query solr to see if other portals already use this label
- var requestSettings = {
- url: MetacatUI.appModel.get("queryServiceUrl") +
- "q=label:\"" + label + "\"" +
- " AND formatId:\"" + this.get("formatId") + "\"" +
- "&rows=0" +
- "&wt=json",
- error: function(response) {
- model.trigger("errorValidatingLabel");
- },
- success: function(response){
- if( response.response.numFound > 0 ){
- //Add this label to the blockList so we don't have to query for it later
- var blockList = model.get("labelBlockList");
- if( Array.isArray(blockList) ){
- blockList.push(label);
- }
-
- model.trigger("labelTaken");
- } else {
- if( MetacatUI.appModel.get("alternateRepositories").length ){
-
- MetacatUI.appModel.setActiveAltRepo();
- var activeAltRepo = MetacatUI.appModel.getActiveAltRepo();
- if( activeAltRepo ){
- var requestSettings = {
- url: activeAltRepo.queryServiceUrl +
- "q=label:\"" + label + "\"" +
- " AND formatId:\"" + model.get("formatId") + "\"" +
- "&rows=0" +
- "&wt=json",
- error: function(response) {
- model.trigger("errorValidatingLabel");
- },
- success: function(response){
- if( response.response.numFound > 0 ){
- //Add this label to the blockList so we don't have to query for it later
- var blockList = model.get("labelBlockList");
- if( Array.isArray(blockList) ){
- blockList.push(label);
- }
-
- model.trigger("labelTaken");
- } else {
- model.trigger("labelAvailable");
- }
- }
- }
- //Attach the User auth info and send the request
- requestSettings = _.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings());
- $.ajax(requestSettings);
- }
-
- }
- else{
- model.trigger("labelAvailable");
- }
- }
- }
- }
- //Attach the User auth info and send the request
- requestSettings = _.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings());
- $.ajax(requestSettings);
- },
-
-
-
- /**
- * Queries the CN Solr to retrieve the updated BlockList
- */
- updateNodeBlockList: function(){
- var model = this;
-
- $.ajax({
- url: MetacatUI.appModel.get('nodeServiceUrl'),
- dataType: "text",
- error: function(data, textStatus, xhr) {
- // if there is an error in retrieving the node list;
- // proceed with the existing node list to perform the checks
- model.checkPortalLabelAvailability()
- },
- success: function(data, textStatus, xhr) {
-
- var xmlResponse = $.parseXML(data) || null;
- if(!xmlResponse) return;
-
- // update the node block list on success
- model.saveNodeBlockList(xmlResponse);
- }
- });
- },
-
- /**
- * Parses the retrieved XML document and saves the node information to the BlockList
- *
- * @param {XMLDocument} The XMLDocument returned from the fetch() AJAX call
- */
- saveNodeBlockList: function(xml){
- var model = this,
- children = xml.children || xml.childNodes;
-
- //Traverse the XML response to get the MN info
- _.each(children, function(d1NodeList){
-
- var d1NodeListChildren = d1NodeList.children || d1NodeList.childNodes;
-
- //The first (and only) child should be the d1NodeList
- _.each(d1NodeListChildren, function(thisNode){
-
- //Ignore parts of the XML that is not MN info
- if(!thisNode.attributes) return;
-
- //'node' will be a single node
- var node = {},
- nodeProperties = thisNode.children || thisNode.childNodes;
-
- //Grab information about this node from XML nodes
- _.each(nodeProperties, function(nodeProperty){
-
- if(nodeProperty.nodeName == "property")
- node[$(nodeProperty).attr("key")] = nodeProperty.textContent;
- else
- node[nodeProperty.nodeName] = nodeProperty.textContent;
-
- //Check if this member node has v2 read capabilities - important for the Package service
- if((nodeProperty.nodeName == "services") && nodeProperty.childNodes.length){
- var v2 = $(nodeProperty).find("service[name='MNRead'][version='v2'][available='true']").length;
- node["readv2"] = v2;
- }
- });
-
- //Grab information about this node from XLM attributes
- _.each(thisNode.attributes, function(attribute){
- node[attribute.nodeName] = attribute.nodeValue;
- });
-
- // Append Node name, node identifier and node short identifier to the array.
- // node identifier
- if (Array.isArray(model.get("labelBlockList")) && ((model.get("labelBlockList")).indexOf(node.identifier) < 0)) {
- model.get("labelBlockList").push(node.identifier);
- }
-
- // node name
- if(node.CN_node_name) {
- node.name = node.CN_node_name;
- if (Array.isArray(model.get("labelBlockList")) && ((model.get("labelBlockList")).indexOf(node.name) < 0)) {
- model.get("labelBlockList").push(node.name);
- }
- }
-
- // node short identifier
- node.shortIdentifier = node.identifier.substring(node.identifier.lastIndexOf(":") + 1);
- if (Array.isArray(model.get("labelBlockList")) && ((model.get("labelBlockList")).indexOf(node.shortIdentifier) < 0)) {
- model.get("labelBlockList").push(node.shortIdentifier);
- }
-
- });
- });
-
- this.set("checkedNodeLabels", "true");
- },
-
- /**
- * Removes nodes from the XML that do not have an accompanying model
- * (i.e. nodes which were probably removed by the user during editing)
- *
- * @param {jQuery} nodes - The nodes to potentially remove
- * @param {Model[]} models - The model to compare to
- */
- removeExtraNodes: function(nodes, models){
- // Remove the extra nodes
- var extraNodes = nodes.length - models.length;
- if(extraNodes > 0){
- for(var i = models.length; i < nodes.length; i++){
- $(nodes[i]).remove();
- }
- }
- },
-
- /**
- * Saves the portal XML document to the server using the DataONE API
- */
- save: function(){
-
- var model = this;
-
- // Remove empty filters from the custom portal search filters.
- this.get("filterGroups").forEach(function(filterGroupModel){
- filterGroupModel.get("filters").removeEmptyFilters();
- }, this);
-
- // Ensure empty filters (rule groups) are removed, including from
- // within any nested filter groups
- this.get("definitionFilters").removeEmptyFilters(true);
-
- // Validate before we try anything else
- if(!this.isValid()){
- //Trigger the invalid and cancelSave events
- this.trigger("invalid");
- this.trigger("cancelSave");
- //Don't save the model since it's invalid
- return false;
- }
- else{
- //Double-check that the label is available, if it was changed
- if( (this.isNew() || this.get("originalLabel") != this.get("label")) && !this.get("labelDoubleChecked") ){
- //If the label is taken
- this.once("labelTaken", function(){
-
- //Stop listening to the label availability
- this.stopListening("labelAvailable");
-
- //Set that the label has been double-checked
- this.set("labelDoubleChecked", true);
-
- //If this portal is in a free trial of DataONE Plus, generate a new random label
- // and start the save process again
- if( MetacatUI.appModel.get("enableBookkeeperServices") ){
-
- var subscription = MetacatUI.appUserModel.get("dataoneSubscription");
- if(subscription && subscription.isTrialing()) {
- this.setRandomLabel();
-
- this.set("labelDoubleChecked", true);
-
- // Start the save process again
- this.save();
-
- return;
- }
-
- }
- else{
- //If the label is taken, trigger an invalid event
- this.trigger("invalid");
- //Trigger a cancellation of the save event
- this.trigger("cancelSave");
- }
-
- });
-
- this.once("labelAvailable", function(){
- this.stopListening("labelTaken");
- this.set("labelDoubleChecked", true);
- this.save();
- });
-
- // Check label availability
- this.checkLabelAvailability(this.get("label"));
-
- // console.log("Double checking label");
-
- //Don't proceed with the rest of the save
- return;
- }
- else{
- this.trigger("valid");
- }
-
- }
-
- //Check if the checksum has been calculated yet.
- if( !this.get("checksum") ){
- // Serialize the XML
- var xml = this.serialize();
-
- //If there is no xml returned from the serialize() function, then there
- // was an error, so don't save.
- if( typeof xml === "undefined" || !xml ){
- //If no error message is set on the model, trigger an error now.
- // If there is an error message already, it means the error has already
- // been triggered inside the serialize() function.
- if( !this.get("errorMessage") ){
- this.trigger("errorSaving", MetacatUI.appModel.get("portalEditSaveErrorMsg"));
- }
-
- return;
- }
-
- var xmlBlob = new Blob([xml], {type : 'application/xml'});
-
- //Set the Blob as the upload file
- this.set("uploadFile", xmlBlob);
-
- //When it is calculated, restart this function
- this.off("checksumCalculated", this.save);
- this.on("checksumCalculated", this.save);
- //Calculate the checksum for this file
- this.calculateChecksum();
-
- //Exit this function until the checksum is done
- return;
- }
-
- this.constructor.__super__.save.call(this);
- },
-
- /**
- * Removes or hides the given section from this Portal
- * @param {PortalSectionModel|string} section - Either the PortalSectionModel
- * to remove, or the name of the section to remove. Some sections in the portals
- * are not tied to PortalSectionModels, because they are created from other parts of the Portal
- * document. For example, the Data, Metrics, and Members sections.
- */
- removeSection: function(section){
-
- try{
-
- //If this section is a string, remove it by adding custom options
- if(typeof section == "string"){
- switch( section.toLowerCase() ){
- case "data":
- this.set("hideData", true);
- break;
- case "metrics":
- this.set("hideMetrics", true);
- break;
- case "members":
- this.set("hideMembers", true);
- break;
- }
- }
- //If this section is a section model, delete it from this Portal
- else if( PortalSectionModel.prototype.isPrototypeOf(section) ){
-
- // Remove the section from the model's sections array object.
- // Use clone() to create new array reference and ensure change
- // event is tirggered.
- var sectionModels = _.clone(this.get("sections"));
- sectionModels.splice( $.inArray(section, sectionModels), 1);
- this.set({sections: sectionModels});
- }
- else{
- return;
- }
- }
- catch(e){
- console.error(e);
- }
-
- },
-
- /**
- * Adds the given section to this Portal
- * @param {PortalSectionModel|string} section - Either the PortalSectionModel
- * to add, or the name of the section to add. Some sections in the portals
- * are not tied to PortalSectionModels, because they are created from other parts of the Portal
- * document. For example, the Data, Metrics, and Members sections.
- */
- addSection: function(section){
- try{
- //If this section is a string, add it by adding custom options
- if(typeof section == "string"){
- switch( section.toLowerCase() ){
- case "data":
- this.set("hideData", null);
- break;
- case "metrics":
- this.set("hideMetrics", null);
- break;
- case "members":
- this.set("hideMembers", null);
- break;
- case "freeform":
-
- // Add a new, blank markdown section with a default image
- var sectionModels = _.clone(this.get("sections")),
- newSection = new PortalSectionModel({
- portalModel: this,
- // Include a default image if some are configured.
- image: this.getRandomSectionImage()
- });
-
- sectionModels.push( newSection );
- this.set("sections", sectionModels);
- // Trigger event manually so we can just pass newSection
- this.trigger("addSection", newSection);
- break;
- }
- }
- // If this section is a section model, add it to this Portal
- else if( PortalSectionModel.prototype.isPrototypeOf(section) ){
- var sectionModels = _.clone(this.get("sections"));
- sectionModels.push( section );
- this.set({sections: sectionModels});
- // trigger event manually so we can just pass newSection
- this.trigger("addSection", section);
- }
- else{
- return;
- }
- }
- catch(e){
- console.error(e);
- }
- },
-
- /**
- * removePortalImage - remove a PortalImage model from either the
- * logo, sections, or acknowledgmentsLogos node of the portal model.
- *
- * @param {Image} portalImage the portalImage model to remove
- */
- removePortalImage: function(portalImage){
- try{
- // find the portalImage to remove
- switch (portalImage.get("nodeName")) {
- case "logo":
- if(portalImage === this.get("logo")){
- this.set("logo", this.defaults().logo);
- }
- break;
- case "image":
- _.each(this.get("sections"), function(section, i) {
- if(portalImage === section.get("image")){
- section.set("image", section.defaults().image)
- }
- });
- break;
- case "acknowledgmentsLogo":
- var ackLogos = _.clone(this.get("acknowledgmentsLogos"));
- ackLogos.splice( $.inArray(portalImage, ackLogos), 1);
- this.set({acknowledgmentsLogos: ackLogos});
- break;
- }
-
- } catch(e){
- console.log("Failed to remove a portalImage model, error message: " + e);
- }
-
- },
-
- /**
- * Saves a reference to this Portal on the MetacatUI global object
- */
- cachePortal: function(){
-
- if( this.get("id") ){
- MetacatUI.portals = MetacatUI.portals || {};
- MetacatUI.portals[this.get("id")] = this;
- }
-
- this.on("change:id", this.cachePortal);
- },
-
- /**
- * Creates a URL for viewing more information about this object
- * @return {string}
- */
- createViewURL: function(){
- return MetacatUI.root + "/" + MetacatUI.appModel.get("portalTermPlural") + "/" + encodeURIComponent((this.get("label") || this.get("seriesId") || this.get("id")));
- },
-
- /**
- * Sets attributes on this Portal using the given Member Node data
- * @param {object} nodeInfoObject - A literal object taken from the NodeModel 'members' array
- */
- createNodeAttributes: function(nodeInfoObject) {
- var nodePortalModel = {};
-
- if (nodeInfoObject === undefined) {
- nodeInfoObject = {}
- }
-
- //TODO - check for undefined for each of the nodeInfo properties
-
- // Setting basic properties from the node info object
- this.set("name", nodeInfoObject.name);
- this.set("logo", nodeInfoObject.logo);
- this.set("description", nodeInfoObject.description);
-
- // Creating repo specific Filters
- var nodeFilterModel = new FilterModel({
- fields: ["datasource"],
- values: [nodeInfoObject.identifier],
- label: "Datasets for a repository",
- matchSubstring: false,
- operator: "OR"
- });
-
- // adding the filter in the node model
- this.get("definitionFilters").add(nodeFilterModel);
-
- // Set up the search model
- this.get("searchModel").get("filters").add(nodeFilterModel);
-
- },
-
- /**
- * Cleans up the given text so that it is XML-valid by escaping reserved characters, trimming white space, etc.
- *
- * @param {string} textString - The string to clean up
- * @return {string} - The cleaned up string
- */
- cleanXMLText: function(textString){
-
- if( typeof textString != "string" )
- return;
-
- textString = textString.trim();
-
- //Check for XML/HTML elements
- _.each(textString.match(/<\s*[^>]*>/g), function(xmlNode){
-
- //Encode <, >, and substrings
- var tagName = xmlNode.replace(/>/g, ">");
- tagName = tagName.replace(/]*>/g), function (xmlNode) {
+ //Encode <, >, and substrings
+ var tagName = xmlNode.replace(/>/g, ">");
+ tagName = tagName.replace(/ element from a portal document
+ *
+ * @param {XMLElement} objectDOM - A ContentSectionType XML element from a portal document
+ * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
*/
- var PortalSectionModel = Backbone.Model.extend(
- /** @lends PortalSectionModel.prototype */{
- defaults: function(){
- return {
- label: "Untitled",
- image: "",
- title: "",
- introduction: "",
- content: new EMLText({
- type: "content",
- parentModel: this
- }),
- literatureCited: null,
- objectDOM: null,
- sectionType: "",
- portalModel: null
- }
- },
-
- /**
- * Parses a element from a portal document
- *
- * @param {XMLElement} objectDOM - A ContentSectionType XML element from a portal document
- * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
- */
- parse: function(objectDOM){
-
- if(!objectDOM){
- return {};
- }
-
- var $objectDOM = $(objectDOM),
- modelJSON = {};
-
- //Parse all the simple string elements
- modelJSON.label = $objectDOM.children("label").text();
- modelJSON.title = $objectDOM.children("title").text();
- modelJSON.introduction = $objectDOM.children("introduction").text();
-
- //Parse the image URL or identifier
- var image = $objectDOM.children("image");
- if( image.length ){
- var portImageModel = new PortalImage({
- objectDOM: image[0],
- portalModel: this.get("portalModel")
- });
- portImageModel.set(portImageModel.parse());
- modelJSON.image = portImageModel;
- }
-
- //Create an EMLText model for the section content
- modelJSON.content = new EMLText({
- objectDOM: $objectDOM.children("content")[0]
- });
- modelJSON.content.set(modelJSON.content.parse($objectDOM.children("content")));
+ parse: function (objectDOM) {
+ if (!objectDOM) {
+ return {};
+ }
- return modelJSON;
+ var $objectDOM = $(objectDOM),
+ modelJSON = {};
- },
+ //Parse all the simple string elements
+ modelJSON.label = $objectDOM.children("label").text();
+ modelJSON.title = $objectDOM.children("title").text();
+ modelJSON.introduction = $objectDOM.children("introduction").text();
- /**
- * Makes a copy of the original XML DOM and updates it with the new values from the model.
- *
- * @return {XMLElement} An updated ContentSectionType XML element from a portal document
- */
- updateDOM: function(){
+ //Parse the image URL or identifier
+ var image = $objectDOM.children("image");
+ if (image.length) {
+ var portImageModel = new PortalImage({
+ objectDOM: image[0],
+ portalModel: this.get("portalModel"),
+ });
+ portImageModel.set(portImageModel.parse());
+ modelJSON.image = portImageModel;
+ }
- var objectDOM = this.get("objectDOM");
+ //Create an EMLText model for the section content
+ modelJSON.content = new EMLText({
+ objectDOM: $objectDOM.children("content")[0],
+ });
+ modelJSON.content.set(
+ modelJSON.content.parse($objectDOM.children("content")),
+ );
- if (objectDOM) {
- objectDOM = objectDOM.cloneNode(true);
- $(objectDOM).empty();
- } else {
- // create an XML section element from scratch
- var xmlText = "",
- objectDOM = new DOMParser().parseFromString(xmlText, "text/xml"),
- objectDOM = $(objectDOM).children()[0];
- };
+ return modelJSON;
+ },
- // Get and update the simple text strings (everything but content)
- var sectionTextData = {
- label: this.get("label"),
- title: this.get("title"),
- introduction: this.get("introduction")
- };
+ /**
+ * Makes a copy of the original XML DOM and updates it with the new values from the model.
+ *
+ * @return {XMLElement} An updated ContentSectionType XML element from a portal document
+ */
+ updateDOM: function () {
+ var objectDOM = this.get("objectDOM");
+
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
+ $(objectDOM).empty();
+ } else {
+ // create an XML section element from scratch
+ var xmlText = "",
+ objectDOM = new DOMParser().parseFromString(xmlText, "text/xml"),
+ objectDOM = $(objectDOM).children()[0];
+ }
- _.map(sectionTextData, function(value, nodeName){
+ // Get and update the simple text strings (everything but content)
+ var sectionTextData = {
+ label: this.get("label"),
+ title: this.get("title"),
+ introduction: this.get("introduction"),
+ };
+ _.map(
+ sectionTextData,
+ function (value, nodeName) {
// Don't serialize default values, except for default label strings, since labels are required
- if(value && (value != this.defaults()[nodeName] || (nodeName == "label" && typeof value == "string")) ){
+ if (
+ value &&
+ (value != this.defaults()[nodeName] ||
+ (nodeName == "label" && typeof value == "string"))
+ ) {
// Make new sub-node
- var sectionSubnodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
+ var sectionSubnodeSerialized =
+ objectDOM.ownerDocument.createElement(nodeName);
$(sectionSubnodeSerialized).text(value);
this.addUpdatedXMLNode(objectDOM, sectionSubnodeSerialized);
}
//If the value was removed from the model, then remove the element from the XML
- else{
+ else {
$(objectDOM).children(nodeName).remove();
}
+ },
+ this,
+ );
+
+ //Update the image element
+ if (
+ this.get("image") &&
+ typeof this.get("image").updateDOM == "function"
+ ) {
+ var imageSerialized = this.get("image").updateDOM();
+
+ this.addUpdatedXMLNode(objectDOM, imageSerialized);
+ } else {
+ $(objectDOM).children("image").remove();
+ }
- }, this);
-
- //Update the image element
- if( this.get("image") && typeof this.get("image").updateDOM == "function" ){
+ // Get markdown which is a child of content
+ var content = this.get("content");
- var imageSerialized = this.get("image").updateDOM();
+ if (content) {
+ var contentSerialized = content.updateDOM("content");
- this.addUpdatedXMLNode(objectDOM, imageSerialized);
- }
- else{
- $(objectDOM).children("image").remove();
- }
+ this.addUpdatedXMLNode(objectDOM, contentSerialized);
+ } else {
+ $(objectDOM).children("content").remove();
+ }
- // Get markdown which is a child of content
- var content = this.get("content");
+ //If nothing was serialized, return an empty string
+ if (!$(objectDOM).children().length) {
+ return "";
+ }
- if(content){
- var contentSerialized = content.updateDOM("content");
+ return objectDOM;
+ },
- this.addUpdatedXMLNode(objectDOM, contentSerialized);
+ /**
+ * Takes the updated XML node and inserts it into the given object DOM in
+ * the correct position.
+ * @param {Element} objectDOM - The full object DOM for this model
+ * @param {Element} newElement - The updated element to insert into the object DOM
+ */
+ addUpdatedXMLNode: function (objectDOM, newElement) {
+ //If a parameter is missing, don't do anything
+ if (!objectDOM || !newElement) {
+ return;
+ }
- }
- else{
- $(objectDOM).children("content").remove();
- }
+ try {
+ //Get the node name of the new element
+ var nodeName = $(newElement)[0].nodeName;
+
+ if (nodeName) {
+ //Only insert the new element if there is content in it
+ if (
+ $(newElement).children().length ||
+ $(newElement).text().length
+ ) {
+ //Add the new element to the owner Document
+ objectDOM.ownerDocument.adoptNode(newElement);
+
+ //Get the existing node
+ var existingNodes = $(objectDOM).children(nodeName);
+
+ //Get the position that the image should be
+ var insertAfter = this.getXMLPosition(objectDOM, nodeName);
+
+ if (insertAfter) {
+ //Insert it into that position
+ $(insertAfter).after(newElement);
+ } else {
+ objectDOM.appendChild(newElement);
+ }
- //If nothing was serialized, return an empty string
- if( !$(objectDOM).children().length ){
- return "";
+ existingNodes.remove();
+ }
}
+ } catch (e) {
+ console.log(e);
+ return;
+ }
+ },
- return objectDOM;
-
- },
-
- /**
- * Takes the updated XML node and inserts it into the given object DOM in
- * the correct position.
- * @param {Element} objectDOM - The full object DOM for this model
- * @param {Element} newElement - The updated element to insert into the object DOM
- */
- addUpdatedXMLNode: function(objectDOM, newElement){
+ /**
+ * Finds the node in the given portal XML document afterwhich the
+ * given node type should be inserted
+ *
+ * @param {Element} parentNode - The parent XML element
+ * @param {string} nodeName - The name of the node to be inserted
+ * into xml
+ * @return {(jQuery|boolean)} A jQuery object indicating a position,
+ * or false when nodeName is not in the
+ * portal schema
+ */
+ getXMLPosition: function (parentNode, nodeName) {
+ var nodeOrder = [
+ "label",
+ "title",
+ "introduction",
+ "image",
+ "content",
+ "option",
+ ];
+
+ var position = _.indexOf(nodeOrder, nodeName);
+
+ // First check that nodeName is in the list of nodes
+ if (position == -1) {
+ return false;
+ }
- //If a parameter is missing, don't do anything
- if( !objectDOM || !newElement ){
- return;
+ // If there's already an occurence of nodeName...
+ if ($(parentNode).children(nodeName).length > 0) {
+ // ...insert it after the last occurence
+ return $(parentNode).children(nodeName).last();
+ } else {
+ // Go through each node in the node list and find the position
+ // after which this node will be inserted
+ for (var i = position - 1; i >= 0; i--) {
+ if ($(parentNode).children(nodeOrder[i]).length) {
+ return $(parentNode).children(nodeOrder[i]).last();
+ }
}
+ }
- try{
- //Get the node name of the new element
- var nodeName = $(newElement)[0].nodeName;
-
- if( nodeName ){
-
- //Only insert the new element if there is content in it
- if( $(newElement).children().length || $(newElement).text().length ){
-
- //Add the new element to the owner Document
- objectDOM.ownerDocument.adoptNode(newElement);
+ return false;
+ },
- //Get the existing node
- var existingNodes = $(objectDOM).children(nodeName);
+ /**
+ * Overrides the default Backbone.Model.validate.function() to
+ * check if this PortalSection model has all the required values necessary
+ * to save to the server.
+ *
+ * @return {Object} If there are errors, an object comprising error
+ * messages. If no errors, returns nothing.
+ */
+ validate: function () {
+ try {
+ var errors = {},
+ requiredFields = MetacatUI.appModel.get(
+ "portalEditorRequiredFields",
+ );
+
+ //--Validate the label--
+ //Labels are always required
+ if (!this.get("label")) {
+ errors.label = "Please provide a page name.";
+ }
- //Get the position that the image should be
- var insertAfter = this.getXMLPosition(objectDOM, nodeName);
+ //---Validate the title---
+ //If section titles are required and there isn't one, set an error message
+ if (
+ requiredFields.sectionTitle &&
+ typeof this.get("title") == "string" &&
+ !this.get("title").length
+ ) {
+ errors.title = "Please provide a title for this page.";
+ }
- if( insertAfter ){
- //Insert it into that position
- $(insertAfter).after(newElement);
- } else {
- objectDOM.appendChild(newElement);
- }
+ //---Validate the introduction---
+ //If section introductions are required and there isn't one, set an error message
+ if (
+ requiredFields.sectionIntroduction &&
+ typeof this.get("introduction") == "string" &&
+ !this.get("introduction").length
+ ) {
+ errors.introduction =
+ "Please provide some a sub-title or some introductory text for this page.";
+ }
- existingNodes.remove();
- }
- }
+ //---Validate the section content---
+ //Content is always required
+ if (!this.get("content")) {
+ errors.markdown = "Please provide content for this page.";
}
- catch(e){
- console.log(e);
- return;
+ //Check if there is either markdown or an array of strings in the text attribute
+ else if (
+ !this.get("content").get("markdown") &&
+ !this.get("content").get("text").length
+ ) {
+ errors.markdown = "Please provide content for this page.";
}
-
- },
-
- /**
- * Finds the node in the given portal XML document afterwhich the
- * given node type should be inserted
- *
- * @param {Element} parentNode - The parent XML element
- * @param {string} nodeName - The name of the node to be inserted
- * into xml
- * @return {(jQuery|boolean)} A jQuery object indicating a position,
- * or false when nodeName is not in the
- * portal schema
- */
- getXMLPosition: function(parentNode, nodeName){
-
- var nodeOrder = [ "label", "title", "introduction", "image", "content", "option"];
-
- var position = _.indexOf(nodeOrder, nodeName);
-
- // First check that nodeName is in the list of nodes
- if ( position == -1 ) {
- return false;
- };
-
- // If there's already an occurence of nodeName...
- if($(parentNode).children(nodeName).length > 0){
- // ...insert it after the last occurence
- return $(parentNode).children(nodeName).last();
- } else {
- // Go through each node in the node list and find the position
- // after which this node will be inserted
- for (var i = position - 1; i >= 0; i--) {
- if ( $(parentNode).children(nodeOrder[i]).length ) {
- return $(parentNode).children(nodeOrder[i]).last();
- }
- }
+ //Check if the markdown hasn't been changed from the example markdown
+ else if (
+ this.get("content").get("markdown") ==
+ this.get("content").get("markdownExample")
+ ) {
+ errors.markdown = "Please provide content for this page.";
}
- return false;
- },
-
- /**
- * Overrides the default Backbone.Model.validate.function() to
- * check if this PortalSection model has all the required values necessary
- * to save to the server.
- *
- * @return {Object} If there are errors, an object comprising error
- * messages. If no errors, returns nothing.
- */
- validate: function(){
-
- try{
-
- var errors = {},
- requiredFields = MetacatUI.appModel.get("portalEditorRequiredFields");
-
- //--Validate the label--
- //Labels are always required
- if( !this.get("label") ){
- errors.label = "Please provide a page name.";
- }
-
- //---Validate the title---
- //If section titles are required and there isn't one, set an error message
- if( requiredFields.sectionTitle &&
- typeof this.get("title") == "string" &&
- !this.get("title").length ){
- errors.title = "Please provide a title for this page.";
- }
-
- //---Validate the introduction---
- //If section introductions are required and there isn't one, set an error message
- if( requiredFields.sectionIntroduction &&
- typeof this.get("introduction") == "string" &&
- !this.get("introduction").length ){
- errors.introduction = "Please provide some a sub-title or some introductory text for this page.";
- }
-
- //---Validate the section content---
- //Content is always required
- if( !this.get("content") ){
- errors.markdown = "Please provide content for this page.";
- }
- //Check if there is either markdown or an array of strings in the text attribute
- else if( !this.get("content").get("markdown") && !this.get("content").get("text").length ){
- errors.markdown = "Please provide content for this page.";
- }
- //Check if the markdown hasn't been changed from the example markdown
- else if( this.get("content").get("markdown") == this.get("content").get("markdownExample") ){
- errors.markdown = "Please provide content for this page.";
- }
-
- //---Validate the section image---
-
- if(requiredFields.sectionImage && (!this.get("image") || this.get("image").isEmpty())){
- errors.sectionImage = "A section image is required."
- }
-
- //Return the errors object
- if( Object.keys(errors).length )
- return errors;
- else{
- return;
- }
+ //---Validate the section image---
+ if (
+ requiredFields.sectionImage &&
+ (!this.get("image") || this.get("image").isEmpty())
+ ) {
+ errors.sectionImage = "A section image is required.";
}
- catch(e){
- console.error(e);
+
+ //Return the errors object
+ if (Object.keys(errors).length) return errors;
+ else {
return;
}
-
+ } catch (e) {
+ console.error(e);
+ return;
}
+ },
+ },
+ );
-
- });
-
- return PortalSectionModel;
+ return PortalSectionModel;
});
diff --git a/src/js/models/portals/PortalVizSectionModel.js b/src/js/models/portals/PortalVizSectionModel.js
index 490f214a7..d28a0c100 100644
--- a/src/js/models/portals/PortalVizSectionModel.js
+++ b/src/js/models/portals/PortalVizSectionModel.js
@@ -1,195 +1,197 @@
/* global define */
-define(["jquery",
- "underscore",
- "backbone",
+define([
+ "jquery",
+ "underscore",
+ "backbone",
"models/portals/PortalSectionModel",
- "models/maps/Map"
- ],
- function ($, _, Backbone, PortalSectionModel, Map) {
+ "models/maps/Map",
+], function ($, _, Backbone, PortalSectionModel, Map) {
+ /**
+ * @class PortalVizSectionModel
+ * @classdesc A Portal Section for Data Visualizations. This is still an experimental feature and not recommended for general use.
+ * @classcategory Models/Portals
+ * @extends PortalSectionModel
+ * @private
+ */
+ var PortalVizSectionModel = PortalSectionModel.extend(
+ /** @lends PortalVizSectionModel.prototype */ {
+ type: "PortalVizSection",
+
+ defaults: function () {
+ return _.extend(PortalSectionModel.prototype.defaults(), {
+ sectionType: "visualization",
+ visualizationType: "",
+ supportedVisualizationTypes: ["fever", "cesium"],
+ });
+ },
/**
- * @class PortalVizSectionModel
- * @classdesc A Portal Section for Data Visualizations. This is still an experimental feature and not recommended for general use.
- * @classcategory Models/Portals
- * @extends PortalSectionModel
- * @private
+ * Parses a element from a portal document
+ *
+ * @param {XMLElement} objectDOM - A ContentSectionType XML element from a portal document
+ * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
*/
- var PortalVizSectionModel = PortalSectionModel.extend(
- /** @lends PortalVizSectionModel.prototype */{
-
- type: "PortalVizSection",
-
- defaults: function(){
- return _.extend(PortalSectionModel.prototype.defaults(), {
- sectionType: "visualization",
- visualizationType: "",
- supportedVisualizationTypes: ["fever", "cesium"]
- });
- },
-
- /**
- * Parses a element from a portal document
- *
- * @param {XMLElement} objectDOM - A ContentSectionType XML element from a portal document
- * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
- */
- parse: function(objectDOM){
-
- if(!objectDOM){
- return {};
- }
+ parse: function (objectDOM) {
+ if (!objectDOM) {
+ return {};
+ }
- //Create a jQuery object of the XML DOM
- var $objectDOM = $(objectDOM),
+ //Create a jQuery object of the XML DOM
+ var $objectDOM = $(objectDOM),
//Parse the XML using the parent class, PortalSectionModel.parse()
- modelJSON = this.constructor.__super__.parse(objectDOM);
+ modelJSON = this.constructor.__super__.parse(objectDOM);
- //Parse the visualization type
- var allOptions = $objectDOM.children("option"),
- vizType = "";
+ //Parse the visualization type
+ var allOptions = $objectDOM.children("option"),
+ vizType = "";
- var vizTypeNode = allOptions.find("optionName:contains(visualizationType)");
- if( vizTypeNode.length ){
- vizType = vizTypeNode.first().siblings("optionValue").text();
+ var vizTypeNode = allOptions.find(
+ "optionName:contains(visualizationType)",
+ );
+ if (vizTypeNode.length) {
+ vizType = vizTypeNode.first().siblings("optionValue").text();
- //Right now, only support "fever" as a visualization type, until this feature is expanded.
- if(vizType == "fever"){
+ //Right now, only support "fever" as a visualization type, until this feature is expanded.
+ if (vizType == "fever") {
// modelJSON.visualizationType = "fever";
- }
+ }
- var vizTypes = this.get("supportedVisualizationTypes");
- if( Array.isArray(vizTypes) && vizTypes.includes(vizType) ){
- modelJSON.visualizationType = vizType;
- }
+ var vizTypes = this.get("supportedVisualizationTypes");
+ if (Array.isArray(vizTypes) && vizTypes.includes(vizType)) {
+ modelJSON.visualizationType = vizType;
+ }
- // Find the map configuration JSON in the section option, if there is one.
- if (vizType == "cesium") {
- var mapConfigNode = allOptions.find("optionName:contains(mapConfig)");
- var mapConfig = {};
- if (mapConfigNode.length) {
- mapConfig = mapConfigNode.first().siblings("optionValue").text();
- if (mapConfig && mapConfig.length) {
- mapConfig = JSON.parse(mapConfig)
- }
+ // Find the map configuration JSON in the section option, if there is one.
+ if (vizType == "cesium") {
+ var mapConfigNode = allOptions.find(
+ "optionName:contains(mapConfig)",
+ );
+ var mapConfig = {};
+ if (mapConfigNode.length) {
+ mapConfig = mapConfigNode.first().siblings("optionValue").text();
+ if (mapConfig && mapConfig.length) {
+ mapConfig = JSON.parse(mapConfig);
}
- modelJSON.mapModel = new Map(mapConfig)
}
-
+ modelJSON.mapModel = new Map(mapConfig);
}
+ }
- return modelJSON;
-
- },
-
- /**
- * Makes a copy of the original XML DOM and updates it with the new values from the model.
- * For now, this function only updates the label. All other parts of Viz sections are not editable
- * in MetacatUI, since this is still an experimental feature.
- *
- * @return {XMLElement} An updated ContentSectionType XML element from a portal document
- */
- updateDOM: function(){
+ return modelJSON;
+ },
- var objectDOM = this.get("objectDOM");
+ /**
+ * Makes a copy of the original XML DOM and updates it with the new values from the model.
+ * For now, this function only updates the label. All other parts of Viz sections are not editable
+ * in MetacatUI, since this is still an experimental feature.
+ *
+ * @return {XMLElement} An updated ContentSectionType XML element from a portal document
+ */
+ updateDOM: function () {
+ var objectDOM = this.get("objectDOM");
- //Clone the DOM if it exists already
- if (objectDOM) {
- objectDOM = objectDOM.cloneNode(true);
+ //Clone the DOM if it exists already
+ if (objectDOM) {
+ objectDOM = objectDOM.cloneNode(true);
//Or create a new DOM
- } else {
- // create an XML section element from scratch
- var xmlText = " FEVer visualization sectionType visualization " +
- "visualizationType fever ",
- objectDOM = new DOMParser().parseFromString(xmlText, "text/xml"),
- objectDOM = $(objectDOM).children()[0];
- };
-
- // Get and update the simple text strings (everything but content)
- var sectionTextData = {
- label: this.get("label")
- };
+ } else {
+ // create an XML section element from scratch
+ var xmlText =
+ " FEVer visualization sectionType visualization " +
+ "visualizationType fever ",
+ objectDOM = new DOMParser().parseFromString(xmlText, "text/xml"),
+ objectDOM = $(objectDOM).children()[0];
+ }
- _.map(sectionTextData, function(value, nodeName){
+ // Get and update the simple text strings (everything but content)
+ var sectionTextData = {
+ label: this.get("label"),
+ };
+ _.map(
+ sectionTextData,
+ function (value, nodeName) {
// Don't serialize default values, except for default label strings, since labels are required
- if(value && (value != this.defaults()[nodeName] || (nodeName == "label" && typeof value == "string")) ){
+ if (
+ value &&
+ (value != this.defaults()[nodeName] ||
+ (nodeName == "label" && typeof value == "string"))
+ ) {
// Make new sub-node
- var sectionSubnodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
+ var sectionSubnodeSerialized =
+ objectDOM.ownerDocument.createElement(nodeName);
$(sectionSubnodeSerialized).text(value);
this.addUpdatedXMLNode(objectDOM, sectionSubnodeSerialized);
}
//If the value was removed from the model, then remove the element from the XML
- else{
+ else {
$(objectDOM).children(nodeName).remove();
}
-
- }, this);
-
- //Make sure the content element is valid
- var contentEl = $(objectDOM).children("content");
- if( contentEl.length ){
- //If there is content in the content element
- if( contentEl[0].childNodes.length ){
- //If there is only text in the element, we need to wrap it in a element so it's schema valid
- if( contentEl[0].childNodes[0].nodeType == 3 ){
- $(contentEl[0]).html("" + contentEl[0].childNodes[0].textContent + " ");
- }
- }
- }
-
- //If nothing was serialized, return an empty string
- if( !$(objectDOM).children().length ){
- return "";
+ },
+ this,
+ );
+
+ //Make sure the content element is valid
+ var contentEl = $(objectDOM).children("content");
+ if (contentEl.length) {
+ //If there is content in the content element
+ if (contentEl[0].childNodes.length) {
+ //If there is only text in the element, we need to wrap it in a element so it's schema valid
+ if (contentEl[0].childNodes[0].nodeType == 3) {
+ $(contentEl[0]).html(
+ "