define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
$,
_,
Backbone,
DataONEObject,
) {
/**
* @class EMLParty
* @classcategory Models/Metadata/EML211
* @classdesc EMLParty represents a single Party from the EML 2.1.1 and 2.2.0
* metadata schema. This can be a person or organization.
* @see https://eml.ecoinformatics.org/schema/eml-party_xsd.html#ResponsibleParty
* @extends Backbone.Model
* @constructor
*/
var EMLParty = Backbone.Model.extend(
/** @lends EMLParty.prototype */ {
defaults: function () {
return {
objectXML: null,
objectDOM: null,
individualName: null,
organizationName: null,
positionName: null,
address: [],
phone: [],
fax: [],
email: [],
onlineUrl: [],
roles: [],
references: null,
userId: [],
xmlID: null,
type: null,
typeOptions: [
"associatedParty",
"contact",
"creator",
"metadataProvider",
"publisher",
],
roleOptions: [
"custodianSteward",
"principalInvestigator",
"collaboratingPrincipalInvestigator",
"coPrincipalInvestigator",
"user",
],
parentModel: null,
removed: false, //Indicates whether this model has been removed from the parent model
};
},
initialize: function (options) {
if (options && options.objectDOM)
this.set(this.parse(options.objectDOM));
if (!this.get("xmlID")) this.createID();
this.on("change:roles", this.setType);
},
/*
* Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
* Used during parse() and serialize()
*/
nodeNameMap: function () {
return {
administrativearea: "administrativeArea",
associatedparty: "associatedParty",
deliverypoint: "deliveryPoint",
electronicmailaddress: "electronicMailAddress",
givenname: "givenName",
individualname: "individualName",
metadataprovider: "metadataProvider",
onlineurl: "onlineUrl",
organizationname: "organizationName",
positionname: "positionName",
postalcode: "postalCode",
surname: "surName",
userid: "userId",
};
},
/*
Parse the object DOM to create the model
@param objectDOM the XML DOM to parse
@return modelJSON the resulting model attributes object
*/
parse: function (objectDOM) {
if (!objectDOM) var objectDOM = this.get("objectDOM");
var model = this,
modelJSON = {};
//Set the name
var person = $(objectDOM).children("individualname, individualName");
if (person.length) modelJSON.individualName = this.parsePerson(person);
//Set the phone and fax numbers
var phones = $(objectDOM).children("phone"),
phoneNums = [],
faxNums = [];
phones.each(function (i, phone) {
if ($(phone).attr("phonetype") == "voice")
phoneNums.push($(phone).text());
else if ($(phone).attr("phonetype") == "facsimile")
faxNums.push($(phone).text());
});
modelJSON.phone = phoneNums;
modelJSON.fax = faxNums;
//Set the address
var addresses = $(objectDOM).children("address") || [],
addressesJSON = [];
addresses.each(function (i, address) {
addressesJSON.push(model.parseAddress(address));
});
modelJSON.address = addressesJSON;
//Set the text fields
modelJSON.organizationName =
$(objectDOM).children("organizationname, organizationName").text() ||
null;
modelJSON.positionName =
$(objectDOM).children("positionname, positionName").text() || null;
// roles
modelJSON.roles = [];
$(objectDOM)
.find("role")
.each(function (i, role) {
modelJSON.roles.push($(role).text());
});
//Set the id attribute
modelJSON.xmlID = $(objectDOM).attr("id");
//Email - only set it on the JSON if it exists (we want to avoid an empty string value in the array)
if (
$(objectDOM).children("electronicmailaddress, electronicMailAddress")
.length
) {
modelJSON.email = _.map(
$(objectDOM).children(
"electronicmailaddress, electronicMailAddress",
),
function (email) {
return $(email).text();
},
);
}
//Online URL - only set it on the JSON if it exists (we want to avoid an empty string value in the array)
if ($(objectDOM).find("onlineurl, onlineUrl").length) {
// modelJSON.onlineUrl = [$(objectDOM).find("onlineurl, onlineUrl").first().text()];
modelJSON.onlineUrl = $(objectDOM)
.find("onlineurl, onlineUrl")
.map(function (i, v) {
return $(v).text();
})
.get();
}
//User ID - only set it on the JSON if it exists (we want to avoid an empty string value in the array)
if ($(objectDOM).find("userid, userId").length) {
modelJSON.userId = [
$(objectDOM).find("userid, userId").first().text(),
];
}
return modelJSON;
},
parseNode: function (node) {
if (!node || (Array.isArray(node) && !node.length)) return;
this.set($(node)[0].localName, $(node).text());
},
parsePerson: function (personXML) {
var person = {
givenName: [],
surName: "",
salutation: [],
},
givenName = $(personXML).find("givenname, givenName"),
surName = $(personXML).find("surname, surName"),
salutations = $(personXML).find("salutation");
//Concatenate all the given names into one, for now
//TODO: Support multiple given names
givenName.each(function (i, name) {
if (i == 0) person.givenName[0] = "";
person.givenName[0] += $(name).text() + " ";
if (i == givenName.length - 1)
person.givenName[0] = person.givenName[0].trim();
});
person.surName = surName.text();
salutations.each(function (i, name) {
person.salutation.push($(name).text());
});
return person;
},
parseAddress: function (addressXML) {
var address = {},
delPoint = $(addressXML).find("deliverypoint, deliveryPoint"),
city = $(addressXML).find("city"),
adminArea = $(addressXML).find(
"administrativearea, administrativeArea",
),
postalCode = $(addressXML).find("postalcode, postalCode"),
country = $(addressXML).find("country");
address.city = city.length ? city.text() : "";
address.administrativeArea = adminArea.length ? adminArea.text() : "";
address.postalCode = postalCode.length ? postalCode.text() : "";
address.country = country.length ? country.text() : "";
//Get an array of all the address line (or delivery point) values
var addressLines = [];
_.each(
delPoint,
function (addressLine, i) {
addressLines.push($(addressLine).text());
},
this,
);
address.deliveryPoint = addressLines;
return address;
},
serialize: function () {
var objectDOM = this.updateDOM(),
xmlString = objectDOM.outerHTML;
//Camel-case the XML
xmlString = this.formatXML(xmlString);
return xmlString;
},
/*
* Updates the attributes on this model based on the application user (the app UserModel)
*/
createFromUser: function () {
//Create the name from the user
var name = this.get("individualName") || {};
name.givenName = [MetacatUI.appUserModel.get("firstName")];
name.surName = MetacatUI.appUserModel.get("lastName");
this.set("individualName", name);
//Get the email and username
if (MetacatUI.appUserModel.get("email"))
this.set("email", [MetacatUI.appUserModel.get("email")]);
this.set("userId", [MetacatUI.appUserModel.get("username")]);
},
/*
* Makes a copy of the original XML DOM and updates it with the new values from the model.
*/
updateDOM: function () {
var type = this.get("type") || "associatedParty",
objectDOM = this.get("objectDOM");
// If there is already an XML node for this model and it is the wrong type,
// then replace the XML node contents
if (objectDOM && objectDOM.nodeName != type.toUpperCase()) {
objectDOM = $(document.createElement(type)).html(objectDOM.innerHTML);
}
// If there is already an XML node for this model and it is the correct type,
// then simply clone the XML node
else if (objectDOM) {
objectDOM = objectDOM.cloneNode(true);
}
// Otherwise, create a new XML node
else {
objectDOM = document.createElement(type);
}
//There needs to be at least one individual name, organization name, or position name
if (
this.nameIsEmpty() &&
!this.get("organizationName") &&
!this.get("positionName")
)
return "";
var name = this.get("individualName");
if (name) {
//Get the individualName node
var nameNode = $(objectDOM).find("individualname");
if (!nameNode.length) {
nameNode = document.createElement("individualname");
$(objectDOM).prepend(nameNode);
}
//Empty the individualName node
$(nameNode).empty();
// salutation[s]
if (!Array.isArray(name.salutation) && name.salutation)
name.salutation = [name.salutation];
_.each(name.salutation, function (salutation) {
$(nameNode).prepend("<salutation>" + salutation + "</salutation>");
});
//Given name
if (!Array.isArray(name.givenName) && name.givenName)
name.givenName = [name.givenName];
_.each(name.givenName, function (givenName) {
//If there is a given name string, create a givenName node
if (typeof givenName == "string" && givenName) {
$(nameNode).append("<givenname>" + givenName + "</givenname>");
}
});
// surname
if (name.surName)
$(nameNode).append("<surname>" + name.surName + "</surname>");
}
//If there is no name set on the model, remove it from the DOM
else {
$(objectDOM).find("individualname").remove();
}
// organizationName
if (this.get("organizationName")) {
//Get the organization name node
if ($(objectDOM).find("organizationname").length)
var orgNameNode = $(objectDOM).find("organizationname").detach();
else var orgNameNode = document.createElement("organizationname");
//Insert the text
$(orgNameNode).text(this.get("organizationName"));
//If the DOM is empty, append it
if (!$(objectDOM).children().length) $(objectDOM).append(orgNameNode);
else {
var insertAfter = this.getEMLPosition(
objectDOM,
"organizationname",
);
if (insertAfter && insertAfter.length)
insertAfter.after(orgNameNode);
else $(objectDOM).prepend(orgNameNode);
}
}
//Remove the organization name node if there is no organization name
else {
var orgNameNode = $(objectDOM).find("organizationname").remove();
}
// positionName
if (this.get("positionName")) {
//Get the name node
if ($(objectDOM).find("positionname").length)
var posNameNode = $(objectDOM).find("positionname").detach();
else var posNameNode = document.createElement("positionname");
//Insert the text
$(posNameNode).text(this.get("positionName"));
//If the DOM is empty, append it
if (!$(objectDOM).children().length) $(objectDOM).append(posNameNode);
else {
let insertAfter = this.getEMLPosition(objectDOM, "positionname");
if (insertAfter) insertAfter.after(posNameNode);
else $(objectDOM).prepend(posNameNode);
}
}
//Remove the position name node if there is no position name
else {
$(objectDOM).find("positionname").remove();
}
// address
_.each(
this.get("address"),
function (address, i) {
var addressNode = $(objectDOM).find("address")[i];
if (!addressNode) {
addressNode = document.createElement("address");
this.getEMLPosition(objectDOM, "address").after(addressNode);
}
//Remove all the delivery points since they'll be reserialized
$(addressNode).find("deliverypoint").remove();
_.each(address.deliveryPoint, function (deliveryPoint, ii) {
if (!deliveryPoint) return;
var delPointNode = $(addressNode).find("deliverypoint")[ii];
if (!delPointNode) {
delPointNode = document.createElement("deliverypoint");
//Add the deliveryPoint node to the address node
//Insert after the last deliveryPoint node
var appendAfter = $(addressNode).find("deliverypoint")[ii - 1];
if (appendAfter) $(appendAfter).after(delPointNode);
//Or just prepend to the beginning
else $(addressNode).prepend(delPointNode);
}
$(delPointNode).text(deliveryPoint);
});
if (address.city) {
var cityNode = $(addressNode).find("city");
if (!cityNode.length) {
cityNode = document.createElement("city");
if (this.getEMLPosition(addressNode, "city")) {
this.getEMLPosition(addressNode, "city").after(cityNode);
} else {
$(addressNode).append(cityNode);
}
}
$(cityNode).text(address.city);
} else {
$(addressNode).find("city").remove();
}
if (address.administrativeArea) {
var adminAreaNode = $(addressNode).find("administrativearea");
if (!adminAreaNode.length) {
adminAreaNode = document.createElement("administrativearea");
if (this.getEMLPosition(addressNode, "administrativearea")) {
this.getEMLPosition(addressNode, "administrativearea").after(
adminAreaNode,
);
} else {
$(addressNode).append(adminAreaNode);
}
}
$(adminAreaNode).text(address.administrativeArea);
} else {
$(addressNode).find("administrativearea").remove();
}
if (address.postalCode) {
var postalcodeNode = $(addressNode).find("postalcode");
if (!postalcodeNode.length) {
postalcodeNode = document.createElement("postalcode");
if (this.getEMLPosition(addressNode, "postalcode")) {
this.getEMLPosition(addressNode, "postalcode").after(
postalcodeNode,
);
} else {
$(addressNode).append(postalcodeNode);
}
}
$(postalcodeNode).text(address.postalCode);
} else {
$(addressNode).find("postalcode").remove();
}
if (address.country) {
var countryNode = $(addressNode).find("country");
if (!countryNode.length) {
countryNode = document.createElement("country");
if (this.getEMLPosition(addressNode, "country")) {
this.getEMLPosition(addressNode, "country").after(
countryNode,
);
} else {
$(addressNode).append(countryNode);
}
}
$(countryNode).text(address.country);
} else {
$(addressNode).find("country").remove();
}
},
this,
);
if (this.get("address").length == 0) {
$(objectDOM).find("address").remove();
}
// phone[s]
$(objectDOM).find("phone[phonetype='voice']").remove();
_.each(
this.get("phone"),
function (phone) {
var phoneNode = $(document.createElement("phone"))
.attr("phonetype", "voice")
.text(phone);
this.getEMLPosition(objectDOM, "phone").after(phoneNode);
},
this,
);
// fax[es]
$(objectDOM).find("phone[phonetype='facsimile']").remove();
_.each(
this.get("fax"),
function (fax) {
var faxNode = $(document.createElement("phone"))
.attr("phonetype", "facsimile")
.text(fax);
this.getEMLPosition(objectDOM, "phone").after(faxNode);
},
this,
);
// electronicMailAddress[es]
$(objectDOM).find("electronicmailaddress").remove();
_.each(
this.get("email"),
function (email) {
var emailNode = document.createElement("electronicmailaddress");
this.getEMLPosition(objectDOM, "electronicmailaddress").after(
emailNode,
);
$(emailNode).text(email);
},
this,
);
// online URL[es]
$(objectDOM).find("onlineurl").remove();
_.each(
this.get("onlineUrl"),
function (onlineUrl, i) {
var urlNode = document.createElement("onlineurl");
this.getEMLPosition(objectDOM, "onlineurl").after(urlNode);
$(urlNode).text(onlineUrl);
},
this,
);
//user ID
var userId = Array.isArray(this.get("userId"))
? this.get("userId")
: [this.get("userId")];
_.each(
userId,
function (id) {
if (!id) return;
var idNode = $(objectDOM).find("userid");
//Create the userid node
if (!idNode.length) {
idNode = $(document.createElement("userid"));
this.getEMLPosition(objectDOM, "userid").after(idNode);
}
//If this is an orcid identifier, format it correctly
if (this.isOrcid(id)) {
// Add the directory attribute
idNode.attr("directory", "https://orcid.org");
//If this ORCID does not start with "http"
if (id.indexOf("http") == -1) {
//If this is an ORCID with just the 16-digit numbers and hyphens, then add
// the https://orcid.org/ prefix to it
if (id.length == 19) {
id = "https://orcid.org/" + id;
}
//If it starts with "orcid.org", then add the "https://" prefix
else if (id.indexOf("orcid.org") == 0) {
id = "https://" + id;
}
//If it starts with "www.orcid.org", then add the "https" prefix and remove the "www"
else if (id.indexOf("www.orcid.org") == 0) {
id = "https://" + id.replace("www.orcid.org", "orcid.org");
}
}
//If there is a "www", remove it
if (id.indexOf("www.orcid.org") > -1) {
id = id.replace("www.orcid.org", "orcid.org");
}
//If it has the http:// prefix, add the 's' for secure protocol
if (id.indexOf("http://") == 0) {
id = id.replace("http", "https");
}
} else {
idNode.attr("directory", "unknown");
}
$(idNode).text(id);
},
this,
);
//Remove all the user id's if there aren't any in the model
if (userId.length == 0) {
$(objectDOM).find("userid").remove();
}
// role
//If this party type is not an associated party, then remove the role element
if (type != "associatedParty" && type != "personnel") {
$(objectDOM).find("role").remove();
}
//Otherwise, change the value of the role element
else {
// If for some reason there is no role, create a default role
if (!this.get("roles").length) {
var roles = ["Associated Party"];
} else {
var roles = this.get("roles");
}
_.each(
roles,
function (role, i) {
var roleSerialized = $(objectDOM).find("role");
if (roleSerialized.length) {
$(roleSerialized[i]).text(role);
} else {
roleSerialized = $(document.createElement("role")).text(role);
this.getEMLPosition(objectDOM, "role").after(roleSerialized);
}
},
this,
);
}
//XML id attribute
this.createID();
//if(this.get("xmlID"))
$(objectDOM).attr("id", this.get("xmlID"));
//else
// $(objectDOM).removeAttr("id");
// Remove empty (zero-length or whitespace-only) nodes
$(objectDOM)
.find("*")
.filter(function () {
return $.trim(this.innerHTML) === "";
})
.remove();
return objectDOM;
},
/*
* Adds this EMLParty model to it's parent EML211 model in the appropriate role array
*
* @return {boolean} - Returns true if the merge was successful, false if the merge was cancelled
*/
mergeIntoParent: function () {
//Get the type of EML Party, in relation to the parent model
if (this.get("type") && this.get("type") != "associatedParty")
var type = this.get("type");
else var type = "associatedParty";
//Update the list of EMLParty models in the parent model
var parentEML = this.getParentEML();
if (parentEML.type != "EML") return false;
//Add this model to the EML model
var successfulAdd = parentEML.addParty(this);
//Validate the model
this.isValid();
return successfulAdd;
},
isEmpty: function () {
// If we add any new fields, be sure to add the attribute here
var attributes = [
"userId",
"fax",
"phone",
"onlineUrl",
"email",
"positionName",
"organizationName",
];
//Check each value in the model that gets serialized to see if there is a value
for (var i in attributes) {
//Get the value from the model for this attribute
var modelValue = this.get(attributes[i]);
//If this is an array, then we want to check if there are any values in it
if (Array.isArray(modelValue)) {
if (modelValue.length > 0) return false;
}
//Otherwise, check if this value differs from the default value
else if (this.get(attributes[i]) !== this.defaults()[attributes[i]]) {
return false;
}
}
//Check for a first and last name
if (
this.get("individualName") &&
(this.get("individualName").givenName ||
this.get("individualName").surName)
)
return false;
//Check for addresses
var isAddress = false;
if (this.get("address")) {
//Checks if there are any values anywhere in the address
_.each(this.get("address"), function (address) {
//Delivery point is an array so we need to check the first and second
//values of that array
if (
address.administrativeArea ||
address.city ||
address.country ||
address.postalCode ||
(address.deliveryPoint &&
address.deliveryPoint.length &&
(address.deliveryPoint[0] || address.deliveryPoint[1]))
) {
isAddress = true;
}
});
}
//If we found an address value anywhere, then it is not empty
if (isAddress) return false;
//If we never found a value, then return true because this model is empty
return true;
},
/*
* Returns the node in the given EML snippet that the given node type should be inserted after
*/
getEMLPosition: function (objectDOM, nodeName) {
var nodeOrder = [
"individualname",
"organizationname",
"positionname",
"address",
"phone",
"electronicmailaddress",
"onlineurl",
"userid",
"role",
];
var addressOrder = [
"deliverypoint",
"city",
"administrativearea",
"postalcode",
"country",
];
//If this is an address node, find the position within the address
if (_.contains(addressOrder, nodeName)) {
nodeOrder = addressOrder;
}
var position = _.indexOf(nodeOrder, nodeName);
if (position == -1) return $(objectDOM).children().last();
//Go through each node in the node list and find the position where this node will be inserted after
for (var i = position - 1; i >= 0; i--) {
if ($(objectDOM).find(nodeOrder[i]).length)
return $(objectDOM).find(nodeOrder[i]).last();
}
return false;
},
createID: function () {
this.set(
"xmlID",
Math.ceil(
Math.random() * (9999999999999999 - 1000000000000000) +
1000000000000000,
),
);
},
setType: function () {
if (this.get("roles")) {
if (this.get("roles").length && !this.get("type")) {
this.set("type", "associatedParty");
}
}
},
trickleUpChange: function () {
if (this.get("parentModel")) {
MetacatUI.rootDataPackage.packageModel.set("changed", true);
}
},
removeFromParent: function () {
if (!this.get("parentModel")) return;
else if (typeof this.get("parentModel").removeParty != "function")
return;
this.get("parentModel").removeParty(this);
this.set("removed", true);
},
/*
* Checks the values of the model to determine if it is EML-valid
*/
validate: function () {
var individualName = this.get("individualName") || {},
givenName = individualName.givenName || [],
surName = individualName.surName || null,
errors = {};
//If there are no values in this model that would be serialized, then the model is valid
if (
!this.get("organizationName") &&
!this.get("positionName") &&
!givenName[0]?.length &&
!surName &&
!this.get("address").length &&
!this.get("phone").length &&
!this.get("fax").length &&
!this.get("email").length &&
!this.get("onlineUrl").length &&
!this.get("userId").length
) {
return;
}
//The EMLParty must have either an organization name, position name, or surname.
// It must ALSO have a type or role.
if (
!this.get("organizationName") &&
!this.get("positionName") &&
(!this.get("individualName") || !surName)
) {
errors = {
surName:
"Either a last name, position name, or organization name is required.",
positionName: "",
organizationName: "",
};
}
//If there is a first name and no last name, then this is not a valid individualName
else if (
givenName[0]?.length &&
!surName &&
this.get("organizationName") &&
this.get("positionName")
) {
errors = { surName: "Provide a last name." };
}
//Check that each required field has a value. Required fields are configured in the {@link AppConfig}
let roles =
this.get("type") == "associatedParty"
? this.get("roles")
: [this.get("type")];
for (role of roles) {
let requiredFields = MetacatUI.appModel.get(
"emlEditorRequiredFields_EMLParty",
)[role];
requiredFields?.forEach((field) => {
let currentVal = this.get(field);
if (!currentVal || !currentVal?.length) {
errors[field] =
`Provide a${["a", "e", "i", "o", "u"].includes(field.charAt(0)) ? "n " : " "} ${field}. `;
}
});
}
return Object.keys(errors)?.length ? errors : false;
},
isOrcid: function (username) {
if (!username) return false;
//If the ORCID URL is anywhere in this username string, it is an ORCID
if (username.indexOf("orcid.org") > -1) {
return true;
}
/* The ORCID checksum algorithm to determine is a character string is an ORCiD
* http://support.orcid.org/knowledgebase/articles/116780-structure-of-the-orcid-identifier
*/
var total = 0,
baseDigits = username.replace(/-/g, "").substr(0, 15);
for (var i = 0; i < baseDigits.length; i++) {
var digit = parseInt(baseDigits.charAt(i));
total = (total + digit) * 2;
}
var remainder = total % 11,
result = (12 - remainder) % 11,
checkDigit = result == 10 ? "X" : result.toString(),
isOrcid = checkDigit == username.charAt(username.length - 1);
return isOrcid;
},
/*
* Clones all the values of this array into a new JS Object.
* Special care is needed for nested objects and arrays
* This is helpful when copying this EMLParty to another role in the EML
*/
copyValues: function () {
//Get a JSON object of all the model copyValues
var modelValues = this.toJSON();
//Go through each model value and properly clone the arrays
_.each(Object.keys(modelValues), function (key, i) {
//Clone the array via slice()
if (Array.isArray(modelValues[key]))
modelValues[key] = modelValues[key].slice(0);
});
//Individual Names are objects, so properly clone them
if (modelValues.individualName) {
modelValues.individualName = Object.assign(
{},
modelValues.individualName,
);
}
//Addresses are objects, so properly clone them
if (modelValues.address.length) {
_.each(modelValues.address, function (address, i) {
modelValues.address[i] = Object.assign({}, address);
//The delivery point is an array of strings, so properly clone the array
if (Array.isArray(modelValues.address[i].deliveryPoint))
modelValues.address[i].deliveryPoint =
modelValues.address[i].deliveryPoint.slice(0);
});
}
return modelValues;
},
/**
* getName - For an individual, returns the first and last name as a string. Otherwise,
* returns the organization or position name.
*
* @return {string} Returns the name of the party or an empty string if one cannot be found
*
* @since 2.15.0
*/
getName: function () {
return this.get("individualName")
? this.get("individualName").givenName +
" " +
this.get("individualName").surName
: this.get("organizationName") || this.get("positionName") || "";
},
/**
* Return the EML Party as a CSL JSON object. See
* {@link https://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html#names}.
* @return {object} The CSL JSON object
* @since 2.23.0
*/
toCSLJSON: function () {
const name = this.get("individualName");
const csl = {
family: name?.surName || null,
given: name?.givenName || null,
literal:
this.get("organizationName") || this.get("positionName") || "",
"dropping-particle": name?.salutation || null,
};
// If any of the fields are arrays, join them with a space
for (const key in csl) {
if (Array.isArray(csl[key])) {
csl[key] = csl[key].join(" ");
}
}
return csl;
},
/*
* function nameIsEmpty - Returns true if the individualName set on this
* model contains only empty values. Otherwise, returns false. This is just a
* shortcut for manually checking each name field individually.
*
* @return {boolean}
*/
nameIsEmpty: function () {
var name = this.get("individualName");
if (!name || typeof name != "object") return true;
//Check if there are given names
var givenName = name.givenName,
givenNameEmpty = false;
if (
!givenName ||
(Array.isArray(givenName) && givenName.length == 0) ||
(typeof givenName == "string" && givenName.trim().length == 0)
)
givenNameEmpty = true;
//Check if there are no sur names
var surName = name.surName,
surNameEmpty = false;
if (
!surName ||
(Array.isArray(surName) && surName.length == 0) ||
(typeof surName == "string" && surName.trim().length == 0)
)
surNameEmpty = true;
//Check if there are no salutations
var salutation = name.salutation,
salutationEmpty = false;
if (
!salutation ||
(Array.isArray(salutation) && salutation.length == 0) ||
(typeof salutation == "string" && salutation.trim().length == 0)
)
salutationEmpty = true;
if (givenNameEmpty && surNameEmpty && salutationEmpty) return true;
else return false;
},
/*
* Climbs up the model heirarchy until it finds the EML model
*
* @return {EML211 or false} - Returns the EML 211 Model or false if not found
*/
getParentEML: function () {
var emlModel = this.get("parentModel"),
tries = 0;
while (emlModel.type !== "EML" && tries < 6) {
emlModel = emlModel.get("parentModel");
tries++;
}
if (emlModel && emlModel.type == "EML") return emlModel;
else return false;
},
/**
* @type {object[]}
* @property {string} label - The name of the party category to display to the user
* @property {string} dataCategory - The string that is used to represent this party. This value
* should exactly match one of the strings listed in EMLParty typeOptions or EMLParty roleOptions.
* @property {string} description - An optional description to display below the label to help the user
* with this category.
* @property {boolean} createFromUser - If set to true, the information from the logged-in user will be
* used to create an EML party for this category if none exist already when the view loads.
* @property {number} limit - If the number of parties allowed for this category is not unlimited,
* then limit should be set to the maximum allowable number.
* @since 2.21.0
*/
partyTypes: [
{
label: "Dataset Creators (Authors/Owners/Originators)",
dataCategory: "creator",
description:
"Each person or organization listed as a Creator will be listed in the data" +
" citation. At least one person, organization, or position with a 'Creator'" +
" role is required.",
createFromUser: true,
},
{
label: "Contact",
dataCategory: "contact",
createFromUser: true,
},
{
label: "Principal Investigators",
dataCategory: "principalInvestigator",
},
{
label: "Co-Principal Investigators",
dataCategory: "coPrincipalInvestigator",
},
{
label: "Collaborating-Principal Investigators",
dataCategory: "collaboratingPrincipalInvestigator",
},
{
label: "Metadata Provider",
dataCategory: "metadataProvider",
},
{
label: "Custodians/Stewards",
dataCategory: "custodianSteward",
},
{
label: "Publisher",
dataCategory: "publisher",
description: "Only one publisher can be specified.",
limit: 1,
},
{
label: "Users",
dataCategory: "user",
},
],
formatXML: function (xmlString) {
return DataONEObject.prototype.formatXML.call(this, xmlString);
},
},
);
return EMLParty;
});