/* global define */ define(['underscore', 'jquery', 'backbone', 'models/DataONEObject', 'models/metadata/eml211/EMLMeasurementScale', 'text!templates/metadata/eml-measurement-scale.html', 'text!templates/metadata/codelist-row.html', 'text!templates/metadata/nonNumericDomain.html', 'text!templates/metadata/textDomain.html'], function(_, $, Backbone, DataONEObject, EMLMeasurementScale, EMLMeasurementScaleTemplate, CodeListRowTemplate, NonNumericDomainTemplate, TextDomainTemplate){ /** * @class EMLMeasurementScaleView * @classdesc An EMLMeasurementScaleView displays the info about one the measurement scale or category of an eml attribute * @classcategory Views/Metadata * @extends Backbone.View */ var EMLMeasurementScaleView = Backbone.View.extend( /** @lends EMLMeasurementScaleView.prototype */{ tagName: "div", className: "eml-measurement-scale", id: null, /* The HTML template for a measurement scale */ template: _.template(EMLMeasurementScaleTemplate), codeListRowTemplate: _.template(CodeListRowTemplate), nonNumericDomainTemplate: _.template(NonNumericDomainTemplate), textDomainTemplate: _.template(TextDomainTemplate), /* Events this view listens to */ events: { "click .category" : "switchCategory", "change .datetime-string" : "toggleCustomDateTimeFormat", "change .possible-text" : "toggleNonNumericDomain", "keyup .new .codelist" : "addNewCodeRow", "click .code-row .remove" : "removeCodeRow", "mouseover .code-row .remove" : "previewCodeRemove", "mouseout .code-row .remove" : "previewCodeRemove", "change .units" : "updateModel", "change .datetime" : "updateModel", "change .codelist" : "updateModel", "change .textDomain" : "updateModel", "focusout .code-row" : "showValidation", "focusout .units.input" : "showValidation" }, initialize: function(options){ if(!options) var options = {}; this.isNew = (options.isNew === true) ? true : this.model? false : true; this.model = options.model || EMLMeasurementScale.getInstance(); this.parentView = options.parentView || null; }, render: function(){ //Render the template var viewHTML = this.template(this.model.toJSON()); if(this.isNew) this.$el.addClass("new"); //Insert the template HTML this.$el.html(viewHTML); //Render any nonNumericDomain models this.$(".non-numeric-domain").append( this.nonNumericDomainTemplate(this.model.get("nonNumericDomain")) ); //Render the text domain choices and details this.$(".text-domain").html( this.textDomainTemplate() ); //If this attribute is already defined as nonNumericDomain, then fill in the metadata _.each(this.model.get("nonNumericDomain"), function(domain){ var nominalTextDomain = this.$(".nominal-options .text-domain"), ordinalTextDomain = this.$(".ordinal-options .text-domain"); if(domain.textDomain){ if(this.model.get("measurementScale") == "nominal"){ nominalTextDomain.html( this.textDomainTemplate(domain.textDomain) ); } else{ ordinalTextDomain.html( this.textDomainTemplate(domain.textDomain) ); } } else if(domain.enumeratedDomain){ this.renderCodeList(domain.enumeratedDomain); } }, this); //Add the new code rows in the code list table this.addNewCodeRow("nominal"); this.addNewCodeRow("ordinal"); }, postRender: function(){ //Determine which category to select //Interval measurement scales will be displayed as ratio var selectedCategory = this.model.get("measurementScale") == "interval" ? "ratio" : this.model.get("measurementScale"); //Set the category this.$(".category[value='" + selectedCategory + "']").prop("checked", true); this.switchCategory(); this.renderUnitDropdown(); this.chooseDateTimeFormat(); this.chooseNonNumericDomain(); }, /* * Render the table of code definitions from the enumeratedDomain node of the EML */ renderCodeList: function(codeList){ var scaleType = this.model.get("measurementScale"), $container = this.$("." + scaleType + "-options .enumeratedDomain.non-numeric-domain-type .table"); _.each(codeList.codeDefinition, function(definition){ var row = this.codeListRowTemplate(definition); //Add the row to the table $container.append(row); }, this); }, showValidation: function(e){ //Reset the error messages and styling this.$(".error").removeClass("error"); this.$(".notification").text(""); //If the measurement scale model is NOT valid if( !this.$(".category:checked").length ){ this.$(".category-container") .addClass("error") .find(".notification") .text("Choose a category") .addClass("error"); //Trigger the invalid event on the attribute model this.model.get("parentModel").trigger("invalid", this.model.get("parentModel")); } else if( !this.model.isValid() ){ //Get the errors var errors = this.model.validationError, modelType = this.model.get("measurementScale"); //Display error messages for each type of error _.each(Object.keys(errors), function(attr){ //If this is an enumeratedDomain error if(attr == "enumeratedDomain"){ var view = this; //Give the user a few milliseconds to focus on a new element setTimeout(function(){ //Highlight the inputs in code rows that are empty var emptyInputs = view.$("." + modelType + "-options .codelist.input") .not(document.activeElement) .filter(function(){ if( $(this).val() ) return false; else return true; }); emptyInputs.addClass("error"); if(emptyInputs.length) view.$("." + modelType + "-options [data-category='enumeratedDomain'] .notification").text(errors[attr]).addClass("error"); }, 200); } //For all other attributes, just display the errors the same way else{ this.$("." + modelType + "-options [data-category='" + attr + "'] .notification").text(errors[attr]).addClass("error"); this.$("." + modelType + "-options .input[data-category='" + attr + "']").addClass("error"); } //Highlight the border of the non numeric domain container if(attr == "nonNumericDomain"){ this.$("." + modelType + "-options.non-numeric-domain").addClass("error"); } }, this); //Trigger the invalid event on the attribute model // this.model.get("parentModel").trigger("invalid", this.model.get("parentModel")); } else{ //Trigger the valid event on the attribute model // this.model.get("parentModel").trigger("valid", this.model.get("parentModel")); } }, switchCategory: function(){ //Switch the category in the view var chosenCategory = this.$("input[name='measurementScale']:checked").val(); //Show the new category options this.$(".options").hide(); this.$("." + chosenCategory + "-options.options").show(); //Get the current category var modelCategory = this.model.get("measurementScale"); //Get the parent attribute model var parentEMLAttrModel = this.model.get("parentModel"); //Switch the model type, if needed if(chosenCategory && (modelCategory != chosenCategory) && !(modelCategory == "interval" && chosenCategory == "ratio")){ var newModel; if(typeof this.modelCache != "object"){ this.modelCache = {}; } //Get the model type from this view's cache if(this.modelCache[chosenCategory]) newModel = this.modelCache[chosenCategory]; else if( chosenCategory == "ratio" && this.modelCache["interval"] ) newModel = this.modelCache["interval"]; //Get a new model instance based on the type else newModel = EMLMeasurementScale.getInstance(chosenCategory); //Save this model for later in case the user switches back if(modelCategory) this.modelCache[modelCategory] = this.model; //save the new model this.model = newModel; //Set references to and from this model and the parent attribute model this.model.set("parentModel", parentEMLAttrModel); parentEMLAttrModel.set("measurementScale", this.model); //Update the codelist values, if needed if(chosenCategory == "nominal" || chosenCategory == "ordinal" && this.model.get("nonNumericDomain").length && this.model.get("nonNumericDomain")[0].enumeratedDomain){ this.updateCodeList(); } } }, renderUnitDropdown: function(){ if(this.$("select.units").length) return; //Create a dropdown menu var select = $(document.createElement("select")) .addClass("units full-width input") .attr("data-category", "unit"); var eml = this.model.getParentEML(); //Get the units collection or wait until it has been fetched if(!eml.units.length){ this.listenTo(eml.units, "sync", this.renderUnitDropdown); return; } //Create a default option var defaultOption = $(document.createElement("option")) .text("Choose a standard unit"); select.append(defaultOption); //Create an "Other" option to show at the top var otherOption = $(document.createElement("option")) .text("Other / None") .attr("value", "dimensionless"); select.append(otherOption); //Create each unit option in the unit dropdown eml.units.each(function(unit){ var option = $(document.createElement("option")) .val(unit.get("_name")) .text(unit.get("_name").charAt(0).toUpperCase() + unit.get("_name").slice(1) + " (" + unit.get("description") + ")") .data({ model: unit }); select.append(option); }, this); //Add the dropdown to the page this.$(".units-container").append(select); //Select the unit from the EML, if there is one var currentUnit = this.model.get("unit"); if(currentUnit && currentUnit.standardUnit){ //Get the dropdown for this measurement scale // (We default interval to ratio in the editor) var currentDropdown = this.$(".ratio-options select"); //Select the unit from the EML currentDropdown.val(currentUnit.standardUnit); } //If this unit is a custom unit else if( currentUnit && currentUnit.customUnit ){ //Create an