diff --git a/README.md b/README.md index ef0cc63..4ec1aab 100644 --- a/README.md +++ b/README.md @@ -379,6 +379,26 @@ query().sort('lastName.toLowerCase()'); The second query above will sort by last name irrespective of casing. +### Complex queries + +Operators can work on two fields instead of just on a field and a value: + +```js +query('field1').is(query.field('field2')); +``` + +Likewise, you can first give a value and then a field: + +```js +query(query.value('value1')).is(query.field('field2')); +``` + +Or compare two plain values: + +```js +query(query.value('value1')).is('value2'); +``` + ## Backbone Support and Adding query to Backbone.Collections query was built with Backbone in mind. Though you may use `query("get('firstName')").is("John")` to effectively work with diff --git a/lib/query.js b/lib/query.js index a963db2..d894880 100644 --- a/lib/query.js +++ b/lib/query.js @@ -14,6 +14,26 @@ function query(field) { this._reduce = []; } +query.boxed = function (field) { + this.value = field; +}; + +query.field = function (field) { + if ( !(this instanceof query.field) ) { + return new query.field(field); + } + query.boxed.apply(this, arguments); +}; +query.field.prototype = new query.boxed(); + +query.value = function (val) { + if ( !(this instanceof query.value) ) { + return new query.value(val); + } + query.boxed.apply(this, arguments); +}; +query.value.prototype = new query.boxed(); + function select(array) { if ( !(this instanceof select) ) { return new select(array); @@ -157,15 +177,15 @@ query.select = select; return this._oper(this._store(lookup)); }, has: function(value) { - if (this._expression) this._expression.template = '%not(%term != null && %term.indexOf(%value) != -1)'; + if (this._expression) this._expression.template = '%not(%term != null && %term.indexOf(%term) != -1)'; return this._oper(null, value); }, startsWith: function(value) { - if (this._expression) this._expression.template = '%not(%term != null && %term.substr(0, %operator) == %value)'; + if (this._expression) this._expression.template = '%not(%term != null && %term.substr(0, %operator) == %term)'; return this._oper(value.length, value); }, endsWith: function(value) { - if (this._expression) this._expression.template = '%not(%term != null && %term.substr(%term.length - %operator) == %value)'; + if (this._expression) this._expression.template = '%not(%term != null && %term.substr(%term.length - %operator) == %term)'; return this._oper(value.length, value); }, gt: function(value) { @@ -185,7 +205,7 @@ query.select = select; return this._oper(this._store(value)); }, same: function(value) { - if (this._expression) this._expression.template = '%not(JSON.stringify(%term) %operator %value)'; + if (this._expression) this._expression.template = '%not(JSON.stringify(%term) %operator %term)'; value = JSON.stringify(value); return this._oper('==', value); }, @@ -213,7 +233,7 @@ query.select = select; } return this._oper(this._store(value)); } else { - if (this._expression) this._expression.template = '%not(type(%term) %operator %value)'; + if (this._expression) this._expression.template = '%not(type(%term) %operator %term)'; return this._oper('==', value); } }, @@ -274,22 +294,35 @@ query.select = select; this.operator = operator; this.value = value; this.not = not; - this.template = '%not(%term %operator %value)'; + this.template = '%not(%term %operator %term)'; } Expression.prototype = { toString: function() { var self = this; + var asDate; + var terms = [ this.term, this.value ].map(function (term, k) { + if (!(term instanceof query.boxed)) { + term = new query[ (k === 0) ? 'field' : 'val'](term); + } + asDate |= term.value instanceof Date || term.forceDate; + return term; + }); + function getTerm(t) { + if (t instanceof query.field) { + return '(obj.' + t.value + ' || (typeof obj.get === "function" && obj.get("' + t.value + '")))' + (asDate ? '.getTime()' : ''); + } else { + return JSON.stringify(asDate ? t.value.getTime() : t.value); + } + } return this.template.replace(/%\w+/g, function(match) { switch(match) { case '%not': return self.not ? '!' : ''; case '%term': - return '(obj.' + self.term + ' || (typeof obj.get === "function" && obj.get("' + self.term + '")))' + (self.value instanceof Date ? '.getTime()' : ''); + return getTerm(terms.shift()); case '%operator': return self.operator; - case '%value': - return JSON.stringify(self.value instanceof Date ? self.value.getTime() : self.value); } }); }