Source: src/js/models/filters/ChoiceFilter.js

/* global define */
define(['jquery', 'underscore', 'backbone', 'models/filters/Filter'],
    function($, _, Backbone, Filter) {

  /**
  * @class ChoiceFilter
  * @classdesc A Filter whose search term is one or more choices from a defined list
  * @classcategory Models/Filters
  * @name ChoiceFilter
  * @constructs ChoiceFilter
  * @extends Filter
  */
	var ChoiceFilter = Filter.extend(
    /** @lends ChoiceFilter.prototype */{

    /**
    * @inheritdoc
    * @type {string}
    */
    type: "ChoiceFilter",

    /**
    * The Backbone Model attributes set on this ChoiceFilter
    * @type {object}
    * @extends Filter#defaultts
    * @property {boolean} chooseMultiple - If true, this ChoiceFilter can have multiple choices set as the search term
    * @property {string[]} choices - The list of search terms that can possibly be set on this Filter
    * @property {string} nodeName - The XML node name to use when serializing this model into XML
    */
    defaults: function(){
      return _.extend(Filter.prototype.defaults(), {
        chooseMultiple: true,
        //@type {object} - A literal JS object with a "label" and "value" attribute
        choices: [],
        nodeName: "choiceFilter"
      });
    },

    /**
    * Parses the choiceFilter XML node into JSON
    *
    * @param {Element} xml - The XML Element that contains all the ChoiceFilter elements
    * @return {JSON} - The JSON object literal to be set on the model
    */
    parse: function(xml){

      var modelJSON = Filter.prototype.parse.call(this, xml);

      //Parse the chooseMultiple boolean field
      modelJSON.chooseMultiple = (this.parseTextNode(xml, "chooseMultiple") === "true")? true : false;

      //Start an array for the choices
      modelJSON.choices = [];

      //Iterate over each choice and parse it
      var self = this;
      $(xml).find("choice").each(function(i, choiceNode){

        //Parse the label and value nodes into a literal object
        var choiceObject = {
          label: self.parseTextNode(choiceNode, "label"),
          value: self.parseTextNode(choiceNode, "value")
        }

        //Check that there is a label and value (value can be boolean false or 0, so just check for null or undefined)
        if(choiceObject.label && choiceObject.value !== null && typeof choiceObject.value !== "undefined"){
          modelJSON.choices.push(choiceObject);
        }

      });

      return modelJSON;
    },

    /**
     * Updates the XML DOM with the new values from the model
     *  @inheritdoc
     *  @return {XMLElement} An updated choiceFilter XML element from a portal document
    */
    updateDOM:function(options){

      try{

        var objectDOM = Filter.prototype.updateDOM.call(this);

        if(typeof options != "object"){
          var options = {};
        }

        if( this.get("isUIFilterType") ){
          // Serialize <choice> elements
          var choices = this.get("choices");

          if(choices){
            //Remove all the choice elements
            $(objectDOM).children("choice").remove();

            //Make a new choice element for each choice in the model
            _.each(choices, function(choice){
              // Make new <choice> node
              choiceSerialized = objectDOM.ownerDocument.createElement("choice");
              // Make choice subnodes <label> and <value>
              _.map(choice, function(value, nodeName){

                if(value || value === false){
                  var nodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
                  $(nodeSerialized).text(value);
                  $(choiceSerialized).append(nodeSerialized);
                }

              });
            // append subnodes
            $(objectDOM).append(choiceSerialized);

            });

          }

          //Get the chooseMultiple value from the model
          var chooseMultiple = this.get("chooseMultiple");
          //Remove the chooseMultiple element
          $(objectDOM).children("chooseMultiple").remove();
          //If the model value is a boolean, create a chooseMultiple element and add it to the DOM
          if(chooseMultiple === true || chooseMultiple === false){
            chooseMultipleSerialized = objectDOM.ownerDocument.createElement("chooseMultiple");
            $(chooseMultipleSerialized).text(chooseMultiple);
            $(objectDOM).append(chooseMultipleSerialized);
          };
        }
        else{
          //Remove the filterOptions
          $(objectDOM).find("filterOptions").remove();

          //Change the root element into a <filter> element
          var newFilterEl = objectDOM.ownerDocument.createElement("filter");
          $(newFilterEl).html( $(objectDOM).children() );

          //Return this node
          return newFilterEl;
        }

        return objectDOM;
      }
      //If there's an error, return the original DOM or an empty string
      catch(e){
        return this.get("objectDOM") || "";
      }

      },
    
    /**
    * Checks if the values set on this model are valid and expected
    * @return {object} - Returns a literal object with the invalid attributes and their
    * corresponding error message
    */
    validate : function(){
      try {

        // Validate most of the ChoiceFilter attributes using the parent validate
        // function
        var errors = Filter.prototype.validate.call(this);

        // If everything is valid so far, then we have to create a new object to store
        // errors
        if (typeof errors != "object") {
          errors = {};
        }

        // Delete error messages for the attributes that are going to be validated
        // specially for the ChoiceFilter
        delete errors.choices;

        // Save a reference to the choices
        var choices = this.get("choices")

        // Ensure that there is at least one choice
        if (!choices || choices.length === 0) {
          errors.choices = "At least one search term option is required."
        } else {
          // Remove any empty choices
          choices = choices.filter(function (choice) {
            return (choice.value || choice.label)
          })
          // If there is no value but there is a label, then set the search value to the
          // label. If there is a value but no label, set the label to the value.
          choices.forEach(function (choice) {
            if (!choice.value) {
              choice.value = choice.label
            }
            if (!choice.label) {
              choice.label = choice.value
            }
          })
        }

        // Return the errors, if there are any
        if (Object.keys(errors).length)
          return errors;
        else {
          return;
        }
      }
      catch (error) {
        console.log(
          'There was an error validating a ChoiceFilter' +
          '. Error details: ' + error
        );
      }
    },

  });

  return ChoiceFilter;
});