Skip to content
Merged
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
37 changes: 37 additions & 0 deletions classes/controllers/FrmFieldsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@ private static function get_classes_for_builder_field( $field, $display, $field_
$li_classes = $field_info->form_builder_classes( $display['type'] );
$li_classes .= ' frm_form_field frmstart ';

$style_align_class = self::get_builder_field_style_align_class( $field, $field_info );

if ( $style_align_class ) {
$li_classes .= $style_align_class . ' ';
}

if ( isset( $field['classes'] ) ) {
$li_classes .= trim( $field['classes'] ) . ' ';
}
Expand All @@ -231,6 +237,37 @@ private static function get_classes_for_builder_field( $field, $display, $field_
return $li_classes;
}

/**
* Apply the active style alignment to a radio or checkbox builder preview on load.
*
* The per-field alignment setting only exists in Pro, so on load the style setting is
* used. When the field has an explicit alignment (Pro), the frm_build_field_class filter
* applies it instead. When Pro is not installed, any saved alignment is treated as empty.
*
* @since x.x
*
* @param array $field
* @param FrmFieldType $field_info
*
* @return string
*/
private static function get_builder_field_style_align_class( $field, $field_info ) {
if ( ! $field || ! is_array( $field ) || ( ! FrmField::is_radio( $field ) && ! FrmField::is_checkbox( $field ) ) ) {
return '';
}

$align = FrmAppHelper::pro_is_installed() ? FrmField::get_option( $field, 'align' ) : '';

if ( $align ) {
return '';
}

$align = FrmStylesHelper::get_align_from_active_style( $field );
$field_info->prepare_align_class( $align );

return $align;
}

public static function destroy() {
FrmAppHelper::permission_check( 'frm_edit_forms' );
check_ajax_referer( 'frm_ajax', 'nonce' );
Expand Down
16 changes: 16 additions & 0 deletions classes/helpers/FrmStylesHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,22 @@ private static function description_margin_for_screensize( $width ) {
return $change_margin;
}

/**
* Get the raw alignment value stored in the active style for a radio or checkbox field.
*
* @since x.x
*
* @param array|int $field The 'field' array.
*
* @return string
*/
public static function get_align_from_active_style( $field ) {
$field_type = FrmField::is_checkbox( $field ) ? 'checkbox' : 'radio';
$key = FrmStylesController::get_align_key_for_style_settings( $field_type );

return FrmStylesController::get_active_style( $field )->post_content[ $key ] ?? '';
}

/**
* @since 2.3
*
Expand Down
41 changes: 41 additions & 0 deletions classes/helpers/FrmStylesPreviewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,47 @@ public function adjust_form_for_preview() {
$this->hide_captcha_fields();
$this->disable_javascript_validation();
$this->add_a_div_class_for_default_label_positions();
$this->add_a_div_class_for_style_driven_alignment();
}

/**
* Add a marker class to radio and checkbox fields that use the style's option
* alignment setting rather than a field level override.
*
* Only fields with this class change when the radio or checkbox alignment style
* setting is updated in the styler preview. Fields with their own alignment
* override are left untouched.
*
* @since x.x
*
* @return void
*/
private function add_a_div_class_for_style_driven_alignment() {
add_filter(
'frm_field_div_classes',
/**
* @param string $classes
* @param array|object $field
*
* @return string
*/
function ( $classes, $field ) {
if ( ! FrmField::is_radio( $field ) && ! FrmField::is_checkbox( $field ) ) {
return $classes;
}

// Mirror FrmFieldType::get_container_class(): an override only exists in Pro.
$align = FrmAppHelper::pro_is_installed() ? FrmField::get_option( $field, 'align' ) : '';

if ( ! $align ) {
$classes .= ' frm-default-option-align';
}

return $classes;
},
10,
2
);
}

/**
Expand Down
19 changes: 1 addition & 18 deletions classes/models/fields/FrmFieldType.php
Original file line number Diff line number Diff line change
Expand Up @@ -1085,31 +1085,14 @@ public function get_container_class() {
$align = FrmAppHelper::pro_is_installed() ? FrmField::get_option( $this->field, 'align' ) : '';

if ( ! $align ) {
$align = $this->get_align_class_from_style_settings();
$align = FrmStylesHelper::get_align_from_active_style( $this->field );
}

$this->prepare_align_class( $align );

return $align ? ' ' . $align : '';
}

/**
* Get the alignment value from the active style settings.
*
* Used as the fallback when the field has no specific alignment option set.
*
* @since x.x
*
* @return string
*/
protected function get_align_class_from_style_settings() {
$field_type = FrmField::is_checkbox( $this->field ) ? 'checkbox' : 'radio';
$key = FrmStylesController::get_align_key_for_style_settings( $field_type );
$active_style = FrmStylesController::get_active_style( $this->field );

return $active_style->post_content[ $key ] ?? '';
}

/**
* @since 4.0
*
Expand Down
89 changes: 89 additions & 0 deletions js/admin/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,95 @@
document.getElementById( 'frm_style_sidebar' ).classList.add( 'wp-core-ui' );

jQuery( document ).on( 'input change', 'input[data-frmrange]', initSliderPreview );

initOptionLayoutPreview();
}

/**
* Sync the radio/checkbox option layout (alignment) style settings to the preview form in real time.
*
* The layout is applied to the front end through a container class, so changing the style setting
* does not re-render the preview form. This swaps the container class on preview fields that use the
* style setting. The styler preview marks those fields with .frm-default-option-align, so fields with
* their own alignment override are left untouched.
*
* @return {void}
*/
function initOptionLayoutPreview() {
bindOptionLayoutSelect( document.getElementById( 'frm_radio_align' ), '.frm_radio' );
bindOptionLayoutSelect( document.getElementById( 'frm_check_align' ), '.frm_checkbox' );
}

/**
* Listen for changes on an option layout style setting and update the preview to match.
*
* @param {HTMLSelectElement|null} select The style setting dropdown.
* @param {string} optionSelector The single option selector ('.frm_radio' or '.frm_checkbox').
* @return {void}
*/
function bindOptionLayoutSelect( select, optionSelector ) {
if ( ! select ) {
return;
}

select.addEventListener( 'change', () => {
updatePreviewOptionLayout( optionSelector, select.value );
} );
}

/**
* Replace the alignment container class on preview fields that are using the style setting.
*
* @param {string} optionSelector The single option selector ('.frm_radio' or '.frm_checkbox').
* @param {string} newAlign The newly selected style alignment value.
* @return {void}
*/
function updatePreviewOptionLayout( optionSelector, newAlign ) {
const newClass = optionLayoutAlignToClass( newAlign );

if ( ! newClass ) {
return;
}

const activeForm = document.getElementById( 'frm_active_style_form' );
if ( ! activeForm ) {
return;
}

const alignClasses = [ 'vertical_radio', 'horizontal_radio', 'frm_two_col', 'frm_three_col', 'frm_four_col' ];
const containers = new Set();

activeForm.querySelectorAll( optionSelector ).forEach( option => {
const container = option.closest( '.frm_form_field' );

// Only fields using the style setting (no override) are marked in the styler preview.
if ( container && container.classList.contains( 'frm-default-option-align' ) ) {

Check failure on line 126 in js/admin/style.js

View workflow job for this annotation

GitHub Actions / Run ESLint

Prefer optional chaining: `container?.classList.contains( 'frm-default-option-align' )`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer using an optional chain expression instead, as it's more concise and easier to read


The optional chaining operator can be used to perform null checks before accessing a property, or calling a function.

containers.add( container );
}
} );

containers.forEach( container => {
container.classList.remove( ...alignClasses );
container.classList.add( newClass );
} );
}

/**
* Map an option layout style value to its front-end container class.
*
* @param {string} align The style alignment value.
* @return {string} The matching container class.
*/
function optionLayoutAlignToClass( align ) {
if ( 'inline' === align ) {
return 'horizontal_radio';
}

if ( 'block' === align ) {
return 'vertical_radio';
}

return align;
}

/**
Expand Down
Loading