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

/* global define */
define(["jquery", "underscore", "backbone", "collections/Filters", "models/filters/Filter",
    "models/filters/BooleanFilter", "models/filters/ChoiceFilter", "models/filters/DateFilter",
    "models/filters/NumericFilter", "models/filters/ToggleFilter"],
    function($, _, Backbone, Filters, Filter,
        BooleanFilter, ChoiceFilter, DateFilter,
        NumericFilter, ToggleFilter) {

  /**
  * @class FilterGroup
  * @classdesc A group of multiple Filters, which may be combined to create a complex query
  * @classcategory Models/Filters
  * @extends Backbone.Model
  * @constructs
  */
	var FilterGroup = Backbone.Model.extend(
    /** @lends FilterGroup.prototype */{

    //Default attributes for this model
    defaults: function(){
      return {
        label: null,
        description: null,
        icon: null,
        filters:  new Filters(),
        objectDOM: null
      }
    },

    /**
    * This function is executed whenever a new model is created.
    */
    initialize: function(attributes){
      if( attributes.objectDOM ){
        this.set( this.parse(attributes.objectDOM) );
      }

      if(attributes && attributes.filters){
        var filtersCollection = new Filters();
        filtersCollection.add(attributes.filters);
        this.set("filters", filtersCollection);
      }
    },

    /**
    * Overrides the default Backbone.Model.parse() function to parse the filterGroup
    * XML snippet
    *
    * @param {Element} xml - The XML Element that contains all the FilterGroup elements
    * @return {JSON} The result of the parsed XML, in JSON. To be set directly on the model.
    */
    parse: function(xml){
      var modelJSON = {};

      //Parse all the text nodes
      modelJSON.label = this.parseTextNode(xml, "label");
      modelJSON.description = this.parseTextNode(xml, "description");
      modelJSON.icon = this.parseTextNode(xml, "icon");

      //Start an array for the filters
      modelJSON.filters = new Filters();

      //Iterate over each child and look for filter elements
      $(xml).children().each(function(i, filterNode){

        var filterType = filterNode.tagName;

        switch (filterType) {
          case "filter":
            modelJSON.filters.add( new Filter({ objectDOM: filterNode, inFilterGroup: true }) );
            break;
          case "numericFilter":
            modelJSON.filters.add( new NumericFilter({ objectDOM: filterNode, inFilterGroup: true }) );
            break;
          case "booleanFilter":
            modelJSON.filters.add( new BooleanFilter({ objectDOM: filterNode, inFilterGroup: true }) );
            break;
          case "choiceFilter":
            modelJSON.filters.add( new ChoiceFilter({ objectDOM: filterNode, inFilterGroup: true }) );
            break;
          case "dateFilter":
            modelJSON.filters.add( new DateFilter({ objectDOM: filterNode, inFilterGroup: true }) );
            break;
          case "toggleFilter":
            modelJSON.filters.add( new ToggleFilter({ objectDOM: filterNode, inFilterGroup: true }) );
            break;
        }

      });

      return modelJSON;
    },

    /**
    * Gets the text content of the XML node matching the given node name
    *
    * @param {Element} parentNode - The parent node to select from
    * @param {string} nodeName - The name of the XML node to parse
    * @param {boolean} isMultiple - If true, parses the nodes into an array
    * @return {(string|Array)} - Returns a string or array of strings of the text content
    */
    parseTextNode: function( parentNode, nodeName, isMultiple ){
      var node = $(parentNode).children(nodeName);

      //If no matching nodes were found, return falsey values
      if( !node || !node.length ){

        //Return an empty array if the isMultiple flag is true
        if( isMultiple )
          return [];
        //Return null if the isMultiple flag is false
        else
          return null;
      }
      //If exactly one node is found and we are only expecting one, return the text content
      else if( node.length == 1 && !isMultiple ){
        return node[0].textContent.trim();
      }
      //If more than one node is found, parse into an array
      else{

        return _.map(node, function(node){
          return node.textContent.trim() || null;
        });

      }
    },

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

      // Get the current object DOM
      var objectDOM = this.get("objectDOM");

      // Clone and empty the DOM if it exists
      if (objectDOM) {
        objectDOM = objectDOM.cloneNode(true);
        $(objectDOM).empty();
      // Otherwise create a new <filterGroup> node from scratch
      } else {
          // create an XML filterGroup element from scratch
          var objectDOM = new DOMParser().parseFromString("<filterGroup></filterGroup>", "text/xml"),
              objectDOM = $(objectDOM).children()[0];
      }

      // Get the new filter group data
      var filterGroupData = {
        label: this.get("label"),
        description: this.get("description"),
        icon: this.get("icon")
      }

      // Serialize the simple text elements
      _.map(filterGroupData, function(value, nodeName){
        // Don't serialize falsey values
        if(value){
          // Make new sub-node
          var nodeSerialized = objectDOM.ownerDocument.createElement(nodeName);
          $(nodeSerialized).text(value);
          // Append new sub-node to objectDOM
          $(objectDOM).append(nodeSerialized);
        }
      });

      // Serialize the filters
      var filterModels = this.get("filters").models;

      // Don't serialize falsey values
      if(filterModels){
        // Update each filter and append it to the DOM
        _.each(filterModels, function(filterModel){
          if(filterModel){
            var filterModelSerialized = filterModel.updateDOM();
          }
          $(objectDOM).append(filterModelSerialized);
        });
      }

      return objectDOM

    }

  });

  return FilterGroup;
});