/*global define */
define(['jquery',
'jqueryui',
'underscore',
'backbone',
'gmaps',
'fancybox',
'clipboard',
'collections/DataPackage',
'models/DataONEObject',
'models/PackageModel',
'models/SolrResult',
'models/metadata/ScienceMetadata',
'models/MetricsModel',
'models/Utilities',
'views/DownloadButtonView',
'views/ProvChartView',
'views/MetadataIndexView',
'views/ExpandCollapseListView',
'views/ProvStatementView',
'views/PackageTableView',
'views/CitationView',
'views/AnnotationView',
'views/MarkdownView',
'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/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, Utilities, DownloadButtonView, ProvChart, MetadataIndex, ExpandCollapseList, ProvStatement, PackageTable,
CitationView, AnnotationView, MarkdownView, MetadataTemplate, DataSourceTemplate, PublishDoiTemplate,
VersionTemplate, LoadingTemplate, ControlsTemplate, MetadataInfoIconsTemplate, AlertTemplate, EditMetadataTemplate, DataDisplayTemplate,
MapTemplate, AnnotationTemplate, metaTagsHighwirePressTemplate, uuid, MetricView) {
'use strict';
/**
* @class MetadataView
* @classdesc A human-readable view of a science metadata file
* @classcategory Views
* @extends Backbone.View
* @constructor
*/
var MetadataView = Backbone.View.extend(
/** @lends MetadataView.prototype */{
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),
versionTemplate: _.template(VersionTemplate),
loadingTemplate: _.template(LoadingTemplate),
controlsTemplate: _.template(ControlsTemplate),
infoIconsTemplate: _.template(MetadataInfoIconsTemplate),
dataSourceTemplate: _.template(DataSourceTemplate),
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);
//Listen to when the metadata has been rendered
this.once("metadataLoaded", function(){
this.createAnnotationViews();
this.insertMarkdownViews();
});
//Listen to when the package table has been rendered
this.once("packageTableRendered", function(){
//Scroll to the element on the page that is in the hash fragment (if there is one)
this.scrollToFragment();
});
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 DataONEObject model to use in the DataPackage collection.
var dataOneObject = new ScienceMetadata({ id: this.model.get("id") });
// Create a new data package with this id
this.dataPackage = new DataPackage([dataOneObject], {id: pid});
this.dataPackage.mergeModels([ this.model ]);
//Fetch the data package. DataPackage.parse() triggers 'complete'
this.dataPackage.fetch({
fetchModels: false
});
this.listenToOnce(this.dataPackage, "complete", function() {
// parseProv triggers "queryComplete"
this.dataPackage.parseProv();
this.checkForProv();
var packageTableView = _.findWhere(this.subviews, { type: "PackageTable"});
if( packageTableView ){
packageTableView.dataPackageCollection = this.dataPackage;
packageTableView.checkForPrivateMembers();
}
});
},
/*
* 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"){
//Get the metadata pids that document this data object
var isDocBy = this.model.get("isDocumentedBy");
//If there is only one metadata pid that documents this data object, then
// get that metadata model for this view.
if(isDocBy && isDocBy.length == 1){
this.pid = _.first(isDocBy);
this.getModel(this.pid);
return;
}
//If more than one metadata doc documents this data object, it is most likely
// multiple versions of the same metadata. So we need to find the latest version.
else if( isDocBy && isDocBy.length > 1 ){
var view = this;
require(["collections/Filters", "collections/SolrResults"], function(Filters, SolrResults){
//Create a search for the metadata docs that document this data object
var searchFilters = new Filters([{
values: isDocBy,
fields: ["id", "seriesId"],
operator: "OR",
matchSubstring: false
}]),
//Create a list of search results
searchResults = new SolrResults([], {
rows: isDocBy.length,
query: searchFilters.getQuery(),
fields: "obsoletes,obsoletedBy,id"
});
//When the search results are returned, process those results
view.listenToOnce(searchResults, "sync", function(searchResults){
//Keep track of the latest version of the metadata doc(s)
var latestVersions = [];
//Iterate over each search result and find the latest version of each metadata version chain
searchResults.each( function(searchResult){
//If this metadata isn't obsoleted by another object, it is the latest version
if( !searchResult.get("obsoletedBy") ){
latestVersions.push( searchResult.get("id") );
}
//If it is obsoleted by another object but that newer object does not document this data, then this is the latest version
else if( !_.contains(isDocBy, searchResult.get("obsoletedBy")) ){
latestVersions.push( searchResult.get("id") );
}
}, view);
//If at least one latest version was found (should always be the case),
if( latestVersions.length ){
//Set that metadata pid as this view's pid and get that metadata model.
// TODO: Support navigation to multiple metadata docs. This should be a rare occurence, but
// it is possible that more than one metadata version chain documents a data object, and we need
// to show the user that the data is involved in multiple datasets.
view.pid = latestVersions[0];
view.getModel(latestVersions[0]);
}
//If a latest version wasn't found, which should never happen, but just in case, default to the
// last metadata pid in the isDocumentedBy field (most liekly to be the most recent since it was indexed last).
else{
var fallbackPid = _.last(isDocBy);
view.pid = fallbackPid;
view.getModel(fallbackPid);
}
});
//Send the query to the Solr search service
searchResults.query();
});
return;
}
else{
this.noMetadata(this.model);
}
}
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/" + encodeURIComponent(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);
//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(this.model.get("id")));
},
/*
* 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 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
this.listenToOnce(thisPackage, "change:parentPackageMetadata", this.insertParentLink);
//When the package info is fully retrieved
this.listenToOnce(thisPackage, 'complete', function(thisPackage){
//When all packages are fully retrieved
completePackages++;
if(completePackages >= packageIDs.length){
var latestPackages = _.filter(this.packageModels, function(m){
return !_.contains(packageIDs, m.get("obsoletedBy"));
});
this.packageModels = latestPackages;
this.insertPackageDetails(latestPackages);
}
});
//Save the package in the view
this.packageModels.push(thisPackage);
//Make sure we get archived content, too
thisPackage.set("getArchivedMembers", true);
//Get the members
thisPackage.getMembers({getParentMetadata: true });
}, this);
}
},
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();
//Find the tab links for attribute names
this.$(".attributeListTable tr a").on('shown', function(e){
//When the attribute link is clicked on, highlight the tab as active
$(e.target).parents(".attributeListTable").find(".active").removeClass("active");
$(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
this.$(".attributeListTable tr:first-child()").addClass("active");
},
/*
* 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;
}
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(),
nestedPckgsToDisplay = [];
//If this metadata is not archived, filter out archived packages
if( !this.model.get("archived") ){
nestedPckgsToDisplay = _.reject(nestedPckgs, function(pkg){
return (pkg.get("archived"))
});
}
else{
//Display all packages is this metadata is archived
nestedPckgsToDisplay = nestedPckgs;
}
if(nestedPckgsToDisplay.length > 0){
if( !(!this.model.get("archived") && packageModel.get("archived") == true) ){
var title = 'Current Data Set (1 of ' + (nestedPckgsToDisplay.length + 1) + ') Package: ' + packageModel.get("id") + '';
this.insertPackageTable(packageModel, { title: title });
}
_.each(nestedPckgsToDisplay, function(nestedPackage, i, list){
if( !(!this.model.get("archived") && nestedPackage.get("archived") == true) ){
var title = 'Nested Data Set (' + (i+2) + ' of ' +
(list.length+1) + ') Package: ' +
nestedPackage.get("id") + '(View ) ';
this.insertPackageTable(nestedPackage, { title: title, nested: true });
}
}, this);
}
else{
//If this metadata is not archived, then don't display archived packages
if( !(!this.model.get("archived") && packageModel.get("archived") == true) ){
var title = packageModel.get("id") ? 'Package: ' + packageModel.get("id") + '' : "";
title = "Files in this dataset " + title;
this.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();
}, this);
//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;
this.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);
//Trigger a custom event in this view that indicates the package table has been rendered
this.trigger("packageTableRendered");
},
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/" + encodeURIComponent(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();
//If there isn't a center point found, don't draw the map.
if( typeof latLngCEN == "undefined" ){
return;
}
var url = "https://maps.google.com/?ll=" + latLngCEN.lat() + "," + latLngCEN.lng() +
"&spn=0.003833,0.010568" +
"&t=m" +
"&z=5";
//Get the map path color
var pathColor = MetacatUI.appModel.get("datasetMapPathColor");
if( pathColor ){
pathColor = "color:" + pathColor + "|";
}
else{
pathColor = "";
}
//Get the map path fill color
var fillColor = MetacatUI.appModel.get("datasetMapFillColor");
if( fillColor ){
fillColor = "fillcolor:" + fillColor + "|";
}
else{
fillColor = "";
}
//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();
//Construct a URL to the profile of this repository
var profileURL = (dataSource.identifier == MetacatUI.appModel.get("nodeId"))?
MetacatUI.root + "/profile" :
MetacatUI.appModel.get("dataoneSearchUrl") + "/portals/" + dataSource.shortIdentifier;
//Insert the data source template
this.$(this.dataSourceContainer).html(this.dataSourceTemplate({
node : dataSource,
profileURL: profileURL
})).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(){
//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 the Edit button is enabled
if( MetacatUI.appModel.get("displayDatasetEditButton") ){
//Check that this is an editable metadata format
if( _.contains(MetacatUI.appModel.get("editableFormats"), this.model.get("formatId")) ){
//Insert the Edit Metadata template
container.append(
this.editMetadataTemplate({
identifier: pid,
supported: true
}));
}
//If this format is not editable, insert an unspported Edit Metadata template
else{
container.append(this.editMetadataTemplate({
supported: false
}));
}
}
try{
//Determine if this metadata can be published.
// The Publish feature has to be enabled in the app.
// The model cannot already have a DOI
var canBePublished = MetacatUI.appModel.get("enablePublishDOI") && !model.isDOI();
//If publishing is enabled, check if only certain users and groups can publish metadata
if( canBePublished ){
//Get the list of authorized publishers from the AppModel
var authorizedPublishers = MetacatUI.appModel.get("enablePublishDOIForSubjects");
//If the logged-in user is one of the subjects in the list or is in a group that is
// in the list, then this metadata can be published. Otherwise, it cannot.
if( Array.isArray(authorizedPublishers) && authorizedPublishers.length ){
if( MetacatUI.appUserModel.hasIdentityOverlap(authorizedPublishers) ){
canBePublished = true;
}
else{
canBePublished = false;
}
}
}
//If this metadata can be published, then insert the Publish button template
if( canBePublished ){
//Insert a Publish button template
container.append(
viewRef.doiTemplate({
isAuthorized: true,
identifier: pid
}));
}
}
catch(e){
console.error("Cannot display the publish button: ", e);
}
//Check the authority on the package models
//If there is no package, then exit now
if(!viewRef.packageModels || !viewRef.packageModels.length)
return;
//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;
//Now get the RDF XML and check for the user's authority on this resource map
packageModel.fetch();
packageModel.checkAuthority();
});
//Check if the current user has authority to `changePermission` on this metadata
this.model.checkAuthority();
},
/*
* 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(){
// 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, '\\\\$&');
};
var 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.
var thisFormatId = this.model.get("formatId");
var mdqFormatSupported = false;
var formatFound = false;
if(mdqFormatIds !== null) {
for (var ifmt = 0; ifmt < mdqFormatIds.length; ++ifmt) {
var currentFormatId = RegExp.escape(mdqFormatIds[ifmt]);
var re = new RegExp(currentFormatId);
formatFound = re.test(thisFormatId);
if(formatFound) {
break;
}
}
}
//Get template
var controlsContainer = this.controlsTemplate({
citationTarget: this.citationContainer,
url: window.location,
displayQualtyReport: MetacatUI.appModel.get("mdqBaseUrl") && formatFound && MetacatUI.appModel.get("displayDatasetQualityMetric"),
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='+ window.location.href+
'&title='+encodeURIComponent(self.model.get("title"))+
'&environment='+environment+
'&api='+MetacatUI.appModel.get("d1CNBaseUrl")+MetacatUI.appModel.get("d1CNService");
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($('