toy
toy

Reputation: 12141

How to conditional merge some elements in a list in JavaScript?

I'm building this Builder pattern and I'd like to merge some of the elements in a list together. But I'd like to do this in a cleaner way. This is what I've come up with so far which works but I'm sure there's a better way to do this.

function FiltersBuilder() {
  this.filters = [];
};

FiltersBuilder.prototype.addFilter = function(options) {
  var filter = {
    'type': 'selector',
    'dimension': options.dimension,
    'value': options.value
  }
  this.filters.push(filter);
  return this;
}

FiltersBuilder.prototype.addRegexFilter = function(options) {
  var filter = {
    'type': 'regex',
    'dimension': options.dimension,
    'pattern': options.value
  }
  this.filters.push(filter);
  return this;
}

FiltersBuilder.prototype.not = function() {
  var not = {
    'type': 'not'
  };
  this.filters.push(not);
  return this;
}

FiltersBuilder.prototype.getFilters = function() {
  var result = [];
  this.filters.forEach(function each(filter, index, theFilters) {
    if (filter.type === 'not') {
      var filterToMerge = theFilters[index + 1];
      var mergedFilter = _.merge(filter, {field: filterToMerge});
      result.push(mergedFilter);
    } else {
        if(index > 0) {
        notFilter = theFilters[index - 1];
        if(notFilter.type === 'not') {
            return;
        }
      }
      result.push(filter);
    }
  });

  return result;
}

var filterBuilder = new FiltersBuilder();
filterBuilder.addFilter({
  dimension: '_o',
  value: 'origin'
});
filterBuilder.not().addRegexFilter({
  dimension: 'cTy',
  value: 'value'
});

console.log(filterBuilder.getFilters());

If not method is called before adding filter, I would like to merge the not filter with the next element and add that object to the result list. But if not is not called before adding filter then don't do anything just add the filter to result list.

https://jsfiddle.net/9wyqbovu/

Upvotes: 0

Views: 1042

Answers (2)

user663031
user663031

Reputation:

Instead of treating not as a filter to be added to the list of filters, and processed when you get the filters, treat it as a modifier--a flag. Maintain a not property on the FiltersBuilder object, initialized to false, which a call to FilterBuilders.not will toggle. On each filter, also add a not property, which is set by the current value of the not flag on FilterBuilders (which is then reset). In other words:

function FiltersBuilder() {
  this.filters = [];
  this.not = false;
};

FiltersBuilder.prototype.addFilter = function(options) {
  var filter = {
    'type': 'selector',
    'dimension': options.dimension,
    'value': options.value,
    not: this.not
  }
  this.not = false;
  this.filters.push(filter);
  return this;
}

FiltersBuilder.prototype.addRegexFilter = function(options) {
  var filter = {
    'type': 'regex',
    'dimension': options.dimension,
    'pattern': options.value,
    not: this.not
  }
  this.not = false;
  this.filters.push(filter);
  return this;
}

FiltersBuilder.prototype.not = function() {
  this.not = !this.not;
}

FiltersBuilder.prototype.getFilters = function() {
  return this.filters;
}

var filterBuilder = new FiltersBuilder();
filterBuilder.addFilter({
  dimension: '_o',
  value: 'origin'
});
filterBuilder.not().addRegexFilter({
  dimension: 'cTy',
  value: 'value'
});

console.log(filterBuilder.getFilters());

If you would prefer to be able to say filterBuilder.not.addFilter (without the parentheses after not), then define not as a getter, ie

Object.defineProperty(FiltersBuilder.prototype, "not", {
  get: function() {
    this.not = !this.not;
    return this;
  }
});

Upvotes: 3

MinusFour
MinusFour

Reputation: 14423

Well, how about if we do iterate it from the right and use unshift instead of push:

getFilters = function(){
    return this.filters.reduceRight(function(results, filter){
        if(filter.type === 'not'){
           //get filter to 'not'
           var notFilter = results[0];
           Object.assign(notFilter, filter); //Or _.merge
        } else {
           results.unshift(filter);
        }
        return results;
    }, []);
}

Alternatively, you can use push instead of unshift, grab the last element of the array as the filter to negate (instead of the first one) and reverse the results.

getFilters = function(){
    return this.filters.reduceRight(function(results, filter){
        if(filter.type === 'not'){
           //get filter to 'not'
           var notFilter = results[results.length - 1];
           Object.assign(notFilter, filter); //Or _.merge
        } else {
           results.push(filter);
        }
        return results;
    }, []).reverse();
}

Upvotes: 2

Related Questions