Gravity Forms Package - Help needed

Since we, and our clients, really would like to see Gravity Forms work with Frontity I’ve decided to take a shot at it.

At the moment I’ve hacked together a simple implementation inside a theme which directly uses the REST API of Gravity Forms (/wp-json/gf/v2/) to get forms through a custom handler and send back submissions directly with fetch. However this is not very developer friendly :wink:
And since I’m far from a skilled React developer to begin with it would be great to get some support and feedback on my journey to create a package which (of course) will be released to te public as soon as possible.

I did noticed something in the CF7 package (which I used for reference), which is the usage of processors instead of actually using the REST API (no idea if that exist for CF7).
So I was wondering if this would be preferred, or actually retrieving all the data from the REST API (after getting the form ID) would make more sense. To me the latter makes more sense, since it will allow both handling the form embedded in the content (get ID from HTML with processor) and manually placing a form with any ID in React without putting it in a page/post first.

Another thing I was wondering about is on how to make a package have it’s own components (since a lot HTML needs to be rendered), but give the developer implementing it the freedom to override them with their own structure and styling (without breaking the logic behind it).
As a PHP developer I’m used to hooking into existing code to modify (like WP filters) or override it
(dependency injection), and I haven’t figured out (yet) how to do that in Frontity/React, so if someone has an idea or two about this I’ll be very happy.

TL;DR

  • I have a hacky, but working, implementation of Gravity Forms in Frontity, but will need some help and feedback to make it into a working package for Frontity.
  • Which method of handling the form would be preferred? Processors to modify the forms rendered by WP, or a handler to retrieve the raw data from the REST API and ignore whatever WP does?
  • Is it possible to build a package in such a way that the user can inject their own components (theme config) which override the components from the package?

Oh, and lastly, is there a decent guide on how to build packages? Because themes are relatively easy, but packages seem to be slightly more complicated and the amount of Frontity packages out there are still limited, so hard to use existing code to figure out stuff.

1 Like

Hi,

I actually started on this as well, but got stuck on authentication. Do you have a repo I can look at to see how you did that? :slight_smile:

I don’t have a repo but I can share the hacky code :blush:

Authentication works by enabling the REST API of Gravity Forms and getting the v2 API keys, which I’ve set in state.source.gfAuth.key and state.source.gfAuth.secret.

Retrieving Form

import { Buffer } from 'buffer';

const formHandler = {
    name: "form",
    priority: 10,
    pattern: "@form/:id",
    func: async ({ link, params, state, libraries }) => {
      const { id } = params;

      // Fetch the menu data from the endpoint
      const response = await libraries.source.api.get({
        endpoint: `/gf/v2/forms/${id}`,
        auth: 'Basic ' + (Buffer.from(state.source.gfAuth.key + ':' + state.source.gfAuth.secret).toString('base64')),
      });
  
      // Parse the JSON to get the object
      const formData = await response.json();

      // Add the form data to source.data
      const form = state.source.data[link];
      Object.assign(form, {
          data: formData
      });
    },
  };
  
  export default formHandler;

Submitting Data (snippet)

// Form ID
const formId = 1;

const onSubmit = async (event) => {
        event.preventDefault();

        // Take the submitted data and build a FormData object, which is required by GF
        let data = {};
        const formData = new FormData(event.target);
        for (var key of formData.keys()) {
            console.log(key, formData.get(key));
            data[key] = formData.get(key);
        }

        // Return if a submission is pending.
        if (state.forms.submit[formId]?.isSubmitting) {
            //return warn(
            //    "You cannot submit a comment to the same post if another is already pending."
            //);
        }

        let responseData;

        let form = state.forms.submit[formId];
        // Create it if doesn't exist yet.
        if (!form) {
            form = state.forms.submit[formId] = {
                isSubmitting: false,
                isSubmitted: false,
                isError: false,
                errorMessage: "",
                errorCode: "",
                errorStatusCode: null,
            };
        }

        form.isError = false;
        form.errorMessage = "";
        form.errorCode = "";
        form.isSubmitting = true;
        form.isSubmitted = false;
        form.errorStatusCode = null;

        // Create a custom URL to the GF REST API
        const formPost = state.source.api.replace(/\/$/, "") + '/gf/v2/forms/' + formId + '/submissions/';

        // POST the data to the REST API, and handle the response
        fetch(formPost, {
            method: 'POST',
            mode: 'no-cors',
            referrerPolicy: 'no-referrer',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Basic ' + (Buffer.from(state.source.gfAuth.key + ':' + state.source.gfAuth.secret).toString('base64')),
            },
            body: JSON.stringify(data),
        }).then(response => {
            if (response.status === 201) {
                // Parse the JSON to get the object
                responseData = response.json();
                
                // Add the form data to source.data
                // NOTE: '@form/' is a custom handler I use for storing forms
                const formSource = state.source.data['@forms/' + formId + '/submit/'];
    
                if (formSource) {
                    Object.assign(formSource, {
                        isFetching: false,
                        isReady: false,
                        type: "form-submit",
                    });
        
                    Object.assign(formSource, {
                        data: responseData,
                        form: form
                    });
        
                    // Explicitly mark the data as ready
                    Object.assign(formSource, {
                        isFetching: false,
                        isReady: true,
                    });
                }
    
                // Reset the form status
                form.isSubmitting = false;
                form.isSubmitted = true;
    
                return response;
            } else
            // Handle 4xx errors
            if (response.status >= 400 && response.status < 500) {
                errorBody = response.json();
        
                form.isSubmitting = false;
                form.isError = true;
                form.errorMessage = errorBody.message;
                form.errorCode = errorBody.code;
                form.errorStatusCode = response.status;

                return response;
            }
            return Promise.reject();
        }).catch((error) => {
            // Network error.
            form.isSubmitting = false;
            form.isError = true;
            form.errorMessage = error.message;

            return;
        });

As you can see, authentication is nothing more than sending an extra header with the API keys (also took me some time to figure out).
The rest of the code that I have is big mess and completely build around my project, so not reusable within a package.

Hi @Johan,

Because of you, I now have a working package, thanks!!! :smiley: Just got it to work, but I haven’t looked at validation yet. But you can see it in action here:

repo:

Hello again, I now have a working package with validation. :smiley:

3 Likes

Hey guys, this is awesome! :grinning_face_with_smiling_eyes::clap:

1 Like

Thank you guys for trying to tackle this, one thing I wanted to check on is if there is concern for the fact this is technically exposing your API keys when you can open console and run

frontity.state.source.gfAuth

That is a great point. Could that be prevented with env variables? Need to test it out.

@luisherranz possible to hide things from the console on live sites?

1 Like

@kasper Thanks again for this super valuable package. Could you provide some direction on handling “Required” fields?

Thanks again for your package @kasper! I have cloned your mars theme example, and have a version of my site working with that. I’m trying to get the package running on the twenty-twenty theme, however when I try to submit a response on my form I run into the following error. Is there an easy fix for this?

index.js?ffbf:103 
Uncaught (in promise) 
TypeError: Cannot read properties of undefined (reading 'cors')
    at eval (index.js?ffbf:103:51)
    at Object.eval [as sendForm] (create-store.js?81b0:47:20)
    at handleOnSubmit (Form.js?bc55:31:20)
    at HTMLUnknownElement.callCallback (react-dom.development.js?61bb:3945:14)
    at Object.invokeGuardedCallbackDev (react-dom.development.js?61bb:3994:16)
    at invokeGuardedCallback (react-dom.development.js?61bb:4056:31)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js?61bb:4070:25)
    at executeDispatch (react-dom.development.js?61bb:8243:3)
    at processDispatchQueueItemsInOrder (react-dom.development.js?61bb:8275:7)
    at processDispatchQueue (react-dom.development.js?61bb:8288:5)

Ok, got it working with the twenty twenty theme. Essentially I stripped the code out of the plugin and added to the Frontity Theme directly in the state, actions and library. Thanks again for your plugin!