define(["jquery", "underscore", "backbone", "models/DataONEObject"], function (
$,
_,
Backbone,
DataONEObject,
) {
/**
* @class EMLTemporalCoverage
* @classcategory Models/Metadata/EML211
* @extends Backbone.Model
*/
var EMLTemporalCoverage = Backbone.Model.extend(
/** @lends EMLTemporalCoverage.prototype */ {
defaults: {
objectXML: null,
objectDOM: null,
beginDate: null,
beginTime: null,
endDate: null,
endTime: null,
},
initialize: function (attributes) {
if (attributes && attributes.objectDOM)
this.set(this.parse(attributes.objectDOM));
this.on(
"change:beginDate change:beginTime change:endDate change:endTime",
this.trickleUpChange,
);
},
/*
* 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 {
begindate: "beginDate",
calendardate: "calendarDate",
enddate: "endDate",
rangeofdates: "rangeOfDates",
singledatetime: "singleDateTime",
spatialraster: "spatialRaster",
spatialvector: "spatialVector",
storedprocedure: "storedProcedure",
temporalcoverage: "temporalCoverage",
};
},
parse: function (objectDOM) {
if (!objectDOM) var objectDOM = this.get("objectDOM");
var rangeOfDates = $(objectDOM).children("rangeofdates"),
singleDateTime = $(objectDOM).children("singledatetime");
// If the temporalCoverage element has both a rangeOfDates and a
// singleDateTime (invalid EML), the rangeOfDates is preferred.
if (rangeOfDates.length) {
return this.parseRangeOfDates(rangeOfDates);
} else if (singleDateTime.length) {
return this.parseSingleDateTime(singleDateTime);
}
},
parseRangeOfDates: function (rangeOfDates) {
var beginDate = $(rangeOfDates).find("beginDate"),
endDate = $(rangeOfDates).find("endDate"),
properties = {};
if (beginDate.length > 0) {
if ($(beginDate).find("calendardate")) {
properties.beginDate = $(beginDate)
.find("calendardate")
.first()
.text();
}
if ($(beginDate).find("time").length > 0) {
properties.beginTime = $(beginDate).find("time").first().text();
}
}
if (endDate.length > 0) {
if ($(endDate).find("calendardate").length > 0) {
properties.endDate = $(endDate).find("calendardate").first().text();
}
if ($(endDate).find("time").length > 0) {
properties.endTime = $(endDate).find("time").first().text();
}
}
return properties;
},
parseSingleDateTime: function (singleDateTime) {
var calendarDate = $(singleDateTime).find("calendardate"),
time = $(singleDateTime).find("time");
return {
beginDate:
calendarDate.length > 0 ? calendarDate.first().text() : null,
beginTime: time.length > 0 ? time.first().text() : null,
};
},
serialize: function () {
var objectDOM = this.updateDOM(),
xmlString = objectDOM.outerHTML;
//Camel-case the XML
xmlString = this.formatXML(xmlString);
return xmlString;
},
/*
* Makes a copy of the original XML DOM and updates it with the new values from the model.
*/
updateDOM: function () {
var objectDOM;
if (this.get("objectDOM")) {
objectDOM = this.get("objectDOM").cloneNode(true);
//Empty the DOM
$(objectDOM).empty();
} else {
objectDOM = $("<temporalcoverage></temporalcoverage>");
}
if (this.get("beginDate") && this.get("endDate")) {
$(objectDOM).append(this.serializeRangeOfDates());
} else if (!this.get("endDate")) {
$(objectDOM).append(this.serializeSingleDateTime());
} else if (this.get("singleDateTime")) {
var singleDateTime = $(objectDOM).find("singledatetime");
if (!singleDateTime.length) {
singleDateTime = document.createElement("singledatetime");
$(objectDOM).append(singleDateTime);
}
if (this.get("singleDateTime").calendarDate)
$(singleDateTime).html(
this.serializeSingleDateTime(
this.get("singleDateTime").calendarDate,
),
);
}
// Remove empty (zero-length or whitespace-only) nodes
$(objectDOM)
.find("*")
.filter(function () {
return $.trim(this.innerHTML) === "";
})
.remove();
return objectDOM;
},
serializeRangeOfDates: function () {
var objectDOM = $(document.createElement("rangeofdates")),
beginDateEl = $(document.createElement("begindate")),
endDateEl = $(document.createElement("enddate"));
if (this.get("beginDate")) {
$(beginDateEl).append(
this.serializeCalendarDate(this.get("beginDate")),
);
if (this.get("beginTime")) {
$(beginDateEl).append(this.serializeTime(this.get("beginTime")));
}
objectDOM.append(beginDateEl);
}
if (this.get("endDate")) {
$(endDateEl).append(this.serializeCalendarDate(this.get("endDate")));
if (this.get("endTime")) {
$(endDateEl).append(this.serializeTime(this.get("endTime")));
}
objectDOM.append(endDateEl);
}
return objectDOM;
},
serializeSingleDateTime: function () {
var objectDOM = $(document.createElement("singleDateTime"));
if (this.get("beginDate")) {
$(objectDOM).append(
this.serializeCalendarDate(this.get("beginDate")),
);
if (this.get("beginTime")) {
$(objectDOM).append(this.serializeTime(this.get("beginTime")));
}
}
return objectDOM;
},
serializeCalendarDate: function (date) {
return $(document.createElement("calendarDate")).html(date);
},
serializeTime: function (time) {
return $(document.createElement("time")).html(time);
},
trickleUpChange: function () {
if (
_.contains(MetacatUI.rootDataPackage.models, this.get("parentModel"))
)
MetacatUI.rootDataPackage.packageModel.set("changed", true);
},
mergeIntoParent: function () {
if (
this.get("parentModel") &&
this.get("parentModel").type == "EML" &&
!_.contains(this.get("parentModel").get("temporalCoverage"), this)
)
this.get("parentModel").get("temporalCoverage").push(this);
},
formatXML: function (xmlString) {
return DataONEObject.prototype.formatXML.call(this, xmlString);
},
// Checks the values of this model and determines whether they are valid according the the EML 2.1.1 schema.
// Returns a hash of error messages
validate: function () {
var beginDate = this.get("beginDate"),
beginTime = this.get("beginTime"),
endDate = this.get("endDate"),
endTime = this.get("endTime"),
errors = {};
// A valid temporal coverage at least needs a start date
if (!beginDate) {
errors.beginDate = "Provide a begin date.";
}
// endTime is set but endDate is not
else if (
endTime &&
endTime.length > 0 &&
(!endDate || endDate.length == 0)
) {
errors.endDate = "Provide an end date.";
}
//Check the validity of the date format
if (beginDate && !this.isDateFormatValid(beginDate)) {
errors.beginDate =
"The begin date must be formatted as YYYY-MM-DD or YYYY.";
}
//Check the validity of the date format
if (endDate && !this.isDateFormatValid(endDate)) {
errors.endDate =
"The end date must be formatted as YYYY-MM-DD or YYYY.";
}
if (typeof endDate == "string" && endDate.length && beginDate <= 0) {
errors.beginDate = "The begin date must be greater than zero.";
}
if (typeof endDate == "string" && endDate.length && endDate <= 0) {
errors.endDate = "The end date must be greater than zero.";
}
//Check the validity of the begin time format
if (beginTime) {
var timeErrorMessage = this.validateTimeFormat(beginTime);
if (typeof timeErrorMessage == "string")
errors.beginTime = timeErrorMessage;
}
//Check the validity of the end time format
if (endTime) {
var timeErrorMessage = this.validateTimeFormat(endTime);
if (typeof timeErrorMessage == "string")
errors.endTime = timeErrorMessage;
}
// Check if begin date greater than end date for the temporalCoverage
if (this.isGreaterDate(beginDate, endDate))
errors.beginDate = "The begin date must be before the end date.";
// Check if begin time greater than end time for the temporalCoverage in case of equal dates.
if (this.isGreaterTime(beginDate, endDate, beginTime, endTime))
errors.beginTime = "The begin time must be before the end time.";
if (Object.keys(errors).length) return errors;
else return;
},
isDateFormatValid: function (dateString) {
//Date strings that are four characters should be a full year. Make sure all characters are numbers
if (dateString.length == 4) {
var digits = dateString.match(/[0-9]/g);
return digits.length == 4;
}
//Date strings that are 10 characters long should be a valid date
else {
var dateParts = dateString.split("-");
if (
dateParts.length != 3 ||
dateParts[0].length != 4 ||
dateParts[1].length != 2 ||
dateParts[2].length != 2
)
return false;
dateYear = dateParts[0];
dateMonth = dateParts[1];
dateDay = dateParts[2];
// Validating the values for the date and month if in YYYY-MM-DD format.
if (dateMonth < 1 || dateMonth > 12) return false;
else if (dateDay < 1 || dateDay > 31) return false;
else if (
(dateMonth == 4 ||
dateMonth == 6 ||
dateMonth == 9 ||
dateMonth == 11) &&
dateDay == 31
)
return false;
else if (dateMonth == 2) {
// Validation for leap year dates.
var isleap =
dateYear % 4 == 0 && (dateYear % 100 != 0 || dateYear % 400 == 0);
if (dateDay > 29 || (dateDay == 29 && !isleap)) return false;
}
var digits = _.filter(dateParts, function (part) {
return part.match(/[0-9]/g).length == part.length;
});
return digits.length == 3;
}
},
validateTimeFormat: function (timeString) {
//If the last character is a "Z", then remove it for now
if (
timeString.substring(timeString.length - 1, timeString.length) == "Z"
) {
timeString = timeString.replace("Z", "", "g");
}
if (timeString.length == 8) {
var timeParts = timeString.split(":");
if (timeParts.length != 3) {
return "Time must be formatted as HH:MM:SS";
}
// Validation pattern for HH:MM:SS values.
// Range for HH validation : 00-24
// Range for MM validation : 00-59
// Range for SS validation : 00-59
// Leading 0's are must in case of single digit values.
var timePattern = /^(?:2[0-4]|[01][0-9]):[0-5][0-9]:[0-5][0-9]$/,
validTimePattern = timeString.match(timePattern);
//If the hour is 24, only accept 00:00 for MM:SS. Any minutes or seconds in the midnight hour should be
//formatted as 00:XX:XX not 24:XX:XX
if (
validTimePattern &&
timeParts[0] == "24" &&
(timeParts[1] != "00" || timeParts[2] != "00")
) {
return "The midnight hour starts at 00:00:00 and ends at 00:59:59.";
} else if (!validTimePattern && parseInt(timeParts[0]) > "24") {
return "Time of the day starts at 00:00 and ends at 23:59.";
} else if (!validTimePattern && parseInt(timeParts[1]) > "59") {
return "Minutes should be between 00 and 59.";
} else if (!validTimePattern && parseInt(timeParts[2]) > "59") {
return "Seconds should be between 00 and 59.";
} else return true;
} else return "Time must be formatted as HH:MM:SS";
},
/**
* This function checks whether the begin date is greater than the end date.
*
* @param {string} beginDate the begin date string
* @param {string} endDate the end date string
* @return {boolean}
*/
isGreaterDate: function (beginDate, endDate) {
if (typeof beginDate == "undefined" || !beginDate) return false;
if (typeof endDate == "undefined" || !endDate) return false;
//Making sure that beginDate year is smaller than endDate year
if (beginDate.length == 4 && endDate.length == 4) {
if (beginDate > endDate) {
return true;
}
}
//Checking equality for either dateStrings that are greater than 4 characters
else {
beginDateParts = beginDate.split("-");
endDateParts = endDate.split("-");
if (beginDateParts.length == endDateParts.length) {
if (beginDateParts[0] > endDateParts[0]) {
return true;
} else if (beginDateParts[0] == endDateParts[0]) {
if (beginDateParts[1] > endDateParts[1]) {
return true;
} else if (beginDateParts[1] == endDateParts[1]) {
if (beginDateParts[2] > endDateParts[2]) {
return true;
}
}
}
} else {
if (beginDateParts[0] > endDateParts[0]) {
return true;
}
}
}
return false;
},
/**
* This function checks whether the begin time is greater than the end time.
*
* @param {string} beginDate the begin date string
* @param {string} endDate the end date string
* @param {string} beginTime the begin time string
* @param {string} endTime the end time string
* @return {boolean}
*/
isGreaterTime: function (beginDate, endDate, beginTime, endTime) {
if (!beginTime || !endTime) return false;
var equalDates = false;
//Making sure that beginDate year is smaller than endDate year
if (beginDate.length == 4 && endDate.length == 4) {
if (beginDate == endDate) {
equalDates = true;
}
} else {
beginDateParts = beginDate.split("-");
endDateParts = endDate.split("-");
if (beginDateParts.length == endDateParts.length) {
if (beginDateParts[0] == endDateParts[0]) {
if (beginDateParts[1] == endDateParts[1]) {
if (beginDateParts[2] == endDateParts[2]) {
equalDates = true;
}
}
}
}
}
// If the dates are equal, check for validity of time frame.
if (equalDates) {
beginTimeParts = beginTime.split(":");
endTimeParts = endTime.split(":");
if (beginTimeParts[0] > endTimeParts[0]) {
return true;
} else if (beginTimeParts[0] == endTimeParts[0]) {
if (beginTimeParts[1] > endTimeParts[1]) {
return true;
} else if (beginTimeParts[1] == endTimeParts[1]) {
if (beginTimeParts[2] > endTimeParts[2]) {
return true;
}
}
}
}
return false;
},
/**
* Checks if this model has no values set on it
* @return {boolean}
*/
isEmpty: function () {
return (
!this.get("beginDate") &&
!this.get("beginTime") &&
!this.get("endDate") &&
!this.get("endTime")
);
},
/*
* 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;
},
},
);
return EMLTemporalCoverage;
});