diff --git a/src/js/views/MetadataView.js b/src/js/views/MetadataView.js
index 19d42fa7b..599abd610 100644
--- a/src/js/views/MetadataView.js
+++ b/src/js/views/MetadataView.js
@@ -17,8 +17,6 @@ define([
"views/DownloadButtonView",
"views/ProvChartView",
"views/MetadataIndexView",
- "views/ExpandCollapseListView",
- "views/ProvStatementView",
"views/CitationHeaderView",
"views/citations/CitationModalView",
"views/AnnotationView",
@@ -34,9 +32,7 @@ define([
"text!templates/editMetadata.html",
"text!templates/dataDisplay.html",
"text!templates/map.html",
- "text!templates/annotation.html",
"text!templates/metaTagsHighwirePress.html",
- "uuid",
"views/MetricView",
], (
$,
@@ -44,7 +40,7 @@ define([
_,
Backbone,
gmaps,
- fancybox,
+ _fancybox,
Clipboard,
DataPackage,
DataONEObject,
@@ -57,8 +53,6 @@ define([
DownloadButtonView,
ProvChart,
MetadataIndex,
- ExpandCollapseList,
- ProvStatement,
CitationHeaderView,
CitationModalView,
AnnotationView,
@@ -74,9 +68,7 @@ define([
EditMetadataTemplate,
DataDisplayTemplate,
MapTemplate,
- AnnotationTemplate,
metaTagsHighwirePressTemplate,
- uuid,
MetricView,
) => {
"use strict";
@@ -98,8 +90,8 @@ define([
saveProvPending: false,
model: new SolrResult(),
- packageModels: new Array(),
- entities: new Array(),
+ packageModels: [],
+ entities: [],
dataPackage: null,
dataPackageSynced: false,
el: "#Content",
@@ -133,8 +125,8 @@ define([
objectIds: [],
/**
- * Text to display in the help tooltip for the alternative identifier field,
- * if the field is present.
+ * Text to display in the help tooltip for the alternative identifier
+ * field, if the field is present.
* @type {string}
* @since 2.26.0
*/
@@ -147,7 +139,7 @@ define([
history and evolution of the dataset.
`,
- // Delegated events for creating new items, and clearing completed ones.
+ /** @inheritdoc */
events: {
"click #publish": "publish",
"mouseover .highlight-node": "highlightNode",
@@ -156,9 +148,15 @@ define([
"click #save-metadata-prov": "saveProv",
},
- initialize(options) {
- if (options === undefined || !options) var options = {};
-
+ /**
+ * Initialize the MetadataView
+ * @param {object} options Object containing the view's options
+ * @param {string} [options.pid] The identifier of the metadata object to
+ * render
+ * @param {string} [options.el] The jQuery selector for the element in
+ * which to render the view
+ */
+ initialize(options = {}) {
this.pid =
options.pid || options.id || MetacatUI.appModel.get("pid") || null;
@@ -167,7 +165,7 @@ define([
if (typeof options.el !== "undefined") this.setElement(options.el);
},
- // Render the main metadata view
+ /** @inheritdoc */
render() {
this.stopListening();
@@ -175,10 +173,10 @@ define([
// this.showLoading("Loading...");
// Reset various properties of this view first
- this.classMap = new Array();
- this.subviews = new Array();
+ this.classMap = [];
+ this.subviews = [];
this.model.set(this.model.defaults);
- this.packageModels = new Array();
+ this.packageModels = [];
// get the pid to render
if (!this.pid) this.pid = MetacatUI.appModel.get("pid");
@@ -186,17 +184,18 @@ define([
this.listenTo(MetacatUI.appUserModel, "change:loggedIn", this.render);
// Listen to when the metadata has been rendered
- this.once("metadataLoaded", function () {
+ this.once("metadataLoaded", () => {
this.createAnnotationViews();
this.insertMarkdownViews();
});
// Listen to when the package table has been rendered
- this.once("dataPackageRendered", function () {
+ this.once("dataPackageRendered", () => {
const packageTableContainer = this.$("#data-package-container");
$(packageTableContainer).children(".loading").remove();
- // Scroll to the element on the page that is in the hash fragment (if there is one)
+ // Scroll to the element on the page that is in the hash fragment (if
+ // there is one)
this.scrollToFragment();
});
@@ -206,8 +205,8 @@ define([
},
/**
- * Retrieve the resource map given its PID, and when it's fetched,
- * check for write permissions, then check for private members in the package
+ * Retrieve the resource map given its PID, and when it's fetched, check
+ * for write permissions, then check for private members in the package
* table view, if there is one.
* @param {string} pid - The PID of the resource map
*/
@@ -224,15 +223,15 @@ define([
// If there is no resource map
if (!pid) {
- // mark the data package as synced,
- // since there are no other models to fetch
+ // mark the data package as synced, since there are no other models to
+ // fetch
this.dataPackageSynced = true;
this.trigger("changed:dataPackageSynced");
this.checkWritePermissions();
return;
}
- this.listenToOnce(this.dataPackage, "complete", function () {
+ this.listenToOnce(this.dataPackage, "complete", () => {
this.dataPackageSynced = true;
this.trigger("changed:dataPackageSynced");
const dataPackageView = _.findWhere(this.subviews, {
@@ -267,7 +266,7 @@ define([
) {
this.checkWritePermissions();
} else {
- this.listenToOnce(this.dataPackage.packageModel, "sync", function () {
+ this.listenToOnce(this.dataPackage.packageModel, "sync", () => {
this.checkWritePermissions();
});
}
@@ -278,47 +277,49 @@ define([
},
/*
- * Retrieves information from the index about this object, given the id (passed from the URL)
- * When the object info is retrieved from the index, we set up models depending on the type of object this is
+ * Retrieves information from the index about this object, given the id
+ * (passed from the URL) When the object info is retrieved from the index,
+ * we set up models depending on the type of object this is
*/
- getModel(pid) {
- // Get the pid and sid
- if (typeof pid === "undefined" || !pid) var { pid } = this;
- if (typeof this.seriesId !== "undefined" && this.seriesId)
- var sid = this.seriesId;
+ getModel(id) {
+ const pid = id || this.pid;
+ const sid = this.seriesId || null;
// Get the package ID
this.model.set({ id: pid, seriesId: sid });
const { model } = this;
- this.listenToOnce(model, "sync", function () {
+ this.listenToOnce(model, "sync", () => {
if (
- this.model.get("formatType") == "METADATA" ||
+ this.model.get("formatType") === "METADATA" ||
!this.model.get("formatType")
) {
this.model = model;
this.renderMetadata();
- } else if (this.model.get("formatType") == "DATA") {
+ } else if (this.model.get("formatType") === "DATA") {
// Get the metadata pids that document this data object
const 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) {
+ // 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.navigateWithFragment(_.first(isDocBy), 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.
+ // 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.
if (isDocBy && isDocBy.length > 1) {
const view = this;
+ // eslint-disable-next-line import/no-dynamic-require
require(["collections/Filters", "collections/SolrResults"], (
Filters,
SolrResults,
) => {
- // Create a search for the metadata docs that document this data object
+ // Create a search for the metadata docs that document this data
+ // object
const searchFilters = new Filters([
{
values: isDocBy,
@@ -336,17 +337,21 @@ define([
});
// When the search results are returned, process those results
- view.listenToOnce(searchResults, "sync", (searchResults) => {
+ view.listenToOnce(searchResults, "sync", () => {
// Keep track of the latest version of the metadata doc(s)
const latestVersions = [];
- // Iterate over each search result and find the latest version of each metadata version chain
+ // Iterate over each search result and find the latest version
+ // of each metadata version chain
searchResults.each((searchResult) => {
- // If this metadata isn't obsoleted by another object, it is the latest version
+ // 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
+ // 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"))
) {
@@ -354,16 +359,21 @@ define([
}
}, view);
- // If at least one latest version was found (should always be the case),
+ // 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.
+ // 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.navigateWithFragment(latestVersions[0], view.pid);
}
- // 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).
+ // 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 {
view.navigateWithFragment(_.last(isDocBy), view.pid);
}
@@ -376,11 +386,11 @@ define([
return;
}
this.noMetadata(this.model);
- } else if (this.model.get("formatType") == "RESOURCE") {
+ } else if (this.model.get("formatType") === "RESOURCE") {
const packageModel = new Package({ id: this.model.get("id") });
packageModel.on(
"complete",
- function () {
+ () => {
const metadata = packageModel.getMetadata();
if (!metadata) {
@@ -411,6 +421,12 @@ define([
model.getInfo();
},
+ /**
+ * Render the main components of the metadata view. Insert HTML from the
+ * view service or fallback to rendering from the index. Insert
+ * breadcrumbs, citation, data source logo, metadata controls, and
+ * metadata metrics.
+ */
renderMetadata() {
const pid = this.model.get("id");
@@ -435,8 +451,8 @@ define([
// Insert various metadata controls in the page
this.insertControls();
- // If we're displaying the metrics well then display copy citation and edit button
- // inside the well
+ // If we're displaying the metrics well then display copy citation and
+ // edit button inside the well
if (MetacatUI.appModel.get("displayDatasetMetrics")) {
// Insert Metrics Stats into the dataset landing pages
this.insertMetricsControls();
@@ -448,31 +464,29 @@ define([
);
// Check for a view service in this MetacatUI.appModel
- if (
- MetacatUI.appModel.get("viewServiceUrl") !== undefined &&
- MetacatUI.appModel.get("viewServiceUrl")
- )
- var endpoint =
- MetacatUI.appModel.get("viewServiceUrl") + encodeURIComponent(pid);
-
- if (endpoint && typeof endpoint !== "undefined") {
+ const viewServiceUrl = MetacatUI.appModel.get("viewServiceUrl");
+ if (viewServiceUrl) {
+ const endpoint = viewServiceUrl + encodeURIComponent(pid);
const viewRef = this;
const loadSettings = {
url: endpoint,
- success(response, status, xhr) {
+ success(response, status, _xhr) {
try {
- // If the user has navigated away from the MetadataView, then don't render anything further
- if (MetacatUI.appView.currentView != viewRef) return;
+ // If the user has navigated away from the MetadataView, then
+ // don't render anything further
+ if (MetacatUI.appView.currentView !== viewRef) return;
- // Our fallback is to show the metadata details from the Solr index
+ // Our fallback is to show the metadata details from the Solr
+ // index
if (
- status == "error" ||
+ status === "error" ||
!response ||
typeof response !== "string"
)
viewRef.renderMetadataFromIndex();
else {
- // Check for a response that is a 200 OK status, but is an error msg
+ // Check for a response that is a 200 OK status, but is an
+ // error msg
if (
response.length < 250 &&
response.indexOf("Error transforming document") > -1 &&
@@ -481,8 +495,9 @@ define([
viewRef.renderMetadataFromIndex();
return;
}
- // Mark this as a metadata doc with no stylesheet, or one that is at least different than usual EML and FGDC
- if (response.indexOf('id="Metadata"') == -1) {
+ // Mark this as a metadata doc with no stylesheet, or one that
+ // is at least different than usual EML and FGDC
+ if (response.indexOf('id="Metadata"') === -1) {
viewRef.$el.addClass("container no-stylesheet");
if (viewRef.model.get("indexed")) {
@@ -499,7 +514,8 @@ define([
viewRef.storeEntityPIDs(entityEl, entityId);
});
- // If there is no info from the index and there is no metadata doc rendered either, then display a message
+ // If there is no info from the index and there is no metadata
+ // doc rendered either, then display a message
if (
viewRef.$el.is(".no-stylesheet") &&
viewRef.model.get("archived") &&
@@ -518,19 +534,20 @@ define([
// Add a map of the spatial coverage
if (gmaps) viewRef.insertSpatialCoverageMap();
- // Injects Clipboard objects into DOM elements returned from the View Service
+ // Injects Clipboard objects into DOM elements returned from
+ // the View Service
viewRef.insertCopiables();
}
} catch (e) {
- console.log(
- "Error rendering metadata from the view service",
- e,
+ MetacatUI.analytics?.trackException(
+ `Error rendering metadata from the view service. Fellback to index, ${e}, Response: ${response}`,
+ pid,
+ false,
);
- console.log("Response from the view service: ", response);
viewRef.renderMetadataFromIndex();
}
},
- error(xhr, textStatus, errorThrown) {
+ error(_xhr, _textStatus, _errorThrown) {
viewRef.renderMetadataFromIndex();
},
};
@@ -549,7 +566,10 @@ define([
this.insertCitationMetaTags();
},
- /* If there is no view service available, then display the metadata fields from the index */
+ /**
+ * If there is no view service available, then display the metadata fields
+ * from the index
+ */
renderMetadataFromIndex() {
const metadataFromIndex = new MetadataIndex({
pid: this.pid,
@@ -571,6 +591,7 @@ define([
});
},
+ /** @deprecated */
removeCitation() {
let citation = "";
let citationEl = null;
@@ -583,13 +604,14 @@ define([
// Save this element in the view
citationEl = this.$(".citation");
}
- // Older versions of Metacat (v2.4.3 and older) will not have the citation class in the XSLT. Find the citation another way
+ // Older versions of Metacat (v2.4.3 and older) will not have the
+ // citation class in the XSLT. Find the citation another way
else {
// Find the DOM element with the citation
const wells = this.$(".well");
- const viewRef = this;
- // Find the div.well with the citation. If we never find it, we don't insert the list of contents
+ // Find the div.well with the citation. If we never find it, we don't
+ // insert the list of contents
_.each(wells, (well) => {
if (
(!citationEl &&
@@ -607,7 +629,8 @@ define([
}
});
- // Remove the unnecessary classes that are used in older versions of Metacat (2.4.3 and older)
+ // Remove the unnecessary classes that are used in older versions of
+ // Metacat (2.4.3 and older)
const citationText = $(citationEl).find(".span10");
$(citationText).removeClass("span10").addClass("span12");
}
@@ -618,6 +641,9 @@ define([
citationEl.remove();
},
+ /**
+ * Add breadcrumbs to the page to show the user where they are in the app
+ */
insertBreadcrumbs() {
const breadcrumbs = $(document.createElement("ol"))
.addClass("breadcrumb")
@@ -641,7 +667,7 @@ define([
`${MetacatUI.root}/data${
MetacatUI.appModel.get("page") > 0
? `/page/${
- parseInt(MetacatUI.appModel.get("page")) + 1
+ parseInt(MetacatUI.appModel.get("page"), 10) + 1
}`
: ""
}`,
@@ -662,14 +688,14 @@ define([
),
);
- if (MetacatUI.uiRouter.lastRoute() == "data") {
+ if (MetacatUI.uiRouter.lastRoute() === "data") {
$(breadcrumbs).prepend(
$(document.createElement("a"))
.attr(
"href",
`${MetacatUI.root}/data/page/${
MetacatUI.appModel.get("page") > 0
- ? parseInt(MetacatUI.appModel.get("page")) + 1
+ ? parseInt(MetacatUI.appModel.get("page"), 10) + 1
: ""
}`,
)
@@ -686,7 +712,7 @@ define([
this.$(this.breadcrumbContainer).html(breadcrumbs);
},
- /*
+ /**
* When the metadata object doesn't exist, display a message to the user
*/
showNotFound() {
@@ -695,21 +721,17 @@ define([
return;
}
- try {
- // Check if a query string was in the URL and if so, try removing it in the identifier
- if (this.model.get("id").match(/\?\S+\=\S+/g) && !this.findTries) {
- const newID = this.model.get("id").replace(/\?\S+\=\S+/g, "");
- this.onClose();
- this.model.set("id", newID);
- this.pid = newID;
- this.findTries = 1;
- this.render();
- return;
- }
- } catch (e) {
- console.warn("Caught error while determining query string", e);
+ // Check if a query string was in the URL and if so, try removing it in
+ // the identifier
+ if (this.model.get("id").match(/\?\S+=\S+/g) && !this.findTries) {
+ const newID = this.model.get("id").replace(/\?\S+=\S+/g, "");
+ this.onClose();
+ this.model.set("id", newID);
+ this.pid = newID;
+ this.findTries = 1;
+ this.render();
+ return;
}
-
// Construct a message that shows this object doesn't exist
const msg =
`
Nothing was found.
` +
@@ -727,7 +749,8 @@ define([
// Show the not found error message
this.showError(msg);
- // Add the pid to the link href. Add via JS so it is Attribute-encoded to prevent XSS attacks
+ // Add the pid to the link href. Add via JS so it is Attribute-encoded
+ // to prevent XSS attacks
this.$("#metadata-view-not-found-message a").attr(
"href",
`${MetacatUI.root}/data/query=${encodeURIComponent(
@@ -736,12 +759,11 @@ define([
);
},
- /*
- * When the metadata object is private, display a message to the user
- */
+ /** When the metadata object is private, display a message to the user */
showIsPrivate() {
- // If we haven't checked the logged-in status of the user yet, wait a bit
- // until we show a 401 msg, in case this content is their private content
+ // If we haven't checked the logged-in status of the user yet, wait a
+ // bit until we show a 401 msg, in case this content is their private
+ // content
if (!MetacatUI.appUserModel.get("checked")) {
this.listenToOnce(
MetacatUI.appUserModel,
@@ -751,9 +773,12 @@ define([
return;
}
- // If the user is logged in, the message will display that this dataset is private.
+ let msg = "";
+
+ // If the user is logged in, the message will display that this dataset
+ // is private.
if (MetacatUI.appUserModel.get("loggedIn")) {
- var msg =
+ msg =
'' +
@@ -763,7 +788,7 @@ define([
}
// If the user isn't logged in, display a log in link.
else {
- var msg =
+ msg =
`` +
@@ -780,7 +805,14 @@ define([
this.showError(msg);
},
+ /**
+ * Retrieves and processes the details of the specified data packages.
+ * @param {string[]} packageIDs - An array of package IDs to retrieve
+ * details for. If the array is empty or not provided, it processes the
+ * current metadata document as a standalone package.
+ */
getPackageDetails(packageIDs) {
+ const view = this;
let completePackages = 0;
// This isn't a package, but just a lonely metadata doc...
@@ -794,7 +826,7 @@ define([
} else {
_.each(
packageIDs,
- function (thisPackageID, i) {
+ (thisPackageID, _i) => {
// Create a model representing the data package
const thisPackage = new Package({ id: thisPackageID });
@@ -806,28 +838,24 @@ define([
);
// When the package info is fully retrieved
- this.listenToOnce(
- thisPackage,
- "complete",
- function (thisPackage) {
- // When all packages are fully retrieved
- completePackages++;
- if (completePackages >= packageIDs.length) {
- const latestPackages = _.filter(
- this.packageModels,
- (m) => !_.contains(packageIDs, m.get("obsoletedBy")),
- );
+ view.listenToOnce(thisPackage, "complete", () => {
+ // When all packages are fully retrieved
+ completePackages += 1;
+ if (completePackages >= packageIDs.length) {
+ const latestPackages = _.filter(
+ view.packageModels,
+ (m) => !_.contains(packageIDs, m.get("obsoletedBy")),
+ );
- // Set those packages as the most recent package
- this.packageModels = latestPackages;
+ // Set those packages as the most recent package
+ view.packageModels = latestPackages;
- this.insertPackageDetails(latestPackages);
- }
- },
- );
+ view.insertPackageDetails(latestPackages);
+ }
+ });
// Save the package in the view
- this.packageModels.push(thisPackage);
+ view.packageModels.push(thisPackage);
// Make sure we get archived content, too
thisPackage.set("getArchivedMembers", true);
@@ -840,8 +868,10 @@ define([
}
},
+ /** Make minor modifications to the HTML returned from the view service */
alterMarkup() {
- // Find the taxonomic range and give it a class for styling - for older versions of Metacat only (v2.4.3 and older)
+ // Find the taxonomic range and give it a class for styling - for older
+ // versions of Metacat only (v2.4.3 and older)
if (!this.$(".taxonomicCoverage").length)
this.$('h4:contains("Taxonomic Range")')
.parent()
@@ -860,7 +890,8 @@ define([
$(e.target).parents("tr").first().addClass("active");
});
- // Mark the first row in each attribute list table as active since the first attribute is displayed at first
+ // Mark the first row in each attribute list table as active since the
+ // first attribute is displayed at first
this.$(".attributeListTable tr:first-child()").addClass("active");
// Add explanation text to the alternate identifier
@@ -871,73 +902,71 @@ define([
* Inserts an info icon next to the alternate identifier field, if it
* exists. The icon will display a tooltip with the help text for the
* field.
- * @returns {jQuery} The jQuery object for the icon element.
+ * @returns {jQuery|null} The jQuery object for the icon element.
* @since 2.26.0
*/
renderAltIdentifierHelpText() {
- try {
- // Find the HTML element that contains the alternate identifier.
- const altIdentifierLabel = this.$(
- ".control-label:contains('Alternate Identifier')",
- );
+ // Find the HTML element that contains the alternate identifier.
+ const altIdentifierLabel = this.$(
+ ".control-label:contains('Alternate Identifier')",
+ );
- // It may not exist for all datasets.
- if (!altIdentifierLabel.length) return;
+ // It may not exist for all datasets.
+ if (!altIdentifierLabel.length) return null;
- const text = this.alternativeIdentifierHelpText;
+ const text = this.alternativeIdentifierHelpText;
- if (!text) return;
+ if (!text) return null;
- // Create the tooltip
- const icon = $(document.createElement("i"))
- .addClass("tooltip-this icon icon-info-sign")
- .css("margin-left", "4px");
+ // Create the tooltip
+ const icon = $(document.createElement("i"))
+ .addClass("tooltip-this icon icon-info-sign")
+ .css("margin-left", "4px");
- // Activate the jQuery tooltip plugin
- icon.tooltip({
- title: text,
- placement: "top",
- container: "body",
- });
+ // Activate the jQuery tooltip plugin
+ icon.tooltip({
+ title: text,
+ placement: "top",
+ container: "body",
+ });
- // Add the icon to the label.
- altIdentifierLabel.append(icon);
+ // Add the icon to the label.
+ altIdentifierLabel.append(icon);
- return icon;
- } catch (e) {
- console.log("Error adding help text to alternate identifier", e);
- }
+ return icon;
},
- /*
- * Inserts a table with all the data package member information and sends the call to display annotations
+ /**
+ * Inserts a table with all the data package member information and sends
+ * the call to display annotations
+ * @param {Array} packageModels - An array of Package models
+ * @param {object} options - An object with options for rendering the
+ * package table
+ * @returns {MetadataView|null} Returns this view object
*/
- insertPackageDetails(packages, options) {
- if (typeof options === "undefined") {
- var options = {};
- }
+ insertPackageDetails(packageModels, options = {}) {
// Don't insert the package details twice
const view = this;
const tableEls = this.$(view.tableContainer).children().not(".loading");
- if (tableEls.length > 0) return;
+ if (tableEls.length > 0) return view;
// wait for the metadata to load
const metadataEls = this.$(view.metadataContainer).children();
if (!metadataEls.length || metadataEls.first().is(".loading")) {
- this.once("metadataLoaded", function () {
+ this.once("metadataLoaded", () => {
view.insertPackageDetails(this.packageModels, options);
});
- return;
+ return view;
}
- if (!packages) var packages = this.packageModels;
+ const packages = packageModels || this.packageModels;
// Get the entity names from this page/metadata
this.getEntityNames(packages);
_.each(
packages,
- function (packageModel) {
+ function showDetails(packageModel) {
// If the package model is not complete, don't do anything
if (!packageModel.complete) return;
@@ -959,51 +988,57 @@ define([
if (
!(
!this.model.get("archived") &&
- packageModel.get("archived") == true
- )
- ) {
- var title = packageModel.get("id")
- ? `Package: ${packageModel.get(
- "id",
- )}`
- : "";
- options.title = `Files in this dataset ${title}`;
- options.nested = true;
- this.insertPackageTable(packageModel, options);
- }
- } else {
- // If this metadata is not archived, then don't display archived packages
- if (
- !(
- !this.model.get("archived") &&
- packageModel.get("archived") == true
+ packageModel.get("archived") === true
)
) {
- var title = packageModel.get("id")
+ const title = packageModel.get("id")
? `Package: ${packageModel.get(
"id",
)}`
: "";
- options.title = `Files in this dataset ${title}`;
- this.insertPackageTable(packageModel, options);
+ const tableOptions = { ...options, nested: true, title };
+ this.insertPackageTable(packageModel, tableOptions);
}
+ } else if (
+ // If this metadata is not archived, then don't display archived
+ // packages
+ !(
+ !this.model.get("archived") &&
+ packageModel.get("archived") === true
+ )
+ ) {
+ const title = packageModel.get("id")
+ ? `Package: ${packageModel.get(
+ "id",
+ )}`
+ : "";
+ const tableOptions = {
+ ...options,
+ title: `Files in this dataset ${title}`,
+ };
+ this.insertPackageTable(packageModel, tableOptions);
}
- // Remove the extra download button returned from the XSLT since the package table will have all the download links
+ // Remove the extra download button returned from the XSLT since the
+ // package table will have all the download links
$("#downloadPackage").remove();
},
this,
);
- // If this metadata doc is not in a package, but is just a lonely metadata doc...
+ // If this metadata doc is not in a package, but is just a lonely
+ // metadata doc...
if (!packages.length) {
const packageModel = new Package({
members: [this.model],
});
packageModel.complete = true;
- options.title = "Files in this dataset";
- options.disablePackageDownloads = true;
- this.insertPackageTable(packageModel, options);
+ const tableOptions = {
+ ...options,
+ title: "Files in this dataset",
+ disablePackageDownloads: true,
+ };
+ this.insertPackageTable(packageModel, tableOptions);
}
// Insert the data details sections
@@ -1026,21 +1061,25 @@ define([
(p) => !p.get("obsoletedBy"),
);
- // If all of the packages are obsoleted, then use the last package in the array,
- // which is most likely the most recent.
- /** @todo Use the DataONE version API to find the most recent package in the version chain */
+ // If all of the packages are obsoleted, then use the last package
+ // in the array, which is most likely the most recent.
+
+ // @todo Use the DataONE version API to find the most recent
+ // package in the version chain
if (!mostRecentPackage) {
mostRecentPackage = packages[packages.length - 1];
}
- // Get the data package only if it is not the same as the previously fetched package
- if (mostRecentPackage.get("id") != packages[0].get("id"))
+ // Get the data package only if it is not the same as the previously
+ // fetched package
+ if (mostRecentPackage.get("id") !== packages[0].get("id"))
this.getDataPackage(mostRecentPackage.get("id"));
}
} catch (e) {
- console.error(
- "Could not get the data package (prov will not be displayed, possibly other info as well).",
- e,
+ MetacatUI.analytics?.trackException(
+ "Could not get the data package and could not display provenance graphs.",
+ view.pid,
+ false,
);
}
@@ -1050,9 +1089,18 @@ define([
return this;
},
+ /**
+ * Inserts a package table into the view.
+ * @param {object} packageModel - The package model.
+ * @param {object} options - The options for the package table.
+ * @param {string} options.title - The title of the package table.
+ * @param {boolean} options.disablePackageDownloads - Whether to disable
+ * package downloads.
+ * @param {boolean} options.nested - Whether the package table is nested.
+ */
insertPackageTable(packageModel, options) {
const view = this;
- if (this.dataPackage == null || !this.dataPackageSynced) {
+ if (!this.dataPackage || !this.dataPackageSynced) {
this.listenToOnce(this, "changed:dataPackageSynced", () => {
view.insertPackageTable(packageModel, options);
});
@@ -1067,16 +1115,16 @@ define([
this.dataPackage.mergeModels(packageModel.get("members"));
}
+ let title = "";
+ let disablePackageDownloads = false;
+ let nested = false;
+
if (options) {
- var title = options.title || "";
- var disablePackageDownloads =
- options.disablePackageDownloads || false;
- var nested =
+ title = options.title || "";
+ disablePackageDownloads = options.disablePackageDownloads || false;
+ nested =
typeof options.nested === "undefined" ? false : options.nested;
- } else
- var title = "",
- nested = false,
- disablePackageDownloads = false;
+ }
//* * Draw the package table **//
const tableView = new DataPackageView({
@@ -1099,23 +1147,25 @@ define([
const numTables = $(tablesContainer).find(
"table.download-contents",
).length;
- if (numTables == 1) {
- var tableContainer = $(document.createElement("div")).attr(
+
+ let tableContainer = tablesContainer;
+ if (numTables === 1) {
+ tableContainer = $(document.createElement("div")).attr(
"id",
`additional-tables-for-${this.cid}`,
);
tableContainer.hide();
$(tablesContainer).append(tableContainer);
- } else if (numTables > 1)
- var tableContainer = this.$(`#additional-tables-for-${this.cid}`);
- else var tableContainer = tablesContainer;
+ } else if (numTables > 1) {
+ tableContainer = this.$(`#additional-tables-for-${this.cid}`);
+ }
// Insert the package table HTML
$(tableContainer).empty();
$(tableContainer).append(tableView.render().el);
- // Add Package Download
- // create an instance of DownloadButtonView to handle package downloads
+ // Add Package Download create an instance of DownloadButtonView to
+ // handle package downloads
this.downloadButtonView = new DownloadButtonView({
model: packageModel,
view: "actionsView",
@@ -1135,15 +1185,21 @@ define([
this.subviews.push(tableView);
- // Trigger a custom event in this view that indicates the package table has been rendered
+ // Trigger a custom event in this view that indicates the package table
+ // has been rendered
this.trigger("dataPackageRendered");
},
+ /**
+ * Inserts parent package links into the view for nested packages.
+ * @param {PackageModel} packageModel - The package model containing the
+ * parent package metadata.
+ */
insertParentLink(packageModel) {
const parentPackageMetadata = packageModel.get("parentPackageMetadata");
const view = this;
- _.each(parentPackageMetadata, (m, i) => {
+ _.each(parentPackageMetadata, (m, _i) => {
const title = m.get("title");
const icon = $(document.createElement("i")).addClass(
"icon icon-on-left icon-level-up",
@@ -1161,45 +1217,51 @@ define([
});
},
+ /**
+ * Shows a map with the bounding coordinates of the spatial coverage of
+ * the dataset.
+ * @param {Array} [customCoordinates] - An array of custom coordinates to
+ * use for the map in the order of [north, south, east, west].
+ * @returns {boolean} Returns false if the map could not be inserted.
+ */
insertSpatialCoverageMap(customCoordinates) {
- // Find the geographic region container. Older versions of Metacat (v2.4.3 and less) will not have it classified so look for the header text
- if (!this.$(".geographicCoverage").length) {
+ let geoCoverEls = this.$(".geographicCoverage");
+ let parseText = false;
+ let directions = ["north", "south", "east", "west"];
+ let [n, s, e, w] = customCoordinates || [];
+
+ // Find the geographic region container. Older versions of Metacat
+ // (v2.4.3 and less) will not have it classified so look for the header
+ // text
+ if (!geoCoverEls.length) {
// For EML
let title = this.$('h4:contains("Geographic Region")');
// For FGDC
- if (title.length == 0) {
+ if (title.length === 0) {
title = this.$('label:contains("Bounding Coordinates")');
}
- var georegionEls = $(title).parent();
- var parseText = true;
- var directions = new Array("North", "South", "East", "West");
- } else {
- var georegionEls = this.$(".geographicCoverage");
- var directions = new Array("north", "south", "east", "west");
+ geoCoverEls = $(title).parent();
+ parseText = true;
+ directions = ["North", "South", "East", "West"];
}
- for (let i = 0; i < georegionEls.length; i++) {
- var georegion = georegionEls[i];
+ for (let i = 0; i < geoCoverEls.length; i += 1) {
+ const georegion = geoCoverEls[i];
- if (typeof customCoordinates !== "undefined") {
- // Extract the coordinates
- var n = customCoordinates[0];
- var s = customCoordinates[1];
- var e = customCoordinates[2];
- var w = customCoordinates[3];
- } else {
- var coordinates = new Array();
+ if (!customCoordinates?.length) {
+ const coordinates = [];
_.each(directions, (direction) => {
// Parse text for older versions of Metacat (v2.4.3 and earlier)
+ let coordinate = "";
if (parseText) {
const labelEl = $(georegion).find(
`label:contains("${direction}")`,
);
if (labelEl.length) {
- var coordinate = $(labelEl).next().html();
+ coordinate = $(labelEl).next().html();
if (
typeof coordinate !== "undefined" &&
coordinate.indexOf(" ") > -1
@@ -1210,7 +1272,7 @@ define([
);
}
} else {
- var coordinate = $(georegion)
+ coordinate = $(georegion)
.find(`.${direction}BoundingCoordinate`)
.attr("data-value");
}
@@ -1220,10 +1282,7 @@ define([
});
// Extract the coordinates
- var n = coordinates[0];
- var s = coordinates[1];
- var e = coordinates[2];
- var w = coordinates[3];
+ [n, s, e, w] = coordinates;
}
// Create Google Map LatLng objects out of our coordinates
@@ -1238,7 +1297,7 @@ define([
// If there isn't a center point found, don't draw the map.
if (typeof latLngCEN === "undefined") {
- return;
+ return false;
}
// Get the map path color
@@ -1267,15 +1326,15 @@ define([
`&key=${MetacatUI.mapKey}'/>`;
// Find the spot in the DOM to insert our map image
- if (parseText)
- var insertAfter = $(georegion)
+ let insertAfter = georegion;
+ if (parseText) {
+ insertAfter = $(georegion)
.find('label:contains("West")')
.parent()
.parent().length
? $(georegion).find('label:contains("West")').parent().parent()
: georegion;
- // The last coordinate listed
- else var insertAfter = georegion;
+ }
// Get the URL to the interactive Google Maps instance
const url = this.getGoogleMapsUrl(latLngCEN, bounds);
@@ -1323,8 +1382,8 @@ define([
},
/**
- * Returns the zoom level that will display the given bounding box at
- * the given dimensions.
+ * Returns the zoom level that will display the given bounding box at the
+ * given dimensions.
* @param {LatLngBounds} bounds - The bounding box to display.
* @param {object} mapDim - The dimensions of the map.
* @param {number} mapDim.height - The height of the map.
@@ -1339,8 +1398,9 @@ define([
// useful
/**
- *
- * @param lat
+ * Converts a latitude to radians.
+ * @param {number} lat - The latitude to convert.
+ * @returns {number} The latitude in radians.
*/
function latRad(lat) {
const sin = Math.sin((lat * Math.PI) / 180);
@@ -1349,10 +1409,12 @@ define([
}
/**
- *
- * @param mapPx
- * @param worldPx
- * @param fraction
+ * Returns the zoom level that will display the given bounding box at
+ * the given dimensions.
+ * @param {number} mapPx - The dimensions of the map.
+ * @param {number} worldPx - The dimensions of the world.
+ * @param {number} fraction - The fraction of the world to display.
+ * @returns {number} The zoom level.
*/
function zoom(mapPx, worldPx, fraction) {
return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2);
@@ -1372,13 +1434,15 @@ define([
return Math.min(latZoom, lngZoom, ZOOM_MAX);
},
+ /** Insert the citation header into the view */
insertCitation() {
- if (!this.model) return false;
+ if (!this.model) return;
// Create a citation header element from the model attributes
const header = new CitationHeaderView({ model: this.model });
this.$(this.citationContainer).html(header.render().el);
},
+ /** Show a logo for the repository that hosts this metadata */
insertDataSource() {
if (
!this.model ||
@@ -1403,7 +1467,7 @@ define([
// Construct a URL to the profile of this repository
const profileURL =
- dataSource.identifier == MetacatUI.appModel.get("nodeId")
+ dataSource.identifier === MetacatUI.appModel.get("nodeId")
? `${MetacatUI.root}/profile`
: `${MetacatUI.appModel.get("dataoneSearchUrl")}/portals/${
dataSource.shortIdentifier
@@ -1448,18 +1512,18 @@ define([
},
animation: false,
})
- .on("mouseenter", function () {
- const _this = this;
+ .on("mouseenter", () => {
+ const el = this;
$(this).popover("show");
$(".popover").on("mouseleave", () => {
- $(_this).popover("hide");
+ $(el).popover("hide");
});
})
- .on("mouseleave", function () {
- const _this = this;
+ .on("mouseleave", () => {
+ const el = this;
setTimeout(() => {
if (!$(".popover:hover").length) {
- $(_this).popover("hide");
+ $(el).popover("hide");
}
}, 300);
});
@@ -1467,9 +1531,9 @@ define([
},
/**
- * Check whether the user has write permissions on the resource map and the EML.
- * Once the permission checks have finished, continue with the functions that
- * depend on them.
+ * Check whether the user has write permissions on the resource map and
+ * the EML. Once the permission checks have finished, continue with the
+ * functions that depend on them.
*/
checkWritePermissions() {
const view = this;
@@ -1480,20 +1544,20 @@ define([
const modelsToCheck = [this.model, resourceMap];
modelsToCheck.forEach((model, index) => {
- // If there is no resource map or no EML,
- // then the user does not need permission to edit it.
- if (!model || model.get("notFound") == true) {
+ // If there is no resource map or no EML, then the user does not need
+ // permission to edit it.
+ if (!model || model.get("notFound") === true) {
authorization[index] = true;
- // If we already checked, and the user is authorized,
- // record that information in the authorzation array.
+ // If we already checked, and the user is authorized, record that
+ // information in the authorzation array.
} else if (model.get("isAuthorized_write") === true) {
authorization[index] = true;
- // If we already checked, and the user is not authorized,
- // record that information in the authorzation array.
+ // If we already checked, and the user is not authorized, record
+ // that information in the authorzation array.
} else if (model.get("isAuthorized_write") === false) {
authorization[index] = false;
- // If we haven't checked for authorization yet, do that now.
- // Return to this function once we've finished checking.
+ // If we haven't checked for authorization yet, do that now. Return
+ // to this function once we've finished checking.
} else {
view.stopListening(model, "change:isAuthorized_write");
view.listenToOnce(model, "change:isAuthorized_write", () => {
@@ -1509,11 +1573,13 @@ define([
// Check that all the models were tested for authorization
- // Every value in the auth array must be true for the user to have full permissions
+ // Every value in the auth array must be true for the user to have full
+ // permissions
const allTrue = _.every(authorization, (test) => test);
- // When we have completed checking each of the models that we need to check for
- // permissions, every value in the authorization array should be "true" or "false",
- // and the array should have the same length as the modelsToCheck array.
+ // When we have completed checking each of the models that we need to
+ // check for permissions, every value in the authorization array should
+ // be "true" or "false", and the array should have the same length as
+ // the modelsToCheck array.
const allBoolean = _.every(
authorization,
(test) => typeof test === "boolean",
@@ -1521,22 +1587,25 @@ define([
const allChecked =
allBoolean && authorization.length === modelsToCheck.length;
- // Check for and render prov diagrams now that we know whether or not the user has editor permissions
- // (There is a different version of the chart for users who can edit the resource map and users who cannot)
+ // Check for and render prov diagrams now that we know whether or not
+ // the user has editor permissions (There is a different version of the
+ // chart for users who can edit the resource map and users who cannot)
if (allChecked) {
this.checkForProv();
} else {
return;
}
- // Only render the editor controls if we have completed the checks AND the user has full editor permissions
+ // Only render the editor controls if we have completed the checks AND
+ // the user has full editor permissions
if (allTrue) {
this.insertEditorControls();
}
},
/*
- * Inserts control elements onto the page for the user to interact with the dataset - edit, publish, etc.
- * Editor permissions should already have been checked before running this function.
+ * Inserts control elements onto the page for the user to interact with
+ * the dataset - edit, publish, etc. Editor permissions should already
+ * have been checked before running this function.
*/
insertEditorControls() {
const view = this;
@@ -1545,14 +1614,16 @@ define([
: null;
const modelsToCheck = [this.model, resourceMap];
const authorized = _.every(modelsToCheck, (model) =>
- // If there is no EML or no resource map, the user doesn't need permission to edit it.
- !model || model.get("notFound") == true
+ // If there is no EML or no resource map, the user doesn't need
+ // permission to edit it.
+ !model || model.get("notFound") === true
? true
: model.get("isAuthorized_write") === true,
);
// Only run this function when the user has full editor permissions
- // (i.e. write permission on the EML, and write permission on the resource map if there is one.)
+ // (i.e. write permission on the EML, and write permission on the
+ // resource map if there is one.)
if (!authorized) {
return;
}
@@ -1562,7 +1633,7 @@ define([
this.model.get("obsoletedBy").length > 0) ||
this.model.get("archived")
) {
- return false;
+ return;
}
// Save the element that will contain the owner control buttons
@@ -1590,7 +1661,8 @@ define([
}),
);
}
- // If this format is not editable, insert an unspported Edit Metadata template
+ // If this format is not editable, insert an unspported Edit Metadata
+ // template
else {
container.append(
this.editMetadataTemplate({
@@ -1600,47 +1672,45 @@ define([
}
}
- 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
- let canBePublished =
- MetacatUI.appModel.get("enablePublishDOI") && !view.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
- const 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.
+ // Determine if this metadata can be published. The Publish feature has
+ // to be enabled in the app. The model cannot already have a DOI
+ let canBePublished =
+ MetacatUI.appModel.get("enablePublishDOI") && !view.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
+ const 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 (
- Array.isArray(authorizedPublishers) &&
- authorizedPublishers.length
+ MetacatUI.appUserModel.hasIdentityOverlap(authorizedPublishers)
) {
- if (
- MetacatUI.appUserModel.hasIdentityOverlap(authorizedPublishers)
- ) {
- canBePublished = true;
- } else {
- canBePublished = false;
- }
+ 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(
- view.doiTemplate({
- isAuthorized: true,
- identifier: pid,
- }),
- );
- }
- } catch (e) {
- console.error("Cannot display the publish button: ", e);
+ // If this metadata can be published, then insert the Publish button
+ // template
+ if (canBePublished) {
+ // Insert a Publish button template
+ container.append(
+ view.doiTemplate({
+ isAuthorized: true,
+ identifier: pid,
+ }),
+ );
}
},
@@ -1650,12 +1720,11 @@ define([
* View Service in that it depends on elements with the class "copy" being
* contained in the HTML returned from the View Service.
*
- * To add more copiable buttons (or other elements) to a View Service XSLT,
- * you should be able to just add something like:
+ * To add more copiable buttons (or other elements) to a View Service
+ * XSLT, you should be able to just add something like:
*
*
+ * Copy
*
* to your XSLT and this should pick it up automatically.
*/
@@ -1675,8 +1744,8 @@ define([
);
// 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
+ // it didn't look flexible enough to allow me update innerHTML in a
+ // chain
setTimeout(() => {
$(el).html("Copy");
}, 500);
@@ -1689,21 +1758,18 @@ define([
* - A "Copy Citation" button to copy the citation text
*/
insertControls() {
- // Convert the support mdq formatId list to a version
- // that JS regex likes (with special characters double
- RegExp.escape = function (s) {
- return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\\\$&");
- };
+ // Convert the support mdq formatId list to a version that JS regex
+ // likes (with special characters double
+ RegExp.escape = (s) => s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\\\$&");
const mdqFormatIds = MetacatUI.appModel.get("mdqFormatIds");
- // Check of the current formatId is supported by the current
- // metadata quality suite. If not, the 'Assessment Report' button
- // will not be displacyed in the metadata controls panel.
+ // Check of the current formatId is supported by the current metadata
+ // quality suite. If not, the 'Assessment Report' button will not be
+ // displacyed in the metadata controls panel.
const thisFormatId = this.model.get("formatId");
- const mdqFormatSupported = false;
let formatFound = false;
if (mdqFormatIds !== null) {
- for (let ifmt = 0; ifmt < mdqFormatIds.length; ++ifmt) {
+ for (let ifmt = 0; ifmt < mdqFormatIds.length; ifmt += 1) {
const currentFormatId = RegExp.escape(mdqFormatIds[ifmt]);
const re = new RegExp(currentFormatId);
formatFound = re.test(thisFormatId);
@@ -1760,7 +1826,8 @@ define([
},
/**
- *Creates a button which the user can click to launch the package in Whole Tale
+ *Creates a button which the user can click to launch the package in Whole
+ *Tale
*/
createWholeTaleButton() {
const self = this;
@@ -1783,24 +1850,24 @@ define([
});
},
- // Inserting the Metric Stats
+ /** Insert the Metric Stats */
insertMetricsControls() {
// Exit if metrics shouldn't be shown for this dataset
if (this.model.hideMetrics()) {
return;
}
- const pid_list = [];
- pid_list.push(this.pid);
+ const pidList = [];
+ pidList.push(this.pid);
const metricsModel = new MetricsModel({
- pid_list,
+ pid_list: pidList,
type: "dataset",
});
metricsModel.fetch();
this.metricsModel = metricsModel;
- // Retreive the model from the server for the given PID
- // TODO: Create a Metric Request Object
+ // Retreive the model from the server for the given PID TODO: Create a
+ // Metric Request Object
if (MetacatUI.appModel.get("displayDatasetMetrics")) {
const buttonToolbar = this.$(".metrics-container");
@@ -1824,26 +1891,22 @@ define([
buttonToolbar.append(citationsMetricView.render().el);
this.subviews.push(citationsMetricView);
- try {
- // Check if the registerCitation=true query string is set
- if (window.location.search) {
- if (
- window.location.search.indexOf("registerCitation=true") > -1
- ) {
- // Open the modal for the citations
- citationsMetricView.showMetricModal();
-
- // Show the register citation form
- if (citationsMetricView.modalView) {
- citationsMetricView.modalView.on(
- "renderComplete",
- citationsMetricView.modalView.showCitationForm,
- );
- }
+ // Check if the registerCitation=true query string is set
+ if (window.location.search) {
+ if (
+ window.location.search.indexOf("registerCitation=true") > -1
+ ) {
+ // Open the modal for the citations
+ citationsMetricView.showMetricModal();
+
+ // Show the register citation form
+ if (citationsMetricView.modalView) {
+ citationsMetricView.modalView.on(
+ "renderComplete",
+ citationsMetricView.modalView.showCitationForm,
+ );
}
}
- } catch (e) {
- console.warn("Not able to show the register citation form ", e);
}
}
@@ -1861,22 +1924,22 @@ define([
/**
* Check if the DataPackage provenance parsing has completed. If it has,
- * draw provenance charts. If it hasn't start the parseProv function.
- * The view must have the DataPackage collection set as view.dataPackage
- * for this function to run.
+ * draw provenance charts. If it hasn't start the parseProv function. The
+ * view must have the DataPackage collection set as view.dataPackage for
+ * this function to run.
*/
checkForProv() {
if (!this.dataPackage) {
return;
}
- // Render the provenance trace using the redrawProvCharts function instead of the drawProvCharts function
- // just in case the prov charts have already been inserted. Redraw will make sure they are removed
+ // Render the provenance trace using the redrawProvCharts function
+ // instead of the drawProvCharts function just in case the prov charts
+ // have already been inserted. Redraw will make sure they are removed
// before being re-inserted.
- const { model } = this;
- if (this.dataPackage.provenanceFlag == "complete") {
+ if (this.dataPackage.provenanceFlag === "complete") {
this.redrawProvCharts(this.dataPackage);
} else {
- this.listenToOnce(this.dataPackage, "queryComplete", function () {
+ this.listenToOnce(this.dataPackage, "queryComplete", () => {
this.redrawProvCharts(this.dataPackage);
});
// parseProv triggers "queryComplete"
@@ -1884,21 +1947,28 @@ define([
}
},
- /*
- * Renders ProvChartViews on the page to display provenance on a package level and on an individual object level.
- * This function looks at four sources for the provenance - the package sources, the package derivations, member sources, and member derivations
+ /**
+ * Renders ProvChartViews on the page to display provenance on a package
+ * level and on an individual object level. This function looks at four
+ * sources for the provenance - the package sources, the package
+ * derivations, member sources, and member derivations
+ * @param {DataPackage} collection - The DataPackage collection to render
+ * provenance for
*/
- drawProvCharts(dataPackage) {
+ drawProvCharts(collection) {
+ const dataPackage = collection || this.dataPackage;
+ const view = this;
// Set a listener to re-draw the prov charts when needed
- this.stopListening(this.dataPackage, "redrawProvCharts");
+ this.stopListening(dataPackage, "redrawProvCharts");
this.listenToOnce(
- this.dataPackage,
+ dataPackage,
"redrawProvCharts",
this.redrawProvCharts,
);
- // Provenance has to be retrieved from the Package Model (getProvTrace()) before the charts can be drawn
- if (dataPackage.provenanceFlag != "complete") return false;
+ // Provenance has to be retrieved from the Package Model
+ // (getProvTrace()) before the charts can be drawn
+ if (dataPackage.provenanceFlag !== "complete") return;
// If the user is authorized to edit the provenance for this package
// then turn on editing, so that edit icons are displayed.
@@ -1911,11 +1981,12 @@ define([
}
// If none of the models in this package have the formatId attributes,
- // we should fetch the DataPackage since it likely has only had a shallow fetch so far
+ // we should fetch the DataPackage since it likely has only had a
+ // shallow fetch so far
const formats = _.compact(dataPackage.pluck("formatId"));
- // If the number of formatIds is less than the number of models in this collection,
- // then we need to get them.
+ // If the number of formatIds is less than the number of models in this
+ // collection, then we need to get them.
if (formats.length < dataPackage.length) {
let modelsToMerge = [];
@@ -1924,7 +1995,7 @@ define([
// Get the PackageModel for this DataPackage
const packageModel = _.find(
this.packageModels,
- (packageModel) => packageModel.get("id") == dataPackage.id,
+ (model) => model.get("id") === dataPackage.id,
);
// Merge the SolrResult models into the DataONEObject models
@@ -1933,18 +2004,21 @@ define([
}
}
- // If there is at least one model to merge into this data package, do so
+ // If there is at least one model to merge into this data package, do
+ // so
if (modelsToMerge.length) {
dataPackage.mergeModels(modelsToMerge);
}
// If there are no models to merge in, get them from the index
else {
- // Listen to the DataPackage fetch to complete and re-execute this function
- this.listenToOnce(dataPackage, "complete", function () {
+ // Listen to the DataPackage fetch to complete and re-execute this
+ // function
+ this.listenToOnce(dataPackage, "complete", () => {
this.drawProvCharts(dataPackage);
});
- // Create a query that searches for all the members of this DataPackage in Solr
+ // Create a query that searches for all the members of this
+ // DataPackage in Solr
dataPackage.solrResults.currentquery = `${dataPackage.filterModel.getQuery()}%20AND%20-formatType:METADATA`;
dataPackage.solrResults.fields = "id,seriesId,formatId,fileName";
dataPackage.solrResults.rows = dataPackage.length;
@@ -1956,13 +2030,13 @@ define([
// Fetch the data package with the "fromIndex" option
dataPackage.fetch({ fromIndex: true });
- // Exit this function since it will be executed again when the fetch is complete
+ // Exit this function since it will be executed again when the fetch
+ // is complete
return;
}
}
-
- var view = this;
- // Draw two flow charts to represent the sources and derivations at a package level
+ // Draw two flow charts to represent the sources and derivations at a
+ // package level
const packageSources = dataPackage.sourcePackages;
const packageDerivations = dataPackage.derivationPackages;
@@ -1994,12 +2068,13 @@ define([
dataPackage.derivations.length ||
editModeOn
) {
- // Draw the provenance charts for each member of this package at an object level
- _.each(dataPackage.toArray(), (member, i) => {
+ // Draw the provenance charts for each member of this package at an
+ // object level
+ _.each(dataPackage.toArray(), (member, _i) => {
// Don't draw prov charts for metadata objects.
if (
- member.get("type").toLowerCase() == "metadata" ||
- member.get("formatType").toLowerCase() == "metadata"
+ member.get("type").toLowerCase() === "metadata" ||
+ member.get("formatType").toLowerCase() === "metadata"
) {
return;
}
@@ -2011,13 +2086,13 @@ define([
}
// Retrieve the sources and derivations for this member
- const memberSources = member.get("provSources") || new Array();
- const memberDerivations =
- member.get("provDerivations") || new Array();
+ const memberSources = member.get("provSources") || [];
+ const memberDerivations = member.get("provDerivations") || [];
- // Make the source chart for this member.
- // If edit is on, then either a 'blank' sources ProvChart will be displayed if there
- // are no sources for this member, or edit icons will be displayed with prov icons.
+ // Make the source chart for this member. If edit is on, then either
+ // a 'blank' sources ProvChart will be displayed if there are no
+ // sources for this member, or edit icons will be displayed with
+ // prov icons.
if (memberSources.length || editModeOn) {
const memberSourcesProvChart = new ProvChart({
sources: memberSources,
@@ -2035,9 +2110,10 @@ define([
view.$(view.articleContainer).addClass("gutters");
}
- // Make the derivation chart for this member
- // If edit is on, then either a 'blank' derivations ProvChart will be displayed if there,
- // are no derivations for this member or edit icons will be displayed with prov icons.
+ // Make the derivation chart for this member If edit is on, then
+ // either a 'blank' derivations ProvChart will be displayed if
+ // there, are no derivations for this member or edit icons will be
+ // displayed with prov icons.
if (memberDerivations.length || editModeOn) {
const memberDerivationsProvChart = new ProvChart({
derivations: memberDerivations,
@@ -2061,10 +2137,9 @@ define([
if (this.$(".prov-chart").length > 10000) {
const allNodes = this.$(".prov-chart .node");
let ids = [];
- var view = this;
let i = 1;
- $(allNodes).each(function () {
+ $(allNodes).each(() => {
ids.push($(this).attr("data-id"));
});
ids = _.uniq(ids);
@@ -2075,12 +2150,13 @@ define([
.not(".editorNode");
// var matchingEntityDetails = view.findEntityDetailsContainer(id);
- // Don't use the unique class on images since they will look a lot different anyway by their image
+ // Don't use the unique class on images since they will look a lot
+ // different anyway by their image
if (!$(matchingNodes).first().hasClass("image")) {
const className = `uniqueNode${i}`;
// Add the unique class and up the iterator
- if (matchingNodes.prop("tagName") != "polygon")
+ if (matchingNodes.prop("tagName") !== "polygon")
$(matchingNodes).addClass(className);
else
$(matchingNodes).attr(
@@ -2096,40 +2172,41 @@ define([
id,
className,
});
- i++;
+ i += 1;
}
});
}
},
- /* Step through all prov charts and re-render each one that has been
- marked for re-rendering.
- */
+ /**
+ * Step through all prov charts and re-render each one that has been
+ * marked for re-rendering.
+ */
redrawProvCharts() {
const view = this;
// Check if prov edits are active and turn on the prov save bar if so.
// Alternatively, turn off save bar if there are no prov edits, which
- // could occur if a user undoes a previous which could result in
- // an empty edit list.
+ // could occur if a user undoes a previous which could result in an
+ // empty edit list.
if (this.dataPackage.provEditsPending()) {
this.showEditorControls();
} else {
this.hideEditorControls();
- // Reset the edited flag for each package member
- _.each(this.dataPackage.toArray(), (item) => {
- item.selectedInEditor == false;
+ this.dataPackage.toArray().forEach((item) => {
+ // eslint-disable-next-line no-param-reassign
+ item.selectedInEditor = false;
});
}
- _.each(this.subviews, (thisView, i) => {
+ _.each(this.subviews, (thisView, _i) => {
// Check if this is a ProvChartView
if (
thisView.className &&
thisView.className.indexOf("prov-chart") !== -1
) {
- // Check if this ProvChartView is marked for re-rendering
- // Erase the current ProvChartView
+ // Check if this ProvChartView is marked for re-rendering Erase the
+ // current ProvChartView
thisView.onClose();
}
});
@@ -2138,18 +2215,19 @@ define([
this.subviews = _.filter(
this.subviews,
(item) =>
- item.className && item.className.indexOf("prov-chart") == -1,
+ item.className && item.className.indexOf("prov-chart") === -1,
);
view.drawProvCharts(this.dataPackage);
},
- /*
+ /**
* When the data package collection saves successfully, tell the user
+ * @param {DataPackage} savedObject - The object that was saved
*/
saveSuccess(savedObject) {
// We only want to perform these actions after the package saves
- if (savedObject.type != "DataPackage") return;
+ if (savedObject.type !== "DataPackage") return;
// Change the URL to the new id
MetacatUI.uiRouter.navigate(
@@ -2170,7 +2248,8 @@ define([
// Reset the state to clean
this.dataPackage.packageModel.set("changed", false);
- // If provenance relationships were updated, then reset the edit list now.
+ // If provenance relationships were updated, then reset the edit list
+ // now.
if (this.dataPackage.provEdits.length) this.dataPackage.provEdits = [];
this.saveProvPending = false;
@@ -2180,19 +2259,21 @@ define([
// Turn off "save" footer
this.hideEditorControls();
- // Update the metadata table header with the new resource map id.
- // First find the DataPackageView for the top level package, and
- // then re-render it with the update resmap id.
+ // Update the metadata table header with the new resource map id. First
+ // find the DataPackageView for the top level package, and then
+ // re-render it with the update resmap id.
const view = this;
const metadataId = this.packageModels[0].getMetadata().get("id");
- _.each(this.subviews, (thisView, i) => {
+ _.each(this.subviews, (thisView, _i) => {
// Check if this is a ProvChartView
if (thisView.type && thisView.type.indexOf("DataPackage") !== -1) {
- if (thisView.currentlyViewing == metadataId) {
+ if (thisView.currentlyViewing === metadataId) {
const packageId = view.dataPackage.packageModel.get("id");
const title = packageId
? `Package: ${packageId}`
: "";
+
+ // eslint-disable-next-line no-param-reassign
thisView.title = `Files in this dataset ${title}`;
thisView.render();
}
@@ -2200,8 +2281,9 @@ define([
});
},
- /*
+ /**
* When the data package collection fails to save, tell the user
+ * @param {string} errorMsg - The error message to display
*/
saveError(errorMsg) {
const errorId = `error${Math.round(Math.random() * 100)}`;
@@ -2234,14 +2316,15 @@ define([
this.hideEditorControls();
},
- /* If provenance relationships have been modified by the provenance editor (in ProvChartView), then
- update the ORE Resource Map and save it to the server.
- */
+ /**
+ * If provenance relationships have been modified by the provenance editor
+ * (in ProvChartView), then update the ORE Resource Map and save it to the
+ * server.
+ */
saveProv() {
// Only call this function once per save operation.
if (this.saveProvPending) return;
- const view = this;
if (this.dataPackage.provEditsPending()) {
this.saveProvPending = true;
// If the Data Package failed saving, display an error message
@@ -2255,12 +2338,13 @@ define([
this.showSaving();
this.dataPackage.saveProv();
} else {
- // TODO: should a dialog be displayed saying that no prov edits were made?
+ // TODO: should a dialog be displayed saying that no prov edits were
+ // made?
}
},
+ /** Inactivate the save button during the save process */
showSaving() {
- // Change the style of the save button
this.$("#save-metadata-prov")
.html(' Saving...')
.addClass("btn-disabled");
@@ -2268,6 +2352,7 @@ define([
this.$("input, textarea, select, button").prop("disabled", true);
},
+ /** Activate the save button after the save process */
hideSaving() {
this.$("input, textarea, select, button").prop("disabled", false);
@@ -2275,35 +2360,43 @@ define([
this.$("#save-metadata-prov").html("Save").removeClass("btn-disabled");
},
+ /** Show the editor controls */
showEditorControls() {
this.$("#editor-footer").slideDown();
},
+ /** Hide the editor controls */
hideEditorControls() {
this.$("#editor-footer").slideUp();
},
+ /**
+ * Get the names of the entities in this package
+ * @param {Array} packageModels - An array of models in this package
+ */
getEntityNames(packageModels) {
const viewRef = this;
_.each(packageModels, (packageModel) => {
- // Don't get entity names for larger packages - users must put the names in the system metadata
+ // Don't get entity names for larger packages - users must put the
+ // names in the system metadata
if (packageModel.get("members").length > 100) return;
- // If this package has a different metadata doc than the one we are currently viewing
+ // If this package has a different metadata doc than the one we are
+ // currently viewing
const metadataModel = packageModel.getMetadata();
if (!metadataModel) return;
- if (metadataModel.get("id") != viewRef.pid) {
+ if (metadataModel.get("id") !== viewRef.pid) {
const requestSettings = {
url:
MetacatUI.appModel.get("viewServiceUrl") +
encodeURIComponent(metadataModel.get("id")),
- success(parsedMetadata, response, xhr) {
- _.each(packageModel.get("members"), (solrResult, i) => {
+ success(parsedMetadata, _response, _xhr) {
+ _.each(packageModel.get("members"), (solrResult, _i) => {
let entityName = "";
- if (solrResult.get("formatType") == "METADATA")
+ if (solrResult.get("formatType") === "METADATA")
entityName = solrResult.get("title");
const container = viewRef.findEntityDetailsContainer(
@@ -2338,14 +2431,14 @@ define([
return;
}
- _.each(packageModel.get("members"), (solrResult, i) => {
+ _.each(packageModel.get("members"), (solrResult, _i) => {
let entityName = "";
if (solrResult.get("fileName"))
entityName = solrResult.get("fileName");
- else if (solrResult.get("formatType") == "METADATA")
+ else if (solrResult.get("formatType") === "METADATA")
entityName = solrResult.get("title");
- else if (solrResult.get("formatType") == "RESOURCE") return;
+ else if (solrResult.get("formatType") === "RESOURCE") return;
else {
const container = viewRef.findEntityDetailsContainer(solrResult);
@@ -2360,6 +2453,12 @@ define([
});
},
+ /**
+ * Get the name of the entity in the given container element
+ * @param {Element} containerEl - The DOM element that contains the entity
+ * name
+ * @returns {string} - The name of the entity
+ */
getEntityName(containerEl) {
if (!containerEl) return false;
@@ -2377,19 +2476,26 @@ define([
return entityName;
},
- // Checks if the metadata has entity details sections
+ /**
+ * Checks if the metadata has entity details sections
+ * @returns {boolean} - True if the metadata has entity details sections
+ */
hasEntityDetails() {
return this.$(".entitydetails").length > 0;
},
/**
- * Finds the element in the rendered metadata that describes the given data entity.
- * @param {(DataONEObject|SolrResult|string)} model - Either a model that represents the data object or the identifier of the data object
- * @param {Element} [el] - The DOM element to exclusivly search inside.
- * @returns {Element} - The DOM element that describbbes the given data entity.
+ * Finds the element in the rendered metadata that describes the given
+ * data entity.
+ * @param {(DataONEObject|SolrResult|string)} model - Either a model that
+ * represents the data object or the identifier of the data object
+ * @param {Element} [containerEl] - The DOM element to exclusivly search
+ * inside.
+ * @returns {Element|null} - The DOM element that describes the given data
+ * entity or null if it cannot be found.
*/
- findEntityDetailsContainer(model, el) {
- if (!el) var { el } = this;
+ findEntityDetailsContainer(model, containerEl) {
+ const el = containerEl || this.el;
// Get the id and file name for this data object
let id = "";
@@ -2398,8 +2504,7 @@ define([
// If a model is given, get the id and file name from the object
if (
model &&
- (DataONEObject.prototype.isPrototypeOf(model) ||
- SolrResult.prototype.isPrototypeOf(model))
+ (model instanceof DataONEObject || model instanceof SolrResult)
) {
id = model.get("id");
fileName = model.get("fileName");
@@ -2410,7 +2515,7 @@ define([
}
// Otherwise, there isn't enough info to find the element, so exit
else {
- return;
+ return null;
}
// If we already found it earlier, return it now
@@ -2427,8 +2532,9 @@ define([
return container;
}
- // Are we looking for the main object that this MetadataView is displaying?
- if (id == this.pid) {
+ // Are we looking for the main object that this MetadataView is
+ // displaying?
+ if (id === this.pid) {
if (this.$("#Metadata").length > 0) return this.$("#Metadata");
return this.el;
}
@@ -2436,7 +2542,8 @@ define([
// Metacat 2.4.2 and up will have the Online Distribution Link marked
let link = this.$(`.entitydetails a[data-pid='${id}']`);
- // Otherwise, try looking for an anchor with the id matching this object's id
+ // Otherwise, try looking for an anchor with the id matching this
+ // object's id
if (!link.length)
link = $(el).find(`a#${id.replace(/[^A-Za-z0-9]/g, "\\$&")}`);
@@ -2460,12 +2567,14 @@ define([
container = $(link).parents(".entitydetails");
if (container.length < 1) {
- // backup - find the parent of this link that is a direct child of the form element
+ // backup - find the parent of this link that is a direct child of
+ // the form element
const firstLevelContainer = _.intersection(
$(link).parents("form").children(),
$(link).parents(),
);
- // Find the controls-well inside of that first level container, which is the well that contains info about this data object
+ // Find the controls-well inside of that first level container,
+ // which is the well that contains info about this data object
if (firstLevelContainer.length > 0)
container = $(firstLevelContainer).children(".controls-well");
@@ -2487,12 +2596,15 @@ define([
// ----Find by file name rather than id-----
if (!fileName) {
// Get the name of the object first
- for (var i = 0; i < this.packageModels.length; i++) {
- var model = _.findWhere(this.packageModels[i].get("members"), {
- id,
- });
- if (model) {
- fileName = model.get("fileName");
+ for (let i = 0; i < this.packageModels.length; i += 1) {
+ const packageModel = _.findWhere(
+ this.packageModels[i].get("members"),
+ {
+ id,
+ },
+ );
+ if (packageModel) {
+ fileName = packageModel.get("fileName");
break;
}
}
@@ -2505,13 +2617,14 @@ define([
`.entitydetails .control-label:contains('Entity Name') + .controls-well:contains('${fileName}')`,
];
- // Search through each possible location in the DOM where the file name might be
- for (var i = 0; i < possibleLocations.length; i++) {
+ // Search through each possible location in the DOM where the file
+ // name might be
+ for (let i = 0; i < possibleLocations.length; i += 1) {
// Get the elements in this view that match the possible location
const matches = this.$(possibleLocations[i]);
// If exactly one match is found
- if (matches.length == 1) {
+ if (matches.length === 1) {
// Get the entity details parent element
container = $(matches).parents(".entitydetails").first();
// Set the object ID on the element for easier locating later
@@ -2528,15 +2641,15 @@ define([
}
}
- // --- The last option:----
- // If this package has only one item, we can assume the only entity details are about that item
+ // --- The last option:---- If this package has only one item, we can
+ // assume the only entity details are about that item
const members = this.packageModels[0].get("members");
const dataMembers = _.filter(
members,
- (m) => m.get("formatType") == "DATA",
+ (m) => m.get("formatType") === "DATA",
);
- if (dataMembers.length == 1) {
- if (this.$(".entitydetails").length == 1) {
+ if (dataMembers.length === 1) {
+ if (this.$(".entitydetails").length === 1) {
this.$(".entitydetails").attr("data-id", id);
// Store the PID on this element for moreInfo icons
this.storeEntityPIDs(this.$(".entitydetails"), id);
@@ -2549,7 +2662,8 @@ define([
},
/*
- * Inserts new image elements into the DOM via the image template. Use for displaying images that are part of this metadata's resource map.
+ * Inserts new image elements into the DOM via the image template. Use for
+ * displaying images that are part of this metadata's resource map.
*/
insertDataDetails() {
// If there is a metadataIndex subview, render from there.
@@ -2565,32 +2679,32 @@ define([
const viewRef = this;
- _.each(this.packageModels, (packageModel) => {
- const dataDisplay = "";
+ this.packageModels.forEach((packageModel) => {
const images = [];
- const other = [];
const packageMembers = packageModel.get("members");
// Don't do this for large packages
if (packageMembers.length > 150) return;
- //= === Loop over each visual object and create a dataDisplay template for it to attach to the DOM ====
- _.each(packageMembers, (solrResult, i) => {
+ //= === Loop over each visual object and create a dataDisplay template
+ // for it to attach to the DOM ====
+ _.each(packageMembers, (solrResult, _i) => {
// Don't display any info about nested packages
- if (solrResult.type == "Package") return;
+ if (solrResult.type === "Package") return;
const objID = solrResult.get("id");
- if (objID == viewRef.pid) return;
+ if (objID === viewRef.pid) return;
// Is this a visual object (image)?
const type =
- solrResult.type == "SolrResult"
+ solrResult.type === "SolrResult"
? solrResult.getType()
: "Data set";
- if (type == "image") images.push(solrResult);
+ if (type === "image") images.push(solrResult);
- // Find the part of the HTML Metadata view that describes this data object
+ // Find the part of the HTML Metadata view that describes this data
+ // object
const anchor = $(document.createElement("a")).attr(
"id",
objID.replace(/[^A-Za-z0-9]/g, "-"),
@@ -2602,10 +2716,11 @@ define([
});
downloadButton.render();
- // Insert the data display HTML and the anchor tag to mark this spot on the page
+ // Insert the data display HTML and the anchor tag to mark this spot
+ // on the page
if (container) {
// Only show data displays for images hosted on the same origin
- if (type == "image") {
+ if (type === "image") {
// Create the data display HTML
const dataDisplay = $.parseHTML(
viewRef
@@ -2622,13 +2737,14 @@ define([
$(container).children("label").first().after(dataDisplay);
else $(container).prepend(dataDisplay);
- // If this image is private, we need to load it via an XHR request
+ // If this image is private, we need to load it via an XHR
+ // request
if (!solrResult.get("isPublic")) {
// Create an XHR
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
- xhr.onload = function () {
+ xhr.onload = () => {
if (xhr.response)
$(dataDisplay)
.find("img")
@@ -2657,8 +2773,9 @@ define([
}
});
- //= === Initialize the fancybox images =====
- // We will be checking every half-second if all the HTML has been loaded into the DOM - once they are all loaded, we can initialize the lightbox functionality.
+ //= === Initialize the fancybox images ===== We will be checking every
+ // half-second if all the HTML has been loaded into the DOM - once
+ // they are all loaded, we can initialize the lightbox functionality.
const numImages = images.length;
// The shared lightbox options for both images
const lightboxOptions = {
@@ -2669,7 +2786,8 @@ define([
aspectRatio: true,
closeClick: true,
afterLoad() {
- // Create a custom HTML caption based on data stored in the DOM element
+ // Create a custom HTML caption based on data stored in the DOM
+ // element
viewRef.title = `${viewRef.title} Download `;
},
helpers: {
@@ -2689,17 +2807,18 @@ define([
imgLightboxOptions.type = "image";
imgLightboxOptions.perload = 1;
- const initializeImgLightboxes = function () {
- numImgChecks++;
+ const initializeImgLightboxes = () => {
+ numImgChecks += 1;
// Initialize what images have loaded so far after 5 seconds
- if (numImgChecks == 10) {
+ if (numImgChecks === 10) {
$(lightboxImgSelector).fancybox(imgLightboxOptions);
}
- // When 15 seconds have passed, stop checking so we don't blow up the browser
+ // When 15 seconds have passed, stop checking so we don't blow up
+ // the browser
else if (numImgChecks > 30) {
$(lightboxImgSelector).fancybox(imgLightboxOptions);
- window.clearInterval(imgIntervalID);
+ window.clearInterval(viewRef.imgIntervalID);
return;
}
@@ -2710,10 +2829,10 @@ define([
$(lightboxImgSelector).fancybox(imgLightboxOptions);
// We're done - clear the interval
- window.clearInterval(imgIntervalID);
+ window.clearInterval(viewRef.imgIntervalID);
};
- var imgIntervalID = window.setInterval(
+ viewRef.imgIntervalID = window.setInterval(
initializeImgLightboxes,
500,
);
@@ -2721,9 +2840,8 @@ define([
});
},
+ /** Remove ecogrid links and replace them with workable links */
replaceEcoGridLinks() {
- const viewRef = this;
-
// Find the element in the DOM housing the ecogrid link
$("a:contains('ecogrid://')").each((i, thisLink) => {
// Get the link text
@@ -2744,17 +2862,22 @@ define([
});
},
+ /**
+ * Publish the data package with a DOI
+ * @param {Event} event - The click event
+ */
publish(event) {
// target may not actually prevent click events, so double check
const disabled = $(event.target).closest("a").attr("disabled");
if (disabled) {
- return false;
+ return;
}
const publishServiceUrl = MetacatUI.appModel.get("publishServiceUrl");
const pid = $(event.target).closest("a").attr("pid");
+ // eslint-disable-next-line no-restricted-globals, no-alert
const ret = confirm(
`Are you sure you want to publish ${pid} with a DOI?`,
- );
+ ); // TODO: We should use a custom modal here instead of the browser's confirm dialog
if (ret) {
// show the loading icon
@@ -2769,7 +2892,7 @@ define([
xhrFields: {
withCredentials: true,
},
- success(data, textStatus, xhr) {
+ success(data, _textStatus, _xhr) {
// the response should have new identifier in it
identifier = $(data).find("d1\\:identifier, identifier").text();
@@ -2798,7 +2921,7 @@ define([
}, 3000);
}
},
- error(xhr, textStatus, errorThrown) {
+ error(xhr, _textStatus, _errorThrown) {
// show the error message, but stay on the same page
const msg = `Publish failed: ${$(xhr.responseText)
.find("description")
@@ -2818,7 +2941,12 @@ define([
}
},
- // When the given ID from the URL is a resource map that has no metadata, do the following...
+ /**
+ * When the given ID from the URL is a resource map that has no metadata,
+ * render metadata from the index instead.
+ * @param {SolrResult} solrResultModel - The SolrResult model for the
+ * resource map
+ */
noMetadata(solrResultModel) {
this.hideLoading();
this.$el.html(this.template());
@@ -2844,9 +2972,10 @@ define([
);
},
- // this will lookup the latest version of the PID
+ /** Lokup the latest version of the PID and display a link to it */
showLatestVersion() {
- // If this metadata doc is not obsoleted by a new version, then exit the function
+ // If this metadata doc is not obsoleted by a new version, then exit the
+ // function
if (!this.model.get("obsoletedBy")) {
return;
}
@@ -2856,7 +2985,7 @@ define([
// When the latest version is found,
this.listenTo(this.model, "change:newestVersion", () => {
// Make sure it has a newer version, and if so,
- if (view.model.get("newestVersion") != view.model.get("id")) {
+ if (view.model.get("newestVersion") !== view.model.get("id")) {
// Put a link to the newest version in the content
view.$(".newer-version").replaceWith(
view.versionTemplate({
@@ -2879,6 +3008,10 @@ define([
this.model.findLatestVersion();
},
+ /**
+ * Indicate that the metadata is being loaded
+ * @param {string} message - The message to display while loading
+ */
showLoading(message) {
this.hideLoading();
@@ -2893,11 +3026,16 @@ define([
this.$el.html(loading);
},
+ /** Hide the loading message */
hideLoading() {
if (this.$loading) this.$loading.remove();
if (this.$detached) this.$el.html(this.$detached);
},
+ /**
+ * Show an error message to the user
+ * @param {string} msg - The error message to display
+ */
showError(msg) {
// Remove any existing error messages
this.$el.children(".alert-container").remove();
@@ -2913,10 +3051,13 @@ define([
},
/**
- * When the "Metadata" button in the table is clicked while we are on the Metadata view,
- * we want to scroll to the anchor tag of this data object within the page instead of navigating
- * to the metadata page again, which refreshes the page and re-renders (more loading time)
- * @param e
+ * When the "Metadata" button in the table is clicked while we are on the
+ * Metadata view, we want to scroll to the anchor tag of this data object
+ * within the page instead of navigating to the metadata page again, which
+ * refreshes the page and re-renders (more loading time)
+ * @param {Event} e - The click event
+ * @returns {boolean} - Returns false if the click event should not be
+ * followed
*/
previewData(e) {
// Don't go anywhere yet...
@@ -2926,10 +3067,12 @@ define([
let link = $(e.target);
if (!$(link).hasClass("preview")) link = $(link).parents("a.preview");
- if (link) {
- var id = $(link).attr("data-id");
- if (typeof id === "undefined" || !id) return false; // This will make the app defualt to the child view previewData function
- } else return false;
+ if (!link?.length) return false;
+
+ const id = $(link).attr("data-id");
+
+ // This will make the app defualt to the child view previewData function
+ if (!id) return false;
// If we are on the Metadata view, update the URL and scroll to the
// anchor
@@ -2968,27 +3111,32 @@ define([
/**
* Navigate to a new /view URL with a fragment
*
- * Used in getModel() when the pid originally passed into MetadataView
- * is not a metadata PID but is, instead, a data PID. getModel() does
- * the work of finding an appropriate metadata PID for the data PID and
- * this method handles re-routing to the correct URL.
- * @param {string} metadata_pid - The new metadata PID
- * @param {string} data_pid - Optional. A data PID that's part of the
- * package metadata_pid exists within.
+ * Used in getModel() when the pid originally passed into MetadataView is
+ * not a metadata PID but is, instead, a data PID. getModel() does the
+ * work of finding an appropriate metadata PID for the data PID and this
+ * method handles re-routing to the correct URL.
+ * @param {string} metadataPid - The new metadata PID
+ * @param {string} dataPid - Optional. A data PID that's part of the
+ * package metadataPid exists within.
*/
- navigateWithFragment(metadata_pid, data_pid) {
- let next_route = `view/${encodeURIComponent(metadata_pid)}`;
+ navigateWithFragment(metadataPid, dataPid) {
+ let nextRoute = `view/${encodeURIComponent(metadataPid)}`;
- if (typeof data_pid === "string" && data_pid.length > 0) {
- next_route += `#${encodeURIComponent(data_pid)}`;
+ if (typeof dataPid === "string" && dataPid.length > 0) {
+ nextRoute += `#${encodeURIComponent(dataPid)}`;
}
- MetacatUI.uiRouter.navigate(next_route, { trigger: true });
+ MetacatUI.uiRouter.navigate(nextRoute, { trigger: true });
},
+ /**
+ * Close any active popovers when the user clicks outside of them
+ * @param {Event} e - The click event
+ */
closePopovers(e) {
- // If this is a popover element or an element that has a popover, don't close anything.
- // Check with the .classList attribute to account for SVG elements
+ // If this is a popover element or an element that has a popover, don't
+ // close anything. Check with the .classList attribute to account for
+ // SVG elements
const svg = $(e.target).parents("svg");
if (
@@ -3004,6 +3152,11 @@ define([
this.$(".popover-this.active").popover("hide");
},
+ /**
+ * When the user clicks on a node in the provenance chart, highlight the
+ * node and its metadata section
+ * @param {Event} e - The click event
+ */
highlightNode(e) {
// Find the id
let id = $(e.target).attr("data-id");
@@ -3012,13 +3165,13 @@ define([
id = $(e.target).parents("[data-id]").attr("data-id");
// If there is no id, return
- if (typeof id === "undefined") return false;
+ if (typeof id === "undefined") return;
// Highlight its node
$(`.prov-chart .node[data-id='${id}']`).toggleClass("active");
// Highlight its metadata section
- if (MetacatUI.appModel.get("pid") == id)
+ if (MetacatUI.appModel.get("pid") === id)
this.$("#Metadata").toggleClass("active");
else {
const entityDetails = this.findEntityDetailsContainer(id);
@@ -3026,16 +3179,15 @@ define([
}
},
+ /** Actions to perform when the view is closed */
onClose() {
- const viewRef = this;
-
this.stopListening();
_.each(this.subviews, (subview) => {
if (subview.onClose) subview.onClose();
});
- this.packageModels = new Array();
+ this.packageModels = [];
this.model.set(this.model.defaults);
this.pid = null;
this.dataPackage = null;
@@ -3056,6 +3208,7 @@ define([
* Generate a string appropriate to go into the author/creator portion of
* a dataset citation from the value stored in the underlying model's
* origin field.
+ * @returns {string} - A string of author names, formatted for citation
*/
getAuthorText() {
const authors = this.model.get("origin");
@@ -3063,9 +3216,9 @@ define([
let authorText = "";
_.each(authors, (author) => {
- count++;
+ count += 1;
- if (count == 6) {
+ if (count === 6) {
authorText += ", et al. ";
return;
}
@@ -3078,7 +3231,7 @@ define([
authorText += ",";
}
- if (count == authors.length) {
+ if (count === authors.length) {
authorText += " and";
}
@@ -3097,6 +3250,8 @@ define([
* Generate a string appropriate to be used in the publisher portion of a
* dataset citation. This method falls back to the node ID when the proper
* node name cannot be fetched from the app's NodeModel instance.
+ * @returns {string} - A string of the publisher name, formatted for
+ * citation
*/
getPublisherText() {
const datasource = this.model.get("datasource");
@@ -3111,10 +3266,12 @@ define([
/**
* Generate a string appropriate to be used as the publication date in a
* dataset citation.
+ * @returns {string} - A string of the publication date, formatted for
+ * citation
*/
getDatePublishedText() {
- // Dataset/datePublished
- // Prefer pubDate, fall back to dateUploaded so we have something to show
+ // Dataset/datePublished Prefer pubDate, fall back to dateUploaded so we
+ // have something to show
if (this.model.get("pubDate") !== "") {
return this.model.get("pubDate");
}
@@ -3122,25 +3279,19 @@ define([
},
/**
- * Generate Schema.org-compliant JSONLD for the model bound to the view into
- * the head tag of the page by `insertJSONLD`.
+ * Generate Schema.org-compliant JSONLD for the model bound to the view
+ * into the head tag of the page by `insertJSONLD`.
*
- * Note: `insertJSONLD` should be called to do the actual inserting into the
- * DOM.
+ * Note: `insertJSONLD` should be called to do the actual inserting into
+ * the DOM.
+ * @returns {object} - JSON-LD object for the model bound to the view
*/
generateJSONLD() {
const { model } = this;
- // Determine the path (either #view or view, depending on router
- // configuration) for use in the 'url' property
- const { href } = document.location;
- const route = href
- .replace(`${document.location.origin}/`, "")
- .split("/")[0];
-
// First: Create a minimal Schema.org Dataset with just the fields we
- // know will come back from Solr (System Metadata fields).
- // Add the rest in conditional on whether they are present.
+ // know will come back from Solr (System Metadata fields). Add the rest
+ // in conditional on whether they are present.
const elJSON = {
"@context": {
"@vocab": "https://schema.org/",
@@ -3244,10 +3395,10 @@ define([
if (model.get("abstract")) {
elJSON.description = model.get("abstract");
} else {
- const datasets_url = `https://dataone.org/datasets/${encodeURIComponent(
+ const datasetsUrl = `https://dataone.org/datasets/${encodeURIComponent(
model.get("id"),
)}`;
- elJSON.description = `No description is available. Visit ${datasets_url} for complete metadata about this dataset.`;
+ elJSON.description = `No description is available. Visit ${datasetsUrl} for complete metadata about this dataset.`;
}
// Dataset/keywords
@@ -3285,9 +3436,10 @@ define([
/**
* Generate a Schema.org/identifier from the model's id
*
- * Tries to use the PropertyValue pattern when the identifier is a DOI
- * and falls back to a Text value otherwise
+ * Tries to use the PropertyValue pattern when the identifier is a DOI and
+ * falls back to a Text value otherwise
* @param {string} identifier - The raw identifier
+ * @returns {object} - A Schema.org/PropertyValue object or a string
*/
generateSchemaOrgIdentifier(identifier) {
if (!this.model.isDOI()) {
@@ -3313,10 +3465,11 @@ define([
*
* Either generates a GeoCoordinates (when the north and east coords are
* the same) or a GeoShape otherwise.
- * @param north
- * @param east
- * @param south
- * @param west
+ * @param {number} north - North bounding coordinate
+ * @param {number} east - East bounding coordinate
+ * @param {number} south - South bounding coordinate
+ * @param {number} west - West bounding coordinate
+ * @returns {object} - A Schema.org/Place/geo object
*/
generateSchemaOrgGeo(north, east, south, west) {
if (north === south) {
@@ -3340,8 +3493,8 @@ define([
* whether the north and south bounding coordinates are the same.
*
* Part of the reason for factoring this out, in addition to code
- * organization issues, is that the GeoJSON spec requires us to modify
- * the raw result from Solr when the coverage crosses -180W which is common
+ * organization issues, is that the GeoJSON spec requires us to modify the
+ * raw result from Solr when the coverage crosses -180W which is common
* for datasets that cross the Pacific Ocean. In this case, We need to
* convert the east bounding coordinate from degrees west to degrees east.
*
@@ -3351,6 +3504,7 @@ define([
* @param {number} east - East bounding coordinate
* @param {number} south - South bounding coordinate
* @param {number} west - West bounding coordinate
+ * @returns {string} - A stringified GeoJSON object
*/
generateGeoJSONString(north, east, south, west) {
if (north === south) {
@@ -3363,8 +3517,8 @@ define([
* Generate a GeoJSON Point object
* @param {number} north - North bounding coordinate
* @param {number} east - East bounding coordinate
- *
- * Example:
+ * @returns {string} - A stringified GeoJSON Point object
+ * @example
* {
* "type": "Point",
* "coordinates": [
@@ -3386,10 +3540,8 @@ define([
* @param {number} east - East bounding coordinate
* @param {number} south - South bounding coordinate
* @param {number} west - West bounding coordinate
- *
- *
- * Example:
- *
+ * @returns {string} - A stringified GeoJSON Polygon object
+ * @example
* {
* "type": "Polygon",
* "coordinates": [[
@@ -3405,14 +3557,12 @@ define([
'{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[';
// Handle the case when the polygon wraps across the 180W/180E boundary
- if (east < west) {
- east = 360 - east;
- }
+ const fixedEast = east < west ? 360 - east : east;
const inner =
`[${west},${south}],` +
- `[${east},${south}],` +
- `[${east},${north}],` +
+ `[${fixedEast},${south}],` +
+ `[${fixedEast},${north}],` +
`[${west},${north}],` +
`[${west},${south}]`;
@@ -3423,16 +3573,13 @@ define([
/**
* Create a canonical IRI for a DOI given a random DataONE identifier.
- * @param {string} identifier: The identifier to (possibly) create the IRI
- * for.
- * @param identifier
- * @returns {string|null} Returns null when matching the identifier to a DOI
- * regex fails or a string when the match is successful
- *
* Useful for describing resources identified by DOIs in linked open data
* contexts or possibly also useful for comparing two DOIs for equality.
- *
* Note: Really could be generalized to more identifier schemes.
+ * @param {string} identifier The identifier to (possibly) create the IRI
+ * for.
+ * @returns {string|null} Returns null when matching the identifier to a
+ * DOI regex fails or a string when the match is successful
*/
getCanonicalDOIIRI(identifier) {
return MetacatUI.appModel.DOItoURL(identifier) || null;
@@ -3489,21 +3636,18 @@ define([
);
},
+ /** Insert the interactive annoation views */
createAnnotationViews() {
- try {
- const viewRef = this;
-
- _.each($(".annotation"), (annoEl) => {
- const newView = new AnnotationView({
- el: annoEl,
- });
- viewRef.subviews.push(newView);
+ const viewRef = this;
+ _.each($(".annotation"), (annoEl) => {
+ const newView = new AnnotationView({
+ el: annoEl,
});
- } catch (e) {
- console.error(e);
- }
+ viewRef.subviews.push(newView);
+ });
},
+ /** Insert the markdown views */
insertMarkdownViews() {
const viewRef = this;
@@ -3522,19 +3666,27 @@ define([
});
},
+ /**
+ * Store PIDs for each entity as an array on the view (this.entities)
+ * @param {HTMLElement} entityEl - An element that contains the entity PID
+ * as a data-id attribute. Used if the entity PID is not provided.
+ * @param {string} entityId - The identifier for the entity.
+ */
storeEntityPIDs(entityEl, entityId) {
let entityPID = entityId;
// Get the entity ID if it is null or undefined
- if (entityPID == null) entityPID = $(entityEl).data("id");
+ if (entityPID === null) entityPID = $(entityEl).data("id");
// Perform clean up with the entity ID
if (entityPID && typeof entityPID === "string") {
- // Check and replace urn-uuid- with urn:uuid: if the string starts with urn-uuid-
+ // Check and replace urn-uuid- with urn:uuid: if the string starts
+ // with urn-uuid-
if (entityPID.startsWith("urn-uuid-")) {
entityPID = entityPID.replace("urn-uuid-", "urn:uuid:");
}
- // Check and replace doi-10. with doi:10. if the string starts with doi-10.
+ // Check and replace doi-10. with doi:10. if the string starts with
+ // doi-10.
if (entityPID.startsWith("doi-10.")) {
entityPID = entityPID.replace("doi-10.", "doi:10.");
}
diff --git a/src/js/views/TableEditorView.js b/src/js/views/TableEditorView.js
index 71f6dbde2..98fc449dd 100644
--- a/src/js/views/TableEditorView.js
+++ b/src/js/views/TableEditorView.js
@@ -5,22 +5,20 @@ define([
"markdownTableFromJson",
"markdownTableToJson",
"text!templates/tableEditor.html",
-], function (
- _,
- $,
- Backbone,
- markdownTableFromJson,
- markdownTableToJson,
- Template,
-) {
+], (_, $, Backbone, markdownTableFromJson, markdownTableToJson, Template) => {
+ // a utility function to check if a value is empty for sorting
+ const valIsEmpty = (x) =>
+ x === "" || x === undefined || x === null || Number.isNaN(x);
+
/**
* @class TableEditorView
- * @classdesc A view of an HTML textarea with markdown editor UI and preview tab
+ * @classdesc A view of an HTML textarea with markdown editor UI and preview
+ * tab
* @classcategory Views
- * @extends Backbone.View
- * @constructor
+ * @augments Backbone.View
+ * @class
*/
- var TableEditorView = Backbone.View.extend(
+ const TableEditorView = Backbone.View.extend(
/** @lends TableEditorView.prototype */
{
/**
@@ -48,14 +46,14 @@ define([
* header row
* @type {number}
*/
- rowCount: 0, // No of rows
+ rowCount: 0,
/**
- * The current number of columns displayed in the spreadsheet, including the
- * row number column
+ * The current number of columns displayed in the spreadsheet, including
+ * the row number column
* @type {number}
*/
- colCount: 0, // No of cols
+ colCount: 0,
/**
* The same data shown in the table as a stringified JSON object.
@@ -70,8 +68,9 @@ define([
sortingHistory: new Map(),
/**
- * The events this view will listen to and the associated function to call.
- * @type {Object}
+ * The events this view will listen to and the associated function to
+ * call.
+ * @type {object}
*/
events: {
"click #reset": "resetData",
@@ -93,269 +92,204 @@ define([
/**
* Initialize is executed when a new tableEditor is created.
* @constructs TableEditorView
- * @param {Object} options - A literal object with options to pass to the view
- */
- initialize: function (options) {
- try {
- options = _.extend(this.defaults, options);
-
- // Get all the options and apply them to this view
- if (options) {
- var optionKeys = Object.keys(options);
- _.each(
- optionKeys,
- function (key, i) {
- this[key] = options[key];
- },
- this,
- );
- }
- } catch (e) {
- console.log(
- "Failed to initialize the table editor view, error message: " + e,
- );
- }
+ * @param {object} options - A literal object with options to pass to the
+ * view
+ * @param {string} options.markdown - A markdown table to edit.
+ * @param {string} options.tableData - The table data as a stringified
+ * JSON in the form of an array of arrays. Only used if markdown is not
+ * provided.
+ */
+ initialize(options = {}) {
+ const mergedOptions = { ...this.defaults, ...options };
+
+ Object.keys(mergedOptions).forEach((key) => {
+ this[key] = mergedOptions[key];
+ });
},
/**
- * render - Renders the tableEditor - add UI for creating and editing tables
- */
- render: function () {
- try {
- // Insert the template into the view
- this.$el
- .html(
- this.template({
- cid: this.cid,
- }),
- )
- .data("view", this);
-
- // If initalized with markdown, convert to JSON and use as table data
- // Parse the table string into a javascript object so that we can pass it
- // into the table editor view to be edited by the user.
- if (this.markdown && this.markdown.length > 0) {
- var tableArray = this.getJSONfromMarkdown(this.markdown);
- if (tableArray && Array.isArray(tableArray) && tableArray.length) {
- this.saveData(tableArray);
- this.createSpreadsheet();
- // Add the column that we use for row numbers in the editor
- this.addColumn(0, "left");
- }
- } else {
+ * Renders the tableEditor - add UI for creating and editing tables
+ */
+ render() {
+ // Insert the template into the view
+ this.$el
+ .html(
+ this.template({
+ cid: this.cid,
+ }),
+ )
+ .data("view", this);
+
+ // If initalized with markdown, convert to JSON and use as table data
+ // Parse the table string into a javascript object so that we can pass
+ // it into the table editor view to be edited by the user.
+ if (this.markdown && this.markdown.length > 0) {
+ const tableArray = this.getJSONfromMarkdown(this.markdown);
+ if (tableArray && Array.isArray(tableArray) && tableArray.length) {
+ this.saveData(tableArray);
this.createSpreadsheet();
+ // Add the column that we use for row numbers in the editor
+ this.addColumn(0, "left");
}
- } catch (e) {
- console.log(
- "Failed to render the table editor view, error message: " + e,
- );
+ } else {
+ this.createSpreadsheet();
}
},
/**
- * createSpreadsheet - Creates or re-creates the table & headers with data,
- * if there is any.
+ * Creates or re-creates the table & headers with data, if there is any.
*/
- createSpreadsheet: function () {
- try {
- const spreadsheetData = this.getData();
+ createSpreadsheet() {
+ const spreadsheetData = this.getData();
- this.rowCount = spreadsheetData.length - 1 || this.initialRowCount;
- this.colCount = spreadsheetData[0].length - 1 || this.initialColCount;
+ this.rowCount = spreadsheetData.length - 1 || this.initialRowCount;
+ this.colCount = spreadsheetData[0].length - 1 || this.initialColCount;
- const tableHeaderElement = this.$el.find(".table-headers")[0];
- const tableBodyElement = this.$el.find(".table-body")[0];
+ const tableHeaderElement = this.$el.find(".table-headers")[0];
+ const tableBodyElement = this.$el.find(".table-body")[0];
- const tableBody = tableBodyElement.cloneNode(true);
- tableBodyElement.parentNode.replaceChild(tableBody, tableBodyElement);
- const tableHeaders = tableHeaderElement.cloneNode(true);
- tableHeaderElement.parentNode.replaceChild(
- tableHeaders,
- tableHeaderElement,
- );
+ const tableBody = tableBodyElement.cloneNode(true);
+ tableBodyElement.parentNode.replaceChild(tableBody, tableBodyElement);
+ const tableHeaders = tableHeaderElement.cloneNode(true);
+ tableHeaderElement.parentNode.replaceChild(
+ tableHeaders,
+ tableHeaderElement,
+ );
- tableHeaders.innerHTML = "";
- tableBody.innerHTML = "";
+ tableHeaders.innerHTML = "";
+ tableBody.innerHTML = "";
- tableHeaders.appendChild(this.createHeaderRow(this.colCount));
- this.createTableBody(tableBody, this.rowCount, this.colCount);
+ tableHeaders.appendChild(this.createHeaderRow(this.colCount));
+ this.createTableBody(tableBody, this.rowCount, this.colCount);
- this.populateTable();
- } catch (e) {
- console.log(
- "Failed to create a spreadsheet in the table editor view, error message: " +
- e,
- );
- }
+ this.populateTable();
},
/**
- * populateTable - Fill data in created table from saved data
+ * Fill data in created table from saved data
*/
- populateTable: function () {
- try {
- const data = this.getData();
- if (data === undefined || data === null) return;
-
- for (let i = 0; i < data.length; i++) {
- for (let j = 1; j < data[i].length; j++) {
- const cell = this.$el.find(`#r-${i}-${j}`)[0];
- let value = data[i][j];
- if (i > 0) {
- cell.innerHTML = data[i][j];
- } else {
- // table headers
- if (!value) {
- value = "Col " + j;
- }
- $(cell).find(".column-header-span")[0].innerHTML = value;
+ populateTable() {
+ const data = this.getData();
+ if (data === undefined || data === null) return;
+
+ for (let i = 0; i < data.length; i += 1) {
+ for (let j = 1; j < data[i].length; j += 1) {
+ const cell = this.$el.find(`#r-${i}-${j}`)[0];
+ let value = data[i][j];
+ if (i > 0) {
+ cell.innerHTML = data[i][j];
+ } else {
+ // table headers
+ if (!value) {
+ value = `Col ${j}`;
}
+ // TODO: test this
+ $(cell).find(".column-header-span").text(value);
}
}
- } catch (e) {
- console.log(
- "Failed to populate the table in the table editor view, error message: " +
- e,
- );
}
},
/**
- * getData - Get the saved data and parse it. If there's no saved data,
- * create it.
+ * Get the saved data and parse it. If there's no saved data, create it.
+ * @returns {Array} The table data as an array of arrays
*/
- getData: function () {
- try {
- let data = this.tableData;
- if (data === undefined || data === null || data.length == 0) {
- return this.initializeData();
- }
- return JSON.parse(data);
- } catch (e) {
- console.log(
- "Failed to get and parse data in the Table Editor View, error message: " +
- e,
- );
+ getData() {
+ const data = this.tableData;
+ if (!data) {
+ return this.initializeData();
}
+ return JSON.parse(data);
},
/**
- * initializeData - Create some empty arrays to hold data
+ * Create some empty arrays to hold data
+ * @returns {Array} An array of arrays, each of which is an empty array
*/
- initializeData: function () {
- try {
- const data = [];
- for (let i = 0; i <= this.rowCount; i++) {
- const child = [];
- for (let j = 0; j <= this.colCount; j++) {
- child.push("");
- }
- data.push(child);
+ initializeData() {
+ const data = [];
+ for (let i = 0; i <= this.rowCount; i += 1) {
+ const child = [];
+ for (let j = 0; j <= this.colCount; j += 1) {
+ child.push("");
}
- return data;
- } catch (e) {
- console.log(
- "Failed to create new data in the Table Editor View, error message: " +
- e,
- );
+ data.push(child);
}
+ return data;
},
/**
- * updateData - When the user focuses out, presume they've changed the data,
- * and updated the saved data.
- *
+ * When the user focuses out, presume they've changed the data, and
+ * updated the saved data.
* @param {event} e The focus out event that triggered this function
*/
- updateData: function (e) {
- try {
- if (e.target) {
- let item;
- let newValue;
- if (e.target.nodeName === "TD") {
- item = e.target;
- newValue = item.textContent;
- } else if (e.target.classList.contains("column-header-span")) {
- item = e.target.parentNode;
- newValue = e.target.textContent;
- }
- if (item) {
- const indices = item.id.split("-");
- let spreadsheetData = this.getData();
- spreadsheetData[indices[1]][indices[2]] = newValue;
- this.saveData(spreadsheetData);
- }
+ updateData(e) {
+ if (e.target) {
+ let item;
+ let newValue;
+ if (e.target.nodeName === "TD") {
+ item = e.target;
+ newValue = item.textContent;
+ } else if (e.target.classList.contains("column-header-span")) {
+ item = e.target.parentNode;
+ newValue = e.target.textContent;
+ }
+ if (item) {
+ const indices = item.id.split("-");
+ const spreadsheetData = this.getData();
+ spreadsheetData[indices[1]][indices[2]] = newValue;
+ this.saveData(spreadsheetData);
}
- } catch (e) {
- console.log(
- "Failed to update data in the Table Editor View, error message: " +
- e,
- );
}
},
/**
- * saveData - Save the data as a string.
- *
- * @param {type} data description
- * @return {type} description
+ * Save the data as a string on the tableData property of the view
+ * @param {Array} data The table data as an array of arrays
*/
- saveData: function (data) {
- try {
- this.tableData = JSON.stringify(data);
- } catch (e) {
- console.log(
- "Failed to save data in the Table Editor View, error message: " + e,
- );
- }
+ saveData(data) {
+ this.tableData = JSON.stringify(data);
},
/**
- * resetData - Clear the saved data and reset the table to the default
- * number of rows & columns
- *
- * @param {event} e - the event that triggered this function
+ * Clear the saved data and reset the table to the default number of rows
+ * & columns
+ * @param {event} _e - the event that triggered this function
*/
- resetData: function (e) {
- try {
- confirmation = confirm(
- "This will erase all data and reset the table. Are you sure?",
- );
- if (confirmation == true) {
- this.tableData = "";
- this.rowCount = this.initialRowCount;
- this.colCount = this.initialColCount;
- this.createSpreadsheet();
- } else {
- return;
- }
- } catch (e) {
- console.log(
- "Failed to reset data in the Table Editor View, error message: " +
- e,
- );
+ resetData(_e) {
+ // eslint-disable-next-line no-restricted-globals, no-alert
+ const confirmation = confirm(
+ "This will erase all data and reset the table. Are you sure?",
+ );
+ if (confirmation === true) {
+ this.tableData = "";
+ this.rowCount = this.initialRowCount;
+ this.colCount = this.initialColCount;
+ this.createSpreadsheet();
+ } else {
+ // TODO?
}
},
/**
- * createHeaderRow - Create a header row for the table
- */
- createHeaderRow: function () {
- try {
- const tr = document.createElement("tr");
- tr.setAttribute("id", "r-0");
- for (let i = 0; i <= this.colCount; i++) {
- const th = document.createElement("th");
- th.setAttribute("id", `r-0-${i}`);
- th.setAttribute("class", `${i === 0 ? "" : "column-header"}`);
- if (i !== 0) {
- const span = document.createElement("span");
- span.innerHTML = `Col ${i}`;
- span.setAttribute("class", "column-header-span");
- span.setAttribute("contentEditable", "true");
- const dropDownDiv = document.createElement("div");
- dropDownDiv.setAttribute("class", "dropdown");
- dropDownDiv.innerHTML = `
+ * Create a header row for the table
+ * @returns {HTMLElement} The header row element
+ */
+ createHeaderRow() {
+ const tr = document.createElement("tr");
+ tr.setAttribute("id", "r-0");
+ for (let i = 0; i <= this.colCount; i += 1) {
+ const th = document.createElement("th");
+ th.setAttribute("id", `r-0-${i}`);
+ th.setAttribute("class", `${i === 0 ? "" : "column-header"}`);
+ if (i !== 0) {
+ const span = document.createElement("span");
+ span.innerHTML = `Col ${i}`;
+ span.setAttribute("class", "column-header-span");
+ span.setAttribute("contentEditable", "true");
+ const dropDownDiv = document.createElement("div");
+ dropDownDiv.setAttribute("class", "dropdown");
+ dropDownDiv.innerHTML = `
@@ -366,39 +300,33 @@ define([
`;
- th.appendChild(span);
- th.appendChild(dropDownDiv);
- }
- tr.appendChild(th);
+ th.appendChild(span);
+ th.appendChild(dropDownDiv);
}
- return tr;
- } catch (e) {
- console.log(
- "Failed to create header row in the Table Editor View, error message: " +
- e,
- );
+ tr.appendChild(th);
}
+ return tr;
},
/**
- * createTableBodyRow - Create a row for the table
- *
- * @param {number} rowNum The table row number to add to the table, where 0 is the header row
- */
- createTableBodyRow: function (rowNum) {
- try {
- const tr = document.createElement("tr");
- tr.setAttribute("id", `r-${rowNum}`);
- for (let i = 0; i <= this.colCount; i++) {
- const cell = document.createElement(`${i === 0 ? "th" : "td"}`);
- // header
- if (i === 0) {
- cell.contentEditable = false;
- const span = document.createElement("span");
- const dropDownDiv = document.createElement("div");
- span.innerHTML = rowNum;
- dropDownDiv.setAttribute("class", "dropdown");
- dropDownDiv.innerHTML = `
+ * Create a row for the table
+ * @param {number} rowNum The table row number to add to the table, where
+ * 0 is the header row
+ * @returns {HTMLElement} The row element
+ */
+ createTableBodyRow(rowNum) {
+ const tr = document.createElement("tr");
+ tr.setAttribute("id", `r-${rowNum}`);
+ for (let i = 0; i <= this.colCount; i += 1) {
+ const cell = document.createElement(`${i === 0 ? "th" : "td"}`);
+ // header
+ if (i === 0) {
+ cell.contentEditable = false;
+ const span = document.createElement("span");
+ const dropDownDiv = document.createElement("div");
+ span.innerHTML = rowNum;
+ dropDownDiv.setAttribute("class", "dropdown");
+ dropDownDiv.innerHTML = `
@@ -408,441 +336,333 @@ define([
`;
- cell.appendChild(span);
- cell.appendChild(dropDownDiv);
- cell.setAttribute("class", "row-header");
- } else {
- cell.contentEditable = true;
- }
- cell.setAttribute("id", `r-${rowNum}-${i}`);
- tr.appendChild(cell);
+ cell.appendChild(span);
+ cell.appendChild(dropDownDiv);
+ cell.setAttribute("class", "row-header");
+ } else {
+ cell.contentEditable = true;
}
- return tr;
- } catch (e) {
- console.log(
- "Failed to create table row in the Table Editor View, error message: " +
- e,
- );
+ cell.setAttribute("id", `r-${rowNum}-${i}`);
+ tr.appendChild(cell);
}
+ return tr;
},
/**
- * createTableBody - Given a table element, add table rows
- *
+ * Given a table element, add table rows
* @param {HTMLElement} tableBody A table HTML Element
*/
- createTableBody: function (tableBody) {
- try {
- for (let rowNum = 1; rowNum <= this.rowCount; rowNum++) {
- tableBody.appendChild(this.createTableBodyRow(rowNum));
- }
- } catch (e) {
- console.log(
- "Failed to create table body in the Table Editor View, error message: " +
- e,
- );
+ createTableBody(tableBody) {
+ for (let rowNum = 1; rowNum <= this.rowCount; rowNum += 1) {
+ tableBody.appendChild(this.createTableBodyRow(rowNum));
}
},
/**
- * addRow - Utility function to add row
- *
+ * Utility function to add row
* @param {number} currentRow The row number at which to add a new row
- * @param {string} direction Can be "top" or "bottom", indicating whether to new row should be above or below the current row
- */
- addRow: function (currentRow, direction) {
- try {
- let data = this.getData();
- const colCount = data[0].length;
- const newRow = new Array(colCount).fill("");
- if (direction === "top") {
- data.splice(currentRow, 0, newRow);
- } else if (direction === "bottom") {
- data.splice(currentRow + 1, 0, newRow);
- }
- this.rowCount++;
- this.saveData(data);
- this.createSpreadsheet();
- } catch (e) {
- console.log(
- "Failed to add row in the Table Editor View, error message: " + e,
- );
+ * @param {string} direction Can be "top" or "bottom", indicating
+ * whether to new row should be above or below the current row
+ */
+ addRow(currentRow, direction) {
+ const data = this.getData();
+ const colCount = data[0].length;
+ const newRow = new Array(colCount).fill("");
+ if (direction === "top") {
+ data.splice(currentRow, 0, newRow);
+ } else if (direction === "bottom") {
+ data.splice(currentRow + 1, 0, newRow);
}
+ this.rowCount += 1;
+ this.saveData(data);
+ this.createSpreadsheet();
},
/**
- * deleteRow - Utility function to delete row
- *
+ * Utility function to delete row
* @param {number} currentRow The row number to delete
*/
- deleteRow: function (currentRow) {
- try {
- let data = this.getData();
- // Don't allow deletion of the last row
- if (data.length <= 2) {
- this.resetData();
- return;
- }
- data.splice(currentRow, 1);
- this.rowCount--;
- this.saveData(data);
- this.createSpreadsheet();
- } catch (e) {
- console.log(
- "Failed to delete row in the Table Editor View, error message: " +
- e,
- );
+ deleteRow(currentRow) {
+ const data = this.getData();
+ // Don't allow deletion of the last row
+ if (data.length <= 2) {
+ this.resetData();
+ return;
}
+ data.splice(currentRow, 1);
+ this.rowCount -= 1;
+ this.saveData(data);
+ this.createSpreadsheet();
},
/**
- * addColumn - Utility function to add columns
- *
- * @param {number} currentCol The column number at which to add a new column
- * @param {string} direction Can be "left" or "right", indicating whether to new column should be to the left or right of the current column
- */
- addColumn: function (currentCol, direction) {
- try {
- let data = this.getData();
- for (let i = 0; i <= this.rowCount; i++) {
- if (direction === "left") {
- data[i].splice(currentCol, 0, "");
- } else if (direction === "right") {
- data[i].splice(currentCol + 1, 0, "");
- }
+ * Utility function to add columns
+ * @param {number} currentCol The column number at which to add a new
+ * column
+ * @param {string} direction Can be "left" or "right", indicating
+ * whether to new column should be to the left or right of the current
+ * column
+ */
+ addColumn(currentCol, direction) {
+ const data = this.getData();
+ for (let i = 0; i <= this.rowCount; i += 1) {
+ if (direction === "left") {
+ data[i].splice(currentCol, 0, "");
+ } else if (direction === "right") {
+ data[i].splice(currentCol + 1, 0, "");
}
- this.colCount++;
- this.saveData(data);
- this.createSpreadsheet();
- } catch (e) {
- console.log(
- "Failed to add column in the Table Editor View, error message: " +
- e,
- );
}
+ this.colCount += 1;
+ this.saveData(data);
+ this.createSpreadsheet();
},
/**
- * deleteColumn - Utility function to delete column
- *
+ * Utility function to delete column
* @param {number} currentCol The number of the column to delete
*/
- deleteColumn: function (currentCol) {
- try {
- let data = this.getData();
- // Don't allow deletion of the last column
- if (data[0].length <= 2) {
- this.resetData();
- return;
- }
- for (let i = 0; i <= this.rowCount; i++) {
- data[i].splice(currentCol, 1);
- }
- this.colCount--;
- this.saveData(data);
- this.createSpreadsheet();
- } catch (e) {
- console.log(
- "Failed to delete column in the Table Editor View, error message: " +
- e,
- );
+ deleteColumn(currentCol) {
+ const data = this.getData();
+ // Don't allow deletion of the last column
+ if (data[0].length <= 2) {
+ this.resetData();
+ return;
+ }
+ for (let i = 0; i <= this.rowCount; i += 1) {
+ data[i].splice(currentCol, 1);
}
+ this.colCount -= 1;
+ this.saveData(data);
+ this.createSpreadsheet();
},
/**
- * sortColumn - Utility function to sort columns
- *
+ * Utility function to sort columns
* @param {number} currentCol The column number of the column to delete
*/
- sortColumn: function (currentCol) {
- try {
- let spreadSheetData = this.getData();
- let data = spreadSheetData.slice(1);
- let headers = spreadSheetData.slice(0, 1)[0];
- if (!data.some((a) => a[currentCol] !== "")) return;
- if (this.sortingHistory.has(currentCol)) {
- const sortOrder = this.sortingHistory.get(currentCol);
- switch (sortOrder) {
- case "desc":
- data.sort(this.ascSort.bind(this, currentCol));
- this.sortingHistory.set(currentCol, "asc");
- break;
- case "asc":
- data.sort(this.dscSort.bind(this, currentCol));
- this.sortingHistory.set(currentCol, "desc");
- break;
- }
- } else {
+ sortColumn(currentCol) {
+ const spreadSheetData = this.getData();
+ const data = spreadSheetData.slice(1);
+ const headers = spreadSheetData.slice(0, 1)[0];
+ if (!data.some((a) => a[currentCol] !== "")) return;
+ if (this.sortingHistory.has(currentCol)) {
+ const sortOrder = this.sortingHistory.get(currentCol);
+ if (sortOrder === "desc") {
data.sort(this.ascSort.bind(this, currentCol));
this.sortingHistory.set(currentCol, "asc");
+ } else {
+ data.sort(this.dscSort.bind(this, currentCol));
+ this.sortingHistory.set(currentCol, "desc");
}
- data.splice(0, 0, headers);
- this.saveData(data);
- this.createSpreadsheet();
- } catch (e) {
- console.log(
- "Failed to sort column in the Table Editor View, error message: " +
- e,
- );
+ } else {
+ data.sort(this.ascSort.bind(this, currentCol));
+ this.sortingHistory.set(currentCol, "asc");
}
+ data.splice(0, 0, headers);
+ this.saveData(data);
+ this.createSpreadsheet();
},
/**
- * ascSort - Compare Functions for sorting - ascending
- *
- * @param {number} currentCol The number of the column to sort
- * @param {*} a One of two items to compare
- * @param {*} b The second of two items to compare
- * @return {number} A number indicating the order to place a vs b in the list. It it returns less than zero, then a will be placed before b in the list.
+ * Compare Functions for sorting - ascending
+ * @param {number} currentCol The number of the column to sort
+ * @param {*} a One of two items to compare
+ * @param {*} b The second of two items to compare
+ * @returns {number} A number indicating the order to place a vs b in the
+ * list. It it returns less than zero, then a will be placed before b in
+ * the list.
*/
- ascSort: function (currentCol, a, b) {
+ ascSort(currentCol, a, b) {
try {
- let _a = a[currentCol];
- let _b = b[currentCol];
- if (_a === "") return 1;
- if (_b === "") return -1;
+ let valA = a[currentCol];
+ let valB = b[currentCol];
+
+ if (valIsEmpty(valA)) return 1;
+ if (valIsEmpty(valB)) return -1;
// Check for strings and numbers
- if (isNaN(_a) || isNaN(_b)) {
- _a = _a.toUpperCase();
- _b = _b.toUpperCase();
- if (_a < _b) return -1;
- if (_a > _b) return 1;
- return 0;
+ if (typeof valA === "number" && typeof valB === "number") {
+ return valA - valB;
}
- return _a - _b;
+ valA = String(valA).toUpperCase();
+ valB = String(valB).toUpperCase();
+ if (valA < valB) return -1;
+ if (valA > valB) return 1;
+ return 0;
} catch (e) {
- console.log(
- "The ascending compare function in Table Editor View failed, error message: " +
- e,
- );
return 0;
}
},
/**
- * dscSort - Descending compare function
- *
- * @param {number} currentCol The number of the column to sort
- * @param {*} a One of two items to compare
- * @param {*} b The second of two items to compare
- * @return {number} A number indicating the order to place a vs b in the list. It it returns less than zero, then a will be placed before b in the list.
+ * Descending compare function
+ * @param {number} currentCol The number of the column to sort
+ * @param {*} a One of two items to compare
+ * @param {*} b The second of two items to compare
+ * @returns {number} A number indicating the order to place a vs
+ * b in the list. It it returns less than zero, then a will be placed
+ * before b in the list.
*/
- dscSort: function (currentCol, a, b) {
+ dscSort(currentCol, a, b) {
try {
- let _a = a[currentCol];
- let _b = b[currentCol];
- if (_a === "") return 1;
- if (_b === "") return -1;
+ let valA = a[currentCol];
+ let valB = b[currentCol];
+ if (valIsEmpty(valA)) return -1;
+ if (valIsEmpty(valB)) return 1;
// Check for strings and numbers
- if (isNaN(_a) || isNaN(_b)) {
- _a = _a.toUpperCase();
- _b = _b.toUpperCase();
- if (_a < _b) return 1;
- if (_a > _b) return -1;
- return 0;
+ if (typeof valA === "number" && typeof valB === "number") {
+ return valB - valA;
}
- return _b - _a;
+ valA = String(valA).toUpperCase();
+ valB = String(valB).toUpperCase();
+ if (valB < valA) return -1;
+ if (valB > valA) return 1;
+ return 0;
} catch (e) {
- console.log(
- "The descending compare function in Table Editor View failed, error message: " +
- e,
- );
return 0;
}
},
/**
- * convertToMarkdown - Returns the table data as markdown
- *
- * @return {string} The markdownified table as string
+ * Returns the table data as markdown
+ * @returns {string} The markdownified table as string
*/
- getMarkdown: function () {
- try {
- // Ensure there are at least two dashes below the table header,
- // i.e. use | -- | not | - |
- // Showdown requries this to avoid ambiguous markdown.
- const minStringLength = function (s) {
- l = s.length <= 1 ? 2 : s.length;
- return l;
- };
- // Get the current table data
- var tableData = this.getData();
- // Remove the empty column that we use for row numbers first
- if (this.hasEmptyCol1(tableData)) {
- for (let i = 0; i <= tableData.length - 1; i++) {
- tableData[i].splice(0, 1);
- }
+ getMarkdown() {
+ // Ensure there are at least two dashes below the table header, i.e. use
+ // | -- | not | - | Showdown requries this to avoid ambiguous markdown.
+ const minStringLength = (s) => (s.length <= 1 ? 2 : s.length);
+ // Get the current table data
+ const tableData = this.getData();
+ // Remove the empty column that we use for row numbers first
+ if (this.hasEmptyCol1(tableData)) {
+ for (let i = 0; i <= tableData.length - 1; i += 1) {
+ tableData[i].splice(0, 1);
}
- // Convert json data to markdown, for options see https://github.com/wooorm/markdown-table
- // TODO: Add alignment information that we will store in view as an array
- // Include in markdownTableFromJson() options like this - align: ['l', 'c', 'r']
- var markdown = markdownTableFromJson(tableData, {
- stringLength: minStringLength,
- });
- // Add a new line to the end
- return markdown + "\n";
- } catch (e) {
- console.log(
- "Failed to convert json to markdown in the Table Editor View, error message: " +
- e,
- );
- return "";
}
+ // Convert json data to markdown, for options see
+ // https://github.com/wooorm/markdown-table TODO: Add alignment
+ // information that we will store in view as an array Include in
+ // markdownTableFromJson() options like this - align: ['l', 'c', 'r']
+ const markdown = markdownTableFromJson(tableData, {
+ stringLength: minStringLength,
+ });
+ // Add a new line to the end
+ return `${markdown}\n`;
},
/**
- * getJSONfromMarkdown - Converts a given markdown table string to JSON.
- *
+ * Converts a given markdown table string to JSON.
* @param {string} markdown description
- * @return {Array} The markdown table as an array of arrays, where the header is the first array and each row is an array that follows.
- */
- getJSONfromMarkdown: function (markdown) {
- try {
- parsedMarkdown = markdownTableToJson(markdown);
- if (!parsedMarkdown) return;
- // TODO: Add alignment information to the view, returned as parsedMarkdown.align
- return parsedMarkdown.table;
- } catch (e) {
- console.log(
- "Failed to parse markdown in the Table Editor View, error message: " +
- e,
- );
- return [];
- }
+ * @returns {Array} The markdown table as an array of arrays,
+ * where the header is the first array and each row is an array that
+ * follows.
+ */
+ getJSONfromMarkdown(markdown) {
+ const parsedMarkdown = markdownTableToJson(markdown);
+ if (!parsedMarkdown) return null;
+ // TODO: Add alignment information to the view, returned as
+ // parsedMarkdown.align
+ return parsedMarkdown.table;
},
/**
- * hasEmptyCol1 - Checks whether the first column is empty.
- *
- * @param {Object} data The table data in the form of an array of arrays
- * @return {boolean} returns true if the first column is empty, false if at least one cell in the first column contains a value
+ * Checks whether the first column is empty.
+ * @param {object} data The table data in the form of an array of arrays
+ * @returns {boolean} returns true if the first column is empty, false
+ * if at least one cell in the first column contains a value
*/
- hasEmptyCol1: function (data) {
- try {
- var firstColEmpty = true;
- // Check if the first item in each row is blank
- for (let i = 0; i <= data.length - 1; i++) {
- if (data[i][0] != "") {
- firstColEmpty = false;
- break;
- }
+ hasEmptyCol1(data) {
+ let firstColEmpty = true;
+ // Check if the first item in each row is blank
+ for (let i = 0; i <= data.length - 1; i += 1) {
+ if (data[i][0] !== "" && data[i][0] !== undefined) {
+ firstColEmpty = false;
+ break;
}
- return firstColEmpty;
- } catch (e) {
- console.log(
- "Failed to detect if there's an empty first column in the Table Editor View. Assuming the first column has data, but this could cause some issues. Error message: " +
- e,
- );
- return false;
}
+ return firstColEmpty;
},
/**
- * closeDropdown - Close the dropdown menu if the user clicks outside of it
- *
+ * Close the dropdown menu if the user clicks outside of it
* @param {type} e The event that triggered this function
*/
- closeDropdown: function (e) {
- try {
- if (!e.target.matches(".dropbtn") || !e) {
- var dropdowns = document.getElementsByClassName("dropdown-content");
- var i;
- for (i = 0; i < dropdowns.length; i++) {
- var openDropdown = dropdowns[i];
- if (openDropdown.classList.contains("show")) {
- openDropdown.classList.remove("show");
- }
+ closeDropdown(e) {
+ if (!e.target.matches(".dropbtn") || !e) {
+ const dropdowns = document.getElementsByClassName("dropdown-content");
+ let i;
+ for (i = 0; i < dropdowns.length; i += 1) {
+ const openDropdown = dropdowns[i];
+ if (openDropdown.classList.contains("show")) {
+ openDropdown.classList.remove("show");
}
}
- } catch (e) {
- console.log(
- "Failed to close a dropdown menu in the Table Editor View, error message: " +
- e,
- );
}
},
/**
- * handleHeadersClick - Called when the table header is clicked. Depending
- * on what is clicked, shows or hides the dropdown menus in the header,
- * or calls one of the functions listed in the menu (e.g. delete column).
- *
+ * Called when the table header is clicked. Depending on what is clicked,
+ * shows or hides the dropdown menus in the header, or calls one of the
+ * functions listed in the menu (e.g. delete column).
* @param {event} e The event that triggered this function
*/
- handleHeadersClick: function (e) {
- try {
- var view = this;
- if (e.target) {
- var classes = e.target.classList;
-
- if (classes.contains("column-header-span")) {
- // If the header element is clicked...
- } else if (classes.contains("dropbtn")) {
- const idArr = e.target.id.split("-");
- document
- .getElementById(`col-dropdown-${idArr[2]}`)
- .classList.toggle("show");
- } else if (classes.contains("col-dropdown-option")) {
- const index = e.target.parentNode.id.split("-")[2];
-
- if (classes.contains("col-insert-left")) {
- view.addColumn(index, "left");
- } else if (classes.contains("col-insert-right")) {
- view.addColumn(index, "right");
- } else if (classes.contains("col-sort")) {
- view.sortColumn(index);
- } else if (classes.contains("col-delete")) {
- view.deleteColumn(index);
- }
+ handleHeadersClick(e) {
+ const view = this;
+ if (e.target) {
+ const classes = e.target.classList;
+
+ if (classes.contains("column-header-span")) {
+ // If the header element is clicked...
+ } else if (classes.contains("dropbtn")) {
+ const idArr = e.target.id.split("-");
+ document
+ .getElementById(`col-dropdown-${idArr[2]}`)
+ .classList.toggle("show");
+ } else if (classes.contains("col-dropdown-option")) {
+ const index = e.target.parentNode.id.split("-")[2];
+
+ if (classes.contains("col-insert-left")) {
+ view.addColumn(index, "left");
+ } else if (classes.contains("col-insert-right")) {
+ view.addColumn(index, "right");
+ } else if (classes.contains("col-sort")) {
+ view.sortColumn(index);
+ } else if (classes.contains("col-delete")) {
+ view.deleteColumn(index);
}
}
- } catch (e) {
- console.log(
- "Failed to handle a click in the table header in the Table Editor View, error message: " +
- e,
- );
}
},
/**
- * handleHeadersClick - Called when the table body is clicked. Depending
- * on what is clicked, shows or hides the dropdown menus in the body,
- * or calls one of the functions listed in the menu (e.g. delete row).
- *
- * @param {type} e description
- * @return {type} description
+ * Called when the table body is clicked. Depending on what is clicked,
+ * shows or hides the dropdown menus in the body, or calls one of the
+ * functions listed in the menu (e.g. delete row).
+ * @param {type} e The event that triggered this function
*/
- handleBodyClick: function (e) {
- try {
- var view = this;
- if (e.target) {
- var classes = e.target.classList;
-
- if (classes.contains("dropbtn")) {
- const idArr = e.target.id.split("-");
- view.$el
- .find(`#row-dropdown-${idArr[2]}`)[0]
- .classList.toggle("show");
- } else if (classes.contains("row-dropdown-option")) {
- const index = parseInt(e.target.parentNode.id.split("-"))[2];
- if (classes.contains("row-insert-top")) {
- view.addRow(index, "top");
- }
- if (classes.contains("row-insert-bottom")) {
- view.addRow(index, "bottom");
- }
- if (classes.contains("row-delete")) {
- view.deleteRow(index);
- }
+ handleBodyClick(e) {
+ const view = this;
+ if (e.target) {
+ const classes = e.target.classList;
+
+ if (classes.contains("dropbtn")) {
+ const idArr = e.target.id.split("-");
+ view.$el
+ .find(`#row-dropdown-${idArr[2]}`)[0]
+ .classList.toggle("show");
+ } else if (classes.contains("row-dropdown-option")) {
+ const index = parseInt(e.target.parentNode.id.split("-"), 10)[2];
+ if (classes.contains("row-insert-top")) {
+ view.addRow(index, "top");
+ }
+ if (classes.contains("row-insert-bottom")) {
+ view.addRow(index, "bottom");
+ }
+ if (classes.contains("row-delete")) {
+ view.deleteRow(index);
}
}
- } catch (e) {
- console.log(
- "Failed to handle a click in the table body in the Table Editor View, error message: " +
- e,
- );
}
},
},