Custom Gravity Forms Field with Multiple Inputs
Asked Answered
T

2

7

I have been working on a Gravity Forms extension for a client. The concept is to add a new field type with 4 inputs. I have tried about 10 different variations on how people build custom gravity form fields, but I keep running into the same issue.

When creating a custom field, if I use only 1 input under the naming convention of input_{field_id} the form will save and validate properly. But the moment I try to add more than one field using the names input_{field_id}.{i} just like the built in fields the form will no longer save my data.

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

class GF_Field_Attendees extends GF_Field {

    public $type = 'attendees';

    public function get_form_editor_field_title() { return esc_attr__( 'Attendees', 'gravityforms' ); }

    public function get_form_editor_button() {
        return array(
            'group' => 'advanced_fields',
            'text'  => $this->get_form_editor_field_title(),
            'onclick'   => "StartAddField('".$this->type."');",
        );
    }

    public function get_form_editor_field_settings() {
        return array(
            'conditional_logic_field_setting',
            'prepopulate_field_setting',
            'error_message_setting',
            'label_setting',
            'admin_label_setting',
            'rules_setting',
            'duplicate_setting',
            'description_setting',
            'css_class_setting',
        );
    }

    public function is_conditional_logic_supported() { return true; }

    public function get_field_input( $form, $value = '', $entry = null ) {
        $form_id    = $form['id'];
        $field_id   = intval( $this->id );

        $first  = esc_attr( GFForms::get( 'input_' . $this->id . '_1', $value ) );
        $last   = esc_attr( GFForms::get( 'input_' . $this->id . '_2', $value ) );
        $email  = esc_attr( GFForms::get( 'input_' . $this->id . '_3', $value ) );
        $phone  = esc_attr( GFForms::get( 'input_' . $this->id . '_4', $value ) );

        $disabled_text = $is_form_editor ? "disabled='disabled'" : '';
        $class_suffix  = $is_entry_detail ? '_admin' : '';

        $first_tabindex = GFCommon::get_tabindex();
        $last_tabindex  = GFCommon::get_tabindex();
        $email_tabindex = GFCommon::get_tabindex();
        $phone_tabindex = GFCommon::get_tabindex();

        $required_attribute     = $this->isRequired ? 'aria-required="true"' : '';
        $invalid_attribute      = $this->failed_validation ? 'aria-invalid="true"' : 'aria-invalid="false"';

        $first_markup = '<span id="input_'.$field_id.'_'.$form_id.'.1_container" class="attendees_first">';
            $first_markup .= '<input type="text" name="input_'.$field_id.'.1" id="input_'.$field_id.'_'.$form_id.'_1" value="'.$first.'" aria-label="First Name" '.$first_tabindex.' '.$disabled_text.' '.$required_attribute.' '.$invalid_attribute.'>';
            $first_markup .= '<label for="input_'.$field_id.'_'.$form_id.'_1">First Name</label>';
        $first_markup .= '</span>';

        $last_markup = '<span id="input_'.$field_id.'_'.$form_id.'.2_container" class="attendees_last">';
            $last_markup .= '<input type="text" name="input_'.$field_id.'.2" id="input_'.$field_id.'_'.$form_id.'_2" value="'.$last.'" aria-label="Last Name" '.$last_tabindex.' '.$disabled_text.' '.$required_attribute.' '.$invalid_attribute.'>';
            $last_markup .= '<label for="input_'.$field_id.'_'.$form_id.'_2">Last Name</label>';
        $last_markup .= '</span>';

        $email_markup = '<span id="input_'.$field_id.'_'.$form_id.'.3_container" class="attendees_email">';
            $email_markup .= '<input type="text" name="input_'.$field_id.'.3" id="input_'.$field_id.'_'.$form_id.'_3" value="'.$email.'" aria-label="Email" '.$email_tabindex.' '.$disabled_text.' '.$required_attribute.' '.$invalid_attribute.'>';
            $email_markup .= '<label for="input_'.$field_id.'_'.$form_id.'_3">Email</label>';
        $email_markup .= '</span>';

        $phone_markup = '<span id="input_'.$field_id.'_'.$form_id.'.4_container" class="attendees_phone">';
            $phone_markup .= '<input type="text" name="input_'.$field_id.'.4" id="input_'.$field_id.'_'.$form_id.'_4" value="'.$phone.'" aria-label="Phone #" '.$phone_tabindex.' '.$disabled_text.' '.$required_attribute.' '.$invalid_attribute.'>';
            $phone_markup .= '<label for="input_'.$field_id.'_'.$form_id.'_4">Phone #</label>';
        $phone_markup .= '</span>';

        $css_class = $this->get_css_class();

        return "<div class='ginput_complex{$class_suffix} ginput_container {$css_class} gfield_trigger_change' id='{$field_id}'>
                    {$first_markup}
                    {$last_markup}
                    {$email_markup}
                    {$phone_markup}
                    <div class='gf_clear gf_clear_complex'></div>
                </div>";
    }

    public function get_css_class() {
        $first_input = GFFormsModel::get_input( $this, $this->id . '_2' );
        $last_input  = GFFormsModel::get_input( $this, $this->id . '_3' );
        $email_input = GFFormsModel::get_input( $this, $this->id . '_4' );
        $phone_input   = GFFormsModel::get_input( $this, $this->id . '_5' );

        $css_class = '';
        $visible_input_count = 0;

        if ( $first_input && ! rgar( $first_input, 'isHidden' ) ) {
            $visible_input_count++;
            $css_class .= 'has_first_name ';
        } else {
            $css_class .= 'no_first_name ';
        }

        if ( $last_input && ! rgar( $last_input, 'isHidden' ) ) {
            $visible_input_count++;
            $css_class .= 'has_last_name ';
        } else {
            $css_class .= 'no_last_name ';
        }

        if ( $email_input && ! rgar( $email_input, 'isHidden' ) ) {
            $visible_input_count++;
            $css_class .= 'has_email ';
        } else {
            $css_class .= 'no_email ';
        }

        if ( $phone_input && ! rgar( $phone_input, 'isHidden' ) ) {
            $visible_input_count++;
            $css_class .= 'has_phone ';
        } else {
            $css_class .= 'no_phone ';
        }

        $css_class .= "gf_attendees_has_{$visible_input_count} ginput_container_attendees ";

        return trim( $css_class );
    }

    public function get_value_submission( $field_values, $get_from_post ) {
        if(!$get_from_post) {
            return $field_values;
        }

        return $_POST;
    }
}

GF_Fields::register( new GF_Field_Attendees() );

I have spend about 20 hours trying different fixes and searching the internet to get this working, with no luck to show for it. At one point I was able to get the form fields to save using a different method (see below), but I could not make the field required or use conditional login on it, which is a must.

$group_title = "Attendees";
$group_name = "attendees";
$group_fields = array(
    'attendee_first' => 'First Name',
    'attendee_last' => 'Last Name',
    'attendee_email' => 'Email',
    'attendee_phone' => 'Phone'
);
$group_values = array();

add_filter('gform_add_field_buttons', add_field);
function add_field($field_group)
{
  global $group_title, $group_name;

  foreach ($field_group as &$group) {

    if ($group['name'] == 'advanced_fields') {
      $group['fields'][] = array (
        'class'     => 'button',
        'value'     => __($group_title, 'gravityforms'),
        'onclick'   => "StartAddField('".$group_name."');",
        'data-type' => $group_name
      );
      break;
    }
  }

  return $field_group;
}

add_filter('gform_field_type_title', add_field_title, 10, 2);
function add_field_title($title, $field_type)
{
  global $group_title, $group_name;

  if ($field_type == $group_name) {
    $title = __($group_title, 'gravityforms');
  }

  return $title;
}

add_filter('gform_field_input', 'render_fields', 10, 5);
function render_fields($input, $field, $value, $entry_id, $form_id)
{
  global $group_name, $group_fields;

  if ($field->type == $group_name)
  {
    $i = 1;
    $input = '<div class="ginput_complex ginput_container">';
    foreach ($group_fields as $key => $val) {
        $input .= '<span id="input_'.$field['id'].'_'.$form_id.'_'.$i.'_container" class="name_suffix ">';
            $input .= '<input type="text" name="input_'.$field['id'].'_'.$i.'" id="input_'.$field['id'].'_'.$form_id.'_'.$i.'" value="'.$value[$field['id'].'.'.$i].'" class="'.esc_attr($key).'" aria-label="'.$val.'">';
            $input .= '<label for="input_'.$field['id'].'_'.$form_id.'_'.$i.'">'.$val.'</label>';
        $input .= '</span>';
        $i ++;
        if ($i % 10 == 0) { $i++; }
    }
    $input .= '</div>';
  }

  return $input;
}

add_action('gform_editor_js_set_default_values', set_default_values);
function set_default_values()
{
  global $group_title, $group_name, $group_fields;
  ?>
  case '<?php echo $group_name; ?>' :
    field.label = '<?php _e($group_title, 'gravityforms'); ?>';
    field.inputs = [
        <?php
        $i = 1;
        foreach ($group_fields as $key => $val) { ?>
            new Input(field.id + 0.<?php echo $i; ?>, '<?php echo esc_js(__($val, 'gravityforms')); ?>'),
        <?php
            $i++;
            if ($i % 10 == 0) { $i++; }
        } ?>
    ];
    break;
  <?php
}

add_filter( 'gform_entry_field_value', 'category_names', 10, 4 );
function category_names( $value, $field, $lead, $form )
{
  global $group_name, $group_values;

  if($field->type == $group_name)
  {
    $array = array();
    $output = "";
    foreach($field->inputs as $input)
    {
      $array[$input['label']] = $value[$input['id']];

      $output .= "<strong>".$input['label'].":</strong> ";
      $output .= $value[$input['id']]."<br>";
    }
    $group_values[] = $array;

    return $output;
  }

  return $value;
}

If anyone can help me with either issue, it would be greatly appreciated.

Class update:

  • Cleaned Up get_field_input

  • Added get_value_submission

Talc answered 24/3, 2017 at 21:25 Comment(1)
I still haven't found any answers to my question. If anybody ever solves this I would love to hear about it.Talc
T
16

After working with the Gravity Forms support team for a few days, we were able to come up with this solution. Everything seems to be working now. Hope this helps someone in the future.

class GF_Field_Attendees extends GF_Field {

    public $type = 'attendees';

    public function get_form_editor_field_title() {
        return esc_attr__( 'Attendees', 'gravityforms' );
    }

    public function get_form_editor_button() {
        return array(
            'group' => 'advanced_fields',
            'text'  => $this->get_form_editor_field_title(),
        );
    }

    public function get_form_editor_field_settings() {
        return array(
            'conditional_logic_field_setting',
            'prepopulate_field_setting',
            'error_message_setting',
            'label_setting',
            'admin_label_setting',
            'rules_setting',
            'duplicate_setting',
            'description_setting',
            'css_class_setting',
        );
    }

    public function is_conditional_logic_supported() {
        return true;
    }

    public function get_field_input( $form, $value = '', $entry = null ) {
        $is_entry_detail = $this->is_entry_detail();
        $is_form_editor  = $this->is_form_editor();

        $form_id  = $form['id'];
        $field_id = intval( $this->id );

        $first = $last = $email = $phone = '';

        if ( is_array( $value ) ) {
            $first = esc_attr( rgget( $this->id . '.1', $value ) );
            $last  = esc_attr( rgget( $this->id . '.2', $value ) );
            $email = esc_attr( rgget( $this->id . '.3', $value ) );
            $phone = esc_attr( rgget( $this->id . '.4', $value ) );
        }

        $disabled_text = $is_form_editor ? "disabled='disabled'" : '';
        $class_suffix  = $is_entry_detail ? '_admin' : '';

        $first_tabindex = GFCommon::get_tabindex();
        $last_tabindex  = GFCommon::get_tabindex();
        $email_tabindex = GFCommon::get_tabindex();
        $phone_tabindex = GFCommon::get_tabindex();

        $required_attribute = $this->isRequired ? 'aria-required="true"' : '';
        $invalid_attribute  = $this->failed_validation ? 'aria-invalid="true"' : 'aria-invalid="false"';

        $first_markup = '<span id="input_' . $field_id . '_' . $form_id . '.1_container" class="attendees_first">';
        $first_markup .= '<input type="text" name="input_' . $field_id . '.1" id="input_' . $field_id . '_' . $form_id . '_1" value="' . $first . '" aria-label="First Name" ' . $first_tabindex . ' ' . $disabled_text . ' ' . $required_attribute . ' ' . $invalid_attribute . '>';
        $first_markup .= '<label for="input_' . $field_id . '_' . $form_id . '_1">First Name</label>';
        $first_markup .= '</span>';

        $last_markup = '<span id="input_' . $field_id . '_' . $form_id . '.2_container" class="attendees_last">';
        $last_markup .= '<input type="text" name="input_' . $field_id . '.2" id="input_' . $field_id . '_' . $form_id . '_2" value="' . $last . '" aria-label="Last Name" ' . $last_tabindex . ' ' . $disabled_text . ' ' . $required_attribute . ' ' . $invalid_attribute . '>';
        $last_markup .= '<label for="input_' . $field_id . '_' . $form_id . '_2">Last Name</label>';
        $last_markup .= '</span>';

        $email_markup = '<span id="input_' . $field_id . '_' . $form_id . '.3_container" class="attendees_email">';
        $email_markup .= '<input type="text" name="input_' . $field_id . '.3" id="input_' . $field_id . '_' . $form_id . '_3" value="' . $email . '" aria-label="Email" ' . $email_tabindex . ' ' . $disabled_text . ' ' . $required_attribute . ' ' . $invalid_attribute . '>';
        $email_markup .= '<label for="input_' . $field_id . '_' . $form_id . '_3">Email</label>';
        $email_markup .= '</span>';

        $phone_markup = '<span id="input_' . $field_id . '_' . $form_id . '.4_container" class="attendees_phone">';
        $phone_markup .= '<input type="text" name="input_' . $field_id . '.4" id="input_' . $field_id . '_' . $form_id . '_4" value="' . $phone . '" aria-label="Phone #" ' . $phone_tabindex . ' ' . $disabled_text . ' ' . $required_attribute . ' ' . $invalid_attribute . '>';
        $phone_markup .= '<label for="input_' . $field_id . '_' . $form_id . '_4">Phone #</label>';
        $phone_markup .= '</span>';

        $css_class = $this->get_css_class();

        return "<div class='ginput_complex{$class_suffix} ginput_container {$css_class} gfield_trigger_change' id='{$field_id}'>
                    {$first_markup}
                    {$last_markup}
                    {$email_markup}
                    {$phone_markup}
                    <div class='gf_clear gf_clear_complex'></div>
                </div>";
    }

    public function get_css_class() {
        $first_input = GFFormsModel::get_input( $this, $this->id . '.1' );
        $last_input  = GFFormsModel::get_input( $this, $this->id . '.2' );
        $email_input = GFFormsModel::get_input( $this, $this->id . '.3' );
        $phone_input = GFFormsModel::get_input( $this, $this->id . '.4' );

        $css_class           = '';
        $visible_input_count = 0;

        if ( $first_input && ! rgar( $first_input, 'isHidden' ) ) {
            $visible_input_count ++;
            $css_class .= 'has_first_name ';
        } else {
            $css_class .= 'no_first_name ';
        }

        if ( $last_input && ! rgar( $last_input, 'isHidden' ) ) {
            $visible_input_count ++;
            $css_class .= 'has_last_name ';
        } else {
            $css_class .= 'no_last_name ';
        }

        if ( $email_input && ! rgar( $email_input, 'isHidden' ) ) {
            $visible_input_count ++;
            $css_class .= 'has_email ';
        } else {
            $css_class .= 'no_email ';
        }

        if ( $phone_input && ! rgar( $phone_input, 'isHidden' ) ) {
            $visible_input_count ++;
            $css_class .= 'has_phone ';
        } else {
            $css_class .= 'no_phone ';
        }

        $css_class .= "gf_attendees_has_{$visible_input_count} ginput_container_attendees ";

        return trim( $css_class );
    }

    public function get_form_editor_inline_script_on_page_render() {

        // set the default field label for the field
        $script = sprintf( "function SetDefaultValues_%s(field) {
        field.label = '%s';
        field.inputs = [new Input(field.id + '.1', '%s'), new Input(field.id + '.2', '%s'), new Input(field.id + '.3', '%s'), new Input(field.id + '.4', '%s')];
        }", $this->type, $this->get_form_editor_field_title(), 'First Name', 'Last Name', 'Email', 'Phone' ) . PHP_EOL;

        return $script;
    }

    public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
        if ( is_array( $value ) ) {
            $first = trim( rgget( $this->id . '.1', $value ) );
            $last  = trim( rgget( $this->id . '.2', $value ) );
            $email = trim( rgget( $this->id . '.3', $value ) );
            $phone = trim( rgget( $this->id . '.4', $value ) );

            $return = $first;
            $return .= ! empty( $return ) && ! empty( $last ) ? " $last" : $last;
            $return .= ! empty( $return ) && ! empty( $email ) ? " $email" : $email;
            $return .= ! empty( $return ) && ! empty( $phone ) ? " $phone" : $phone;

        } else {
            $return = '';
        }

        if ( $format === 'html' ) {
            $return = esc_html( $return );
        }

        return $return;
    }

}

GF_Fields::register( new GF_Field_Attendees() );
Talc answered 13/4, 2017 at 17:34 Comment(4)
Thanks for sharingSvetlanasvoboda
You glorious beast, this is exactly what I needed. I did need to make certain fields required and others not though. I just used a similar validation to the name field: pastebin.com/raw/HjdDytLJNovia
Thanks for sharing. Too bad that the official documentation is so poor.Ebro
Really helpful answer. I'll just add that the specific line that led to successful saving in my case was field.inputs = [new Input(field.id + '.1', '%s')...]; in the get_form_editor_inline_script_on_page_render function. The default inputs property on a GF_Field instance is null and has to be set to an array for complex inputs.Modiolus
F
0

I've been having difficulties getting the value from rgget for the fields and I had to change it to the following to get it to work:

rgget( 'input_' . $this->id . '_1', $value )

Frons answered 30/1, 2023 at 23:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.