diff --git a/assets/controllers/filters/number_constraint_controller.js b/assets/controllers/filters/number_constraint_controller.js index fe236bcf..2a432426 100644 --- a/assets/controllers/filters/number_constraint_controller.js +++ b/assets/controllers/filters/number_constraint_controller.js @@ -13,8 +13,14 @@ export default class extends Controller { */ update() { + const two_element_values = [ + "BETWEEN", + 'RANGE_IN_RANGE', + 'RANGE_INTERSECT_RANGE' + ]; + for (const thingToHide of this.thingsToHideTargets) { - thingToHide.classList.toggle("d-none", this.operatorTarget.value !== "BETWEEN"); + thingToHide.classList.toggle("d-none", !two_element_values.includes(this.operatorTarget.value)); } } } \ No newline at end of file diff --git a/src/DataTables/Filters/Constraints/Part/ParameterConstraint.php b/src/DataTables/Filters/Constraints/Part/ParameterConstraint.php index 288eff88..58509546 100644 --- a/src/DataTables/Filters/Constraints/Part/ParameterConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/ParameterConstraint.php @@ -22,6 +22,9 @@ class ParameterConstraint extends AbstractConstraint /** @var TextConstraint */ protected $value_text; + /** @var ParameterValueConstraint */ + protected $value; + /** @var string The alias to use for the subquery */ protected $alias; @@ -33,6 +36,7 @@ class ParameterConstraint extends AbstractConstraint $this->alias = uniqid('param_', false); $this->value_text = new TextConstraint($this->alias . '.value_text'); + $this->value = new ParameterValueConstraint($this->alias ); } public function isEnabled(): bool @@ -71,6 +75,7 @@ class ParameterConstraint extends AbstractConstraint //Apply all subfilters $this->value_text->apply($subqb); + $this->value->apply($subqb); //Copy all parameters from the subquery to the main query //We can not use setParameters here, as this would override the exiting paramaters in queryBuilder @@ -144,14 +149,12 @@ class ParameterConstraint extends AbstractConstraint } /** - * DO NOT USE THIS SETTER! - * This is just a workaround for collectionType behavior - * @param $value - * @return $this + * @return ParameterValueConstraint */ - /*public function setValueText($value): self + public function getValue(): ParameterValueConstraint { - //Do not really set the value here, as we dont want to override the constraint created in the constructor - return $this; - }*/ + return $this->value; + } + + } \ No newline at end of file diff --git a/src/DataTables/Filters/Constraints/Part/ParameterValueConstraint.php b/src/DataTables/Filters/Constraints/Part/ParameterValueConstraint.php new file mode 100644 index 00000000..27b91965 --- /dev/null +++ b/src/DataTables/Filters/Constraints/Part/ParameterValueConstraint.php @@ -0,0 +1,130 @@ +', '<=', '>=', 'BETWEEN', + //Additional operators + 'IN_RANGE', 'NOT_IN_RANGE', 'GREATER_THAN_RANGE', 'GREATER_EQUAL_RANGE', 'LESS_THAN_RANGE', 'LESS_EQUAL_RANGE', 'RANGE_IN_RANGE', 'RANGE_INTERSECT_RANGE']; + + /** + * @param string $alias The alias which is used in the sub query of ParameterConstraint + */ + public function __construct(string $alias) { + $this->alias = $alias; + + parent::__construct($alias . '.value_typical'); + } + + public function apply(QueryBuilder $queryBuilder): void + { + //Skip if not enabled + if(!$this->isEnabled()) { + return; + } + + $paramName1 = $this->generateParameterIdentifier('value1'); + $paramName2 = $this->generateParameterIdentifier('value2'); + + if ($this->operator === 'IN_RANGE') { + + $queryBuilder->andWhere( + "({$this->alias}.value_min <= :{$paramName1} AND {$this->alias}.value_max >= :{$paramName1}) OR + ({$this->alias}.value_typical = :{$paramName1})" + ); + + $queryBuilder->setParameter($paramName1, $this->value1); + + return; + } + + if ($this->operator === 'NOT_IN_RANGE') { + + $queryBuilder->andWhere( + "({$this->alias}.value_min > :{$paramName1} OR {$this->alias}.value_max < :{$paramName1}) AND + ({$this->alias}.value_typical IS NULL OR {$this->alias}.value_typical != :{$paramName1})" + ); + + $queryBuilder->setParameter($paramName1, $this->value1); + + return; + } + + if ($this->operator === 'GREATER_THAN_RANGE') { + $queryBuilder->andWhere( + "{$this->alias}.value_max < :{$paramName1} OR {$this->alias}.value_typical < :{$paramName1}" + ); + + $queryBuilder->setParameter($paramName1, $this->value1); + + return; + } + + if ($this->operator === 'GREATER_EQUAL_RANGE') { + $queryBuilder->andWhere( + "{$this->alias}.value_max <= :{$paramName1} OR {$this->alias}.value_typical <= :{$paramName1}" + ); + + $queryBuilder->setParameter($paramName1, $this->value1); + + return; + } + + if ($this->operator === 'LESS_THAN_RANGE') { + $queryBuilder->andWhere( + "{$this->alias}.value_min > :{$paramName1} OR {$this->alias}.value_typical > :{$paramName1}" + ); + + $queryBuilder->setParameter($paramName1, $this->value1); + + return; + } + + if ($this->operator === 'LESS_EQUAL_RANGE') { + $queryBuilder->andWhere( + "{$this->alias}.value_min >= :{$paramName1} OR {$this->alias}.value_typical >= :{$paramName1}" + ); + + $queryBuilder->setParameter($paramName1, $this->value1); + + return; + } + + // This operator means the constraint range must lie completely within the parameter value range + if ($this->operator === 'RANGE_IN_RANGE') { + $queryBuilder->andWhere( + "({$this->alias}.value_min <= :{$paramName1} AND {$this->alias}.value_max >= :{$paramName2}) OR + ({$this->alias}.value_typical >= :{$paramName1} AND {$this->alias}.value_typical <= :{$paramName2})" + ); + + $queryBuilder->setParameter($paramName1, $this->value1); + $queryBuilder->setParameter($paramName2, $this->value2); + + return; + } + + if ($this->operator === 'RANGE_INTERSECT_RANGE') { + $queryBuilder->andWhere( + //The ORs are important here!! + "({$this->alias}.value_min <= :{$paramName1} OR {$this->alias}.value_min <= :{$paramName2}) OR + ({$this->alias}.value_max >= :{$paramName1} OR {$this->alias}.value_max >= :{$paramName2}) OR + ({$this->alias}.value_typical >= :{$paramName1} AND {$this->alias}.value_typical <= :{$paramName2})" + ); + + $queryBuilder->setParameter($paramName1, $this->value1); + $queryBuilder->setParameter($paramName2, $this->value2); + + return; + } + + + //For all other cases use the default implementation + parent::apply($queryBuilder); + } +} \ No newline at end of file diff --git a/src/Form/Filters/Constraints/NumberConstraintType.php b/src/Form/Filters/Constraints/NumberConstraintType.php index 25b6666a..8a76d01c 100644 --- a/src/Form/Filters/Constraints/NumberConstraintType.php +++ b/src/Form/Filters/Constraints/NumberConstraintType.php @@ -13,6 +13,17 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class NumberConstraintType extends AbstractType { + protected const CHOICES = [ + '' => '', + '=' => '=', + '!=' => '!=', + '<' => '<', + '>' => '>', + '<=' => '<=', + '>=' => '>=', + 'filter.number_constraint.value.operator.BETWEEN' => 'BETWEEN', + ]; + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ @@ -31,17 +42,6 @@ class NumberConstraintType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options): void { - $choices = [ - '' => '', - '=' => '=', - '!=' => '!=', - '<' => '<', - '>' => '>', - '<=' => '<=', - '>=' => '>=', - 'filter.number_constraint.value.operator.BETWEEN' => 'BETWEEN', - ]; - $builder->add('value1', NumberType::class, array_merge_recursive([ 'label' => 'filter.number_constraint.value1', 'attr' => [ @@ -68,7 +68,7 @@ class NumberConstraintType extends AbstractType $builder->add('operator', ChoiceType::class, [ 'label' => 'filter.number_constraint.operator', - 'choices' => $choices, + 'choices' => static::CHOICES, 'required' => false, ]); } diff --git a/src/Form/Filters/Constraints/ParameterConstraintType.php b/src/Form/Filters/Constraints/ParameterConstraintType.php index f5596bfe..1a4507a6 100644 --- a/src/Form/Filters/Constraints/ParameterConstraintType.php +++ b/src/Form/Filters/Constraints/ParameterConstraintType.php @@ -41,6 +41,9 @@ class ParameterConstraintType extends AbstractType //'required' => false, ] ); + $builder->add('value', ParameterValueConstraintType::class, [ + ]); + /* * I am not quite sure why this is needed, but somehow symfony tries to create a new instance of TextConstraint * instead of using the existing one for the prototype (or the one from empty data). This fails as the constructor of TextConstraint requires diff --git a/src/Form/Filters/Constraints/ParameterValueConstraintType.php b/src/Form/Filters/Constraints/ParameterValueConstraintType.php new file mode 100644 index 00000000..571ff9ae --- /dev/null +++ b/src/Form/Filters/Constraints/ParameterValueConstraintType.php @@ -0,0 +1,33 @@ + '', + 'filter.parameter_value_constraint.operator.=' => '=', + 'filter.parameter_value_constraint.operator.!=' => '!=', + 'filter.parameter_value_constraint.operator.<' => '<', + 'filter.parameter_value_constraint.operator.>' => '>', + 'filter.parameter_value_constraint.operator.<=' => '<=', + 'filter.parameter_value_constraint.operator.>=' => '>=', + 'filter.parameter_value_constraint.operator.BETWEEN' => 'BETWEEN', + + //Extensions by ParameterValueConstraint + 'filter.parameter_value_constraint.operator.IN_RANGE' => 'IN_RANGE', + 'filter.parameter_value_constraint.operator.NOT_IN_RANGE' => 'NOT_IN_RANGE', + 'filter.parameter_value_constraint.operator.GREATER_THAN_RANGE' => 'GREATER_THAN_RANGE', + 'filter.parameter_value_constraint.operator.GREATER_EQUAL_RANGE' => 'GREATER_EQUAL_RANGE', + 'filter.parameter_value_constraint.operator.LESS_THAN_RANGE' => 'LESS_THAN_RANGE', + 'filter.parameter_value_constraint.operator.LESS_EQUAL_RANGE' => 'LESS_EQUAL_RANGE', + + 'filter.parameter_value_constraint.operator.RANGE_IN_RANGE' => 'RANGE_IN_RANGE', + 'filter.parameter_value_constraint.operator.RANGE_INTERSECT_RANGE' => 'RANGE_INTERSECT_RANGE' + ]; + + public function getParent(): string + { + return NumberConstraintType::class; + } +} \ No newline at end of file diff --git a/templates/Form/FilterTypesLayout.html.twig b/templates/Form/FilterTypesLayout.html.twig index 55292570..16f93e04 100644 --- a/templates/Form/FilterTypesLayout.html.twig +++ b/templates/Form/FilterTypesLayout.html.twig @@ -43,9 +43,9 @@ {% block parameter_constraint_widget %} {% import 'components/collection_type.macro.html.twig' as collection %} - {{ form_widget(form.name, {"attr": {"data-pages--parameters-autocomplete-target": "name"}}) }} + {{ form_widget(form.name, {"attr": {"data-pages--parameters-autocomplete-target": "name"}}) }} {{ form_widget(form.symbol, {"attr": {"data-pages--parameters-autocomplete-target": "symbol", "data-pages--latex-preview-target": "input"}}) }} - + {{ form_widget(form.value) }} {{ form_widget(form.unit, {"attr": {"data-pages--parameters-autocomplete-target": "unit", "data-pages--latex-preview-target": "input"}}) }} {{ form_widget(form.value_text) }} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 73469609..f0945934 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -9543,5 +9543,95 @@ Element 3 Filter + + + filter.parameter_value_constraint.operator.= + Typ. Value = + + + + + filter.parameter_value_constraint.operator.!= + Typ. Value != + + + + + filter.parameter_value_constraint.operator.< + + + + + + filter.parameter_value_constraint.operator.> + ]]> + + + + + filter.parameter_value_constraint.operator.<= + + + + + + filter.parameter_value_constraint.operator.>= + =]]> + + + + + filter.parameter_value_constraint.operator.BETWEEN + Typ. Value is between + + + + + filter.parameter_value_constraint.operator.IN_RANGE + In Value range + + + + + filter.parameter_value_constraint.operator.NOT_IN_RANGE + Not in Value range + + + + + filter.parameter_value_constraint.operator.GREATER_THAN_RANGE + Greater than Value range + + + + + filter.parameter_value_constraint.operator.GREATER_EQUAL_RANGE + Greater equal than Value range + + + + + filter.parameter_value_constraint.operator.LESS_THAN_RANGE + Less than Value range + + + + + filter.parameter_value_constraint.operator.LESS_EQUAL_RANGE + Less equal than Value range + + + + + filter.parameter_value_constraint.operator.RANGE_IN_RANGE + Range is completly in Value range + + + + + filter.parameter_value_constraint.operator.RANGE_INTERSECT_RANGE + Range intersects Value range + +