From abe08b6597250ce8d620e17146bfe82a72ed2d33 Mon Sep 17 00:00:00 2001 From: laurenwalker Date: Thu, 7 May 2020 12:15:49 -0500 Subject: [PATCH 1/4] Update `limitPortalsToSubjects` config for arctic theme --- src/js/themes/arctic/models/AppModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/themes/arctic/models/AppModel.js b/src/js/themes/arctic/models/AppModel.js index fb4de123e..86d1dff1a 100644 --- a/src/js/themes/arctic/models/AppModel.js +++ b/src/js/themes/arctic/models/AppModel.js @@ -365,7 +365,7 @@ define(['jquery', 'underscore', 'backbone'], * Limits only the following people or groups to create new portals. * @type {string[]} */ - limitPortalsToSubjects: ["CN=arctic-data-admins,DC=dataone,DC=org"], + limitPortalsToSubjects: [], /** * This message will display when a user tries to create a new Portal in the PortalEditor From b1c35308f764f27024295cc16d13859ede35ceae Mon Sep 17 00:00:00 2001 From: laurenwalker Date: Thu, 7 May 2020 18:33:13 -0500 Subject: [PATCH 2/4] Don't use wildcards for search phrases. Allow date range strings as filter values. Closes #1374 --- src/js/models/filters/ChoiceFilter.js | 2 +- src/js/models/filters/Filter.js | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/js/models/filters/ChoiceFilter.js b/src/js/models/filters/ChoiceFilter.js index b3a757ae8..81e644d75 100644 --- a/src/js/models/filters/ChoiceFilter.js +++ b/src/js/models/filters/ChoiceFilter.js @@ -35,7 +35,7 @@ define(['jquery', 'underscore', 'backbone', 'models/filters/Filter'], }); }, - /* + /** * Parses the choiceFilter XML node into JSON * * @param {Element} xml - The XML Element that contains all the ChoiceFilter elements diff --git a/src/js/models/filters/Filter.js b/src/js/models/filters/Filter.js index ca656a32b..7cdeb7af1 100644 --- a/src/js/models/filters/Filter.js +++ b/src/js/models/filters/Filter.js @@ -340,12 +340,15 @@ define(['jquery', 'underscore', 'backbone'], //Escape special characters value = this.escapeSpecialChar(value); - //Add the value to the query string. Wrap in wildcards, if specified - if( value.indexOf(" ") > -1 ){ - value = "\"" + value + "\""; - } - if( this.get("matchSubstring") ){ + 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); + + //If the value is a search phrase (more than one word), and not a date range string, wrap in quotes + if( value.indexOf(" ") > -1 && !isDateRange ){ + valuesQueryString = "\"" + value + "\""; + } + else if( this.get("matchSubstring") && !isDateRange ){ //Look for existing wildcard characters at the end of the value string if( value.match( /^\*|\*$/ ) ){ From 7f90ad8ac948405d952771c7d0f9a9ee425b5996 Mon Sep 17 00:00:00 2001 From: laurenwalker Date: Thu, 7 May 2020 18:51:37 -0500 Subject: [PATCH 3/4] Updated docs --- docs/docs/AccessPolicy.html | 4 +- docs/docs/AccessPolicyView.html | 4 +- docs/docs/AccessRule.html | 4 +- docs/docs/AppModel.html | 4 +- docs/docs/BooleanFilter.html | 18 +- docs/docs/ChoiceFilter.html | 26 +- docs/docs/Citations.html | 4 +- docs/docs/CollectionModel.html | 20 +- docs/docs/ColorPaletteView.html | 4 +- docs/docs/DataCatalogViewWithFilters.html | 30 +- ...DataCatalogView_drawTiles-TextOverlay.html | 6 +- docs/docs/DataItemView.html | 4 +- docs/docs/DataPackage.html | 257 +- docs/docs/DateFilter.html | 16 +- docs/docs/EML211EditorView.html | 247 +- docs/docs/EMLEntity.html | 4 +- docs/docs/EMLGeoCoverage.html | 4 +- docs/docs/EMLNonNumericDomain.html | 4 +- docs/docs/EMLNumericDomain.html | 4 +- docs/docs/EMLPartyView.html | 4 +- docs/docs/EMLTemporalCoverage.html | 4 +- docs/docs/EMlGeoCoverageView.html | 4 +- docs/docs/EditCollectionView.html | 4 +- docs/docs/EditorView.html | 56 +- docs/docs/Filter.html | 18 +- docs/docs/FilterGroup.html | 4 +- docs/docs/FilterGroupsView.html | 4 +- docs/docs/Filters.html | 10 +- docs/docs/ImageUploaderView.html | 4 +- docs/docs/MetadataView.html | 32 +- docs/docs/NavbarView.html | 4 +- docs/docs/NumericFilter.html | 16 +- docs/docs/ObjectFormats.html | 4 +- docs/docs/PortEditorDataView.html | 4 +- docs/docs/PortEditorImageView.html | 4 +- docs/docs/PortEditorLogosView.html | 4 +- docs/docs/PortEditorMdSectionView.html | 4 +- docs/docs/PortEditorSectionView.html | 4 +- docs/docs/PortEditorSectionsView.html | 124 +- docs/docs/PortEditorSettingsView.html | 4 +- docs/docs/PortalDataView.html | 4 +- docs/docs/PortalEditorView.html | 306 ++- docs/docs/PortalListView.html | 4 +- docs/docs/PortalMembersView.html | 4 +- docs/docs/PortalModel.html | 4 +- docs/docs/PortalSectionView.html | 4 +- docs/docs/PortalView.html | 4 +- docs/docs/QualityReport.html | 4 +- docs/docs/Search.html | 4 +- docs/docs/SolrResultList.html | 4 +- docs/docs/SpatialFilter.html | 18 +- docs/docs/ToggleFilter.html | 18 +- docs/docs/UIRouter.html | 1018 ++++++- docs/docs/UserView.html | 6 +- docs/docs/app.js.html | 5 +- docs/docs/collections_AccessPolicy.js.html | 4 +- docs/docs/collections_Citations.js.html | 4 +- docs/docs/collections_DataPackage.js.html | 373 +-- docs/docs/collections_Filters.js.html | 8 +- docs/docs/collections_ObjectFormats.js.html | 4 +- docs/docs/collections_QualityReport.js.html | 4 +- docs/docs/collections_SolrResults.js.html | 4 +- docs/docs/global.html | 2032 +++++++++++++- docs/docs/index.html | 4 +- docs/docs/models_AccessRule.js.html | 4 +- docs/docs/models_AppModel.js.html | 72 +- docs/docs/models_CollectionModel.js.html | 4 +- docs/docs/models_DataONEObject.js.html | 10 +- docs/docs/models_LookupModel.js.html | 4 +- docs/docs/models_Map.js.html | 4 +- docs/docs/models_PackageModel.js.html | 4 +- docs/docs/models_Search.js.html | 4 +- docs/docs/models_SolrResult.js.html | 11 +- docs/docs/models_Stats.js.html | 187 +- docs/docs/models_UserModel.js.html | 36 +- .../docs/models_filters_BooleanFilter.js.html | 4 +- docs/docs/models_filters_ChoiceFilter.js.html | 6 +- docs/docs/models_filters_DateFilter.js.html | 4 +- docs/docs/models_filters_Filter.js.html | 17 +- docs/docs/models_filters_FilterGroup.js.html | 4 +- .../docs/models_filters_NumericFilter.js.html | 4 +- .../docs/models_filters_SpatialFilter.js.html | 4 +- docs/docs/models_filters_ToggleFilter.js.html | 4 +- .../models_metadata_eml211_EML211.js.html | 2118 +++++++++++++++ .../models_metadata_eml211_EMLEntity.js.html | 4 +- ...els_metadata_eml211_EMLGeoCoverage.js.html | 4 +- ...etadata_eml211_EMLNonNumericDomain.js.html | 4 +- ...s_metadata_eml211_EMLNumericDomain.js.html | 4 +- ...etadata_eml211_EMLTemporalCoverage.js.html | 4 +- .../models_metadata_eml220_EMLText.js.html | 4 +- docs/docs/models_portals_PortalImage.js.html | 4 +- docs/docs/models_portals_PortalModel.js.html | 4 +- .../models_portals_PortalSectionModel.js.html | 4 +- docs/docs/routers_router.js.html | 26 +- .../themes_arctic_models_AppModel.js.html | 64 +- docs/docs/themes_arctic_models_Map.js.html | 4 +- .../docs/themes_arctic_routers_router.js.html | 26 +- .../themes_dataone_models_AppModel.js.html | 46 +- .../themes_dataone_routers_router.js.html | 4 +- docs/docs/themes_knb_models_AppModel.js.html | 62 +- docs/docs/themes_knb_routers_router.js.html | 4 +- docs/docs/themes_opc_routers_router.js.html | 82 + ...emes_opc_views_metadata_EML211View.js.html | 290 ++ docs/docs/views_AccessPolicyView.js.html | 4 +- docs/docs/views_AccessRuleView.js.html | 4 +- docs/docs/views_AnnotationView.js.html | 4 +- docs/docs/views_AppView.js.html | 628 +++++ docs/docs/views_ColorPaletteView.js.html | 4 +- docs/docs/views_DataCatalogView.js.html | 15 +- .../views_DataCatalogViewWithFilters.js.html | 4 +- docs/docs/views_DataItemView.js.html | 4 +- docs/docs/views_DraftsView.js.html | 252 ++ docs/docs/views_EditCollectionView.js.html | 4 +- docs/docs/views_EditorView.js.html | 6 +- docs/docs/views_ImageUploaderView.js.html | 4 +- docs/docs/views_MetadataView.js.html | 139 +- docs/docs/views_NavbarView.js.html | 4 +- docs/docs/views_StatsView.js.html | 136 +- docs/docs/views_TOCView.js.html | 4 +- docs/docs/views_UserView.js.html | 20 +- .../views_filters_FilterGroupsView.js.html | 4 +- .../views_metadata_EML211EditorView.js.html | 76 +- docs/docs/views_metadata_EML211View.js.html | 2420 +++++++++++++++++ .../docs/views_metadata_EMLEntityView.js.html | 622 +++++ .../views_metadata_EMLGeoCoverageView.js.html | 4 +- docs/docs/views_metadata_EMLPartyView.js.html | 4 +- .../docs/views_portals_PortalDataView.js.html | 4 +- .../docs/views_portals_PortalListView.js.html | 4 +- .../views_portals_PortalMembersView.js.html | 4 +- .../views_portals_PortalMetricsView.js.html | 9 +- .../views_portals_PortalSectionView.js.html | 4 +- docs/docs/views_portals_PortalView.js.html | 4 +- ..._portals_editor_PortEditorDataView.js.html | 4 +- ...portals_editor_PortEditorImageView.js.html | 4 +- ...portals_editor_PortEditorLogosView.js.html | 4 +- ...als_editor_PortEditorMdSectionView.js.html | 4 +- ...rtals_editor_PortEditorSectionView.js.html | 4 +- ...tals_editor_PortEditorSectionsView.js.html | 44 +- ...tals_editor_PortEditorSettingsView.js.html | 4 +- ...ws_portals_editor_PortalEditorView.js.html | 41 +- 140 files changed, 11255 insertions(+), 1222 deletions(-) create mode 100644 docs/docs/models_metadata_eml211_EML211.js.html create mode 100644 docs/docs/themes_opc_routers_router.js.html create mode 100644 docs/docs/themes_opc_views_metadata_EML211View.js.html create mode 100644 docs/docs/views_AppView.js.html create mode 100644 docs/docs/views_DraftsView.js.html create mode 100644 docs/docs/views_metadata_EML211View.js.html create mode 100644 docs/docs/views_metadata_EMLEntityView.js.html diff --git a/docs/docs/AccessPolicy.html b/docs/docs/AccessPolicy.html index 61841d28a..f5d6b5fcf 100644 --- a/docs/docs/AccessPolicy.html +++ b/docs/docs/AccessPolicy.html @@ -1280,13 +1280,13 @@
Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/AccessPolicyView.html b/docs/docs/AccessPolicyView.html index 4fdfb8027..663b88fc1 100644 --- a/docs/docs/AccessPolicyView.html +++ b/docs/docs/AccessPolicyView.html @@ -2002,13 +2002,13 @@

togglePr
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/AccessRule.html b/docs/docs/AccessRule.html index 6857f0f2d..9127213d6 100644 --- a/docs/docs/AccessRule.html +++ b/docs/docs/AccessRule.html @@ -624,13 +624,13 @@

Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/AppModel.html b/docs/docs/AppModel.html index d4ed9bd75..a5bd800a8 100644 --- a/docs/docs/AppModel.html +++ b/docs/docs/AppModel.html @@ -243,13 +243,13 @@
Type:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/BooleanFilter.html b/docs/docs/BooleanFilter.html index 73030b322..ee0ac55f2 100644 --- a/docs/docs/BooleanFilter.html +++ b/docs/docs/BooleanFilter.html @@ -796,7 +796,7 @@
Parameters:
Source:
@@ -1209,7 +1209,7 @@

hasCh
Source:
@@ -1466,7 +1466,7 @@

Parameters:
Source:
@@ -2118,7 +2118,7 @@

resetValue<
Source:
@@ -2334,7 +2334,7 @@

Properties:
Source:
@@ -2510,7 +2510,7 @@
Parameters:
Source:
@@ -2627,7 +2627,7 @@

validateSource:
@@ -2695,13 +2695,13 @@
Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/ChoiceFilter.html b/docs/docs/ChoiceFilter.html index 4a8859ffd..e39464b35 100644 --- a/docs/docs/ChoiceFilter.html +++ b/docs/docs/ChoiceFilter.html @@ -540,7 +540,7 @@
Parameters:
Source:
@@ -953,7 +953,7 @@

hasCh
Source:
@@ -1210,7 +1210,7 @@

Parameters:
Source:
@@ -1272,7 +1272,7 @@

parse - Parses the given XML node into a JSON object to be set on the model + Parses the choiceFilter XML node into JSON @@ -1324,7 +1324,7 @@
Parameters:
- The XML element that contains all the filter elements + The XML Element that contains all the ChoiceFilter elements @@ -1370,7 +1370,7 @@
Parameters:
Source:
@@ -1399,7 +1399,7 @@
Returns:
- - The JSON object of all the filter attributes + - The JSON object literal to be set on the model
@@ -1862,7 +1862,7 @@

resetValue<
Source:
@@ -2078,7 +2078,7 @@

Properties:
Source:
@@ -2254,7 +2254,7 @@
Parameters:
Source:
@@ -2371,7 +2371,7 @@

validateSource:
@@ -2439,13 +2439,13 @@
Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/Citations.html b/docs/docs/Citations.html index 22dd2baa9..aeea3871e 100644 --- a/docs/docs/Citations.html +++ b/docs/docs/Citations.html @@ -169,13 +169,13 @@

Extends


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/CollectionModel.html b/docs/docs/CollectionModel.html index af4163a92..caa3e894c 100644 --- a/docs/docs/CollectionModel.html +++ b/docs/docs/CollectionModel.html @@ -445,7 +445,7 @@

addTo
Source:
@@ -538,7 +538,7 @@

bytesToSiz
Source:
@@ -830,7 +830,7 @@

Parameters:
Source:
@@ -1200,7 +1200,7 @@

createVi
Source:
@@ -1976,7 +1976,7 @@

Parameters:
Source:
@@ -2228,7 +2228,7 @@
Parameters:
Source:
@@ -2344,7 +2344,7 @@

isNewSource:
@@ -3229,7 +3229,7 @@

updateSy
Source:
@@ -3727,13 +3727,13 @@

Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/ColorPaletteView.html b/docs/docs/ColorPaletteView.html index 483df5cb4..e91515bc4 100644 --- a/docs/docs/ColorPaletteView.html +++ b/docs/docs/ColorPaletteView.html @@ -616,13 +616,13 @@

render
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/DataCatalogViewWithFilters.html b/docs/docs/DataCatalogViewWithFilters.html index 8327bc358..2c00eb169 100644 --- a/docs/docs/DataCatalogViewWithFilters.html +++ b/docs/docs/DataCatalogViewWithFilters.html @@ -748,7 +748,7 @@

addAllSource:
@@ -842,7 +842,7 @@

addMarkers<
Source:
@@ -935,7 +935,7 @@

addOneSource:
@@ -1029,7 +1029,7 @@

add
Source:
@@ -1168,7 +1168,7 @@

Parameters:
Source:
@@ -1444,7 +1444,7 @@
Parameters:
Source:
@@ -1537,7 +1537,7 @@

drawTilesSource:
@@ -1679,7 +1679,7 @@
Parameters:
Source:
@@ -1911,7 +1911,7 @@
Parameters:
Source:
@@ -2004,7 +2004,7 @@

removeMa
Source:
@@ -2097,7 +2097,7 @@

removeTile
Source:
@@ -2617,7 +2617,7 @@

Parameters:
Source:
@@ -2760,7 +2760,7 @@
Parameters:
Source:
@@ -3071,13 +3071,13 @@

toggleMa
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/DataCatalogView_drawTiles-TextOverlay.html b/docs/docs/DataCatalogView_drawTiles-TextOverlay.html index 2505fcd17..cb8ff7154 100644 --- a/docs/docs/DataCatalogView_drawTiles-TextOverlay.html +++ b/docs/docs/DataCatalogView_drawTiles-TextOverlay.html @@ -91,7 +91,7 @@

new TextOv
Source:
@@ -153,13 +153,13 @@

new TextOv
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/DataItemView.html b/docs/docs/DataItemView.html index 6d5406505..91e5aa428 100644 --- a/docs/docs/DataItemView.html +++ b/docs/docs/DataItemView.html @@ -385,13 +385,13 @@

Parameters:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/DataPackage.html b/docs/docs/DataPackage.html index af274043f..bf6d5ef1a 100644 --- a/docs/docs/DataPackage.html +++ b/docs/docs/DataPackage.html @@ -1469,7 +1469,7 @@

fetchFr
Source:
@@ -1558,7 +1558,7 @@

getCnURISource:
@@ -1719,7 +1719,7 @@
Parameters:
Source:
@@ -1923,7 +1923,250 @@
Parameters:
Source:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

save(optionsopt)

+ + + + + + +
+ Overwrite the Backbone.Collection.sync() function to set custom options +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
options + + +Object + + + + + + <optional>
+ + + + + +
Options for this DataPackage save +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
sysMetaOnly + + +Boolean + + + + + + <optional>
+ + + + + +
If true, only the system metadata of this Package will be saved.
resourceMapOnly + + +Boolean + + + + + + <optional>
+ + + + + +
If true, only the Resource Map/Package object will be saved. Metadata and Data objects aggregated by the package will be skipped.
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -2011,7 +2254,7 @@

up
Source:
@@ -2057,13 +2300,13 @@

up
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/DateFilter.html b/docs/docs/DateFilter.html index 03c4fba4a..96ca4c4e2 100644 --- a/docs/docs/DateFilter.html +++ b/docs/docs/DateFilter.html @@ -609,7 +609,7 @@

Parameters:
Source:
@@ -1238,7 +1238,7 @@

hasCh
Source:
@@ -1495,7 +1495,7 @@

Parameters:
Source:
@@ -2147,7 +2147,7 @@

resetValue<
Source:
@@ -2363,7 +2363,7 @@

Properties:
Source:
@@ -2539,7 +2539,7 @@
Parameters:
Source:
@@ -2722,13 +2722,13 @@
Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/EML211EditorView.html b/docs/docs/EML211EditorView.html index 42c68c99a..60f40a173 100644 --- a/docs/docs/EML211EditorView.html +++ b/docs/docs/EML211EditorView.html @@ -94,7 +94,7 @@

new E
Source:
@@ -445,7 +445,7 @@

Type:
Source:
@@ -670,7 +670,7 @@
Type:
Source:
@@ -742,7 +742,7 @@
Type:
Source:
@@ -819,7 +819,7 @@
Type:
Source:
@@ -891,7 +891,7 @@
Type:
Source:
@@ -976,7 +976,7 @@

cancelSource:
@@ -1070,7 +1070,7 @@

canCloseSource:
@@ -1185,7 +1185,96 @@

checkVal
Source:
+ + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +

clearOldDrafts()

+ + + + + + +
+ Clear older drafts by iterating over the sorted list of drafts +stored by LocalForage and removing any beyond a hardcoded limit. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -1278,7 +1367,7 @@

Source:
@@ -1393,7 +1482,7 @@

handl
Source:
@@ -1487,7 +1576,7 @@

hasU
Source:
@@ -1598,7 +1687,7 @@

hideContr
Source:
@@ -1743,7 +1832,7 @@

Parameters:
Source:
@@ -1836,7 +1925,7 @@

hideSaving<
Source:
@@ -1924,7 +2013,7 @@

initialize<
Source:
@@ -2017,7 +2106,7 @@

onCloseSource:
@@ -2110,7 +2199,7 @@

remov
Source:
@@ -2345,7 +2434,7 @@

Parameters:
Source:
@@ -2438,7 +2527,7 @@

Source:
@@ -2531,7 +2620,7 @@

r
Source:
@@ -2674,7 +2763,7 @@

Parameters:
Source:
@@ -2767,7 +2856,95 @@

saveSource:
+ + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +

saveDraft()

+ + + + + + +
+ Save a draft of the parent EML model +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -2909,7 +3086,7 @@
Parameters:
Source:
@@ -3051,7 +3228,7 @@
Parameters:
Source:
@@ -3146,7 +3323,7 @@

setListen
Source:
@@ -3288,7 +3465,7 @@

Parameters:
Source:
@@ -3381,7 +3558,7 @@

showContr
Source:
@@ -3552,7 +3729,7 @@

Parameters:
Source:
@@ -3645,7 +3822,7 @@

showNotFo
Source:
@@ -3738,7 +3915,7 @@

showSaving<
Source:
@@ -3831,7 +4008,7 @@

showSignIn<
Source:
@@ -3924,7 +4101,7 @@

showVal
Source:
@@ -3970,13 +4147,13 @@

showVal
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/EMLEntity.html b/docs/docs/EMLEntity.html index 71c5f66f2..6d45706f7 100644 --- a/docs/docs/EMLEntity.html +++ b/docs/docs/EMLEntity.html @@ -258,13 +258,13 @@

updateF
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/EMLGeoCoverage.html b/docs/docs/EMLGeoCoverage.html index 75fe007b8..e8fb7b9b4 100644 --- a/docs/docs/EMLGeoCoverage.html +++ b/docs/docs/EMLGeoCoverage.html @@ -1498,13 +1498,13 @@

Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/EMLNonNumericDomain.html b/docs/docs/EMLNonNumericDomain.html index 5166f3a36..b8570edb6 100644 --- a/docs/docs/EMLNonNumericDomain.html +++ b/docs/docs/EMLNonNumericDomain.html @@ -346,13 +346,13 @@

parse
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/EMLNumericDomain.html b/docs/docs/EMLNumericDomain.html index 78a3c0b6d..c5b421bdd 100644 --- a/docs/docs/EMLNumericDomain.html +++ b/docs/docs/EMLNumericDomain.html @@ -325,13 +325,13 @@

parse
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/EMLPartyView.html b/docs/docs/EMLPartyView.html index f9284d634..2ed495abe 100644 --- a/docs/docs/EMLPartyView.html +++ b/docs/docs/EMLPartyView.html @@ -450,13 +450,13 @@

showVal
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/EMLTemporalCoverage.html b/docs/docs/EMLTemporalCoverage.html index d1f90be99..b53d8810c 100644 --- a/docs/docs/EMLTemporalCoverage.html +++ b/docs/docs/EMLTemporalCoverage.html @@ -664,13 +664,13 @@

Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/EMlGeoCoverageView.html b/docs/docs/EMlGeoCoverageView.html index f032c8278..2bb4a5e88 100644 --- a/docs/docs/EMlGeoCoverageView.html +++ b/docs/docs/EMlGeoCoverageView.html @@ -156,13 +156,13 @@

new
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/EditCollectionView.html b/docs/docs/EditCollectionView.html index 11eb6b595..a445c40c8 100644 --- a/docs/docs/EditCollectionView.html +++ b/docs/docs/EditCollectionView.html @@ -1355,13 +1355,13 @@

toggleH
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/EditorView.html b/docs/docs/EditorView.html index e63346aae..75413a4f8 100644 --- a/docs/docs/EditorView.html +++ b/docs/docs/EditorView.html @@ -719,7 +719,7 @@

cancelSource:
@@ -808,7 +808,7 @@

canCloseSource:
@@ -918,7 +918,7 @@

checkVal
Source:
@@ -1006,7 +1006,7 @@

Source:
@@ -1116,7 +1116,7 @@

handl
Source:
@@ -1205,7 +1205,7 @@

hasU
Source:
@@ -1311,7 +1311,7 @@

hideContr
Source:
@@ -1451,7 +1451,7 @@

Parameters:
Source:
@@ -1539,7 +1539,7 @@

hideSaving<
Source:
@@ -1627,7 +1627,7 @@

onCloseSource:
@@ -1715,7 +1715,7 @@

remov
Source:
@@ -1940,7 +1940,7 @@

Parameters:
Source:
@@ -2028,7 +2028,7 @@

Source:
@@ -2116,7 +2116,7 @@

r
Source:
@@ -2254,7 +2254,7 @@

Parameters:
Source:
@@ -2342,7 +2342,7 @@

saveSource:
@@ -2479,7 +2479,7 @@
Parameters:
Source:
@@ -2616,7 +2616,7 @@
Parameters:
Source:
@@ -2706,7 +2706,7 @@

setListen
Source:
@@ -2843,7 +2843,7 @@

Parameters:
Source:
@@ -2931,7 +2931,7 @@

showContr
Source:
@@ -3097,7 +3097,7 @@

Parameters:
Source:
@@ -3185,7 +3185,7 @@

showNotFo
Source:
@@ -3273,7 +3273,7 @@

showSaving<
Source:
@@ -3361,7 +3361,7 @@

showSignIn<
Source:
@@ -3449,7 +3449,7 @@

showVal
Source:
@@ -3495,13 +3495,13 @@

showVal
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/Filter.html b/docs/docs/Filter.html index 3d8d19801..9f3e31b68 100644 --- a/docs/docs/Filter.html +++ b/docs/docs/Filter.html @@ -781,7 +781,7 @@

Parameters:
Source:
@@ -1179,7 +1179,7 @@

hasCh
Source:
@@ -1426,7 +1426,7 @@

Parameters:
Source:
@@ -2058,7 +2058,7 @@

resetValue<
Source:
@@ -2269,7 +2269,7 @@

Properties:
Source:
@@ -2440,7 +2440,7 @@
Parameters:
Source:
@@ -2552,7 +2552,7 @@

validateSource:
@@ -2620,13 +2620,13 @@
Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/FilterGroup.html b/docs/docs/FilterGroup.html index f6e5ec1db..6b7799ab2 100644 --- a/docs/docs/FilterGroup.html +++ b/docs/docs/FilterGroup.html @@ -737,13 +737,13 @@
Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/FilterGroupsView.html b/docs/docs/FilterGroupsView.html index f27027d36..3442266fd 100644 --- a/docs/docs/FilterGroupsView.html +++ b/docs/docs/FilterGroupsView.html @@ -1605,13 +1605,13 @@
Properties:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/Filters.html b/docs/docs/Filters.html index c5917d676..6b15d62d9 100644 --- a/docs/docs/Filters.html +++ b/docs/docs/Filters.html @@ -213,7 +213,7 @@

filt
Source:
@@ -394,7 +394,7 @@

Parameters:
Source:
@@ -999,7 +999,7 @@
Parameters:
Source:
@@ -1045,13 +1045,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/ImageUploaderView.html b/docs/docs/ImageUploaderView.html index 07abaa3a6..41c2dfd90 100644 --- a/docs/docs/ImageUploaderView.html +++ b/docs/docs/ImageUploaderView.html @@ -2705,13 +2705,13 @@

showSav
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/MetadataView.html b/docs/docs/MetadataView.html index ea043ce77..ae836d419 100644 --- a/docs/docs/MetadataView.html +++ b/docs/docs/MetadataView.html @@ -213,7 +213,7 @@

Source:
@@ -399,7 +399,7 @@

Parameters:
Source:
@@ -589,7 +589,7 @@
Parameters:
Source:
@@ -808,7 +808,7 @@
Parameters:
Source:
@@ -1027,7 +1027,7 @@
Parameters:
Source:
@@ -1119,7 +1119,7 @@

generat
Source:
@@ -1210,7 +1210,7 @@

g
Source:
@@ -1300,7 +1300,7 @@

getAutho
Source:
@@ -1389,7 +1389,7 @@

g
Source:
@@ -1479,7 +1479,7 @@

getPu
Source:
@@ -1570,7 +1570,7 @@

Source:
@@ -1714,7 +1714,7 @@

Parameters:
Source:
@@ -1804,7 +1804,7 @@

previewDat
Source:
@@ -1895,7 +1895,7 @@

setUpAn
Source:
@@ -1941,13 +1941,13 @@

setUpAn
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/NavbarView.html b/docs/docs/NavbarView.html index c2ac16a15..1cc10d616 100644 --- a/docs/docs/NavbarView.html +++ b/docs/docs/NavbarView.html @@ -375,13 +375,13 @@

Type:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/NumericFilter.html b/docs/docs/NumericFilter.html index 4e5dd0ccf..c2da20675 100644 --- a/docs/docs/NumericFilter.html +++ b/docs/docs/NumericFilter.html @@ -632,7 +632,7 @@
Parameters:
Source:
@@ -1151,7 +1151,7 @@

hasCh
Source:
@@ -1408,7 +1408,7 @@

Parameters:
Source:
@@ -2060,7 +2060,7 @@

resetValue<
Source:
@@ -2276,7 +2276,7 @@

Properties:
Source:
@@ -2452,7 +2452,7 @@
Parameters:
Source:
@@ -2635,13 +2635,13 @@
Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/ObjectFormats.html b/docs/docs/ObjectFormats.html index 7a4c36064..a3632e76e 100644 --- a/docs/docs/ObjectFormats.html +++ b/docs/docs/ObjectFormats.html @@ -463,13 +463,13 @@
Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortEditorDataView.html b/docs/docs/PortEditorDataView.html index 8dfa53e62..2f502afc3 100644 --- a/docs/docs/PortEditorDataView.html +++ b/docs/docs/PortEditorDataView.html @@ -1151,13 +1151,13 @@

render
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortEditorImageView.html b/docs/docs/PortEditorImageView.html index 2d5a6b19e..27dae3b71 100644 --- a/docs/docs/PortEditorImageView.html +++ b/docs/docs/PortEditorImageView.html @@ -2887,13 +2887,13 @@

showVal
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortEditorLogosView.html b/docs/docs/PortEditorLogosView.html index 5ba8a3373..bd72b8498 100644 --- a/docs/docs/PortEditorLogosView.html +++ b/docs/docs/PortEditorLogosView.html @@ -1258,13 +1258,13 @@

showVal
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortEditorMdSectionView.html b/docs/docs/PortEditorMdSectionView.html index f3ecfb069..a13bffbeb 100644 --- a/docs/docs/PortEditorMdSectionView.html +++ b/docs/docs/PortEditorMdSectionView.html @@ -2264,13 +2264,13 @@

Parameters:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortEditorSectionView.html b/docs/docs/PortEditorSectionView.html index 81a30a746..63fbb3059 100644 --- a/docs/docs/PortEditorSectionView.html +++ b/docs/docs/PortEditorSectionView.html @@ -1640,13 +1640,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortEditorSectionsView.html b/docs/docs/PortEditorSectionsView.html index 9e32e2c25..c6004aa5c 100644 --- a/docs/docs/PortEditorSectionsView.html +++ b/docs/docs/PortEditorSectionsView.html @@ -1680,7 +1680,7 @@
Parameters:
Source:
@@ -1863,7 +1863,7 @@
Parameters:
Source:
@@ -1951,7 +1951,7 @@

closePop
Source:
@@ -2111,7 +2111,7 @@

Parameters:
Source:
@@ -2266,7 +2266,7 @@
Parameters:
Source:
@@ -2428,7 +2428,7 @@
Parameters:
Source:
@@ -2591,7 +2591,7 @@
Parameters:
Source:
@@ -2750,7 +2750,7 @@
Parameters:
Source:
@@ -2890,7 +2890,7 @@
Parameters:
Source:
@@ -2978,7 +2978,7 @@

onCloseSource:
@@ -3253,7 +3253,7 @@
Parameters:
Source:
@@ -3390,7 +3390,7 @@
Parameters:
Source:
@@ -3539,7 +3539,7 @@
Parameters:
Source:
@@ -3627,7 +3627,7 @@

renderSource:
@@ -3715,7 +3715,7 @@

rende
Source:
@@ -3875,7 +3875,7 @@

Parameters:
Source:
@@ -3963,7 +3963,7 @@

Source:
@@ -4051,7 +4051,7 @@

rend
Source:
@@ -4139,7 +4139,7 @@

r
Source:
@@ -4227,7 +4227,7 @@

renderS
Source:
@@ -4376,7 +4376,7 @@

Parameters:
Source:
@@ -4513,7 +4513,7 @@
Parameters:
Source:
@@ -4664,7 +4664,7 @@
Parameters:
Source:
@@ -4753,7 +4753,7 @@

Source:
@@ -4976,7 +4976,7 @@
Parameters:
Source:
@@ -5084,7 +5084,7 @@
Parameters:
- If true, the section label will be added to the path + If true, the editor section label will be added to the path @@ -5125,7 +5125,7 @@
Parameters:
Source:
@@ -5285,7 +5285,7 @@
Parameters:
Source:
@@ -7024,7 +7024,7 @@
Parameters:
Source:
@@ -7207,7 +7207,7 @@
Parameters:
Source:
@@ -7295,7 +7295,7 @@

closePop
Source:
@@ -7455,7 +7455,7 @@

Parameters:
Source:
@@ -7610,7 +7610,7 @@
Parameters:
Source:
@@ -7772,7 +7772,7 @@
Parameters:
Source:
@@ -7935,7 +7935,7 @@
Parameters:
Source:
@@ -8094,7 +8094,7 @@
Parameters:
Source:
@@ -8234,7 +8234,7 @@
Parameters:
Source:
@@ -8322,7 +8322,7 @@

onCloseSource:
@@ -8597,7 +8597,7 @@
Parameters:
Source:
@@ -8734,7 +8734,7 @@
Parameters:
Source:
@@ -8883,7 +8883,7 @@
Parameters:
Source:
@@ -8971,7 +8971,7 @@

renderSource:
@@ -9059,7 +9059,7 @@

rende
Source:
@@ -9219,7 +9219,7 @@

Parameters:
Source:
@@ -9307,7 +9307,7 @@

Source:
@@ -9395,7 +9395,7 @@

rend
Source:
@@ -9483,7 +9483,7 @@

r
Source:
@@ -9571,7 +9571,7 @@

renderS
Source:
@@ -9720,7 +9720,7 @@

Parameters:
Source:
@@ -9857,7 +9857,7 @@
Parameters:
Source:
@@ -10008,7 +10008,7 @@
Parameters:
Source:
@@ -10097,7 +10097,7 @@

Source:
@@ -10320,7 +10320,7 @@
Parameters:
Source:
@@ -10428,7 +10428,7 @@
Parameters:
- If true, the section label will be added to the path + If true, the editor section label will be added to the path @@ -10469,7 +10469,7 @@
Parameters:
Source:
@@ -10629,7 +10629,7 @@
Parameters:
Source:
@@ -10675,13 +10675,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortEditorSettingsView.html b/docs/docs/PortEditorSettingsView.html index 5841d2cb4..f5b7610e0 100644 --- a/docs/docs/PortEditorSettingsView.html +++ b/docs/docs/PortEditorSettingsView.html @@ -1690,13 +1690,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortalDataView.html b/docs/docs/PortalDataView.html index fbffb6cd4..5fe3ce752 100644 --- a/docs/docs/PortalDataView.html +++ b/docs/docs/PortalDataView.html @@ -1675,13 +1675,13 @@

renderTOC
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortalEditorView.html b/docs/docs/PortalEditorView.html index 0c76c0de7..602922933 100644 --- a/docs/docs/PortalEditorView.html +++ b/docs/docs/PortalEditorView.html @@ -670,7 +670,7 @@
Type:
Source:
@@ -760,6 +760,78 @@
Type:
+

newPortalDefaultSectionLabel :string

+ + + + +
+ When a new portal is being created, this is the label of the section that will be active when the editor first renders +
+ + + +
Type:
+
    +
  • + +string + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + +

newPortalTempName :string

@@ -816,7 +888,7 @@
Type:
Source:
@@ -961,7 +1033,7 @@
Type:
Source:
@@ -1033,7 +1105,79 @@
Type:
Source:
+ + + + + + + +

+ + + + + + + + +

sectionsView :PortEditorSectionsView

+ + + + +
+ A reference to the PortEditorSectionsView for this instance of the PortEditorView +
+ + + +
Type:
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -1110,7 +1254,79 @@
Type:
Source:
+ + + + + + + +
+ + + + + + + + +

subviews :Array.<Backbone.View>

+ + + + +
+ An array of Backbone Views that are contained in this view. +
+ + + +
Type:
+
    +
  • + +Array.<Backbone.View> + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -1172,7 +1388,7 @@

templateSource:
@@ -1316,7 +1532,7 @@
Type:
Source:
@@ -1401,7 +1617,7 @@

authoriz
Source:
@@ -1494,7 +1710,7 @@

cancelSource:
@@ -1588,7 +1804,7 @@

canCloseSource:
@@ -1703,7 +1919,7 @@

checkVal
Source:
@@ -1791,7 +2007,7 @@

createMode
Source:
@@ -1884,7 +2100,7 @@

Source:
@@ -1999,7 +2215,7 @@

handl
Source:
@@ -2087,7 +2303,7 @@

handleScr
Source:
@@ -2181,7 +2397,7 @@

hasU
Source:
@@ -2292,7 +2508,7 @@

hideContr
Source:
@@ -2385,7 +2601,7 @@

hideLoadin
Source:
@@ -2478,7 +2694,7 @@

hideSaving<
Source:
@@ -2615,7 +2831,7 @@

Parameters:
Source:
@@ -2708,7 +2924,7 @@

onCloseSource:
@@ -2801,7 +3017,7 @@

remov
Source:
@@ -2894,7 +3110,7 @@

renderSource:
@@ -3036,7 +3252,7 @@
Parameters:
Source:
@@ -3129,7 +3345,7 @@

Source:
@@ -3222,7 +3438,7 @@

r
Source:
@@ -3312,7 +3528,7 @@

rende
Source:
@@ -3400,7 +3616,7 @@

ren
Source:
@@ -3543,7 +3759,7 @@

Parameters:
Source:
@@ -3636,7 +3852,7 @@

saveSource:
@@ -3778,7 +3994,7 @@
Parameters:
Source:
@@ -3920,7 +4136,7 @@
Parameters:
Source:
@@ -4015,7 +4231,7 @@

setListen
Source:
@@ -4157,7 +4373,7 @@

Parameters:
Source:
@@ -4250,7 +4466,7 @@

showContr
Source:
@@ -4421,7 +4637,7 @@

Parameters:
Source:
@@ -4514,7 +4730,7 @@

showNotFo
Source:
@@ -4607,7 +4823,7 @@

showSaving<
Source:
@@ -4700,7 +4916,7 @@

showSignIn<
Source:
@@ -4793,7 +5009,7 @@

showVal
Source:
@@ -4953,7 +5169,7 @@

Parameters:
Source:
@@ -5042,7 +5258,7 @@
Parameters:
Source:
@@ -5238,13 +5454,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortalListView.html b/docs/docs/PortalListView.html index dc6e4c97d..0d54826b7 100644 --- a/docs/docs/PortalListView.html +++ b/docs/docs/PortalListView.html @@ -1029,13 +1029,13 @@

showError
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortalMembersView.html b/docs/docs/PortalMembersView.html index 46687f69a..d4ab608f9 100644 --- a/docs/docs/PortalMembersView.html +++ b/docs/docs/PortalMembersView.html @@ -1603,13 +1603,13 @@

renderTOC
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortalModel.html b/docs/docs/PortalModel.html index ae9d6fb3c..21775aa87 100644 --- a/docs/docs/PortalModel.html +++ b/docs/docs/PortalModel.html @@ -3555,13 +3555,13 @@
Properties:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortalSectionView.html b/docs/docs/PortalSectionView.html index b2f3e9b9c..f927053fd 100644 --- a/docs/docs/PortalSectionView.html +++ b/docs/docs/PortalSectionView.html @@ -1535,13 +1535,13 @@

renderTOC
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/PortalView.html b/docs/docs/PortalView.html index e8e7bf758..dc5f5c921 100644 --- a/docs/docs/PortalView.html +++ b/docs/docs/PortalView.html @@ -3195,13 +3195,13 @@

upd
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/QualityReport.html b/docs/docs/QualityReport.html index 8d1b6fc34..b5bbceee3 100644 --- a/docs/docs/QualityReport.html +++ b/docs/docs/QualityReport.html @@ -169,13 +169,13 @@

Extends


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/Search.html b/docs/docs/Search.html index e973a8138..0e9a5b85b 100644 --- a/docs/docs/Search.html +++ b/docs/docs/Search.html @@ -526,13 +526,13 @@
Properties:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/SolrResultList.html b/docs/docs/SolrResultList.html index f7e970998..ed4d5606b 100644 --- a/docs/docs/SolrResultList.html +++ b/docs/docs/SolrResultList.html @@ -167,13 +167,13 @@

Extends


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/SpatialFilter.html b/docs/docs/SpatialFilter.html index 753191ead..1aa30a3b0 100644 --- a/docs/docs/SpatialFilter.html +++ b/docs/docs/SpatialFilter.html @@ -519,7 +519,7 @@
Parameters:
Source:
@@ -1008,7 +1008,7 @@

hasCh
Source:
@@ -1265,7 +1265,7 @@

Parameters:
Source:
@@ -1917,7 +1917,7 @@

resetValue<
Source:
@@ -2133,7 +2133,7 @@

Properties:
Source:
@@ -2309,7 +2309,7 @@
Parameters:
Source:
@@ -2426,7 +2426,7 @@

validateSource:
@@ -2494,13 +2494,13 @@
Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/ToggleFilter.html b/docs/docs/ToggleFilter.html index 32d933d54..edcf0b77c 100644 --- a/docs/docs/ToggleFilter.html +++ b/docs/docs/ToggleFilter.html @@ -592,7 +592,7 @@
Parameters:
Source:
@@ -1005,7 +1005,7 @@

hasCh
Source:
@@ -1262,7 +1262,7 @@

Parameters:
Source:
@@ -1914,7 +1914,7 @@

resetValue<
Source:
@@ -2130,7 +2130,7 @@

Properties:
Source:
@@ -2306,7 +2306,7 @@
Parameters:
Source:
@@ -2423,7 +2423,7 @@

validateSource:
@@ -2491,13 +2491,13 @@
Returns:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/UIRouter.html b/docs/docs/UIRouter.html index b6357413c..bae039b14 100644 --- a/docs/docs/UIRouter.html +++ b/docs/docs/UIRouter.html @@ -94,7 +94,7 @@

new UIRouter<
Source:
@@ -155,6 +155,186 @@

Extends

Methods

+ + + + + + +

renderDrafts()

+ + + + + + +
+ Renders the Drafts view which is a simple view backed by LocalForage that +lists drafts created in the Editor so users can recover any failed +submissions. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

renderDrafts()

+ + + + + + +
+ Renders the Drafts view which is a simple view backed by LocalForage that +lists drafts created in the Editor so users can recover any failed +submissions. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -213,7 +393,7 @@

renderPor
Source:
@@ -301,7 +481,7 @@

renderPor
Source:
@@ -516,7 +696,7 @@

Parameters:
Source:
@@ -731,7 +911,7 @@
Parameters:
Source:
@@ -846,7 +1026,7 @@

new UIRouter<
Source:
@@ -913,7 +1093,7 @@

Methods

-

renderPortal()

+

renderDrafts()

@@ -921,7 +1101,9 @@

renderPor
- Render the portal view based on the given name, id, or section + Renders the Drafts view which is a simple view backed by LocalForage that +lists drafts created in the Editor so users can recover any failed +submissions.
@@ -965,7 +1147,7 @@

renderPor
Source:
@@ -1001,7 +1183,7 @@

renderPor -

renderPortal()

+

renderDrafts()

@@ -1009,7 +1191,9 @@

renderPor
- Render the portal view based on the given name, id, or section + Renders the Drafts view which is a simple view backed by LocalForage that +lists drafts created in the Editor so users can recover any failed +submissions.
@@ -1053,7 +1237,7 @@

renderPor
Source:
@@ -1089,7 +1273,7 @@

renderPor -

renderPortalEditor(portalTermPluralopt, portalIdentifieropt, portalSectionopt)

+

renderPortal()

@@ -1097,7 +1281,7 @@

ren
- Renders the PortalEditorView + Render the portal view based on the given name, id, or section
@@ -1108,77 +1292,253 @@

ren -

Parameters:
- - - - - - - - - - - - +
-
- - + - - - - - + - + - - - + - + - - + - - - - + +
Source:
+
+ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

renderPortal()

+ + + + + + +
+ Render the portal view based on the given name, id, or section +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

renderPortalEditor(portalTermPluralopt, portalIdentifieropt, portalSectionopt)

+ + + + + + +
+ Renders the PortalEditorView +
+ + + + + + + + + +
Parameters:
+ + +
NameTypeAttributesDescription
portalTermPlural - - -string + + - - - - <optional>
- + - + - -
This should match the `portalTermPlural` configured in the AppModel.
portalIdentifier - - -string + + - -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + +
NameTypeAttributesDescription
portalTermPlural + + +string + + + + + + <optional>
+ + + + + +
This should match the `portalTermPlural` configured in the AppModel.
portalIdentifier + + +string + + + + @@ -1268,7 +1628,7 @@
Parameters:
Source:
@@ -1436,18 +1796,254 @@
Parameters:
- -
The name of the section within the portal to navigate to (e.g. "data")
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +

UIRouter()

+ +
MetacatUI Router
+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new UIRouter()

+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + +

Extends

+ + + + +
    +
  • Backbone.Router
  • +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

renderDrafts()

+ + + + + + +
+ Renders the Drafts view which is a simple view backed by LocalForage that +lists drafts created in the Editor so users can recover any failed +submissions. +
+ + + + - - The name of the section within the portal to navigate to (e.g. "data") - - - - @@ -1483,7 +2079,7 @@
Parameters:
Source:
@@ -1514,48 +2110,23 @@
Parameters:
- - - - - -
- -
- - - - - - - -
- -
- -

UIRouter()

- -
MetacatUI Router
- - -
+ -
-
- -

Constructor

+

renderDrafts()

-

new UIRouter()

- - +
+ Renders the Drafts view which is a simple view backed by LocalForage that +lists drafts created in the Editor so users can recover any failed +submissions. +
@@ -1598,7 +2169,7 @@

new UIRouter<
Source:
@@ -1628,37 +2199,6 @@

new UIRouter< - -

- - -

Extends

- - - - -
    -
  • Backbone.Router
  • -
- - - - - - - - - - - - - - - - - -

Methods

- @@ -1717,7 +2257,7 @@

renderPor
Source:
@@ -1805,7 +2345,7 @@

renderPor
Source:
@@ -2020,7 +2560,7 @@

Parameters:
Source:
@@ -2235,7 +2775,7 @@
Parameters:
Source:
@@ -2350,7 +2890,7 @@

new UIRouter<
Source:
@@ -2411,6 +2951,186 @@

Extends

Methods

+ + + + + + +

renderDrafts()

+ + + + + + +
+ Renders the Drafts view which is a simple view backed by LocalForage that +lists drafts created in the Editor so users can recover any failed +submissions. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

renderDrafts()

+ + + + + + +
+ Renders the Drafts view which is a simple view backed by LocalForage that +lists drafts created in the Editor so users can recover any failed +submissions. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -2469,7 +3189,7 @@

renderPor
Source:
@@ -2557,7 +3277,7 @@

renderPor
Source:
@@ -2772,7 +3492,7 @@

Parameters:
Source:
@@ -2987,7 +3707,7 @@
Parameters:
Source:
@@ -3033,13 +3753,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/UserView.html b/docs/docs/UserView.html index f70b9a905..f6ba28f81 100644 --- a/docs/docs/UserView.html +++ b/docs/docs/UserView.html @@ -279,7 +279,7 @@

render
Source:
@@ -325,13 +325,13 @@

render
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:46 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:14 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/app.js.html b/docs/docs/app.js.html index 3132abc76..b3eebca8b 100644 --- a/docs/docs/app.js.html +++ b/docs/docs/app.js.html @@ -73,6 +73,7 @@

Source: app.js

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', @@ -352,13 +353,13 @@

Source: app.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/collections_AccessPolicy.js.html b/docs/docs/collections_AccessPolicy.js.html index 76655b1ea..ea104b7dd 100644 --- a/docs/docs/collections_AccessPolicy.js.html +++ b/docs/docs/collections_AccessPolicy.js.html @@ -372,13 +372,13 @@

Source: collections/AccessPolicy.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/collections_Citations.js.html b/docs/docs/collections_Citations.js.html index 20b588de4..2a0a9dc6e 100644 --- a/docs/docs/collections_Citations.js.html +++ b/docs/docs/collections_Citations.js.html @@ -69,13 +69,13 @@

Source: collections/Citations.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/collections_DataPackage.js.html b/docs/docs/collections_DataPackage.js.html index 839b6674e..3a93cec78 100644 --- a/docs/docs/collections_DataPackage.js.html +++ b/docs/docs/collections_DataPackage.js.html @@ -1089,8 +1089,11 @@

Source: collections/DataPackage.js

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){ @@ -1116,215 +1119,234 @@

Source: collections/DataPackage.js

return; } - //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" }); - 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("uploadStatus") == "q" || - //m.get("uploadStatus") == "e" || - (m.get("type") == "Metadata" && - m.get("uploadStatus") != "p" && - m.get("uploadStatus") != "c" && - m.get("uploadStatus") != "e" && - m.get("uploadStatus") !== null)) - }); - - //First quickly validate all the models before attempting to save any - var allValid = _.every(modelsToBeSaved, function(m) { + if( options.resourceMapOnly !== true ){ - if( m.isValid() ){ - m.trigger("valid"); - return true; - } - else{ - return false; - } + //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("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 metaata 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.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") ) { + // 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; + this.packageModel.set("changed", false); + this.packageModel.set("uploadStatus", "q"); + this.trigger("cancelSave"); + 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); + //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, "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); + //If the model fails to save, start this save function + this.stopListening(model, "cancelSave", this.save); + this.listenToOnce(model, "cancelSave", this.save); - //If this is a Data model and it has no content changes, we only want to save the system metadata - if( model.get("type") == "Data" && !model.get("hasContentChanges") ){ - model.updateSysMeta(); - } - else{ - //Save the model and watch for fails - model.save(); - } + //Save the model and watch for fails + model.save(); - //Add it to the list of models in progress - modelsInProgress.push(model); + //Add it to the list of models in progress + modelsInProgress.push(model); - }, this); + }, 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; + //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, "sysMetaUpdated", this.save); + //Update the system metadata + dataModel.updateSysMeta(); + //Add it to the list of models in progress + modelsInProgress.push(dataModel); + }, this); - //Do we need to update this resource map? - if(!this.needsUpdate()) return; + //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; + } - var requestType; + //Do we need to update this resource map? + if(!this.needsUpdate()) return; - //Set a new id and keep our old id - if(this.packageModel.isNew()){ - requestType = "POST"; - } - else{ - //Update the identifier for this object - this.packageModel.updateID(); - requestType = "PUT"; - } + //Determine the HTTP request type + var requestType; + //Set a new id and keep our old id + if(this.packageModel.isNew()){ + requestType = "POST"; + } + else{ + //Update the identifier for this object + this.packageModel.updateID(); + requestType = "PUT"; + } - //Create a FormData object to send data with the XHR - var formData = new FormData(); + //Create a FormData object to send data with the XHR + var formData = new FormData(); - //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")); - } + //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")); + } - try { - //Create the resource map XML - var mapXML = this.serialize(); - } - catch (serializationException) { + try { + //Create the resource map XML + var mapXML = this.serialize(); + } + catch (serializationException) { - //If serialization failed, revert back to our old id - this.packageModel.resetID(); + //If serialization failed, revert back to our old id + this.packageModel.resetID(); - this.trigger("errorSaving"); + this.trigger("errorSaving"); - return; - } + return; + } - var mapBlob = new Blob([mapXML], {type : 'application/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 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"); + //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"); + //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(); + //Create the system metadata + var sysMetaXML = this.packageModel.serializeSysMeta(); - //Send the system metadata - var xmlBlob = new Blob([sysMetaXML], {type : 'application/xml'}); + //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); + //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){ + 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()); + //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 the upload status for all members + _.each(collection.where({ uploadStatus: "c" }), function(m){ + m.set("uploadStatus", m.defaults().uploadStatus); + }); - //Reset the upload status for the package - collection.packageModel.set("uploadStatus", collection.packageModel.defaults().uploadStatus); + //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); + // Reset the content changes status + collection.packageModel.set("hasContentChanges", false); - collection.trigger("successSaving", collection); + collection.trigger("successSaving", collection); - collection.packageModel.fetch({merge: true}); + collection.packageModel.fetch({merge: true}); - }, - error: function(data){ + _.each(sysMetaToUpdate, function(dataModel){ + dataModel.set("sysMetaUploadStatus", "c"); + }); - //Reset the id back to its original state - collection.packageModel.resetID(); + }, + error: function(data){ - //Reset the upload status for all members - _.each(collection.where({ uploadStatus: "c" }), function(m){ - m.set("uploadStatus", m.defaults().uploadStatus); - }); + //Reset the id back to its original state + collection.packageModel.resetID(); - //Send this exception to Google Analytics - if(MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")){ - ga("send", "exception", { - "exDescription": "DataPackage save error: " + errorMsg + - " | Id: " + collection.packageModel.get("id") + " | v. " + MetacatUI.metacatUIVersion, - "exFatal": true + //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(); - } + //Send this exception to Google Analytics + if(MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")){ + ga("send", "exception", { + "exDescription": "DataPackage save error: " + errorMsg + + " | Id: " + collection.packageModel.get("id") + " | v. " + MetacatUI.metacatUIVersion, + "exFatal": true + }); + } - //Save the error message in the model - collection.packageModel.set("errorMessage", parsedResponse); + //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(); + } - //Reset the upload status for the package - collection.packageModel.set("uploadStatus", "e"); + //Save the error message in the model + collection.packageModel.set("errorMessage", parsedResponse); - collection.trigger("errorSaving", parsedResponse); - } - } - $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings())); + //Reset the upload status for the package + collection.packageModel.set("uploadStatus", "e"); + + collection.trigger("errorSaving", parsedResponse); + } + } + $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings())); }, /* @@ -1855,16 +1877,11 @@

Source: collections/DataPackage.js

} }, this); - // Since the provenance editor is run from the MetadataView, only - // the resource map will have to be updated (with the new prov rels), - // as no other editing is possible. Therefor we have to manually set - // the resource maps' new id so that the serialize() function will treat - // this as an update, not a new resource map. - //var oldId = this.dataPackage.packageModel.get("id"); - //var newId = "resource_map_" + "urn:uuid:" + uuid.v4(); - //this.dataPackage.packageModel.set("oldPid", oldId); - //this.dataPackage.packageModel.set("id", newId); - this.save(); + // 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 + }); }, @@ -2921,13 +2938,13 @@

Source: collections/DataPackage.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/collections_Filters.js.html b/docs/docs/collections_Filters.js.html index 189bcf0d2..540fdfe20 100644 --- a/docs/docs/collections_Filters.js.html +++ b/docs/docs/collections_Filters.js.html @@ -171,6 +171,10 @@

Source: collections/Filters.js

if( completeQuery.length && idFilterQuery.length ){ completeQuery = "(" + completeQuery + ")%20OR%20" + idFilterQuery; } + //If the query is ONLY made of id filters, then the id filter query is the complete query + else if( !completeQuery.length && idFilterQuery.length ){ + completeQuery = idFilterQuery; + } //Return the completed query return completeQuery; @@ -339,13 +343,13 @@

Source: collections/Filters.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/collections_ObjectFormats.js.html b/docs/docs/collections_ObjectFormats.js.html index 5e75b996f..f25e1376b 100644 --- a/docs/docs/collections_ObjectFormats.js.html +++ b/docs/docs/collections_ObjectFormats.js.html @@ -101,13 +101,13 @@

Source: collections/ObjectFormats.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/collections_QualityReport.js.html b/docs/docs/collections_QualityReport.js.html index 319c9c625..fff7c0530 100644 --- a/docs/docs/collections_QualityReport.js.html +++ b/docs/docs/collections_QualityReport.js.html @@ -210,13 +210,13 @@

Source: collections/QualityReport.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/collections_SolrResults.js.html b/docs/docs/collections_SolrResults.js.html index cbb52d6af..8c749da01 100644 --- a/docs/docs/collections_SolrResults.js.html +++ b/docs/docs/collections_SolrResults.js.html @@ -288,13 +288,13 @@

Source: collections/SolrResults.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/global.html b/docs/docs/global.html index 62f39d08d..0565a990b 100644 --- a/docs/docs/global.html +++ b/docs/docs/global.html @@ -175,6 +175,620 @@
Type:

Methods

+ + + + + + +

addKeyword(keyword, thesaurus)

+ + + + + + +
+ Add a keyword to the KeywordSet +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
keyword + + +* + + + + the keyword to add
thesaurus + + +* + + + + the thesaurus the keyword is a term in
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

addNewKeyword(e)

+ + + + + + +
+ Adds a new keyword to the KeywordSet +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
e + + +* + + + + the event triggered by adding a keyword
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

checkIncompatibility()

+ + + + + + +
+ Checks if the user's browser is an outdated version that won't work with +MetacatUI well, and displays a warning message to the user.. +The user agent is checked against the `unsupportedBrowsers` list in the AppModel. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

clearOldDrafts()

+ + + + + + +
+ Clear older drafts by iterating over the sorted list of drafts +stored by LocalForage and removing any beyond a hardcoded limit. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

friendlyTimeDiff(datetime:)

+ + + + + + +
+ Formats a time difference, in milliseconds, in a human-friendly way +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
datetime: + + +string + + + + A datetime as a string which needs to be +parsed before working with
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -188,21 +802,1319 @@

ge -
- Calls the monitor/status DataONE MN API and gets the size of the index queue. -
+
+ Calls the monitor/status DataONE MN API and gets the size of the index queue. +
+ + + + + + + + + +

Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
onSuccess + + +function + + + + + + <optional>
+ + + + + +
onError + + +function + + + + + + <optional>
+ + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

listenForActivity()

+ + + + + + +
+ Listens to the focus event on the window to detect when a user switches back to this browser tab from somewhere else +When a user checks back, we want to check for log-in status +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

listenForTimeout()

+ + + + + + +
+ Will determine the length of time until the user's current token expires, +and will set a window timeout for that length of time. When the timeout +is triggered, the sign in modal window will be displayed so that the user +can sign in again (which happens in AppView.showTimeoutSignIn()) +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

movePartyDown(partyModel:)

+ + + + + + +
+ Attempt to move a party one index forward within its sibling models +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
partyModel: + + +EMLParty + + + + The EMLParty model we're moving
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

movePartyUp(partyModel:)

+ + + + + + +
+ Attempt to move a party one index forward within its sibling models +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
partyModel: + + +EMLParty + + + + The EMLParty model we're moving
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

movePersonDown(e:)

+ + + + + + +
+ Attempt to move the current person (Party) one index forward (down). +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
e: + + +EventHandler + + + + The click event handler
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

movePersonUp(e:)

+ + + + + + +
+ Attempt to move the current person (Party) one index backward (up). +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
e: + + +EventHandler + + + + The click event handler
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

renderSupport() → {object}

+ + + + + + +
+ Render the support page +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ the rendered support page +
+ + + +
+
+ Type +
+
+ +object + + +
+
+ + + + + + + + + + + + + +

saveDraft()

+ + + + + + +
+ Save a draft of the parent EML model +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

showAlert(msg, classesopt, containeropt, delayopt, optionsopt)

+ + + + + + +
+ Displays the given message to the user in a Bootstrap "alert" style. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
msg + + +string +| + +Element + + + + + + + + + +
classes + + +string + + + + + + <optional>
+ + + + +
container + + +string +| +Element -
Parameters:
+ +
+ + <optional>
+ + + + + +
delay + + +boolean + + + + + + <optional>
+ + + + + +
options + + +object + + + + + + <optional>
+ + + + + +
+
Properties
+ + @@ -227,13 +2139,13 @@
Parameters:
- + - +
onSuccessemailBody -function +string @@ -260,13 +2172,13 @@
Parameters:
onErrorremove -function +boolean @@ -293,6 +2205,13 @@
Parameters:
+
+ @@ -327,7 +2246,98 @@
Parameters:
Source:
+ + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +

showTimeoutSignIn()

+ + + + + + +
+ If the user's auth token has expired, a new SignInView model window is +displayed so the user can sign back in. A listener is set on the appUserModel +so that when they do successfully sign back in, we set another timeout listener +via AppView.listenForTimeout() +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -373,13 +2383,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/index.html b/docs/docs/index.html index ca98fc499..0ea76f468 100644 --- a/docs/docs/index.html +++ b/docs/docs/index.html @@ -51,13 +51,13 @@


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_AccessRule.js.html b/docs/docs/models_AccessRule.js.html index 345ca31b9..9c1ba7c10 100644 --- a/docs/docs/models_AccessRule.js.html +++ b/docs/docs/models_AccessRule.js.html @@ -291,13 +291,13 @@

Source: models/AccessRule.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_AppModel.js.html b/docs/docs/models_AppModel.js.html index 7a10f578c..97736f5bc 100644 --- a/docs/docs/models_AppModel.js.html +++ b/docs/docs/models_AppModel.js.html @@ -123,7 +123,21 @@

Source: models/AppModel.js

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.", + /** + * 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} emlKeywordThesauri.label - A readable and short label for the keyword thesaurus that is displayed in the UI + * @property {string} emlKeywordThesauri.thesaurus - The exact keyword thesaurus name that will be saved in the EML + * @readonly + */ + emlKeywordThesauri: [{ + label: "GCMD", + thesaurus: "NASA Global Change Master Directory (GCMD)" + }], + baseUrl: window.location.origin || (window.location.protocol + "//" + window.location.host), + // the most likely item to change is the Metacat deployment context context: '/metacat', d1Service: '/d1/mn/v2', @@ -175,7 +189,16 @@

Source: models/AppModel.js

//signInUrl: null, signOutUrl: null, 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} + */ + enableLdapSignIn: false, signInUrlLdap: null, + tokenUrl: null, checkTokenUrl: null, accountsUrl: null, @@ -189,7 +212,7 @@

Source: models/AppModel.js

// suidIds and suiteLables must be specified as a list, even if only one suite is available. mdqSuiteIds: ["FAIR.suite.1"], mdqSuiteLabels: ["FAIR Suite v1.0"], - // Quality suites for aggregated quality scores (i.e. metrics tab) + // Quality suites for aggregated quality scores (i.e. metrics tab) mdqAggregatedSuiteIds: ["FAIR.suite.1"], mdqAggregatedSuiteLabels: ["FAIR Suite v1.0"], mdqFormatIds:["eml*", "https://eml*", "*isotc211*"], @@ -201,10 +224,10 @@

Source: models/AppModel.js

metricsUrl: 'https://logproc-stage-ucsb-1.test.dataone.org/metrics', // Metrics Falgs for the /profile view (summary view) - hideSummaryCitationsChart: false, - hideSummaryDownloadsChart: false, + hideSummaryCitationsChart: true, + hideSummaryDownloadsChart: true, hideSummaryMetadataAssessment: true, - hideSummaryViewsChart: false, + hideSummaryViewsChart: true, /** * Metrics flag for the Dataset Landing Page @@ -242,6 +265,23 @@

Source: models/AppModel.js

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} + */ + 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 their objects. * @type {boolean} @@ -253,7 +293,7 @@

Source: models/AppModel.js

* Each literal object here gets set directly on an AccessRule model. * See the AccessRule model list of default attributes for options on what to set here. * @see {@link AccessRule} - * @type {object} + * @type {object[]} */ defaultAccessPolicy: [{ subject: "public", @@ -511,7 +551,14 @@

Source: models/AppModel.js

* Set this option to true to display the annotation icon in search result rows when a dataset has an annotation * @type {boolean} */ - showAnnotationIndicator: false + 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[]} + */ + unsupportedBrowsers: [/(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:)(\d+)/] /** * The following configuration options are deprecated or experimental and should only be changed by advanced users @@ -551,7 +598,7 @@

Source: models/AppModel.js

if( this.get("enableMonitorStatus") ){ this.set("monitorStatusUrl", this.get('baseUrl') + this.get('context') + this.get('d1Service') + "/monitor/status"); } - + // Metadata quality report services this.set('mdqSuitesServiceUrl', this.get("mdqBaseUrl") + "/suites/"); this.set('mdqRunsServiceUrl', this.get('mdqBaseUrl') + "/runs/"); @@ -595,8 +642,11 @@

Source: models/AppModel.js

this.set("signInUrl", this.get('portalUrl') + "startRequest?target="); if(typeof this.get("signInUrlOrcid") !== "undefined") this.set("signInUrlOrcid", this.get('portalUrl') + "oauth?action=start&target="); - if(typeof this.get("signInUrlLdap") !== "undefined") + + if(this.get("enableLdapSignIn") && !this.get("signInUrlLdap")){ this.set("signInUrlLdap", this.get('portalUrl') + "ldap?target="); + } + if(this.get('orcidBaseUrl')) this.set('orcidSearchUrl', this.get('orcidBaseUrl') + '/v1.1/search/orcid-bio?q='); @@ -626,7 +676,7 @@

Source: models/AppModel.js

//The package service for v2 DataONE API this.set('packageServiceUrl', this.get('baseUrl') + this.get('context') + this.get('d1Service') + '/packages/application%2Fbagit-097/'); - + // Metadata quality report services this.set('mdqSuitesServiceUrl', this.get("mdqBaseUrl") + "/suites/"); this.set('mdqRunsServiceUrl', this.get('mdqBaseUrl') + "/runs/"); @@ -653,13 +703,13 @@

Source: models/AppModel.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_CollectionModel.js.html b/docs/docs/models_CollectionModel.js.html index f37e3bb80..d4fb94400 100644 --- a/docs/docs/models_CollectionModel.js.html +++ b/docs/docs/models_CollectionModel.js.html @@ -647,13 +647,13 @@

Source: models/CollectionModel.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_DataONEObject.js.html b/docs/docs/models_DataONEObject.js.html index 30a5c6455..bec0a1a7a 100644 --- a/docs/docs/models_DataONEObject.js.html +++ b/docs/docs/models_DataONEObject.js.html @@ -94,6 +94,7 @@

Source: models/DataONEObject.js

synced: false, // True if the full model has been synced uploadStatus: null, //c=complete, p=in progress, q=queued, e=error, no upload status=not in queue uploadProgress: null, + sysMetaUploadStatus: null, //c=complete, p=in progress, q=queued, e=error, 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, @@ -682,6 +683,9 @@

Source: models/DataONEObject.js

//Update the upload status to "p" for "in progress" this.set("uploadStatus", "p"); + //Update the system metadata upload status to "p" as well, so the app + // knows that the system metadata, specifically, is being updated. + this.set("sysMetaUploadStatus", "p"); var formData = new FormData(); @@ -715,6 +719,7 @@

Source: models/DataONEObject.js

//Update the upload status to "c" for "complete" model.set("uploadStatus", "c"); + model.set("sysMetaUploadStatus", "c"); //Trigger a custom event that the sys meta was updated model.trigger("sysMetaUpdated"); @@ -743,6 +748,7 @@

Source: models/DataONEObject.js

model.set("errorMessage", parsedResponse); model.set("uploadStatus", "e"); + model.set("sysMetaUploadStatus", "e"); //Send this exception to Google Analytics if (MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")) { @@ -1945,13 +1951,13 @@

Source: models/DataONEObject.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_LookupModel.js.html b/docs/docs/models_LookupModel.js.html index b02cddca6..7b6881b5e 100644 --- a/docs/docs/models_LookupModel.js.html +++ b/docs/docs/models_LookupModel.js.html @@ -604,13 +604,13 @@

Source: models/LookupModel.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_Map.js.html b/docs/docs/models_Map.js.html index 0445f56e7..10760772a 100644 --- a/docs/docs/models_Map.js.html +++ b/docs/docs/models_Map.js.html @@ -233,13 +233,13 @@

Source: models/Map.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_PackageModel.js.html b/docs/docs/models_PackageModel.js.html index 4d575799f..cf6f74f5f 100644 --- a/docs/docs/models_PackageModel.js.html +++ b/docs/docs/models_PackageModel.js.html @@ -1519,13 +1519,13 @@

Source: models/PackageModel.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_Search.js.html b/docs/docs/models_Search.js.html index 038f449d4..2f7d5e5a0 100644 --- a/docs/docs/models_Search.js.html +++ b/docs/docs/models_Search.js.html @@ -1130,13 +1130,13 @@

Source: models/Search.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_SolrResult.js.html b/docs/docs/models_SolrResult.js.html index 7a41e9a17..c9ea0aea0 100644 --- a/docs/docs/models_SolrResult.js.html +++ b/docs/docs/models_SolrResult.js.html @@ -415,6 +415,13 @@

Source: models/SolrResult.js

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){ @@ -826,13 +833,13 @@

Source: models/SolrResult.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_Stats.js.html b/docs/docs/models_Stats.js.html index acd49b25f..6887162ee 100644 --- a/docs/docs/models_Stats.js.html +++ b/docs/docs/models_Stats.js.html @@ -59,25 +59,17 @@

Source: models/Stats.js

dataUpdateDates: null, metadataUpdateDates: null, - downloads: 0, - metadataDownloads: null, - dataDownloads: null, - metadataDownloadDates: null, - dataDownloadDates: null, - firstBeginDate: 0, temporalCoverage: 0, coverageYears: 0, - hideMetadataAssessment: false, + hideMetadataAssessment: false, mdqScoresImage: 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: 1958, - usePOST: false, - - supportDownloads: (MetacatUI.appModel.get("nodeId") && MetacatUI.appModel.get("d1LogServiceUrl")) + usePOST: false }, //Some dated used for query creation @@ -121,9 +113,6 @@

Source: models/Stats.js

} })(); - //this.on("change:dataDownloads", this.sumDownloads); - //this.on("change:metadataDownloads", this.sumDownloads); - //Set the request type (GET or POST) this.setRequestType(); this.on("change:query", this.setRequestType); @@ -148,11 +137,11 @@

Source: models/Stats.js

this.getFormatTypes(); - this.getDownloadDates(); - - // Only get the Mdq scores if the hideMetadataAssessment is set to false - if (!this.get("hideMetadataAssessment")) + // 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(); + } }, @@ -639,7 +628,7 @@

Source: models/Stats.js

var now = new Date(); - var metadataQueryParams = "AND -obsoletedBy:* AND formatType:METADATA AND -formatId:*dataone.org/collections* AND -formatId:*dataone.org/portals*"; + var metadataQueryParams = " AND -obsoletedBy:* AND formatType:METADATA AND -formatId:*dataone.org/collections* AND -formatId:*dataone.org/portals*"; var metadataQuery = this.get('query') + metadataQueryParams; var firstPossibleUpdate = MetacatUI.nodeModel.isCN(MetacatUI.nodeModel.get("currentMemberNode"))? @@ -1070,162 +1059,6 @@

Source: models/Stats.js

$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings())); }, - /* - * The Downloads query can be customized only by: filtering by a certain MN or filtering by a rightsHolder - */ - - getDataDownloadDates: function(){ - if(!MetacatUI.appModel.get("d1LogServiceUrl")){ - this.set("downloads", 0); - this.trigger("change:downloads"); - return; - } - - var model = this; - - var logSearch = new LogsSearch(); - logSearch.set("event", "read"); - logSearch.set("formatType", "DATA"); - logSearch.set("facetRanges", ["dateLogged"]); - - var searchModel = this.get("searchModel"); - if(searchModel && searchModel.get("dataSource")){ - logSearch.set("nodeId", searchModel.get("dataSource")) - } - if(searchModel && searchModel.get("username")){ - logSearch.set("username", searchModel.get("username")); - } - - var requestSettings = { - url: MetacatUI.appModel.get("d1LogServiceUrl") + "q=" + logSearch.getQuery() + logSearch.getFacetQuery() + "&wt=json&rows=0", - type: "GET", - dataType: "json", - success: function(data, textStatus, xhr){ - var counts = data.facet_counts.facet_ranges.dateLogged.counts; - model.set("dataDownloads", data.response.numFound); - model.set("dataDownloadDates", counts); - - if(data.response.numFound == 0) model.trigger("change:dataDownloads"); - } - } - - $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings())); - }, - - getMetadataDownloadDates: function(){ - if(!MetacatUI.appModel.get("d1LogServiceUrl")){ - this.set("downloads", 0); - this.trigger("change:downloads"); - - return; - } - - var model = this; - - var logSearch = new LogsSearch(); - logSearch.set("event", "read"); - logSearch.set("formatType", "METADATA"); - logSearch.set("facetRanges", ["dateLogged"]); - - var searchModel = this.get("searchModel"); - if(searchModel && searchModel.get("dataSource")){ - logSearch.set("nodeId", searchModel.get("dataSource")) - } - if(searchModel && searchModel.get("username")){ - logSearch.set("username", searchModel.get("username")); - } - - var requestSettings = { - url: MetacatUI.appModel.get("d1LogServiceUrl") + "q=" + logSearch.getQuery() + logSearch.getFacetQuery() + "&wt=json&rows=0", - type: "GET", - dataType: "json", - success: function(data, textStatus, xhr){ - var counts = data.facet_counts.facet_ranges.dateLogged.counts; - - model.set("metadataDownloads", data.response.numFound); - model.set("metadataDownloadDates", counts); - - if(data.response.numFound == 0) model.trigger("change:metadataDownloads"); - } - } - - $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings())); - }, - - getDownloadDates: function(){ - if( !this.get("supportDownloads") ){ - this.set("downloads", 0); - this.trigger("change:downloads"); - return; - } - - var model = this; - - var logSearch = new LogsSearch(); - logSearch.set("event", "read"); - logSearch.set("formatType", ["METADATA", "DATA"]); - logSearch.set("facetRanges", ["dateLogged"]); - logSearch.set("facets", ["formatType"]); - - var today = new Date(); - today.setDate(today.getUTCDay() + 1); - today.setHours(0); - today.setMinutes(0); - today.setSeconds(0); - today.setMilliseconds(0); - - logSearch.set("facetRangeStart", this.firstPossibleDataONEDownload); - logSearch.set("facetRangeEnd", today.toISOString()); - - var searchModel = this.get("searchModel"); - if(searchModel && searchModel.get("dataSource")){ - logSearch.set("nodeId", searchModel.get("dataSource")) - } - if(searchModel && searchModel.get("username")){ - logSearch.set("username", searchModel.get("username")); - } - - var requestSettings = { - url: MetacatUI.appModel.get("d1LogServiceUrl") + "q=" + logSearch.getQuery() + logSearch.getFacetQuery() + "&wt=json&rows=0", - type: "GET", - dataType: "json", - success: function(data, textStatus, xhr){ - var counts = data.facet_counts.facet_ranges.dateLogged.counts; - - var m_index = _.indexOf(data.facet_counts.facet_fields.formatType, "METADATA"); - if(m_index > -1) - model.set("metadataDownloads", data.facet_counts.facet_fields.formatType[m_index+1]); - else - model.set("metadataDownloads", 0); - - var d_index = _.indexOf(data.facet_counts.facet_fields.formatType, "DATA"); - if(d_index > -1) - model.set("dataDownloads", data.facet_counts.facet_fields.formatType[d_index+1]); - else - model.set("dataDownloads", 0); - - if(data.facet_counts.facet_fields.formatType[m_index+1] == 0) model.trigger("change:metadataDownloads"); - if( data.facet_counts.facet_fields.formatType[d_index+1] == 0) model.trigger("change:dataDownloads"); - - - model.set("downloads", data.response.numFound); - if(data.response.numFound == 0) model.trigger("change:downloads"); - - model.set("downloadDates", counts); - } - } - - $.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings())); - }, - - sumDownloads: function(){ - if((this.get("dataDownloads") == null) || (this.get("metadataDownloads") == null)) return; - - this.set("downloads", this.get("dataDownloads") + this.get("metadataDownloads")); - - if(this.get("downloads") == 0) this.trigger("change:downloads"); - }, - imgLoad: function(url) { // Create new promise with the Promise() constructor; // This has as its argument a function with two parameters, resolve and reject @@ -1298,7 +1131,7 @@

Source: models/Stats.js

catch(e){ this.set("mdqScoresImage", this.defaults.mdqScoresImage); this.trigger("change:mdqScoresImage"); - console.error("Cannot get the Metadata Quality scores: ", e); + console.error("Cannot get the Metadata Assessment scores: ", e); } }, @@ -1326,13 +1159,13 @@

Source: models/Stats.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_UserModel.js.html b/docs/docs/models_UserModel.js.html index 7a3a40c8c..71a8f862c 100644 --- a/docs/docs/models_UserModel.js.html +++ b/docs/docs/models_UserModel.js.html @@ -638,6 +638,9 @@

Source: models/UserModel.js

var requestSettings = { type: "GET", url: url, + headers: { + "Cache-Control": "no-cache" + }, success: function(data, textStatus, xhr){ if(data){ // the response should have the token @@ -999,6 +1002,35 @@

Source: models/UserModel.js

return; } + }, + + /** + * 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; + } + + return _.intersection(this.get("allIdentitiesAndGroups"), subjects).length; + } + catch(e){ + console.error(e); + return false; + } + }, reset: function(){ @@ -1019,13 +1051,13 @@

Source: models/UserModel.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_filters_BooleanFilter.js.html b/docs/docs/models_filters_BooleanFilter.js.html index 261444119..018255e4a 100644 --- a/docs/docs/models_filters_BooleanFilter.js.html +++ b/docs/docs/models_filters_BooleanFilter.js.html @@ -142,13 +142,13 @@

Source: models/filters/BooleanFilter.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_filters_ChoiceFilter.js.html b/docs/docs/models_filters_ChoiceFilter.js.html index a84e398de..f6f8b2718 100644 --- a/docs/docs/models_filters_ChoiceFilter.js.html +++ b/docs/docs/models_filters_ChoiceFilter.js.html @@ -64,7 +64,7 @@

Source: models/filters/ChoiceFilter.js

}); }, - /* + /** * Parses the choiceFilter XML node into JSON * * @param {Element} xml - The XML Element that contains all the ChoiceFilter elements @@ -190,13 +190,13 @@

Source: models/filters/ChoiceFilter.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_filters_DateFilter.js.html b/docs/docs/models_filters_DateFilter.js.html index 561c4b4f9..6ea6308b0 100644 --- a/docs/docs/models_filters_DateFilter.js.html +++ b/docs/docs/models_filters_DateFilter.js.html @@ -452,13 +452,13 @@

Source: models/filters/DateFilter.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_filters_Filter.js.html b/docs/docs/models_filters_Filter.js.html index 7c586ce72..5bf46ff78 100644 --- a/docs/docs/models_filters_Filter.js.html +++ b/docs/docs/models_filters_Filter.js.html @@ -369,12 +369,15 @@

Source: models/filters/Filter.js

//Escape special characters value = this.escapeSpecialChar(value); - //Add the value to the query string. Wrap in wildcards, if specified - if( value.indexOf(" ") > -1 ){ - value = "\"" + value + "\""; - } - if( this.get("matchSubstring") ){ + 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); + + //If the value is a search phrase (more than one word), and not a date range string, wrap in quotes + if( value.indexOf(" ") > -1 && !isDateRange ){ + valuesQueryString = "\"" + value + "\""; + } + else if( this.get("matchSubstring") && !isDateRange ){ //Look for existing wildcard characters at the end of the value string if( value.match( /^\*|\*$/ ) ){ @@ -740,13 +743,13 @@

Source: models/filters/Filter.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_filters_FilterGroup.js.html b/docs/docs/models_filters_FilterGroup.js.html index 16af15067..a78d063e2 100644 --- a/docs/docs/models_filters_FilterGroup.js.html +++ b/docs/docs/models_filters_FilterGroup.js.html @@ -226,13 +226,13 @@

Source: models/filters/FilterGroup.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_filters_NumericFilter.js.html b/docs/docs/models_filters_NumericFilter.js.html index 0f49c9aa8..266951446 100644 --- a/docs/docs/models_filters_NumericFilter.js.html +++ b/docs/docs/models_filters_NumericFilter.js.html @@ -421,13 +421,13 @@

Source: models/filters/NumericFilter.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_filters_SpatialFilter.js.html b/docs/docs/models_filters_SpatialFilter.js.html index 24eecdb77..f24939a80 100644 --- a/docs/docs/models_filters_SpatialFilter.js.html +++ b/docs/docs/models_filters_SpatialFilter.js.html @@ -181,13 +181,13 @@

Source: models/filters/SpatialFilter.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_filters_ToggleFilter.js.html b/docs/docs/models_filters_ToggleFilter.js.html index e62a65f2b..3ebcb8751 100644 --- a/docs/docs/models_filters_ToggleFilter.js.html +++ b/docs/docs/models_filters_ToggleFilter.js.html @@ -172,13 +172,13 @@

Source: models/filters/ToggleFilter.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_metadata_eml211_EML211.js.html b/docs/docs/models_metadata_eml211_EML211.js.html new file mode 100644 index 000000000..986d3d37b --- /dev/null +++ b/docs/docs/models_metadata_eml211_EML211.js.html @@ -0,0 +1,2118 @@ + + + + + MetacatUI Dev Docs: Source: models/metadata/eml211/EML211.js + + + + + + + + + + + +
+ +

Source: models/metadata/eml211/EML211.js

+ + + + + + +
+
+
/* 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'],
+    function($, _, Backbone, uuid, Units, ScienceMetadata, DataONEObject,
+        EMLGeoCoverage, EMLKeywordSet, EMLTaxonCoverage, EMLTemporalCoverage,
+        EMLDistribution, EMLEntity, EMLDataTable, EMLOtherEntity, EMLParty,
+            EMLProject, EMLText, EMLMethods) {
+
+      /*
+      An EML211 object represents an Ecological Metadata Language
+      document, version 2.1.1
+      */
+      var EML211 = ScienceMetadata.extend({
+
+        type: "EML",
+
+        defaults: function(){
+          return _.extend(ScienceMetadata.prototype.defaults(), {
+            id: "urn:uuid:" + uuid.v4(),
+            formatId: "eml://ecoinformatics.org/eml-2.1.1",
+            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/.",
+              onlineDist: [], // array of EMLOnlineDist objects
+              offlineDist: [], // array of EMLOfflineDist 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: null, // An EMLMethods objects
+              project: null // An EMLProject object
+          });
+        },
+
+        units: new Units(),
+
+        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());
+
+            this.on("sync", function(){
+              this.set("synced", true);
+            });
+
+            //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",
+                "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",
+                "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",
+                "licenseurl" : "licenseURL",
+                "linenumber" : "lineNumber",
+                "literalcharacter" : "literalCharacter",
+                "literallayout" : "literalLayout",
+                "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",
+                "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",
+                "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"
+              }
+          );
+        },
+
+        /* Fetch the EML from the MN object service */
+        fetch: function(options) {
+          if( ! options ) var options = {};
+
+          //Add the authorization header and other AJAX settings
+           _.extend(options, MetacatUI.appUserModel.createAjaxSettings(), {dataType: "text"});
+
+            // Merge the system metadata into the object first
+            _.extend(options, {merge: true});
+            this.fetchSystemMetadata(options);
+
+            //If we are retrieving system metadata only, then exit now
+            if(options.sysMeta)
+              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"],
+              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
+              }));
+            }
+            //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
+                      }));
+                });
+              }
+
+              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
+                        }));
+                });
+
+              }
+
+            }
+                //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
+                    }));
+
+                }
+          else if(_.contains(emlMethods, thisNode.localName)) {
+            if(typeof modelJSON[thisNode.localName] === "undefined") modelJSON[thisNode.localName] = [];
+
+            modelJSON[thisNode.localName] =  new EMLMethods({
+              objectDOM: thisNode,
+              parentModel: model
+            });
+
+          }
+          //Parse keywords
+          else if(thisNode.localName == "keywordset"){
+            //Start an array of keyword sets
+            if(typeof modelJSON["keywordSets"] == "undefined") modelJSON["keywordSets"] = [];
+
+            modelJSON["keywordSets"].push(new EMLKeywordSet({
+              objectDOM: thisNode,
+              parentModel: model
+            }));
+          }
+          //Parse intellectual rights
+          else if(thisNode.localName == "intellectualrights"){
+            var value = "";
+
+            if($(thisNode).children("para").length == 1)
+              value = $(thisNode).children("para").first().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)){
+
+            //Start an array of Entities
+            if(typeof modelJSON["entities"] == "undefined")
+              modelJSON["entities"] = [];
+
+            //Create the model
+            var entityModel;
+            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 EMLOtherEntity({
+                  objectDOM: thisNode,
+                  parentModel: model,
+                          entityType: "application/octet-stream"
+                }, {
+                  parse: true
+                });
+            }
+
+            modelJSON["entities"].push(entityModel);
+          }
+          else{
+            //Is this a multi-valued field in EML?
+            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]))
+                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);
+          }
+
+        }
+
+        return modelJSON;
+      },
+
+      /*
+       * 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(){
+
+        //Get the EML document
+        var xmlString   = this.get("objectXML"),
+            eml         = $.parseHTML(xmlString),
+            datasetNode = $(eml).filter("eml\\:eml").find("dataset");
+
+        //Update the packageId on the eml node with the EML id
+        $(eml).attr("packageId", this.get("id"));
+
+        var nodeNameMap = this.nodeNameMap();
+
+        //Serialize the basic text fields
+        var basicText = ["alternateIdentifier", "title"];
+        _.each(basicText, function(fieldName){
+        var basicTextValues = this.get(fieldName);
+
+        if(!Array.isArray(basicTextValues))
+          basicTextValues = [basicTextValues];
+
+        // Remove existing nodes
+        datasetNode.find(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 "";
+          }
+        });
+
+        var insertAfter = this.getEMLPosition(eml, fieldName.toLowerCase());
+
+        if(insertAfter){
+          insertAfter.after(nodes);
+        }
+        else{
+          datasetNode.prepend(nodes);
+        }
+
+      }, 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];
+          }
+
+          $(node).html( $(thisTextModel.updateDOM() ).html());
+
+        }, this);
+
+        // Remove the extra nodes
+        this.removeExtraNodes(nodes, emlTextModels);
+
+      }, this);
+
+      //Create a <coverage> 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");
+
+        //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());
+            }
+            //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"));
+
+        }
+        //If all the taxon coverages are empty, remove the parent taxonomicCoverage node
+        else if( !sortedTaxonModels["notEmpty"] || sortedTaxonModels["notEmpty"].length == 0 ){
+          existingTaxonCov.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());
+        }
+        //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 <coverage> node if it's empty
+      if(coverageNode.children().length == 0){
+        coverageNode.remove();
+      }
+
+      //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();
+
+       this.set("creator", [party]);
+      }
+
+      //Serialize the creators
+      this.serializeParties(eml, "creator");
+
+      //Serialize the metadata providers
+      this.serializeParties(eml, "metadataProvider");
+
+      //Serialize the associated parties
+      this.serializeParties(eml, "associatedParty");
+
+      //Serialize the contacts
+      this.serializeParties(eml, "contact");
+
+      //Serialize the publishers
+      this.serializeParties(eml, "publisher");
+
+      // 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 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 <methods> node to the EML
+            datasetNode.find("methods").detach();
+
+            var insertAfter = this.getEMLPosition(eml, "methods");
+
+            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();
+        }
+
+      }
+
+      //Serialize the keywords
+      this.serializeKeywords(eml, "keywordSets");
+
+      //Serialize the intellectual rights
+      if(this.get("intellectualRights")){
+        if(datasetNode.find("intellectualRights").length)
+          datasetNode.find("intellectualRights").html("<para>" + this.get("intellectualRights") + "</para>")
+        else{
+
+          this.getEMLPosition(eml, "intellectualrights").after(
+              $(document.createElement("intellectualRights"))
+                .html("<para>" + this.get("intellectualRights") + "</para>"));
+        }
+      }
+
+      //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");
+
+      //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(eml, 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).find(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).find(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();
+
+         //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);
+
+         this.set(type, contacts);
+
+         //Call this function again to serialize the new models
+         this.serializeParties(eml, type);
+       }
+     },
+
+
+      serializeKeywords: function(eml) {
+        // Remove all existing keywordSets before appending
+        $(eml).find('dataset').find('keywordset').remove();
+
+        if (this.get('keywordSets').length == 0) return;
+
+        // Create the new keywordSets nodes
+        var nodes = _.map(this.get('keywordSets'), function(kwd) {
+          return kwd.updateDOM();
+        });
+
+            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){
+        // 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 EML document to the server using the DataONE API
+       */
+      save: function(attributes, options){
+
+        //Validate before we try anything else
+        if(!this.isValid()){
+          this.trigger("invalid");
+          this.trigger("cancelSave");
+          return false;
+        }
+        else{
+          this.trigger("valid");
+        }
+
+        this.setFileName();
+
+        //Set the upload transfer as in progress
+        this.set("uploadStatus", "p");
+
+        //Reset the draftSaved attribute
+        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" });
+
+         party.createFromUser();
+
+         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" });
+
+         party.createFromUser();
+
+         this.set("contact", [party]);
+        }
+
+        //If this is an existing object and there is no system metadata, retrieve it
+        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);
+             }
+          }
+
+          //Fetch the system metadata now
+          this.fetchSystemMetadata(fetchOptions);
+
+          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.fetch({merge: true, sysMeta: 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);
+
+            });
+
+            //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);
+
+            });
+
+            //Save the EML plain text copy
+            copy.save();
+
+            //Send this exception to Google Analytics
+            if(MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")){
+              ga('send', 'exception', {
+                'exDescription': "EML save error: " + errorMsg + " | Id: " + model.get("id") +
+                  " | v. " + MetacatUI.metacatUIVersion + " | EML draft: " + copy.get("id"),
+                'exFatal': 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() {
+        var errors = {};
+
+        //A title is always required by EML
+        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."]
+          }
+        }
+
+        // Validate the temporal coverage
+        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 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." }];
+        }
+        //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();
+                           }) )) {
+          //Iterate over each temporal coverage and add it's validation errors
+          _.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 ){
+          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);
+
+        //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);
+
+        //Validate the EMLTaxonCoverage model
+        var taxonModel = this.get("taxonCoverage")[0];
+
+        if( !taxonModel.isEmpty() && !taxonModel.isValid() ){
+          errors = _.extend(errors, taxonModel.validationError);
+        }
+        else if( taxonModel.isEmpty() &&
+          this.get("taxonCoverage").length == 1 &&
+          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 )
+              errors.entities = [entityModel.validationError];
+            else
+              errors.entities.push(entityModel.validationError);
+          }
+
+        });
+
+        //Check the required fields for this MetacatUI configuration
+        if(MetacatUI.appModel.get("emlEditorRequiredFields")){
+            _.each(Object.keys(MetacatUI.appModel.get("emlEditorRequiredFields")), function(key){
+              var isRequired = MetacatUI.appModel.get("emlEditorRequiredFields")[key];
+
+              //If it's not required, then exit
+              if(!isRequired) return;
+
+              if(key == "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(key == "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(key == "geoCoverage"){
+                if(!this.get("geoCoverage").length)
+                  errors.geoCoverage = "At least one location is required.";
+              }
+              else if(key == "intellectualRights"){
+                if( !this.get("intellectualRights") )
+                  errors.intellectualRights = "Select usage rights for this data set.";
+              }
+              else if(key == "studyExtentDescription"){
+                if( !this.get("methods") || !this.get("methods").get("studyExtentDescription") )
+                  errors.studyExtentDescription = "Provide a study extent description.";
+              }
+              else if(key == "samplingDescription"){
+                if( !this.get("methods") || !this.get("methods").get("samplingDescription") )
+                  errors.samplingDescription = "Provide a sampling description.";
+              }
+              else if(key == "temporalCoverage"){
+                if(!this.get("temporalCoverage").length)
+                  errors.temporalCoverage = "Provide the date(s) for this data set.";
+              }
+              else if(key == "taxonCoverage"){
+                if(!this.get("taxonCoverage").length)
+                  errors.taxonCoverage = "At least one taxa rank and value is required.";
+              }
+              else if(key == "keywordSets"){
+                if( !this.get("keywordSets").length )
+                  errors.keywordSets = "Provide at least one keyword.";
+              }
+              else if(key == "methods"){
+                if(!this.get("methods"))
+                  errors.methods = "At least one method step is required.";
+              }
+              else if(key == "funding"){
+                if(!this.get("project") || !this.get("project").get("funding").length)
+                  errors.funding = "Provide at least one project funding number or name.";
+              }
+              else if(key == "abstract"){
+                if(!this.get("abstract").length)
+                  errors["abstract"] = "Provide an abstract.";
+              }
+              else if( !this.get(key) || (Array.isArray(this.get(key)) && !this.get(key).length) ){
+                errors[key] = "Provide a " + key + ".";
+              }
+            }, this);
+
+        }
+
+        if( Object.keys(errors).length )
+          return errors;
+        else{
+          return;
+        }
+      },
+
+      /* Returns a boolean for whether the argument 'value' is a valid
+      value for EML's yearDate type which is used in a few places.
+
+      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));
+      },
+
+      /*
+       * 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 = {};
+        else options = _.clone(options);
+
+        var model = this,
+          fetchOptions = _.extend({
+            url: MetacatUI.appModel.get("metaServiceUrl") + this.get("id"),
+            dataType: "text",
+            success: function(response){
+              model.set(DataONEObject.prototype.parse.call(model, response));
+            },
+            error: function(){
+              model.trigger('error');
+            }
+          }, options);
+
+          //Add the authorization header and other AJAX settings
+          _.extend(fetchOptions, MetacatUI.appUserModel.createAjaxSettings());
+
+          $.ajax(fetchOptions);
+      },
+
+      /*
+       * Returns the node in the given EML document that the given node type should be inserted after
+       */
+      getEMLPosition: function(eml, nodeName) {
+        var nodeOrder = ["alternateidentifier", "shortname", "title", "creator", "metadataprovider", "associatedparty",
+                        "pubdate", "language", "series", "abstract", "keywordset", "additionalinfo", "intellectualrights",
+                        "distribution", "coverage", "purpose", "maintenance", "contact", "publisher", "pubplace",
+                        "methods", "project", "datatable", "spatialraster", "spatialvector", "storedprocedure", "view", "otherentity"];
+          var entityNodes = ["datatable", "spatialraster", "spatialvector", "storedprocedure", "view", "otherentity"];
+          var isEntityNode = _.contains(entityNodes, nodeName);
+          var position = _.indexOf(nodeOrder, nodeName.toLowerCase());
+          if ( position == -1 ) {
+              return false;
+          }
+
+          //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 ( $(eml).find(nodeOrder[i]).length ) {
+                  // Handle non-entity nodes
+                  if ( ! isEntityNode ) {
+                      return $(eml).find("dataset").children(nodeOrder[i]).last();
+                  } else {
+                      // Handle entity nodes by returning the
+                      // last child of the parent <dataset> since
+                      // entities have a {0..n}+ model
+                      // (i.e optional, repeatable, no specific order)
+                      return $(eml).find("dataset").children().last();
+                  }
+              }
+        }
+
+        return false;
+      },
+
+      /*
+       * 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;
+
+        //If nothing else has been changed, then this object hasn't had any updates
+        return false;
+      },
+
+      /*
+       Add an entity into the EML 2.1.1 object
+      */
+      addEntity: function(emlEntity, position) {
+        //Get the current list of entities
+        var currentEntities = this.get("entities");
+
+        if( typeof position == "undefined" || position == -1)
+          currentEntities.push(emlEntity);
+        else
+          //Add the entity model to the entity array
+          currentEntities.splice(position, 0, emlEntity);
+
+        this.trigger("change:entities");
+
+        this.trickleUpChange();
+
+        return this;
+      },
+
+      /*
+       Remove an entity from the EML 2.1.1 object
+      */
+      removeEntity: function(emlEntity) {
+          if(!emlEntity || typeof emlEntity != "object")
+            return;
+
+        //Get the current list of entities
+        var entities = this.get("entities");
+
+        entities = _.without(entities, emlEntity);
+
+        this.set("entities", entities);
+      },
+
+      /*
+       * Find the entity model for a given DataONEObject
+       */
+      getEntity: function(dataONEObj){
+
+        //If an EMLEntity model has been found for this object before, then return it
+        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;
+
+          // 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 &&
+            ((fileNameFromEML == dataONEObj.get("fileName")) ||
+              (fileNameFromEML.replace(/ /g, "_") == dataONEObj.get("fileName"))) ){
+
+            //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);
+
+        //If we found an entity, give it an ID and return it
+        if(entity){
+
+          //If this entity has been matched to another DataONEObject already, then don't match it again
+          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") ){
+            return;
+          }
+          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);
+
+            //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")));
+        });
+
+        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];
+        }
+
+        //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];
+          }
+
+        }
+
+        return false;
+
+      },
+
+      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()
+          });
+
+          this.addEntity(entityModel);
+
+          //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"))
+            });
+          });
+
+      },
+
+      /*
+      * 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();
+
+        //Get the EML string, if there is one, to check if this id already exists
+        var emlString = this.get("objectXML");
+
+        //If this id already exists in the EML...
+        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){
+        //The list of attributes this EMLParty might be stored in
+        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);
+      },
+
+      /**
+       * Attempt to move a party one index forward within its sibling models
+       *
+       * @param {EMLParty} partyModel: The EMLParty model we're moving
+       */
+      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));
+
+          // 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 === -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);
+      },
+
+      /**
+       * Attempt to move a party one index forward within its sibling models
+       *
+       * @param {EMLParty} partyModel: The EMLParty model we're moving
+       */
+      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;
+          });
+
+          if (index === -1) {
+            // Couldn't find the model
+            return;
+          }
+
+          // 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){
+
+        //If the EMLParty model is empty, don't add it to the EML211 model
+        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( typeof position == "undefined" ){
+          this.get(role).push(partyModel);
+        }
+        else {
+          this.get(role).splice(position, 0, partyModel);
+        }
+
+        this.trigger("change:" + role);
+
+        return true;
+      },
+
+      createUnits: function(){
+        this.units.fetch();
+      },
+
+      /* Initialize the object XML for brand spankin' new EML objects */
+      createXML: function() {
+          var xml = "<eml:eml xmlns:eml=\"eml://ecoinformatics.org/eml-2.1.1\"></eml:eml>",
+              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", "eml://ecoinformatics.org/eml-2.1.1 eml.xsd");
+              eml.attr("packageId", this.get("id"));
+              eml.attr("system", "knb"); // We could make this configurable at some point
+
+              // Add the dataset
+              eml.append(document.createElement("dataset"));
+              eml.find("dataset").append(document.createElement("title"));
+
+              emlString = $(document.createElement("div")).append(eml.clone()).html();
+
+              return emlString;
+      },
+
+      /*
+          Replace elements named "source" with "sourced" due to limitations
+          with using $.parseHTML() rather than $.parseXML()
+
+          @param xmlString  The XML string to make the replacement in
+      */
+      cleanUpXML: function(xmlString){
+        xmlString.replace("<source>", "<sourced>");
+        xmlString.replace("</source>", "</sourced>");
+
+        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 {
+          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
+            });
+
+        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;
+
+        textString = textString.trim();
+
+        //Check for XML/HTML elements
+        _.each(textString.match(/<\s*[^>]*>/g), function(xmlNode){
+
+          //Encode <, >, and </ substrings
+          var tagName = xmlNode.replace(/>/g, "&gt;");
+          tagName = tagName.replace(/</g, "&lt;");
+
+          //Replace the xmlNode in the full text string
+          textString = textString.replace(xmlNode, tagName);
+
+        });
+
+        return textString;
+
+      },
+
+      /*
+          Dereference "reference" elements and replace them with a cloned copy
+          of the referenced content
+
+          @param xmlString  The XML string with reference elements to transform
+      */
+      dereference: function(xmlString) {
+          var referencesList; // the array of references elements in the document
+          var referencedID;  // The id of the referenced element
+          var referencesParentEl;  // The parent of the given references element
+          var referencedEl; // The referenced DOM to be copied
+
+          xmlDOM = $.parseXML(xmlString);
+          referencesList = xmlDOM.getElementsByTagName("references");
+
+          if (referencesList.length) {
+              // Process each references elements
+              _.each(referencesList, function(referencesEl, index, referencesList) {
+                  // Can't rely on the passed referencesEl since the list length changes
+                  // because of the remove() below. Reuse referencesList[0] for every item:
+                  // referencedID = $(referencesEl).text(); // doesn't work
+                  referencesEl = referencesList[0];
+                  referencedID = $(referencesEl).text();
+                  referencesParentEl = ($(referencesEl).parent())[0];
+                  if (typeof referencedID !== "undefined" && referencedID != "") {
+                      referencedEl = xmlDOM.getElementById(referencedID);
+                      if (typeof referencedEl != "undefined") {
+                          // Clone the referenced element and replace the references element
+                          var referencedClone = ($(referencedEl).clone())[0];
+                          $(referencesParentEl)
+                              .children(referencesEl.localName)
+                              .replaceWith($(referencedClone).children());
+                          //$(referencesParentEl).append($(referencedClone).children());
+                          $(referencesParentEl).attr("id", DataONEObject.generateId());
+                      }
+                  }
+              }, xmlDOM);
+          }
+          return (new XMLSerializer()).serializeToString(xmlDOM);
+      },
+
+      /*
+      * Uses the EML `title` to set the `fileName` attribute on this model.
+      */
+      setFileName: function(){
+
+        var title = "";
+
+        // Get the title from the metadata
+        if( Array.isArray(this.get("title")) ){
+          title = this.get("title")[0];
+        }
+        else if( typeof this.get("title") == "string" ){
+          title = this.get("title");
+        }
+
+        //Max title length
+        var maxLength = 50;
+
+        //trim the string to the maximum length
+        var trimmedTitle = title.trim().substr(0, maxLength);
+
+        //re-trim if we are in the middle of a word
+        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, "_");
+
+        //Set the fileName on the model
+        this.set("fileName", trimmedTitle + ".xml");
+      },
+
+      trickleUpChange: function(){
+        if( !MetacatUI.rootDataPackage || !MetacatUI.rootDataPackage.packageModel )
+          return;
+
+        //Mark the package as changed
+        MetacatUI.rootDataPackage.packageModel.set("changed", true);
+      }
+
+    });
+
+    return EML211;
+  }
+);
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time) +
+ + + + + diff --git a/docs/docs/models_metadata_eml211_EMLEntity.js.html b/docs/docs/models_metadata_eml211_EMLEntity.js.html index f92c9ceac..097124042 100644 --- a/docs/docs/models_metadata_eml211_EMLEntity.js.html +++ b/docs/docs/models_metadata_eml211_EMLEntity.js.html @@ -561,13 +561,13 @@

Source: models/metadata/eml211/EMLEntity.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_metadata_eml211_EMLGeoCoverage.js.html b/docs/docs/models_metadata_eml211_EMLGeoCoverage.js.html index 040d5f09a..02324db45 100644 --- a/docs/docs/models_metadata_eml211_EMLGeoCoverage.js.html +++ b/docs/docs/models_metadata_eml211_EMLGeoCoverage.js.html @@ -476,13 +476,13 @@

Source: models/metadata/eml211/EMLGeoCoverage.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_metadata_eml211_EMLNonNumericDomain.js.html b/docs/docs/models_metadata_eml211_EMLNonNumericDomain.js.html index 26865a391..5c44f582c 100644 --- a/docs/docs/models_metadata_eml211_EMLNonNumericDomain.js.html +++ b/docs/docs/models_metadata_eml211_EMLNonNumericDomain.js.html @@ -911,13 +911,13 @@

Source: models/metadata/eml211/EMLNonNumericDomain.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_metadata_eml211_EMLNumericDomain.js.html b/docs/docs/models_metadata_eml211_EMLNumericDomain.js.html index 82a92073c..28f87d2a7 100644 --- a/docs/docs/models_metadata_eml211_EMLNumericDomain.js.html +++ b/docs/docs/models_metadata_eml211_EMLNumericDomain.js.html @@ -468,13 +468,13 @@

Source: models/metadata/eml211/EMLNumericDomain.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_metadata_eml211_EMLTemporalCoverage.js.html b/docs/docs/models_metadata_eml211_EMLTemporalCoverage.js.html index 410818895..b46fda1bb 100644 --- a/docs/docs/models_metadata_eml211_EMLTemporalCoverage.js.html +++ b/docs/docs/models_metadata_eml211_EMLTemporalCoverage.js.html @@ -537,13 +537,13 @@

Source: models/metadata/eml211/EMLTemporalCoverage.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_metadata_eml220_EMLText.js.html b/docs/docs/models_metadata_eml220_EMLText.js.html index 603d7740e..ffd4aed4e 100644 --- a/docs/docs/models_metadata_eml220_EMLText.js.html +++ b/docs/docs/models_metadata_eml220_EMLText.js.html @@ -144,13 +144,13 @@

Source: models/metadata/eml220/EMLText.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_portals_PortalImage.js.html b/docs/docs/models_portals_PortalImage.js.html index 12c40eeb1..a911ff868 100644 --- a/docs/docs/models_portals_PortalImage.js.html +++ b/docs/docs/models_portals_PortalImage.js.html @@ -223,13 +223,13 @@

Source: models/portals/PortalImage.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_portals_PortalModel.js.html b/docs/docs/models_portals_PortalModel.js.html index da14ff400..cceed45aa 100644 --- a/docs/docs/models_portals_PortalModel.js.html +++ b/docs/docs/models_portals_PortalModel.js.html @@ -1612,13 +1612,13 @@

Source: models/portals/PortalModel.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/models_portals_PortalSectionModel.js.html b/docs/docs/models_portals_PortalSectionModel.js.html index 8533a6774..e045124bc 100644 --- a/docs/docs/models_portals_PortalSectionModel.js.html +++ b/docs/docs/models_portals_PortalSectionModel.js.html @@ -342,13 +342,13 @@

Source: models/portals/PortalSectionModel.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/routers_router.js.html b/docs/docs/routers_router.js.html index fe678a37d..9bffa9fb9 100644 --- a/docs/docs/routers_router.js.html +++ b/docs/docs/routers_router.js.html @@ -65,7 +65,8 @@

Source: routers/router.js

'quality(/s=:suiteId)(/:pid)(/)' : 'renderMdqRun', // MDQ page 'api(/:anchorId)(/)' : 'renderAPI', // API page 'projects(/:portalId)(/:portalSection)(/)': 'renderPortal', // portal page - "edit/:portalTermPlural(/:portalIdentifier)(/:portalSection)(/)" : "renderPortalEditor" + "edit/:portalTermPlural(/:portalIdentifier)(/:portalSection)(/)" : "renderPortalEditor", + 'drafts' : 'renderDrafts' }, helpPages: { @@ -232,6 +233,18 @@

Source: routers/router.js

} }, + /** + * Renders the Drafts view which is a simple view backed by LocalForage that + * lists drafts created in the Editor so users can recover any failed + * submissions. + */ + renderDrafts: function() { + require(['views/DraftsView'], function(DraftsView){ + MetacatUI.appView.draftsView = new DraftsView(); + MetacatUI.appView.showView(MetacatUI.appView.draftsView); + }); + }, + renderMdqRun: function (suiteId, pid) { this.routeHistory.push("quality"); @@ -382,7 +395,12 @@

Source: routers/router.js

this.routeHistory.push("edit/"+ MetacatUI.appModel.get("portalTermPlural") +"/" + portalIdentifier + "/" + portalSection); } else{ - this.routeHistory.push("edit/" + MetacatUI.appModel.get("portalTermPlural") +"/" + portalIdentifier); + if( !portalIdentifier ){ + this.routeHistory.push("edit/" + MetacatUI.appModel.get("portalTermPlural")); + } + else{ + this.routeHistory.push("edit/" + MetacatUI.appModel.get("portalTermPlural") +"/" + portalIdentifier); + } } require(['views/portals/editor/PortalEditorView'], function(PortalEditorView){ @@ -729,13 +747,13 @@

Source: routers/router.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/themes_arctic_models_AppModel.js.html b/docs/docs/themes_arctic_models_AppModel.js.html index cd03aa3e1..25c854e46 100644 --- a/docs/docs/themes_arctic_models_AppModel.js.html +++ b/docs/docs/themes_arctic_models_AppModel.js.html @@ -107,7 +107,21 @@

Source: themes/arctic/models/AppModel.js

"our support team, who will contact " + "you via email as soon as possible about getting your data package submitted. ", - baseUrl: window.location.origin || (window.location.protocol + "//" + window.location.host), + /** + * 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} emlKeywordThesauri.label - A readable and short label for the keyword thesaurus that is displayed in the UI + * @property {string} emlKeywordThesauri.thesaurus - The exact keyword thesaurus name that will be saved in the EML + * @readonly + */ + emlKeywordThesauri: [{ + label: "GCMD", + thesaurus: "NASA Global Change Master Directory (GCMD)" + }], + + baseUrl: "https://arcticdata.io",//window.location.origin || (window.location.protocol + "//" + window.location.host), + // the most likely item to change is the Metacat deployment context context: '/metacat', d1Service: '/d1/mn/v2', @@ -162,18 +176,25 @@

Source: themes/arctic/models/AppModel.js

//signInUrl: null, signOutUrl: null, signInUrlOrcid: null, - //signInUrlLdap: 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} + */ + enableLdapSignIn: false, + signInUrlLdap: null, tokenUrl: null, mdqBaseUrl: "https://docker-ucsb-4.dataone.org:30443/quality", // suidIds and suiteLables must be specified as a list, even if only one suite is available. mdqSuiteIds: ["arctic.data.center.suite.1"], mdqSuiteLabels: ["Arctic Data Center Conformance Suite v1.0"], - // Quality suites for aggregated quality scores (i.e. metrics tab) + // Quality suites for aggregated quality scores (i.e. metrics tab) mdqAggregatedSuiteIds: ["FAIR.suite.1"], mdqAggregatedSuiteLabels: ["FAIR Suite v1.0"], mdqFormatIds:["eml*", "https://eml*"], - + // Metrics endpoint url metricsUrl: 'https://logproc-stage-ucsb-1.test.dataone.org/metrics', @@ -215,6 +236,23 @@

Source: themes/arctic/models/AppModel.js

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} + */ + enablePublishDOI: false, + + /** + * 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: [], + /** * Semantic annotation configuration * Include your Bioportal api key to show ontology information for metadata annotations @@ -356,7 +394,7 @@

Source: themes/arctic/models/AppModel.js

* Limits only the following people or groups to create new portals. * @type {string[]} */ - limitPortalsToSubjects: ["CN=arctic-data-admins,DC=dataone,DC=org"], + limitPortalsToSubjects: [], /** * This message will display when a user tries to create a new Portal in the PortalEditor @@ -483,6 +521,13 @@

Source: themes/arctic/models/AppModel.js

} ], + /** + * 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[]} + */ + unsupportedBrowsers: [/(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:)(\d+)/] + /** * The following configuration options are deprecated or experimental and should only be changed by advanced users */ @@ -578,8 +623,11 @@

Source: themes/arctic/models/AppModel.js

} if(typeof this.get("signInUrlOrcid") !== "undefined") this.set("signInUrlOrcid", this.get('portalUrl') + "oauth?action=start&target="); - if(typeof this.get("signInUrlLdap") !== "undefined") + + if(this.get("enableLdapSignIn") && !this.get("signInUrlLdap")){ this.set("signInUrlLdap", this.get('portalUrl') + "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")) @@ -611,13 +659,13 @@

Source: themes/arctic/models/AppModel.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/themes_arctic_models_Map.js.html b/docs/docs/themes_arctic_models_Map.js.html index 3811af9af..957eb5751 100644 --- a/docs/docs/themes_arctic_models_Map.js.html +++ b/docs/docs/themes_arctic_models_Map.js.html @@ -231,13 +231,13 @@

Source: themes/arctic/models/Map.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/themes_arctic_routers_router.js.html b/docs/docs/themes_arctic_routers_router.js.html index d8ed1a9cc..0bb78785d 100644 --- a/docs/docs/themes_arctic_routers_router.js.html +++ b/docs/docs/themes_arctic_routers_router.js.html @@ -56,7 +56,8 @@

Source: themes/arctic/routers/router.js

'quality(/s=:suiteId)(/:pid)(/)' : 'renderMdqRun', // MDQ page 'api(/:anchorId)(/)' : 'renderAPI', // API page 'projects(/:portalId)(/:portalSection)(/)': 'renderPortal', // portal page - "edit/:portalTermPlural(/:portalIdentifier)(/:portalSection)(/)" : "renderPortalEditor" + "edit/:portalTermPlural(/:portalIdentifier)(/:portalSection)(/)" : "renderPortalEditor", + 'drafts' : 'renderDrafts' }, helpPages: { @@ -349,6 +350,18 @@

Source: themes/arctic/routers/router.js

} }, + /** + * Renders the Drafts view which is a simple view backed by LocalForage that + * lists drafts created in the Editor so users can recover any failed + * submissions. + */ + renderDrafts: function() { + require(['views/DraftsView'], function(DraftsView){ + MetacatUI.appView.draftsView = new DraftsView(); + MetacatUI.appView.showView(MetacatUI.appView.draftsView); + }); + }, + /** * Renders the PortalEditorView * @param {string} [portalTermPlural] - This should match the `portalTermPlural` configured in the AppModel. @@ -371,7 +384,12 @@

Source: themes/arctic/routers/router.js

this.routeHistory.push("edit/"+ MetacatUI.appModel.get("portalTermPlural") +"/" + portalIdentifier + "/" + portalSection); } else{ - this.routeHistory.push("edit/"+ MetacatUI.appModel.get("portalTermPlural") +"/" + portalIdentifier); + if( !portalIdentifier ){ + this.routeHistory.push("edit/" + MetacatUI.appModel.get("portalTermPlural")); + } + else{ + this.routeHistory.push("edit/" + MetacatUI.appModel.get("portalTermPlural") +"/" + portalIdentifier); + } } require(['views/portals/editor/PortalEditorView'], function(PortalEditorView){ @@ -587,13 +605,13 @@

Source: themes/arctic/routers/router.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/themes_dataone_models_AppModel.js.html b/docs/docs/themes_dataone_models_AppModel.js.html index 5997848ac..4a8256191 100644 --- a/docs/docs/themes_dataone_models_AppModel.js.html +++ b/docs/docs/themes_dataone_models_AppModel.js.html @@ -133,14 +133,21 @@

Source: themes/dataone/models/AppModel.js

signInUrl: null, signOutUrl: null, signInUrlOrcid: null, - //signInUrlLdap: 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} + */ + enableLdapSignIn: false, + signInUrlLdap: null, tokenUrl: null, // Metadata quality report services mdqBaseUrl: "https://docker-ucsb-4.dataone.org:30443/quality", // suidIds and suiteLables must be specified as a list, even if only one suite is available. mdqSuiteIds: ["FAIR.suite.1"], mdqSuiteLabels: ["FAIR Suite v1.0"], - // Quality suites for aggregated quality scores (i.e. metrics tab) + // Quality suites for aggregated quality scores (i.e. metrics tab) mdqAggregatedSuiteIds: ["FAIR.suite.1"], mdqAggregatedSuiteLabels: ["FAIR Suite v1.0"], mdqFormatIds:["eml*", "https://eml*", "*isotc211*"], @@ -188,6 +195,23 @@

Source: themes/dataone/models/AppModel.js

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} + */ + enablePublishDOI: false, + + /** + * 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, then archived content is available in the search index. // Set to false if this MetacatUI is using a Metacat version before 2.10.0 archivedContentIsIndexed: true, @@ -293,7 +317,14 @@

Source: themes/dataone/models/AppModel.js

* Limits only the following people or groups to create new portals. * @type {string[]} */ - limitPortalsToSubjects: [] + limitPortalsToSubjects: [], + + /** + * 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[]} + */ + unsupportedBrowsers: [/(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:)(\d+)/] /** * The following configuration options are deprecated or experimental and should only be changed by advanced users @@ -370,8 +401,11 @@

Source: themes/dataone/models/AppModel.js

} if(typeof this.get("signInUrlOrcid") !== "undefined") this.set("signInUrlOrcid", this.get('portalUrl') + "oauth?action=start&target="); - if(typeof this.get("signInUrlLdap") !== "undefined") + + if(this.get("enableLdapSignIn") && !this.get("signInUrlLdap")){ this.set("signInUrlLdap", this.get('portalUrl') + "ldap?target="); + } + if(this.get('orcidBaseUrl')) this.set('orcidSearchUrl', this.get('orcidBaseUrl') + '/v1.1/search/orcid-bio?q='); @@ -409,13 +443,13 @@

Source: themes/dataone/models/AppModel.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/themes_dataone_routers_router.js.html b/docs/docs/themes_dataone_routers_router.js.html index 8a5d6bffe..f7ab908a4 100644 --- a/docs/docs/themes_dataone_routers_router.js.html +++ b/docs/docs/themes_dataone_routers_router.js.html @@ -493,13 +493,13 @@

Source: themes/dataone/routers/router.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/themes_knb_models_AppModel.js.html b/docs/docs/themes_knb_models_AppModel.js.html index a286f513f..bbe94847a 100644 --- a/docs/docs/themes_knb_models_AppModel.js.html +++ b/docs/docs/themes_knb_models_AppModel.js.html @@ -104,7 +104,21 @@

Source: themes/knb/models/AppModel.js

editorSaveErrorMsg: "Not all of your changes could be submitted.", editorSaveErrorMsgWithDraft: "Not all of your changes could be submitted. ", - baseUrl: window.location.origin || (window.location.protocol + "//" + window.location.host), + /** + * 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} emlKeywordThesauri.label - A readable and short label for the keyword thesaurus that is displayed in the UI + * @property {string} emlKeywordThesauri.thesaurus - The exact keyword thesaurus name that will be saved in the EML + * @readonly + */ + emlKeywordThesauri: [{ + label: "GCMD", + thesaurus: "NASA Global Change Master Directory (GCMD)" + }], + + baseUrl: "https://knb.ecoinformatics.org",//window.location.origin || (window.location.protocol + "//" + window.location.host), + // the most likely item to change is the Metacat deployment context context: '/metacat', d1Service: '/d1/mn/v2', @@ -154,7 +168,16 @@

Source: themes/knb/models/AppModel.js

signInUrl: null, signOutUrl: null, 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} + */ + enableLdapSignIn: true, signInUrlLdap: null, + tokenUrl: null, accountsUrl: null, pendingMapsUrl: null, @@ -168,7 +191,7 @@

Source: themes/knb/models/AppModel.js

// suidIds and suiteLables must be specified as a list, even if only one suite is available. mdqSuiteIds: ["knb.suite.1"], mdqSuiteLabels: ["KNB Metadata Completeness Suite v1.0"], - // Quality suites for aggregated quality scores (i.e. metrics tab) + // Quality suites for aggregated quality scores (i.e. metrics tab) mdqAggregatedSuiteIds: ["FAIR.suite.1"], mdqAggregatedSuiteLabels: ["FAIR Suite v1.0"], mdqFormatIds:["eml*", "https://eml*"], @@ -214,6 +237,23 @@

Source: themes/knb/models/AppModel.js

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} + */ + 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, then archived content is available in the search index. // Set to false if this MetacatUI is using a Metacat version before 2.10.0 archivedContentIsIndexed: true, @@ -485,7 +525,14 @@

Source: themes/knb/models/AppModel.js

} ] } - ] + ], + + /** + * 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[]} + */ + unsupportedBrowsers: [/(?:\b(MS)?IE\s+|\bTrident\/7\.0;.*\s+rv:)(\d+)/] /** * The following configuration options are deprecated or experimental and should only be changed by advanced users @@ -578,8 +625,11 @@

Source: themes/knb/models/AppModel.js

this.set("signInUrl", this.get('portalUrl') + "startRequest?target="); if(typeof this.get("signInUrlOrcid") !== "undefined") this.set("signInUrlOrcid", this.get('portalUrl') + "oauth?action=start&target="); - if(typeof this.get("signInUrlLdap") !== "undefined") + + if(this.get("enableLdapSignIn") && !this.get("signInUrlLdap")){ this.set("signInUrlLdap", this.get('portalUrl') + "ldap?target="); + } + if(this.get('orcidBaseUrl')) this.set('orcidSearchUrl', this.get('orcidBaseUrl') + '/v1.1/search/orcid-bio?q='); @@ -614,13 +664,13 @@

Source: themes/knb/models/AppModel.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/themes_knb_routers_router.js.html b/docs/docs/themes_knb_routers_router.js.html index 255989c46..c02f4b5a8 100644 --- a/docs/docs/themes_knb_routers_router.js.html +++ b/docs/docs/themes_knb_routers_router.js.html @@ -124,13 +124,13 @@

Source: themes/knb/routers/router.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/themes_opc_routers_router.js.html b/docs/docs/themes_opc_routers_router.js.html new file mode 100644 index 000000000..510c50c6f --- /dev/null +++ b/docs/docs/themes_opc_routers_router.js.html @@ -0,0 +1,82 @@ + + + + + MetacatUI Dev Docs: Source: themes/opc/routers/router.js + + + + + + + + + + + +
+ +

Source: themes/opc/routers/router.js

+ + + + + + +
+
+
/*global Backbone */
+'use strict';
+
+define(['jquery', 'underscore', 'backbone', 'routers/BaseRouter'],
+function ($, _, Backbone, BaseRouter) {
+
+    // MetacatUI Router
+    // ----------------
+    var UIRouter = BaseRouter.extend({
+        routes: _.extend(BaseRouter.prototype.routes, {
+            "support(/:anchorId)(/)": "renderSupport",
+        }),
+        
+        /**
+         * Render the support page
+         * @return {object} the rendered support page
+         */
+        renderSupport: function(anchorId) {
+            this.routeHistory.push("support");
+            MetacatUI.appModel.set('anchorId', anchorId);
+            var options = {
+                    pageName: "support",
+                    anchorId: anchorId
+                }
+
+            this.renderText(options);
+        }
+    });
+    return UIRouter;
+});
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time) +
+ + + + + diff --git a/docs/docs/themes_opc_views_metadata_EML211View.js.html b/docs/docs/themes_opc_views_metadata_EML211View.js.html new file mode 100644 index 000000000..d956b0e27 --- /dev/null +++ b/docs/docs/themes_opc_views_metadata_EML211View.js.html @@ -0,0 +1,290 @@ + + + + + MetacatUI Dev Docs: Source: themes/opc/views/metadata/EML211View.js + + + + + + + + + + + +
+ +

Source: themes/opc/views/metadata/EML211View.js

+ + + + + + +
+
+
define(['underscore', 'jquery', 'backbone',
+        'views/metadata/BaseEML211View',  // Override this view
+        'views/metadata/ScienceMetadataView',
+        'views/metadata/EMLGeoCoverageView',
+        'views/metadata/EMLPartyView',
+        'views/metadata/EMLMethodsView',
+        'views/metadata/EMLTempCoverageView',
+        'models/metadata/eml211/EML211',
+        'models/metadata/eml211/EMLGeoCoverage',
+        'models/metadata/eml211/EMLKeywordSet',
+        'models/metadata/eml211/EMLParty',
+        'models/metadata/eml211/EMLProject',
+        'models/metadata/eml211/EMLText',
+        'models/metadata/eml211/EMLTaxonCoverage',
+        'models/metadata/eml211/EMLTemporalCoverage',
+        'models/metadata/eml211/EMLMethods',
+        'text!templates/metadata/eml.html',
+        'text!templates/metadata/eml-people.html',
+        'text!templates/metadata/EMLPartyCopyMenu.html',
+        'text!templates/metadata/metadataOverview.html',
+        'text!templates/metadata/dates.html',
+        'text!templates/metadata/locationsSection.html',
+        'text!templates/metadata/taxonomicCoverage.html',
+        'text!templates/metadata/taxonomicClassificationTable.html',
+        'text!templates/metadata/taxonomicClassificationRow.html'],
+    function(_, $, Backbone,
+        BaseEML211View,
+        ScienceMetadataView, 
+        EMLGeoCoverageView, 
+        EMLPartyView, 
+        EMLMethodsView, 
+        EMLTempCoverageView,
+        EML, 
+        EMLGeoCoverage,
+        EMLKeywordSet, 
+        EMLParty, 
+        EMLProject, 
+        EMLText, 
+        EMLTaxonCoverage,
+        EMLTemporalCoverage, 
+        EMLMethods, 
+        Template, 
+        PeopleTemplate, 
+        EMLPartyCopyMenuTemplate, 
+        OverviewTemplate,
+        DatesTemplate, 
+        LocationsTemplate, 
+        TaxonomicCoverageTemplate, 
+        TaxonomicClassificationTable, 
+        TaxonomicClassificationRow) {
+
+    var EMLView = BaseEML211View.extend({
+        /*
+         * Adds a single funding input row. Can either be called directly or used as an event callback
+         */
+        addFunding: function(argument){
+            if (this.edit) {
+                if (typeof argument == "string") {
+                    var value = argument;
+                } else if (!argument) {
+                    var value = "";
+                //Don't add another new funding input if there already is one
+                } else if ( !value && (typeof argument == "object") && !$(argument.target).is(".new") ) {
+                    return;
+                } else if((typeof argument == "object") && argument.target) {
+                    var event = argument;
+
+                    // Don't add a new funding row if the current one is empty
+                    if ( $(event.target).val().trim() === "") return;
+                }
+
+                var fundingInput = $(document.createElement("input"))
+                    .attr("type", "text")
+                    .attr("data-category", "funding")
+                    .addClass("span12 funding hover-autocomplete-target")
+                    .attr("placeholder", "Enter information about the funding source of this project")
+                    .val(value),
+                    hiddenFundingInput = fundingInput.clone().attr("type", "hidden").val(value).attr("id", "").addClass("hidden"),
+                    loadingSpinner = $(document.createElement("i")).addClass("icon icon-spinner input-icon icon-spin subtle hidden");
+
+                //Append all the elements to a container
+                var containerEl = $(document.createElement("div"))
+                    .addClass("ui-autocomplete-container funding-row")
+                    .append(fundingInput, loadingSpinner, hiddenFundingInput);
+
+                if (!value) {
+                    $(fundingInput).addClass("new");
+
+                    if (event) {
+                        $(event.target).parents("div.funding-row").append(this.createRemoveButton(
+                                'project', 'funding', '.funding-row', 'div.funding-container'));
+                        $(event.target).removeClass("new");
+                    }
+                } else { // Add a remove button if this is a non-new funding element
+                    $(containerEl).append(this.createRemoveButton(
+                        'project', 'funding', '.funding-row', 'div.funding-container'));
+                }
+
+                var view = this;
+
+                //Setup the autocomplete widget for the funding input
+                fundingInput.autocomplete({
+                    source: function(request, response){
+                        var beforeRequest = function(){
+                            loadingSpinner.show();
+                        }
+
+                        var afterRequest = function(){
+                            loadingSpinner.hide();
+                        }
+
+                        return MetacatUI.appLookupModel.getGrantAutocomplete(request, response, beforeRequest, afterRequest)
+                    },
+                    select: function(e, ui) {
+                        e.preventDefault();
+                        var value = "NSF Award " + ui.item.value + " (" + ui.item.label + ")";
+                        hiddenFundingInput.val(value);
+                        fundingInput.val(value);
+                        $(".funding .ui-helper-hidden-accessible").hide();
+                        loadingSpinner.css("top", "5px");
+                        view.updateFunding(e);
+                    },
+                    position: {
+                        my: "left top",
+                        at: "left bottom",
+                        of: fundingInput,
+                        collision: "fit"
+                    },
+                    appendTo: containerEl,
+                    minLength: 3
+                });
+                this.$(".funding-container").append(containerEl);
+            }
+        },
+
+        /**
+         * Add a keyword to the KeywordSet
+         * @param {*} keyword  the keyword to add
+         * @param {*} thesaurus  the thesaurus the keyword is a term in
+         */
+        addKeyword: function(keyword, thesaurus){
+            if(typeof keyword != "string" || !keyword){
+                var keyword = "";
+
+                //Only show one new keyword row at a time
+                if((this.$(".keyword.new").length == 1) && !this.$(".keyword.new").val())
+                    return;
+                else if(this.$(".keyword.new").length > 1)
+                    return;
+            }
+
+            //Create the keyword row HTML
+            var row = $(document.createElement("div")).addClass("row-fluid keyword-row"),
+                keywordInput = $(document.createElement("input"))
+                    .attr("type", "text")
+                    .addClass("keyword span10")
+                    .attr("placeholder", "Add one new keyword"),
+                thesInput = $(document.createElement("select"))
+                    .addClass("thesaurus span2")
+                    .append(
+                        $(document.createElement("option"))
+                            .val("None")
+                            .text("None"))
+                    .append(
+                        $(document.createElement("option"))
+                            .val("California Ocean Protection Council")
+                            .text("California Ocean Protection Council"))
+                    .append(
+                        $(document.createElement("option"))
+                            .val("GCMD")
+                            .text("GCMD")),
+                removeButton;
+
+            // Piece together the inputs
+            row.append(keywordInput, thesInput);
+
+            //Select GCMD in the select menu
+            if (thesaurus && thesaurus.indexOf("GCMD") > -1) {
+                thesInput.val("GCMD");
+            }
+            // Select CA OPC in the select menu
+            if (thesaurus && thesaurus.indexOf("California Ocean Protection Council") > -1) {
+                thesInput.val("California Ocean Protection Council");
+            }
+
+            if(!keyword)
+                row.addClass("new");
+            else{
+
+                //Set the keyword value on the text input
+                keywordInput.val(keyword);
+
+                // Add a remove button unless this is the .new keyword
+                row.append(this.createRemoveButton(null, 'keywordSets', 'div.keyword-row', 'div.keywords'));
+            }
+
+            this.$(".keywords").append(row);
+        },
+
+        /**
+         * Adds a new keyword to the KeywordSet
+         * @param {*} e the event triggered by adding a keyword
+         */
+        addNewKeyword: function(e) {
+            if ($(e.target).val().trim() === "") return;
+
+            $(e.target).parents(".keyword-row").first().removeClass("new");
+
+            // Add in a remove button
+            $(e.target).parents(".keyword-row")
+                .append(this.createRemoveButton(null, 'keywordSets', 'div.keyword-row', 'div.keywords'));
+
+            var row = $(document.createElement("div"))
+                .addClass("row-fluid keyword-row new")
+                .data({ model: new EMLKeywordSet() }),
+                keywordInput = $(document.createElement("input"))
+                    .attr("type", "text")
+                    .addClass("keyword span10"),
+                thesInput = $(document.createElement("select"))
+                    .addClass("thesaurus span2")
+                    .append(
+                        $(document.createElement("option"))
+                            .val("None")
+                            .text("None"))
+                     .append(
+                         $(document.createElement("option"))
+                             .val("California Ocean Protection Council")
+                             .text("California Ocean Protection Council"))
+                    .append(
+                        $(document.createElement("option"))
+                            .val("GCMD")
+                            .text("GCMD"));
+            row.append(keywordInput, thesInput);
+            this.$(".keywords").append(row);
+        }
+    });
+    return EMLView;
+});
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time) +
+ + + + + diff --git a/docs/docs/views_AccessPolicyView.js.html b/docs/docs/views_AccessPolicyView.js.html index c503f1829..d4ba90135 100644 --- a/docs/docs/views_AccessPolicyView.js.html +++ b/docs/docs/views_AccessPolicyView.js.html @@ -675,13 +675,13 @@

Source: views/AccessPolicyView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_AccessRuleView.js.html b/docs/docs/views_AccessRuleView.js.html index 5d3d785e7..1b134bd6c 100644 --- a/docs/docs/views_AccessRuleView.js.html +++ b/docs/docs/views_AccessRuleView.js.html @@ -515,13 +515,13 @@

Source: views/AccessRuleView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_AnnotationView.js.html b/docs/docs/views_AnnotationView.js.html index af0e4b205..dcd991868 100644 --- a/docs/docs/views_AnnotationView.js.html +++ b/docs/docs/views_AnnotationView.js.html @@ -335,13 +335,13 @@

Source: views/AnnotationView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_AppView.js.html b/docs/docs/views_AppView.js.html new file mode 100644 index 000000000..b0d3faf48 --- /dev/null +++ b/docs/docs/views_AppView.js.html @@ -0,0 +1,628 @@ + + + + + MetacatUI Dev Docs: Source: views/AppView.js + + + + + + + + + + + +
+ +

Source: views/AppView.js

+ + + + + + +
+
+
/*global define */
+define(['jquery',
+		'underscore',
+		'backbone',
+		'views/AltHeaderView',
+		'views/NavbarView',
+		'views/FooterView',
+		'views/SignInView',
+		'text!templates/alert.html',
+		'text!templates/appHead.html',
+    'text!templates/jsonld.txt',
+		'text!templates/app.html',
+		'text!templates/loading.html'
+	    ],
+	function($, _, Backbone, AltHeaderView, NavbarView, FooterView, SignInView,
+    AlertTemplate, AppHeadTemplate, JsonLDTemplate, AppTemplate, LoadingTemplate) {
+	'use strict';
+
+	var app = app || {};
+
+	var theme = document.getElementById("loader").getAttribute("data-theme");
+
+	// Our overall **AppView** is the top-level piece of UI.
+	var AppView = Backbone.View.extend({
+
+		// Instead of generating a new element, bind to the existing skeleton of
+		// the App already present in the HTML.
+		el: '#metacatui-app',
+
+		//Templates
+		template: _.template(AppTemplate),
+		alertTemplate: _.template(AlertTemplate),
+		appHeadTemplate: _.template(AppHeadTemplate),
+    jsonLDTemplate: _.template(JsonLDTemplate),
+		loadingTemplate: _.template(LoadingTemplate),
+
+		events: {
+											 "click" : "closePopovers",
+	 		              'click .btn.direct-search' : 'routeToMetadata',
+		 	          'keypress input.direct-search' : 'routeToMetadataOnEnter',
+		 	                 "click .toggle-slide"   : "toggleSlide",
+				 		 	      "click input.copy" : "higlightInput",
+					 		 	  "focus input.copy" : "higlightInput",
+					 		   "click textarea.copy" : "higlightInput",
+					 		   "focus textarea.copy" : "higlightInput",
+					 		 	  "click .open-chat" : "openChatWithMessage",
+					 		 "click .login.redirect" : "sendToLogin",
+					 	   "focus .jump-width-input" : "widenInput",
+					 	"focusout .jump-width-input" : "narrowInput"
+		},
+
+		initialize: function () {
+
+			//Check for the LDAP sign in error message
+			if(window.location.search.indexOf("error=Unable%20to%20authenticate%20LDAP%20user") > -1){
+				window.location = window.location.origin + window.location.pathname + "#signinldaperror";
+			}
+
+			//Is there a logged-in user?
+			MetacatUI.appUserModel.checkStatus();
+
+			// set up the head - make sure to prepend, otherwise the CSS may be out of order!
+			$("head").prepend(this.appHeadTemplate({
+				theme: MetacatUI.theme,
+				themeTitle: MetacatUI.themeTitle,
+				googleAnalyticsKey: MetacatUI.appModel.get("googleAnalyticsKey")
+      }))
+      //Add the JSON-LD to the head element
+      .append($(document.createElement("script")).attr("type", "application/ld+json")
+                                                 .attr("id", "jsonld")
+                                                 .html(this.jsonLDTemplate()));
+
+			// set up the body
+			this.$el.append(this.template());
+
+			// render the nav
+			MetacatUI.navbarView = new NavbarView();
+			MetacatUI.navbarView.setElement($('#Navbar')).render();
+
+			MetacatUI.altHeaderView = new AltHeaderView();
+			MetacatUI.altHeaderView.setElement($('#HeaderContainer')).render();
+
+			MetacatUI.footerView = new FooterView();
+			MetacatUI.footerView.setElement($('#Footer')).render();
+
+			//Load the Slaask chat widget if it is enabled in this theme
+			if(MetacatUI.appModel.get("slaaskKey") && window._slaask)
+		    	_slaask.init(MetacatUI.appModel.get("slaaskKey"));
+
+			//Change the document title when the app changes the MetacatUI.appModel title at any time
+			this.listenTo(MetacatUI.appModel, "change:title", this.changeTitle);
+
+			this.listenForActivity();
+			this.listenForTimeout();
+
+			this.initializeWidgets();
+
+      this.checkIncompatibility();
+		},
+
+		//Changes the web document's title
+		changeTitle: function(){
+			document.title = MetacatUI.appModel.get("title");
+		},
+
+		// Render the main view and/or re-render subviews. Don't call .html() here
+		// so we don't lose state, rather use .setElement(). Delegate rendering
+		// and event handling to sub views
+		render: function () {
+
+			return this;
+		},
+
+		// the currently rendered view
+		currentView: null,
+
+		// Our view switcher for the whole app
+		showView: function(view, viewOptions) {
+
+			//reference to appView
+			var thisAppViewRef = this;
+
+			// Change the background image if there is one
+			MetacatUI.navbarView.changeBackground();
+
+			// close the current view
+			if (this.currentView){
+
+        //If the current view has a function to confirm closing of the view, call it
+				if( typeof this.currentView.canClose == "function" ){
+
+          //If the user or view confirmed that the view shouldn't be closed, then don't navigate to the next route
+          if( !this.currentView.canClose() ){
+
+            //Get a confirmation message from the view, or use a default one
+            if( typeof this.currentView.getConfirmCloseMessage == "function" ){
+              var confirmMessage = this.currentView.getConfirmCloseMessage();
+            }
+            else{
+              var confirmMessage = "Leave this page?";
+            }
+
+            //Show a confirm alert to the user and wait for their response
+            var leave = confirm(confirmMessage);
+            //If they clicked Cancel, then don't navigate to the next route
+            if(!leave){
+              MetacatUI.uiRouter.undoLastRoute();
+              return;
+            }
+          }
+				}
+
+				// need reference to the old/current view for the callback method
+				var oldView = this.currentView;
+
+				this.currentView.$el.fadeOut('slow', function() {
+					// clean up old view
+					if (oldView.onClose)
+						oldView.onClose();
+
+					// render the new view
+					view.render(viewOptions);
+					view.$el.fadeIn('slow', function() {
+
+						// after fade in, do postRender()
+						if (view.postRender)
+							view.postRender();
+						// force scroll to top if no custom scrolling is implemented
+						else
+							thisAppViewRef.scrollToTop();
+					});
+				});
+			} else {
+
+				// just show the view without transition
+				view.render(viewOptions);
+
+				if (view.postRender)
+					view.postRender();
+				// force scroll to top if no custom scrolling is implemented
+				else
+					thisAppViewRef.scrollToTop();
+			}
+
+
+			// track the current view
+			this.currentView = view;
+			this.sendAnalytics();
+
+			this.trigger("appRenderComplete");
+		},
+
+		sendAnalytics: function(){
+			if(!MetacatUI.appModel.get("googleAnalyticsKey") || (typeof ga === "undefined")) return;
+
+			var page = window.location.pathname || "/";
+			page = page.replace("#", ""); //remove the leading pound sign
+
+			ga('send', 'pageview', {'page':  page});
+		},
+
+		routeToMetadata: function(e){
+			e.preventDefault();
+
+			//Get the value from the input element
+			var form = $(e.target).attr("form") || null,
+				val = this.$("#" + form).find("input[type=text]").val();
+
+			//Remove the text from the input
+			this.$("#" + form).find("input[type=text]").val("");
+
+			if(!val) return false;
+
+			MetacatUI.uiRouter.navigate('view/'+ val, {trigger: true});
+		},
+
+		routeToMetadataOnEnter: function(e){
+			//If the user pressed a key inside a text input, we only want to proceed if it was the Enter key
+			if((e.type == "keypress") && (e.keycode != 13))
+				return;
+			else
+				this.routeToMetadata(e);
+		},
+
+		sendToLogin: function(e){
+			if(e) e.preventDefault();
+
+			var url = $(e.target).attr("href");
+			url = url.substring(0, url.indexOf("target=")+7);
+			url += window.location.href;
+
+			window.location.href = url;
+		},
+
+		resetSearch: function(){
+			// Clear the search and map model to start a fresh search
+			MetacatUI.appSearchModel.clear();
+			MetacatUI.appSearchModel.set(MetacatUI.appSearchModel.defaults);
+			MetacatUI.mapModel.clear();
+			MetacatUI.mapModel.set(MetacatUI.mapModel.defaults);
+
+			//Clear the search history
+			MetacatUI.appModel.set("searchHistory", new Array());
+
+			MetacatUI.uiRouter.navigate('data', {trigger: true});
+		},
+
+		closePopovers: function(e){
+			if(this.currentView && this.currentView.closePopovers)
+				this.currentView.closePopovers(e);
+		},
+
+		toggleSlide: function(e){
+			if(e) e.preventDefault();
+			else return false;
+
+			var clickedOn   = $(e.target),
+				toggleElId  = clickedOn.attr("data-slide-el") || clickedOn.parents("[data-slide-el]").attr("data-slide-el"),
+				toggleEl    = $("#" + toggleElId);
+
+			toggleEl.slideToggle("fast", function(){
+				//Toggle the display of the link if it has the right class
+				if(clickedOn.is(".toggle-display-on-slide")){
+					clickedOn.siblings(".toggle-display-on-slide").toggle();
+					clickedOn.toggle();
+				}
+			});
+		},
+
+    /**
+    * Displays the given message to the user in a Bootstrap "alert" style.
+    * @param {string|Element} msg
+    * @param {string} [classes]
+    * @param {string|Element} [container]
+    * @param {boolean} [delay]
+    * @param {object} [options]
+    * @param {string} [options.emailBody]
+    * @param {boolean} [options.remove]
+    */
+		showAlert: function(msg, classes, container, delay, options) {
+			if(!classes)
+				var classes = 'alert-success';
+			if(!container || !$(container).length)
+				var container = this.$el;
+
+			//Remove any alerts that are already in this container
+			if($(container).children(".alert-container").length > 0)
+				$(container).children(".alert-container").remove();
+
+			//Allow messages to be HTML or strings
+			if(typeof msg != "string")
+				msg = $(document.createElement("div")).append($(msg)).html();
+
+			var emailOptions = "";
+
+			//Check for more options
+			if(typeof options != "undefined" && options.emailBody)
+				emailOptions += "?body=" + options.emailBody;
+
+			//Allow error messages to be removed
+			var remove = options? options.remove : false;
+
+			var alert = $.parseHTML(this.alertTemplate({
+				msg: msg,
+				classes: classes,
+				emailOptions: emailOptions,
+				remove: remove
+			}).trim());
+
+			if(delay){
+				$(alert).hide();
+				$(container).prepend(alert);
+				$(alert).show().delay(typeof delay == "number"? delay : 3000).fadeOut();
+			}
+			else
+				$(container).prepend(alert);
+		},
+
+		/**
+    * Listens to the focus event on the window to detect when a user switches back to this browser tab from somewhere else
+		* When a user checks back, we want to check for log-in status
+    */
+		listenForActivity: function(){
+			MetacatUI.appUserModel.on("change:loggedIn", function(){
+				if(!MetacatUI.appUserModel.get("loggedIn")) return;
+
+				//When the user re-focuses back on the window
+				$(window).focus(function(){
+					//If the user has logged out in the meantime, then exit
+					if(!MetacatUI.appUserModel.get("loggedIn")) return;
+
+						//If the expiration date of the token has passed, then allow the user to sign back in
+						if( MetacatUI.appUserModel.get("expires") <= new Date() ){
+							MetacatUI.appView.showTimeoutSignIn();
+						}
+
+				});
+			});
+		},
+
+		/**
+		* Will determine the length of time until the user's current token expires,
+		* and will set a window timeout for that length of time. When the timeout
+		* is triggered, the sign in modal window will be displayed so that the user
+		* can sign in again (which happens in AppView.showTimeoutSignIn())
+		*/
+		listenForTimeout: function(){
+
+			//Only proceed if the user is logged in
+			if( !MetacatUI.appUserModel.get("checked") ){
+
+				//When the user logged back in, listen again for the next timeout
+				this.listenToOnce(MetacatUI.appUserModel, "change:checked", function(){
+					//If the user is logged in, then listen call this function again
+					if(MetacatUI.appUserModel.get("checked") && MetacatUI.appUserModel.get("loggedIn"))
+						this.listenForTimeout();
+				});
+
+				return;
+			}
+			else if( !MetacatUI.appUserModel.get("loggedIn") ){
+
+				//When the user logged back in, listen again for the next timeout
+				this.listenToOnce(MetacatUI.appUserModel, "change:loggedIn", function(){
+					//If the user is logged in, then listen call this function again
+					if(MetacatUI.appUserModel.get("checked") && MetacatUI.appUserModel.get("loggedIn"))
+						this.listenForTimeout();
+				});
+
+				return;
+
+			}
+
+			var view = this,
+					expires = MetacatUI.appUserModel.get("expires"),
+					timeLeft = expires - new Date();
+
+			//If there is no time left until expiration, then show the sign in view now
+			if( timeLeft < 0 ){
+				this.showTimeoutSignIn();
+			}
+			//Otherwise, set a timeout for a expiration time, then show the Sign In View
+			else{
+				var timeoutId = setTimeout(function(){
+													view.showTimeoutSignIn.call(view);
+												}, timeLeft);
+
+				//Save the timeout id in case we want to destroy the timeout later
+				MetacatUI.appUserModel.set("timeoutId", timeoutId);
+			}
+		},
+
+		/**
+		* If the user's auth token has expired, a new SignInView model window is
+		* displayed so the user can sign back in. A listener is set on the appUserModel
+		* so that when they do successfully sign back in, we set another timeout listener
+		* via AppView.listenForTimeout()
+		*/
+		showTimeoutSignIn: function(){
+			if(MetacatUI.appUserModel.get("expires") <= new Date()){
+				MetacatUI.appUserModel.set("loggedIn", false);
+
+				 var signInView = new SignInView({
+						 inPlace: true,
+						 closeButtons: false,
+						 topMessage: "Your session has timed out. Click Sign In to open a " +
+						 						 "new window to sign in again. Make sure your browser settings allow pop-ups."
+				 })
+				 var signInForm = signInView.render().el;
+
+				 if(this.subviews && Array.isArray(this.subviews))
+					 this.subviews.push(signInView);
+				 else
+					 this.subviews = [signInView];
+
+				$("body").append(signInForm);
+				$(signInForm).modal();
+
+				//When the user logged back in, listen again for the next timeout
+				this.listenToOnce(MetacatUI.appUserModel, "change:checked", function(){
+					if(MetacatUI.appUserModel.get("checked") && MetacatUI.appUserModel.get("loggedIn"))
+						this.listenForTimeout();
+				});
+			}
+		},
+
+
+		openChatWithMessage: function(){
+			if(!_slaask) return;
+
+	    	$("#slaask-input").val(MetacatUI.appModel.get("defaultSupportMessage"));
+	    	$("#slaask-button").trigger("click");
+
+		},
+
+		initializeWidgets: function(){
+			 // Autocomplete widget extension to provide description tooltips.
+ 		    $.widget( "app.hoverAutocomplete", $.ui.autocomplete, {
+
+ 		        // Set the content attribute as the "item.desc" value.
+ 		        // This becomes the tooltip content.
+ 		        _renderItem: function( ul, item ) {
+ 		        	// if we have a label, use it for the title
+ 		        	var title = item.value;
+ 		        	if (item.label) {
+ 		        		title = item.label;
+ 		        	}
+ 		        	// if we have a description, use it for the content
+ 		        	var content = item.value;
+ 		        	if (item.desc) {
+ 		        		content = item.desc;
+ 		        		if (item.desc != item.value) {
+ 			        		content += " (" + item.value + ")";
+ 		        		}
+ 		        	}
+ 		        	var element = this._super( ul, item )
+ 	                .attr( "data-title", title )
+ 	                .attr( "data-content", content );
+ 		        	element.popover(
+ 		        			{
+ 		        				placement: "right",
+ 		        				trigger: "hover",
+ 		        				container: 'body'
+
+ 		        			});
+ 		            return element;
+ 		        }
+ 		    });
+		},
+
+    /**
+    * Checks if the user's browser is an outdated version that won't work with
+    * MetacatUI well, and displays a warning message to the user..
+    * The user agent is checked against the `unsupportedBrowsers` list in the AppModel.
+    */
+    checkIncompatibility: function(){
+      //Check if this browser is incompatible with this app. i.e. It is an old browser version
+      var isUnsupportedBrowser = _.some( MetacatUI.appModel.get("unsupportedBrowsers"), function(browserRegEx){
+        var matches = navigator.userAgent.match(browserRegEx);
+        return (matches && matches.length > 0);
+      });
+
+      if( !isUnsupportedBrowser ){
+        return;
+      }
+      else{
+        //Show a warning message to the user about their browser.
+        this.showAlert("Your web browser is out of date. Update your browser for more security, " +
+                       "speed and the best experience on this site.", "alert-warning", this.$el,
+                       false, { remove: true });
+        this.$el.children(".alert-container").addClass("important-app-message");
+      }
+    },
+
+		/********************** Utilities ********************************/
+		// Various utility functions to use across the app //
+		/************ Function to add commas to large numbers ************/
+		commaSeparateNumber: function(val){
+			if(!val) return 0;
+
+			if(val < 1) return  Math.round(val * 100) / 100;
+
+		    while (/(\d+)(\d{3})/.test(val.toString())){
+		      val = val.toString().replace(/(\d+)(\d{3})/, '$1'+','+'$2');
+		    }
+		    return val;
+		 },
+		 numberAbbreviator: function(number, decimalPlaces) {
+		 	if(number === 0){
+		 		return 0;
+		 	}
+            decimalPlaces = Math.pow(10,decimalPlaces);
+            var abbreviations = [ "K", "M", "B", "T" ];
+
+            // Go through the array backwards, so we do the largest first
+            for (var i=abbreviations.length-1; i>=0; i--) {
+
+                // Convert array index to "1000", "1000000", etc
+                var size = Math.pow(10,(i+1)*3);
+
+                // If the number is bigger or equal do the abbreviation
+                if(size <= number) {
+
+                    // Here, we multiply by decimalPlaces, round, and then divide by decimalPlaces.
+                    // This gives us nice rounding to a particular decimal place.
+                    number = Math.round(number*decimalPlaces/size)/decimalPlaces;
+
+                    // Handle special case where we round up to the next abbreviation
+                    if((number == 1000) && (i < abbreviations.length - 1)) {
+                        number = 1;
+                        i++;
+                    }
+
+                    // Add the letter for the abbreviation
+                    number += abbreviations[i];
+                    break;
+                }
+            }
+            return number;
+        },
+		higlightInput: function(e){
+			if(!e) return;
+
+			e.preventDefault();
+			e.target.setSelectionRange(0, 9999);
+		},
+
+		widenInput: function(e){
+			$(e.target).css("width", "200px");
+		},
+
+		narrowInput: function(e){
+			$(e.target).delay(500).animate({"width": "60px"});
+		},
+
+		// scroll to top of page
+		scrollToTop: function() {
+			$("body,html").stop(true,true) //stop first for it to work in FF
+						  .animate({ scrollTop: 0 }, "slow");
+			return false;
+		},
+
+		scrollTo: function(pageElement, offsetTop){
+			//Find the header height if it is a fixed element
+			var headerOffset = (this.$("#Header").css("position") == "fixed") ? this.$("#Header").outerHeight() : 0;
+			var navOffset    = (this.$("#Navbar").css("position") == "fixed") ? this.$("#Navbar").outerHeight() : 0;
+			var totalOffset = headerOffset + navOffset;
+
+			$("body,html").stop(true,true) //stop first for it to work in FF
+						  .animate({ scrollTop: $(pageElement).offset().top - 40 - totalOffset}, 1000);
+			return false;
+		}
+
+	});
+	return AppView;
+});
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time) +
+ + + + + diff --git a/docs/docs/views_ColorPaletteView.js.html b/docs/docs/views_ColorPaletteView.js.html index 6e965690b..56670d5b4 100644 --- a/docs/docs/views_ColorPaletteView.js.html +++ b/docs/docs/views_ColorPaletteView.js.html @@ -91,13 +91,13 @@

Source: views/ColorPaletteView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_DataCatalogView.js.html b/docs/docs/views_DataCatalogView.js.html index 8d54c6e39..ca1b4094c 100644 --- a/docs/docs/views_DataCatalogView.js.html +++ b/docs/docs/views_DataCatalogView.js.html @@ -171,8 +171,15 @@

Source: views/DataCatalogView.js

if ((MetacatUI.appModel.get("searchHistory").length > 0) && (!this.searchModel || Object.keys(this.searchModel).length == 0) ) { - this.searchModel = _.last(MetacatUI.appModel.get("searchHistory")).search.clone(); - this.mapModel = _.last(MetacatUI.appModel.get("searchHistory")).map.clone(); + var lastSearchModel = _.last(MetacatUI.appModel.get("searchHistory")); + if(lastSearchModel){ + this.searchModel = lastSearchModel.clone(); + + if( lastSearchModel.map ){ + this.mapModel = lastSearchModel.map.clone(); + } + } + } else if ((typeof MetacatUI.appSearchModel !== "undefined") && (!this.searchModel || Object.keys(this.searchModel).length == 0) ) { @@ -3258,13 +3265,13 @@

Source: views/DataCatalogView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_DataCatalogViewWithFilters.js.html b/docs/docs/views_DataCatalogViewWithFilters.js.html index dd4fb5a64..ceb774c77 100644 --- a/docs/docs/views_DataCatalogViewWithFilters.js.html +++ b/docs/docs/views_DataCatalogViewWithFilters.js.html @@ -718,13 +718,13 @@

Source: views/DataCatalogViewWithFilters.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_DataItemView.js.html b/docs/docs/views_DataItemView.js.html index 345c1828e..e7ce155b1 100644 --- a/docs/docs/views_DataItemView.js.html +++ b/docs/docs/views_DataItemView.js.html @@ -899,13 +899,13 @@

Source: views/DataItemView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_DraftsView.js.html b/docs/docs/views_DraftsView.js.html new file mode 100644 index 000000000..ae068e713 --- /dev/null +++ b/docs/docs/views_DraftsView.js.html @@ -0,0 +1,252 @@ + + + + + MetacatUI Dev Docs: Source: views/DraftsView.js + + + + + + + + + + + +
+ +

Source: views/DraftsView.js

+ + + + + + +
+
+
define(["jquery", "underscore", "backbone", "localforage", "clipboard", "text!templates/draftsTemplate.html"],
+  function($, _, Backbone, LocalForage, Clipboard, draftsTemplate){
+    var view = Backbone.View.extend({
+      type: "DraftsView",
+      el: "#Content",
+      className: "div",
+      template: _.template(draftsTemplate),
+
+      initialize: function() {
+        return this;
+      },
+
+      render: function() {
+        var view = this;
+        var drafts = [];
+
+        LocalForage.iterate(function(value, key, iterationNumber) {
+          // Extract each draft
+          drafts.push({
+            key: key,
+            value: value,
+            fileName: (typeof value.title === "string") ?
+              value.title.substr(0, 50).replace(/[^a-zA-Z0-9_]/, "_") : "draft",
+            friendlyTimeDiff: view.friendlyTimeDiff(value.datetime)
+          });
+        }).then(function(){
+          // Sort by datetime
+          drafts = _.sortBy(drafts, function(draft) {
+            return draft.value.datetime.toString();
+          }).reverse();
+        }).then(function() {
+          // Render
+          view.$el.html(
+            view.template({
+              drafts: drafts
+            })
+          );
+
+          // Insert downloadables
+          view.insertDownloadables();
+          // Insert copiables
+          view.insertCopiables();
+        }).catch(function(err) {
+          console.log(err);
+          view.$el.html("<div>There was an error listing drafts.</div>");
+        });
+
+        return this;
+      },
+
+      // Attach a click handler for download buttons that triggers a draft
+      // or all drafts to be downloaded
+      insertDownloadables: function() {
+        var view = this;
+
+        // Build handlers for single downloaders
+        _.each(this.$el.find(".draft-download"), function(el) {
+          var a = $(el).find("a.download");
+
+          var text = $(el).find("textarea")[0].value;
+          var fileName = a.data("filename") || "draft.xml";
+
+          $(a).on("click", view.createDownloader(text, fileName));
+        });
+
+        // Build handler for Download All button
+        this.$el.find(".download-all").on("click", this.createDownloadAll());
+      },
+
+      // Creates a function for use as an event handler in insertDownloadables
+      // that creates a closure around the content (text) and filename and
+      // causes the browser to download the draft when clicked
+      createDownloader: function(text, fileName) {
+        return function() {
+          var blob = new Blob([text], { type: "application/xml" })
+          var url = window.URL.createObjectURL(blob);
+
+          var a = document.createElement("a");
+          a.style = "display: none;";
+          a.href = url;
+          a.download = fileName;
+          a.click();
+          a.remove();
+        }
+      },
+
+      createDownloadAll: function() {
+        var drafts = [];
+
+        _.each(this.$el.find("textarea"), function(textarea) {
+          drafts.push(textarea.value);
+        });
+
+        var doc = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<drafts>\n" +
+          _.map(drafts, function(draft) {
+            return "\t<draft>\n\t\t" +
+              draft +
+              "\n\t</draft>\n"
+          }).join("") +
+        "</drafts>";
+
+        return function() {
+          var blob = new Blob([doc], { type: "application/xml" })
+          var url = window.URL.createObjectURL(blob);
+
+          var a = document.createElement("a");
+          a.style = "display: none;";
+          a.href = url;
+          a.download = "drafts.xml";
+          a.click();
+          a.remove();
+        }
+      },
+
+      insertCopiables: function() {
+        var copiables = $(".copy-to-clipboard");
+
+        _.each(copiables, function(copiable, i) {
+          var clipboard = new Clipboard(copiable,
+            {
+              text: function(trigger) {
+                return $("#draft-" + i).text()
+              }
+            });
+
+          clipboard.on("success", function(e) {
+            var el = $(e.trigger);
+
+            $(el).html( $(document.createElement("span")).addClass("icon icon-ok success") );
+
+            // Use setTimeout instead of jQuery's built-in Events system because
+            // it didn't look flexible enough to allow me update innerHTML in
+            // a chain
+            setTimeout(function() {
+              $(el).html('<i class="icon icon-copy"></i> Copy to Clipboard');
+            }, 500)
+          });
+        });
+      },
+
+      /**
+       * Formats a time difference, in milliseconds, in a human-friendly way
+       * @param {string} datetime: A datetime as a string which needs to be
+       * parsed before working with
+       */
+      friendlyTimeDiff: function(datetime) {
+        var friendly,
+             now = new Date(),
+             then = new Date(datetime),
+             diff = now - then;
+
+        // Fall through from largest to smallest, finding the largest unit
+        // that describes the difference with a unit value of one or greater
+        if (diff > 2678400000) {
+          friendly = {
+            value: Math.round(diff / 2678400000) ,
+            unit: "month"
+          }
+        } else if (diff > 604800000) {
+          friendly = {
+            value: Math.round(diff / 604800000),
+            unit: "week"
+          }
+        } else if (diff > 86400000) {
+          friendly = {
+            value: Math.round(diff / 86400000),
+            unit: "day"
+          }
+        } else if (diff > 3600000) {
+          friendly = {
+            value: Math.round(diff / 3600000),
+            unit: "hour"
+          }
+        } else if (diff > 60000) {
+          friendly = {
+            value: Math.round(diff / 60000),
+            unit: "minute"
+          }
+        } else if (diff > 1000) {
+          friendly = {
+            value: Math.round(diff / 1000),
+            unit: "second"
+          }
+        } else {
+          // Shortcircuit if really small and return...
+          return "just now";
+        }
+
+        // Pluralize
+        if (friendly.value !== 1) {
+          friendly.unit = friendly.unit + "s"
+        }
+
+        return friendly.value + " " + friendly.unit + " ago";
+      }
+    })
+
+    return view;
+
+  });
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time) +
+ + + + + diff --git a/docs/docs/views_EditCollectionView.js.html b/docs/docs/views_EditCollectionView.js.html index e54562ee6..2bca35df6 100644 --- a/docs/docs/views_EditCollectionView.js.html +++ b/docs/docs/views_EditCollectionView.js.html @@ -269,13 +269,13 @@

Source: views/EditCollectionView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_EditorView.js.html b/docs/docs/views_EditorView.js.html index 1510d25ab..85fec0bbb 100644 --- a/docs/docs/views_EditorView.js.html +++ b/docs/docs/views_EditorView.js.html @@ -99,6 +99,8 @@

Source: views/EditorView.js

render: function(){ //Style the body as an Editor $("body").addClass("Editor rendering"); + + this.delegateEvents(); }, /** @@ -580,13 +582,13 @@

Source: views/EditorView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_ImageUploaderView.js.html b/docs/docs/views_ImageUploaderView.js.html index 231792171..e286df630 100644 --- a/docs/docs/views_ImageUploaderView.js.html +++ b/docs/docs/views_ImageUploaderView.js.html @@ -599,13 +599,13 @@

Source: views/ImageUploaderView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_MetadataView.js.html b/docs/docs/views_MetadataView.js.html index 9484e2750..f23142059 100644 --- a/docs/docs/views_MetadataView.js.html +++ b/docs/docs/views_MetadataView.js.html @@ -223,11 +223,83 @@

Source: views/MetadataView.js

this.renderMetadata(); } else if(this.model.get("formatType") == "DATA"){ - if(this.model.get("isDocumentedBy")){ - this.pid = _.first(this.model.get("isDocumentedBy")); + + //Get the metadata pids that document this data object + var isDocBy = this.model.get("isDocumentedBy"); + + //If there is only one metadata pid that documents this data object, then + // get that metadata model for this view. + if(isDocBy && isDocBy.length == 1){ + this.pid = _.first(isDocBy); this.getModel(this.pid); return; } + //If more than one metadata doc documents this data object, it is most likely + // multiple versions of the same metadata. So we need to find the latest version. + else if( isDocBy && isDocBy.length > 1 ){ + + var view = this; + + require(["collections/Filters", "collections/SolrResults"], function(Filters, SolrResults){ + //Create a search for the metadata docs that document this data object + var searchFilters = new Filters([{ + values: isDocBy, + fields: ["id", "seriesId"], + operator: "OR", + matchSubstring: false + }]), + //Create a list of search results + searchResults = new SolrResults([], { + rows: isDocBy.length, + query: searchFilters.getQuery(), + fields: "obsoletes,obsoletedBy,id" + }); + + //When the search results are returned, process those results + view.listenToOnce(searchResults, "sync", function(searchResults){ + + //Keep track of the latest version of the metadata doc(s) + var latestVersions = []; + + //Iterate over each search result and find the latest version of each metadata version chain + searchResults.each( function(searchResult){ + + //If this metadata isn't obsoleted by another object, it is the latest version + if( !searchResult.get("obsoletedBy") ){ + latestVersions.push( searchResult.get("id") ); + } + //If it is obsoleted by another object but that newer object does not document this data, then this is the latest version + else if( !_.contains(isDocBy, searchResult.get("obsoletedBy")) ){ + latestVersions.push( searchResult.get("id") ); + } + + }, view); + + //If at least one latest version was found (should always be the case), + if( latestVersions.length ){ + //Set that metadata pid as this view's pid and get that metadata model. + // TODO: Support navigation to multiple metadata docs. This should be a rare occurence, but + // it is possible that more than one metadata version chain documents a data object, and we need + // to show the user that the data is involved in multiple datasets. + view.pid = latestVersions[0]; + view.getModel(latestVersions[0]); + } + //If a latest version wasn't found, which should never happen, but just in case, default to the + // last metadata pid in the isDocumentedBy field (most liekly to be the most recent since it was indexed last). + else{ + var fallbackPid = _.last(isDocBy); + view.pid = fallbackPid; + view.getModel(fallbackPid); + } + + }); + + //Send the query to the Solr search service + searchResults.query(); + }); + + return; + } else{ this.noMetadata(this.model); } @@ -251,6 +323,7 @@

Source: views/MetadataView.js

packageModel.getMembers(); return; } + //Get the package information this.getPackageDetails(model.get("resourceMap")); @@ -982,8 +1055,6 @@

Source: views/MetadataView.js

* and inserts control elements onto the page for the user to interact with the dataset - edit, publish, etc. */ insertOwnerControls: function(){ - if( !MetacatUI.appModel.get("publishServiceUrl") ) - return false; //Do not show user controls for older versions of data sets if(this.model.get("obsoletedBy") && (this.model.get("obsoletedBy").length > 0)) @@ -997,7 +1068,8 @@

Source: views/MetadataView.js

viewRef = this; this.listenToOnce(this.model, "change:isAuthorized", function(){ - if(!model.get("isAuthorized") || model.get("archived")) return false; + if(!model.get("isAuthorized") || model.get("archived")) + return false; //Insert an Edit button if( _.contains(MetacatUI.appModel.get("editableFormats"), this.model.get("formatId")) ){ @@ -1013,19 +1085,46 @@

Source: views/MetadataView.js

})); } - //Insert a Publish button if its not already published with a DOI - if(!model.isDOI()){ - //Insert the template - container.append( - viewRef.doiTemplate({ - isAuthorized: true, - identifier: pid - })); + try{ + //Determine if this metadata can be published. + // The Publish feature has to be enabled in the app. + // The model cannot already have a DOI + var canBePublished = MetacatUI.appModel.get("enablePublishDOI") && !model.isDOI(); + + //If publishing is enabled, check if only certain users and groups can publish metadata + if( canBePublished ){ + //Get the list of authorized publishers from the AppModel + var authorizedPublishers = MetacatUI.appModel.get("enablePublishDOIForSubjects"); + //If the logged-in user is one of the subjects in the list or is in a group that is + // in the list, then this metadata can be published. Otherwise, it cannot. + if( Array.isArray(authorizedPublishers) && authorizedPublishers.length ){ + if( MetacatUI.appUserModel.hasIdentityOverlap(authorizedPublishers) ){ + canBePublished = true; + } + else{ + canBePublished = false; + } + } + } + + //If this metadata can be published, then insert the Publish button template + if( canBePublished ){ + //Insert a Publish button template + container.append( + viewRef.doiTemplate({ + isAuthorized: true, + identifier: pid + })); + } + } + catch(e){ + console.error("Cannot display the publish button: ", e); } //Check the authority on the package models //If there is no package, then exit now - if(!viewRef.packageModels || !viewRef.packageModels.length) return; + if(!viewRef.packageModels || !viewRef.packageModels.length) + return; //Check for authorization on the resource map var packageModel = this.packageModels[0]; @@ -1033,14 +1132,12 @@

Source: views/MetadataView.js

//if there is no package, then exit now if(!packageModel.get("id")) return; - //Listen for changes to the authorization flag - //packageModel.once("change:isAuthorized", viewRef.createProvEditor, viewRef); - //packageModel.once("sync", viewRef.createProvEditor, viewRef); - //Now get the RDF XML and check for the user's authority on this resource map packageModel.fetch(); packageModel.checkAuthority(); }); + + //Check if the current user has authority to `changePermission` on this metadata this.model.checkAuthority(); }, @@ -1094,7 +1191,7 @@

Source: views/MetadataView.js

var mdqFormatIds = MetacatUI.appModel.get("mdqFormatIds"); // Check of the current formatId is supported by the current - // metadata quality suite. If not, the 'Quality Report' button + // metadata quality suite. If not, the 'Assessment Report' button // will not be displacyed in the metadata controls panel. var thisFormatId = this.model.get("formatId"); var mdqFormatSupported = false; @@ -2844,13 +2941,13 @@

Source: views/MetadataView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_NavbarView.js.html b/docs/docs/views_NavbarView.js.html index 099b05a59..e4abacc39 100644 --- a/docs/docs/views_NavbarView.js.html +++ b/docs/docs/views_NavbarView.js.html @@ -218,13 +218,13 @@

Source: views/NavbarView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_StatsView.js.html b/docs/docs/views_StatsView.js.html index 437e38b5b..19f50a972 100644 --- a/docs/docs/views_StatsView.js.html +++ b/docs/docs/views_StatsView.js.html @@ -92,7 +92,7 @@

Source: views/StatsView.js

var nodeId = MetacatUI.appModel.get("nodeId"); // Overwrite the metrics display flags as set in the AppModel - this.hideMetadataAssessment = MetacatUI.appModel.get("hideSummaryMetadataAssessment"); + this.hideMetadataAssessment = false; this.hideCitationsChart = MetacatUI.appModel.get("hideSummaryCitationsChart"); this.hideDownloadsChart = MetacatUI.appModel.get("hideSummaryDownloadsChart"); this.hideViewsChart = MetacatUI.appModel.get("hideSummaryViewsChart"); @@ -105,6 +105,10 @@

Source: views/StatsView.js

} } + //If the metadata summery charts are turned off in the entire app, then they should be turned off here too + if( MetacatUI.appModel.get("hideSummaryMetadataAssessment") === true ){ + this.hideMetadataAssessment = true; + } if ( !this.hideCitationsChart || !this.hideDownloadsChart || !this.hideViewsChart ) { @@ -125,7 +129,9 @@

Source: views/StatsView.js

} if( !this.model ){ - this.model = new StatsModel(); + this.model = new StatsModel({ + hideMetadataAssessment: this.hideMetadataAssessment + }); } //Clear the page @@ -135,9 +141,6 @@

Source: views/StatsView.js

if(d3){ //this.listenTo(this.model, 'change:dataUploadDates', this.drawUploadChart); this.listenTo(this.model, 'change:temporalCoverage', this.drawCoverageChart); - this.listenTo(this.model, 'change:metadataDownloadDates', this.drawDownloadsChart); - this.listenTo(this.model, 'change:dataDownloadDates', this.drawDownloadsChart); - this.listenTo(this.model, 'change:downloadDates', this.drawDownloadsChart); this.listenTo(this.model, "change:dataUpdateDates", this.drawUpdatesChart); this.listenTo(this.model, "change:totalSize", this.drawTotalSize); this.listenTo(this.model, 'change:metadataCount', this.drawTotalCount); @@ -147,7 +150,6 @@

Source: views/StatsView.js

//this.listenTo(this.model, 'change:dataUploads', this.drawUploadTitle); } - this.listenTo(this.model, 'change:downloads', this.drawDownloadTitle); this.listenTo(this.model, 'change:lastEndDate', this.drawCoverageChartTitle); this.listenTo(this.model, "change:totalCount", this.showNoActivity); @@ -168,7 +170,7 @@

Source: views/StatsView.js

})); // Insert the metadata assessment chart - if(!this.hideMetadataAssessment){ + if( this.hideMetadataAssessment !== true ){ this.listenTo(this.model, "change:mdqScoresImage", this.drawMetadataAssessment); this.listenTo(this.model, "change:mdqScoresError", function () { this.$("#metadata-assessment-loading").remove(); @@ -208,9 +210,6 @@

Source: views/StatsView.js

} } - // Set the qualit engine flag appropriately - this.model.set("hideMetadataAssessment", view.hideMetadataAssessment); - //Start retrieving data from Solr this.model.getAll(); @@ -432,12 +431,12 @@

Source: views/StatsView.js

height: 300, width: 380, formatLabel: function(name){ - if((name !== undefined) && (name.indexOf("//ecoinformatics.org") > -1)){ + if((name !== undefined) && ((name.indexOf("//ecoinformatics.org") > -1) || (name.indexOf("//eml.ecoinformatics.org") > -1))){ //EML - extract the version only - if(name.substring(0,4) == "eml:") name = name.substr(name.lastIndexOf("/")+1).toUpperCase().replace('-', ' '); + if((name.substring(0,4) == "eml:") || (name.substring(0,6) == "https:")) name = name.substr(name.lastIndexOf("/")+1).toUpperCase().replace('-', ' '); //EML modules - if(name.indexOf("-//ecoinformatics.org//eml-") > -1) name = "EML " + name.substring(name.indexOf("//eml-")+6, name.lastIndexOf("-")) + " " + name.substr(name.lastIndexOf("-")+1, 5); + if((name.indexOf("-//ecoinformatics.org//eml-") > -1) || (name.indexOf("-//eml.ecoinformatics.org//eml-") > -1)) name = "EML " + name.substring(name.indexOf("//eml-")+6, name.lastIndexOf("-")) + " " + name.substr(name.lastIndexOf("-")+1, 5); } //Dryad - shorten it @@ -445,6 +444,15 @@

Source: views/StatsView.js

//FGDC - just display "FGDC {year}" else if((name !== undefined) && (name.indexOf("FGDC") > -1)) name = "FGDC " + name.substring(name.length-4); + //Onedcx v1.0 + else if((name !== undefined) && (name == "http://ns.dataone.org/metadata/schema/onedcx/v1.0")) name = "Onedcx v1.0"; + + //GMD-NOAA + else if((name !== undefined) && (name == "http://www.isotc211.org/2005/gmd-noaa")) name = "GMD-NOAA"; + + //GMD-PANGAEA + else if((name !== undefined) && (name == "http://www.isotc211.org/2005/gmd-pangaea")) name = "GMD-PANGAEA"; + if(name === undefined) name = ""; return name; } @@ -766,104 +774,6 @@

Source: views/StatsView.js

}, - /* - * drawDownloadsChart - draws a line chart representing the downloads over time - */ - drawDownloadsChart: function(){ - //Only draw the chart once both metadata and data dates have been retrieved - //if(!this.model.get("metadataDownloadDates") || !this.model.get("dataDownloadDates")) return; - - if(!this.model.get("downloadDates")) return; - - //Get the width of the chart by using the parent container width - var parentEl = this.$('.download-chart'); - var width = parentEl.width() || null; - - //If there are no download stats, show a message and exit - if(!this.model.get('downloads')){ - - var msg = "No one has downloaded any of this data or download statistics are not being reported"; - parentEl.html("<p class='subtle center'>" + msg + ".</p>"); - - return; - } - - //Set the frequency of our points - var frequency = 6; - - //Check which line we should draw first since the scale will be based off the first line - - var options = { - data: this.model.get('downloadDates'), - formatFromSolrFacetRanges: true, - id: "download-chart", - yLabel: "all downloads", - barClass: "packages", - roundedRect: true, - roundedRadius: 3, - barLabelClass: "packages", - width: width - }; - - var barChart = new BarChart(options); - parentEl.html(barChart.render().el); - - }, - - //drawDownloadTitle will draw a circle badge title for the downloads time series chart - drawDownloadTitle: function(){ - - //If d3 isn't supported in this browser or didn't load correctly, insert a text title instead - if(!d3){ - this.$('#downloads-title').html("<h2 class='packages fallback'>" + MetacatUI.appView.commaSeparateNumber(this.model.get('downloads')) + "</h2>"); - - return; - } - - //If there are 0 downloads, draw a default/blank chart title - if(!this.model.get('downloads')){ - var downloadChartTitle = new CircleBadge({ - id: "download-chart-title", - className: this.model.get("totalUploads") ? "default" : "no-activity", - globalR: 60, - data: [{ count: 0, label: "downloads" }] - }); - - this.$('#downloads-title').html(downloadChartTitle.render().el); - - this.listenToOnce(this.model, "change:totalUploads", this.drawDownloadTitle); - - return; - } - - //Get information for our download chart title - var titleChartData = [], - metadataDownloads = this.model.get("metadataDownloads"), - dataDownloads = this.model.get("dataDownloads"), - metadataClass = "metadata", - dataClass = "data"; - - if(metadataDownloads == 0) metadataClass = "default"; - if(dataDownloads == 0) dataClass = "default"; - - - var titleChartData = [ - {count: this.model.get("metadataDownloads"), label: "metadata", className: metadataClass}, - {count: this.model.get("dataDownloads"), label: "data", className: dataClass} - ]; - - //Draw the download chart title - var downloadChartTitle = new CircleBadge({ - id: "download-chart-title", - data: titleChartData, - className: "chart-title", - useGlobalR: true, - globalR: 60 - }); - - this.$('#downloads-title').html(downloadChartTitle.render().el); - }, - //Draw a bar chart for the temporal coverage drawCoverageChart: function(e, data){ @@ -1015,13 +925,13 @@

Source: views/StatsView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_TOCView.js.html b/docs/docs/views_TOCView.js.html index a191238a7..eba4b6e85 100644 --- a/docs/docs/views_TOCView.js.html +++ b/docs/docs/views_TOCView.js.html @@ -453,13 +453,13 @@

Source: views/TOCView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_UserView.js.html b/docs/docs/views_UserView.js.html index 13a6d2ed0..7f82f0cb1 100644 --- a/docs/docs/views_UserView.js.html +++ b/docs/docs/views_UserView.js.html @@ -403,13 +403,6 @@

Source: views/UserView.js

view.$("#total-upload-container").text(MetacatUI.appView.commaSeparateNumber(MetacatUI.statsModel.get("totalUploads"))); }); - MetacatUI.statsModel.once("change:downloads", function(){ - if( !this.get("downloads") ) - view.$("#total-download-wrapper, section.downloads").hide(); - else - view.$("#total-download-container").text(MetacatUI.appView.commaSeparateNumber(this.get("downloads"))); - }); - //Create a base query for the statistics var statsSearchModel = this.model.get("searchModel").clone(); statsSearchModel.set("exclude", [], {silent: true}).set("formatType", [], {silent: true}); @@ -1212,12 +1205,12 @@

Source: views/UserView.js

$(".token-tab").tab(); //Create clickable "Copy" buttons to copy text (e.g. token) to the user's clipboard - _.each([copyButton[0], copyRButton[0], copyMatlabButton[0]], function(btn){ - //Create a copy citation button - var clipboard = new Clipboard(btn); + var clipboard = new Clipboard(".copy"); + clipboard.on("success", function(e){ - copySuccess.show().delay(3000).fadeOut(); + $(".copy-success").show().delay(3000).fadeOut(); }); + clipboard.on("error", function(e){ var textarea = $(e.trigger).parent().children("textarea.token"); textarea.trigger("focus"); @@ -1228,7 +1221,6 @@

Source: views/UserView.js

textarea.tooltip("show"); }); - }); }, setUpAutocomplete: function() { @@ -1380,13 +1372,13 @@

Source: views/UserView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_filters_FilterGroupsView.js.html b/docs/docs/views_filters_FilterGroupsView.js.html index 5c8994cf2..918e1142b 100644 --- a/docs/docs/views_filters_FilterGroupsView.js.html +++ b/docs/docs/views_filters_FilterGroupsView.js.html @@ -856,13 +856,13 @@

Source: views/filters/FilterGroupsView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_metadata_EML211EditorView.js.html b/docs/docs/views_metadata_EML211EditorView.js.html index 0095fd129..89fd0ef43 100644 --- a/docs/docs/views_metadata_EML211EditorView.js.html +++ b/docs/docs/views_metadata_EML211EditorView.js.html @@ -31,6 +31,7 @@

Source: views/metadata/EML211EditorView.js

define(['underscore', 'jquery', 'backbone', + 'localforage', 'collections/DataPackage', 'models/metadata/eml211/EML211', 'models/metadata/eml211/EMLOtherEntity', @@ -44,7 +45,7 @@

Source: views/metadata/EML211EditorView.js

'text!templates/editor.html', 'collections/ObjectFormats', 'text!templates/editorSubmitMessage.html'], - function(_, $, Backbone, + function(_, $, Backbone, LocalForage, DataPackage, EML, EMLOtherEntity, ScienceMetadata, EditorView, CitationView, DataPackageView, EMLView, EMLEntityView, SignInView, EditorTemplate, ObjectFormats, EditorSubmitMessageTemplate){ @@ -77,6 +78,7 @@

Source: views/metadata/EML211EditorView.js

* @type {Object} */ events: _.extend(EditorView.prototype.events, { + "change" : "saveDraft", "click .data-package-item .edit" : "showEntity" }), @@ -544,6 +546,9 @@

Source: views/metadata/EML211EditorView.js

// Register a listener for any attribute change this.model.on("change", this.model.handleChange, this.model); + // Register a listener to save drafts on change + this.model.on("change", this.model.saveDraft, this.model); + // If any attributes have changed (including nested objects), show the controls if ( typeof MetacatUI.rootDataPackage.packageModel !== "undefined" ) { this.stopListening(MetacatUI.rootDataPackage.packageModel, "change:changed"); @@ -1050,8 +1055,71 @@

Source: views/metadata/EML211EditorView.js

"The file has not been added."; } MetacatUI.appView.showAlert(message, "alert-info", this.el, 10000, {remove: true}); - } + }, + /** + * Save a draft of the parent EML model + */ + saveDraft: function() { + var view = this; + + try { + var title = this.model.get("title") || "No title"; + + LocalForage.setItem(this.model.get("id"), { + id: this.model.get("id"), + datetime: (new Date()).toISOString(), + title: Array.isArray(title) ? title[0] : title, + draft: this.model.serialize() + }).then(function() { + view.clearOldDrafts(); + }); + } catch (ex) { + console.log("Error saving draft:", ex); + } + }, + + /** + * Clear older drafts by iterating over the sorted list of drafts + * stored by LocalForage and removing any beyond a hardcoded limit. + */ + clearOldDrafts: function() { + var drafts = []; + + try { + LocalForage.iterate(function(value, key, iterationNumber) { + // Extract each draft + drafts.push({ + key: key, + value: value + }); + }).then(function(){ + // Sort by datetime + drafts = _.sortBy(drafts, function(draft) { + return draft.value.datetime.toString(); + }).reverse(); + }).then(function() { + _.each(drafts, function(draft, i) { + var age = (new Date()) - new Date(draft.value.datetime); + var isOld = (age / 2678400000) > 1; // ~31days + + // Delete this draft is not in the most recent 100 or + // if older than 31 days + var shouldDelete = i > 100 || isOld; + + if (!shouldDelete) { + return; + } + LocalForage.removeItem(draft.key).then(function() { + // Item should be removed + }); + }) + }); + } + catch (ex) { + console.log("Failed to clear old drafts: ", ex); + } + } }); return EML211EditorView; }); @@ -1065,13 +1133,13 @@

Source: views/metadata/EML211EditorView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_metadata_EML211View.js.html b/docs/docs/views_metadata_EML211View.js.html new file mode 100644 index 000000000..b13cddf48 --- /dev/null +++ b/docs/docs/views_metadata_EML211View.js.html @@ -0,0 +1,2420 @@ + + + + + MetacatUI Dev Docs: Source: views/metadata/EML211View.js + + + + + + + + + + + +
+ +

Source: views/metadata/EML211View.js

+ + + + + + +
+
+
define(['underscore', 'jquery', 'backbone',
+        'views/metadata/ScienceMetadataView',
+        'views/metadata/EMLGeoCoverageView',
+        'views/metadata/EMLPartyView',
+    		'views/metadata/EMLMethodsView',
+    		'views/metadata/EMLTempCoverageView',
+        'models/metadata/eml211/EML211',
+        'models/metadata/eml211/EMLGeoCoverage',
+        'models/metadata/eml211/EMLKeywordSet',
+        'models/metadata/eml211/EMLParty',
+        'models/metadata/eml211/EMLProject',
+        'models/metadata/eml211/EMLText',
+        'models/metadata/eml211/EMLTaxonCoverage',
+        'models/metadata/eml211/EMLTemporalCoverage',
+		    'models/metadata/eml211/EMLMethods',
+        'text!templates/metadata/eml.html',
+        'text!templates/metadata/eml-people.html',
+        'text!templates/metadata/EMLPartyCopyMenu.html',
+        'text!templates/metadata/metadataOverview.html',
+        'text!templates/metadata/dates.html',
+        'text!templates/metadata/locationsSection.html',
+        'text!templates/metadata/taxonomicCoverage.html',
+    		'text!templates/metadata/taxonomicClassificationTable.html',
+    		'text!templates/metadata/taxonomicClassificationRow.html'],
+	function(_, $, Backbone,
+      ScienceMetadataView, EMLGeoCoverageView, EMLPartyView, EMLMethodsView, EMLTempCoverageView,
+
+      EML, EMLGeoCoverage, EMLKeywordSet, EMLParty, EMLProject, EMLText, EMLTaxonCoverage,
+
+			EMLTemporalCoverage, EMLMethods, Template, PeopleTemplate, EMLPartyCopyMenuTemplate, OverviewTemplate,
+			DatesTemplate, LocationsTemplate, TaxonomicCoverageTemplate, TaxonomicClassificationTable, TaxonomicClassificationRow){
+
+    var EMLView = ScienceMetadataView.extend({
+
+      type: "EML211",
+
+      el: '#metadata-container',
+
+      events: {
+        	"change .text"                 : "updateText",
+
+        	"change .basic-text"            : "updateBasicText",
+        	"keyup  .basic-text.new"        : "addBasicText",
+        	"mouseover .basic-text-row .remove" : "previewTextRemove",
+        	"mouseout .basic-text-row .remove"  : "previewTextRemove",
+
+			    "change .pubDate input"          : "updatePubDate",
+			    "focusout .pubDate input"        : "showPubDateValidation",
+
+			    "keyup .eml-geocoverage.new"        : "updateLocations",
+
+			    "change .taxonomic-coverage"             : "updateTaxonCoverage",
+			    "keyup .taxonomic-coverage .new input"   : "addNewTaxon",
+			    "keyup .taxonomic-coverage .new select"  : "addNewTaxon",
+          "focusout .taxonomic-coverage tr"        : "showTaxonValidation",
+        	"click .taxonomic-coverage-row .remove"  : "removeTaxonRank",
+        	"mouseover .taxonomic-coverage .remove"  : "previewTaxonRemove",
+        	"mouseout .taxonomic-coverage .remove"   : "previewTaxonRemove",
+
+        	"change .keywords"               : "updateKeywords",
+        	"keyup .keyword-row.new input"   : "addNewKeyword",
+        	"mouseover .keyword-row .remove" : "previewKeywordRemove",
+        	"mouseout .keyword-row .remove"  : "previewKeywordRemove",
+
+          "change .usage"                  : "updateRadioButtons",
+
+          "change .funding"                : "updateFunding",
+        	"keyup .funding.new"             : "addFunding",
+        	"mouseover .funding-row .remove" : "previewFundingRemove",
+        	"mouseout .funding-row .remove"  : "previewFundingRemove",
+        	"keyup .funding.error"           : "handleFundingTyping",
+
+          "click .side-nav-item"           : "switchSection",
+
+			    "keyup .eml-party.new"     : "handlePersonTyping",
+        	"change #new-party-menu"   : "chooseNewPersonType",
+        	"click .eml-party .copy"   : "showCopyPersonMenu",
+        	"click #copy-party-save"   : "copyPerson",
+        	"click .eml-party .remove" : "removePerson",
+        	"click .eml-party .move-up" : "movePersonUp",
+        	"click .eml-party .move-down" : "movePersonDown",
+
+			    "click  .remove" : "handleRemove"
+        },
+
+        /* A list of the subviews */
+        subviews: [],
+
+        /* The active section in the view - can only be the section name (e.g. overview, people)
+         * The active section is highlighted in the table of contents and is scrolled to when the page loads
+         */
+        activeSection: "overview",
+
+        /* The visible section in the view - can either be the section name (e.g. overview, people) or "all"
+         * The visible section is the ONLY section that is displayed. If set to all, all sections are displayed.
+         */
+        visibleSection: "overview",
+
+        /* Templates */
+        template: _.template(Template),
+        overviewTemplate: _.template(OverviewTemplate),
+        datesTemplate: _.template(DatesTemplate),
+        locationsTemplate: _.template(LocationsTemplate),
+        taxonomicCoverageTemplate: _.template(TaxonomicCoverageTemplate),
+		    taxonomicClassificationTableTemplate: _.template(TaxonomicClassificationTable),
+        taxonomicClassificationRowTemplate: _.template(TaxonomicClassificationRow),
+        copyPersonMenuTemplate: _.template(EMLPartyCopyMenuTemplate),
+        peopleTemplate: _.template(PeopleTemplate),
+
+        initialize: function(options) {
+
+        	//Set up all the options
+        	if(typeof options == "undefined") var options = {};
+
+        	//The EML Model and ID
+      		this.model = options.model || new EML();
+      		if(!this.model.get("id") && options.id) this.model.set("id", options.id);
+
+    		  //Get the current mode
+    		  this.edit = options.edit || false;
+
+          return this;
+        },
+
+        /* Render the view */
+        render: function() {
+			    MetacatUI.appModel.set('headerType', 'default');
+
+    			//Render the basic structure of the page and table of contents
+    			this.$el.html(this.template({
+    				activeSection: this.activeSection,
+    				visibleSection: this.visibleSection
+    			}));
+    			this.$container = this.$(".metadata-container");
+
+    			//Render all the EML sections when the model is synced
+    			this.renderAllSections();
+    			if(!this.model.get("synced"))
+    				this.listenToOnce(this.model, "sync", this.renderAllSections);
+
+    			//Listen to updates on the data package collections
+    			_.each(this.model.get("collections"), function(dataPackage){
+    				if(dataPackage.type != "DataPackage") return;
+
+    				//When the data package has been saved, render the EML again
+    				this.listenTo(dataPackage, "successSaving", this.renderAllSections);
+    			}, this);
+
+          return this;
+        },
+
+        renderAllSections: function(){
+        	this.renderOverview();
+  	    	this.renderPeople();
+  	    	this.renderDates();
+  	    	this.renderLocations();
+  	    	this.renderTaxa();
+  	    	this.renderMethods();
+  	    	this.renderProject();
+  	    	this.renderSharing();
+
+  	    	//Scroll to the active section
+  	    	if(this.activeSection != "overview"){
+  	    		MetacatUI.appView.scrollTo(this.$(".section." + this.activeSection));
+  	    	}
+
+			    //When scrolling through the metadata, highlight the side navigation
+  	    	var view = this;
+  	    	$(document).scroll(function(){
+  	    		view.highlightTOC.call(view);
+  	    	});
+
+        },
+
+        /*
+         * Renders the Overview section of the page
+         */
+	    renderOverview: function(){
+	    	//Get the overall view mode
+	    	var edit = this.edit;
+
+	    	var view = this;
+
+	    	//Append the empty layout
+	    	var overviewEl = this.$container.find(".overview");
+	    	$(overviewEl).html(this.overviewTemplate());
+
+			  //Title
+		    this.renderTitle();
+		    this.listenTo(this.model, "change:title", this.renderTitle);
+
+	    	//Abstract
+	    	_.each(this.model.get("abstract"), function(abs){
+		    	var abstractEl = this.createEMLText(abs, edit, "abstract");
+
+		    	//Add the abstract element to the view
+		    	$(overviewEl).find(".abstract").append(abstractEl);
+	    	}, this);
+
+	    	if(!this.model.get("abstract").length){
+	    		var abstractEl = this.createEMLText(null, edit, "abstract");
+
+	    		//Add the abstract element to the view
+		    	$(overviewEl).find(".abstract").append(abstractEl);
+	    	}
+
+	    	//Keywords
+	    	//Iterate over each keyword and add a text input for the keyword value and a dropdown menu for the thesaurus
+	    	_.each(this.model.get("keywordSets"), function(keywordSetModel){
+	    		_.each(keywordSetModel.get("keywords"), function(keyword){
+		    		this.addKeyword(keyword, keywordSetModel.get("thesaurus"));
+	    		}, this);
+	    	}, this);
+
+	    	//Add a new keyword row
+	    	this.addKeyword();
+
+	    	//Alternate Ids
+		    var altIdsEls = this.createBasicTextFields("alternateIdentifier", "Add a new alternate identifier");
+		    $(overviewEl).find(".altids").append(altIdsEls);
+
+		    //Usage
+		    //Find the model value that matches a radio button and check it
+			  // Note the replace() call removing newlines and replacing them with a single space
+			  // character. This is a temporary hack to fix https://github.com/NCEAS/metacatui/issues/128
+		    if(this.model.get("intellectualRights"))
+		    	this.$(".checkbox .usage[value='" + this.model.get("intellectualRights").replace(/\r?\n|\r/g, ' ') + "']").prop("checked", true);
+
+		    //Funding
+		    this.renderFunding();
+
+  			// pubDate
+  			// BDM: This isn't a createBasicText call because that helper
+  			// assumes multiple values for the category
+  			// TODO: Consider a re-factor of createBasicText
+  			var pubDateInput = $(overviewEl).find("input.pubDate").val(this.model.get("pubDate"));
+
+  			//Initialize all the tooltips
+  			this.$(".tooltip-this").tooltip();
+
+	    },
+
+	    renderTitle: function(){
+	    	var titleEl = this.createBasicTextFields("title", "Example: Greater Yellowstone Rivers from 1:126,700 U.S. Forest Service Visitor Maps (1961-1983)", false);
+	    	this.$container.find(".overview").find(".title-container").html(titleEl);
+	    },
+
+	    /*
+       * Renders the People section of the page
+       */
+	    renderPeople: function(){
+	    	this.$(".section.people").empty().append("<h2>People</h2>");
+
+	    	var PIs      = _.filter(this.model.get("associatedParty"), function(party){ return party.get("roles").includes("principalInvestigator") }),
+	    		coPIs      = _.filter(this.model.get("associatedParty"), function(party){ return party.get("roles").includes("coPrincipalInvestigator") }),
+	    		collbalPIs = _.filter(this.model.get("associatedParty"), function(party){ return party.get("roles").includes("collaboratingPrincipalInvestigator") }),
+	    		custodian  = _.filter(this.model.get("associatedParty"), function(party){ return party.get("roles").includes("custodianSteward") }),
+	    		user       = _.filter(this.model.get("associatedParty"), function(party){ return party.get("roles").includes("user") });
+
+	    	var emptyTypes = [];
+
+	    	this.partyTypeMap = {
+	    			"collaboratingPrincipalInvestigator" : "Collaborating-Principal Investigators",
+	    			"coPrincipalInvestigator" : "Co-Principal Investigators",
+	    			"principalInvestigator" : "Principal Investigators",
+	    			"creator" : "Dataset Creators (Authors/Owners/Originators)",
+	    			"contact" : "Contacts",
+	    			"metadataProvider" : "Metadata Provider",
+	    			"custodianSteward" : "Custodians/Stewards",
+	    			"publisher" : "Publisher",
+	    			"user" : "Users"
+	    	}
+
+        //Insert the people template
+        this.$(".section[data-section='people']").html(this.peopleTemplate());
+
+        if( this.model.get("creator").length ){
+  	    	//Creators
+  	    	_.each(this.model.get("creator"), function(creator){
+            this.renderPerson(creator, "creator")
+          }, this);
+  	    	this.renderPerson(null, "creator");
+        }
+        else{
+          var creator = new EMLParty({ type: "creator", parentModel: this.model });
+          this.model.get("creator").push(creator);
+          creator.createFromUser();
+          this.renderPerson(creator);
+          this.renderPerson(null, "creator");
+        }
+
+        if( this.model.get("contact").length ){
+  	    	//Contacts
+          _.each(this.model.get("contact"), function(contact){
+            this.renderPerson(contact, "contact")
+          }, this);
+  	    	this.renderPerson(null, "contact");
+        }
+        else{
+          var contact = new EMLParty({ type: "contact", parentModel: this.model });
+          this.model.get("contact").push(contact);
+          contact.createFromUser();
+          this.renderPerson(contact);
+          this.renderPerson(null, "contact");
+        }
+
+	    	//Principal Investigators
+	    	if(PIs.length){
+		    	this.$(".section.people").append("<h4>" + this.partyTypeMap["principalInvestigator"] + "<i class='required-icon hidden' data-category='principalInvestigator'></i></h4>",
+		    			'<div class="row-striped" data-attribute="principalInvestigator"></div>');
+
+          _.each(PIs, function(PI){
+            this.renderPerson(PI, "principalInvestigator")
+          }, this);
+
+	    		this.renderPerson(null, "principalInvestigator");
+	    	}
+	    	else{
+	    		emptyTypes.push("principalInvestigator");
+	    	}
+
+	    	//Co-PIs
+	    	if(coPIs.length){
+		    	this.$(".section.people").append("<h4>" + this.partyTypeMap["coPrincipalInvestigator"] + "<i class='required-icon hidden' data-category='coPrincipalInvestigator'></i></h4>",
+		    			'<div class="row-striped" data-attribute="coPrincipalInvestigator"></div>');
+          _.each(coPIs, function(coPI){
+            this.renderPerson(coPI, "coPrincipalInvestigator")
+          }, this);
+
+	    		this.renderPerson(null, "coPrincipalInvestigator");
+	    	}
+	    	else
+	    		emptyTypes.push("coPrincipalInvestigator");
+
+	    	//Collab PIs
+	    	if(collbalPIs.length){
+		    	this.$(".section.people").append("<h4>" + this.partyTypeMap["collaboratingPrincipalInvestigator"] + "<i class='required-icon hidden' data-category='collaboratingPrincipalInvestigator'></i></h4>",
+		    			'<div class="row-striped" data-attribute="collaboratingPrincipalInvestigator"></div>');
+          _.each(collbalPIs, function(collbalPI){
+            this.renderPerson(collbalPI, "collaboratingPrincipalInvestigator")
+          }, this);
+
+	    		this.renderPerson(null, "collaboratingPrincipalInvestigator");
+	    	}
+	    	else
+	    		emptyTypes.push("collaboratingPrincipalInvestigator");
+
+	    	//Metadata Provider
+	    	if(this.model.get("metadataProvider").length){
+		    	this.$(".section.people").append("<h4>" + this.partyTypeMap["metadataProvider"] + "<i class='required-icon hidden' data-category='metadataProvider'></i></h4>",
+		    			'<div class="row-striped" data-attribute="metadataProvider"></div>');
+          _.each(this.model.get("metadataProvider"), function(provider){
+            this.renderPerson(provider, "metadataProvider")
+          }, this);
+
+		    	this.renderPerson(null, "metadataProvider");
+	    	}
+	    	else
+	    		emptyTypes.push("metadataProvider");
+
+	    	//Custodian/Steward
+	    	if(custodian.length){
+	    		this.$(".section.people").append("<h4>" + this.partyTypeMap["custodianSteward"] + "<i class='required-icon hidden' data-category='custodianSteward'></i></h4>",
+	    			'<div class="row-striped" data-attribute="custodianSteward"></div>');
+
+          _.each(custodian, function(custodianInd){
+            this.renderPerson(custodianInd, "custodianSteward")
+          }, this);
+
+	    		this.renderPerson(null, "custodianSteward");
+	    	}
+	    	else
+	    		emptyTypes.push("custodianSteward");
+
+	    	//Publisher
+	    	if(this.model.get("publisher").length){
+	    		this.$(".section.people").append("<h4>" + this.partyTypeMap["publisher"] + "<i class='required-icon hidden' data-category='publisher'></i></h4>",
+	    			'<p class="subtle">Only one publisher can be specified.</p>',
+	    			'<div class="row-striped" data-attribute="publisher"></div>');
+
+          _.each(this.model.get("publisher"), function(publisher){
+            this.renderPerson(publisher, "publisher")
+          }, this);
+	    	}
+	    	else
+	    		emptyTypes.push("publisher");
+
+	    	//User
+	    	if(user.length){
+		    	this.$(".section.people").append("<h4>" + this.partyTypeMap["user"] + "<i class='required-icon hidden' data-category='user'></i></h4>",
+		    			'<div class="row-striped" data-attribute="user"></div>');
+
+          _.each(user, function(userInd){
+            this.renderPerson(userInd, "user")
+          }, this);
+
+		    	this.renderPerson(null, "user");
+	    	}
+	    	else
+	    		emptyTypes.push("user");
+
+	    	//Display a drop-down menu for all the empty party types
+	    	if(emptyTypes.length){
+
+          //Create a dropdown menu for adding new person types
+		    	var menu = $(document.createElement("select")).attr("id", "new-party-menu").addClass("header-dropdown");
+
+          //Add the first option to the menu, which works as a label
+          menu.append( $(document.createElement("option")).text("Choose new person or organization role ...") );
+
+          //Add some help text for the menu
+          var helpText = "Optionally add other contributors, collaborators, and maintainers of this dataset.";
+          menu.attr("title", helpText);
+
+          //Add a container element for the new party
+		    	var newPartyContainer = $(document.createElement("div"))
+		    							.attr("data-attribute", "new")
+		    							.addClass("row-striped");
+
+          //For each party type that is empty, add it to the menu as an option
+		    	_.each(emptyTypes, function(type){
+
+		    		$(menu).append( $(document.createElement("option"))
+                            .val(type)
+                            .text(this.partyTypeMap[type]) );
+
+		    	}, this);
+
+          //Add the menu and new party element to the page
+		    	this.$(".section.people").append(menu, newPartyContainer);
+
+          //Render a new blank party form
+		    	this.renderPerson(null, "new");
+	    	}
+
+	    	// When the EML model has been saved, re-render the people section.
+	    	// This is needed because the EML model will automatically create a creator and contact
+	    	//   if none is supplied by the user. This will render them when they are added.
+	    	var view = this;
+	    	this.listenTo(this.model, "successSaving", function(){
+	    		this.renderPeople();
+	    	});
+
+    		//Initialize the tooltips
+    		this.$("input.tooltip-this").tooltip({
+    			placement: "top",
+    			title: function(){
+    				return $(this).attr("data-title") || $(this).attr("placeholder")
+    			},
+    			delay: 1000
+    		});
+
+	    },
+
+	    renderPerson: function(emlParty, partyType){
+
+	    	//If no model is given, create a new model
+	    	if(!emlParty){
+	    		var emlParty = new EMLParty({
+	    			parentModel: this.model
+	    		});
+
+	    		//Mark this model as new
+	    		var isNew = true;
+
+	    		//Find the party type or role based on the type given
+          if(partyType){
+            if( _.includes(emlParty.get("roleOptions"), partyType) ){
+              emlParty.get("roles").push(partyType);
+            } else if ( _.includes(emlParty.get("typeOptions"), partyType) ){
+              emlParty.set("type", partyType);
+            }
+          }
+
+	    	}
+	    	else{
+	    		var isNew = false;
+
+		    	//Get the party type, if it was not sent as a parameter
+		    	if(!partyType || !partyType.length ){
+            var partyType = emlParty.get("type") || emlParty.get("roles");
+          }
+
+	    	}
+
+        // partyType is a string when if it's a 'type' and an array if it's 'roles'
+        // If it's a string, convert to an array for the subsequent _.each() function
+        if(typeof partyType == "string"){
+          partyType = [partyType]
+        }
+
+        _.each(partyType, function(partyType){
+
+  	    	//Find the container section for this party type
+  	    	var container = this.$(".section.people").find('[data-attribute="' + partyType + '"]');
+
+  	    	//See if this view already exists
+  	    	if( !isNew && container.length && emlParty ){
+  	    		var partyView;
+
+  	    		_.each(container.find(".eml-party"), function(singlePartyEl){
+
+  	    			//If this EMLPartyView element is for the current model, then get the View
+  	    			if( $(singlePartyEl).data("model") == emlParty )
+  	    				partyView = $(singlePartyEl).data("view");
+  	    		});
+
+  	    		//If a partyView was found, just rerender it and exit
+  	    		if(partyView){
+  	    			partyView.render();
+  	    			return;
+  	    		}
+  	    	}
+
+  	    	//If there still is no partyView found, create a new one
+  	    	var partyView = new EMLPartyView({
+      			model: emlParty,
+      			edit: this.edit,
+      			isNew: isNew
+  	    	});
+
+  	    	//If this person type is not on the page yet, add it
+          // For now, this only adds the first role if person has multiple roles
+  	    	if(!container.length){
+  	    		this.addNewPersonType(emlParty.get("type") || emlParty.get("roles")[0]);
+  	    		container = this.$(".section.people").find('[data-attribute="' + partyType + '"]');
+  	    	}
+
+  	    	if(isNew){
+            container.append(partyView.render().el);
+          }else{
+  	    		if(container.find(".new").length)
+  	    			container.find(".new").before(partyView.render().el);
+  	    		else
+  	    			container.append(partyView.render().el);
+  	    	}
+
+        }, this);
+
+
+	    },
+
+	    /*
+	     * This function reacts to the user typing a new person in the person section (an EMLPartyView)
+	     */
+	    handlePersonTyping: function(e){
+	    	var container = $(e.target).parents(".eml-party"),
+    			emlParty  = container.length? container.data("model") : null,
+    			partyType = container.length && emlParty ? emlParty.get("roles")[0] || emlParty.get("type") : null;
+
+    		if(this.$("[data-attribute='" + partyType + "'] .eml-party.new").length > 1) return;
+
+			//Render a new person
+			if(partyType != "publisher")
+				this.renderPerson(null, partyType);
+	    },
+
+	    /*
+	     * This function is called when someone chooses a new person type from the dropdown list
+	     */
+	    chooseNewPersonType: function(e){
+	    	var partyType = $(e.target).val();
+
+	    	if(!partyType) return;
+
+	    	//Get the form and model
+	    	var partyForm  = this.$(".section.people").find('[data-attribute="new"]'),
+	    		partyModel = partyForm.find(".eml-party").data("model");
+
+	    	//Set the type on this person form
+	    	partyForm.attr("data-attribute", partyType);
+
+	    	//Get the party type dropdown menu
+	    	var partyMenu = this.$("#new-party-menu");
+
+	    	//Add a new header
+	    	partyMenu.before("<h4>" + this.partyTypeMap[partyType] + "</h4>");
+
+	    	if(partyType == "publisher")
+	    		partyMenu.before('<p class="subtle">Only one publisher can be specified.</p>');
+
+	    	//Remove this type from the dropdown menu
+	    	partyMenu.find("[value='" + partyType + "']").remove();
+
+	    	//Remove the menu from the page temporarily
+	    	partyMenu.detach();
+
+	    	//Add the new party type form
+	    	var newPartyContainer = $(document.createElement("div"))
+									.attr("data-attribute", "new")
+									.addClass("row-striped");
+	    	this.$(".section.people").append(newPartyContainer);
+	    	this.renderPerson(null, "new");
+	    	$(newPartyContainer).before(partyMenu);
+
+	    	//Update the model
+        if( _.includes(partyModel.get("roleOptions"), partyType) ){
+          partyModel.get("roles").push(partyType);
+        } else {
+          partyModel.set("type", partyType);
+        }
+
+	    	if(partyModel.isValid()){
+	    		partyModel.mergeIntoParent();
+
+	    		//Add a new person of that type
+	    		this.renderPerson(null, partyType);
+	    	}
+	    	else{
+	    		partyForm.find(".eml-party").data("view").showValidation();
+	    	}
+	    },
+
+	    /*
+	     * addNewPersonType - Adds a header and container to the People section for the given party type/role,
+	     */
+	    addNewPersonType: function(partyType){
+	    	if(!partyType) return;
+
+			// Container element to hold all parties of this type
+			var partyTypeContainer = $(document.createElement("div")).addClass("party-type-container");
+
+	    	// Add a new header for the party type
+	    	var header = $(document.createElement("h4")).text(this.partyTypeMap[partyType]);
+			$(partyTypeContainer).append(header);
+
+	    	//Remove this type from the dropdown menu
+	    	this.$("#new-party-menu").find("[value='" + partyType + "']").remove();
+
+	    	//Add the new party container
+	    	var partyRow = $(document.createElement("div"))
+									.attr("data-attribute", partyType)
+									.addClass("row-striped");
+	    	partyTypeContainer.append(partyRow);
+
+			// Add in the new party type container just before the dropdown
+			this.$("#new-party-menu").before(partyTypeContainer);
+
+	    	//Add a blank form to the new person type section
+	    	this.renderPerson(null, partyType);
+	    },
+
+	    /*
+	     * showCopyPersonMenu: Displays a modal window to the user with a list of roles that they can
+	     * copy this person to
+	     */
+	    showCopyPersonMenu: function(e){
+
+	    	//Get the EMLParty to copy
+	    	var partyToCopy = $(e.target).parents(".eml-party").data("model"),
+	    		menu = this.$("#copy-person-menu");
+
+	    	//Check if the modal window menu has been created already
+	    	if( !menu.length ){
+
+	    		//Create the modal window menu from the template
+	    		menu = $(this.copyPersonMenuTemplate());
+
+	    		//Add to the DOM
+		    	this.$el.append(menu);
+
+		    	//Initialize the modal
+		    	menu.modal();
+	    	}
+	    	else{
+	    		//Reset all the checkboxes
+	    		menu.find("input:checked").prop("checked", false);
+	    		menu.find(".disabled")
+	    			.prop("disabled", false)
+	    			.removeClass("disabled")
+	    			.parent(".checkbox")
+	    			.attr("title", "");
+	    	}
+
+	    	//Disable the roles this person is already in
+	    	var currentRoles = partyToCopy.get("roles") || partyToCopy.get("type") || "";
+        // "type" is a string and "roles" is an array.
+        // so that we can use _.each() on both, convert "type" to an array
+        if(typeof currentRoles == "string"){
+          currentRoles = [currentRoles];
+        }
+        _.each(currentRoles, function(currentRole){
+          menu.find("input[value='" + currentRole + "']")
+  	    		.prop("disabled", "disabled")
+  	    		.addClass("disabled")
+  	    		.parent(".checkbox")
+  	    		.attr("title", "This person is already in the " + this.partyTypeMap[currentRole] + " list.");
+
+        }, this);
+
+	    	//If there is already one publisher, disable that option
+	    	if( this.model.get("publisher").length ){
+	    		var publisherName = this.model.get("publisher")[0].get("individualName") ?
+	    							this.model.get("publisher")[0].get("individualName").givenName + " " +
+	    							this.model.get("publisher")[0].get("individualName").surName :
+	    							this.model.get("publisher")[0].get("organizationName") ||
+	    							this.model.get("publisher")[0].get("positionName") ||
+	    							"Someone";
+
+	    		menu.find("input[value='publisher']")
+		    		.prop("disabled", "disabled")
+		    		.addClass("disabled")
+		    		.parent(".checkbox")
+		    		.attr("title", publisherName + " is already listed as the publisher. (There can be only one).");
+
+	    	}
+
+	    	//Attach the EMLParty to the menu DOMs
+	    	menu.data({
+	    		EMLParty: partyToCopy
+	    	});
+
+	    	//Show the modal window menu now
+	    	menu.modal("show");
+	    },
+
+	    /*
+	     * copyPerson: Gets the selected checkboxes from the copy person menu and copies the EMLParty
+	     * to those new roles
+	     */
+	    copyPerson: function(){
+
+	    	//Get all the checked boxes
+	    	var checkedBoxes = this.$("#copy-person-menu input:checked"),
+	    	//Get the EMLParty to copy
+	    		partyToCopy  = this.$("#copy-person-menu").data("EMLParty");
+
+	    	//For each selected role,
+	    	_.each(checkedBoxes, function(checkedBox){
+
+	    		//Get the roles
+	    		var role = $(checkedBox).val(),
+	    			isAssocParty = _.contains(partyToCopy.get("roleOptions"), role);
+
+	    		//If the new role is an associated party ...
+	    		if( isAssocParty ){
+
+		    		//If there are no parties in this role yet,
+	    			// then add this person type to the view
+	    			if( !this.model.get("associatedParty").length )
+	    				this.addNewPersonType(role);
+	    			else if( !_.find(this.model.get("associatedParty"), function(p){
+	    				return  p.get("role") == role;
+	    			}) ){
+	    				this.addNewPersonType(role);
+	    			}
+
+	    			//Create a new EMLParty model
+    				var newPerson = new EMLParty();
+
+	    			//Create all the attributes for the new person. We're only changing the role
+	    			newPerson.set( partyToCopy.copyValues() );
+	    			newPerson.set("type", "associatedParty");
+	    			newPerson.set("roles", [role]);
+
+            //Add this new EMLParty to the EML model
+            this.model.addParty(newPerson);
+
+	    			//Render this new person
+		    		this.renderPerson(newPerson, role);
+	    		}
+	    		//If the new role is not an associated party...
+	    		else{
+		    		//If there are no parties in this role yet,
+	    			// then add this person type to the view
+	    			if( !this.model.get(role).length )
+		    			this.addNewPersonType(role);
+
+	    			//Create a new EMLParty model
+    				var newPerson = new EMLParty();
+
+    				// Copy the attributes from the original person
+    				// and set it on the new person
+    				newPerson.set(partyToCopy.copyValues());
+    				newPerson.set("type", role);
+    				newPerson.set("roles", newPerson.defaults().role);
+
+            //Add this new EMLParty to the EML model
+            this.model.addParty(newPerson);
+
+	    			//Render this new person
+		    		this.renderPerson(newPerson, role);
+	    		}
+
+	    	}, this);
+
+	    	//If there was at least one copy created, then trigger the change event
+	    	if(checkedBoxes.length){
+	    		this.model.trickleUpChange();
+	    	}
+	    },
+
+	    removePerson: function(e){
+	    	e.preventDefault();
+
+	    	//Get the party view el, view, and model
+	    	var partyEl = $(e.target).parents(".eml-party"),
+	    		partyView = partyEl.data("view"),
+	    		partyToRemove = partyEl.data("model");
+
+	    	//If there is no model found, we have nothing to do, so exit
+	    	if(!partyToRemove) return false;
+
+	    	//Call removeParty on the EML211 model to remove this EMLParty
+	    	this.model.removeParty(partyToRemove);
+
+	    	//Let the EMLPartyView remove itself
+	    	partyView.remove();
+
+			},
+
+      /**
+       * Attempt to move the current person (Party) one index backward (up).
+       *
+       * @param {EventHandler} e: The click event handler
+       */
+			movePersonUp: function(e){
+	    	e.preventDefault();
+
+	    	// Get the party view el, view, and model
+	    	var partyEl = $(e.target).parents(".eml-party"),
+            model = partyEl.data("model"),
+            next = $(partyEl).prev().not(".new");
+
+        if (next.length === 0) {
+          return;
+        }
+
+				// Remove current view, create and insert a new one for the model
+        $(partyEl).remove();
+
+        var newView = new EMLPartyView({
+          model: model,
+          edit: this.edit
+        });
+
+        $(next).before(newView.render().el);
+
+        // Move the party down within the model too
+				this.model.movePartyUp(model);
+				this.model.trickleUpChange();
+			},
+
+      /**
+       * Attempt to move the current person (Party) one index forward (down).
+       *
+       * @param {EventHandler} e: The click event handler
+       */
+	    movePersonDown: function(e){
+				e.preventDefault();
+
+	    	// Get the party view el, view, and model
+	    	var partyEl = $(e.target).parents(".eml-party"),
+            model = partyEl.data("model"),
+            next = $(partyEl).next().not(".new");
+
+        if (next.length === 0) {
+          return;
+        }
+
+				// Remove current view, create and insert a new one for the model
+        $(partyEl).remove();
+
+        var newView = new EMLPartyView({
+          model: model,
+          edit: this.edit
+        });
+
+        $(next).after(newView.render().el);
+
+        // Move the party down within the model too
+	    	this.model.movePartyDown(model);
+				this.model.trickleUpChange();
+	    },
+
+	    /*
+         * Renders the Dates section of the page
+         */
+	    renderDates: function(){
+
+	    	//Add a header
+	    	this.$(".section.dates").html( $(document.createElement("h2")).text("Dates") );
+
+            _.each(this.model.get('temporalCoverage'), function(model){
+
+            	var tempCovView = new EMLTempCoverageView({
+            		model: model,
+            		isNew: false,
+            		edit: this.edit
+            	});
+
+            	tempCovView.render();
+
+    	    	this.$(".section.dates").append(tempCovView.el);
+
+            }, this);
+
+            if( !this.model.get('temporalCoverage').length ){
+            	var tempCovView = new EMLTempCoverageView({
+            		isNew: true,
+            		edit: this.edit,
+            		model: new EMLTemporalCoverage({ parentModel: this.model })
+            	});
+
+            	tempCovView.render();
+
+    	    	this.$(".section.dates").append(tempCovView.el);
+            }
+
+	    },
+
+	    /*
+         * Renders the Locations section of the page
+         */
+	    renderLocations: function(){
+	    	var locationsSection = this.$(".section.locations");
+
+	    	//Add the Locations header
+	    	locationsSection.html(this.locationsTemplate());
+	    	var locationsTable = locationsSection.find(".locations-table");
+
+	    	//Render an EMLGeoCoverage view for each EMLGeoCoverage model
+	    	_.each(this.model.get("geoCoverage"), function(geo, i){
+	    		//Create an EMLGeoCoverageView
+	    		var geoView = new EMLGeoCoverageView({
+	    			model: geo,
+	    			edit: this.edit
+	    			});
+
+	    		//Render the view
+	    		geoView.render();
+
+	    		geoView.$el.find(".remove-container").append(this.createRemoveButton(null, "geoCoverage", ".eml-geocoverage", ".locations-table"));
+
+	    		//Add the locations section to the page
+	    		locationsTable.append(geoView.el);
+
+	    		//Listen to validation events
+	    		this.listenTo(geo, "valid", this.updateLocationsError);
+
+	    		//Save it in our subviews array
+	    		this.subviews.push(geoView);
+	    	}, this);
+
+	    	//Now add one empty row to enter a new geo coverage
+	    	if(this.edit){
+	    		var newGeoModel = new EMLGeoCoverage({ parentModel: this.model, isNew: true}),
+	    			newGeoView = new EMLGeoCoverageView({
+		    			edit: true,
+		    			model: newGeoModel,
+		    			isNew: true
+	    			});
+	    		locationsTable.append(newGeoView.render().el);
+	    		newGeoView.$el.find(".remove-container").append(this.createRemoveButton(null, "geoCoverage", ".eml-geocoverage", ".locations-table"));
+
+	    		//Listen to validation events
+	    		this.listenTo(newGeoModel, "valid", this.updateLocationsError);
+	    	}
+	    },
+
+	    /*
+         * Renders the Taxa section of the page
+         */
+	    renderTaxa: function(){
+	    	this.$(".section.taxa").html($(document.createElement("h2")).text("Taxa"));
+
+			var taxonomy = this.model.get('taxonCoverage');
+
+			// Render a set of tables for each taxonomicCoverage
+			if (typeof taxonomy !== "undefined" && (Array.isArray(taxonomy) && taxonomy.length)) {
+				for (var i = 0; i < taxonomy.length; i++) {
+					this.$(".section.taxa").append(this.createTaxonomicCoverage(taxonomy[i]));
+				}
+			} else {
+				// Create a new one
+				var taxonCov = new EMLTaxonCoverage({
+					parentModel: this.model
+				});
+
+				this.model.set('taxonCoverage', [taxonCov], {silent: true});
+
+				this.$(".section.taxa").append(this.createTaxonomicCoverage(taxonCov));
+			}
+
+            // updating the indexes of taxa-tables before rendering the information on page(view).
+            var taxaNums = this.$(".editor-header-index");
+            for (var i = 0; i < taxaNums.length; i++) {
+                $(taxaNums[i]).text(i + 1);
+            }
+	    },
+
+	    /*
+       * Renders the Methods section of the page
+       */
+	    renderMethods: function(){
+	    	var methodsModel = this.model.get("methods");
+
+  			if (!methodsModel) {
+  				methodsModel = new EMLMethods({ edit: this.edit, parentModel: this.model });
+  			}
+
+			  this.$(".section.methods").html(new EMLMethodsView({
+				      model: methodsModel,
+				      edit: this.edit }).render().el);
+		  },
+
+	    /*
+         * Renders the Projcet section of the page
+         */
+	    renderProject: function(){
+
+	    },
+
+	    /*
+         * Renders the Sharing section of the page
+         */
+	    renderSharing: function(){
+
+	    },
+
+	    /*
+	     * Renders the funding field of the EML
+	     */
+	    renderFunding: function(){
+	    	//Funding
+		    var funding = this.model.get("project") ? this.model.get("project").get("funding") : [];
+
+		    //Clear the funding section
+		    $(".section.overview .funding").empty();
+
+		    //Create the funding input elements
+		    _.each(funding, function(fundingItem, i){
+
+		    	this.addFunding(fundingItem);
+
+		    }, this);
+
+		    //Add a blank funding input
+		    this.addFunding();
+	    },
+
+	    /*
+	     * Adds a single funding input row. Can either be called directly or used as an event callback
+	     */
+	    addFunding: function(argument){
+	    	if(this.edit){
+
+	    		if(typeof argument == "string")
+	    			var value = argument;
+	    		else if(!argument)
+	    			var value = "";
+	    		//Don't add another new funding input if there already is one
+	    		else if( !value && (typeof argument == "object") && !$(argument.target).is(".new") )
+	    			return;
+	    		else if((typeof argument == "object") && argument.target) {
+					var event = argument;
+
+					// Don't add a new funding row if the current one is empty
+					if ( $(event.target).val().trim() === "") return;
+				}
+
+		    	var fundingInput       = $(document.createElement("input"))
+		    								.attr("type", "text")
+		    								.attr("data-category", "funding")
+		    								.addClass("span12 funding hover-autocomplete-target")
+		    								.attr("placeholder", "Search for NSF awards by keyword or enter custom funding information")
+		    								.val(value),
+			    	hiddenFundingInput = fundingInput.clone().attr("type", "hidden").val(value).attr("id", "").addClass("hidden"),
+			    	loadingSpinner     = $(document.createElement("i")).addClass("icon icon-spinner input-icon icon-spin subtle hidden");
+
+		    	//Append all the elements to a container
+		    	var containerEl = $(document.createElement("div"))
+		    						.addClass("ui-autocomplete-container funding-row")
+		    						.append(fundingInput,
+									  loadingSpinner,
+									  hiddenFundingInput);
+
+				if (!value){
+					$(fundingInput).addClass("new");
+
+					if(event) {
+						$(event.target).parents("div.funding-row").append(this.createRemoveButton('project', 'funding', '.funding-row', 'div.funding-container'));
+						$(event.target).removeClass("new");
+					}
+				} else { // Add a remove button if this is a non-new funding element
+					$(containerEl).append(this.createRemoveButton('project', 'funding', '.funding-row', 'div.funding-container'));
+				}
+
+		    	var view = this;
+
+			    //Setup the autocomplete widget for the funding input
+			    fundingInput.autocomplete({
+					source: function(request, response){
+						var beforeRequest = function(){
+							loadingSpinner.show();
+						}
+
+						var afterRequest = function(){
+							loadingSpinner.hide();
+						}
+
+						return MetacatUI.appLookupModel.getGrantAutocomplete(request, response, beforeRequest, afterRequest)
+					},
+					select: function(e, ui) {
+						e.preventDefault();
+
+						var value = "NSF Award " + ui.item.value + " (" + ui.item.label + ")";
+						hiddenFundingInput.val(value);
+						fundingInput.val(value);
+
+						$(".funding .ui-helper-hidden-accessible").hide();
+						loadingSpinner.css("top", "5px");
+
+						view.updateFunding(e);
+
+					},
+					position: {
+						my: "left top",
+						at: "left bottom",
+						of: fundingInput,
+						collision: "fit"
+					},
+					appendTo: containerEl,
+					minLength: 3
+				});
+
+			    this.$(".funding-container").append(containerEl);
+	    	}
+	    },
+
+	    previewFundingRemove: function(e){
+	    	$(e.target).parents(".funding-row").toggleClass("remove-preview");
+	    },
+
+	    handleFundingTyping: function(e){
+	    	var fundingInput = $(e.target);
+
+	    	//If the funding value is at least one character
+	    	if(fundingInput.val().length > 0){
+	    		//Get rid of the error styling in this row
+	    		fundingInput.parent(".funding-row").children().removeClass("error");
+
+	    		//If this was the only funding input with an error, we can safely remove the error message
+	    		if( !this.$("input.funding.error").length )
+	    			this.$("[data-category='funding'] .notification").removeClass("error").text("");
+	    	}
+	    },
+
+	    addKeyword: function(keyword, thesaurus){
+	    	if(typeof keyword != "string" || !keyword){
+	    		var keyword = "";
+
+	    		//Only show one new keyword row at a time
+	    		if((this.$(".keyword.new").length == 1) && !this.$(".keyword.new").val())
+	    			return;
+	    		else if(this.$(".keyword.new").length > 1)
+	    			return;
+	    	}
+
+	    	//Create the keyword row HTML
+	    	var	row          = $(document.createElement("div")).addClass("row-fluid keyword-row"),
+	    		keywordInput = $(document.createElement("input")).attr("type", "text").addClass("keyword span10").attr("placeholder", "Add one new keyword"),
+    			thesInput    = $(document.createElement("select")).addClass("thesaurus span2"),
+          thesOptionExists = false,
+				  removeButton;
+
+        // Piece together the inputs
+        row.append(keywordInput, thesInput);
+
+        //Create the thesaurus options dropdown menu
+        _.each(MetacatUI.appModel.get("emlKeywordThesauri"), function(option){
+
+          var optionEl = $(document.createElement("option")).val(option.thesaurus).text(option.label);
+          thesInput.append( optionEl );
+
+          if( option.thesaurus == thesaurus ){
+            optionEl.prop("selected", true);
+            thesOptionExists = true;
+          }
+        });
+
+        //Add a "None" option, which is always in the dropdown
+        thesInput.prepend( $(document.createElement("option")).val("None").text("None") );
+
+        if( thesaurus == "None" || !thesaurus ){
+          thesInput.val("None");
+        }
+        //If this keyword is from a custom thesaurus that is NOT configured in this App, AND
+        // there is an option with the same label, then remove the option so it doesn't look like a duplicate.
+        else if( !thesOptionExists && _.findWhere(MetacatUI.appModel.get("emlKeywordThesauri"), { label: thesaurus }) ){
+          var duplicateOptions = thesInput.find("option:contains(" + thesaurus + ")");
+          duplicateOptions.each(function(i, option){
+            if( $(option).text() == thesaurus && !$(option).prop("selected") ){
+              $(option).remove();
+            }
+          });
+        }
+        //If this keyword is from a custom thesaurus that is NOT configured in this App, then show it as a custom option
+        else if( !thesOptionExists ){
+          thesInput.append( $(document.createElement("option")).val(thesaurus).text(thesaurus).prop("selected", true) );
+        }
+
+	    	if(!keyword)
+				row.addClass("new");
+	    	else{
+
+    			//Set the keyword value on the text input
+    			keywordInput.val(keyword);
+
+    			// Add a remove button unless this is the .new keyword
+				row.append(this.createRemoveButton(null, 'keywordSets', 'div.keyword-row', 'div.keywords'));
+	    	}
+
+	    	this.$(".keywords").append(row);
+	    },
+
+		addNewKeyword: function(e) {
+			if ($(e.target).val().trim() === "") return;
+
+			$(e.target).parents(".keyword-row").first().removeClass("new");
+
+			// Add in a remove button
+			$(e.target).parents(".keyword-row").append(this.createRemoveButton(null, 'keywordSets', 'div.keyword-row', 'div.keywords'));
+
+			var row          = $(document.createElement("div")).addClass("row-fluid keyword-row new").data({ model: new EMLKeywordSet() }),
+	    		keywordInput = $(document.createElement("input")).attr("type", "text").addClass("keyword span10"),
+    			thesInput    = $(document.createElement("select")).addClass("thesaurus span2");
+
+			row.append(keywordInput, thesInput);
+
+      //Create the thesaurus options dropdown menu
+      _.each(MetacatUI.appModel.get("emlKeywordThesauri"), function(option){
+        thesInput.append( $(document.createElement("option")).val(option.thesaurus).text(option.label) );
+      });
+
+      //Add a "None" option, which is always in the dropdown
+      thesInput.prepend( $(document.createElement("option")).val("None").text("None").prop("selected", true) );
+
+			this.$(".keywords").append(row);
+		},
+
+		previewKeywordRemove: function(e){
+			var row = $(e.target).parents(".keyword-row").toggleClass("remove-preview");
+		},
+
+	    /*
+	     * Update the funding info when the form is changed
+	     */
+	    updateFunding: function(e){
+	    	if(!e) return;
+
+	    	var row      = $(e.target).parent(".funding-row").first(),
+  	    		rowNum   = this.$(".funding-row").index(row),
+  	    		input    = $(row).find("input"),
+            isNew    = $(row).is(".new");
+
+        var newValue = isNew? $(e.target).siblings("input.hidden").val() : $(e.target).val();
+
+        newValue = this.model.cleanXMLText(newValue);
+
+        if( typeof newValue == "string" ){
+          newValue = newValue.trim();
+        }
+
+	    	//If there is no project model
+	    	if(!this.model.get("project")){
+	    		var model = new EMLProject({ parentModel: this.model });
+	    		this.model.set("project", model);
+	    	}
+	    	else
+	    		var model = this.model.get("project");
+
+	    	var currentFundingValues = model.get("funding");
+
+        //If the new value is an empty string, then remove that index in the array
+        if( typeof newValue == "string" && newValue.trim().length == 0 ){
+          currentFundingValues = currentFundingValues.splice(rowNum, 1);
+        }
+        else{
+	    	  currentFundingValues[rowNum] = newValue;
+        }
+
+	    	if(isNew && newValue != ''){
+	    		$(row).removeClass("new");
+
+  				// Add in a remove button
+  				$(e.target).parent().append(this.createRemoveButton('project', 'funding', '.funding-row', 'div.funding-container'));
+
+	    		this.addFunding();
+	    	}
+
+	    	this.model.trickleUpChange();
+
+	    },
+
+	    //TODO: Comma and semi-colon seperate keywords
+	    updateKeywords: function(e){
+
+	    	var keywordSets = this.model.get("keywordSets"),
+	    		newKeywordSets = [];
+
+	    	//Get all the keywords in the view
+	    	_.each(this.$(".keyword-row"), function(thisRow){
+	    		var thesaurus = this.model.cleanXMLText( $(thisRow).find("select").val() ),
+	    			keyword     = this.model.cleanXMLText( $(thisRow).find("input").val() );
+
+	    		if(!keyword) return;
+
+	    		var keywordSet = _.find(newKeywordSets, function(keywordSet){
+		    		return keywordSet.get("thesaurus") == thesaurus;
+		    	});
+
+	    		if(typeof keywordSet != "undefined"){
+		    		keywordSet.get("keywords").push(keyword);
+		    	}
+		    	else{
+		    		newKeywordSets.push(new EMLKeywordSet({
+			    			parentModel: this.model,
+			    			keywords: [keyword],
+			    			thesaurus: thesaurus
+		    			}));
+		    	}
+
+	    	}, this);
+
+	    	//Update the EML model
+	    	this.model.set("keywordSets", newKeywordSets);
+
+	    	if(e){
+		    	var row = $(e.target).parent(".keyword-row");
+
+		    	//Add a new row when the user has added a new keyword just now
+		    	if(row.is(".new")){
+		    		row.removeClass("new");
+					row.append(this.createRemoveButton(null, "keywordSets", "div.keyword-row", "div.keywords"));
+		    		this.addKeyword();
+		    	}
+	    	}
+	    },
+
+	    /*
+	     * Update the EML Geo Coverage models and views when the user interacts with the locations section
+	     */
+	    updateLocations: function(e){
+	    	if(!e) return;
+
+	    	e.preventDefault();
+
+	    	var viewEl = $(e.target).parents(".eml-geocoverage"),
+	    		geoCovModel = viewEl.data("model");
+
+	    	//If the EMLGeoCoverage is new
+	    	if(viewEl.is(".new")){
+
+	    		if(this.$(".eml-geocoverage.new").length > 1)
+	    			return;
+
+	    		//Render the new geo coverage view
+	    		var newGeo = new EMLGeoCoverageView({
+	    			edit: this.edit,
+	    			model: new EMLGeoCoverage({ parentModel: this.model, isNew: true}),
+	    			isNew: true
+	    		});
+	    		this.$(".locations-table").append(newGeo.render().el);
+	    		newGeo.$el.find(".remove-container").append(this.createRemoveButton(null, "geoCoverage", ".eml-geocoverage", ".locations-table"));
+
+	    		//Unmark the view as new
+	    		viewEl.data("view").notNew();
+
+	    		//Get the EMLGeoCoverage model attached to this EMlGeoCoverageView
+	    		var geoModel = viewEl.data("model"),
+	    		//Get the current EMLGeoCoverage models set on the parent EML model
+	    			currentCoverages = this.model.get("geoCoverage");
+
+	    		//Add this new geo coverage model to the parent EML model
+	    		if(Array.isArray(currentCoverages)){
+	    			if( !_.contains(currentCoverages, geoModel) ){
+	    				currentCoverages.push(geoModel);
+	    				this.model.trigger("change:geoCoverage");
+	    			}
+	    		}
+	    		else{
+	    			currentCoverages = [currentCoverages, geoModel];
+	    			this.model.set("geoCoverage", currentCoverages);
+	    		}
+	    	}
+	    },
+
+	    /*
+	     * If all the EMLGeoCoverage models are valid, remove the error messages for the Locations section
+	     */
+	    updateLocationsError: function(){
+	    	var allValid = _.every(this.model.get("geoCoverage"), function(geoCoverageModel){
+
+	    						return geoCoverageModel.isValid();
+
+	    					});
+
+	    	if(allValid){
+	    		this.$(".side-nav-item.error[data-category='geoCoverage']")
+	    			.removeClass("error")
+	    			.find(".icon.error").hide();
+	    		this.$(".section[data-section='locations'] .notification.error")
+	    			.removeClass("error")
+	    			.text("");
+	    	}
+
+	    },
+
+	    /*
+         * Creates the text elements
+         */
+	    createEMLText: function(textModel, edit, category){
+
+	    	if(!textModel && edit){
+	    		return $(document.createElement("textarea"))
+	    				.attr("data-category", category)
+	    				.addClass("xlarge text");
+	    	}
+	    	else if(!textModel && !edit){
+	    		return $(document.createElement("div"))
+						.attr("data-category", category);
+	    	}
+
+	    	//Get the EMLText from the EML model
+	    	var finishedEl;
+
+	    	//Get the text attribute from the EMLText model
+	    	var	paragraphs = textModel.get("text"),
+	    		paragraphsString = "";
+
+	    	//If the text should be editable,
+	    	if(edit){
+		    	//Format the paragraphs with carriage returns between paragraphs
+				paragraphsString = paragraphs.join(String.fromCharCode(13));
+
+		    	//Create the textarea element
+		    	finishedEl = $(document.createElement("textarea"))
+	    						 .addClass("xlarge text")
+	    						 .attr("data-category", category)
+	    						 .html(paragraphsString);
+	    	}
+	    	else{
+	    		//Format the paragraphs with HTML
+		    	_.each(paragraphs, function(p){
+		    		paragraphsString += "<p>" + p + "</p>";
+		    	});
+
+		    	//Create a div
+		    	finishedEl = $(document.createElement("div"))
+		    				.attr("data-category", category)
+		    				.append(paragraphsString);
+	    	}
+
+		    $(finishedEl).data({ model: textModel });
+
+	    	//Return the finished DOM element
+	    	return finishedEl;
+	    },
+
+	    /*
+	     * Updates a basic text field in the EML after the user changes the value
+	     */
+	    updateText: function(e){
+	    	if(!e) return false;
+
+	    	var category  = $(e.target).attr("data-category"),
+	    		currentValue = this.model.get(category),
+	    		textModel = $(e.target).data("model"),
+	    		value     = this.model.cleanXMLText($(e.target).val());
+
+	    	//We can't update anything without a category
+	    	if(!category) return false;
+
+	    	//Get the list of paragraphs - checking for carriage returns and line feeds
+	    	var paragraphsCR = value.split(String.fromCharCode(13));
+	    	var paragraphsLF = value.split(String.fromCharCode(10));
+
+	    	//Use the paragraph list that has the most
+	    	var paragraphs = (paragraphsCR > paragraphsLF)? paragraphsCR : paragraphsLF;
+
+	    	//If this category isn't set yet, then create a new EMLText model
+	    	if(!textModel){
+
+	    		//Get the current value for this category and create a new EMLText model
+	    		var newTextModel = new EMLText({ text: paragraphs, parentModel: this.model });
+
+	    		// Save the new model onto the underlying DOM node
+	    		$(e.target).data({ "model" : newTextModel });
+
+	    		//Set the new EMLText model on the EML model
+	    		if(Array.isArray(currentValue)){
+	    			currentValue.push(newTextModel);
+	    			this.model.trigger("change:" + category);
+	    			this.model.trigger("change");
+	    		}
+	    		else{
+	    			this.model.set(category, newTextModel);
+	    		}
+
+	    	}
+	    	//Update the existing EMLText model
+	    	else{
+
+	    		//If there are no paragraphs or all the paragraphs are empty...
+	    		if( !paragraphs.length || _.every(paragraphs, function(p){ return p.trim() == "" }) ){
+
+	    			//Remove this text model from the array of text models since it is empty
+    				var newValue = _.without(currentValue, textModel);
+    				this.model.set(category, newValue);
+
+	    		}
+	    		else{
+
+		    		textModel.set("text", paragraphs);
+		    		textModel.trigger("change:text");
+
+	    			//Is this text model set on the EML model?
+	    			if( Array.isArray(currentValue) && !_.contains(currentValue, textModel) ){
+
+	    				//Push this text model into the array of EMLText models
+	    				currentValue.push(textModel);
+	    				this.model.trigger("change:" + category);
+		    			this.model.trigger("change");
+
+	    			}
+
+	    		}
+
+	    	}
+
+	    },
+
+	    /*
+	     * Creates and returns an array of basic text input field for editing
+	     */
+	    createBasicTextFields: function(category, placeholder){
+
+	    	var textContainer = $(document.createElement("div")).addClass("text-container"),
+	    		modelValues = this.model.get(category),
+				textRow; // Holds the DOM for each field
+
+	    	//Format as an array
+	    	if(!Array.isArray(modelValues) && modelValues) modelValues = [modelValues];
+
+	    	//For each value in this category, create an HTML element with the value inserted
+	    	_.each(modelValues, function(value, i, allModelValues){
+		    	if(this.edit){
+		    		var textRow = $(document.createElement("div")).addClass("basic-text-row"),
+					    input   = $(document.createElement("input"))
+				    			.attr("type", "text")
+				    			.attr("data-category", category)
+				    			.addClass("basic-text");
+					textRow.append(input.clone().val(value));
+
+					if(category != "title")
+						textRow.append(this.createRemoveButton(null, category, 'div.basic-text-row', 'div.text-container'));
+
+					textContainer.append(textRow);
+
+		    		//At the end, append an empty input for the user to add a new one
+		    		if(i+1 == allModelValues.length && category != "title") {
+						var newRow = $($(document.createElement("div")).addClass("basic-text-row"));
+						newRow.append(input.clone().addClass("new").attr("placeholder", placeholder || "Add a new " + category));
+						textContainer.append(newRow);
+					}
+
+		    	}
+		    	else{
+		    		textContainer.append($(document.createElement("div"))
+		    				.addClass("basic-text-row")
+			    			.attr("data-category", category)
+			    			.text(value));
+		    	}
+	    	}, this);
+
+	    	if((!modelValues || !modelValues.length) && this.edit){
+	    		var input = $(document.createElement("input"))
+			    			.attr("type", "text")
+			    			.attr("data-category", category)
+			    			.addClass("basic-text new")
+			    			.attr("placeholder", placeholder || "Add a new " + category);
+
+	    		textContainer.append($(document.createElement("div")).addClass("basic-text-row").append(input));
+	    	}
+
+	    	return textContainer;
+	    },
+
+	    updateBasicText: function(e){
+	    	if(!e) return false;
+
+	    	//Get the category, new value, and model
+	    	var category = $(e.target).attr("data-category"),
+	    		value    = this.model.cleanXMLText($(e.target).val()),
+	    		model    = $(e.target).data("model") || this.model;
+
+	    	//We can't update anything without a category
+	    	if(!category) return false;
+
+	    	//Get the current value
+	    	var currentValue = model.get(category);
+
+	    	//Insert the new value into the array
+	    	if( Array.isArray(currentValue) ){
+
+	    		//Find the position this text input is in
+	    		var position = $(e.target).parents("div.text-container").first().children("div").index($(e.target).parent());
+
+	    		//Set the value in that position in the array
+	    		currentValue[position] = value;
+
+	    		//Set the changed array on this model
+	    		model.set(category, currentValue);
+	    		model.trigger("change:" + category);
+
+	    	}
+	    	//Update the model if the current value is a string
+	    	else if(typeof currentValue == "string"){
+	    		model.set(category, [value]);
+	    		model.trigger("change:" + category);
+	    	}
+	    	else if(!currentValue) {
+				model.set(category, [value]);
+				model.trigger("change:" + category);
+			}
+
+    		//Add another blank text input
+	    	if($(e.target).is(".new") && value != '' && category != "title"){
+				$(e.target).removeClass("new");
+				this.addBasicText(e);
+	    	}
+
+			// Trigger a change on the entire package
+			MetacatUI.rootDataPackage.packageModel.set("changed", true);
+
+		},
+
+		/* One-off handler for updating pubDate on the model when the form
+		input changes. Fairly similar but just a pared down version of
+		updateBasicText. */
+		updatePubDate: function(e){
+	    	if(!e) return false;
+
+			this.model.set('pubDate', $(e.target).val().trim());
+			this.model.trigger("change");
+
+			// Trigger a change on the entire package
+			MetacatUI.rootDataPackage.packageModel.set("changed", true);
+	    },
+
+	    /*
+	     * Adds a basic text input
+	     */
+	    addBasicText: function(e){
+	    	var category = $(e.target).attr("data-category"),
+	    		allBasicTexts = $(".basic-text.new[data-category='" + category + "']");
+
+	    	//Only show one new row at a time
+    		if((allBasicTexts.length == 1) && !allBasicTexts.val())
+    			return;
+    		else if(allBasicTexts.length > 1)
+    			return;
+    		//We are only supporting one title right now
+    		else if(category == "title")
+    			return;
+
+	    	//Add another blank text input
+			var newRow = $(document.createElement("div")).addClass("basic-text-row");
+
+			newRow.append($(document.createElement("input"))
+				.attr("type", "text")
+				.attr("data-category", category)
+				.attr("placeholder", $(e.target).attr("placeholder"))
+				.addClass("new basic-text"));
+
+	    	$(e.target).parent().after(newRow);
+
+			$(e.target).after(this.createRemoveButton(null, category, '.basic-text-row', "div.text-container"));
+	    },
+
+	    previewTextRemove: function(e){
+	    	$(e.target).parents(".basic-text-row").toggleClass("remove-preview");
+	    },
+
+		// publication date validation.
+		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);
+			}
+		},
+
+		/* Event handler for showing validation messaging for the pubDate input
+		which has to conform to the EML yearDate type (YYYY or YYYY-MM-DD) */
+		showPubDateValidation: function(e) {
+			var container = $(e.target).parents(".pubDate").first(),
+				input = $(e.target),
+				messageEl = $(container).find('.notification'),
+				value = input.val(),
+				errors = [];
+
+			// Remove existing error borders and notifications
+			input.removeClass("error");
+			messageEl.text("");
+			messageEl.removeClass("error");
+
+			if (value != "" && value.length > 0) {
+				if (!this.isDateFormatValid(value)) {
+					errors.push("The value entered for publication date, '" +
+						value +
+						"' is not a valid value for this field. Enter either a year (e.g. 2017) or a date in the format YYYY-MM-DD.");
+
+						input.addClass("error")
+				}
+			}
+
+			if (errors.length > 0) {
+				messageEl.text(errors[0]).addClass("error");
+			}
+		},
+
+		// Creates a table to hold a single EMLTaxonCoverage element (table) for
+		// each root-level taxonomicClassification
+		createTaxonomicCoverage: function(coverage) {
+            var finishedEls = $(this.taxonomicCoverageTemplate({
+            		generalTaxonomicCoverage: coverage.get('generalTaxonomicCoverage') || ""
+            	})),
+            	coverageEl = finishedEls.filter(".taxonomic-coverage");
+
+            coverageEl.data({ model: coverage });
+
+			var classifications = coverage.get("taxonomicClassification");
+
+			// Makes a table... for the root level
+			for (var i = 0; i < classifications.length; i++) {
+				coverageEl.append(this.createTaxonomicClassifcationTable(classifications[i]));
+			}
+
+			// Create a new, blank table for another taxonomicClassification
+			var newTableEl = this.createTaxonomicClassifcationTable();
+
+			coverageEl.append(newTableEl);
+
+			return finishedEls;
+		},
+
+		createTaxonomicClassifcationTable: function(classification) {
+
+            // updating the taxonomic table indexes before adding a new table to the page.
+            var taxaNums = this.$(".editor-header-index");
+            for (var i = 0; i < taxaNums.length; i++) {
+                $(taxaNums[i]).text(i + 1);
+            }
+
+            // Adding the taxoSpeciesCounter to the table header for enhancement of the view
+            var finishedEl = $('<div class="row-striped root-taxonomic-classification-container"></div>');
+            $(finishedEl).append('<h6>Species <span class="editor-header-index">' + (taxaNums.length + 1) + '</span> </h6>');
+
+
+			// Add a remove button if this is not a new table
+			if (!(typeof classification === "undefined")) {
+				$(finishedEl).append(this.createRemoveButton('taxonCoverage', 'taxonomicClassification', '.root-taxonomic-classification-container', '.taxonomic-coverage'));
+			}
+
+
+			var tableEl = $(this.taxonomicClassificationTableTemplate());
+			var tableBodyEl = $(document.createElement("tbody"));
+
+			var queue = [classification],
+			 	rows = [],
+			 	cur;
+
+			while (queue.length > 0) {
+				cur = queue.pop();
+
+				// I threw this in here so I can this function without an
+				// argument to generate a new table from scratch
+				if (typeof cur === "undefined") {
+					continue;
+				}
+
+				rows.push({
+					'taxonRankName' : cur.taxonRankName.toLowerCase(),
+					'taxonRankValue' : cur.taxonRankValue
+				});
+
+				if (cur.taxonomicClassification) {
+					for (var i = 0; i < cur.taxonomicClassification.length; i++) {
+						queue.push(cur.taxonomicClassification[i]);
+					}
+				}
+			}
+
+			for (var j = 0; j < rows.length; j++) {
+				tableBodyEl.append(this.taxonomicClassificationRowTemplate(rows[j]));
+			}
+
+			var newRowEl = $(this.taxonomicClassificationRowTemplate({
+				taxonRankName: '',
+				taxonRankValue: ''
+			}));
+
+			$(newRowEl).addClass("new");
+			$(tableBodyEl).append(newRowEl);
+			$(tableEl).append(tableBodyEl);
+
+			// Add the new class to the entire table if it's a new one
+			if (typeof classification === "undefined") {
+				$(tableEl).addClass("new");
+			}
+
+			$(finishedEl).append(tableEl);
+
+			return finishedEl;
+		},
+
+		/* Update the underlying model and DOM for an EML TaxonomicCoverage
+		section. This method handles updating the underlying TaxonomicCoverage
+		models when the user changes form fields as well as inserting new
+		form fields automatically when the user needs them.
+
+		Since a dataset has multiple TaxonomicCoverage elements at the dataset
+		level, each Taxonomic Coverage is represented by a table element and
+		all taxonomicClassifications within are rows in that table.
+
+		TODO: Finish this function
+		TODO: Link this function into the DOM
+		*/
+		updateTaxonCoverage: function(options) {
+
+			if(options.target){
+				var e = options;
+
+				/*	Getting `model` here is different than in other places because
+					the thing being updated is an `input` or `select` element which
+					is part of a `taxonomicClassification`. The model is
+					`TaxonCoverage` which has one or more
+					`taxonomicClassifications`. So we have to walk up to the
+					hierarchy from input < td < tr < tbody < table < div to get at
+					the underlying TaxonCoverage model.
+				*/
+		    	var coverage = $(e.target).parents(".taxonomic-coverage"),
+					classificationEl = $(e.target).parents(".root-taxonomic-classification"),
+		    		model =  $(coverage).data("model") || this.model,
+					category = $(e.target).attr("data-category"),
+					value = this.model.cleanXMLText($(e.target).val());
+
+		    	//We can't update anything without a coverage, or
+		    	//classification
+				if (!coverage) return false;
+				if (!classificationEl) return false;
+
+				// Use `category` to determine if we're updating the generalTaxonomicCoverage or
+				// the taxonomicClassification
+				if (category && category === "generalTaxonomicCoverage") {
+					model.set('generalTaxonomicCoverage', value);
+
+					return;
+				}
+			}
+			else{
+				var coverage = options.coverage,
+					model    = $(coverage).data("model");
+			}
+
+			// Find all of the root-level taxonomicClassifications
+			var classificationTables = $(coverage).find(".root-taxonomic-classification");
+
+			if (!classificationTables) return false;
+
+			//TODO :This should probably (at least) be in its own View and
+			//definitely refactored into tidy functions.*/
+
+			var rows,
+				collectedClassifications = [];
+
+			for (var i = 0; i < classificationTables.length; i++) {
+
+				rows = $(classificationTables[i]).find("tbody tr");
+
+				if (!rows) continue;
+
+				var topLevelClassification = {},
+					classification = topLevelClassification,
+					currentRank,
+					currentValue;
+
+				for (var j = 0; j < rows.length; j++) {
+
+					currentRank = this.model.cleanXMLText($(rows[j]).find("select").val()) || "";
+					currentValue = this.model.cleanXMLText($(rows[j]).find("input").val()) || "";
+
+					// Skip over rows with empty Rank or Value
+					if (!currentRank.length || !currentValue.length) {
+						continue;
+					}
+
+					//After the first row, start nesting taxonomicClassification objectss
+					if(j > 0){
+						classification.taxonomicClassification = {};
+						classification = classification.taxonomicClassification;
+					}
+
+					// Add it to the classification object
+					classification.taxonRankName = currentRank;
+					classification.taxonRankValue = currentValue;
+
+				}
+
+
+				//Add the top level classification to the array
+				if(Object.keys(topLevelClassification).length)
+					collectedClassifications.push(topLevelClassification);
+			}
+
+			if (!(_.isEqual(collectedClassifications, model.get('taxonomicClassification')))) {
+				model.set('taxonomicClassification', collectedClassifications);
+				this.model.trigger("change");
+			}
+
+			// Handle adding new tables and rows
+			// Do nothing if the value isn't set
+			if (value) {
+				// Add a new row if this is itself a new row
+				if ($(e.target).parents("tr").first().is(".new")) {
+					var newRowEl = $(this.taxonomicClassificationRowTemplate({
+						taxonRankName: '',
+						taxonRankValue: ''
+					})).addClass("new");
+
+					$(e.target).parents("tbody").first().append(newRowEl);
+					$(e.target).parents("tr").first().removeClass("new");
+				}
+
+				// Add a new classification table if this is itself a new table
+				if ($(classificationEl).is(".new")) {
+					$(classificationEl).removeClass("new");
+					$(classificationEl).append(this.createRemoveButton('taxonCoverage', 'taxonomicClassification', '.root-taxonomic-classification-container', '.taxonomic-coverage'));
+					$(coverage).append(this.createTaxonomicClassifcationTable());
+				}
+			}
+		},
+
+		/*
+		 * Adds a new row and/or table to the taxonomic coverage section
+		 */
+		addNewTaxon: function(e){
+			// Don't do anything if the current classification doesn't have new content
+			if ($(e.target).val().trim() === "") return;
+
+			// If the row is new, add a new row to the table
+			if ($(e.target).parents("tr").is(".new")) {
+				var newRow = $(this.taxonomicClassificationRowTemplate({
+								taxonRankName: '',
+								taxonRankValue: ''
+							 }))
+							 .addClass("new");
+
+				//Append the new row and remove the new class from the old row
+				$(e.target).parents("tr").removeClass("new").after(newRow);
+			}
+		},
+
+		removeTaxonRank: function(e){
+			var row = $(e.target).parents(".taxonomic-coverage-row"),
+				coverageEl = $(row).parents(".taxonomic-coverage"),
+				view = this;
+
+			//Animate the row away and then remove it
+			row.slideUp("fast", function(){
+				row.remove();
+				view.updateTaxonCoverage({ coverage: coverageEl });
+			});
+		},
+
+		/*
+		 * After the user focuses out, show validation help, if needed
+		 */
+		showTaxonValidation: function(e){
+
+			//Get the text inputs and select menus
+			var row = $(e.target).parents("tr"),
+				allInputs = row.find("input, select"),
+				tableContainer = $(e.target).parents("table"),
+				errorInputs = [];
+
+			//If none of the inputs have a value and this is a new row, then do nothing
+			if(_.every(allInputs, function(i){ return !i.value }) && row.is(".new"))
+				return;
+
+			//Add the error styling to any input with no value
+			_.each(allInputs, function(input){
+				// Keep track of the number of clicks of each input element so we only show the
+				// error message after the user has focused on both input elements
+				if(!input.value)
+					errorInputs.push(input);
+			});
+
+			if(errorInputs.length){
+
+				//Show the error message after a brief delay
+				setTimeout(function(){
+					//If the user focused on another element in the same row, don't do anything
+					if(_.contains(allInputs, document.activeElement))
+						return;
+
+					//Add the error styling
+					$(errorInputs).addClass("error");
+
+					//Add the error message
+					if( !tableContainer.prev(".notification").length ){
+						tableContainer.before($(document.createElement("p"))
+														.addClass("error notification")
+														.text("Enter a rank name AND value in each row."));
+					}
+
+				}, 200);
+			}
+			else{
+				allInputs.removeClass("error");
+
+				if(!tableContainer.find(".error").length)
+					tableContainer.prev(".notification").remove();
+			}
+
+		},
+
+		previewTaxonRemove: function(e){
+			var removeBtn = $(e.target);
+
+			if(removeBtn.parent().is(".root-taxonomic-classification")){
+				removeBtn.parent().toggleClass("remove-preview");
+			}
+			else{
+				removeBtn.parents(".taxonomic-coverage-row").toggleClass("remove-preview");
+			}
+
+		},
+
+        updateRadioButtons: function(e){
+        	//Get the element of this radio button set that is checked
+        	var choice = this.$("[name='" + $(e.target).attr("name") + "']:checked").val();
+
+        	if(typeof choice == "undefined" || !choice)
+        		this.model.set($(e.target).attr("data-category"), "");
+        	else
+        		this.model.set($(e.target).attr("data-category"), choice);
+
+        	this.model.trickleUpChange();
+        },
+
+        /*
+         * Switch to the given section
+         */
+        switchSection: function(e){
+        	if(!e) return;
+
+        	e.preventDefault();
+
+        	var clickedEl = $(e.target),
+        		section = clickedEl.attr("data-section") ||
+        				  clickedEl.children("[data-section]").attr("data-section") ||
+        				  clickedEl.parents("[data-section]").attr("data-section");
+
+        	if(this.visibleSection == "all")
+        		this.scrollToSection(section);
+        	else{
+        		this.$(".section." + this.activeSection).hide()
+        		this.$(".section." + section).show();
+
+        		this.highlightTOC(section);
+
+        		this.activeSection = section;
+        		this.visibleSection = section;
+
+        		$("body").scrollTop(this.$(".section." + section).offset().top - $("#Navbar").height());
+        	}
+
+
+        },
+
+        /*
+         * When a user clicks on the section names in the side tabs, jump to the section
+         */
+        scrollToSection: function(e){
+        	if(!e) return false;
+
+        	//Stop navigation
+        	e.preventDefault();
+
+        	var section = $(e.target).attr("data-section"),
+        		sectionEl = this.$(".section." + section);
+
+        	if(!sectionEl) return false;
+
+        	//Temporarily unbind the scroll listener while we scroll to the clicked section
+        	$(document).unbind("scroll");
+
+        	var view = this;
+        	setTimeout(function(){
+        		$(document).scroll(view.highlightTOC.call(view));
+        	}, 1500);
+
+        	//Scroll to the section
+        	if(sectionEl == section[0])
+        		MetacatUI.appView.scrollToTop();
+        	else
+        		MetacatUI.appView.scrollTo(sectionEl, $("#Navbar").outerHeight());
+
+        	//Remove the active class from all the menu items
+        	$(".side-nav-item a.active").removeClass("active");
+        	//Set the clicked item to active
+        	$(".side-nav-item a[data-section='" + section + "']").addClass("active");
+
+        	//Set the active section on this view
+        	this.activeSection = section;
+        },
+
+        /*
+         * Highlight the given menu item.
+         * The first argument is either an event object or the section name
+         */
+        highlightTOC: function(section){
+
+        	this.resizeTOC();
+
+        	//Now change sections
+        	if(typeof section == "string"){
+            	//Remove the active class from all the menu items
+            	$(".side-nav-item a.active").removeClass("active");
+
+            	$(".side-nav-item a[data-section='" + section + "']").addClass("active");
+            	this.activeSection = section;
+            	this.visibleSection = section;
+            	return;
+        	}
+        	else if(this.visibleSection == "all"){
+            	//Remove the active class from all the menu items
+            	$(".side-nav-item a.active").removeClass("active");
+
+        		//Get the section
+        		var top = $(window).scrollTop() + $("#Navbar").outerHeight() + 70,
+        			sections = $(".metadata-container .section");
+
+        		//If we're somewhere in the middle, find the right section
+    			for(var i=0; i < sections.length; i++){
+    				if( top > $(sections[i]).offset().top && top < $(sections[i+1]).offset().top ){
+    					$($(".side-nav-item a")[i]).addClass("active");
+    					this.activeSection = $(sections[i]).attr("data-section");
+    					this.visibleSection = $(sections[i]).attr("data-section");
+    					break;
+    				}
+    			}
+
+
+        	}
+        },
+
+    	/*
+    	 * Resizes the vertical table of contents so it's always the same height as the editor body
+    	 */
+        resizeTOC: function(){
+        	var tableBottomHandle = $("#editor-body .ui-resizable-handle");
+
+          if( !tableBottomHandle.length )
+            return;
+
+          var tableBottom = tableBottomHandle[0].getBoundingClientRect().bottom,
+        		  navTop = tableBottom;
+
+        	if(tableBottom < $("#Navbar").outerHeight()){
+        		if( $("#Navbar").css("position") == "fixed" )
+        			navTop = $("#Navbar").outerHeight();
+        		else
+        			navTop = 0;
+        	}
+
+
+        	$(".metadata-toc").css("top", navTop);
+        },
+
+        /*
+         *  -- This function is for development/testing purposes only --
+         *  Trigger a change on all the form elements
+		 *	so that when values are changed by Javascript, we make sure the change event
+		 *  is fired. This is good for capturing changes by Javascript, or
+		 *  browser plugins that fill-in forms, etc.
+         */
+        triggerChanges: function(){
+			$("#metadata-container input").change();
+			$("#metadata-container textarea").change();
+			$("#metadata-container select").change();
+        },
+
+		/* Creates "Remove" buttons for removing non-required sections
+		of the EML from the DOM */
+		createRemoveButton: function(submodel, attribute, selector, container) {
+			return $(document.createElement("span"))
+				.addClass("icon icon-remove remove pointer")
+				.attr("title", "Remove")
+				.data({
+					'submodel' : submodel,
+					'attribute': attribute,
+					'selector': selector,
+					'container': container
+				})
+		},
+
+		/* Generic event handler for removing sections of the EML (both
+		the DOM and inside the EML211Model) */
+		handleRemove: function(e) {
+  			var submodel = $(e.target).data('submodel'), // Optional sub-model to remove attribute from
+  			    attribute = $(e.target).data('attribute'), // Attribute on the EML211 model we're removing from
+  			    selector = $(e.target).data('selector'), // Selector to find the parent DOM elemente we'll remove
+  				  container = $(e.target).data('container'), // Selector to find the parent container so we can remove by index
+  				  parentEl, // Element we'll remove
+  				  model; // Specific sub-model we're removing
+
+  			if (!attribute) return;
+  			if (!container) return;
+
+  			// Find the element we'll remove from the DOM
+  			if (selector) {
+  				parentEl = $(e.target).parents(selector).first();
+  			} else {
+  				parentEl = $(e.target).parents().first();
+  			}
+
+  			if (parentEl.length == 0) return;
+
+  			// Handle remove on a EML model / sub-model
+  			if (submodel) {
+
+  				  model = this.model.get(submodel);
+
+  				  if (!model) return;
+
+    				// Get the current value of the attribute so we can remove from it
+    				var currentValue,
+    					submodelIndex;
+
+    				if (Array.isArray(this.model.get(submodel))) {
+    					// Stop now if there's nothing to remove in the first place
+    					if (this.model.get(submodel).length == 0) return;
+
+    					// For multi-valued submodels, find *which* submodel we are removing or
+    					// removingn from
+    					submodelIndex = $(container).index($(e.target).parents(container).first());
+    					if (submodelIndex === -1) return;
+
+    					currentValue = this.model.get(submodel)[submodelIndex].get(attribute);
+    				} else {
+    					currentValue = this.model.get(submodel).get(attribute);
+    				}
+
+    				//FInd the position of this field in the list of fields
+    				var position = $(e.target).parents(container)
+    								.first()
+    								.children(selector)
+    								.index($(e.target).parents(selector));
+
+    				// Remove from the EML Model
+    				if (position >= 0) {
+    					if (Array.isArray(this.model.get(submodel))) {
+    						currentValue.splice(position, 1); // Splice returns the removed members
+    						this.model.get(submodel)[submodelIndex].set(attribute, currentValue);
+    					} else {
+    						currentValue.splice(position, 1); // Splice returns the removed members
+    						this.model.get(submodel).set(attribute, currentValue);
+    					}
+
+    				}
+
+    			} else if (selector) {
+    				// Find the index this attribute is in the DOM
+    				var position = $(e.target).parents(container).first()
+                          .children(selector)
+                          .index($(e.target).parents(selector));
+
+    				//Remove this index of the array
+    				var currentValue = this.model.get(attribute);
+
+    				if( Array.isArray(currentValue) )
+    					currentValue.splice(position, 1);
+
+    				//Set the array on the model so the 'set' function is executed
+    				this.model.set(attribute, currentValue);
+
+      			}
+            // Handle remove on a basic text field
+            else {
+      				// The DOM order matches the EML model attribute order so we can remove
+      				// by that
+      				var position = $(e.target).parents(container).first().children(selector).index(selector);
+      				var currentValue = this.model.get(attribute);
+
+      				// Remove from the EML Model
+      				if (position >= 0) {
+      					currentValue.splice(position, 1);
+      					this.model.set(attribute, currentValue);
+      				}
+      			}
+
+      			// Trigger a change on the entire package
+      			MetacatUI.rootDataPackage.packageModel.set("changed", true);
+
+      			// Remove the DOM
+      			$(parentEl).remove();
+
+            //updating the tablesIndex once the element has been removed
+            var tableNums = this.$(".editor-header-index");
+            for (var i = 0; i < tableNums.length; i++) {
+                $(tableNums[i]).text(i + 1);
+            }
+		    },
+
+        /* Close the view and its sub views */
+        onClose: function() {
+            this.remove(); // remove for the DOM, stop listening
+            this.off();    // remove callbacks, prevent zombies
+            this.model.off();
+
+            //Remove the scroll event listeners
+            $(document).unbind("scroll");
+
+            this.model = null;
+
+            this.subviews = [];
+			      window.onbeforeunload = null;
+
+        }
+    });
+    return EMLView;
+});
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time) +
+ + + + + diff --git a/docs/docs/views_metadata_EMLEntityView.js.html b/docs/docs/views_metadata_EMLEntityView.js.html new file mode 100644 index 000000000..a96191790 --- /dev/null +++ b/docs/docs/views_metadata_EMLEntityView.js.html @@ -0,0 +1,622 @@ + + + + + MetacatUI Dev Docs: Source: views/metadata/EMLEntityView.js + + + + + + + + + + + +
+ +

Source: views/metadata/EMLEntityView.js

+ + + + + + +
+
+
/* global define */
+define(['underscore', 'jquery', 'backbone', 'localforage',
+        'models/DataONEObject', 'models/metadata/eml211/EMLAttribute', 'models/metadata/eml211/EMLEntity',
+        'views/DataPreviewView',
+        'views/metadata/EMLAttributeView',
+        'text!templates/metadata/eml-entity.html',
+        'text!templates/metadata/eml-attribute-menu-item.html'],
+    function(_, $, Backbone, LocalForage, DataONEObject, EMLAttribute, EMLEntity,
+    		DataPreviewView,
+    		EMLAttributeView,
+    		EMLEntityTemplate,
+    		EMLAttributeMenuItemTemplate){
+
+        /*
+            An EMLEntityView shows the basic attributes of a DataONEObject, as described by EML
+        */
+        var EMLEntityView = Backbone.View.extend({
+
+            tagName: "div",
+
+            className: "eml-entity modal hide fade",
+
+            id: null,
+
+            /* The HTML template for an entity */
+            template: _.template(EMLEntityTemplate),
+            attributeMenuItemTemplate: _.template(EMLAttributeMenuItemTemplate),
+
+            /* Events this view listens to */
+            events: {
+            	"change" : "saveDraft",
+            	"change input" : "updateModel",
+            	"change textarea" : "updateModel",
+            	"click .nav-tabs a" : "showTab",
+            	"click .attribute-menu-item" : "showAttribute",
+            	"mouseover .attribute-menu-item .remove" : "previewAttrRemove",
+            	"mouseout .attribute-menu-item .remove"  : "previewAttrRemove",
+            	"click .attribute-menu-item .remove" : "removeAttribute"
+            },
+
+            initialize: function(options){
+            	if(!options)
+            		var options = {};
+
+            	this.model = options.model || new EMLEntity();
+            	this.DataONEObject = options.DataONEObject;
+            },
+
+            render: function(){
+
+            	this.renderEntityTemplate();
+
+            	this.renderPreview();
+
+            	this.renderAttributes();
+
+              this.listenTo(this.model, "invalid", this.showValidation);
+              this.listenTo(this.model, "valid", this.showValidation);
+
+            },
+
+            renderEntityTemplate: function(){
+            	var modelAttr = this.model.toJSON();
+
+            	if(!modelAttr.entityName)
+            		modelAttr.title = "this data";
+            	else
+            		modelAttr.title = modelAttr.entityName;
+
+            	modelAttr.uniqueId = this.model.cid;
+
+            	this.$el.html(this.template( modelAttr ));
+
+            	//Initialize the modal window
+            	this.$el.modal();
+
+
+       		  	//Set the menu height
+            	var view = this;
+       		  	this.$el.on("shown", function(){
+       		  		view.adjustHeight();
+       		  		view.setMenuWidth();
+
+       		  		window.addEventListener('resize', function(event){
+	       		  		view.adjustHeight();
+	       		  		view.setMenuWidth();
+	       		  	});
+       		  	});
+
+              this.$el.on("hidden", function(){
+                view.showValidation();
+              });
+
+            },
+
+            renderPreview: function(){
+            	//Get the DataONEObject model
+            	if(this.DataONEObject){
+            		var dataPreview = new DataPreviewView({
+            			model: this.DataONEObject
+            		});
+            		dataPreview.render();
+            		this.$(".preview-container").html(dataPreview.el);
+
+            		if(dataPreview.$el.children().length){
+            			this.$(".description").css("width", "calc(100% - 310px)");
+            		}
+            		else
+            			dataPreview.$el.remove();
+            	}
+            },
+
+            renderAttributes: function(){
+            	//Render the attributes
+            	var attributes      = this.model.get("attributeList"),
+            		attributeListEl = this.$(".attribute-list"),
+            		attributeMenuEl = this.$(".attribute-menu");
+
+            	_.each(attributes, function(attr){
+
+            		//Create an EMLAttributeView
+            		var view = new EMLAttributeView({
+            			model: attr
+            		});
+
+            		//Create a link in the attribute menu
+            		var menuItem = $(this.attributeMenuItemTemplate({
+	            			attrId: attr.cid,
+	            			attributeName: attr.get("attributeName"),
+	            			classes: ""
+	            		})).data({
+							model: attr,
+							attributeView: view
+							});
+            		attributeMenuEl.append(menuItem);
+            		menuItem.find(".tooltip-this").tooltip();
+
+            		this.listenTo(attr, "change:attributeName", function(attr){
+            			menuItem.find(".name").text(attr.get("attributeName"));
+            		});
+
+            		view.render();
+
+            		attributeListEl.append(view.el);
+
+            		view.$el.hide();
+
+            		this.listenTo(attr, "change",  this.addAttribute);
+            		this.listenTo(attr, "invalid", this.showAttributeValidation);
+            		this.listenTo(attr, "valid",   this.hideAttributeValidation);
+
+            	}, this);
+
+            	//Add a new blank attribute view at the end
+            	this.addNewAttribute();
+
+            	//If there are no attributes in this EML model yet,
+            	//then make sure we show a new add attribute button when the user starts typing
+            	if(attributes.length == 0){
+            		var onlyAttrView = this.$(".attribute-menu-item").first().data("attributeView"),
+            			view = this,
+            			keyUpCallback = function(){
+            				//This attribute is no longer new
+            				view.$(".attribute-menu-item").first().removeClass("new");
+
+            				//Add a new attribute link and view
+            				view.addNewAttribute();
+
+            				//Don't listen to keyup anymore
+            				onlyAttrView.$el.off("keyup", keyUpCallback);
+            			};
+
+            		onlyAttrView.$el.on("keyup", keyUpCallback);
+            	}
+
+        		//Activate the first navigation item
+        		var firstAttr = this.$(".side-nav-item").first();
+        		firstAttr.addClass("active");
+
+        		//Show the first attribute view
+        		firstAttr.data("attributeView").$el.show();
+
+        		firstAttr.data("attributeView").postRender();
+
+            },
+
+            updateModel: function(e){
+            	var changedAttr = $(e.target).attr("data-category");
+
+            	if(!changedAttr) return;
+
+              var emlModel = this.model.getParentEML(),
+                  newValue = emlModel? emlModel.cleanXMLText($(e.target).val()) : $(e.target).val();
+
+            	this.model.set(changedAttr, newValue);
+
+              this.model.trickleUpChange();
+
+            },
+
+            addNewAttribute: function(){
+            	var newAttrModel = new EMLAttribute({
+            			parentModel: this.model,
+                        xmlID: DataONEObject.generateId()
+            		}),
+            		newAttrView  = new EMLAttributeView({
+            			isNew: true,
+            			model: newAttrModel
+            		});
+
+            	newAttrView.render();
+            	this.$(".attribute-list").append(newAttrView.el);
+            	newAttrView.$el.hide();
+
+            	//Change the last menu item if it still says "Add attribute"
+            	if(this.$(".attribute-menu-item").length == 1){
+            		var firstAttrMenuItem = this.$(".attribute-menu-item").first();
+
+            		if( firstAttrMenuItem.find(".name").text() == "Add attribute" ){
+            			firstAttrMenuItem.find(".name").text("New attribute");
+            			firstAttrMenuItem.find(".add").hide();
+            		}
+            	}
+
+            	//Create the new menu item
+            	var menuItem = $(this.attributeMenuItemTemplate({
+	            		attrId: newAttrModel.cid,
+	            		attributeName: "Add attribute",
+	            		classes: "new"
+	            	})).data({
+						model: newAttrModel,
+						attributeView: newAttrView
+					});
+            	menuItem.find(".add").show();
+            	this.$(".attribute-menu").append(menuItem);
+            	menuItem.find(".tooltip-this").tooltip();
+
+            	//When the attribute name is changed, update the navigation
+        		this.listenTo(newAttrModel, "change:attributeName", function(attr){
+        			menuItem.find(".name").text(attr.get("attributeName"));
+        			menuItem.find(".add").hide();
+        		});
+
+            	this.listenTo(newAttrModel, "change",  this.addAttribute);
+            	this.listenTo(newAttrModel, "invalid", this.showAttributeValidation);
+            	this.listenTo(newAttrModel, "valid",   this.hideAttributeValidation);
+            },
+
+            addAttribute: function(emlAttribute){
+            	//Add the attribute to the attribute list in the EMLEntity model
+            	if( !_.contains(this.model.get("attributeList"), emlAttribute) )
+            		this.model.addAttribute(emlAttribute);
+            },
+
+            removeAttribute: function(e){
+            	var removeBtn = $(e.target);
+
+            	var menuItem  = removeBtn.parents(".attribute-menu-item"),
+            		attrModel = menuItem.data("model");
+
+            	if(attrModel){
+            		//Remove the attribute from the model
+            		this.model.removeAttribute(attrModel);
+
+            		//If this menu item is active, then make the next attribute active instead
+            		if(menuItem.is(".active")){
+            			var nextMenuItem = menuItem.next();
+
+            			if(!nextMenuItem.length || nextMenuItem.is(".new")){
+            				nextMenuItem = menuItem.prev();
+            			}
+
+            			if(nextMenuItem.length){
+	            			nextMenuItem.addClass("active");
+
+	            			this.showAttribute(nextMenuItem.data("model"));
+            			}
+            		}
+
+            		//Remove the elements for this attribute from the page
+            		menuItem.remove();
+	            	this.$(".eml-attribute[data-attribute-id='" + attrModel.cid + "']").remove();
+	            	$(".tooltip").remove();
+
+	            	this.model.trickleUpChange();
+            	}
+            },
+
+            adjustHeight: function(e){
+            	var contentAreaHeight = this.$(".modal-body").height() - this.$(".nav-tabs").height();
+
+            	this.$(".attribute-menu, .attribute-list").css("height", contentAreaHeight + "px");
+            },
+
+            setMenuWidth: function(){
+
+            	this.$(".entity-container .nav").width( this.$el.width() );
+
+            },
+
+            /*
+             * Shows the attribute in the attribute editor
+             * Param e - JS event or attribute model
+             */
+            showAttribute: function(e){
+
+            	if(e.target){
+                   	var clickedEl = $(e.target),
+                   		  menuItem = clickedEl.is(".attribute-menu-item") || clickedEl.parents(".attribute-menu-item");
+
+                	if(clickedEl.is(".remove"))
+                		return;
+            	}
+            	else{
+            		var menuItem = this.$(".attribute-menu-item[data-attribute-id='" + e.cid + "']");
+            	}
+
+            	if(!menuItem)
+            		return;
+
+              //Validate the previously edited attribute
+              //Get the current active attribute
+              var activeAttrTab = this.$(".attribute-menu-item.active");
+
+              //If there is a currently-active attribute tab,
+              if( activeAttrTab.length ){
+                //Get the attribute list from this view's model
+                var emlAttributes = this.model.get("attributeList");
+
+                //If there is an EMLAttribute list,
+                if( emlAttributes && emlAttributes.length ){
+
+                  //Get the active EMLAttribute
+                  var activeEMLAttribute = _.findWhere(emlAttributes, { cid: activeAttrTab.attr("data-attribute-id") });
+
+                  //If there is an active EMLAttribute model, validate it
+                  if( activeEMLAttribute ){
+                    activeEMLAttribute.isValid();
+                  }
+
+                }
+
+              }
+
+            	//If the user clicked on the add attribute link
+            	if( menuItem.is(".new") && this.$(".new.attribute-menu-item").length < 2 ){
+
+            		//Change the attribute menu item
+            		menuItem.removeClass("new").find(".name").text("New attribute");
+            		this.$(".eml-attribute.new").removeClass("new");
+            		menuItem.find(".add").hide();
+
+            		//Add a new attribute view and menu item
+            		this.addNewAttribute();
+
+                //Scroll the attribute menu to the bottom so that the "Add New" button is always visible
+                var attrMenuHeight = this.$(".attribute-menu").scrollTop() + this.$(".attribute-menu").height();
+                this.$(".attribute-menu").scrollTop( attrMenuHeight );
+            	}
+
+            	//Get the attribute view
+            	var attrView = menuItem.data("attributeView");
+
+            	//Change the active attribute in the menu
+            	this.$(".attribute-menu-item.active").removeClass("active");
+            	menuItem.addClass("active");
+
+            	//Hide the old attribute view
+            	this.$(".eml-attribute").hide();
+            	//Show the new attribute view
+            	attrView.$el.show();
+
+              //Scroll to the top of the attribute view
+              this.$(".attribute-list").scrollTop(0);
+
+            	attrView.postRender();
+            },
+
+            /*
+             * Show the attribute validation errors in the attribute navigation menu
+             */
+            showAttributeValidation: function(attr){
+
+            	var attrLink = this.$(".attribute-menu-item[data-attribute-id='" + attr.cid + "']").find("a");
+
+            	//If the validation is already displayed, then exit
+            	if(attrLink.is(".error")) return;
+
+            	var errorIcon = $(document.createElement("i")).addClass("icon icon-exclamation-sign error icon-on-left");
+
+            	attrLink.addClass("error").prepend(errorIcon);
+            },
+
+            /*
+             * Hide the attribute validation errors from the attribute navigation menu
+             */
+            hideAttributeValidation: function(attr){
+            	this.$(".attribute-menu-item[data-attribute-id='" + attr.cid + "']")
+            		.find("a").removeClass("error").find(".icon.error").remove();
+            },
+
+            /*
+             * Show the user what will be removed when this remove button is clicked
+             */
+            previewAttrRemove: function(e){
+            	var removeBtn = $(e.target);
+
+            	removeBtn.parents(".attribute-menu-item").toggleClass("remove-preview");
+            },
+
+            /*
+            * function showValidation
+            *
+            * Will display validation styling and messaging. Should be called after
+            * this view's model has been validated and there are error messages to display
+            */
+            showValidation: function(){
+
+              //Reset the error messages and styling
+              //Only change elements inside the overview-container which contains only the
+              // EMLEntity metadata. The Attributes will be changed by the EMLAttributeView.
+    					this.$(".overview-container .notification").text("");
+              this.$(".overview-tab .icon.error, .attributes-tab .icon.error").remove();
+              this.$(".overview-container, .overview-tab a, .attributes-tab a, .overview-container .error").removeClass("error");
+
+              var overviewTabErrorIcon  = false,
+                  attributeTabErrorIcon = false;
+
+              _.each( this.model.validationError, function(errorMsg, category){
+
+                if( category == "attributeList" ){
+
+                  //Create an error icon for the Attributes tab
+                  if( !attributeTabErrorIcon ){
+                    var errorIcon = $(document.createElement("i"))
+                                      .addClass("icon icon-on-left icon-exclamation-sign error")
+                                      .attr("title", "There is missing information in this tab");
+
+                    //Add the icon to the Overview tab
+                    this.$(".attributes-tab a").prepend(errorIcon).addClass("error");
+                  }
+
+                  return;
+                }
+
+                //Get all the elements for this category and add the error class
+                this.$(".overview-container [data-category='" + category + "']").addClass("error");
+                //Get the notification element for this category and add the error message
+                this.$(".overview-container .notification[data-category='" + category + "']").text(errorMsg);
+
+                //Create an error icon for the Overview tab
+                if( !overviewTabErrorIcon ){
+                  var errorIcon = $(document.createElement("i"))
+                                    .addClass("icon icon-on-left icon-exclamation-sign error")
+                                    .attr("title", "There is missing information in this tab");
+
+                  //Add the icon to the Overview tab
+                  this.$(".overview-tab a").prepend(errorIcon).addClass("error");
+
+                  overviewTabErrorIcon = true;
+                }
+
+              }, this);
+
+            },
+
+            /*
+             * Show the entity overview or attributes tab
+             * depending on the click target
+             */
+            showTab: function(e){
+            	e.preventDefault();
+
+            	//Get the clicked link
+       		  	var link = $(e.target);
+
+       		  	//Remove the active class from all links and add it to the new active link
+       		  	this.$(".nav-tabs li").removeClass("active");
+       		  	link.parent("li").addClass("active");
+
+       		  	//Hide all the panes and show the correct one
+       		  	this.$(".tab-pane").hide();
+       		  	this.$(link.attr("href")).show();
+
+            },
+
+            /*
+             * Show the entity in a modal dialog
+             */
+            show: function(){
+
+            	this.$el.modal('show');
+
+            },
+
+            /*
+             * Hide the entity modal dialog
+             */
+            hide: function(){
+            	this.$el.modal('hide');
+            },
+
+            /**
+             * Save a draft of the parent EML model
+             */
+            saveDraft: function() {
+            	var view = this;
+
+            	try {
+            		var model = this.model.getParentEML();
+            		var title = model.get("title") || "No title";
+
+            		LocalForage.setItem(model.get("id"),
+            		{
+            			id: model.get("id"),
+            			datetime: (new Date()).toISOString(),
+            			title: Array.isArray(title) ? title[0] : title,
+            			draft: model.serialize()
+            		}).then(function() {
+            			view.clearOldDrafts();
+            		});
+            	} catch (ex) {
+            		console.log("Error saving draft:", ex);
+            	}
+            },
+
+            /**
+           	* Clear older drafts by iterating over the sorted list of drafts
+           	* stored by LocalForage and removing any beyond a hardcoded limit.
+           	*/
+           	clearOldDrafts: function() {
+           		var drafts = [];
+
+          		try {
+          			LocalForage.iterate(function(value, key, iterationNumber) {
+          			// Extract each draft
+          			drafts.push({
+          					key: key,
+          					value: value
+          				});
+          			}).then(function(){
+          				// Sort by datetime
+          				drafts = _.sortBy(drafts, function(draft) {
+          					return draft.value.datetime.toString();
+          				}).reverse();
+          			}).then(function() {
+          				_.each(drafts, function(draft, i) {
+          					var age = (new Date()) - new Date(draft.value.datetime);
+          					var isOld = (age / 2678400000) > 1; // ~31days
+          					// Delete this draft is not in the most recent 100 or
+          					// if older than 31 days
+          					var shouldDelete = i > 100 || isOld;
+											if (!shouldDelete) {
+												return;
+											}
+
+											LocalForage.removeItem(draft.key).then(function() {
+												// Item should be removed
+											});
+										})
+									});
+							}
+							catch (ex) {
+								console.log("Failed to clear old drafts: ", ex);
+							}
+						}
+	        });
+
+        return EMLEntityView;
+});
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time) +
+ + + + + diff --git a/docs/docs/views_metadata_EMLGeoCoverageView.js.html b/docs/docs/views_metadata_EMLGeoCoverageView.js.html index 5c2f9126b..b711fbb15 100644 --- a/docs/docs/views_metadata_EMLGeoCoverageView.js.html +++ b/docs/docs/views_metadata_EMLGeoCoverageView.js.html @@ -288,13 +288,13 @@

Source: views/metadata/EMLGeoCoverageView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_metadata_EMLPartyView.js.html b/docs/docs/views_metadata_EMLPartyView.js.html index adac80fde..effd447d2 100644 --- a/docs/docs/views_metadata_EMLPartyView.js.html +++ b/docs/docs/views_metadata_EMLPartyView.js.html @@ -482,13 +482,13 @@

Source: views/metadata/EMLPartyView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_PortalDataView.js.html b/docs/docs/views_portals_PortalDataView.js.html index 2c38849c5..305f82fc4 100644 --- a/docs/docs/views_portals_PortalDataView.js.html +++ b/docs/docs/views_portals_PortalDataView.js.html @@ -134,13 +134,13 @@

Source: views/portals/PortalDataView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_PortalListView.js.html b/docs/docs/views_portals_PortalListView.js.html index 573f34861..4eba207a0 100644 --- a/docs/docs/views_portals_PortalListView.js.html +++ b/docs/docs/views_portals_PortalListView.js.html @@ -332,13 +332,13 @@

Source: views/portals/PortalListView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_PortalMembersView.js.html b/docs/docs/views_portals_PortalMembersView.js.html index 3cc4cd9b6..b8302476f 100644 --- a/docs/docs/views_portals_PortalMembersView.js.html +++ b/docs/docs/views_portals_PortalMembersView.js.html @@ -173,13 +173,13 @@

Source: views/portals/PortalMembersView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_PortalMetricsView.js.html b/docs/docs/views_portals_PortalMetricsView.js.html index 34cf6dca0..0ddba36f8 100644 --- a/docs/docs/views_portals_PortalMetricsView.js.html +++ b/docs/docs/views_portals_PortalMetricsView.js.html @@ -153,11 +153,10 @@

Source: views/portals/PortalMetricsView.js

// Create a StatsModel var statsModel = new StatsModel({ query: statsSearchModel.getQuery(), - searchModel: statsSearchModel, - supportDownloads: false + searchModel: statsSearchModel }); - + var label_list = []; label_list.push(this.model.get("label")); var metricsModel = new MetricsModel({pid_list: label_list, type: "portal"}); @@ -215,13 +214,13 @@

Source: views/portals/PortalMetricsView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_PortalSectionView.js.html b/docs/docs/views_portals_PortalSectionView.js.html index 5207c5a80..b759fc031 100644 --- a/docs/docs/views_portals_PortalSectionView.js.html +++ b/docs/docs/views_portals_PortalSectionView.js.html @@ -287,13 +287,13 @@

Source: views/portals/PortalSectionView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_PortalView.js.html b/docs/docs/views_portals_PortalView.js.html index 686539296..df4109860 100644 --- a/docs/docs/views_portals_PortalView.js.html +++ b/docs/docs/views_portals_PortalView.js.html @@ -755,13 +755,13 @@

Source: views/portals/PortalView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_editor_PortEditorDataView.js.html b/docs/docs/views_portals_editor_PortEditorDataView.js.html index 83f15dcde..89a1483bf 100644 --- a/docs/docs/views_portals_editor_PortEditorDataView.js.html +++ b/docs/docs/views_portals_editor_PortEditorDataView.js.html @@ -160,13 +160,13 @@

Source: views/portals/editor/PortEditorDataView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_editor_PortEditorImageView.js.html b/docs/docs/views_portals_editor_PortEditorImageView.js.html index ef9850fd3..89b52db6b 100644 --- a/docs/docs/views_portals_editor_PortEditorImageView.js.html +++ b/docs/docs/views_portals_editor_PortEditorImageView.js.html @@ -449,13 +449,13 @@

Source: views/portals/editor/PortEditorImageView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_editor_PortEditorLogosView.js.html b/docs/docs/views_portals_editor_PortEditorLogosView.js.html index dadab9194..768d6f965 100644 --- a/docs/docs/views_portals_editor_PortEditorLogosView.js.html +++ b/docs/docs/views_portals_editor_PortEditorLogosView.js.html @@ -257,13 +257,13 @@

Source: views/portals/editor/PortEditorLogosView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_editor_PortEditorMdSectionView.js.html b/docs/docs/views_portals_editor_PortEditorMdSectionView.js.html index 2499e7911..17d257967 100644 --- a/docs/docs/views_portals_editor_PortEditorMdSectionView.js.html +++ b/docs/docs/views_portals_editor_PortEditorMdSectionView.js.html @@ -292,13 +292,13 @@

Source: views/portals/editor/PortEditorMdSectionView.js
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_editor_PortEditorSectionView.js.html b/docs/docs/views_portals_editor_PortEditorSectionView.js.html index 07bc93c16..8385b7e0d 100644 --- a/docs/docs/views_portals_editor_PortEditorSectionView.js.html +++ b/docs/docs/views_portals_editor_PortEditorSectionView.js.html @@ -357,13 +357,13 @@

Source: views/portals/editor/PortEditorSectionView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_editor_PortEditorSectionsView.js.html b/docs/docs/views_portals_editor_PortEditorSectionsView.js.html index e0d4673be..4a6531100 100644 --- a/docs/docs/views_portals_editor_PortEditorSectionsView.js.html +++ b/docs/docs/views_portals_editor_PortEditorSectionsView.js.html @@ -186,6 +186,10 @@

Source: views/portals/editor/PortEditorSectionsView.jsSource: views/portals/editor/PortEditorSectionsView.jsSource: views/portals/editor/PortEditorSectionsView.jsSource: views/portals/editor/PortEditorSectionsView.jsSource: views/portals/editor/PortEditorSectionsView.js
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_editor_PortEditorSettingsView.js.html b/docs/docs/views_portals_editor_PortEditorSettingsView.js.html index 8438fbe18..d6e326f7e 100644 --- a/docs/docs/views_portals_editor_PortEditorSettingsView.js.html +++ b/docs/docs/views_portals_editor_PortEditorSettingsView.js.html @@ -380,13 +380,13 @@

Source: views/portals/editor/PortEditorSettingsView.js
- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
diff --git a/docs/docs/views_portals_editor_PortalEditorView.js.html b/docs/docs/views_portals_editor_PortalEditorView.js.html index be93fbdf1..4ffccdd8c 100644 --- a/docs/docs/views_portals_editor_PortalEditorView.js.html +++ b/docs/docs/views_portals_editor_PortalEditorView.js.html @@ -79,6 +79,12 @@

Source: views/portals/editor/PortalEditorView.js

*/ activeSectionLabel: "", + /** + * When a new portal is being created, this is the label of the section that will be active when the editor first renders + * @type {string} + */ + newPortalDefaultSectionLabel: "Settings", + /** * References to templates for this view. HTML files are converted to Underscore.js templates */ @@ -88,6 +94,18 @@

Source: views/portals/editor/PortalEditorView.js

// used by the metadata editor) with the portal editor version editorSubmitMessageTemplate: _.template(portalEditorSubmitMessageTemplate), + /** + * An array of Backbone Views that are contained in this view. + * @type {Backbone.View[]} + */ + subviews: [], + + /** + * A reference to the PortEditorSectionsView for this instance of the PortEditorView + * @type {PortEditorSectionsView} + */ + sectionsView: null, + /** * The text to use in the editor submit button * @type {string} @@ -137,11 +155,16 @@

Source: views/portals/editor/PortalEditorView.js

*/ initialize: function(options){ + //Reset arrays and objects set on this View, otherwise they will be shared across intances, causing errors + this.subviews = new Array(); + this.sectionsView = null; + if(typeof options == "object"){ // initializing the PortalEditorView properties this.portalIdentifier = options.portalIdentifier ? options.portalIdentifier : undefined; this.activeSectionLabel = options.activeSectionLabel || ""; } + }, /** @@ -217,7 +240,7 @@

Source: views/portals/editor/PortalEditorView.js

if( MetacatUI.appUserModel.get("isAuthorizedCreatePortal") ){ // Start new portals on the settings tab - this.activeSectionLabel = "Settings"; + this.activeSectionLabel = this.newPortalDefaultSectionLabel; // Render the default model if the portal is new this.renderPortalEditor(); @@ -318,6 +341,9 @@

Source: views/portals/editor/PortalEditorView.js

newPortalTempName: this.newPortalTempName }); + //Save the PortEditorSectionsView as a subview + this.subviews.push(this.sectionsView); + //Attach a reference to this view this.sectionsView.editorView = this; @@ -763,6 +789,15 @@

Source: views/portals/editor/PortalEditorView.js

//Remove the scroll listener $(window).off("scroll", "", this.handleScroll); + + //Close and remove all of the subviews + _.invoke(this.subviews, "onClose"); + _.invoke(this.subviews, "remove"); + //Reset the subviews array + this.subviews = new Array(); + + //Reset the sectionsView reference + this.sectionsView = null; }, }); @@ -780,13 +815,13 @@

Source: views/portals/editor/PortalEditorView.js


- Documentation generated by JSDoc 3.6.3 on Thu Mar 12 2020 14:11:45 GMT-0500 (Central Daylight Time) + Documentation generated by JSDoc 3.6.3 on Thu May 07 2020 18:49:13 GMT-0500 (Central Daylight Time)
From 850640c7c1722a5efab0d5ddc5104b3acbae782d Mon Sep 17 00:00:00 2001 From: laurenwalker Date: Thu, 7 May 2020 18:57:05 -0500 Subject: [PATCH 4/4] Changed version to 2.11.1 --- src/index.html | 2 +- src/loader.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.html b/src/index.html index 7bb05d41e..ff375f991 100644 --- a/src/index.html +++ b/src/index.html @@ -11,7 +11,7 @@ -