Skip to content

Custom JS for fields

Sasha Ivanenko edited this page Jan 19, 2023 · 6 revisions

Table of Contents

Introduction

Here I will try to give a universal example of how to write custom JS code for form fields. For example, I will take the code from one ish and rework it in a new way. After that we will see what is their difference.

The task of the code is to limit the number of checkboxes to be selected.

Old-style

(The author @Lonsdale201)

jQuery( document ).ready( function ( $ ) {
	var maxChecked = 2; /* Modfily the maximum checked elements */
	var checkedCount     = 0;
	var warningTextAdded = false;

	$( '.my_input_checkbox' ).click( function () {
		if ( checkedCount >= maxChecked && $( this ).prop( 'checked' ) ) {
			$( this ).prop( 'checked', false );
		}
		else {
			if ( !$( this ).prop( 'checked' ) ) {
				checkedCount--;
			}
			else {
				checkedCount++;
			}
		}
		if ( checkedCount < maxChecked ) {
			$( '#warning-text2' ).remove();
			warningTextAdded = false;
		}
		else if ( !warningTextAdded ) {
			var warningText = $(
				'<div id="warning-text2">You cant select more</div>' );
			$( '.my_input_checkbox' ).
				last().
				closest( '.jet-form-builder__field-wrap' ).
				append( warningText );
			warningTextAdded = true;
		}
	} );
} );

New-style

const {
	      addFilter,
	      addAction,
      } = JetPlugins.hooks;

const findClassName = item => item.includes( 'check-limit' );
const limitCount    = 2;

addAction(
	'jet.fb.input.makeReactive',
	'jet-form-builder/checkbox-limit-restriction',
	/**
	 * @param input {InputData}
	 */
	function ( input ) {
		// get first checkbox node
		const [ nodeMain ] = input.nodes;

		if ( 'checkbox' !== nodeMain.type ||
			![ ...nodeMain.classList ].some( findClassName )
		) {
			return;
		}

		const wrapper     = nodeMain.closest( '.jet-form-builder-row' );
		const messageNode = document.createElement( 'div' );
		messageNode.classList.add( 'warning-text' );
		messageNode.innerText = 'You cant select more';

		// triggered at each change of the value
		input.watch( () => {
			const { length: count } = input.value.current;

			const isFull = count >= limitCount;

			for ( const node of input.nodes ) {
				if ( node.checked ) {
					continue;
				}

				node.disabled = isFull;
			}

			if ( isFull ) {
				wrapper.append( messageNode );
			}
			else {
				messageNode.remove();
			}
		} );
	},
);

What to choose

If you use JetFormBuilder <=2.1.11 version, so you have no choice. You need to write only in old-style format. If the plugin version is >=3.0.0, then you can write as you wish. However, I recommend writing in the new format and here is why.

One of the key points is jQuery.ready. This event is executed only once, when the page is loaded. But our forms may have a Repeater Field in them, or be rendered using JetPopup using the ajax method and in these cases only the internal JetFormBuilder hooks should be used. Of course, if you want, you can continue to write in the old style, but it will be much more difficult.

To prove this, let's try to write a compatibility of our old code for the repeater.

First, let's move our checkbox field inside the repeater in the form editor. At this step, we can see that our code does not work at all, because we add a "handler" to click on the checkboxes that are currently present on the page.

At first glance, it seems that jQuery.on should help, but it does not. Let's see why. Replace

$( '.my_input_checkbox' ).click( function () { /* code */ } );

with

jQuery( document ).on( 'click', '.my_input_checkbox', function () { /* Move the code without changes */ } );

And here is a bottleneck because of the local variable checkedCount. We have only one variable for the whole script and when adding a new repeater item, we will no longer be able to select checkboxes.

And here is a bottleneck because of the local variable checkedCount. We have only one variable for the whole script and when adding a new repeater item, we will no longer be able to select checkboxes.

This problem can be bypassed as follows:

jQuery( document ).ready( function ( $ ) {
	var maxChecked = 2; /* Modfily the maximum checked elements */
	var warningTextAdded = false;

	jQuery( document ).on( 'click', '.my_input_checkbox', function () {
		const wrapper = $( this ).closest( '.jet-form-builder__fields-group' );

		// field-relative counter
		let checkedCount = wrapper.data( 'limit-count' );

		if ( undefined === checkedCount ) {
			wrapper.data( 'limit-count', 0 );
			checkedCount = 0;
		}

		if ( checkedCount >= maxChecked && $( this ).prop( 'checked' ) ) {
			$( this ).prop( 'checked', false );
		}
		else {
			if ( !$( this ).prop( 'checked' ) ) {
				wrapper.data( 'limit-count', --checkedCount );
			}
			else {
				wrapper.data( 'limit-count', ++checkedCount );
			}
		}
		if ( checkedCount < maxChecked ) {
			$( '#warning-text2' ).remove();
			warningTextAdded = false;
		}
		else if ( !warningTextAdded ) {
			var warningText = $(
				'<div id="warning-text2">You cant select more</div>' );
			$( '.my_input_checkbox' ).
				last().
				closest( '.jet-form-builder__field-wrap' ).
				append( warningText );
			warningTextAdded = true;
		}
	} );
} );

We have changed how the counters are stored for each field. Now this example is compatible with the repeater field. But we spent extra time on this. And we will have to spend it again when we want this script to work in popups (JetPopup, Elementor Pro).

Enqueue script

via code

<?php

// functions.php of your child theme or own plugin

const CHECKBOX_LIMIT_HANDLE = 'jet-fb-custom-checkbox-limit';

add_action(
	'jet-form-builder/before-start-form-row',
	function ( \Jet_Form_Builder\Blocks\Types\Base $block ) {
		if ( 'checkbox-field' !== $block->get_name() ) {
			return;
		}
		wp_enqueue_script( CHECKBOX_LIMIT_HANDLE );
	}
);

add_action(
	'wp_enqueue_scripts',
	function () {
		wp_register_script(
			CHECKBOX_LIMIT_HANDLE,
			'path/to/your/script.js',
			// for >= 3.0.0 JetFormBuilder
			array( 'jet-plugins' ),
			// for <= 2.1.11 JetFormBuilder
                        // array()
			'1.0.0',
			true
		);
	}
);

Useful links

🔸 View source of current page