Gravity Wiz

Magically enhanced tutorials, snippets and plugins for Gravity Forms!

  • Gravity Perks
    • Gravity Perks
    • Tutorials & Snippets
    • About
  • Support
    • Documentation
    • Support
    • Account

How To Build a Curbside Pickup Form

Easily add a pickup option for your store using Gravity Forms. Build a custom solution that doesn't require a complicated eCommerce platform.

Last updated July 6, 2020 | Written by Scott Buchmann 1 Comment

  • July 1, 2020: Updated Field Groups snippet to work with when limits were applied to Radio Buttons.

This article requires the GP Limit Choices perk. Buy Gravity Perks today to get this perk plus 33 other premium Gravity Forms plugins!

  • View the Plugin
  • Buy Gravity Perks

View Demo

Overview

With the current global pandemic COVID-19, many essential brick-and-mortar businesses find themselves in need of a curbside pickup scheduling solution. This is new territory for many, and while there are plenty of sophisticated eCommerce options, they require significant setup and time to get going.

Using Gravity Forms and Gravity Forms Limit Choices, you can easily build a form for your customers to schedule their pickup time and limit the number of pickups available per time slot and per day. Let’s get started.

  1. Overview
  2. Build Your Form
  3. Set Up Choice Limits
  4. Wrapping Up
  5. Taking It Further
    1. Show Number of Spots Left

Build Your Form

We’ll begin by adding the necessary fields to our form to start collecting orders. Since this type of form requires capturing lots of data, I suggest making it a multi-page form.

Use Gravity Forms Multi-Page Navigation to improve navigation between the pages like we have in the demo.

On the first page, we ask for the customer’s name, email, and phone number. I have also included a Radio Button field asking who is picking up the order. It has conditional logic attached to it to show another Name field if someone else is picking up the order.

The second page is where the customer puts together their order. My form uses List fields because it gives the user an easy system to add items to their list in organized categories.

There’s also a section at the bottom asking if they will allow substitutes and any additional notes.

On the last page, the pickup date and time are selected. To do this, our form has a Date and a Drop Down field. In the Drop Down field I have entered all of my available pickup time windows. There’s also a couple of Consent fields to make sure our customer understands how this process works.

Set Up Choice Limits

With the form built, the next step is to limit the pickup time slots to prevent too many customers from trying to pick up at the same time. Start by adding limits to each of the times in the Drop Down field.

Next, we need to create a Field Group so that the limited time slots are per day and not per form. A Field Group is a paired set of fields that that creates a unique choice. The Date and Drop Down fields will be grouped together, and the time slots will apply on a per-day basis. This is done through a snippet.

Show CodeDownload Plugin
<?php
/**
* Gravity Perks // GP Limit Choices // Field Group
*
* Specify a group of fields that should create a unique choice to limited. For example, a Date field and Radio Button field could be
* combined to sell x tickets per date where the ticket type is controlled by the Radio Button field and the date is
* selected in the Date field.
*
* @version 1.4
* @author David Smith <david@gravitywiz.com>
* @license GPL-2.0+
* @link http://gravitywiz.com/documentation/gp-limit-choices/
*
* Plugin Name: GP Limit Choices - Field Groups
* Plugin URI: http://gravitywiz.com/documentation/gp-limit-choices/
* Description: Specify a group of fields that should create a unique choice to be limited.
* Author: Gravity Wiz
* Version: 1.4
* Author URI: http://gravitywiz.com
*/
class GP_Limit_Choices_Field_Group {
public function __construct( $args = array() ) {
// set our default arguments, parse against the provided arguments, and store for use throughout the class
$this->_args = wp_parse_args( $args, array(
'form_id' => false,
'field_ids' => array(),
) );
$this->_args['hash'] = hash_hmac( 'sha256', serialize( $this->_args ), 'gplc_field_group' );
add_filter( 'gwlc_choice_counts_query', array( $this, 'limit_by_field_group' ), 10, 2 );
add_action( 'wp_ajax_gplcfg_refresh_field', array( $this, 'ajax_refresh' ) );
add_action( 'wp_ajax_nopriv_gplcfg_refresh_field', array( $this, 'ajax_refresh' ) );
add_filter( 'gform_pre_render', array( $this, 'load_form_script' ), 10, 2 );
add_filter( 'gform_register_init_scripts', array( $this, 'add_init_script' ), 10, 2 );
if( isset( $_POST['action'] ) && $_POST['action'] == 'gplcfg_refresh_field' ) {
remove_action( 'wp', array( 'GFForms', 'maybe_process_form' ), 9 );
remove_action( 'admin_init', array( 'GFForms', 'maybe_process_form' ), 9 );
}
}
public function limit_by_field_group( $query, $field ) {
global $wpdb;
$field_ids = $this->_args['field_ids'];
if( ! $this->is_applicable_form( $field->formId ) || ! in_array( $field->id, $field_ids ) ) {
return $query;
}
unset( $field_ids[ array_search( $field->id, $field_ids ) ] );
$field_ids = array_values( $field_ids );
$form = GFAPI::get_form( $field->formId );
$join = $where = array();
$select = $from = '';
foreach( $field_ids as $index => $field_id ) {
$field = GFFormsModel::get_field( $form, $field_id );
$alias = sprintf( 'fgem%d', $index + 1 );
$_alias = null;
if( $index == 0 ) {
$_alias = $alias;
$select = "SELECT DISTINCT {$alias}.entry_id";
$from = "FROM {$wpdb->prefix}gf_entry_meta {$alias}";
$value = $field->get_value_save_entry( GFFormsModel::get_field_value( $field ), $form, null, null, null );
$where[] = $wpdb->prepare( "( {$alias}.form_id = %d AND {$alias}.meta_key = %s AND {$alias}.meta_value = %s )", $field->formId, $field_id, $value );
} else {
$join[] = "INNER JOIN {$wpdb->prefix}gf_entry_meta {$alias} ON {$_alias}.entry_id = {$alias}.entry_id";
}
}
$field_group_query = array(
'select' => $select,
'from' => $from,
'join' => implode( ' ', $join ),
'where' => sprintf( 'WHERE %s', implode( "\nAND ", $where ) )
);
$query['where'] .= sprintf( ' AND e.id IN( %s )', implode( "\n", $field_group_query ) );
return $query;
}
public function is_applicable_form( $form ) {
$form_id = isset( $form['id'] ) ? $form['id'] : $form;
return empty( $this->_args['form_id'] ) || $form_id == $this->_args['form_id'];
}
public function load_form_script( $form, $is_ajax_enabled ) {
$func = array( 'GP_Limit_Choices_Field_Group', 'output_script' );
if( $this->is_applicable_form( $form ) && ! has_action( 'wp_footer', $func ) ) {
add_action( 'wp_footer', $func );
add_action( 'gform_preview_footer', $func );
}
return $form;
}
public function add_init_script( $form ) {
if( ! $this->is_applicable_form( $form ) ) {
return;
}
$target_field_id = $this->get_target_field_id( $form, $this->_args['field_ids'] );
$args = array(
'formId' => $this->_args['form_id'],
'targetFieldId' => $target_field_id,
'triggerFieldIds' => $this->get_trigger_field_ids( $form, $this->_args['field_ids'] ),
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'hash' => $this->_args['hash'],
);
$script = 'new GPLCFieldGroup( ' . json_encode( $args ) . ' );';
$slug = implode( '_', array( 'gplc_field_group', $this->_args['form_id'], $target_field_id ) );
GFFormDisplay::add_init_script( $this->_args['form_id'], $slug, GFFormDisplay::ON_PAGE_RENDER, $script );
}
public function get_target_field_id( $form, $field_ids ) {
foreach( $field_ids as $field_id ) {
$field = GFAPI::get_field( $form, $field_id );
if ( gp_limit_choices()->is_applicable_field( $field ) ) {
return $field_id;
}
}
return false;
}
public function get_trigger_field_ids( $form, $field_ids ) {
$target_field_id = $this->get_target_field_id( $form, $field_ids );
return array_values( array_filter( $field_ids, function( $field_id ) use ( $target_field_id ) {
return $field_id != $target_field_id;
} ) );
}
public function ajax_refresh() {
// Object can be instantiated multiple times. Only listen for this specific configuration's hash.
if ( rgpost( 'hash' ) !== $this->_args['hash'] ) {
/**
* Return out if the hash doesn't match. If we exit here, other ajax_refresh() calls in other instances
* won't have the chance to run.
*/
return;
}
$entry = GFFormsModel::get_current_lead();
if( ! $entry ) {
wp_send_json_error();
}
$form = gf_apply_filters( array( 'gform_pre_render', $entry['form_id'] ), GFAPI::get_form( $entry['form_id'] ), false, array() );
$field = GFFormsModel::get_field( $form, $this->get_target_field_id( $form, $this->_args['field_ids'] ) );
if( $field->get_input_type() == 'html' ) {
$content = GWPreviewConfirmation::preview_replace_variables( $field->content, $form );
} else {
$value = rgpost( 'input_' . $field->id );
$content = $field->get_field_content( $value, true, $form );
$content = str_replace( '{FIELD}', $field->get_field_input( $form, $value, $entry ), $content );
}
wp_send_json_success( $content );
}
public static function output_script() {
?>
<script type="text/javascript">
( function( $ ) {
window.GPLCFieldGroup = function( args ) {
var self = this;
// copy all args to current object: (list expected props)
for( prop in args ) {
if( args.hasOwnProperty( prop ) )
self[prop] = args[prop];
}
self.init = function() {
self.$form = $( '#gform_wrapper_{0}'.format( self.formId ) );
self.$targetField = $( '#field_{0}_{1}'.format( self.formId, self.targetFieldId ) );
self.$triggerFields = false;
for( var i = 0; i < self.triggerFieldIds.length; i++ ) {
var $field = $( '#field_{0}_{1}'.format( self.formId, self.triggerFieldIds[ i ] ) );
if ( ! self.$triggerFields ) {
self.$triggerFields = $().add( $field );
}
// @todo Test with multiple triggers.
else {
self.$triggerFields.add( $field );
}
}
self.$triggerFields.on( 'change', function() {
self.refresh();
} );
self.refresh();
};
self.refresh = function() {
if( ! self.$targetField.is( ':visible' ) ) {
return;
}
var data = {
action: 'gplcfg_refresh_field',
hash: self.hash
};
self.$form.find( 'input, select, textarea' ).each( function() {
if ( this.type === 'radio' ) {
if ( this.checked ) {
data[ $( this ).attr( 'name' ) ] = $( this ).val();
}
} else {
data[ $( this ).attr( 'name' ) ] = $( this ).val();
}
} );
// Prevent AJAX-enabled forms from intercepting our AJAX request.
delete data['gform_ajax'];
$.post( self.ajaxUrl, data, function( response ) {
if( response.success ) {
self.$targetField.html( response.data );
}
} );
};
self.init();
}
} )( jQuery );
</script>
<?php
}
}
# Configuration
new GP_Limit_Choices_Field_Group( array(
'form_id' => 1485,
'field_ids' => array( 1, 2 )
) );
view raw gp-limit-choices-field-groups.php hosted with ❤ by GitHub

Using the snippet is simple. Paste it into your theme’s functions.php file and edit these lines at the bottom.

new GP_Limit_Choices_Field_Group( array(
'form_id' => 12,
'field_ids' => array( 3, 4 )
) );

Change the form_id to your Form ID, and replace the numbers in the field_ids array to your Date and Drop Down fields.

That’s all there is to it. The Date and Drop Down fields are now grouped together.

Want to also limit which days can be selected in the datepicker? Gravity Forms Limit Dates has you covered.

Wrapping Up

You should now have a form that functions similarly to the demo, and you can start taking orders for pickup at your store with confidence, all without having to invest the time, money, and energy into a robust eCommerce solution.

Plus with a Gravity Forms solution, you can configure the form per your specific requirements instead of trying to cram them into an existing eCommerce solution that isn’t well suited to your unique business.

Taking It Further

Show Number of Spots Left

To show the number of spots left like we have in the demo, use the snippet below. It will automatically update the number of spots left per day.

<?php
/**
* Display how many spots are left in the choice label when using the GP Limit Choices perk
* http://gravitywiz.com/gravity-perks/
*/
add_filter( 'gplc_remove_choices', '__return_false' );
add_filter( 'gplc_pre_render_choice', 'my_add_how_many_left_message', 10, 5 );
function my_add_how_many_left_message( $choice, $exceeded_limit, $field, $form, $count ) {
$limit = rgar( $choice, 'limit' );
$how_many_left = max( $limit - $count, 0 );
$message = "($how_many_left spots left)";
$choice['text'] = $choice['text'] . " $message";
return $choice;
}
view raw gp-limit-choices-spots-left.php hosted with ❤ by GitHub

Never installed a snippet before? Here’s some helpful tips.

Did this resource help you do something awesome with Gravity Forms? Then you'll absolutely love Gravity Perks; a suite of 32+ essential add-ons for Gravity Forms with support you can count on.

  • View All Perks
  • Buy Gravity Perks

Filed Under: How To date field gp limit choices schedule snippet

Comments

  1. Joshua Jenkins says

    June 24, 2020 at 10:26 am

    I’m hoping to use this field group snippet to manage spots, however, I need to have multiple sets of field groups on the same form. Here’s my desired configuration: I have a location field and then a series of fields that list date and time slots as dropdowns.

    new GP_Limit_Choices_Field_Group( array( ‘form_id’ => 10, ‘field_ids’ => array( 7, 30, 31, 32, 33, 34, 35 ) ) );

    BUT I need an array of arrays where field 7 (the location) is paired with the conditionally displayed date/time dropdown field (30 – 35). So really, it’s (7, 30) (7, 31) and so on – as only one date/time dropdown will appear at a time based on logic.

    Is this possible? As always, Gravity Wiz is amazing!

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Categories

  • How To (63)
  • News (21)
  • Plugins (14)
  • Releases (7)
  • Resource (3)
  • Snippets (58)
  • Tutorials (57)
  • Updates (104)

Recent Posts

  • How to Update Posts with Gravity Forms
  • Gravity Wiz Weekly #104
  • The Complete Guide to Using Gravity Forms With Zapier
  • Gravity Wiz Weekly #103
  • Show Active Forms by Default on Form List

Meta

  • Log in
  • Entries feed
  • Comments feed
  • WordPress.org

Copyright © 2021 · Powered by WordPress · Gravity Wiz LLC

  • Support
  • Affiliates
  • About
  • Sitemap
  • Gravity Perks
    ▼
    • Gravity Perks
    • Tutorials & Snippets
    • About
  • Support
    ▼
    • Documentation
    • Support
    • Account