Skip to content

Commit

Permalink
Merge pull request #2521 from NCEAS/feature-1758-data-table-previews-4
Browse files Browse the repository at this point in the history
Create the DataObject View (Step 4 of issue #1758)
  • Loading branch information
rushirajnenuji authored Sep 25, 2024
2 parents c23f645 + cb40d62 commit 8f3de73
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 100 deletions.
175 changes: 84 additions & 91 deletions src/js/models/SolrResult.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
define(["jquery", "underscore", "backbone"], ($, _, Backbone) => {
/**
* @class SolrResult
* @classdesc A single result from the Solr search service
* @classcategory Models
* @extends Backbone.Model
*/
var SolrResult = Backbone.Model.extend(
const SolrResult = Backbone.Model.extend(
/** @lends SolrResult.prototype */ {
// This model contains all of the attributes found in the SOLR 'docs' field inside of the SOLR response element
defaults: {
Expand Down Expand Up @@ -278,103 +278,96 @@ define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
);
},

/*
* This method will download this object while sending the user's auth token in the request.
/**
* Download this object while sending the user's auth token in the
* request.
*/
downloadWithCredentials: function () {
//if(this.get("isPublic")) return;

//Get info about this object
var url = this.get("url"),
model = this;

//Create an XHR
var xhr = new XMLHttpRequest();
async downloadWithCredentials() {
const model = this;

//Open and send the request with the user's auth token
xhr.open("GET", url);
// Call the new getBlob method and handle the response
const response = await this.fetchDataObjectWithCredentials();
const blob = await response.blob();
const filename = this.getFileNameFromResponse(response);

if (MetacatUI.appUserModel.get("loggedIn")) xhr.withCredentials = true;

//When the XHR is ready, create a link with the raw data (Blob) and click the link to download
xhr.onload = function () {
if (this.status == 404) {
this.onerror.call(this);
return;
}

//Get the file name to save this file as
var filename = xhr.getResponseHeader("Content-Disposition");

if (!filename) {
filename =
model.get("fileName") ||
model.get("title") ||
model.get("id") ||
"download";
} else
filename = filename
.substring(filename.indexOf("filename=") + 9)
.replace(/"/g, "");

//Replace any whitespaces
filename = filename.trim().replace(/ /g, "_");

//For IE, we need to use the navigator API
if (navigator && navigator.msSaveOrOpenBlob) {
navigator.msSaveOrOpenBlob(xhr.response, filename);
}
//Other browsers can download it via a link
else {
var a = document.createElement("a");
a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob

// Set the file name.
a.download = filename;

a.style.display = "none";
document.body.appendChild(a);
a.click();
a.remove();
}

model.trigger("downloadComplete");

// Track this event
MetacatUI.analytics?.trackEvent(
"download",
"Download DataONEObject",
model.get("id"),
);
};

xhr.onerror = function (e) {
model.trigger("downloadError");
// For IE, we need to use the navigator API
if (navigator && navigator.msSaveOrOpenBlob) {
navigator.msSaveOrOpenBlob(blob, filename);
} else {
// Other browsers can download it via a link
const a = document.createElement("a");
a.href = window.URL.createObjectURL(blob);
a.download = filename;
a.style.display = "none";
document.body.appendChild(a);
a.click();
a.remove();
}

// Track the error
MetacatUI.analytics?.trackException(
`Download DataONEObject error: ${e || ""}`,
model.get("id"),
true,
);
};
// Track this event
model.trigger("downloadComplete");
MetacatUI.analytics?.trackEvent(
"download",
"Download DataONEObject",
model.get("id"),
);
},

xhr.onprogress = function (e) {
if (e.lengthComputable) {
var percent = (e.loaded / e.total) * 100;
model.set("downloadPercent", percent);
/**
* This method will fetch this object while sending the user's auth token
* in the request. The data can then be downloaded or displayed in the
* browser
* @returns {Promise} A promise that resolves when the data is fetched
* @since 0.0.0
*/
fetchDataObjectWithCredentials() {
const url = this.get("url");
const token = MetacatUI.appUserModel.get("token") || "";
const method = "GET";

return new Promise((resolve, reject) => {
const headers = {};
if (token) {
headers.Authorization = `Bearer ${token}`;
}
};

xhr.responseType = "blob";

if (MetacatUI.appUserModel.get("loggedIn"))
xhr.setRequestHeader(
"Authorization",
"Bearer " + MetacatUI.appUserModel.get("token"),
);
fetch(url, { method, headers })
.then((response) => {
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.statusText}`);
}
resolve(response);
})
.catch((error) => {
reject(error);
});
});
},

xhr.send();
/**
* Get the filename from the response headers or default to the model's
* title, id, or "download"
* @param {Response} response - The response object from the fetch request
* @returns {string} The filename to save the file as
* @since 0.0.0
*/
getFileNameFromResponse(response) {
const model = this;
let filename = response.headers.get("Content-Disposition");

if (!filename) {
filename =
model.get("fileName") ||
model.get("title") ||
model.get("id") ||
"download";
} else {
filename = filename
.substring(filename.indexOf("filename=") + 9)
.replace(/"/g, "");
}
filename = filename.trim().replace(/ /g, "_");
return filename;
},

getInfo: function (fields) {
Expand Down
85 changes: 85 additions & 0 deletions src/js/views/DataObjectView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"use strict";

define(["backbone", "views/TableEditorView"], (Backbone, TableEditorView) => {
// The base class for the view
const BASE_CLASS = "object-view";

/**
* @class DataObjectView
* @classdesc A view that downloads and displays a DataONE object. Currently
* there is support for displaying CSV files as a table.
* @classcategory Views
* @augments Backbone.View
* @class
* @since 0.0.0
* @screenshot views/DataObjectView.png //TODO
*/
const DataObjectView = Backbone.View.extend(
/** @lends DataObjectView.prototype */
{
/** @inheritdoc */
type: "DataObjectView",

/** @inheritdoc */
className: BASE_CLASS,

/** @inheritdoc */
tagName: "div",

/**
* Initializes the DataObjectView
* @param {object} options - Options for the view
* @param {SolrResult} options.model - A SolrResult model
*/
initialize(options) {
this.model = options.model;
// TODO: We get format from the response headers, should we compare it,
// or prevent downloading the object if it's not a supported type?
// this.format = this.model.get("formatId") ||
// this.model.get("mediaType");
},

/** @inheritdoc */
render() {
this.$el.empty();
this.downloadObject().then((response) => this.renderObject(response));
return this;
},

/**
* With the already fetched DataONE object, check the format and render
* the object accordingly.
* @param {Response} response - The response from the DataONE object API
*/
renderObject(response) {
const format = response.headers.get("Content-Type");
if (format === "text/csv") {
response.text().then((text) => {
this.csv = text;
this.showTable();
});
}
},

/**
* Downloads the DataONE object
* @returns {Promise} Promise that resolves with the Response from DataONE
*/
downloadObject() {
return this.model.fetchDataObjectWithCredentials();
},

/** Shows the CSV file as a table */
showTable() {
this.table = new TableEditorView({
viewMode: true,
csv: this.csv,
});
this.el.innerHTML = "";
this.el.appendChild(this.table.render().el);
},
},
);

return DataObjectView;
});
5 changes: 2 additions & 3 deletions src/js/views/TableEditorView.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,7 @@ define([
});
},

/**
* Renders the tableEditor - add UI for creating and editing tables
*/
/** @inheritdoc */
render() {
// Insert the template into the view
this.$el
Expand All @@ -150,6 +148,7 @@ define([
// defaults to empty table
this.createSpreadsheet();
}
return this;
},

/**
Expand Down
Loading

0 comments on commit 8f3de73

Please sign in to comment.