define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
$,
_,
Backbone,
DataONEObject,
) {
/**
* @class PortalImage
* @classdesc A Portal Image model represents a single image used in a Portal
* @classcategory Models/Portals
* @extends Backbone.Model
*/
var PortalImageModel = DataONEObject.extend(
/** @lends PortalImage.prototype */ {
/**
* @inheritdoc
*/
type: "PortalImage",
defaults: function () {
return _.extend(DataONEObject.prototype.defaults(), {
identifier: "",
imageURL: "",
label: "",
associatedURL: "",
objectDOM: null,
nodeName: "image",
portalModel: null,
});
},
initialize: function (attrs) {
// Call the super class initialize function
DataONEObject.prototype.initialize.call(this, attrs);
// If the image model is initialized with an identifier but no image URL,
// create the full image URL
if (this.get("identifier") && !this.get("imageURL")) {
var baseURL = this.getBaseURL(),
imageURL = baseURL + this.get("identifier");
this.set("imageURL", imageURL);
}
},
/**
* Parses an ImageType XML element from a portal document
*
* @param {XMLElement} objectDOM - An ImageType XML element from a portal document
* @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
*/
parse: function (objectDOM) {
if (!objectDOM) {
objectDOM = this.get("objectDOM");
if (!objectDOM) {
return {};
}
}
var portalModel = this.get("portalModel"),
$objectDOM = $(objectDOM),
modelJSON = {};
if (portalModel) {
modelJSON.datasource = portalModel.get("datasource");
modelJSON.submitter = portalModel.get("submitter");
modelJSON.rightsHolder = portalModel.get("rightsHolder");
modelJSON.originMemberNode = portalModel.get("originMemberNode");
modelJSON.authoritativeMemberNode = portalModel.get(
"authoritativeMemberNode",
);
}
modelJSON.nodeName = objectDOM.nodeName;
//Parse all the simple string elements
modelJSON.label = $objectDOM.children("label").text();
modelJSON.associatedURL = $objectDOM.children("associatedURL").text();
// Parse the image URL or identifier
modelJSON.identifier = $objectDOM.children("identifier").text();
if (modelJSON.identifier) {
if (modelJSON.identifier.substring(0, 4) !== "http") {
var baseURL = this.getBaseURL();
modelJSON.imageURL = baseURL + modelJSON.identifier;
} else {
modelJSON.imageURL = modelJSON.identifier;
}
}
return modelJSON;
},
/**
* imageExists - Check if an image exists with the given
* url, or if no url provided, with the baseURL + identifier
*
* @param {string} imageURL The image URL to check
* @return {boolean} Returns true if an HTTP request returns anything but 404
*/
imageExists: function (imageURL) {
if (!imageURL) {
this.get("imageURL");
}
if (!imageURL && this.get("identifier")) {
var baseURL = this.getBaseURL(),
imageURL = baseURL + this.get("identifier");
}
if (!imageURL) {
return false;
}
var http = new XMLHttpRequest();
http.open("HEAD", imageURL, false);
http.send();
return http.status != 404;
},
/**
* getBaseURL - Get the base URL to use with an image identifier
*
* @return {string} The image base URL, or an empty string if not found
*/
getBaseURL: function () {
var url = "",
portalModel = this.get("portalModel"),
// datasource = portalModel ? portalModel.get("datasource") : false;
datasource = this.get("datasource"),
datasource =
portalModel && !datasource
? portalModel.get("datasource")
: datasource;
if (MetacatUI.appModel.get("isCN")) {
var sourceRepo;
//Use the object service URL from the origin MN/datasource
if (datasource) {
sourceRepo = MetacatUI.nodeModel.getMember(datasource);
}
// Use the object service URL from the alt repo
if (!sourceRepo) {
sourceRepo = MetacatUI.appModel.getActiveAltRepo();
}
if (sourceRepo) {
url = sourceRepo.objectServiceUrl;
}
}
if (!url && datasource) {
var imageMN = _.findWhere(
MetacatUI.appModel.get("alternateRepositories"),
{ identifier: datasource },
);
if (imageMN) {
url = imageMN.objectServiceUrl;
}
}
//If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel
if (!url) {
url =
MetacatUI.appModel.get("objectServiceUrl") ||
MetacatUI.appModel.get("resolveServiceUrl");
}
return url;
},
/**
* Makes a copy of the original XML DOM and updates it with the new values from the model
*
* @return {XMLElement} An updated ImageType XML element from a portal document
*/
updateDOM: function () {
//If there is no identifier, don't serialize anything
if (!this.get("identifier")) {
return "";
}
var objectDOM = this.get("objectDOM");
if (objectDOM) {
objectDOM = objectDOM.cloneNode(true);
$(objectDOM).empty();
} else {
// create an XML image element from scratch
var xmlText =
"<" + this.get("nodeName") + "></" + this.get("nodeName") + ">",
objectDOM = new DOMParser().parseFromString(xmlText, "text/xml"),
objectDOM = $(objectDOM).children()[0];
}
// get new image data
var imageData = {
identifier: this.get("identifier"),
label: this.get("label"),
associatedURL: this.get("associatedURL"),
};
_.map(imageData, function (value, nodeName) {
// Don't serialize falsey values
if (value) {
// Make new sub-node
var imageSubnodeSerialized =
objectDOM.ownerDocument.createElement(nodeName);
$(imageSubnodeSerialized).text(value);
// Append new sub-node to objectDOM
$(objectDOM).append(imageSubnodeSerialized);
}
});
return objectDOM;
},
/**
* Overrides the default Backbone.Model.validate.function() to
* check if this PortalImage model has all the required values necessary
* to save to the server.
*
* @return {Object} If there are errors, an object comprising error
* messages. If no errors, returns nothing.
*/
validate: function () {
try {
var errors = {},
requiredFields = MetacatUI.appModel.get(
"portalEditorRequiredFields",
),
label = this.get("label"),
url = this.get("associatedURL"),
id = this.get("identifier"),
genericLabels = ["logo", "image"], // not set by the user
hasLabel =
label &&
typeof label == "string" &&
!genericLabels.includes(label)
? true
: false,
hasURL = url && typeof url == "string" ? true : false,
hasId = id && typeof id == "string" ? true : false,
urlRegex = new RegExp(
/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/,
);
// If it's a logo, check whether it's a required image
if (
this.get("nodeName") === "logo" &&
requiredFields.logo &&
!hasId
) {
errors["identifier"] = "An image is required.";
return errors;
}
// If it's a section image, check whether it's a required image
else if (
this.get("nodeName") === "image" &&
requiredFields.sectionImage &&
!hasId
) {
errors["identifier"] = "An image is required.";
return errors;
}
// If none of the fields have values, the portalImage won't be serialized
else if (!hasId && !hasURL && !hasLabel) {
return;
}
// If the URL isn't a valid format, add an error message
if (hasURL && !urlRegex.test(url)) {
errors["associatedURL"] = "Enter a valid URL.";
}
//If the URL is valid, check if there is an http or https protocol
else if (hasURL && url.substring(0, 4) != "http") {
//If not, add the https protocol
this.set("associatedURL", "https://" + url);
}
return errors;
} catch (e) {
console.error("Error validating a portal image. Error message:" + e);
return;
}
},
/**
* isEmpty - Returns true if the PortalImage model has no label, no associatedURL, and no identifier
*
* @return {boolean} true if the model is empty, false if it has at least a label, url, or id
*/
isEmpty: function () {
return (
!this.get("label") &&
!this.get("associatedURL") &&
!this.get("identifier")
);
},
/**
* Returns true if this PortalImage hasn't been saved to a Portal yet, so it is a new object.
* For now, all PortalImages will be considered new objects since we will not be performing updates on them.
* @return {boolean}
*/
isNew: function () {
if (this.get("identifier")) {
return false;
} else {
return true;
}
},
},
);
return PortalImageModel;
});