Gravity Forms custom checkbox element

19 April 2022 HacksTechnologyWordpress
Share with:

Recently, for a customer project we needed to create a custom checkbox element for Gravity Forms, which lists all entries of a specific custom post type as check boxes. During the development I encountered a weird issue, where this element was not rendering, although when debugging – the GF_FIeld object I created had all the necessary options in it – they were just not rendering. I contacted GF support. I received this:

I’ve never been as motivated to achieve something as I was after this reply. I know – their plugin, their choice, I’m perfectly aware of this, I know it’s their decision to make. But I was expecting more from a plugin, that’s by no means cheap. So it is my choice to provide this for free to anyone, who somehow lands here.

So after some persistent debugging, I discovered that in the theme’s functions.php file there was an API call GFAPI::get_forms(true, false) – we needed to implement a custom logic and needed to select which form goes where . The workaround for this was to enclose this API call (or to be more precise the function, that was calling it) in a hook ‘register_sidebar’ – this way it was executed after this element was created and before its logic was needed. I’m not sure what kind of broken logic caused this, but I discovered the hard way that Gravity Forms seems to purposely make the lives of developers hard.

So, in order to create a GF custom element – we need to implement their field framework and create an element, that extends one of their classes. For the current example I used GF_Field_Checkbox class.

class DataCentersCheckbox extends GF_Field_Checkbox
{
	public $type = 'data_centers_checkbox';
	protected $_supports_state_validation = true;
	public $choices;
	public $inputs;

$type is mandatory – it needs to be an unique name, as it serves as the slug for this element. I’m unsure whether state validation is important. The $choices and $inputs are necessary for the prepopulated options. They will be populated in the Class’ constructor:

	public function __construct($data = array())
	{
		parent::__construct($data);
		$locations = $this->_getLocations();
		$this->_setOptions($locations);
	}

	private function _getLocations(): array
	{
		return get_posts('numberposts=-1&post_status=publish&post_type=locations');
	}

	private function _setOptions($locations) : void
	{
		$inputs = [];
		$choices = [];
		$index = 1;
		foreach ($locations as $location) {
			$choices[] = [
				'text' => $location->post_title,
				'value' => (string)$location->ID,
			];
			$inputs[] = [
				'id' => "{$this->id}.{$index}",
				'label' => $location->post_title,
			];
			$index++;
		}
		$this->choices = $choices;
		$this->inputs = $inputs;
	}

I’ve created two helper methods – _getLocations() and _setOptions(), which are used in the constructor (and might be used in other places in the code). For this example we listed all of the published entries of the custom post with slug “location“. For the setOptions – we need to make sure the $choices and $inputs follow the above convention – this is hard requirement for Gravity Form field to work properly – $this->id comes from the parent class and will have the ID of the element in the current form. The index needs to be populated dynamically. This same logic applies for radio buttons as well. The select (dropdown) field does not require $inputs, in only needs $choices.

Next are the field groups:

	private function _addFieldGroups($fieldGroups): array
	{
		foreach ($fieldGroups as $group) {
			if ($group['name'] === 'custom_fields') {
				return $fieldGroups;
			}
		}

		$fieldGroups[] = [
			'name' => 'custom_fields',
			'label' => __('Custom Fields', 'gravity'),
			'fields' => []
		];

		return $fieldGroups;
	}

	public function add_button($field_groups): array
	{
		$groups = $this->_addFieldGroups($field_groups);

		return parent::add_button($groups);
	}

For our example we created a new category in GF called “custom_fields” and placed elements there. This method checks whether this has been already created by another element (we had few of those). addButtons() creates the button for this element in the WordPress’ web interface, and uses _addFieldGroups() helper method to check whether the category is created or not.

	public function get_form_editor_field_title(): string
	{
		return __('Data Centers Checkbox', 'gravity');
	}

	public function get_form_editor_field_description(): string
	{
		return __('Add checkbox list of the data centers', 'gravity');
	}

	public function get_form_editor_field_icon(): string
	{
		return 'gform-icon--check-box';
	}
	public function get_form_editor_button(): array
	{
		return [
			'group' => 'custom_fields',
			'text' => $this->get_form_editor_field_title(),
			'icon' => $this->get_form_editor_field_icon(),
			'description' => $this->get_form_editor_field_description()
		];
	}

Above methods should be self explanatory.

	public function get_form_editor_field_settings(): array
	{
		return [
			'label_setting',
			'description_setting',
			'rules_setting',
			'error_message_setting',
			'css_class_setting',
			'conditional_logic_field_setting',
		];
	}

The above method (another inherited from the parent class) sets which options are available to the user in Gravity Forms. For this example I removed the option to modify the possible options, as we didn’t want the user to be able to.

In the end – we need to register the element – this needs to be outside the class:

GF_Fields::register(new DataCentersCheckbox());

The complete code bellow:

<?php
if (!class_exists('GF_Field')) {
	die();
}

class DataCentersCheckbox extends GF_Field_Checkbox
{
	public $type = 'data_centers_checkbox';
	protected $_supports_state_validation = true;
	public $choices;
	public $inputs;

	public function __construct($data = array())
	{
		parent::__construct($data);
		$locations = $this->_getLocations();
		$this->_setOptions($locations);
	}

	private function _getLocations(): array
	{
		return get_posts('numberposts=-1&post_status=publish&post_type=locations');
	}

	private function _setOptions($locations) : void
	{
		$inputs = [];
		$choices = [];
		$index = 1;
		foreach ($locations as $location) {
			$choices[] = [
				'text' => $location->post_title,
				'value' => (string)$location->ID,
			];
			$inputs[] = [
				'id' => "{$this->id}.{$index}",
				'label' => $location->post_title,
			];
			$index++;
		}
		$this->choices = $choices;
		$this->inputs = $inputs;
	}

	private function _addFieldGroups($fieldGroups): array
	{
		foreach ($fieldGroups as $group) {
			if ($group['name'] === 'custom_fields') {
				return $fieldGroups;
			}
		}

		$fieldGroups[] = [
			'name' => 'custom_fields',
			'label' => __('Custom Fields', 'gravity'),
			'fields' => []
		];

		return $fieldGroups;
	}

	public function add_button($field_groups): array
	{
		$groups = $this->_addFieldGroups($field_groups);

		return parent::add_button($groups);
	}

	public function get_form_editor_field_title(): string
	{
		return __('Data Centers Checkbox', 'gravity');
	}

	public function get_form_editor_field_description(): string
	{
		return __('Add checkbox list of the data centers', 'gravity');
	}

	public function get_form_editor_field_icon(): string
	{
		return 'gform-icon--check-box';
	}

	public function get_form_editor_field_settings(): array
	{
		return [
			'label_setting',
			'description_setting',
			'rules_setting',
			'error_message_setting',
			'css_class_setting',
			'conditional_logic_field_setting',
		];
	}

	public function is_value_submission_array(): bool
	{
		return true;
	}

	public function is_conditional_logic_supported(): bool
	{
		return true;
	}

	public function get_form_editor_button(): array
	{
		return [
			'group' => 'custom_fields',
			'text' => $this->get_form_editor_field_title(),
			'icon' => $this->get_form_editor_field_icon(),
			'description' => $this->get_form_editor_field_description()
		];
	}
}

GF_Fields::register(new DataCentersCheckbox());