diff --git a/docs/_includes/nav.html b/docs/_includes/nav.html
index f3bc2a142..4c24f8c51 100644
--- a/docs/_includes/nav.html
+++ b/docs/_includes/nav.html
@@ -8,6 +8,7 @@
Themes
Create a custom theme
Images
Citations
+ Funding Autocomplete
API
Configuration
diff --git a/docs/guides/editor/funding-autocomplete.md b/docs/guides/editor/funding-autocomplete.md
new file mode 100644
index 000000000..df3bad123
--- /dev/null
+++ b/docs/guides/editor/funding-autocomplete.md
@@ -0,0 +1,87 @@
+# Setting Up a Proxy for NSF Award API in MetacatUI
+
+MetacatUI integrates with the NSF (National Science Foundation) Award API to fetch award information. Since the NSF Award API does not support CORS (Cross-Origin Resource Sharing) or JSONP (JSON with Padding), it's necessary to set up a server-side proxy. This documentation guides you through setting up an Apache proxy to enable this functionality in MetacatUI.
+
+![NSF Award API Proxy](guides/images/funding.png)
+
+## Prerequisites
+
+- Apache Web Server
+- Access to Apache configuration files
+- MetacatUI already installed and served via Apache
+
+## Steps to Configure Apache as a Proxy
+
+### 1. Enable Required Apache Modules
+
+Ensure that the following Apache modules are enabled:
+
+- `mod_proxy`
+- `mod_proxy_http`
+
+You can enable them by running the following commands:
+
+```bash
+sudo a2enmod proxy
+sudo a2enmod proxy_http
+```
+
+Then, restart Apache to apply the changes:
+
+```bash
+sudo systemctl restart apache2
+```
+
+### 2. Configure Apache Virtual Host
+
+Edit your Apache virtual host configuration file where MetacatUI is served. This file is typically located in `/etc/apache2/sites-available/`.
+
+Add the following configuration inside the `` block:
+
+```apache
+# NSF Award API Proxy Configuration
+ProxyPass "/research.gov/awardapi-service/v1/awards.json" "https://www.research.gov/awardapi-service/v1/awards.json"
+ProxyPassReverse "/research.gov/awardapi-service/v1/awards.json" "https://www.research.gov/awardapi-service/v1/awards.json"
+```
+
+Replace `"https://www.research.gov/awardapi-service/v1/awards.json"` with the actual URL of the NSF Award API if it's different. You may also use a different proxy path if you prefer (other than `/research.gov/awardapi-service/v1/awards.json`), but make sure to update the proxy path in the `grantsUrl` property of the MetacatUI configuration as well.
+
+### 3. Restart Apache
+
+After editing the configuration file, restart Apache to apply the new settings:
+
+```bash
+sudo systemctl restart apache2
+```
+
+## Update MetacatUI Configuration
+
+The last step is to update the MetacatUI configuration to use the proxy path, if you used a different proxy path than the default one. The default path is `/research.gov/awardapi-service/v1/awards.json`, relative to the domain on which your MetacatUI is served. If you used a different proxy path, you will need to update the `grantsUrl` property in the MetacatUI configuration:
+
+```javascript
+grantsUrl: "/research.gov/awardapi-service/v1/awards.json";
+```
+
+Ensure that you also have the funding lookup feature enabled in the MetacatUI configuration by setting the `fundingLookup` property to `true`. It defaults to `false`.
+
+```javascript
+useNSFAwardAPI: true;
+```
+
+## Testing
+
+Ensure that the proxy is correctly set up by accessing the following URL in your browser:
+
+```
+[your MetacatUI domain]/research.gov/awardapi-service/v1/awards.json
+```
+
+You should see the JSON response from the NSF Award API.
+
+## Conclusion
+
+By following these steps, you set up an Apache proxy to enable the NSF award lookup feature in MetacatUI. Ensure you test the configuration to confirm everything is working as expected.
+
+## Additional Notes
+
+- Each MetacatUI installation that wants to use the NSF award lookup feature will need to set up its own proxy.
\ No newline at end of file
diff --git a/docs/guides/images/funding.png b/docs/guides/images/funding.png
new file mode 100644
index 000000000..531eaa9c8
Binary files /dev/null and b/docs/guides/images/funding.png differ
diff --git a/docs/guides/index.md b/docs/guides/index.md
index 868e856a5..44e1f8780 100644
--- a/docs/guides/index.md
+++ b/docs/guides/index.md
@@ -8,5 +8,6 @@ of your MetacatUI application.
- đ Catalog Search View
- đ Cesium Map
- đ Cesium Map for Portals
+- đ Funding Autocomplete
âšī¸ Is something missing? [Email us](mailto:metacat-dev@ecoinformatics.org) or join us on [Slack](https://slack.dataone.org/) and we'll add it!
\ No newline at end of file
diff --git a/src/css/metacatui-common.css b/src/css/metacatui-common.css
index f786db5ce..6b167e72d 100644
--- a/src/css/metacatui-common.css
+++ b/src/css/metacatui-common.css
@@ -8322,11 +8322,11 @@ textarea.medium{
position: relative;
}
.Editor .funding-container .icon-spinner{
+ position: absolute;
+ left: 11px;
top: 5px;
- left: 45px;
- position: absolute;
- font-size: 1.5em;
- display: none;
+ font-size: 1.5em;
+ display: none;
}
.metadata-container #funding-visible{
padding-left: 30px;
diff --git a/src/js/models/AppModel.js b/src/js/models/AppModel.js
index 498b5e496..8701b1f18 100644
--- a/src/js/models/AppModel.js
+++ b/src/js/models/AppModel.js
@@ -720,11 +720,13 @@ define(['jquery', 'underscore', 'backbone'],
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
+ * 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 "https://api.nsf.gov/services/v1/awards.json"
+ * @default "/research.gov/awardapi-service/v1/awards.json"
*/
- grantsUrl: "https://api.nsf.gov/services/v1/awards.json",
+ grantsUrl: "/research.gov/awardapi-service/v1/awards.json",
/**
* The base URL for the ORCID REST services
diff --git a/src/js/models/LookupModel.js b/src/js/models/LookupModel.js
index 8ef1e1745..d65573b4e 100644
--- a/src/js/models/LookupModel.js
+++ b/src/js/models/LookupModel.js
@@ -1,575 +1,637 @@
/*global define */
-define(['jquery', 'jqueryui', 'underscore', 'backbone'],
- function($, $ui, _, Backbone) {
- 'use strict';
-
- /**
- * @class LookupModel
- * @classdesc A uttility model that contains functions for looking up values from various services
- * @classcategory Models
- */
- var LookupModel = Backbone.Model.extend(
- /** @lends LookupModel.prototype */{
- defaults: {
- concepts: {}
- },
-
- initialize: function() {
-
-
-
- },
-
- bioportalSearch: function(request, response, localValues, allValues) {
-
- // make sure we have something to lookup
- if (!MetacatUI.appModel.get('bioportalAPIKey')) {
- response(localValues);
- return;
- }
-
- var query = MetacatUI.appModel.get('bioportalSearchUrl') +
- "?q=" + request.term +
- "&apikey=" + MetacatUI.appModel.get("bioportalAPIKey") +
- "&ontologies=ECSO&pagesize=1000&suggest=true";
- var availableTags = [];
- $.get(query, function(data, textStatus, xhr) {
-
- _.each(data.collection, function(obj) {
- var choice = {};
- choice.label = obj['prefLabel'];
- var synonyms = obj['synonym'];
- if (synonyms) {
- choice.synonyms = [];
- _.each(synonyms, function(synonym) {
- choice.synonyms.push(synonym);
- });
- }
- choice.filterLabel = obj['prefLabel'];
- choice.value = obj['@id'];
- if (obj['definition']) {
- choice.desc = obj['definition'][0];
- }
-
- // mark items that we know we have matches for
- if (allValues) {
- var matchingChoice = _.findWhere(allValues, {value: choice.value});
- if (matchingChoice) {
- //choice.label = "*" + choice.label;
- choice.match = true;
-
- // remove it from the local value - why have two?
- if (localValues) {
- localValues = _.reject(localValues, function(obj) {
- return obj.value == matchingChoice.value;
- });
- }
- //availableTags.push(choice);
- }
- }
-
- availableTags.push(choice);
-
- });
-
- // combine the lists if called that way
- if (localValues) {
- availableTags = localValues.concat(availableTags);
- }
-
- response(availableTags);
-
- });
- },
-
- bioportalExpand: function(term) {
-
- // make sure we have something to lookup
- if (!MetacatUI.appModel.get('bioportalAPIKey')) {
- response(null);
- return;
- }
-
- var terms = [];
- var countdown = 0;
-
- var query = MetacatUI.appModel.get('bioportalSearchUrl') +
- "?q=" + term +
- "&apikey=" + MetacatUI.appModel.get("bioportalAPIKey") +
- "&ontologies=ECSO&pagesize=1000&suggest=true";;
- $.ajax(
- {
- url: query,
- method: "GET",
- async: false, // we want to wait for the response!
- success: function(data, textStatus, xhr) {
-
- _.each(data.collection, function(obj) {
- // use the preferred label
- var prefLabel = obj['prefLabel'];
- terms.push(prefLabel);
-
- // add the synonyms
- var synonyms = obj['synonym'];
- if (synonyms) {
- _.each(synonyms, function(synonym) {
- terms.push(synonym);
- });
- }
- // process the descendants
- var descendantsUrl = obj['links']['descendants'];
- //if (false) {
- if (descendantsUrl && countdown > 0) {
-
- countdown--;
-
- $.ajax(
- {
- url: descendantsUrl + "?apikey=" + MetacatUI.appModel.get("bioportalAPIKey"),
- method: "GET",
- async: false,
- success: function(data, textStatus, xhr) {
- _.each(data.collection, function(obj) {
- var prefLabel = obj['prefLabel'];
- var synonyms = obj['synonym'];
- if (synonyms) {
- _.each(synonyms, function(synonym) {
- terms.push(synonym);
- });
- }
- });
- }
- });
- }
- });
- }
- });
- return terms;
- },
-
- bioportalGetConcepts: function(uri, callback) {
-
- var concepts = this.get('concepts')[uri];
-
- if (concepts) {
- callback(concepts);
- return;
- } else {
- concepts = [];
- }
-
- // make sure we have something to lookup
- if (!MetacatUI.appModel.get('bioportalAPIKey')) {
- return;
- }
-
- var query = MetacatUI.appModel.get('bioportalSearchUrl') +
- "?q=" + encodeURIComponent(uri) +
- "&apikey=" + MetacatUI.appModel.get("bioportalAPIKey") +
- "&ontologies=ECSO&pagesize=1000&suggest=true";
- var availableTags = [];
- var model = this;
- $.get(query, function(data, textStatus, xhr) {
-
- _.each(data.collection, function(obj) {
- var concept = {};
- concept.label = obj['prefLabel'];
- concept.value = obj['@id'];
- if (obj['definition']) {
- concept.desc = obj['definition'][0];
- }
- // add the synonyms
- var synonyms = obj['synonym'];
- if (synonyms) {
- concept.synonyms = [];
- _.each(synonyms, function(synonym) {
- concept.synonyms.push(synonym);
- });
- }
-
- concepts.push(concept);
-
- });
- model.get('concepts')[uri] = concepts;
-
- callback(concepts);
- });
- },
-
- bioportalGetConceptsBatch: function(uris, callback) {
-
- // make sure we have something to lookup
- if (!MetacatUI.appModel.get('bioportalBatchUrl')) {
- return;
- }
- // prepare the request JSON
- var batchData = {};
- batchData["http://www.w3.org/2002/07/owl#Class"] = {};
- batchData["http://www.w3.org/2002/07/owl#Class"]["display"] = "prefLabel,synonym,definition";
- batchData["http://www.w3.org/2002/07/owl#Class"]["collection"] = [];
- _.each(uris, function(uri) {
- var item = {};
- item["class"] = uri;
- item["ontology"] = "http://data.bioontology.org/ontologies/ECSO";
- batchData["http://www.w3.org/2002/07/owl#Class"]["collection"].push(item);
- });
-
- var url = MetacatUI.appModel.get('bioportalBatchUrl');
- var model = this;
- $.ajax(url,
- {
- method: "POST",
- //url: url,
- data: JSON.stringify(batchData),
- contentType: "application/json",
- headers: {
- "Authorization": "apikey token="+ MetacatUI.appModel.get("bioportalAPIKey")
- },
- error: function(e) {
- console.log(e);
- },
- success: function(data, textStatus, xhr) {
-
- _.each(data["http://www.w3.org/2002/07/owl#Class"], function(obj) {
- var concept = {};
- concept.label = obj['prefLabel'];
- concept.value = obj['@id'];
- if (obj['definition']) {
- concept.desc = obj['definition'][0];
- }
- // add the synonyms
- var synonyms = obj['synonym'];
- if (synonyms) {
- concept.synonyms = [];
- _.each(synonyms, function(synonym) {
- concept.synonyms.push(synonym);
- });
- }
-
- var conceptList = [];
- conceptList.push(concept);
- model.get('concepts')[concept.value] = conceptList;
-
- });
-
- callback.apply();
- }
- });
-
- },
-
- orcidGetConcepts: function(uri, callback) {
-
- var people = this.get('concepts')[uri];
-
- if (people) {
- callback(people);
- return;
- } else {
- people = [];
- }
-
- var query = MetacatUI.appModel.get('orcidBaseUrl') + uri.substring(uri.lastIndexOf("/"));
- var model = this;
- $.get(query, function(data, status, xhr) {
- // get the orcid info
- var profile = $(data).find("orcid-profile");
-
- _.each(profile, function(obj) {
- var choice = {};
- choice.label = $(obj).find("orcid-bio > personal-details > given-names").text() + " " + $(obj).find("orcid-bio > personal-details > family-name").text();
- choice.value = $(obj).find("orcid-identifier > uri").text();
- choice.desc = $(obj).find("orcid-bio > personal-details").text();
- people.push(choice);
- });
-
- model.get('concepts')[uri] = people;
-
- // callback with answers
- callback(people);
- })
- },
-
- /*
- * Supplies search results for ORCiDs to autocomplete UI elements
- */
- orcidSearch: function(request, response, more, ignore) {
-
- var people = [];
-
- if(!ignore) var ignore = [];
-
- var query = MetacatUI.appModel.get('orcidSearchUrl') + request.term;
- $.get(query, function(data, status, xhr) {
- // get the orcid info
- var profile = $(data).find("orcid-profile");
-
- _.each(profile, function(obj) {
- var choice = {};
- choice.value = $(obj).find("orcid-identifier > uri").text();
-
- if(_.contains(ignore, choice.value.toLowerCase())) return;
-
- choice.label = $(obj).find("orcid-bio > personal-details > given-names").text() + " " + $(obj).find("orcid-bio > personal-details > family-name").text();
- choice.desc = $(obj).find("orcid-bio > personal-details").text();
- people.push(choice);
- });
-
- // add more if called that way
- if (more) {
- people = more.concat(people);
- }
-
- // callback with answers
- response(people);
- });
- },
-
- /*
- * Gets the bio of a person given an ORCID
- * Updates the given user model with the bio info from ORCID
- */
- orcidGetBio: function(options){
- if(!options || !options.userModel) return;
-
- var orcid = options.userModel.get("username"),
- onSuccess = options.success || function(){},
- onError = options.error || function(){};
-
- $.ajax({
- url: MetacatUI.appModel.get("orcidSearchUrl") + orcid,
- type: "GET",
- //accepts: "application/orcid+json",
- success: function(data, textStatus, xhr){
- // get the orcid info
- var orcidNode = $(data).find("path:contains(" + orcid + ")"),
- profile = orcidNode.length? $(orcidNode).parents("orcid-profile") : [];
-
- if(!profile.length) return;
-
- var fullName = $(profile).find("orcid-bio > personal-details > given-names").text() + " " + $(profile).find("orcid-bio > personal-details > family-name").text();
- options.userModel.set("fullName", fullName);
-
- onSuccess(data, textStatus, xhr);
- },
- error: function(xhr, textStatus, error){
- onError(xhr, textStatus, error);
- }
- });
- },
-
- getGrantAutocomplete: function(request, response){
- var term = $.ui.autocomplete.escapeRegex(request.term),
- filterBy = "";
-
- //Only search after 3 characters or more
- if(term.length < 3) return;
- else if(term.match(/\d/)) return; //Don't search for digit only since it's most likely a user just entering the grant number directy
- else filterBy = "keyword";
-
- var url = MetacatUI.appModel.get("grantsUrl") + "?" + filterBy + "=" + term + "&printFields=title,id";
-
- // Send the AJAX request as a JSONP data type since it will be cross-origin
- var requestSettings = {
- url: url,
- dataType: "jsonp",
- success: function(data, textStatus, xhr) {
-
- // Handle the response from the NSF Award Search API and
- //transform each award query result into a jQueryUI autocomplete item
-
- if(!data || !data.response || !data.response.award) return [];
-
- var list = [];
-
- _.each(data.response.award, function(award, i){
- list.push({
- value: award.id,
- label: award.title
- });
- });
-
- 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);
- })
- , containsMatcher = new RegExp(term, "i")
- , contains = $.grep(list, function (value) {
- return $.inArray(value, startsWith) < 0 &&
- containsMatcher.test(value.label || value.value || value);
- });
-
- response(startsWith.concat(contains));
- }
- }
-
- //Send the query
- $.ajax(requestSettings);
- },
-
- getGrant: function(id, onSuccess, onError){
- if(!id || !onSuccess || !MetacatUI.appModel.get("useNSFAwardAPI") || !MetacatUI.appModel.get("grantsUrl")) return;
-
- var requestSettings = {
- url: MetacatUI.appModel.get("grantsUrl") + "?id=" + id,
- success: function(data, textStatus, xhr){
- if(!data || !data.response || !data.response.award || !data.response.award.length){
- if(onError) onError();
- return;
- }
-
- onSuccess(data.response.award[0]);
- },
- error: function(){
- if(onError) onError();
- }
- }
-
- //Send the query
- $.ajax(requestSettings);
- },
-
- getAccountsAutocomplete: function(request, response){
- var searchTerm = $.ui.autocomplete.escapeRegex(request.term);
-
- //Only search after 2 characters or more
- if(searchTerm.length < 2)
- return;
-
- var url = MetacatUI.appModel.get("accountsUrl") + "?query=" + searchTerm;
-
- // Send the AJAX request as a JSONP data type since it will be cross-origin
- var requestSettings = {
- url: url,
- success: function(data, textStatus, xhr) {
-
- if(!data)
- return [];
-
- //If an XML doc was not returned from the server, then try to parse the response as XML
- if( !XMLDocument.prototype.isPrototypeOf(data) ){
- try{
- data = $.parseXML(data);
+define(["jquery", "jqueryui", "underscore", "backbone"], function (
+ $,
+ $ui,
+ _,
+ Backbone
+) {
+ "use strict";
+
+ /**
+ * @class LookupModel
+ * @classdesc A utility model that contains functions for looking up values
+ * from various services
+ * @classcategory Models
+ */
+ var LookupModel = Backbone.Model.extend(
+ /** @lends LookupModel.prototype */ {
+ defaults: {
+ concepts: {},
+ },
+
+ initialize: function () {},
+
+ bioportalSearch: function (request, response, localValues, allValues) {
+ // make sure we have something to lookup
+ if (!MetacatUI.appModel.get("bioportalAPIKey")) {
+ response(localValues);
+ return;
+ }
+
+ var query =
+ MetacatUI.appModel.get("bioportalSearchUrl") +
+ "?q=" +
+ request.term +
+ "&apikey=" +
+ MetacatUI.appModel.get("bioportalAPIKey") +
+ "&ontologies=ECSO&pagesize=1000&suggest=true";
+ var availableTags = [];
+ $.get(query, function (data, textStatus, xhr) {
+ _.each(data.collection, function (obj) {
+ var choice = {};
+ choice.label = obj["prefLabel"];
+ var synonyms = obj["synonym"];
+ if (synonyms) {
+ choice.synonyms = [];
+ _.each(synonyms, function (synonym) {
+ choice.synonyms.push(synonym);
+ });
}
- catch(e){
- //If the parsing XML failed, exit now
- console.error("The accounts service did not return valid XML.", e);
- return;
+ choice.filterLabel = obj["prefLabel"];
+ choice.value = obj["@id"];
+ if (obj["definition"]) {
+ choice.desc = obj["definition"][0];
}
+
+ // mark items that we know we have matches for
+ if (allValues) {
+ var matchingChoice = _.findWhere(allValues, {
+ value: choice.value,
+ });
+ if (matchingChoice) {
+ //choice.label = "*" + choice.label;
+ choice.match = true;
+
+ // remove it from the local value - why have two?
+ if (localValues) {
+ localValues = _.reject(localValues, function (obj) {
+ return obj.value == matchingChoice.value;
+ });
+ }
+ //availableTags.push(choice);
+ }
+ }
+
+ availableTags.push(choice);
+ });
+
+ // combine the lists if called that way
+ if (localValues) {
+ availableTags = localValues.concat(availableTags);
}
- var list = [];
+ response(availableTags);
+ });
+ },
- _.each($(data).children(/.+subjectInfo/).children(), function(accountNode, i){
+ bioportalExpand: function (term) {
+ // make sure we have something to lookup
+ if (!MetacatUI.appModel.get("bioportalAPIKey")) {
+ response(null);
+ return;
+ }
- var name = "";
- var type = "";
+ var terms = [];
+ var countdown = 0;
+
+ var query =
+ MetacatUI.appModel.get("bioportalSearchUrl") +
+ "?q=" +
+ term +
+ "&apikey=" +
+ MetacatUI.appModel.get("bioportalAPIKey") +
+ "&ontologies=ECSO&pagesize=1000&suggest=true";
+ $.ajax({
+ url: query,
+ method: "GET",
+ async: false, // we want to wait for the response!
+ success: function (data, textStatus, xhr) {
+ _.each(data.collection, function (obj) {
+ // use the preferred label
+ var prefLabel = obj["prefLabel"];
+ terms.push(prefLabel);
+
+ // add the synonyms
+ var synonyms = obj["synonym"];
+ if (synonyms) {
+ _.each(synonyms, function (synonym) {
+ terms.push(synonym);
+ });
+ }
+ // process the descendants
+ var descendantsUrl = obj["links"]["descendants"];
+ //if (false) {
+ if (descendantsUrl && countdown > 0) {
+ countdown--;
+
+ $.ajax({
+ url:
+ descendantsUrl +
+ "?apikey=" +
+ MetacatUI.appModel.get("bioportalAPIKey"),
+ method: "GET",
+ async: false,
+ success: function (data, textStatus, xhr) {
+ _.each(data.collection, function (obj) {
+ var prefLabel = obj["prefLabel"];
+ var synonyms = obj["synonym"];
+ if (synonyms) {
+ _.each(synonyms, function (synonym) {
+ terms.push(synonym);
+ });
+ }
+ });
+ },
+ });
+ }
+ });
+ },
+ });
+ return terms;
+ },
+
+ bioportalGetConcepts: function (uri, callback) {
+ var concepts = this.get("concepts")[uri];
+
+ if (concepts) {
+ callback(concepts);
+ return;
+ } else {
+ concepts = [];
+ }
- if( $(accountNode).children("givenName").length ){
- name = $(accountNode).children("givenName").text() + " " + $(accountNode).children("familyName").text()
- type = "person"
- }
- else{
- name = $(accountNode).children("groupName").text();
- type = "group"
- }
+ // make sure we have something to lookup
+ if (!MetacatUI.appModel.get("bioportalAPIKey")) {
+ return;
+ }
- if( !name ){
- name = $(accountNode).children("subject").text();
- type = "unknown"
+ var query =
+ MetacatUI.appModel.get("bioportalSearchUrl") +
+ "?q=" +
+ encodeURIComponent(uri) +
+ "&apikey=" +
+ MetacatUI.appModel.get("bioportalAPIKey") +
+ "&ontologies=ECSO&pagesize=1000&suggest=true";
+ var availableTags = [];
+ var model = this;
+ $.get(query, function (data, textStatus, xhr) {
+ _.each(data.collection, function (obj) {
+ var concept = {};
+ concept.label = obj["prefLabel"];
+ concept.value = obj["@id"];
+ if (obj["definition"]) {
+ concept.desc = obj["definition"][0];
+ }
+ // add the synonyms
+ var synonyms = obj["synonym"];
+ if (synonyms) {
+ concept.synonyms = [];
+ _.each(synonyms, function (synonym) {
+ concept.synonyms.push(synonym);
+ });
}
- list.push({
- value: $(accountNode).children("subject").text(),
- label: name + " (" + $(accountNode).children("subject").text() + ")",
- type: type
- });
+ concepts.push(concept);
});
+ model.get("concepts")[uri] = concepts;
- 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);
- })
- , containsMatcher = new RegExp(term, "i")
- , contains = $.grep(list, function (value) {
- return $.inArray(value, startsWith) < 0 &&
- containsMatcher.test(value.label || value.value || value);
+ callback(concepts);
+ });
+ },
+
+ bioportalGetConceptsBatch: function (uris, callback) {
+ // make sure we have something to lookup
+ if (!MetacatUI.appModel.get("bioportalBatchUrl")) {
+ return;
+ }
+ // prepare the request JSON
+ var batchData = {};
+ batchData["http://www.w3.org/2002/07/owl#Class"] = {};
+ batchData["http://www.w3.org/2002/07/owl#Class"]["display"] =
+ "prefLabel,synonym,definition";
+ batchData["http://www.w3.org/2002/07/owl#Class"]["collection"] = [];
+ _.each(uris, function (uri) {
+ var item = {};
+ item["class"] = uri;
+ item["ontology"] = "http://data.bioontology.org/ontologies/ECSO";
+ batchData["http://www.w3.org/2002/07/owl#Class"]["collection"].push(
+ item
+ );
+ });
+
+ var url = MetacatUI.appModel.get("bioportalBatchUrl");
+ var model = this;
+ $.ajax(url, {
+ method: "POST",
+ //url: url,
+ data: JSON.stringify(batchData),
+ contentType: "application/json",
+ headers: {
+ Authorization:
+ "apikey token=" + MetacatUI.appModel.get("bioportalAPIKey"),
+ },
+ error: function (e) {
+ console.log(e);
+ },
+ success: function (data, textStatus, xhr) {
+ _.each(data["http://www.w3.org/2002/07/owl#Class"], function (obj) {
+ var concept = {};
+ concept.label = obj["prefLabel"];
+ concept.value = obj["@id"];
+ if (obj["definition"]) {
+ concept.desc = obj["definition"][0];
+ }
+ // add the synonyms
+ var synonyms = obj["synonym"];
+ if (synonyms) {
+ concept.synonyms = [];
+ _.each(synonyms, function (synonym) {
+ concept.synonyms.push(synonym);
+ });
+ }
+
+ var conceptList = [];
+ conceptList.push(concept);
+ model.get("concepts")[concept.value] = conceptList;
});
- response(startsWith.concat(contains));
+ callback.apply();
+ },
+ });
+ },
+
+ orcidGetConcepts: function (uri, callback) {
+ var people = this.get("concepts")[uri];
+
+ if (people) {
+ callback(people);
+ return;
+ } else {
+ people = [];
}
- }
- //Send the query
- $.ajax(requestSettings);
- },
+ var query =
+ MetacatUI.appModel.get("orcidBaseUrl") +
+ uri.substring(uri.lastIndexOf("/"));
+ var model = this;
+ $.get(query, function (data, status, xhr) {
+ // get the orcid info
+ var profile = $(data).find("orcid-profile");
+
+ _.each(profile, function (obj) {
+ var choice = {};
+ choice.label =
+ $(obj).find("orcid-bio > personal-details > given-names").text() +
+ " " +
+ $(obj).find("orcid-bio > personal-details > family-name").text();
+ choice.value = $(obj).find("orcid-identifier > uri").text();
+ choice.desc = $(obj).find("orcid-bio > personal-details").text();
+ people.push(choice);
+ });
- /**
- * Calls the monitor/status DataONE MN API and gets the size of the index queue.
- * @param {function} [onSuccess]
- * @param {function} [onError]
- */
- getSizeOfIndexQueue: function(onSuccess, onError){
+ model.get("concepts")[uri] = people;
- try{
+ // callback with answers
+ callback(people);
+ });
+ },
- if( !MetacatUI.appModel.get("monitorStatusUrl") ){
- if( typeof onSuccess == "function" ){
- onSuccess();
- }
- else{
- //Trigger a custom event for the size of the index queue.
- this.trigger("sizeOfQueue", -1);
+ /*
+ * Supplies search results for ORCiDs to autocomplete UI elements
+ */
+ orcidSearch: function (request, response, more, ignore) {
+ var people = [];
+
+ if (!ignore) var ignore = [];
+
+ var query = MetacatUI.appModel.get("orcidSearchUrl") + request.term;
+ $.get(query, function (data, status, xhr) {
+ // get the orcid info
+ var profile = $(data).find("orcid-profile");
+
+ _.each(profile, function (obj) {
+ var choice = {};
+ choice.value = $(obj).find("orcid-identifier > uri").text();
+
+ if (_.contains(ignore, choice.value.toLowerCase())) return;
+
+ choice.label =
+ $(obj).find("orcid-bio > personal-details > given-names").text() +
+ " " +
+ $(obj).find("orcid-bio > personal-details > family-name").text();
+ choice.desc = $(obj).find("orcid-bio > personal-details").text();
+ people.push(choice);
+ });
+
+ // add more if called that way
+ if (more) {
+ people = more.concat(people);
}
- return false;
- }
+ // callback with answers
+ response(people);
+ });
+ },
- var model = this;
+ /*
+ * Gets the bio of a person given an ORCID Updates the given user model
+ * with the bio info from ORCID
+ */
+ orcidGetBio: function (options) {
+ if (!options || !options.userModel) return;
- //Check if there is an indexing queue, because this model may still be indexing
- var requestSettings = {
- url: MetacatUI.appModel.get("monitorStatusUrl"),
+ var orcid = options.userModel.get("username"),
+ onSuccess = options.success || function () {},
+ onError = options.error || function () {};
+
+ $.ajax({
+ url: MetacatUI.appModel.get("orcidSearchUrl") + orcid,
type: "GET",
- error: function(){
- if( typeof onError == "function" ){
- onError();
- }
+ //accepts: "application/orcid+json",
+ success: function (data, textStatus, xhr) {
+ // get the orcid info
+ var orcidNode = $(data).find("path:contains(" + orcid + ")"),
+ profile = orcidNode.length
+ ? $(orcidNode).parents("orcid-profile")
+ : [];
+
+ if (!profile.length) return;
+
+ var fullName =
+ $(profile)
+ .find("orcid-bio > personal-details > given-names")
+ .text() +
+ " " +
+ $(profile)
+ .find("orcid-bio > personal-details > family-name")
+ .text();
+ options.userModel.set("fullName", fullName);
+
+ onSuccess(data, textStatus, xhr);
},
- success: function(data){
+ error: function (xhr, textStatus, error) {
+ onError(xhr, textStatus, error);
+ },
+ });
+ },
+
+ /**
+ * Using the NSF Award Search API, get a list of grants that match the
+ * given term, as long as the term is at least 3 characters long and
+ * doesn't consist of only digits. Used to populate the autocomplete list
+ * for the Funding fields in the metadata editor. For this method to work,
+ * there must be a grantsUrl set in the MetacatUI.appModel.
+ * @param {jQuery} request - The jQuery UI autocomplete request object
+ * @param {function} response - The jQuery UI autocomplete response function
+ * @see {@link https://www.research.gov/common/webapi/awardapisearch-v1.htm}
+ */
+ getGrantAutocomplete: function (
+ request,
+ response,
+ beforeRequest,
+ afterRequest
+ ) {
+ // Handle errors in this function or in the findGrants function
+ function handleError(error) {
+ if (typeof afterRequest == "function") afterRequest();
+ console.log("Error fetching awards from NSF: ", error);
+ response([]);
+ }
- var sizeOfQueue = parseInt($(data).find("status > index > sizeOfQueue").text());
+ try {
+ let term = request.term;
- if(sizeOfQueue > 0 || sizeOfQueue == 0){
- //Trigger a custom event for the size of the index queue.
- model.trigger("sizeOfQueue", sizeOfQueue);
+ // Only search after 3 characters or more, and not just digits
+ if (!term || term.length < 3 || /^\d+$/.test(term)) return;
+
+ // If the beforeRequest function was passed, call it now
+ if (typeof beforeRequest == "function") beforeRequest();
- if( typeof onSuccess == "function" ){
- onSuccess(sizeOfQueue);
+ // Search for grants
+ this.findGrants(term)
+ .then((awards) => {
+ response(this.formatFundingForAutocomplete(awards));
+ })
+ .catch(handleError)
+ .finally(() => {
+ if (typeof afterRequest == "function") afterRequest();
+ });
+ } catch (error) {
+ handleError(error);
+ }
+ },
+
+ /**
+ * Search the NSF Award Search API for grants that match the given term.
+ * @param {string} term - The term to search for
+ * @param {number} [offset=1] - The offset to use in the search. Defaults
+ * to 1.
+ * @returns {Promise} A promise that resolves to an array of awards in the
+ * format {id: string, title: string}
+ * @since x.x.x
+ */
+ findGrants: function (term, offset = 1) {
+ let awards = [];
+ if (!term || term.length < 3) return awards;
+ const grantsUrl = MetacatUI.appModel.get("grantsUrl");
+ if (!grantsUrl) return awards;
+
+ term = $.ui.autocomplete.escapeRegex(term);
+ term = encodeURIComponent(term);
+ const filterBy = "keyword";
+ const url =
+ `${grantsUrl}?${filterBy}=${term}` +
+ `&printFields=title,id&offset=${offset}`;
+
+ return fetch(url)
+ .then((response) => {
+ return response.json();
+ })
+ .then((data) => {
+ if (!data || !data.response || !data.response.award) return awards;
+ return data.response.award;
+ })
+ .catch((error) => {
+ console.error("Error fetching data: ", error);
+ return awards;
+ });
+ },
+
+ /**
+ * Format awards from the NSF Award Search API for use in the jQuery UI
+ * autocomplete widget.
+ * @param {Array} awards - An array of awards in the format
+ * {id: string, title: string}
+ * @returns {Array} An array of awards in the format
+ * {value: string, label: string}
+ * @since x.x.x
+ */
+ formatFundingForAutocomplete: function (awards) {
+ if (!awards || !awards.length) return [];
+ return awards.map((award) => ({
+ value: award.id,
+ label: award.title,
+ }));
+ },
+
+ getAccountsAutocomplete: function (request, response) {
+ var searchTerm = $.ui.autocomplete.escapeRegex(request.term);
+
+ //Only search after 2 characters or more
+ if (searchTerm.length < 2) return;
+
+ var url =
+ MetacatUI.appModel.get("accountsUrl") + "?query=" + searchTerm;
+
+ // Send the AJAX request as a JSONP data type since it will be
+ // cross-origin
+ var requestSettings = {
+ url: url,
+ success: function (data, textStatus, xhr) {
+ if (!data) return [];
+
+ //If an XML doc was not returned from the server, then try to parse
+ //the response as XML
+ if (!XMLDocument.prototype.isPrototypeOf(data)) {
+ try {
+ data = $.parseXML(data);
+ } catch (e) {
+ //If the parsing XML failed, exit now
+ console.error(
+ "The accounts service did not return valid XML.",
+ e
+ );
+ return;
}
}
- else{
- if( typeof onError == "function" ){
- onError();
+
+ var list = [];
+
+ _.each(
+ $(data)
+ .children(/.+subjectInfo/)
+ .children(),
+ function (accountNode, i) {
+ var name = "";
+ var type = "";
+
+ if ($(accountNode).children("givenName").length) {
+ name =
+ $(accountNode).children("givenName").text() +
+ " " +
+ $(accountNode).children("familyName").text();
+ type = "person";
+ } else {
+ name = $(accountNode).children("groupName").text();
+ type = "group";
+ }
+
+ if (!name) {
+ name = $(accountNode).children("subject").text();
+ type = "unknown";
+ }
+
+ list.push({
+ value: $(accountNode).children("subject").text(),
+ label:
+ name +
+ " (" +
+ $(accountNode).children("subject").text() +
+ ")",
+ 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
+ );
+ }),
+ containsMatcher = new RegExp(term, "i"),
+ contains = $.grep(list, function (value) {
+ return (
+ $.inArray(value, startsWith) < 0 &&
+ containsMatcher.test(value.label || value.value || value)
+ );
+ });
+
+ response(startsWith.concat(contains));
+ },
+ };
+
+ //Send the query
+ $.ajax(requestSettings);
+ },
+
+ /**
+ * Calls the monitor/status DataONE MN API and gets the size of the index
+ * queue.
+ * @param {function} [onSuccess]
+ * @param {function} [onError]
+ */
+ getSizeOfIndexQueue: function (onSuccess, onError) {
+ try {
+ if (!MetacatUI.appModel.get("monitorStatusUrl")) {
+ if (typeof onSuccess == "function") {
+ onSuccess();
+ } else {
+ //Trigger a custom event for the size of the index queue.
+ this.trigger("sizeOfQueue", -1);
}
+
+ return false;
}
- }
- $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
- }
- catch(e){
- console.error(e);
+ var model = this;
- if( typeof onError == "function" ){
- onError();
+ //Check if there is an indexing queue, because this model may still be
+ //indexing
+ var requestSettings = {
+ url: MetacatUI.appModel.get("monitorStatusUrl"),
+ type: "GET",
+ error: function () {
+ if (typeof onError == "function") {
+ onError();
+ }
+ },
+ success: function (data) {
+ var sizeOfQueue = parseInt(
+ $(data).find("status > index > sizeOfQueue").text()
+ );
+
+ if (sizeOfQueue > 0 || sizeOfQueue == 0) {
+ //Trigger a custom event for the size of the index queue.
+ model.trigger("sizeOfQueue", sizeOfQueue);
+
+ if (typeof onSuccess == "function") {
+ onSuccess(sizeOfQueue);
+ }
+ } else {
+ if (typeof onError == "function") {
+ onError();
+ }
+ }
+ },
+ };
+
+ $.ajax(
+ _.extend(
+ requestSettings,
+ MetacatUI.appUserModel.createAjaxSettings()
+ )
+ );
+ } catch (e) {
+ console.error(e);
+
+ if (typeof onError == "function") {
+ onError();
+ }
}
-
- }
+ },
}
-
- });
- return LookupModel;
+ );
+ return LookupModel;
});
diff --git a/src/js/views/metadata/EML211View.js b/src/js/views/metadata/EML211View.js
index 700595a1b..dead17758 100644
--- a/src/js/views/metadata/EML211View.js
+++ b/src/js/views/metadata/EML211View.js
@@ -1196,7 +1196,6 @@ define(['underscore', 'jquery', 'backbone',
fundingInput.val(value);
$(".funding .ui-helper-hidden-accessible").hide();
- loadingSpinner.css("top", "5px");
view.updateFunding(e);
diff --git a/test/config/tests.json b/test/config/tests.json
index 1fb5afcfa..7156a2e2e 100644
--- a/test/config/tests.json
+++ b/test/config/tests.json
@@ -42,5 +42,8 @@
"./js/specs/unit/models/maps/assets/CesiumGeohash.spec.js",
"./js/specs/unit/common/Utilities.spec.js"
],
- "integration": ["./js/specs/integration/collections/SolrResults.spec.js"]
+ "integration": [
+ "./js/specs/integration/collections/SolrResults.spec.js",
+ "./js/specs/integration/models/LookupModel.js"
+ ]
}
diff --git a/test/js/specs/integration/models/LookupModel.js b/test/js/specs/integration/models/LookupModel.js
new file mode 100644
index 000000000..29e7ba74f
--- /dev/null
+++ b/test/js/specs/integration/models/LookupModel.js
@@ -0,0 +1,33 @@
+define(["../../../../../../../../src/js/models/LookupModel"], function (
+ LookupModel
+) {
+ // Configure the Chai assertion library
+ var should = chai.should();
+ var expect = chai.expect;
+
+ describe("Lookup Model", function () {
+ beforeEach(function () {
+ MetacatUI.appModel.set(
+ "grantsUrl",
+ "https://arcticdata.io/research.gov/awardapi-service/v1/awards.json"
+ );
+
+ });
+
+ afterEach(function () {
+ var lookup = null;
+ });
+
+ describe("NSF Awards API Lookup", function () {
+
+ it("should return results for a valid term", async function () {
+ let lookup = new LookupModel();
+ const awards = await lookup.findGrants("alaska");
+ expect(awards).to.be.an("array");
+ expect(awards.length).to.be.greaterThan(0);
+ expect(awards[0]).to.have.property("id");
+ expect(awards[0]).to.have.property("title");
+ });
+ });
+ });
+});