diff --git a/src/components/QueryableInput.vue b/src/components/QueryableInput.vue
index dd84a04c8..0ae5a234d 100644
--- a/src/components/QueryableInput.vue
+++ b/src/components/QueryableInput.vue
@@ -16,9 +16,38 @@
{{ op.label }}
+
+
+ {{ $t('search.noOptions') }}
+
+
+
+
+ -
+
+
import('./Description.vue')
+ Description: () => import('./Description.vue'),
+ Multiselect: () => import('vue-multiselect')
},
mixins: [
DatePickerMixin
@@ -177,7 +208,24 @@ export default {
this.$emit('update:value', CqlValue.create(val));
},
updateOperator(op) {
+ if (op === CqlIn) {
+ this.updateValue([]);
+ }
+ else if (op === CqlBetween) {
+ this.updateValue([this.queryable.defaultValue, this.queryable.defaultValue]);
+ }
this.$emit('update:operator', op);
+ },
+ addInElement(element) {
+ this.updateValue(this.value.value.concat([element]));
+ },
+ setInElements(elements) {
+ this.updateValue(elements);
+ },
+ updateBetweenValue(ix, value) {
+ let values = this.value.value.slice(0);
+ values[ix] = value;
+ this.updateValue(values);
}
}
};
diff --git a/src/locales/en/texts.json b/src/locales/en/texts.json
index 655769935..c5a69e060 100644
--- a/src/locales/en/texts.json
+++ b/src/locales/en/texts.json
@@ -187,6 +187,8 @@
"addFilter": "Add filter",
"addItemIds": "Press enter to add an Item ID",
"additionalFilters": "Additional filters",
+ "between": "between",
+ "betweenOperatorDescription": "Tests whether a numeric field value lies within the given range. The given range is inclusive.",
"buttons": {
"filter": "Filter",
"reset": "Reset"
@@ -200,6 +202,8 @@
"freeTextDescription": "Search for the given text in fields such as the title and the description.",
"greaterThan": "greater than",
"greaterThanEqual": "greater than or equal to",
+ "in": "included in",
+ "inOperatorDescription": "Checks whether the field value is any of the given values.",
"itemIds": "Item IDs",
"itemsPerPage": "Items per page",
"itemsPerPageDescription": "Number of items requested per page, max. {maxItems} items.",
diff --git a/src/models/cql2/operators/advanced.js b/src/models/cql2/operators/advanced.js
index d070bf93d..2f7125313 100644
--- a/src/models/cql2/operators/advanced.js
+++ b/src/models/cql2/operators/advanced.js
@@ -22,3 +22,57 @@ export class CqlLike extends CqlComparisonOperator {
}
}
+
+export class CqlBetween extends CqlComparisonOperator {
+
+ static SYMBOL = "between";
+
+ constructor(pred = null, obj = []) {
+ super(CqlBetween.SYMBOL, pred, obj);
+ }
+
+ static get label() {
+ return "≥ … ≤";
+ }
+
+ static get longLabel() {
+ return i18n.t('search.between');
+ }
+
+ static get description() {
+ return i18n.t('search.betweenOperatorDescription');
+ }
+
+ toText() {
+ return `${this.args[0].toText()} BETWEEN ${this.args[1].toText()} and ${this.args[2].toText()}`;
+ }
+
+}
+
+// Currently only implemented for strings and numbers, not for booleans and time instances
+export class CqlIn extends CqlComparisonOperator {
+
+ static SYMBOL = "in";
+
+ constructor(pred = null, obj = []) {
+ super(CqlIn.SYMBOL, pred, obj);
+ }
+
+ static get label() {
+ return "∈";
+ }
+
+ static get longLabel() {
+ return i18n.t('search.in');
+ }
+
+ static get description() {
+ return i18n.t('search.inOperatorDescription');
+ }
+
+ toText() {
+ let values = this.args.slice(1).map(arg => arg.toText()).join(`,`);
+ return `${this.args[0].toText()} IN (${values})`;
+ }
+
+}
diff --git a/src/models/cql2/queryable.js b/src/models/cql2/queryable.js
index 57c5a76ca..f8c121031 100644
--- a/src/models/cql2/queryable.js
+++ b/src/models/cql2/queryable.js
@@ -1,7 +1,7 @@
import { formatKey } from "@radiantearth/stac-fields/helper";
import i18n from '../../i18n.js';
import { CqlEqual, CqlGreaterThan, CqlGreaterThanEqual, CqlLessThan, CqlLessThanEqual, CqlNotEqual } from "./operators/comparison";
-import { CqlLike } from "./operators/advanced";
+import { CqlBetween, CqlIn, CqlLike } from "./operators/advanced";
export default class Queryable {
@@ -103,8 +103,13 @@ export default class Queryable {
ops.push(CqlGreaterThan);
ops.push(CqlGreaterThanEqual);
}
- else if (this.isText && cql.advancedComparison) {
+ if (this.isNumeric && cql.advancedComparison) {
+ ops.push(CqlBetween);
+ ops.push(CqlIn);
+ }
+ if (this.isText && cql.advancedComparison) {
ops.push(CqlLike);
+ ops.push(CqlIn);
}
return ops;
}
diff --git a/src/models/cql2/value.js b/src/models/cql2/value.js
index 346033d76..78ddd14ba 100644
--- a/src/models/cql2/value.js
+++ b/src/models/cql2/value.js
@@ -7,7 +7,10 @@ export default class CqlValue {
}
static create(value) {
- if (value instanceof Date) {
+ if (Array.isArray(value)) {
+ return new CqlArray(value);
+ }
+ else if (value instanceof Date) {
return new CqlTimestamp(value);
}
else if (typeof value === 'string') {
@@ -63,3 +66,22 @@ export class CqlString extends CqlValue {
}
}
+
+export class CqlArray extends CqlValue {
+
+ constructor(value) {
+ super(value);
+ }
+
+ toJSON() {
+ return this.value;
+ }
+
+ toText() {
+ return this.value.map(elem => {
+ let cql = elem instanceof CqlValue ? elem : CqlValue.create(elem);
+ return cql.toText();
+ });
+ }
+
+}
\ No newline at end of file