/* global define */ define(['jquery', 'underscore', 'backbone', 'models/DataONEObject'], function($, _, Backbone, DataONEObject) { var EMLText = Backbone.Model.extend({ type: "EMLText", defaults: function(){ return { objectXML: null, objectDOM: null, parentModel: null, originalText: [], text: [] //The text content } }, initialize: function(attributes){ var attributes = attributes || {} if(attributes.objectDOM) this.set(this.parse(attributes.objectDOM)); if(attributes.text) { if (_.isArray(attributes.text)) { this.text = attributes.text } else { this.text = [attributes.text] } } this.on("change:text", 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{ } }, /* * function setText * * @param text {string} - The text, usually taken directly from an HTML textarea * value, to parse and set on this model */ setText: function(text){ if( typeof text !== "string" ) return ""; //Get the EML model and use the cleanXMLText function to clean up the text var emlModel = this.getParentEML(); if( typeof emlModel == "object" && emlModel.type == "EML"){ text = emlModel.cleanXMLText(text); } //Get the list of paragraphs - checking for carriage returns and line feeds var paragraphsCR = text.split(String.fromCharCode(13)); var paragraphsLF = text.split(String.fromCharCode(10)); //Use the paragraph list that has the most var paragraphs = (paragraphsCR > paragraphsLF)? paragraphsCR : paragraphsLF; paragraphs = _.map(paragraphs, function(p){ return p.trim() }); this.set("text", paragraphs); }, parse: function(objectDOM){ if(!objectDOM) var objectDOM = this.get("objectDOM").cloneNode(true); //Start a list of paragraphs var paragraphs = []; //Get all the child nodes of this text element var allNodes = $(objectDOM).children(); // Save all the contained nodes as paragraphs // ignore any nested formatting elements for now //TODO: Support more detailed text formatting if( allNodes.length ){ _.each(allNodes, function(node) { if( node.textContent ){ //Get the list of paragraphs - checking for carriage returns and line feeds var paragraphsCR = node.textContent.split(String.fromCharCode(13)); var paragraphsLF = node.textContent.split(String.fromCharCode(10)); //Use the paragraph list that has the most var nestedParagraphs = (paragraphsCR > paragraphsLF)? paragraphsCR : paragraphsLF; paragraphs = _.union(paragraphs, nestedParagraphs); } }); } else if( objectDOM.textContent ){ paragraphs[0] = objectDOM.textContent; } //Trim extra whitespace off each paragraph to get rid of the line break characters paragraphs = _.map(paragraphs, function(text){ if(typeof text == "string") return text.trim(); else return text; }); //Remove all falsey values - primarily empty strings paragraphs = _.compact(paragraphs); return { text: paragraphs, originalText: paragraphs.slice(0) //The slice function will effectively clone the array } }, 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 type = this.get("type") || this.get("parentAttribute") || 'text', objectDOM = this.get("objectDOM") ? this.get("objectDOM").cloneNode(true) : document.createElement(type); //FIrst check if any of the text in this model has changed since it was originally parsed if( _.intersection(this.get("text"), this.get("originalText")).length == this.get("text").length && this.get("objectDOM")){ return objectDOM; } //If there is no text, return an empty string if( this.isEmpty() ){ return ""; } //Empty the DOM $(objectDOM).empty(); //Format the text var paragraphs = this.get("text"); _.each(paragraphs, function(p){ //If this paragraph text is a string, add a node with that text if( typeof p == "string" && p.trim().length ) $(objectDOM).append("" + p + ""); }); return objectDOM; }, /* * 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; }, trickleUpChange: function(){ if( MetacatUI.rootDataPackage && MetacatUI.rootDataPackage.packageModel ){ MetacatUI.rootDataPackage.packageModel.set("changed", true); } }, formatXML: function(xmlString){ return DataONEObject.prototype.formatXML.call(this, xmlString); }, isEmpty: function() { //If the text is an empty array, this is empty if( Array.isArray(this.get("text")) && this.get("text").length == 0 ){ return true; } //If the text is a falsey value, it is empty else if( !this.get("text") ){ return true; } //Iterate over each paragraph in the text array and check if it's an empty string for (var i = 0; i < this.get('text').length; i++) { if (this.get('text')[i].trim().length > 0) return false; } return true; }, toString: function() { var value = []; if (_.isArray(this.get('text'))) { value = this.get('text'); } return value.join('\n\n'); } }); return EMLText; });