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 }} + + + + + +
+ + - + +
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