Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/build/js/global/add-listing.js

Large diffs are not rendered by default.

167 changes: 156 additions & 11 deletions assets/src/js/global/add-listing.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,45 +316,190 @@ $(function () {
/**
* Price field.
*/
function getPriceTypeInput(typeId) {
return $(`#${$(`[for="${typeId}"]`).data('option')}`);
function getPriceTypeInput(typeId, $pricingField) {
const $label = $pricingField
? $pricingField.find(`[for="${typeId}"]`)
: $(`[for="${typeId}"]`);
const option = $label.data('option');
return $pricingField ? $pricingField.find(`#${option}`) : $(`#${option}`);
}

function isPricingControlEmpty($control) {
const value = String($control.val() || '').trim();

if (!value) {
return true;
}

if ($control.attr('id') !== 'price') {
return false;
}

const numericValue = Number(value);
return !Number.isFinite(numericValue) || numericValue === 0;
}

function updatePricingControlValidity($control) {
const control = $control.get(0);

if (!control || typeof control.setCustomValidity !== 'function') {
return;
}

if (!$control.prop('required')) {
control.setCustomValidity('');
return;
}

control.setCustomValidity(
isPricingControlEmpty($control)
? $control.data('required-message') || ''
: ''
);
}

function setPricingControlRequirement($control, required) {
$control.prop('required', required);

if (required) {
$control.attr('required', 'required');
} else {
$control.removeAttr('required');
}

updatePricingControlValidity($control);
}

function updatePricingChoiceValidity($pricingField) {
const isRequired = $pricingField.data('required') === 1;
const $pricingOptions = $pricingField.find(
'.directorist-form-pricing-field__options input'
);
const $firstOption = $pricingOptions.first();
const firstOption = $firstOption.get(0);

$pricingOptions.prop('required', false).removeAttr('required');
$pricingOptions.each(function () {
if (typeof this.setCustomValidity === 'function') {
this.setCustomValidity('');
}
});

if (!firstOption || !isRequired || $pricingOptions.is(':checked')) {
return;
}

$firstOption.prop('required', true).attr('required', 'required');

if (typeof firstOption.setCustomValidity === 'function') {
firstOption.setCustomValidity(
$pricingField
.find('.directorist-form-pricing-field__options')
.data('required-message') || ''
);
}
}

function updatePricingFieldRequirement($pricingField) {
if (!$pricingField.hasClass('price-type-both')) {
return;
}

const isRequired = $pricingField.data('required') === 1;
const $pricingControls = $pricingField.find('#price, #price_range');
const $selectedPriceType = $pricingField.find(
'.directorist-form-pricing-field__options input:checked'
);

$pricingControls.each(function () {
setPricingControlRequirement($(this), false);
});

updatePricingChoiceValidity($pricingField);

if (!isRequired || !$selectedPriceType.length) {
return;
}

getPriceTypeInput($selectedPriceType.attr('id'), $pricingField).each(
function () {
setPricingControlRequirement($(this), true);
}
);
}

$('body').on(
'input change invalid',
'.directorist-form-pricing-field #price, .directorist-form-pricing-field #price_range',
function () {
updatePricingControlValidity($(this));
}
);

$('body').on(
'change invalid',
'.directorist-form-pricing-field__options input',
function () {
updatePricingFieldRequirement(
$(this).closest('.directorist-form-pricing-field')
);
}
);

$('.directorist-form-pricing-field__options').on(
'change',
'input',
function () {
const $pricingField = $(this).closest(
'.directorist-form-pricing-field'
);
const $otherOptions = $(this)
.parent()
.siblings('.directorist-checkbox')
.find('input');

$otherOptions.prop('checked', false);
getPriceTypeInput($otherOptions.attr('id')).hide();
getPriceTypeInput($otherOptions.attr('id'), $pricingField).hide();

if (this.checked) {
getPriceTypeInput(this.id).show();
getPriceTypeInput(this.id, $pricingField).show();
} else {
getPriceTypeInput(this.id).hide();
getPriceTypeInput(this.id, $pricingField).hide();
}

updatePricingFieldRequirement($pricingField);
}
);

if ($('.directorist-form-pricing-field').hasClass('price-type-both')) {
$('#price_range, #price').hide();
$('.directorist-form-pricing-field.price-type-both').each(function () {
const $pricingField = $(this);

$pricingField.find('#price_range, #price').hide();

const $selectedPriceType = $(
const $selectedPriceType = $pricingField.find(
'.directorist-form-pricing-field__options input:checked'
);

if ($selectedPriceType.length) {
getPriceTypeInput($selectedPriceType.attr('id')).show();
getPriceTypeInput($selectedPriceType.attr('id'), $pricingField).show();
} else {
$($('.directorist-form-pricing-field__options input').get(0))
$(
$pricingField
.find('.directorist-form-pricing-field__options input')
.get(0)
)
.prop('checked', true)
.trigger('change');
}
}

updatePricingFieldRequirement($pricingField);
});

$('.directorist-form-pricing-field #price, .directorist-form-pricing-field #price_range').each(
function () {
updatePricingControlValidity($(this));
}
);

const has_tagline = $('#has_tagline').val();
const has_excerpt = $('#has_excerpt').val();
Expand Down
57 changes: 53 additions & 4 deletions includes/fields/class-directorist-pricing-field.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
class Pricing_Field extends Base_Field {
public $type = 'pricing';

public function __construct( array $props = [] ) {
if ( empty( $props['label'] ) ) {
$props['label'] = __( 'Pricing', 'directorist' );
}

parent::__construct( $props );
}

public function get_value( $posted_data ) {
if ( $this->get_price_type_prop() !== 'both' ) {
$posted_data['atbd_listing_pricing'] = $this->get_price_type_prop();
Expand All @@ -21,15 +29,34 @@ public function get_value( $posted_data ) {
return [];
}

$posted_price_type = $posted_data['atbd_listing_pricing'] ?? '';
$posted_price = $posted_data['price'] ?? 0;
$posted_price_range = $posted_data['price_range'] ?? '';

return [
'price_type' => sanitize_text_field( directorist_get_var( $posted_data['atbd_listing_pricing'] ) ),
'price' => round( (float) directorist_get_var( $posted_data['price'], 0 ), 2 ),
'price_range' => sanitize_text_field( directorist_get_var( $posted_data['price_range'] ) )
'price_type' => sanitize_text_field( directorist_get_var( $posted_price_type ) ),
'price' => round( (float) directorist_get_var( $posted_price, 0 ), 2 ),
'price_range' => sanitize_text_field( directorist_get_var( $posted_price_range ) )
];
}

public function validate( $posted_data ) {
$value = $this->get_value( $posted_data );
$value = wp_parse_args(
$this->get_value( $posted_data ),
[
'price_type' => '',
'price' => 0,
'price_range' => ''
]
);

if ( $this->is_required() ) {
$required_message = $this->get_required_error_message( $value );

if ( ! empty( $required_message ) ) {
$this->add_error( $required_message );
}
}

if ( ! empty( $value['price_type'] ) && ! in_array( $value['price_type'], $this->get_price_types(), true ) ) {
/* translators: %s: Price type value */
Expand All @@ -47,6 +74,28 @@ public function validate( $posted_data ) {
return true;
}

protected function get_required_error_message( $value ) {
if ( empty( $value['price_type'] ) ) {
return ( $this->get_price_type_prop() === 'both' )
? __( 'Please choose a pricing option.', 'directorist' )
: __( 'Price is required.', 'directorist' );
}

if ( $value['price_type'] === 'range' && empty( $value['price_range'] ) ) {
return __( 'Price range is required.', 'directorist' );
}

if ( $value['price_type'] === 'price' && $this->is_price_empty( $value['price'] ) ) {
return __( 'Price is required.', 'directorist' );
}

return '';
}

protected function is_price_empty( $price ) {
return (float) $price === 0.0;
}

protected function get_price_types() {
return [ 'price', 'range' ];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,11 @@ function directorist_get_conditional_logic_field( array $args = [] ) {
],
'value' => 'Price of this listing. Eg. 100',
],
'required' => [
'type' => 'toggle',
'label' => __( 'Required', 'directorist' ),
'value' => false,
],
'only_for_admin' => [
'type' => 'toggle',
'label' => __( 'Admin Only', 'directorist' ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ public function get_submission_form_fields_data( array $args = [] ) {
"field_key" => "pricing",
"pricing_type" => "both",
"label" => get_directorist_option( 'pricing_label', 'Pricing' ),
"required" => false,
"price_range_label" => get_directorist_option( 'price_range_label', 'Select Price Range' ),
"price_range_options" => "cheap",
"price_unit_field_type" => "number",
Expand Down Expand Up @@ -1877,4 +1878,4 @@ public function decode_custom_field_option_string( string $string = '' ) {

return $options;
}
}
}
20 changes: 11 additions & 9 deletions templates/listing-form/fields/pricing.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@
$price = get_post_meta( $listing_id, '_price', true );
$price_range = get_post_meta( $listing_id, '_price_range', true );
$price_type = get_post_meta( $listing_id, '_atbd_listing_pricing', true );
$pricing_type = ! empty( $data['pricing_type'] ) ? $data['pricing_type'] : 'both';
$is_required = ! empty( $data['required'] );
$allow_decimal = get_directorist_option( 'allow_decimal', 1 );
$currency_symbol = atbdp_currency_symbol( directorist_get_currency() );

// Get conditional logic attributes using centralized method
$conditional_logic_attr = $listing_form->get_conditional_logic_attributes( $data );
?>
<div class="directorist-form-group directorist-form-pricing-field price-type-<?php echo esc_attr( $data['pricing_type'] ); ?>"<?php echo $conditional_logic_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Already escaped in get_conditional_logic_attributes() ?>>
<div class="directorist-form-group directorist-form-pricing-field price-type-<?php echo esc_attr( $pricing_type ); ?>"<?php echo $is_required ? ' data-required="1"' : ''; ?><?php echo $conditional_logic_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Already escaped in get_conditional_logic_attributes() ?>>
<?php $listing_form->field_label_template( $data ); ?>

<?php if ( $data['pricing_type'] === 'both' ) { ?>
<div class="directorist-form-pricing-field__options">
<?php if ( $pricing_type === 'both' ) { ?>
<div class="directorist-form-pricing-field__options" data-required-message="<?php esc_attr_e( 'Please choose a pricing option.', 'directorist' ); ?>">
<div class="directorist-checkbox directorist_pricing_options">
<input type="checkbox" id="price_selected" value="price" name="atbd_listing_pricing" <?php checked( $price_type, 'price' ); ?>>
<label for="price_selected" class="directorist-checkbox__label" data-option="price"><?php echo esc_html( $data['price_unit_field_label'] );?></label>
Expand All @@ -38,12 +40,12 @@
</div>
<?php } ?>

<?php if ( $data['pricing_type'] === 'both' || $data['pricing_type'] === 'price_unit' ) { ?>
<input class="directorist-form-element directory_field directory_pricing_field" id="price" type="<?php echo esc_attr( $data['price_unit_field_type'] ); ?>" name="price" step="<?php echo esc_attr( $allow_decimal ? 'any' : 1 ); ?>" value="<?php echo esc_attr( $price ); ?>" placeholder="<?php echo esc_attr( ! empty( $data['price_unit_field_placeholder'] ) ? $data['price_unit_field_placeholder'] : '' ); ?>" />
<?php if ( $pricing_type === 'both' || $pricing_type === 'price_unit' ) { ?>
<input class="directorist-form-element directory_field directory_pricing_field" id="price" type="<?php echo esc_attr( $data['price_unit_field_type'] ); ?>" name="price" step="<?php echo esc_attr( $allow_decimal ? 'any' : 1 ); ?>" value="<?php echo esc_attr( $price ); ?>" placeholder="<?php echo esc_attr( ! empty( $data['price_unit_field_placeholder'] ) ? $data['price_unit_field_placeholder'] : '' ); ?>" data-required-message="<?php esc_attr_e( 'Price is required.', 'directorist' ); ?>" <?php echo $is_required && $pricing_type === 'price_unit' ? 'required="required"' : ''; ?> />
<?php } ?>

<?php if ( $data['pricing_type'] === 'both' || $data['pricing_type'] === 'price_range' ) { ?>
<select class="directorist-form-element directory_field directory_pricing_field" id="price_range" name="price_range">
<?php if ( $pricing_type === 'both' || $pricing_type === 'price_range' ) { ?>
<select class="directorist-form-element directory_field directory_pricing_field" id="price_range" name="price_range" data-required-message="<?php esc_attr_e( 'Price range is required.', 'directorist' ); ?>" <?php echo $is_required && $pricing_type === 'price_range' ? 'required="required"' : ''; ?>>
<option value=""><?php echo esc_html( ! empty( $data['price_range_placeholder'] ) ? $data['price_range_placeholder'] : '' ); ?></option>
<option value="skimming"<?php selected( $price_range, 'skimming' ); ?>><?php echo esc_html( sprintf( __( 'Ultra High (%s)', 'directorist' ), str_repeat( $currency_symbol, 4 ) ) );?></option>
<option value="moderate" <?php selected( $price_range, 'moderate' ); ?>><?php echo esc_html( sprintf( __( 'Moderate (%s)', 'directorist' ), str_repeat( $currency_symbol, 3 ) ) );?></option>
Expand All @@ -52,11 +54,11 @@
</select>
<?php } ?>

<?php if ( $data['pricing_type'] === 'price_unit' ) :?>
<?php if ( $pricing_type === 'price_unit' ) :?>
<input type="hidden" name="atbd_listing_pricing" value="price">
<?php endif; ?>

<?php if ( $data['pricing_type'] === 'price_range' ) :?>
<?php if ( $pricing_type === 'price_range' ) :?>
<input type="hidden" name="atbd_listing_pricing" value="range">
<?php endif; ?>
</div>
Loading