/*global define */
define(['jquery',
'jqueryui',
'underscore',
'backbone',
'gmaps',
'fancybox',
'clipboard',
'collections/DataPackage',
'models/DataONEObject',
'models/PackageModel',
'models/SolrResult',
'models/metadata/ScienceMetadata',
'models/MetricsModel',
'views/DownloadButtonView',
'views/ProvChartView',
'views/MetadataIndexView',
'views/ExpandCollapseListView',
'views/ProvStatementView',
'views/PackageTableView',
'views/AnnotatorView',
'views/CitationView',
'text!templates/metadata/metadata.html',
'text!templates/dataSource.html',
'text!templates/publishDOI.html',
'text!templates/newerVersion.html',
'text!templates/loading.html',
'text!templates/metadataControls.html',
'text!templates/metadataInfoIcons.html',
'text!templates/usageStats.html',
'text!templates/downloadContents.html',
'text!templates/alert.html',
'text!templates/editMetadata.html',
'text!templates/dataDisplay.html',
'text!templates/map.html',
'text!templates/annotation.html',
'text!templates/metaTagsHighwirePress.html',
'uuid',
'views/MetricView'
],
function($, $ui, _, Backbone, gmaps, fancybox, Clipboard, DataPackage, DataONEObject, Package, SolrResult, ScienceMetadata,
MetricsModel, DownloadButtonView, ProvChart, MetadataIndex, ExpandCollapseList, ProvStatement, PackageTable,
AnnotatorView, CitationView, MetadataTemplate, DataSourceTemplate, PublishDoiTemplate,
VersionTemplate, LoadingTemplate, ControlsTemplate, MetadataInfoIconsTemplate, UsageTemplate,
DownloadContentsTemplate, AlertTemplate, EditMetadataTemplate, DataDisplayTemplate,
MapTemplate, AnnotationTemplate, metaTagsHighwirePressTemplate, uuid, MetricView) {
'use strict';
var MetadataView = Backbone.View.extend({
subviews: [],
pid: null,
seriesId: null,
saveProvPending: false,
model: new SolrResult(),
packageModels: new Array(),
dataPackage: null,
el: '#Content',
metadataContainer: "#metadata-container",
citationContainer: "#citation-container",
tableContainer: "#table-container",
controlsContainer: "#metadata-controls-container",
metricsContainer: "#metrics-controls-container",
ownerControlsContainer: "#owner-controls-container",
breadcrumbContainer: "#breadcrumb-container",
parentLinkContainer: "#parent-link-container",
dataSourceContainer: "#data-source-container",
articleContainer: "#article-container",
type: "Metadata",
//Templates
template: _.template(MetadataTemplate),
alertTemplate: _.template(AlertTemplate),
doiTemplate: _.template(PublishDoiTemplate),
usageTemplate: _.template(UsageTemplate),
versionTemplate: _.template(VersionTemplate),
loadingTemplate: _.template(LoadingTemplate),
controlsTemplate: _.template(ControlsTemplate),
infoIconsTemplate: _.template(MetadataInfoIconsTemplate),
dataSourceTemplate: _.template(DataSourceTemplate),
downloadContentsTemplate: _.template(DownloadContentsTemplate),
editMetadataTemplate: _.template(EditMetadataTemplate),
dataDisplayTemplate: _.template(DataDisplayTemplate),
mapTemplate: _.template(MapTemplate),
metaTagsHighwirePressTemplate: _.template(metaTagsHighwirePressTemplate),
objectIds: [],
// Delegated events for creating new items, and clearing completed ones.
events: {
"click #publish" : "publish",
"mouseover .highlight-node" : "highlightNode",
"mouseout .highlight-node" : "highlightNode",
"click .preview" : "previewData",
"click #save-metadata-prov" : "saveProv",
},
initialize: function (options) {
if((options === undefined) || (!options)) var options = {};
this.pid = options.pid || options.id || MetacatUI.appModel.get("pid") || null;
if(typeof options.el !== "undefined")
this.setElement(options.el);
},
// Render the main metadata view
render: function () {
this.stopListening();
MetacatUI.appModel.set('headerType', 'default');
// this.showLoading("Loading...");
//Reset various properties of this view first
this.classMap = new Array();
this.subviews = new Array();
this.model.set(this.model.defaults);
this.packageModels = new Array();
// get the pid to render
if(!this.pid)
this.pid = MetacatUI.appModel.get("pid");
this.listenTo(MetacatUI.appUserModel, "change:loggedIn", this.render);
this.getModel();
return this;
},
getDataPackage: function(pid) {
// Get the metadata model that is associated with this DataPackage collection
//var metadataModel = new ScienceMetadata({ id: this.pid });
// Once the ScienceMetadata is populated, populate the associated package
//this.metadataModel = metadataModel;
// Create a new data package with this id
this.dataPackage = new DataPackage([], {id: pid});
//Fetch the data package. DataPackage.parse() triggers 'complete'
this.dataPackage.fetch();
this.listenTo(this.dataPackage, "complete", function() {
// parseProv triggers "queryComplete"
this.dataPackage.parseProv();
this.checkForProv();
});
},
/*
* 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: function(pid){
//Get the pid and sid
if((typeof pid === "undefined") || !pid) var pid = this.pid;
if((typeof this.seriesId !== "undefined") && this.seriesId) var sid = this.seriesId;
//Get the package ID
this.model.set({ id: pid, seriesId: sid });
var model = this.model;
this.listenToOnce(model, "sync", function(){
if(this.model.get("formatType") == "METADATA" || !this.model.get("formatType")){
this.model = model;
this.renderMetadata();
}
else if(this.model.get("formatType") == "DATA"){
if(this.model.get("isDocumentedBy")){
this.pid = _.first(this.model.get("isDocumentedBy"));
this.getModel(this.pid);
return;
}
else{
this.noMetadata(this.model);
}
}
else if(this.model.get("formatType") == "RESOURCE"){
var packageModel = new Package({ id: this.model.get("id") });
packageModel.on("complete", function(){
var metadata = packageModel.getMetadata();
if(!metadata){
this.noMetadata(packageModel);
}
else{
this.model = metadata;
this.pid = this.model.get("id");
this.renderMetadata();
if(this.model.get("resourceMap"))
this.getPackageDetails(this.model.get("resourceMap"));
}
}, this);
packageModel.getMembers();
return;
}
//Get the package information
this.getPackageDetails(model.get("resourceMap"));
});
//Listen to 404 and 401 errors when we get the metadata object
this.listenToOnce(model, "404", this.showNotFound);
this.listenToOnce(model, "401", this.showIsPrivate);
//Fetch the model
model.getInfo();
},
renderMetadata: function(){
var pid = this.model.get("id");
this.hideLoading();
//Load the template which holds the basic structure of the view
this.$el.html(this.template());
this.$(this.tableContainer).html(this.loadingTemplate({
msg: "Retrieving data set details..."
}));
//Insert the breadcrumbs
this.insertBreadcrumbs();
//Insert the citation
this.insertCitation();
//Insert the data source logo
this.insertDataSource();
// is this the latest version? (includes DOI link when needed)
this.showLatestVersion();
// 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 (MetacatUI.appModel.get("displayDatasetMetrics")) {
//Insert Metrics Stats into the dataset landing pages
this.insertMetricsControls();
}
// Edit button and the publish button
this.insertOwnerControls();
//Show loading icon in metadata section
this.$(this.metadataContainer).html(this.loadingTemplate({ msg: "Retrieving metadata ..." }));
// 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")){
var viewRef = this;
var loadSettings = {
url: endpoint,
success: function(response, status, xhr) {
//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
if (status=="error")
viewRef.renderMetadataFromIndex();
else{
//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) && viewRef.model.get("indexed")){
viewRef.renderMetadataFromIndex();
return;
}
//Mark this as a metadata doc with no stylesheet, or one that is at least different than usual EML and FGDC
else if((response.indexOf('id="Metadata"') == -1)){
viewRef.$el.addClass("container no-stylesheet");
if(viewRef.model.get("indexed")){
viewRef.renderMetadataFromIndex();
return;
}
}
//Now show the response from the view service
viewRef.$(viewRef.metadataContainer).html(response);
//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") && !viewRef.model.get("indexed"))
viewRef.$(viewRef.metadataContainer).prepend(viewRef.alertTemplate({ msg: "There is limited metadata about this dataset since it has been archived." }));
viewRef.alterMarkup();
viewRef.trigger("metadataLoaded");
//Add a map of the spatial coverage
if(gmaps) viewRef.insertSpatialCoverageMap();
// Injects Clipboard objects into DOM elements returned from the View Service
viewRef.insertCopiables();
viewRef.setUpAnnotator();
}
},
error: function(xhr, textStatus, errorThrown){
viewRef.renderMetadataFromIndex();
}
}
$.ajax(_.extend(loadSettings, MetacatUI.appUserModel.createAjaxSettings()));
}
else this.renderMetadataFromIndex();
// Insert the Linked Data into the header of the page.
if (MetacatUI.appModel.get("isJSONLDEnabled")) {
var json = this.generateJSONLD();
this.insertJSONLD(json);
}
this.insertCitationMetaTags();
},
/* If there is no view service available, then display the metadata fields from the index */
renderMetadataFromIndex: function(){
var metadataFromIndex = new MetadataIndex({
pid: this.pid,
parentView: this
});
this.subviews.push(metadataFromIndex);
//Add the metadata HTML
this.$(this.metadataContainer).html(metadataFromIndex.render().el);
var view = this;
this.listenTo(metadataFromIndex, "complete", function(){
//Add the package contents
view.insertPackageDetails();
//Add a map of the spatial coverage
if(gmaps) view.insertSpatialCoverageMap();
// render annotator from index content, too
view.setUpAnnotator();
});
},
removeCitation: function(){
var citation = "",
citationEl = null;
//Find the citation element
if(this.$(".citation").length > 0){
//Get the text for the citation
citation = this.$(".citation").text();
//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
else{
//Find the DOM element with the citation
var wells = this.$('.well'),
viewRef = this;
//Find the div.well with the citation. If we never find it, we don't insert the list of contents
_.each(wells, function(well){
if(!citationEl && ($(well).find('#viewMetadataCitationLink').length > 0) || ($(well).children(".row-fluid > .span10 > a"))){
//Save this element in the view
citationEl = well;
//Mark this in the DOM for CSS styling
$(well).addClass('citation');
//Save the text of the citation
citation = $(well).text();
}
});
//Remove the unnecessary classes that are used in older versions of Metacat (2.4.3 and older)
var citationText = $(citationEl).find(".span10");
$(citationText).removeClass("span10").addClass("span12");
}
//Set the document title to the citation
MetacatUI.appModel.set("title", citation);
citationEl.remove();
},
insertBreadcrumbs: function(){
var breadcrumbs = $(document.createElement("ol"))
.addClass("breadcrumb")
.append($(document.createElement("li"))
.addClass("home")
.append($(document.createElement("a"))
.attr("href", MetacatUI.root || "/")
.addClass("home")
.text("Home")))
.append($(document.createElement("li"))
.addClass("search")
.append($(document.createElement("a"))
.attr("href", MetacatUI.root + "/data" + ((MetacatUI.appModel.get("page") > 0)? ("/page/" + (parseInt(MetacatUI.appModel.get("page"))+1)) : ""))
.addClass("search")
.text("Search")))
.append($(document.createElement("li"))
.append($(document.createElement("a"))
.attr("href", MetacatUI.root + "/view/" + this.pid)
.addClass("inactive")
.text("Metadata")));
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) : ""))
.attr("title", "Back")
.addClass("back")
.text(" Back to search")
.prepend($(document.createElement("i"))
.addClass("icon-angle-left")));
$(breadcrumbs).find("a.search").addClass("inactive");
}
this.$(this.breadcrumbContainer).html(breadcrumbs);
},
/*
* When the metadata object doesn't exist, display a message to the user
*/
showNotFound: function(){
//If the model was found, exit this function
if(!this.model.get("notFound")){
return;
}
//Construct a message that shows this object doesn't exist
var msg = "
";
//Remove the loading message
this.hideLoading();
//Show the not found error message
this.showError(msg);
},
/*
* When the metadata object is private, display a message to the user
*/
showIsPrivate: function(){
//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, "change:checked", this.showIsPrivate);
return;
}
//If the user is logged in, the message will display that this dataset is private.
if( MetacatUI.appUserModel.get("loggedIn") ){
var msg = '' +
'' +
'' +
' This is a private dataset.';
}
//If the user isn't logged in, display a log in link.
else{
var msg = '' +
'' +
'' +
' This is a private dataset. If you believe you have permission ' +
'to access this dataset, then sign in.';
}
//Remove the loading message
this.hideLoading();
//Show the not found error message
this.showError(msg);
},
getPackageDetails: function(packageIDs){
var viewRef = this;
var completePackages = 0;
//This isn't a package, but just a lonely metadata doc...
if(!packageIDs || !packageIDs.length){
var thisPackage = new Package({ id: null, members: [this.model] });
thisPackage.flagComplete();
this.packageModels = [thisPackage];
this.insertPackageDetails(thisPackage);
}
else{
_.each(packageIDs, function(thisPackageID, i){
//Create a model representing the data package
var thisPackage = new Package({ id: thisPackageID });
//Listen for any parent packages
viewRef.listenToOnce(thisPackage, "change:parentPackageMetadata", viewRef.insertParentLink);
//When the package info is fully retrieved
viewRef.listenToOnce(thisPackage, 'complete', function(thisPackage){
//When all packages are fully retrieved
completePackages++;
if(completePackages >= packageIDs.length){
var latestPackages = _.filter(viewRef.packageModels, function(m){
return !_.contains(packageIDs, m.get("obsoletedBy"));
});
viewRef.packageModels = latestPackages;
viewRef.insertPackageDetails(latestPackages);
}
});
//Save the package in the view
viewRef.packageModels.push(thisPackage);
//Make sure we get archived content, too
thisPackage.set("getArchivedMembers", true);
//Get the members
thisPackage.getMembers({getParentMetadata: true });
});
}
},
alterMarkup: function(){
//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().addClass('taxonomicCoverage');
//Remove ecogrid links and replace them with workable links
this.replaceEcoGridLinks();
},
/*
* Inserts a table with all the data package member information and sends the call to display annotations
*/
insertPackageDetails: function(packages){
//Don't insert the package details twice
var tableEls = this.$(this.tableContainer).children().not(".loading");
if(tableEls.length > 0) return;
//wait for the metadata to load
var metadataEls = this.$(this.metadataContainer).children();
if(!metadataEls.length || metadataEls.first().is(".loading")){
this.once("metadataLoaded", this.insertPackageDetails);
return;
}
var viewRef = this;
//var dataPackage = this.dataPackage;
if(!packages) var packages = this.packageModels;
//Get the entity names from this page/metadata
this.getEntityNames(packages);
_.each(packages, function(packageModel){
//If the package model is not complete, don't do anything
if(!packageModel.complete) return;
//Insert a package table for each package in viewRef dataset
var nestedPckgs = packageModel.getNestedPackages();
if(nestedPckgs.length > 0){
var title = 'Current Data Set (1 of ' + (nestedPckgs.length + 1) + ') Package: ' + packageModel.get("id") + '';
viewRef.insertPackageTable(packageModel, { title: title });
_.each(nestedPckgs, function(nestedPackage, i, list){
var title = 'Nested Data Set (' + (i+2) + ' of ' + (list.length+1) + ') Package: ' + nestedPackage.get("id") + '(View ) ';
viewRef.insertPackageTable(nestedPackage, { title: title, nested: true });
});
}
else{
var title = packageModel.get("id") ? 'Package: ' + packageModel.get("id") + '' : "";
title = "Files in this dataset " + title;
viewRef.insertPackageTable(packageModel, {title: title});
}
//Remove the extra download button returned from the XSLT since the package table will have all the download links
$("#downloadPackage").remove();
});
//Collapse the table list after the first table
var additionalTables = $(this.$("#additional-tables-for-" + this.cid)),
numTables = additionalTables.children(".download-contents").length,
item = (numTables == 1)? "dataset" : "datasets";
if(numTables > 0){
var expandIcon = $(document.createElement("i")).addClass("icon icon-level-down"),
expandLink = $(document.createElement("a"))
.attr("href", "#")
.addClass("toggle-slide toggle-display-on-slide")
.attr("data-slide-el", "additional-tables-for-" + this.cid)
.text("Show " + numTables + " nested " + item)
.prepend(expandIcon),
collapseLink = $(document.createElement("a"))
.attr("href", "#")
.addClass("toggle-slide toggle-display-on-slide")
.attr("data-slide-el", "additional-tables-for-" + this.cid)
.text("Hide nested " + item)
.hide(),
expandControl = $(document.createElement("div")).addClass("expand-collapse-control").append(expandLink, collapseLink);
additionalTables.before(expandControl);
}
//If this metadata doc is not in a package, but is just a lonely metadata doc...
if(!packages.length){
var packageModel = new Package({
members: [this.model],
});
packageModel.complete = true;
viewRef.insertPackageTable(packageModel);
}
//Insert the data details sections
this.insertDataDetails();
// Get DataPackge info in order to render prov extraced from the resmap.
if(packages.length) this.getDataPackage(packages[0].get("id"));
//Initialize tooltips in the package table(s)
this.$(".tooltip-this").tooltip();
return this;
},
insertPackageTable: function(packageModel, options){
var viewRef = this;
if(options){
var title = options.title || "";
var nested = (typeof options.nested === "undefined")? false : options.nested;
}
else
var title = "", nested = false;
if(typeof packageModel === "undefined") return;
//** Draw the package table **//
var tableView = new PackageTable({
model: packageModel,
currentlyViewing: this.pid,
parentView: this,
title: title,
nested: nested,
metricsModel: this.metricsModel
});
//Get the package table container
var tablesContainer = this.$(this.tableContainer);
//After the first table, start collapsing them
var numTables = $(tablesContainer).find("table.download-contents").length;
if(numTables == 1){
var 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;
//Insert the package table HTML
$(tableContainer).append(tableView.render().el);
$(this.tableContainer).children(".loading").remove();
$(tableContainer).find(".tooltip-this").tooltip();
this.subviews.push(tableView);
},
insertParentLink: function(packageModel){
var parentPackageMetadata = packageModel.get("parentPackageMetadata"),
view = this;
_.each(parentPackageMetadata, function(m, i){
var title = m.get("title"),
icon = $(document.createElement("i")).addClass("icon icon-on-left icon-level-up"),
link = $(document.createElement("a")).attr("href", MetacatUI.root + "/view/" + m.get("id"))
.addClass("parent-link")
.text("Parent dataset: " + title)
.prepend(icon);
view.$(view.parentLinkContainer).append(link);
});
},
insertSpatialCoverageMap: function(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){
//For EML
var title = this.$('h4:contains("Geographic Region")');
//For FGDC
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');
}
for(var i=0; i -1)
coordinate = coordinate.substring(0, coordinate.indexOf(" "));
}
}
else{
var coordinate = $(georegion).find("." + direction + "BoundingCoordinate").attr("data-value");
}
//Save our coordinate value
coordinates.push(coordinate);
});
//Extract the coordinates
var n = coordinates[0];
var s = coordinates[1];
var e = coordinates[2];
var w = coordinates[3];
}
//Create Google Map LatLng objects out of our coordinates
var latLngSW = new gmaps.LatLng(s, w);
var latLngNE = new gmaps.LatLng(n, e);
var latLngNW = new gmaps.LatLng(n, w);
var latLngSE = new gmaps.LatLng(s, e);
//Get the centertroid location of this data item
var bounds = new gmaps.LatLngBounds(latLngSW, latLngNE);
var latLngCEN = bounds.getCenter();
var url = "https://maps.google.com/?ll=" + latLngCEN.lat() + "," + latLngCEN.lng() +
"&spn=0.003833,0.010568" +
"&t=m" +
"&z=10";
//Create a google map image
var mapHTML = "";
//Find the spot in the DOM to insert our map image
if(parseText) var 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;
$(insertAfter).append(this.mapTemplate({
map: mapHTML,
url: url
}));
$('.fancybox-media').fancybox({
openEffect : 'elastic',
closeEffect : 'elastic',
helpers: {
media: {}
}
})
}
return true;
},
insertCitation: function(){
if(!this.model) return false;
//Create a citation element from the model attributes
var citation = new CitationView({
model: this.model,
createLink: false }).render().el;
this.$(this.citationContainer).html(citation);
},
insertDataSource: function(){
if(!this.model || !MetacatUI.nodeModel || !MetacatUI.nodeModel.get("members").length || !this.$(this.dataSourceContainer).length) return;
var dataSource = MetacatUI.nodeModel.getMember(this.model),
replicaMNs = MetacatUI.nodeModel.getMembers(this.model.get("replicaMN"));
//Filter out the data source from the replica nodes
if(Array.isArray(replicaMNs) && replicaMNs.length){
replicaMNs = _.without(replicaMNs, dataSource);
}
if(dataSource && dataSource.logo){
this.$("img.data-source").remove();
//Insert the data source template
this.$(this.dataSourceContainer).html(this.dataSourceTemplate({
node : dataSource
})).addClass("has-data-source");
this.$(this.citationContainer).addClass("has-data-source");
this.$(".tooltip-this").tooltip();
$(".popover-this.data-source.logo").popover({
trigger: "manual",
html: true,
title: "From the " + dataSource.name + " repository",
content: function(){
var content = "
" + dataSource.description + "
";
if(replicaMNs.length){
content += '
Exact copies hosted by ' + replicaMNs.length + ' repositories:
";
}
return content;
},
animation:false
})
.on("mouseenter", function () {
var _this = this;
$(this).popover("show");
$(".popover").on("mouseleave", function () {
$(_this).popover('hide');
});
}).on("mouseleave", function () {
var _this = this;
setTimeout(function () {
if (!$(".popover:hover").length) {
$(_this).popover("hide");
}
}, 300);
});
}
},
/*
* Checks the authority for the logged in user for this dataset
* and inserts control elements onto the page for the user to interact with the dataset - edit, publish, etc.
*/
insertOwnerControls: function(){
if( !MetacatUI.appModel.get("publishServiceUrl") )
return false;
//Do not show user controls for older versions of data sets
if(this.model.get("obsoletedBy") && (this.model.get("obsoletedBy").length > 0))
return false;
var container = this.$(this.ownerControlsContainer);
//Save some references
var pid = this.model.get("id") || this.pid,
model = this.model,
viewRef = this;
this.listenToOnce(this.model, "change:isAuthorized", function(){
if(!model.get("isAuthorized") || model.get("archived")) return false;
//Insert an Edit button
if( _.contains(MetacatUI.appModel.get("editableFormats"), this.model.get("formatId")) ){
container.append(
this.editMetadataTemplate({
identifier: pid,
supported: true
}));
}
else{
container.append(this.editMetadataTemplate({
supported: false
}));
}
//Insert a Publish button if its not already published with a DOI
if(!model.isDOI()){
//Insert the template
container.append(
viewRef.doiTemplate({
isAuthorized: true,
identifier: pid
}));
}
//Check the authority on the package models
//If there is no package, then exit now
if(!viewRef.packageModels || !viewRef.packageModels.length) return;
//Check for authorization on the resource map
var packageModel = this.packageModels[0];
//if there is no package, then exit now
if(!packageModel.get("id")) return;
//Listen for changes to the authorization flag
//packageModel.once("change:isAuthorized", viewRef.createProvEditor, viewRef);
//packageModel.once("sync", viewRef.createProvEditor, viewRef);
//Now get the RDF XML and check for the user's authority on this resource map
packageModel.fetch();
packageModel.checkAuthority();
});
this.model.checkAuthority();
},
/*
* Injects Clipboard objects onto DOM elements returned from the Metacat
* View Service. This code depends on the implementation of the Metacat
* 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 your XSLT and this should pick it up automatically.
*/
insertCopiables: function(){
var copiables = $("#Metadata .copy");
_.each(copiables, function(copiable) {
var clipboard = new Clipboard(copiable);
clipboard.on("success", function(e) {
var el = $(e.trigger);
$(el).html( $(document.createElement("span")).addClass("icon icon-ok success") );
// Use setTimeout instead of jQuery's built-in Events system because
// it didn't look flexible enough to allow me update innerHTML in
// a chain
setTimeout(function() {
$(el).html("Copy");
}, 500)
});
});
},
/*
* Inserts elements users can use to interact with this dataset:
* - A "Copy Citation" button to copy the citation text
*/
insertControls: function(){
//Get template
var controlsContainer = this.controlsTemplate({
citation: $(this.citationContainer).text(),
url: window.location,
mdqUrl: MetacatUI.appModel.get("mdqBaseUrl"),
showWholetale: MetacatUI.appModel.get("showWholeTaleFeatures"),
model: this.model.toJSON()
});
$(this.controlsContainer).html(controlsContainer);
var view = this;
//Insert the info icons
var metricsWell = this.$(".metrics-container");
metricsWell.append( this.infoIconsTemplate({
model: this.model.toJSON()
}) );
if(MetacatUI.appModel.get("showWholeTaleFeatures")) {
this.createWholeTaleButton ();
}
//Create clickable "Copy" buttons to copy text (e.g. citation) to the user's clipboard
var copyBtns = $(this.controlsContainer).find(".copy");
_.each(copyBtns, function(btn){
//Create a copy citation button
var clipboard = new Clipboard(btn);
clipboard.on("success", function(e){
var originalWidth = $(e.trigger).width();
$(e.trigger).html( $(document.createElement("span")).addClass("icon icon-ok success") )
.append(" Copied")
.addClass("success")
.css("width", originalWidth + "px");
setTimeout(function() {
$(e.trigger).html(" Copy Citation")
.removeClass("success")
.css("width", "auto");
}, 500)
});
clipboard.on("error", function(e){
if(!$(e.trigger).prev("input.copy").length){
var textarea = $(document.createElement("input")).val($(e.trigger).attr("data-clipboard-text")).addClass("copy").css("width", "0");
textarea.tooltip({
title: "Press Ctrl+c to copy",
placement: "top"
});
$(e.trigger).before(textarea);
}
else{
var textarea = $(e.trigger).prev("input.copy");
}
textarea.animate({ width: "100px" }, {
duration: "slow",
complete: function(){
textarea.trigger("focus");
textarea.tooltip("show");
}
});
textarea.focusout(function(){
textarea.animate({ width: "0px" }, function(){
textarea.remove();
})
});
});
});
this.$(".tooltip-this").tooltip();
},
/**
*Creates a button which the user can click to launch the package in Whole Tale
*/
createWholeTaleButton: function() {
let self=this;
MetacatUI.appModel.get('taleEnvironments').forEach(function(environment){
var queryParams=
'?uri='+ self.model.id +
'&title='+encodeURIComponent(self.model.get("title"))+
'&environment='+environment;
var composeUrl = MetacatUI.appModel.get('dashboardUrl')+queryParams;
var anchor = $('');
anchor.attr('href',composeUrl).append(
$('').attr('class', 'tab').append(environment));
anchor.attr('target', '_blank');
$('.analyze.dropdown-menu').append($('
";
}
}
function initializeLightboxes(){
numChecks++;
//Initialize what images have loaded so far after 5 seconds
if(numChecks == 10){
$(lightboxSelector).fancybox(lightboxOptions);
}
//When 15 seconds have passed, stop checking so we don't blow up the browser
else if(numChecks > 30){
window.clearInterval(intervalID);
return;
}
//Are all of our pdfs loaded yet?
if(viewRef.$(lightboxSelector).length < numPDFs) return;
else{
//Initialize our lightboxes
$(lightboxSelector).fancybox(lightboxOptions);
//We're done - clear the interval
window.clearInterval(intervalID);
}
}
},
replaceEcoGridLinks: function(){
var viewRef = this;
//Find the element in the DOM housing the ecogrid link
$("a:contains('ecogrid://')").each(function(i, thisLink){
//Get the link text
var linkText = $(thisLink).text();
//Clean up the link text
var withoutPrefix = linkText.substring(linkText.indexOf("ecogrid://") + 10),
pid = withoutPrefix.substring(withoutPrefix.indexOf("/")+1),
baseUrl = MetacatUI.appModel.get('resolveServiceUrl') || MetacatUI.appModel.get('objectServiceUrl');
$(thisLink).attr('href', baseUrl + encodeURIComponent(pid)).text(pid);
});
},
publish: function(event) {
// target may not actually prevent click events, so double check
var disabled = $(event.target).closest("a").attr("disabled");
if (disabled) {
return false;
}
var publishServiceUrl = MetacatUI.appModel.get('publishServiceUrl');
var pid = $(event.target).closest("a").attr("pid");
var ret = confirm("Are you sure you want to publish " + pid + " with a DOI?");
if (ret) {
// show the loading icon
var message = "Publishing package...this may take a few moments";
this.showLoading(message);
var identifier = null;
var viewRef = this;
var requestSettings = {
url: publishServiceUrl + pid,
type: "PUT",
xhrFields: {
withCredentials: true
},
success: function(data, textStatus, xhr) {
// the response should have new identifier in it
identifier = $(data).find("d1\\:identifier, identifier").text();
if (identifier) {
viewRef.hideLoading();
var msg = "Published data package '" + identifier + "'. If you are not redirected soon, you can view your published data package here";
viewRef.$el.find('.container').prepend(
viewRef.alertTemplate({
msg: msg,
classes: 'alert-success'
})
);
// navigate to the new view after a few seconds
setTimeout(
function() {
// avoid a double fade out/in
viewRef.$el.html('');
viewRef.showLoading();
MetacatUI.uiRouter.navigate("view/" + identifier, {trigger: true})
},
3000);
}
},
error: function(xhr, textStatus, errorThrown) {
// show the error message, but stay on the same page
var msg = "Publish failed: " + $(xhr.responseText).find("description").text();
viewRef.hideLoading();
viewRef.showError(msg);
}
}
$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
}
},
//When the given ID from the URL is a resource map that has no metadata, do the following...
noMetadata: function(solrResultModel){
this.hideLoading();
this.$el.html(this.template());
this.pid = solrResultModel.get("resourceMap") || solrResultModel.get("id");
//Insert breadcrumbs
this.insertBreadcrumbs();
this.insertDataSource();
//Insert a table of contents
this.insertPackageTable(solrResultModel);
this.renderMetadataFromIndex();
//Insert a message that this data is not described by metadata
MetacatUI.appView.showAlert("Additional information about this data is limited since metadata was not provided by the creator.", "alert-warning", this.$(this.metadataContainer));
},
// this will lookup the latest version of the PID
showLatestVersion: function() {
//If this metadata doc is not obsoleted by a new version, then exit the function
if( !this.model.get("obsoletedBy") ){
return;
}
var view = this;
//When the latest version is found,
this.listenTo(this.model, "change:newestVersion", function(){
//Make sure it has a newer version, and if so,
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({
pid: view.model.get("newestVersion")
}));
}
else{
view.$(".newer-version").remove();
}
});
//Insert the newest version template with a loading message
this.$el.prepend( this.versionTemplate({
loading: true
}) );
//Find the latest version of this metadata object
this.model.findLatestVersion();
},
showLoading: function(message) {
this.hideLoading();
MetacatUI.appView.scrollToTop();
var loading = this.loadingTemplate({ msg: message });
if(!loading) return;
this.$loading = $($.parseHTML(loading));
this.$detached = this.$el.children().detach();
this.$el.html(loading);
},
hideLoading: function() {
if(this.$loading) this.$loading.remove();
if(this.$detached) this.$el.html(this.$detached);
},
showError: function(msg){
//Remove any existing error messages
this.$el.children(".alert-container").remove();
this.$el.prepend(
this.alertTemplate({
msg: msg,
classes: 'alert-error',
containerClasses: "page",
includeEmail: true
}));
},
setUpAnnotator: function() {
if(!MetacatUI.appModel.get("annotatorUrl")) return;
var annotator = new AnnotatorView({
parentView: this
});
this.subviews.push(annotator);
annotator.render();
},
/**
* 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)
**/
previewData: function(e){
//Don't go anywhere yet...
e.preventDefault();
//Get the target and id of the click
var 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 we are on the Metadata view, then let's scroll to the anchor
MetacatUI.appView.scrollTo( this.findEntityDetailsContainer(id) );
return true;
},
closePopovers: function(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
var svg = $(e.target).parents("svg");
if(_.contains(e.target.classList, "popover-this") ||
($(e.target).parents(".popover-this").length > 0) ||
($(e.target).parents(".popover").length > 0) ||
_.contains(e.target.classList, "popover") ||
(svg.length && _.contains(svg[0].classList, "popover-this"))) return;
//Close all active popovers
this.$(".popover-this.active").popover("hide");
},
highlightNode: function(e){
//Find the id
var id = $(e.target).attr("data-id");
if((typeof id === "undefined") || (!id))
id = $(e.target).parents("[data-id]").attr("data-id");
//If there is no id, return
if(typeof id === "undefined") return false;
//Highlight its node
$(".prov-chart .node[data-id='" + id + "']").toggleClass("active");
//Highlight its metadata section
if(MetacatUI.appModel.get("pid") == id)
this.$("#Metadata").toggleClass("active");
else{
var entityDetails = this.findEntityDetailsContainer(id);
if(entityDetails)
entityDetails.toggleClass("active");
}
},
onClose: function () {
var viewRef = this;
this.stopListening();
_.each(this.subviews, function(subview) {
if(subview.onClose)
subview.onClose();
});
this.packageModels = new Array();
this.model.set(this.model.defaults);
this.pid = null;
this.seriesId = null;
this.$detached = null;
this.$loading = null;
//Put the document title back to the default
MetacatUI.appModel.set("title", MetacatUI.appModel.defaults.title);
//Remove view-specific classes
this.$el.removeClass("container no-stylesheet");
this.$el.empty();
},
/**
* 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.
*/
getAuthorText: function() {
var authors = this.model.get("origin"),
count = 0,
authorText = "";
_.each(authors, function (author) {
count++;
if (count == 6) {
authorText += ", et al. ";
return;
} else if (count > 6) {
return;
}
if (count > 1) {
if (authors.length > 2) {
authorText += ",";
}
if (count == authors.length) {
authorText += " and";
}
if (authors.length > 1) {
authorText += " ";
}
}
authorText += author;
});
return authorText;
},
/**
* 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.
*/
getPublisherText: function() {
var datasource = this.model.get("datasource"),
memberNode = MetacatUI.nodeModel.getMember(datasource);
if (memberNode) {
return memberNode.name;
} else {
return datasource;
}
},
/**
* Generate a string appropriate to be used as the publication date in a
* dataset citation.
*/
getDatePublishedText: function() {
// Dataset/datePublished
// Prefer pubDate, fall back to dateUploaded so we have something to show
if (this.model.get("pubDate") !== "") {
return this.model.get("pubDate")
} else {
return this.model.get("dateUploaded")
}
},
/**
* 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.
*/
generateJSONLD: function () {
var model = this.model;
// Determine the path (either #view or view, depending on router
// configuration) for use in the 'url' property
var href = document.location.href,
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.
var elJSON = {
"@context": {
"@vocab": "http://schema.org",
},
"@type": "Dataset",
"@id": "https://dataone.org/datasets/" +
encodeURIComponent(model.get("id")),
"datePublished" : this.getDatePublishedText(),
"publisher": this.getPublisherText(),
"identifier": model.get("id"),
"url": "https://dataone.org/datasets/" +
encodeURIComponent(model.get("id")),
"schemaVersion": model.get("formatId"),
};
// Second: Add in optional fields
// Name
if (model.get("title")) {
elJSON["name"] = model.get("title")
}
// Creator
if (model.get("origin")) {
elJSON["creator"] = model.get("origin")
}
// Dataset/spatialCoverage
if (model.get("northBoundCoord") &&
model.get("eastBoundCoord") &&
model.get("southBoundCoord") &&
model.get("westBoundCoord")) {
var spatialCoverage = {
"@type": "Place",
"additionalProperty": [
{
"@type": "PropertyValue",
"additionalType": "http://dbpedia.org/resource/Coordinate_reference_system",
"name": "Coordinate Reference System",
"value": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
}
],
"geo": this.generateSchemaOrgGeo(model.get("northBoundCoord"),
model.get("eastBoundCoord"),
model.get("southBoundCoord"),
model.get("westBoundCoord")),
"subjectOf": {
"@type": "CreativeWork",
"fileFormat": "application/vnd.geo+json",
"text": this.generateGeoJSONString(model.get("northBoundCoord"),
model.get("eastBoundCoord"),
model.get("southBoundCoord"),
model.get("westBoundCoord"))
}
};
elJSON.spatialCoverage = spatialCoverage;
}
// Dataset/temporalCoverage
if (model.get("beginDate") && !model.get("endDate")) {
elJSON.temporalCoverage = model.get("beginDate");
} else if (model.get("beginDate") && model.get("endDate")) {
elJSON.temporalCoverage = model.get("beginDate") + "/" + model.get("endDate");
}
// Dataset/variableMeasured
if (model.get("attributeName")) {
elJSON.variableMeasured = model.get("attributeName");
}
// Dataset/description
if (model.get("abstract")) {
elJSON.description = model.get("abstract");
}
// Dataset/keywords
if (model.get("keywords")) {
elJSON.keywords = model.get("keywords").join(", ");
}
return elJSON;
},
/**
* Insert Schema.org-compliant JSONLD for the model bound to the view into
* the head tag of the page (at the end).
*
* @param {object} json - JSON-LD to insert into the page
*
* Some notes:
*
* - Checks if the JSONLD already exists from the previous data view
* - If not create a new script tag and append otherwise replace the text
* for the script
*/
insertJSONLD: function(json) {
if (!document.getElementById('jsonld')) {
var el = document.createElement('script');
el.type = 'application/ld+json';
el.id = 'jsonld';
el.text = JSON.stringify(json);
document.querySelector('head').appendChild(el);
} else {
var script = document.getElementById('jsonld');
script.text = JSON.stringify(json);
}
},
/**
* Generate a Schema.org/Place/geo from bounding coordinates
*
* Either generates a GeoCoordinates (when the north and east coords are
* the same) or a GeoShape otherwise.
*/
generateSchemaOrgGeo: function(north, east, south, west) {
if (north === south) {
return {
"@type": "GeoCoordinates",
"latitude" : north,
"longitude" : west
}
} else {
return {
"@type": "GeoShape",
"box": west + ", " + south + " " + east + ", " + north
}
}
},
/**
* Creates a (hopefully) valid geoJSON string from the a set of bounding
* coordinates from the Solr index (north, east, south, west).
*
* This function produces either a GeoJSON Point or Polygon depending on
* 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
* for datasets that cross the Pacific Ocean. In this case, We need to
* convert the east bounding coordinate from degrees west to degrees east.
*
* e.g., if the east bounding coordinate is 120 W and west bounding
* coordinate is 140 E, geoJSON requires we specify 140 E as 220
*
* @param {number} north - North bounding coordinate
* @param {number} east - East bounding coordinate
* @param {number} south - South bounding coordinate
* @param {number} west - West bounding coordinate
*/
generateGeoJSONString: function(north, east, south, west) {
if (north === south) {
return this.generateGeoJSONPoint(north, east);
} else {
return this.generateGeoJSONPolygon(north, east, south, west);
}
},
/**
* Generate a GeoJSON Point object
*
* @param {number} north - North bounding coordinate
* @param {number} east - East bounding coordinate
*
* Example:
* {
* "type": "Point",
* "coordinates": [
* -105.01621,
* 39.57422
* ]}
*/
generateGeoJSONPoint: function(north, east) {
var preamble = "{\"type\":\"Point\",\"coordinates\":",
inner = "[" + east + "," + north + "]",
postamble = "}";
return preamble + inner + postamble;
},
/**
* Generate a GeoJSON Polygon object from
*
* @param {number} north - North bounding coordinate
* @param {number} east - East bounding coordinate
* @param {number} south - South bounding coordinate
* @param {number} west - West bounding coordinate
*
*
* Example:
*
* {
* "type": "Polygon",
* "coordinates": [[
* [ 100, 0 ],
* [ 101, 0 ],
* [ 101, 1 ],
* [ 100, 1 ],
* [ 100, 0 ]
* ]}
*
*/
generateGeoJSONPolygon: function(north, east, south, west) {
var preamble = "{\"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
}
var inner = "[" + west + "," + south + "]," +
"[" + east + "," + south + "]," +
"[" + east + "," + north + "]," +
"[" + west + "," + north + "]," +
"[" + west + "," + south + "]";
var postamble = "]]}}";
return preamble + inner + postamble;
},
/**
* Insert citation information as meta tags into the head of the page
*
* Currently supports Highwire Press style tags (citation_) which is
* supposedly what Google (Scholar), Mendeley, and Zotero support.
*/
insertCitationMetaTags: function() {
// Generate template data to use for all templates
var title = this.model.get("title"),
authors = this.getAuthorText(),
publisher = this.getPublisherText(),
date = new Date(this.getDatePublishedText()).getUTCFullYear().toString();
// Generate HTML strings from each template
var hwpt = this.metaTagsHighwirePressTemplate({
title: title,
authors: authors,
publisher: publisher,
date: date
});
// Clear any that are already in the document.
$("meta[name='citation_title']").remove();
$("meta[name='citation_authors']").remove();
$("meta[name='citation_publisher']").remove();
$("meta[name='citation_date']").remove();
// Insert
document.head.insertAdjacentHTML("beforeend", hwpt);
}
});
return MetadataView;
});