Gravity Forms - markhowellsmead/helpers GitHub Wiki
- https://gravitywiz.com/
- https://gravityplus.pro/gravity-forms-user-registration-enhanced/ (User registration and user profile editor)
- https://www.gravityforms.com/add-ons/user-registration/
Gravity Forms often sticks the CSS link
tags directly in the body
of the page. And only on pages where a form has been placed using a shortcode or by a direct PHP call.
This can cause problems with specificity. It's better to load Gravity Forms CSS files in the head
and then enqueue your CSS files after them.
Or just stop using their CSS altogether.
add_action('gform_enqueue_scripts', [$this, 'dequeueGravityFormsCSS'], 11);
with
public function dequeueGravityFormsCSS()
{
wp_dequeue_style('gforms_reset_css');
wp_dequeue_style('gforms_datepicker_css');
wp_dequeue_style('gforms_formsmain_css');
wp_dequeue_style('gforms_ready_class_css');
wp_dequeue_style('gforms_browsers_css');
}
add_action('wp_enqueue_scripts', function(){
if (function_exists('gravity_form_enqueue_scripts')) {
gravity_form_enqueue_scripts(1);
}
});
wp_enqueue_style('myThemeCSS', get_template_directory_uri() . '/theme.css', ['gforms_reset_css', 'gforms_formsmain_css', 'gforms_ready_class_css', 'gforms_browsers_css'], '1.0.0');
The following replaces the standard icon with a 1x1px transparent GIF. The CSS adds a spinning animation and a border.
add_filter('gform_ajax_spinner_url', function(){
return 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
}, 10);
Don't forget to autoprefix.
.gform_ajax_spinner {
border: 2px solid rgba(0, 0, 0, 0.1);
border-left: 2px solid silver;
animation: gf_ajaxspinner 1.1s infinite linear;
border-radius: 50%;
width: 1.5rem;
height: 1.5rem;
flex-basis: 1.5rem; // if necessary
margin-left: 0.5rem;
padding: 0;
}
@keyframes gf_ajaxspinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
Don't forget to remove this when you're finished.
add_action('gform_pre_submission', function () {
die('x');
}, 10);
To create a custom Gravity Forms field, you need to add a button to the backend interface, so that the user can pull the field into the form configuration. Then you need to define a (single) custom field function which outputs the HTML in both the frontend and backend. Use the helper function GFCommon::is_form_editor()
to switch HTML outputs for frontend and backend.
In the following code, we'll add a custom checkbox checkbox_ct_terms
, which has a special function.
/**
* Add the custom field as a button to the available fields in BE
*/
add_filter('gform_add_field_buttons', function ($field_groups) {
foreach ($field_groups as &$group) {
if ($group['name'] == 'advanced_fields') {
$group['fields'][] = array(
'class' => 'button',
'value' => __('Nutzungsbest.', 'wptheme.sage'),
'onclick' => 'StartAddField(\'checkbox_ct_terms\');'
);
break;
}
}
return $field_groups;
});
/**
* Output the field HTML (used in FE and BE)
* The call GFCommon::is_form_editor() allows you to return different output for FE and BE
*/
add_action('gform_field_input', function ($input, $field, $value, $lead_id, $form_id) {
if ($field['type'] == 'checkbox_ct_terms') {
$extra_css = isset($field['cssClass']) ? $field['cssClass'] : ”;
return sprintf(
'<div class="ginput_container ginput_container_checkbox ginput_container_checkbox_ct_terms %5$s">
<ul class="gfield_checkbox" id="input_1_%1$s">
<li class="gchoice_1_%1$s_1">
<input %6$s name="input_%1$s.1" type="checkbox" value="%3$s" id="choice_1_%1$s_1" tabindex="%2$s" required="true">
<label for="choice_1_%1$s_1" id="label_1_%1$s_1" class="gfield_label">%4$s</label>%7$s
</li>
</ul>
</div>',
$field['id'],
GFCommon::get_tabindex(),
__('Ich akzeptiere die Nutzungsbedingungen und rechtlichen Hinweise.', 'wptheme.sage'), // field value
__('Ich akzeptiere die Nutzungsbedingungen und rechtlichen Hinweise. <span class="gfield_required">*</span>', 'wptheme.sage'), // field label
$extra_css,
GFCommon::is_form_editor() ? 'disabled' : '',
filter_var($field['checkbox_ct_terms_field_url'], FILTER_VALIDATE_URL) ? sprintf(
' (<a class="gfield_checkbox_more_link" href="%1$s" target="_blank">%2$s</a>)',
$field['checkbox_ct_terms_field_url'],
__('Mehr dazu', 'wptheme.sage')
) : (GFCommon::is_form_editor() ? '<em>' .__('Ungültige URL eingetragen!', 'wptheme.sage').'</em>' : '')
);
}
return $input;
}, 10, 5);
To add a new settings field option to the custom field you've created, you need to hook into gform_field_standard_settings
. (See https://www.gravityhelp.com/documentation/category/field-settings/ for other settings groups.)
/**
* Add custom backend settings field to a Gravity Forms field for a URL.
* https://engineering.growella.com/gravity-forms-custom-field-settings/
*/
add_action('gform_field_standard_settings', function ($position) {
if (100 !== $position) {
return;
}
printf(
'<li class="checkbox_ct_terms_field_url_setting field_setting">
<ul>
<li>
<label for="checkbox_ct_terms_field_url" class="section_label">%1$s</label>
<input id="checkbox_ct_terms_field_url" type="url" style="width: 100%%" onchange="SetFieldProperty(\'checkbox_ct_terms_field_url\', this.value)" />
</li>
</ul>
</li>',
esc_html('URL', 'wptheme.sage')
);
});
You assign permissions - that the settings field may appear in the backend editor - using a JavaScript snippet, which is embedded directly after the settings field.
/**
* Add an inline JS link below the field in BE, which initialises the field's backend functionality
*/
add_action('gform_editor_js', function () {
echo '<script src="' .get_template_directory_uri(). '/dist/lib/js/gf_checkbox_ct_terms.js"></script>';
});
The content of that file is as follows (in this example). You're allowing the custom settings field checkbox_ct_terms_field_url
by adding it to the fieldSettings
array entry for the custom field checkbox_ct_terms
.
var fieldSettings;
(function ($) {
$(document).ready(function ($) {
// Which subfields are allowed on this field type?
fieldSettings.checkbox_ct_terms. = '.checkbox_ct_terms_field_url_setting, .admin_label_setting, .label_setting, .error_message_setting, .css_class_setting';
// Fill the stored value of the checkbox_ct_terms_field_url subfield
$(document).bind('gform_load_field_settings', function (event, field, form) {
$('#checkbox_ct_terms_field_url').val(field['checkbox_ct_terms_field_url']);
});
});
}(jQuery));
/**
* Ensure value of custom field 'checkbox_ct_terms' is included in confirmation messages ({all_fields})
*/
add_filter('gform_merge_tag_filter', function ($value, $merge_tag, $modifier, $field, $raw_value) {
switch ($field->type) {
case 'checkbox_ct_terms':
if (empty($value) && isset($_POST['input_' .$field['id']. '_' .$field['formId']])) {
$value = esc_attr($_POST['input_' .$field['id']. '_' .$field['formId']]);
}
break;
}
return $value;
}, 10, 5);
Extends the list of class names on the overall field container - usually li.gfield
- with a class name indicating the type of field.
use DOMDocument;
…
add_filter('gform_field_container', [$this, 'addFieldTypeClass'], 10, 2);
…
/**
* Add a field type class name to every field
*
* @param string $html
* @param mixed $field
* @return void
*/
public function addFieldTypeClass(string $html, mixed $field)
{
libxml_use_internal_errors(true);
$document = new DOMDocument();
$document->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$class = $document->documentElement->childNodes[0]->childNodes[0]->getAttribute('class');
$document->documentElement->childNodes[0]->childNodes[0]->setAttribute('class', "{$class} gfield_container--{$field->type}");
$body = $document->saveHtml($document->getElementsByTagName('body')->item(0));
return str_replace(['<body>', '</body>'], '', $body);
}
Version without Real Time Validation:
(function($) {
/**
* Scroll to the first error message (respecting an offset)
* this requires only jQuery, not require any additional plugins
*/
$(document).bind('gform_post_render', function() {
var $firstError = $('li.gfield.gfield_error:first');
if ($firstError.length > 0) {
$firstError.find('input, select, textarea').eq(0).focus();
var scrollTo = $firstError.offset().top;
if ($('.c-toolbar:first').length) {
scrollTo -= $('.c-toolbar').outerHeight(true) * 1.2;
}
$('html, body').stop().animate({
scrollTop: scrollTo
}, 1000, 'swing');
}
});
})(jQuery);
Version with Real Time Validation for Gravity Forms:
$(window).on('load.formoffset resize.formoffset', function() {
if($('.ct-signup-form').length > 0){
window.lv_offset = function(currentOffset) {
return $('.ct-signup-form .gfield.gfield_error').first().offset().top;
};
}
});
/**
* Customize the inline error messages for the Gravity Forms Real Time Validation plugin
* @param array $messages The default messages
* @return array The potentially amended messages
*/
public function errorMessages($messages)
{
foreach (array_keys($messages) as $key) {
$messages[$key] = _x('Dies ist ein Pflichtfeld und muss ausgefüllt werden', 'Default custom live form validation message', 'wptheme-starter');
}
return $messages;
}
hooked to
add_filter('lv_default_error_messages', [$this, 'errorMessages'], 10, 1);
add_filter('gform_field_choices', [$this, 'checkboxChoices'], 10, 2);
…
public function checkboxChoices($choices, $field)
{
if ($field->type !== 'checkbox' && $field->type !== 'radio') {
return $choices;
}
libxml_use_internal_errors(true);
$domDocument = new DOMDocument();
$domDocument->loadHTML(mb_convert_encoding($choices, 'HTML-ENTITIES', 'UTF-8'));
$xpath = new DOMXpath($domDocument);
foreach ($xpath->query('//label') as $label) {
$span = $domDocument->createElement('span');
$span->setAttribute('class', 'gchoice_text o-formchoice__text');
foreach ($label->childNodes as $child) {
$span->appendChild($child->cloneNode(true));
}
while ($label->hasChildNodes()) {
$label->removeChild($label->firstChild);
}
$label->appendChild($span);
}
$body = $domDocument->saveHtml($domDocument->getElementsByTagName('body')->item(0));
return str_replace(['<body>', '</body>'], '', $body);
}