Creates intersection filter for numeric as well. #887.

This commit is contained in:
mateuswetah 2024-06-03 18:21:53 -03:00
parent ebf478d5ff
commit d215c9a9ce
9 changed files with 613 additions and 16 deletions

View File

@ -252,6 +252,7 @@ import { formHooks } from "../../js/mixins";
import FormFilterNumeric from '../filter-types/numeric/FormNumeric.vue';
import FormFilterNumericInterval from '../filter-types/numeric-interval/FormNumericInterval.vue';
import FormFilterNumericListInterval from '../filter-types/numeric-list-interval/FormNumericListInterval.vue';
import FormFilterNumericsIntersection from '../filter-types/numerics-intersection/FormNumericsIntersection.vue';
import FormFilterDatesIntersection from '../filter-types/dates-intersection/FormDatesIntersection.vue';
export default {
@ -260,6 +261,7 @@ export default {
'tainacan-filter-form-numeric': FormFilterNumeric,
'tainacan-filter-form-numeric-interval': FormFilterNumericInterval,
'tainacan-filter-form-numeric-list-interval': FormFilterNumericListInterval,
'tainacan-filter-form-numerics-intersection': FormFilterNumericsIntersection,
'tainacan-filter-form-dates-intersection': FormFilterDatesIntersection
},
mixins: [ formHooks ],

View File

@ -72,7 +72,9 @@
<b-field
:addons="false"
:label="$i18n.getHelperTitle('tainacan-filter-dates-intersection', 'accept_date_interval')"
style="margin-top: 1.125rem;">
style="margin-top: 1.125rem;"
:type="errors && errors['accept_date_interval'] != undefined ? 'is-danger' : ''"
:message="errors && errors['accept_date_interval'] != undefined ? errors['accept_date_interval'] : ''">
&nbsp;
<b-switch
v-model="acceptDateInterval"
@ -179,8 +181,8 @@
},
onUpdateSecondDateMetadatumId() {
const selectedMetadatum = this.metadata.find( aMetadatum => aMetadatum.id == this.secondDateMetadatumId );
this.selectedMetadatumName = selectedMetadatum ? selectedMetadatum.name : '';
this.selectedMetadatumId = selectedMetadatum ? selectedMetadatum.id : '';
this.secondDateMetadatumName = selectedMetadatum ? selectedMetadatum.name : '';
this.secondDateMetadatumId = selectedMetadatum ? selectedMetadatum.id : '';
this.emitValues();
},
emitValues() {

View File

@ -90,24 +90,16 @@ class Dates_Intersection extends Filter_Type {
$errors = [];
if ( empty($this->get_option('secondary_filter_metadatum_id')) )
$errors[] = [
'secondary_filter_metadatum_id' => __('The secondary date metadatum is required.','tainacan')
];
$errors['secondary_filter_metadatum_id'] = __('The secondary date metadatum is required.','tainacan');
if ( empty($this->get_option('first_comparator')) )
$errors[] = [
'first_comparator' => __('The first comparator is required.','tainacan')
];
$errors['first_comparator'] = __('The first comparator is required.','tainacan');
if ( empty($this->get_option('second_comparator')) )
$errors[] = [
'second_comparator' => __('The second comparator is required.','tainacan')
];
$errors['second_comparator'] = __('The second comparator is required.','tainacan');
if ( empty($this->get_option('accept_date_interval')) )
$errors[] = [
'accept_date_interval' => __('The filter should define if it accepts date interval.','tainacan')
];
$errors['accept_date_interval'] = __('The filter should define if it accepts date interval.','tainacan');
return count($errors) > 0 ? $errors : true;
}

View File

@ -43,6 +43,7 @@ class Filter_Type_Helper {
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\TaxonomyTaginput');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\TaxonomyCheckbox');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Numeric_List_Interval');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Numerics_Intersection');
// the priority should see less than on function
// `load_admin_page()` of class `Admin` in file /src/views/class-tainacan-admin.php

View File

@ -0,0 +1,298 @@
<template>
<div>
<b-field :addons="false">
<label class="label is-inline">
{{ $i18n.getHelperTitle('tainacan-filter-numeric-interval', 'step') }}<span>&nbsp;*&nbsp;</span>
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-numeric-interval', 'step')"
:message="$i18n.getHelperMessage('tainacan-filter-numeric-interval', 'step')" />
</label>
<div
v-if="!showEditStepOptions"
class="is-flex">
<b-select
v-model="step"
name="step_options"
@update:model-value="emitValues()">
<option value="0.001">
0.001
</option>
<option value="0.01">
0.01
</option>
<option value="0.1">
0.1
</option>
<option value="1">
1
</option>
<option value="2">
2
</option>
<option value="5">
5
</option>
<option value="10">
10
</option>
<option value="100">
100
</option>
<option value="1000">
1000
</option>
<option
v-if="step && ![0.001,0.01,0.1,1,2,5,10,100,1000].find( (element) => element == step )"
:value="step">
{{ step }}</option>
</b-select>
<button
class="button is-white is-pulled-right"
:aria-label="$i18n.get('edit')"
@click.prevent="showEditStepOptions = true">
<span
v-tooltip="{
content: $i18n.get('edit'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-edit has-text-secondary" />
</span>
</button>
</div>
<div
v-if="showEditStepOptions"
class="is-flex">
<b-input
v-model="step"
name="max_options"
type="number"
step="1"
@update:model-value="emitValues()" />
<button
class="button is-white is-pulled-right"
@click.prevent="showEditStepOptions = false">
<span
v-tooltip="{
content: $i18n.get('close'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-close has-text-secondary" />
</span>
</button>
</div>
</b-field>
<b-field
:addons="false"
:type="metadataType"
:message="metadataMessage">
<label class="label is-inline">
{{ $i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'secondary_filter_metadatum_id') }}<span :class="metadataType">&nbsp;*&nbsp;</span>
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'secondary_filter_metadatum_id')"
:message="$i18n.getHelperMessage('tainacan-filter-numerics-intersection', 'secondary_filter_metadatum_id')" />
</label>
<b-select
v-model="secondNumericMetadatumId"
name="numerics_intersect[secondary_filter_metadatum_id]"
:placeholder="$i18n.get('instruction_select_second_numeric_to_compare' )"
:loading="loading"
expanded
@change="onUpdateSecondNumericMetadatumId()"
@focus="clear()">
<option :value="''">
{{ $i18n.get('instruction_select_second_numeric_to_compare' ) }}
</option>
<option
v-for="option in metadata.filter(aMetadatum => aMetadatum.id != filter.metadatumId )"
:key="option.id"
:value="option.id">
{{ option.name }}
</option>
</b-select>
</b-field>
<div style="column-count: 2;">
<b-field :addons="false">
<label
style="line-height: normal;"
class="label is-inline">
{{ $i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'first_comparator') }}<span>&nbsp;*&nbsp;</span>
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'first_comparator')"
:message="$i18n.getHelperMessage('tainacan-filter-numerics-intersection', 'first_comparator')" />
</label>
<b-select
v-model="firstComparator"
@update:model-value="emitValues()">
<option
v-for="(comparatorObject, comparatorKey) in comparatorsObject"
:key="comparatorKey"
:value="comparatorKey"
v-html="comparatorObject.symbol + '&nbsp;' + comparatorObject.label" />
</b-select>
</b-field>
<b-field :addons="false">
<label
style="line-height: normal;"
class="label is-inline">
{{ $i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'second_comparator') }}<span>&nbsp;*&nbsp;</span>
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'second_comparator')"
:message="$i18n.getHelperMessage('tainacan-filter-numerics-intersection', 'second_comparator')" />
</label>
<b-select
v-model="secondComparator"
@update:model-value="emitValues()">
<option
v-for="(comparatorObject, comparatorKey) in comparatorsObject"
:key="comparatorKey"
:value="comparatorKey"
v-html="comparatorObject.symbol + '&nbsp;' + comparatorObject.label" />
</b-select>
</b-field>
</div>
<b-field
:addons="false"
:label="$i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'accept_numeric_interval')"
style="margin-top: 1.125rem;"
:type="errors && errors['accept_numeric_interval'] != undefined ? 'is-danger' : ''"
:message="errors && errors['accept_numeric_interval'] != undefined ? errors['accept_numeric_interval'] : ''">
&nbsp;
<b-switch
v-model="acceptNumericInterval"
size="is-small"
:true-value="'yes'"
:false-value="'no'"
:native-value="acceptNumericInterval == 'yes' ? 'yes' : 'no'"
name="accept_numeric_interval"
@update:model-value="emitValues()">
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'accept_numeric_interval')"
:message="$i18n.getHelperMessage('tainacan-filter-numerics-intersection', 'accept_numeric_interval')" />
</b-switch>
</b-field>
</div>
</template>
<script>
import { tainacanApi } from '../../../js/axios';
export default {
props: {
filter: Object,
modelValue: Object,
errors: Object
},
emits: [
'update:model-value',
],
data() {
return {
step: [Number, String],
showEditStepOptions: false,
metadata: [],
loading: true,
metadataType: '',
metadataMessage: '',
secondNumericMetadatumId: [Number, String],
secondNumericMetadatumName: String,
firstComparator: String,
secondComparator: String,
comparatorsObject: {},
acceptNumericInterval: String
}
},
watch: {
errors() {
if ( this.errors && this.errors.secondary_filter_metadatum_id !== '' )
this.setErrorsAttributes( 'is-danger', this.errors.secondary_filter_metadatum_id );
else
this.setErrorsAttributes( '', '' );
}
},
created() {
this.step = this.modelValue && this.modelValue.step ? this.modelValue.step : 1;
this.secondNumericMetadatumId = this.modelValue && this.modelValue.secondary_filter_metadatum_id ? this.modelValue.secondary_filter_metadatum_id : '';
this.secondNumericMetadatumName = this.modelValue && this.modelValue.secondary_filter_metadatum_name ? this.modelValue.secondary_filter_metadatum_name : '';
this.firstComparator = this.modelValue && this.modelValue.first_comparator ? this.modelValue.first_comparator : '>=';
this.secondComparator = this.modelValue && this.modelValue.second_comparator ? this.modelValue.second_comparator : '<=';
this.acceptNumericInterval = this.modelValue && this.modelValue.accept_numeric_interval ? this.modelValue.accept_numeric_interval : 'no';
this.loading = true;
this.fetchMetadata();
this.comparatorsObject = {
'=': {
symbol: '&#61;',
label: this.$i18n.get('is_equal_to')
},
'!=': {
symbol: '&#8800;',
label: this.$i18n.get('is_not_equal_to')
},
'>': {
symbol: '&#62;',
label: this.$i18n.get('greater_than')
},
'>=': {
symbol: '&#8805;',
label: this.$i18n.get('greater_than_or_equal_to')
},
'<': {
symbol: '&#60;',
label: this.$i18n.get('less_than')
},
'<=': {
symbol: '&#8804;',
label: this.$i18n.get('less_than_or_equal_to')
}
};
},
methods: {
async fetchMetadata() {
let endpoint = this.filter.collection_id && this.filter.collection_id !== 'default' ? ( '/collection/' + this.filter.collection_id + '/metadata' ) : '/metadata';
endpoint += '?metaquery[0][key]=metadata_type&metaquery[0][value]=Tainacan\\Metadata_Types\\Numeric&nopaging=1&exclude=' + this.filter.metadatum_id;
return await tainacanApi.get(endpoint)
.then(res => {
this.loading = false;
this.metadata = res.data ? res.data : [];
})
.catch(error => {
this.loading = false;
this.$console.log(error);
});
},
onUpdateSecondNumericMetadatumId() {
const selectedMetadatum = this.metadata.find( aMetadatum => aMetadatum.id == this.secondNumericMetadatumId );
this.secondNumericMetadatumName = selectedMetadatum ? selectedMetadatum.name : '';
this.secondNumericMetadatumId = selectedMetadatum ? selectedMetadatum.id : '';
this.emitValues();
},
emitValues() {
this.$emit('update:model-value', {
step: this.step,
first_comparator: this.firstComparator,
second_comparator: this.secondComparator,
secondary_filter_metadatum_id: this.secondNumericMetadatumId,
secondary_filter_metadatum_name: this.secondNumericMetadatumName,
accept_numeric_interval: this.acceptNumericInterval
});
},
setErrorsAttributes( type, message ) {
this.metadataType = type;
this.metadataMessage = message;
},
clear(){
this.metadataType = '';
this.metadataMessage = '';
},
}
}
</script>

View File

@ -0,0 +1,139 @@
<template>
<div>
<b-numberinput
v-model="valueInit"
:aria-labelledby="'filter-label-id-' + filter.id"
:aria-minus-label="$i18n.get('label_decrease')"
:aria-plus-label="$i18n.get('label_increase')"
size="is-small"
:step="filterTypeOptions.step"
@update:model-value="($event) => { resetPage(); validadeValues($event) }"
/>
<p
v-if="filterTypeOptions.accept_numeric_interval === 'yes'"
style="font-size: 0.75em; margin-bottom: 0.125em;"
class="has-text-centered is-marginless">
{{ $i18n.get('label_until') }}
</p>
<b-numberinput
v-if="filterTypeOptions.accept_numeric_interval === 'yes'"
v-model="valueEnd"
:aria-labelledby="'filter-label-id-' + filter.id"
:aria-minus-label="$i18n.get('label_decrease')"
:aria-plus-label="$i18n.get('label_increase')"
size="is-small"
:step="filterTypeOptions.step"
@update:model-value="($event) => { resetPage(); validadeValues($event) }" />
</div>
</template>
<script>
import { filterTypeMixin } from '../../../js/filter-types-mixin';
export default {
mixins: [ filterTypeMixin ],
emits: [
'input',
],
data(){
return {
valueInit: null,
valueEnd: null
}
},
watch: {
'query': {
handler() {
this.updateSelectedValues();
},
deep: true
}
},
mounted() {
this.updateSelectedValues();
},
methods: {
// only validate if the first value is higher than first
validadeValues: _.debounce( function () {
if ( this.filterTypeOptions.accept_numeric_interval !== 'yes' )
this.valueEnd = this.valueInit;
if (this.valueInit == null || this.valueEnd == null )
return
if (this.valueInit.constructor == Number)
this.valueInit = this.valueInit.valueOf();
if (this.valueEnd.constructor == Number)
this.valueEnd = this.valueEnd.valueOf();
this.valueInit = parseFloat(this.valueInit);
this.valueEnd = parseFloat(this.valueEnd);
if ( isNaN(this.valueInit) || isNaN(this.valueEnd) )
return
if ( this.filterTypeOptions.accept_numeric_interval === 'yes' && this.valueInit > this.valueEnd ) {
this.showErrorMessage();
return;
}
this.emit();
}, 600),
// message for error
showErrorMessage(){
this.$buefy.toast.open({
duration: 3000,
message: this.$i18n.get('info_error_first_value_greater'),
position: 'is-bottom',
type: 'is-danger'
})
},
// emit the operation for listeners
emit() {
let values = [ this.valueInit, this.valueEnd ];
let type = ! Number.isInteger( this.valueInit ) || ! Number.isInteger( this.valueEnd ) ? 'DECIMAL(20,3)' : 'NUMERIC';
this.$emit('input', {
filter: 'intersection',
type: type,
compare: this.filterTypeOptions.first_comparator,
metadatum_id: this.metadatumId,
collection_id: this.collectionId,
value: this.filterTypeOptions.accept_numeric_interval === 'yes' ? values : values[0]
});
this.$emit('input', {
filter: 'intersection',
type: type,
compare: this.filterTypeOptions.second_comparator,
metadatum_id: this.filterTypeOptions.secondary_filter_metadatum_id,
collection_id: this.collectionId,
value: this.filterTypeOptions.accept_numeric_interval === 'yes' ? values : values[0]
});
},
updateSelectedValues(){
if ( !this.query || !this.query.metaquery || !Array.isArray( this.query.metaquery ) )
return false;
let index = this.query.metaquery.findIndex(newMetadatum => newMetadatum.key == this.metadatumId );
if ( index >= 0 ) {
let metaquery = this.query.metaquery[ index ];
if ( metaquery.value && metaquery.value.length > 1 ) {
this.valueInit = new Number(metaquery.value[0]);
this.valueEnd = new Number(metaquery.value[1]);
}
} else {
this.valueInit = null;
this.valueEnd = null;
}
},
}
}
</script>
<style scoped>
.field {
margin-bottom: 0.125em !important;
}
</style>

View File

@ -0,0 +1,161 @@
<?php
namespace Tainacan\Filter_Types;
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
/**
* Class TainacanFilterType
*/
class Numerics_Intersection extends Filter_Type {
function __construct(){
$this->set_name( __('Numerics Intersection', 'tainacan') );
$this->set_supported_types(['float']);
$this->set_component('tainacan-filter-numerics-intersection');
$this->set_form_component('tainacan-filter-form-numerics-intersection');
$this->set_default_options([
'step' => 1,
'secondary_filter_metadatum_id' => '',
'secondary_filter_metadatum_name' => '',
'first_comparator' => '>=',
'second_comparator' => '<=',
'accept_numeric_interval' => 'no'
]);
$this->set_use_max_options(false);
$this->set_preview_template('
<div>
<div class="b-numberinput field is-grouped">
<p class="control">
<button type="button" class="button is-primary is-small">
<span class="icon is-small">
<i class="mdi mdi-minus"></i>
</span>
</button>
</p>
<div class="control is-small is-clearfix">
<input type="number" step="0.01" class="input is-small" value="6">
</div>
<p class="control">
<button type="button" class="button is-primary is-small">
<span class="icon is-small">
<i class="mdi mdi-plus"></i>
</span>
</button>
</p>
</div>
<p class="is-size-7 has-text-centered is-marginless">until</p>
<div class="b-numberinput field is-grouped">
<p class="control">
<button type="button" class="button is-primary is-small">
<span class="icon is-small">
<i class="mdi mdi-minus"></i>
</span>
</button>
</p>
<div class="control is-small is-clearfix">
<input type="number" step="0.01" class="input is-small" value="10">
</div>
<p class="control">
<button type="button" class="button is-primary is-small">
<span class="icon is-small">
<i class="mdi mdi-plus"></i>
</span>
</button>
</p>
</div>
</div>
');
}
public function get_form_labels(){
return [
'step' => [
'title' => __( 'Step', 'tainacan' ),
'description' => __( 'The amount to be increased or decreased when clicking on the filter control buttons. This also defines whether the input accepts decimal numbers.', 'tainacan' ),
],
'secondary_filter_metadatum_id' => [
'title' => __( 'Second numeric metadatum', 'tainacan' ),
'description' => __( 'The other metadatum to which this filter will compare values to find if there is an intersection of numeric values.', 'tainacan' ),
],
'secondary_filter_metadatum_name' => [
'title' => __( 'Second numeric metadatum', 'tainacan' ),
'description' => __( 'Label of the other metadatum to which this filter will compare values to find if there is an intersection of numeric values.', 'tainacan' ),
],
'first_comparator' => [
'title' => __( 'First comparator', 'tainacan' ),
'description' => __( 'Comparator to be used for checking the first metadata value.', 'tainacan' ),
],
'second_comparator' => [
'title' => __( 'Second comparator', 'tainacan' ),
'description' => __( 'Comparator to be used for checking the second metadata value.', 'tainacan' ),
],
'accept_numeric_interval' => [
'title' => __( 'Accept numeric interval', 'tainacan' ),
'description' => __( 'If checked, the filter will accept numeric intervals as values.', 'tainacan' ),
]
];
}
/**
* @param \Tainacan\Entities\Filter $filter
* @return array|bool true if is validate or array if has error
*/
public function validate_options(\Tainacan\Entities\Filter $filter) {
if ( !in_array($filter->get_status(), apply_filters('tainacan-status-require-validation', ['publish','future','private'])) )
return true;
$errors = [];
if ( empty($this->get_option('secondary_filter_metadatum_id')) )
$errors['secondary_filter_metadatum_id'] = __('The secondary numeric metadatum is required.','tainacan');
if ( empty($this->get_option('first_comparator')) )
$errors['first_comparator'] = __('The first comparator is required.','tainacan');
if ( empty($this->get_option('second_comparator')) )
$errors['second_comparator'] = __('The second comparator is required.','tainacan');
if ( empty($this->get_option('accept_numeric_interval')) )
$errors['accept_numeric_interval'] = __('The filter should define if it accepts a numeric interval.','tainacan');
return count($errors) > 0 ? $errors : true;
}
}
class Numerics_Intersection_Interval_Helper {
use \Tainacan\Traits\Singleton_Instance;
protected function init() {
add_filter( 'tainacan-api-items-tainacan-filter-numerics-intersection-filter-arguments', [$this, 'format_filter_arguments']);
}
function format_filter_arguments( $filter_arguments ) {
if (
!isset($filter_arguments['compare']) ||
!isset($filter_arguments['label'])
) {
return $filter_arguments;
}
if (
is_array($filter_arguments['label']) &&
count($filter_arguments['label']) === 2
) {
$filter_arguments['label'] = $filter_arguments['label'][0] . ' - ' . $filter_arguments['label'][1];
}
if (
isset( $filter_arguments['filter'] ) &&
isset( $filter_arguments['filter']['filter_type_options'] ) &&
isset( $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'] ) &&
!empty( $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'] )
) {
$filter_arguments['filter']['name'] = $filter_arguments['filter']['name'] . ' - ' . $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'];
}
return $filter_arguments;
}
}
Numerics_Intersection_Interval_Helper::get_instance();

View File

@ -102,7 +102,8 @@
TainacanFilterDateInterval: defineAsyncComponent(() => import('./date-interval/TainacanFilterDateInterval.vue')),
TainacanFilterDatesIntersection: defineAsyncComponent(() => import('./dates-intersection/TainacanFilterDatesIntersection.vue')),
TainacanFilterNumericInterval: defineAsyncComponent(() => import('./numeric-interval/TainacanFilterNumericInterval.vue')),
TainacanFilterNumericListInterval: defineAsyncComponent(() => import('./numeric-list-interval/TainacanFilterNumericListInterval.vue'))
TainacanFilterNumericListInterval: defineAsyncComponent(() => import('./numeric-list-interval/TainacanFilterNumericListInterval.vue')),
TainacanFilterNumericsIntersection: defineAsyncComponent(() => import('./numerics-intersection/TainacanFilterNumericsIntersection.vue'))
},
props: {
filter: Object,

View File

@ -789,6 +789,7 @@ return apply_filters( 'tainacan-i18n', [
'instruction_select_geocoordinate_metadatum' => __( 'Select a geocoordinate metadatum', 'tainacan' ),
'instruction_multiple_terms_insertion' => __( 'Type or paste here a list of names using a separator to create multiple terms at once.', 'tainacan' ),
'instruction_select_second_date_to_compare' => __( 'Select the second date metadatum', 'tainacan' ),
'instruction_select_second_numeric_to_compare' => __( 'Select the second numeric metadatum', 'tainacan' ),
// Info. Other feedback to user.
'info_items_tab_all' => __( 'Every item, except by those sent to trash.', 'tainacan' ),