diff --git a/classes/fields.class.php b/classes/fields.class.php index f1d2111..92a3a82 100644 --- a/classes/fields.class.php +++ b/classes/fields.class.php @@ -788,7 +788,7 @@ function render_all_input_types( $name, $data, $fields_type, $field_index, $valu $html_input .= ''; $html_input .= ''; - $html_input .= '
'; + $html_input .= '
'; $html_input .= '

' . __( 'only if', 'woocommerce-product-addon' ) . '

'; $html_input .= '
'; @@ -807,8 +807,18 @@ function render_all_input_types( $name, $data, $fields_type, $field_index, $valu $html_input .= '
'; foreach ( $condition_rules as $rule_index => $condition ) { - $element_values = isset( $condition['element_values'] ) ? stripslashes( $condition['element_values'] ) : ''; - $element = isset( $condition['elements'] ) ? stripslashes( $condition['elements'] ) : ''; + $element_values = isset( $condition['element_values'] ) ? stripslashes( $condition['element_values'] ) : ''; + $element = isset( $condition['elements'] ) ? stripslashes( $condition['elements'] ) : ''; + $element_constant_value = isset( $condition['element_constant'] ) ? stripslashes( $condition['element_constant'] ) : ''; + + $element_between_value_to = ''; + $element_between_value_from = ''; + + if ( isset( $condition['cond-between-interval'] ) && is_array( $condition['cond-between-interval'] ) ) { + $element_between_value_to = isset( $condition['cond-between-interval']['to'] ) ? $condition['cond-between-interval']['to'] : ''; + $element_between_value_from = isset( $condition['cond-between-interval']['from'] ) ? $condition['cond-between-interval']['from'] : ''; + } + $operator_is = ( $condition['operators'] == 'is' ) ? 'selected="selected"' : ''; $operator_not = ( $condition['operators'] == 'not' ) ? 'selected="selected"' : ''; $operator_greater = ( $condition['operators'] == 'greater than' ) ? 'selected="selected"' : ''; @@ -826,22 +836,53 @@ function render_all_input_types( $name, $data, $fields_type, $field_index, $valu $html_input .= '
'; // is value meta - $html_input .= '
'; + $pro_enabled = ppom_pro_is_installed() && 'valid' === apply_filters( 'product_ppom_license_status', '' ); + + $html_input .= '
'; $html_input .= ' '; $html_input .= '
'; // conditional elements values - $html_input .= '
'; + $html_input .= '
'; $html_input .= ''; + $html_input .= ''; + + $html_input .= '
'; + $html_input .= ''; + $html_input .= '' . __( 'and', 'woocommerce-product-addon' ) . ''; + $html_input .= ''; + $html_input .= '
'; + + // Upsell + $html_input .= ' ' . __('Upgrade to Unlock', 'woocommerce-product-addon') . ''; // $html_input .= ''; $html_input .= '
'; @@ -891,12 +932,33 @@ function render_all_input_types( $name, $data, $fields_type, $field_index, $valu $html_input .= '
'; // is + $pro_enabled = ppom_pro_is_installed() && 'valid' === apply_filters( 'product_ppom_license_status', '' ); + $html_input .= '
'; $html_input .= ' '; $html_input .= '
'; diff --git a/css/ppom-admin.css b/css/ppom-admin.css index f31af64..899cfa3 100644 --- a/css/ppom-admin.css +++ b/css/ppom-admin.css @@ -144,6 +144,7 @@ ul.ppom-options-container li { .ppom-condition-style-wrap p { font-size: 15px; + margin: 0px; } /*.ppom-slider select.form-control {*/ @@ -769,6 +770,11 @@ select { display: block; } +.row.ppom-condition-style-wrap { + display: flex; + align-items: center; +} + @media (min-width: 992px) { .col-md-6 { width: 50% !important; @@ -1796,4 +1802,30 @@ header.ppom-modal-header { .ppom-attach-container .ppom-settings-container { margin: 10px 0; -} \ No newline at end of file +} +.ppom-hide-element { + display: none !important; +} + +.ppom-invisible-element { + visibility: hidden; +} + +.ppom-between-input-container { + display: flex; + flex-direction: row; + align-items: center; + + gap: 10px; +} + +.ppom-between-input-container span { + font-size: 16px; +} + +.ppom-upsell-condition { + font-size: 16px; + padding: 9px; + display: flex; + align-items: center; +} diff --git a/inc/functions.php b/inc/functions.php index b65ec18..5faa79f 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -2349,10 +2349,30 @@ function ppom_get_conditional_data_attributes( $meta ) { $bound = isset( $conditions['bound'] ) ? ppom_wpml_translate( $conditions['bound'], 'PPOM' ) : ''; $visibility = isset( $conditions['visibility'] ) ? ppom_wpml_translate( $conditions['visibility'], 'PPOM' ) : ''; - + $conditions['rules'] = array_filter( $conditions['rules'], function( $rule ) { + if ( in_array( $rule['operators'], array( 'any', 'empty', 'even-number', 'odd-number', 'regex') ) ) { + return true; + } + + if ( in_array( $rule['operators'], array( 'number-multiplier', 'regex', 'contains', 'not-contains') ) ) { + return ! empty( $rule['element_constant'] ); + } + + if ( in_array( $rule['operators'], array( 'greater than', 'less than', 'is', 'not' ) ) ) { + return ! empty( $rule['element_constant'] ) || ! empty( $rule['element_values'] ); + } + + if ( 'between' === $rule['operators'] ) { + return ( + ! empty( $rule['cond-between-interval']) && is_array( $rule['cond-between-interval'] ) + && isset( $rule['cond-between-interval']['to'] ) + && isset( $rule['cond-between-interval']['from'] ) + ); + } + return ! empty( $rule['element_values'] ); } ); @@ -2362,19 +2382,32 @@ function( $rule ) { $attr_html .= ' data-cond-bind="' . esc_attr( $bound ) . '"'; $attr_html .= ' data-cond-visibility="' . esc_attr( strtolower( $visibility ) ) . '"'; - $index = 0; + $index = 0; + $pro_enabled = ppom_pro_is_installed() && 'valid' === apply_filters( 'product_ppom_license_status', '' ); + foreach ( $conditions['rules'] as $rule ) { - $counter = ++ $index; - $input = 'input' . $counter; - $value = 'val' . $counter; - $opr = 'operator' . $counter; - $element = isset( $rule['elements'] ) ? ppom_wpml_translate( $rule['elements'], 'PPOM' ) : ''; - $element_val = isset( $rule['element_values'] ) ? ppom_wpml_translate( $rule['element_values'], 'PPOM' ) : ''; - $operator = isset( $rule['operators'] ) ? ppom_wpml_translate( $rule['operators'], 'PPOM' ) : ''; - $attr_html .= ' data-cond-' . $input . '="' . esc_attr( $element ) . '"'; - $attr_html .= ' data-cond-' . $value . '="' . esc_attr( $element_val ) . '"'; - $attr_html .= ' data-cond-' . $opr . '="' . esc_attr( $operator ) . '"'; + $counter = ++ $index; + $input = 'input' . $counter; + $value = 'val' . $counter; + $opr = 'operator' . $counter; + $element = isset( $rule['elements'] ) ? ppom_wpml_translate( $rule['elements'], 'PPOM' ) : ''; + $element_val = isset( $rule['element_values'] ) ? ppom_wpml_translate( $rule['element_values'], 'PPOM' ) : ''; + $constant_val = isset( $rule['element_constant'] ) ? ppom_wpml_translate( $rule['element_constant'], 'PPOM' ) : ''; + $operator = isset( $rule['operators'] ) ? ppom_wpml_translate( $rule['operators'], 'PPOM' ) : ''; + + $attr_html .= ' data-cond-' . $input . '="' . esc_attr( $element ) . '"'; + $attr_html .= ' data-cond-' . $value . '="' . esc_attr( $element_val ) . '"'; + $attr_html .= ' data-cond-' . $opr . '="' . esc_attr( $operator ) . '"'; + + if ( $pro_enabled && ! empty( $constant_val ) ) { + $attr_html .= ' data-cond-constant-val-' . $counter . '="' . esc_attr( $constant_val ) . '"'; + } + + if ( $pro_enabled && 'between' === $operator && isset( $rule['cond-between-interval'] ) ) { + $attr_html .= ' data-cond-between-from-' . $counter . '="' . esc_attr( $rule['cond-between-interval']['from'] ) . '"'; + $attr_html .= ' data-cond-between-to-' . $counter . '="' . esc_attr( $rule['cond-between-interval']['to'] ) . '"'; + } } } diff --git a/js/admin/ppom-admin.js b/js/admin/ppom-admin.js index f4bec3a..300a3ba 100644 --- a/js/admin/ppom-admin.js +++ b/js/admin/ppom-admin.js @@ -1,4 +1,39 @@ +// @ts-check "use strict"; + +const FIELD_COMPATIBLE_WITH_SELECT_OPTIONS = [ 'select', 'radio', 'checkbox', 'switcher' ]; +const OPERATOR_COMPARISON_VALUE_FIELD_TYPE = { + 'select': FIELD_COMPATIBLE_WITH_SELECT_OPTIONS, +} +const COMPARISON_VALUE_CAN_USE_SELECT = [ 'is', 'not', 'greater than', 'less than' ]; +const HIDE_COMPARISON_INPUT_FIELD = ['any', 'empty', 'odd-number', 'even-number']; +const FIELDS_COMPATIBLE_WITH_TEXT = [ 'text', 'textarea', 'date', 'email' ] +const FIELDS_COMPATIBLE_WITH_NUMBERS = [ ...FIELD_COMPATIBLE_WITH_SELECT_OPTIONS, 'number' ]; +const OPERATORS_FIELD_COMPATIBILITY = { + 'is': [...FIELD_COMPATIBLE_WITH_SELECT_OPTIONS, ...FIELDS_COMPATIBLE_WITH_TEXT, ...FIELDS_COMPATIBLE_WITH_NUMBERS], + 'not': [...FIELD_COMPATIBLE_WITH_SELECT_OPTIONS, ...FIELDS_COMPATIBLE_WITH_TEXT, ...FIELDS_COMPATIBLE_WITH_NUMBERS], + 'greater than': FIELDS_COMPATIBLE_WITH_NUMBERS, + 'less than': FIELDS_COMPATIBLE_WITH_NUMBERS, + 'even-number': FIELDS_COMPATIBLE_WITH_NUMBERS, + 'odd-number': FIELDS_COMPATIBLE_WITH_NUMBERS, + 'between': FIELDS_COMPATIBLE_WITH_NUMBERS, + 'number-multiplier': FIELDS_COMPATIBLE_WITH_NUMBERS, + 'any': [...FIELD_COMPATIBLE_WITH_SELECT_OPTIONS, ...FIELDS_COMPATIBLE_WITH_TEXT, ...FIELDS_COMPATIBLE_WITH_NUMBERS], + 'empty': [...FIELD_COMPATIBLE_WITH_SELECT_OPTIONS, ...FIELDS_COMPATIBLE_WITH_TEXT, ...FIELDS_COMPATIBLE_WITH_NUMBERS], + 'contains': [...FIELD_COMPATIBLE_WITH_SELECT_OPTIONS, ...FIELDS_COMPATIBLE_WITH_TEXT], + 'not-contains': [...FIELD_COMPATIBLE_WITH_SELECT_OPTIONS, ...FIELDS_COMPATIBLE_WITH_TEXT], + 'regex': [...FIELD_COMPATIBLE_WITH_SELECT_OPTIONS, ...FIELDS_COMPATIBLE_WITH_TEXT] +} + +const proOperatorOptionsToLock = new Set(); + +/** + * An array to store available condition targets. + * + * @type {Array<{fieldLabel?: string, fieldId?: string, fieldType?: string, canUse: boolean}>} + */ +const availableConditionTargets = []; + jQuery(function($) { var loader = new ImageLoader(ppom_vars.loader); @@ -433,6 +468,9 @@ jQuery(function($) { } ); clone_new_field.find('.ppom-field-checker').attr('data-field-index', field_no); clone_new_field.find('.ppom-field-checker').addClass('ppom-add-fields-js-action'); + + // NOTE: Hide Condition tab on new field modal because of wrong behavior. + clone_new_field.find('#condition_tab').addClass('ppom-hide-element'); // var color_picker_input = clone_new_field.find('.ppom-color-picker-init').clone(); // clone_new_field.find('.ppom-color-picker-cloner').html(color_picker_input); @@ -1187,56 +1225,212 @@ jQuery(function($) { }); } + /** + * Filter the operator options list based on the target type field. + * + * @param {string?} fieldType The PPOM field type. + * @param {HTMLSelectElement} operatorSelectField The select input for condition operator. + * @returns + */ + function toggleOperatorFieldByTargetType( fieldType, operatorSelectField ) { + if ( ! operatorSelectField ) { + return; + } + + let shouldHideSelectInput = true; + const currentValue = operatorSelectField?.value; + + operatorSelectField.querySelectorAll('optgroup').forEach( optgroup => { + let shouldHideGroup = true; + optgroup.querySelectorAll('option').forEach( option => { + const isAvailable = option?.value && OPERATORS_FIELD_COMPATIBILITY[option.value] ? OPERATORS_FIELD_COMPATIBILITY[option.value].includes(fieldType) : option?.value; + if ( shouldHideGroup && isAvailable ) { + shouldHideGroup = false; + } + + if ( option.value === currentValue && !isAvailable ) { + operatorSelectField.value = 'any'; // NOTE: Default to 'any' if the current select value is unavailable. + } + + option.classList.toggle('ppom-hide-element', !isAvailable ); + }); + + if ( ! shouldHideGroup ) { + shouldHideSelectInput = false; + } + + optgroup.classList.toggle( 'ppom-hide-element', shouldHideGroup ); + }); + + operatorSelectField.classList.toggle( 'ppom-invisible-element', shouldHideSelectInput ); + tryToggleConditionInputFields( operatorSelectField ); + } /** - 27- Get All Fields Title On Condition Element Value After Click On Condition Tab + 27- Refresh the condition comparison option list for PPOM field target that are of type select. **/ - // populate_conditional_elements(); + function updateTargetComparisonValueSelect( targetSelect, conditionContainer, initialSelectedValue ) { + /** @type {string?} */ + const targetElementNameToPullOptions = targetSelect.value; - $(document).on('change', 'select[data-metatype="elements"]', function(e) { - e.preventDefault(); - - var element_name = $(this).val(); - var div = $(this).closest('.ppom-slider'); + /** @type {HTMLDivElement?} */ + conditionContainer ??= targetSelect.closest('.webcontact-rules'); + const targetSelectOptions = conditionContainer?.querySelector('select[data-metatype="element_values"]'); - var selected_rule_box = $(this).closest('.webcontact-rules'); - var element_value_box = selected_rule_box.find('select[data-metatype="element_values"]'); + if ( !conditionContainer || !targetSelectOptions ) { + return; + } - $(".ppom-slider").each(function(i, item) { + document.querySelectorAll('.ppom-slider').forEach(sliderItem => { - var data_name = $(item).find('input[data-metatype="data_name"]').val(); + const targetElementFieldId = sliderItem.querySelector('input[data-metatype="data_name"]')?.value; + if ( targetElementFieldId !== targetElementNameToPullOptions ) { + return; + } - if (data_name == element_name) { + const operatorsInput = conditionContainer.querySelector('[data-metatype="operators"]'); + if ( ! operatorsInput ) { + return; + } + + // Reset the options lists based on the new selection. + const newOptions = []; + + sliderItem.querySelectorAll('.data-options').forEach(/** @type {HTMLDivElement} */conditionValueContainer => { + const condition_type = conditionValueContainer.getAttribute('data-condition-type'); + + const conditionValueId = conditionValueContainer + .querySelector( + condition_type === 'simple_options' + ? 'input[data-metatype="option"]' + : '.ppom-image-option-title' + )?.value?.trim(); + + if ( ! conditionValueId ) { + return; + } - // resetting - jQuery(element_value_box).html(''); + const optionElement = document.createElement('option'); + optionElement.value = ppom_escape_html(conditionValueId); + optionElement.textContent = conditionValueId; + + newOptions.push( optionElement ); + }); + targetSelectOptions.replaceChildren(...newOptions); + }); - $(item).find('.data-options').each(function(i, condition_val) { + if ( initialSelectedValue ) { + targetSelectOptions.value = initialSelectedValue; + } + } + /** + * Toggle the visibility for input fields type based on operator current value. + * + * @param {HTMLSelectElement?} conditionOperatorInput + * @returns + */ + function tryToggleConditionInputFields( conditionOperatorInput ) { + if ( ! conditionOperatorInput ) { + return; + } - var condition_type = $(condition_val).attr('data-condition-type'); - if (condition_type == 'simple_options') { - var con_val = $(condition_val).find('input[data-metatype="option"]').val(); - } - else if (condition_type == 'image_options') { - var con_val = $(condition_val).find('.ppom-image-option-title').val(); - } + const selectedOperator = conditionOperatorInput?.value; - if ($.trim(con_val) !== '') { + /** + * @type {HTMLDivElement|null} + */ + const container = conditionOperatorInput?.closest('.webcontact-rules'); + if ( !container) { + return; + } + /** + * @type {HTMLSelectElement|null} + */ + const conditionTargetSelectOptionsInput = container.querySelector( 'select[data-metatype="element_values"]' ); - var val_id = $.trim(con_val); + /** + * @type {HTMLInputElement|null} + */ + const conditionConstantInput = container.querySelector( '[data-metatype="element_constant"]' ); - var $html = ''; - $html += ''; + /** + * @type {HTMLSelectElement|null} + */ + const conditionTargetSelectInput = container.querySelector( '[data-metatype="elements"]' ); - $($html).appendTo(element_value_box); - } - }); + if ( !conditionConstantInput || !conditionTargetSelectOptionsInput || !conditionTargetSelectInput ) { + return; + } + + /** + * @type {HTMLDivElement|null} + */ + const betweenInputs = container.querySelector('.ppom-between-input-container'); + + let shouldHideSelectInput = false; + let shouldHideTextInput = false; + let shouldHideBetweenInputs = false; + let shouldHideUpsell = true; + + if ( proOperatorOptionsToLock.has( selectedOperator ) ) { + shouldHideSelectInput = true; + shouldHideTextInput = true; + shouldHideBetweenInputs = true; + shouldHideUpsell = false; + } + else if ( 'between' === selectedOperator ) { + shouldHideSelectInput = true; + shouldHideTextInput = true; + shouldHideBetweenInputs = false; + } + else if ( HIDE_COMPARISON_INPUT_FIELD.includes( selectedOperator ) ) { + shouldHideSelectInput = true; + shouldHideTextInput = true; + shouldHideBetweenInputs = true; + } else { + shouldHideSelectInput = true; + shouldHideBetweenInputs = true; + + /** + * @type {HTMLOptionElement|null} + */ + const targetFieldTypeInput = conditionTargetSelectInput.querySelector(`option[value="${conditionTargetSelectInput.value}"]`); + if ( + COMPARISON_VALUE_CAN_USE_SELECT.includes( selectedOperator ) && + targetFieldTypeInput?.dataset?.fieldtype && + OPERATOR_COMPARISON_VALUE_FIELD_TYPE['select'].includes( targetFieldTypeInput.dataset.fieldtype ) + ) { + shouldHideTextInput = true; + shouldHideSelectInput = false; } - }); + } + + if ( shouldHideSelectInput && shouldHideTextInput && shouldHideBetweenInputs && shouldHideUpsell ) { + conditionConstantInput.parentNode?.classList.add('ppom-invisible-element'); // NOTE: Make the entire container visible to preserve the space. + } else { + conditionTargetSelectOptionsInput.classList.toggle("ppom-hide-element", shouldHideSelectInput ); + conditionConstantInput.classList.toggle("ppom-hide-element", shouldHideTextInput ); + betweenInputs?.classList.toggle("ppom-hide-element", shouldHideBetweenInputs ); + container.querySelector('.ppom-upsell-condition')?.classList.toggle("ppom-hide-element", shouldHideUpsell); + + conditionConstantInput.parentNode?.classList.remove('ppom-invisible-element'); + } + } + + // Apply actions on initialization based on operator value. + document.querySelectorAll('select[data-metatype="operators"]').forEach( conditionOperatorInput => { + tryToggleConditionInputFields( conditionOperatorInput ); + }); + + // Apply actions when operator value changes. + document.addEventListener('change', function (e) { + if ( ! e.target.matches('select[data-metatype="operators"]') ) { + return; + } + + e.preventDefault(); + tryToggleConditionInputFields( e.target ); }); $(document).on('change', '[data-meta-id="conditions"] select[data-metatype="element_values"]', function(e) { @@ -1248,79 +1442,164 @@ jQuery(function($) { $(document).on('click', '.ppom-condition-tab-js', function(e) { e.preventDefault(); + populate_conditional_elements(); + }); - var div = $(this).closest('.ppom-slider'); - var elements = div.find('select[data-metatype="elements"]'); + /** + * Populate the condition target select with eligible options based on the operator. + * + * @param {HTMLSelectElement?} selectInput + * @param {string?} conditionOperator + * @param {string[]} excludeIds + * @returns + */ + function populate_condition_target( selectInput, conditionOperator, excludeIds = [] ) { + if ( !selectInput ) { + return; + } - elements.each(function(i, item) { + const newOptions = availableConditionTargets + .filter( ({ fieldId, canUse }) => canUse && !excludeIds.includes( fieldId) ) + .map( target => { + + const option = document.createElement('option'); + option.value = target.fieldId; + option.textContent = target.fieldLabel; + option.dataset.fieldtype = target.fieldType; + + return option; + }); - var conditional_elements = item.value; - var exiting_meta = $(item).attr('data-existingvalue', conditional_elements); - }); + selectInput.replaceChildren( ...newOptions ); + } - populate_conditional_elements(elements); + function findFieldTypeById( fieldId ) { + if ( !fieldId ) { + return undefined; + } - }); + for ( const target of availableConditionTargets ) { + if ( target.fieldId === fieldId ) { + return target.fieldType; + } + } - function populate_conditional_elements(elements) { + return undefined; + } - // resetting - jQuery('select[data-metatype="elements"]').html(''); + function can_use_field_type( fieldType ) { + if ( ! fieldType?.length ) { + return false; + } - jQuery(".ppom-slider").each(function(i, item) { + for ( const operatorCompatibleFields of Object.values( OPERATORS_FIELD_COMPATIBILITY ) ) { + if ( operatorCompatibleFields.includes( fieldType ) ) { + return true; + } + } - var conditional_elements = jQuery(item).find( - 'input[data-metatype="title"]').val(); - var conditional_elements_value = jQuery(item).find( - 'input[data-metatype="data_name"]').val(); + return false; + } - if ($.trim(conditional_elements_value) !== '') { + /** + * Populate the condition target select with eligible options based on the operator on initialization and value change. + * + */ + function populate_conditional_elements() { - var $html = ''; - $html += ''; + // Get all available PPOM fields. + availableConditionTargets.splice(0, availableConditionTargets.length); + document.querySelectorAll(".ppom-slider").forEach(item => { + const fieldLabel = item.querySelector('input[data-metatype="title"]')?.value; + const fieldId = item.querySelector('input[data-metatype="data_name"]')?.value?.trim(); + const fieldType = item.querySelector('input[data-metatype="type"]')?.value; + const canUse = can_use_field_type( fieldType ); - $($html).appendTo('select[data-metatype="elements"]'); + if ( !fieldLabel || !fieldId || !fieldType ) { + return; } + availableConditionTargets.push({ fieldLabel, fieldId, fieldType, canUse }); }); + + // Change the target options for all the rules. + document.querySelectorAll(".ppom-slider").forEach(item => { + if ( ! item.id ) { + return; + } + const conditionContainers = item.querySelector('div[data-meta-id="conditions"]')?.querySelectorAll('.webcontact-rules'); + + conditionContainers?.forEach(conditionContainer => { + const conditionTargetsSelect = conditionContainer.querySelector('[data-metatype="elements"]'); + if ( ! conditionTargetsSelect ) { + return; + } - // setting the existing conditional elements - $(".ppom-slider").each(function(i, item) { - - $(item).find('select[data-metatype="elements"]').each(function(i, condition_element) { + const conditionOperatorSelect = conditionContainer.querySelector('[data-metatype="operators"]'); + const fieldId = item.querySelector('input[data-metatype="data_name"]')?.value?.trim(); - var existing_value1 = $(condition_element).attr("data-existingvalue"); + populate_condition_target( conditionTargetsSelect, conditionOperatorSelect?.value, [fieldId] ); - if ($.trim(existing_value1) !== '') { - jQuery(condition_element).val(existing_value1); + if ( conditionTargetsSelect?.dataset?.existingvalue ) { + conditionTargetsSelect.value = conditionTargetsSelect?.dataset?.existingvalue; } + + // NOTE: Get all the locked operators. Unlock them to be eligible to show the upsell. + conditionOperatorSelect?.querySelectorAll( 'option' ).forEach( option => { + if ( ! option.disabled ) { + return; + } + proOperatorOptionsToLock.add( option.value ); + option.disabled = false; + }); + + toggleOperatorFieldByTargetType( findFieldTypeById( conditionTargetsSelect?.value ), conditionOperatorSelect ); + + const optionsInput = conditionContainer.querySelector('[data-metatype="element_values"]'); + + updateTargetComparisonValueSelect( + conditionTargetsSelect, + conditionContainer, + optionsInput?.dataset?.existingvalue + ); }); }); + } + /** + * Update the values of the operators selector and the comparison fields. + * + * NOTE: We are using a global listener since some node are dinamically created/cloned. + */ + document.addEventListener('change', function(e) { + if ( ! e.target.matches('select[data-metatype="elements"]') ) { + return; + } + e.preventDefault(); + const conditionContainer = e.target.closest('.webcontact-rules'); + const conditionOperatorSelect = conditionContainer?.querySelector('[data-metatype="operators"]'); + if ( ! conditionContainer || ! conditionOperatorSelect ) { + return; + } + + toggleOperatorFieldByTargetType( findFieldTypeById(e.target?.value), conditionOperatorSelect ); + updateTargetComparisonValueSelect( e.target, conditionContainer ); - // setting the existing conditional elements values - $(".ppom-slider").each(function(i, item) { - - $(item).find('select[data-metatype="element_values"]').each(function(i, condition_element) { - - var div = $(this).closest('.webcontact-rules'); - var existing_value1 = $(condition_element).attr("data-existingvalue"); - - div.find('select[data-metatype="elements"]').trigger('change'); - if ($.trim(existing_value1) !== '') { - jQuery(condition_element).val(existing_value1); - } - }); - }); - - } - + const optionsInput = conditionContainer.querySelector('[data-metatype="element_values"]'); + const constantInput = conditionContainer.querySelector('[data-metatype="element_constant"]'); + + // Reset values. + if ( constantInput ) { + constantInput.value = ''; + } + if ( optionsInput ) { + optionsInput.value = ''; + } + }); + /** 28- validate API WooCommerce Product **/ diff --git a/js/ppom-conditions-v2.js b/js/ppom-conditions-v2.js index 5e8296c..e3c5fec 100644 --- a/js/ppom-conditions-v2.js +++ b/js/ppom-conditions-v2.js @@ -1,3 +1,5 @@ +// @ts-check + /** * PPOM Conditional Version 2 * More Fast and Optimized @@ -48,38 +50,37 @@ jQuery(function($) { }, 100); // $('form.cart').on('change', 'select, input[type="radio"], input[type="checkbox"]', function(ev) { - - $(".ppom-wrapper").on('change', 'select,input:radio,input:checkbox', function(e) { - + + function trigger_check_conditions( modifiedElement ) { let value = null; - if (($(this).is(':radio') || $(this).is(':checkbox'))) { - value = this.checked ? $(this).val() : null; + if (modifiedElement.type === 'radio' || modifiedElement.type === 'checkbox') { + value = modifiedElement.checked ? modifiedElement.value : null; + } else { + value = modifiedElement.value; } - else { - - value = $(this).val(); - } - - const data_name = $(this).data('data_name'); - // console.log("Checking condition for ", data_name); - ppom_check_conditions(data_name, function(element_dataname, event_type) { - // console.log(`${element_dataname} ===> ${event_type}`); - $.event.trigger({ - type: event_type, - field: element_dataname, - time: new Date() + const data_name = modifiedElement.dataset?.data_name; + ppom_check_conditions(data_name, (element_dataname, event_type) => { + const event = new CustomEvent(event_type, { + detail: { + field: element_dataname, + time: new Date() + } }); + document.dispatchEvent(event); }); + } + + $(".ppom-wrapper").on('change', 'select, input:radio, input:checkbox, input[type="date"]', function(_e) { + trigger_check_conditions( this ); + }); + + $(".ppom-wrapper").on('keyup', 'input:text, input[type="number"], input[type="email"]', function(_e) { + trigger_check_conditions( this ); }); $(document).on('ppom_hidden_fields_updated', function(e) { ppom_fields_hidden_conditionally(); - - // $("#conditionally_hidden").val(ppom_hidden_fields); - // console.log(` hiddend field updated ==> ${e.field}`); - // $("#conditionally_hidden").val(ppom_hidden_fields); - // ppom_update_option_prices(); }); @@ -274,10 +275,8 @@ jQuery(function($) { function ppom_check_conditions(data_name, callback) { let is_matched = false; - const ppom_type = jQuery(`.ppom-input[data-data_name="${data_name}"]`).data('type'); - const is_file = jQuery("a.file.ppom-input").length > 0; let event_type, element_data_name; - + jQuery(`div.ppom-cond-${data_name}`).each(function() { // return this.data('cond-val1').match(/\w*-Back/); // console.log(jQuery(this)); @@ -290,21 +289,30 @@ function ppom_check_conditions(data_name, callback) { var matched_conditions = []; let cond_elements = []; for (var t = 1; t <= total_cond; t++) { + const targetFieldToCompare = jQuery(this).data(`cond-input${t}`)?.toString()?.toLowerCase() + const targetFieldValue = ppom_get_element_value(targetFieldToCompare); - const cond_element = jQuery(this).data(`cond-input${t}`)?.toString()?.toLowerCase(); - const cond_val = jQuery(this).data(`cond-val${t}`).toString(); + const selectOptionValue = jQuery(this).data(`cond-val${t}`)?.toString(); const operator = jQuery(this).data(`cond-operator${t}`); - const field_val = ppom_get_element_value(cond_element); - - // const field_val = ppom_get_field_type(field_obj); - // console.log(cond_element,field_val,cond_val); - // if (cond_element !== data_name) continue; - is_matched = ppom_compare_values(field_val, cond_val, operator); - // console.log(`${data_name} TRIGGERS :: ${t} ***** ${element_data_name} ==> field value ${field_val} || cond_valu ${cond_val} || operator ${operator} || Binding ${binding} is_matched=>${is_matched}`); + const constantValue = jQuery(this).data(`cond-constant-val-${t}`)?.toString(); + const betweenValueTo = jQuery(this).data(`cond-between-to-${t}`); + const betweenValueFrom = jQuery(this).data(`cond-between-from-${t}`); + + + is_matched = ppom_compare_values({ + valueToCompare: targetFieldValue, + selectOptionToCompare: selectOptionValue, + constantValueToCompare: constantValue, + betweenValueInterval: { + from: betweenValueFrom, + to: betweenValueTo + }, + operator + }); - if(is_matched) { + if ( is_matched ) { matched = ++matched; - cond_elements.push(cond_element); + cond_elements.push(targetFieldToCompare); } matched_conditions[element_data_name] = matched; @@ -325,9 +333,9 @@ function ppom_check_conditions(data_name, callback) { } }); - if ( typeof callback == "function" ) + if ( typeof callback == "function" ) { callback(element_data_name, event_type); - // return is_matched; + } } else if ( ! is_matched || matched_conditions[element_data_name] !== total_cond) { @@ -350,9 +358,6 @@ function ppom_check_conditions(data_name, callback) { callback(element_data_name, event_type); } } - - // return is_matched; - // return jQuery(this).data('cond-val1') === jQuery(this).val(); }); } @@ -408,26 +413,94 @@ function ppom_get_element_value(data_name) { return element_value; } -function ppom_compare_values(v1, v2, operator) { - +/** + * Compares values based on the provided operator. + * + * @param {Object} args - The arguments object containing comparison parameters. + * @param {string} args.valueToCompare - The target value to compare. + * @param {string} args.selectOptionToCompare - The select option value to compare. + * @param {string} args.constantValueToCompare - The constant value to compare. + * @param {{to: string, from: string}} args.betweenValueInterval - The between interval. + * @param {string} args.operator - The operator used for comparison. + * @returns {boolean} - The result of the comparison. + */ +function ppom_compare_values( args ) { + const { valueToCompare, selectOptionToCompare, constantValueToCompare, operator, betweenValueInterval } = args; let result = false; switch (operator) { case 'is': - if( Array.isArray(v1) ) { - result = jQuery.inArray(v2, v1) !== -1 ? true : false; - }else{ - result = v1 === v2 ? true : false; + if ( Array.isArray(valueToCompare) ) { + result = jQuery.inArray(selectOptionToCompare, valueToCompare) !== -1; + } else { + result = valueToCompare === selectOptionToCompare; + if ( !selectOptionToCompare && constantValueToCompare ) { + result = valueToCompare === constantValueToCompare + } } break; + case 'not': - result = v1 !== v2 ? true : false; + result = valueToCompare !== selectOptionToCompare; + if ( !selectOptionToCompare && constantValueToCompare ) { + result = valueToCompare !== constantValueToCompare + } break; case 'greater than': - result = parseFloat(v1) > parseFloat(v2) ? true : false; + result = parseFloat(valueToCompare) > parseFloat(selectOptionToCompare); + if ( !selectOptionToCompare && constantValueToCompare ) { + result = parseFloat(valueToCompare) > parseFloat(constantValueToCompare) + } break; + case 'less than': - result = parseFloat(v1) < parseFloat(v2) ? true : false; + result = parseFloat(valueToCompare) < parseFloat(selectOptionToCompare); + if ( !selectOptionToCompare && constantValueToCompare ) { + result = parseFloat(valueToCompare) < parseFloat(constantValueToCompare) + } + break; + + case 'any': + result = valueToCompare !== undefined && valueToCompare !== null && valueToCompare !== ''; + break; + + case 'empty': + result = valueToCompare === undefined || valueToCompare === null || valueToCompare === ''; + break; + + case 'between': + result = ( + parseFloat(valueToCompare) >= parseFloat( betweenValueInterval.from ) && + parseFloat(valueToCompare) <= parseFloat( betweenValueInterval.to ) + ); + break; + + case 'number-multiplier': + result = parseFloat(valueToCompare) % parseFloat(constantValueToCompare) === 0; + break; + + case 'even-number': + result = parseFloat(valueToCompare) % 2 === 0; + break; + + case 'odd-number': + result = parseFloat(valueToCompare) % 2 !== 0; + break; + + case 'contains': + result = valueToCompare?.includes(constantValueToCompare); + break; + + case 'not contains': + result = !valueToCompare?.includes(constantValueToCompare); + break; + + case 'regex': + if ( typeof constantValueToCompare === 'string' ) { + const [_, pattern, flags] = constantValueToCompare.split('/'); + const regex = new RegExp(pattern || constantValueToCompare, flags); + result = regex.test(valueToCompare); + } break; default: