Answered

How to write your own frontend for the Form Builder Module

The Form Builder module that comes included in Agility CMS instances allows editors to create and manage their forms without needing a developer.

In the ASP.NET Sync model integration, the form rendering and submission is handled for you automatically (vis this JS file) - although it does take some setup by a developer initially to ensure they have all the appropriate dependencies set up.

For sites that are using the Fetch API or the Gatsby Source Plugin , in a headless fashion, you'll need to write your own frontend logic to handle the form rendering and submission.

What data is returned by the Module?

The module data (from the API) will look like:

{
"textblob": "eyJ2YWx1ZXMiOnsiVGl0bGUiOiIiLCJSZWNvcmRUeXBlSUQiOjEyLCJTdWJtaXRCdXR0b25MYWJlbCI6IlN1Ym1pdCIsIlN1Y2Nlc3NDb3B5IjoiVGhhbmsgeW91ISIsIkVycm9yQ29weSI6IlNvcnJ5LCBzb21ldGhpbmcgd2VudCB3cm9uZyB3aXRoIHlvdXIgc3VibWlzc2lvbiIsIldlYkhvb2siOiIiLCJBZGRpdGlvbmFsSlNPTiI6Ilt7XCJEYXRhVHlwZVwiOjAsXCJOYW1lXCI6XCJBcnRpc3ROYW1lXCIsXCJEZXNjcmlwdGlvblwiOm51bGwsXCJQbGFjZWhvbGRlclRleHRcIjpcIkFydGlzdCBOYW1lXCIsXCJDb2x1bW5XaWR0aFwiOlwiZnVsbFwifSx7XCJEYXRhVHlwZVwiOjAsXCJOYW1lXCI6XCJOYW1lb2ZQaWVjZVwiLFwiRGVzY3JpcHRpb25cIjpudWxs...",
"recordTypeID": "12",
"submitButtonLabel": "Submit",
"successCopy": "Thank you!",
"errorCopy": "Sorry, something went wrong with your submission",
"additionalJSON": "[{\"DataType\":0,\"Name\":\"ArtistName\",\"Description\":null,\"PlaceholderText\":\"Artist Name\",\"ColumnWidth\":\"full\"},{\"DataType\":0,\"Name\":\"NameofPiece\",\"Description\":null,\"PlaceholderText\":\"Name of Piece\",\"ColumnWidth\":\"full\"},{\"DataType\":1,\"Name\":\"DescriptionofWork\",\"Description\":null,\"PlaceholderText\":\"Description of Work\",\"ColumnWidth\":\"full\"},{\"DataType\":0,\"Name\":\"EmailAddress\",\"Description\":null,\"PlaceholderText\":\"Email Address\",\"ColumnWidth\":\"full\"},{\"DataType\":5,\"Name\":\"Uploadimagesofwork\",\"Descri...",
"codeFieldsJSON": "[]",
"emailBodyTemplate": "<table><tr><td>Artist Name</td><td>##ArtistName##</td></tr><tr><td>Email Address</td><td>##EmailAddress##</td></tr><tr><td>Name of Piece</td><td>##NameofPiece##</td></tr><tr><td>Description of Work</td><td>##DescriptionofWork##</td></tr><tr><td>Upload images of work </td><td>##Uploadimagesofwork##</td></tr><tr><td>I give permission to feature this work in a virtual version of this exhibition</td><td>##Igivepermissiontofeaturethisworkinavirtualversionofthisexhibitio##</td></tr></table><p>The subm...",
"themeID": "0",
"state": 2,
"modified": "2020-03-31T11:01:00.437",
"versionID": 15550,
"referenceName": "artsubmissions_formbuilder831",
"definitionName": "AgilityFormBuilder",
"itemOrder": 0
}

Of particular importance here is the `textblob` property. This is a base64 encoded string. When decoded, this looks like:

{
  "values": {
    "Title": "",
    "RecordTypeID": 12,
    "SubmitButtonLabel": "Submit",
    "SuccessCopy": "Thank you!",
    "ErrorCopy": "Sorry, something went wrong with your submission",
    "WebHook": "",
    "AdditionalJSON": "[{\"DataType\":0,\"Name\":\"ArtistName\",\"Description\":null,\"PlaceholderText\":\"Artist Name\",\"ColumnWidth\":\"full\"},{\"DataType\":0,\"Name\":\"NameofPiece\",\"Description\":null,\"PlaceholderText\":\"Name of Piece\",\"ColumnWidth\":\"full\"},{\"DataType\":1,\"Name\":\"DescriptionofWork\",\"Description\":null,\"PlaceholderText\":\"Description of Work\",\"ColumnWidth\":\"full\"},{\"DataType\":0,\"Name\":\"EmailAddress\",\"Description\":null,\"PlaceholderText\":\"Email Address\",\"ColumnWidth\":\"full\"},{\"DataType\":5,\"Name\":\"Uploadimagesofwork\",\"Description\":null,\"PlaceholderText\":null,\"ColumnWidth\":\"full\"},{\"DataType\":9,\"Name\":\"Igivepermissiontofeaturethisworkinavirtualversionofthisexhibitio\",\"Description\":null,\"PlaceholderText\":null,\"ColumnWidth\":\"full\"}]",
    "CodeFieldsJSON": "[]",
    "EmailTo": "",
    "EmailFrom": "",
    "EmailSubjectTemplate": "",
    "EmailBodyTemplate": "<table><tr><td>Artist Name</td><td>##ArtistName##</td></tr><tr><td>Email Address</td><td>##EmailAddress##</td></tr><tr><td>Name of Piece</td><td>##NameofPiece##</td></tr><tr><td>Description of Work</td><td>##DescriptionofWork##</td></tr><tr><td>Upload images of work </td><td>##Uploadimagesofwork##</td></tr><tr><td>I give permission to feature this work in a virtual version of this exhibition</td><td>##Igivepermissiontofeaturethisworkinavirtualversionofthisexhibitio##</td></tr></table><p>The submission was received at ##CreatedOn##",
    "ThemeID": "0",
    "InputHeight": "",
    "InputBackgroundColor": "",
    "InputFontSize": "",
    "InputFontColor": "",
    "InputMarginTop": "",
    "InputMarginBottom": "",
    "InputOutline": "",
    "InputBorder": "",
    "InputFocusedBorder": "",
    "InputBorderRadius": "",
    "InputPadding": "",
    "FormFontFamily": "",
    "FormBackgroundColor": "",
    "FormPadding": "",
    "FormMargin": "",
    "FormBorder": "",
    "FormBorderRadius": "",
    "ButtonBackgroundColor": "",
    "ButtonFontColor": "",
    "ButtonFontSize": "",
    "ButtonFontWeight": "",
    "ButtonHeight": "",
    "ButtonWidth": "",
    "ButtonMargin": "",
    "ButtonPadding": "",
    "ButtonBorder": "",
    "ButtonBorderRadius": "",
    "LabelFontSize": "",
    "LabelFontColor": "",
    "LabelFontWeight": "",
    "LabelMargin": "",
    "LabelPadding": ""
  },
  "contentID": 1357,
  "languageCode": "en-us",
  "fieldTypes": [
    {
      "AdditionalMetaData": null,
      "AllowNull": true,
      "DataType": 0,
      "DefaultValue": null,
      "FieldOptions": [],
      "FileServiceMetaData": {
        "FieldName": null,
        "FieldValue": null,
        "FileService": 0,
        "FileServiceState": 0,
        "ID": 0,
        "OriginalFilePath": null,
        "RecordID": 0
      },
      "FilterIllegalWords": true,
      "ID": 58,
      "IncludeInGrid": true,
      "IsAlertField": false,
      "IsEmailField": false,
      "IsHiddenField": false,
      "IsLoginField": false,
      "IsUnique": false,
      "Label": "Artist Name",
      "MaxLength": 0,
      "Name": "ArtistName",
      "SortOrder": 0,
      "SyndicateValueToService": null,
      "SyndicatedServiceValueID": null,
      "ValidationMessage": null,
      "ValidationRegEx": null,
      "IconClass": "icon-a-text-format",
      "Expanded": false,
      "Required": false,
      "ColumnWidth": "full",
      "subscriptions": {
        "ColumnWidth": {
          "_target": "full",
          "isDisposed": false
        }
      },
      "JSONFieldValues": {
        "DataType": 0,
        "Name": "ArtistName",
        "Description": null,
        "PlaceholderText": "Artist Name",
        "ColumnWidth": "full"
      },
      "DropdownOptions": [],
      "DropdownOptionsInitialized": false,
      "HasOptions": false,
      "HasEmptyOption": false,
      "ColumnWidthOptions": [
        {
          "Text": "1/4",
          "Value": "quarter"
        },
        {
          "Text": "1/3",
          "Value": "third"
        },
        {
          "Text": "1/2",
          "Value": "half"
        },
        {
          "Text": "1",
          "Value": "full"
        }
      ],
      "UniqueID": "field-type-42",
      "JSONFields": {
        "DataType": 0,
        "Name": "ArtistName",
        "Description": null,
        "PlaceholderText": "Artist Name",
        "ColumnWidth": "full"
      }
    },
    {
      "AdditionalMetaData": null,
      "AllowNull": false,
      "DataType": 0,
      "DefaultValue": null,
      "FieldOptions": [],
      "FileServiceMetaData": {
        "FieldName": null,
        "FieldValue": null,
        "FileService": 0,
        "FileServiceState": 0,
        "ID": 0,
        "OriginalFilePath": null,
        "RecordID": 0
      },
      "FilterIllegalWords": true,
      "ID": 59,
      "IncludeInGrid": true,
      "IsAlertField": false,
      "IsEmailField": false,
      "IsHiddenField": false,
      "IsLoginField": false,
      "IsUnique": false,
      "Label": "Email Address",
      "MaxLength": 0,

      "Name": "EmailAddress",
      "SortOrder": 1,
      "SyndicateValueToService": null,
      "SyndicatedServiceValueID": null,
      "ValidationMessage": null,
      "ValidationRegEx": null,
      "IconClass": "icon-a-text-format",
      "Expanded": false,
      "Required": true,
      "ColumnWidth": "full",
      "subscriptions": {
        "ColumnWidth": {
          "_target": "full",
          "isDisposed": false
        }
      },
      "JSONFieldValues": {
        "DataType": 0,
        "Name": "EmailAddress",
        "Description": null,
        "PlaceholderText": "Email Address",
        "ColumnWidth": "full"
      },
      "DropdownOptions": [],
      "DropdownOptionsInitialized": false,
      "HasOptions": false,
      "HasEmptyOption": false,
      "ColumnWidthOptions": [
        {
          "Text": "1/4",
          "Value": "quarter"
        },
        {
          "Text": "1/3",
          "Value": "third"
        },
        {
          "Text": "1/2",
          "Value": "half"
        },
        {
          "Text": "1",
          "Value": "full"
        }
      ],
      "UniqueID": "field-type-43",
      "JSONFields": {
        "DataType": 0,
        "Name": "EmailAddress",
        "Description": null,
        "PlaceholderText": "Email Address",
        "ColumnWidth": "full"
      }
    },
    {
      "AdditionalMetaData": null,
      "AllowNull": true,
      "DataType": 0,
      "DefaultValue": null,
      "FieldOptions": [],
      "FileServiceMetaData": {
        "FieldName": null,
        "FieldValue": null,
        "FileService": 0,
        "FileServiceState": 0,
        "ID": 0,
        "OriginalFilePath": null,
        "RecordID": 0
      },
      "FilterIllegalWords": true,
      "ID": 60,
      "IncludeInGrid": true,
      "IsAlertField": false,
      "IsEmailField": false,
      "IsHiddenField": false,
      "IsLoginField": false,
      "IsUnique": false,
      "Label": "Name of Piece",
      "MaxLength": 0,
      "Name": "NameofPiece",
      "SortOrder": 2,
      "SyndicateValueToService": null,
      "SyndicatedServiceValueID": null,
      "ValidationMessage": null,
      "ValidationRegEx": null,
      "IconClass": "icon-a-text-format",
      "Expanded": false,
      "Required": false,
      "ColumnWidth": "full",
      "subscriptions": {
        "ColumnWidth": {
          "_target": "full",
          "isDisposed": false
        }
      },
      "JSONFieldValues": {
        "DataType": 0,
        "Name": "NameofPiece",
        "Description": null,
        "PlaceholderText": "Name of Piece",
        "ColumnWidth": "full"
      },
      "DropdownOptions": [],
      "DropdownOptionsInitialized": false,
      "HasOptions": false,
      "HasEmptyOption": false,
      "ColumnWidthOptions": [
        {
          "Text": "1/4",
          "Value": "quarter"
        },
        {
          "Text": "1/3",
          "Value": "third"
        },
        {
          "Text": "1/2",
          "Value": "half"
        },
        {
          "Text": "1",
          "Value": "full"
        }
      ],
      "UniqueID": "field-type-44",
      "JSONFields": {
        "DataType": 0,
        "Name": "NameofPiece",
        "Description": null,
        "PlaceholderText": "Name of Piece",
        "ColumnWidth": "full"
      }
    },
    {
      "AdditionalMetaData": null,
      "AllowNull": true,
      "DataType": 1,
      "DefaultValue": null,
      "FieldOptions": [],
      "FileServiceMetaData": {
        "FieldName": null,
        "FieldValue": null,
        "FileService": 0,
        "FileServiceState": 0,
        "ID": 0,
        "OriginalFilePath": null,
        "RecordID": 0
      },
      "FilterIllegalWords": true,
      "ID": 61,
      "IncludeInGrid": true,
      "IsAlertField": false,
      "IsEmailField": false,
      "IsHiddenField": false,
      "IsLoginField": false,
      "IsUnique": false,
      "Label": "Description of Work",
      "MaxLength": 0,
      "Name": "DescriptionofWork",
      "SortOrder": 3,
      "SyndicateValueToService": null,
      "SyndicatedServiceValueID": null,
      "ValidationMessage": null,
      "ValidationRegEx": null,
      "IconClass": "icon-a-document",
      "Expanded": false,
      "Required": false,
      "ColumnWidth": "full",
      "subscriptions": {
        "ColumnWidth": {
          "_target": "full",
          "isDisposed": false
        }
      },
      "JSONFieldValues": {
        "DataType": 1,
        "Name": "DescriptionofWork",
        "Description": null,
        "PlaceholderText": "Description of Work",
        "ColumnWidth": "full"
      },
      "DropdownOptions": [],
      "DropdownOptionsInitialized": false,
      "HasOptions": false,
      "HasEmptyOption": false,
      "ColumnWidthOptions": [
        {

          "Text": "1/4",
          "Value": "quarter"
        },
        {
          "Text": "1/3",
          "Value": "third"
        },
        {
          "Text": "1/2",
          "Value": "half"
        },
        {
          "Text": "1",
          "Value": "full"
        }
      ],
      "UniqueID": "field-type-45",
      "JSONFields": {
        "DataType": 1,
        "Name": "DescriptionofWork",
        "Description": null,
        "PlaceholderText": "Description of Work",
        "ColumnWidth": "full"
      }
    },
...

  ],

  "uniqueID": "agility-form-cms-container-5"

}
 

What you need to do

Now that you have all of your field data, you can begin to start parsing the JSON into an array of fields to render. Then, you can wire-up some JavaScript to handle the form submission.

  1. Render the appropriate form field types
  2. Handle appropriate field validations
  3. //set up UGC authentication
    var
     _AgilityUGCSettings = { 


        'Url': 'https://ugc.agilitycms.com/Agility-UGC-API-JSONP.svc',
        'AccessKey': '553901D2-F5E1-4BBA-B346-159xxxxxxx', //the website API Access Key provided to you
        'Seconds': '567353588', //is the number of seconds that have elapsed since Jan 1/2001.
        'RandomNumber': '205', //just a random number between 1-1000
        'ProfileRecordID': '-1', //the profile record ID of the logged-in website user, -1 is anonymous
        'AccessHash': '7c690bdac92defff3a676e24ded04c5xxxxxxx' //The SHA hash of all the above variables
                                                               //(Seconds.ProfileID.SecretKey.AccessKey.Random)
    };
  4. Hook into the onSubmit method of the form and save the form to UGC using:
    let data = 

        "accessKey": "553901D2-F5E1-4BBA-B346-159xxxxxxx",
        "seconds": "567353588",
        "randomNumber": "205",
        "profileRecordI": "-1", 
        "accessHash": "7c690bdac92defff3a676e24ded04c5xxxxxxx"
    };
    const postData = 
    {
            "ContentID": 1357,
            "LangugeCode": "en-us",
            "Mode": 2,
            "RecordTypeID": 12,
            "Values": {
                "FirstName": "John",
                "LastName": "Doe",
                 ....
            }
    }
    data.postData = JSON.stringify(postData)
    //POST to https://ugc.agilitycms.com/ugc-api/SaveFormPost

    If you want to see how it is currently done using the .NET integration, here is the JavaScript that is used. This is the best example we have right now.

  5. Test to ensure the submission was successful and that your email and/or webhook are still appropriately fired. The email and webhook is handled in the UGC endpoint that you are posting to.

    if you get a CORS error, contact support@agilitycms.com and notify them of your domain - it may need to be whitelisted.

Got a solution working? We'd love to hear about it and share it with the community!

 

1

Comments

8 comments
  • This looks promising! It looks like the FormBuilder.js from the .Net site uses jQuery, so I will have to see if I can refactor it without that dependency.

    0
    Comment actions Permalink
  • Yes, I would recommend NOT using jQuery. There really isn't any need for it 😉. Better to simplify and not have any other external dependencies. For UGC authentication, there is one area you might have trouble with and that is building the AccessHash key. Typically this is done on the server-side, so you'll want to make sure you do this ONLY during build time in gatsby.

    I'll see if we can find a way to do this in JS.

    0
    Comment actions Permalink
  • Yes, anything you can do to help me figure this out would be greatly appreciated. Especially when it involves things other than rendering a component to the page.

    0
    Comment actions Permalink
  • One of our partners confirmed they have some code they can share around generating markup as well generating a UGC access key. They were a little tied up this afternoon, but said they can share the code soon. I'll see if we can get this for tomorrow for you.

    0
    Comment actions Permalink
  • That would be fantastic, thanks!

     

    0
    Comment actions Permalink
  • Alright, here is how you can generate the UGC Access Key in JS. This should be done in node only, not on the client-side (for security reasons).

    import sha1 from 'sha1';
    const UGCCREDENTIALS = {
    key: "XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX",
    pass: "XXXXXX-XXXX-XXXX-XXXXX-XXXXXXXXX"
    }
    const generateUGSettings = (UGCCREDENTIALS) => {
    // SHA 1 of Seconds.ProfileID.SecretKey.AccessKey.Random
    const jan12001 = new Date("Jan 1 2001");
    const now = new Date();
    const msecondsSinceJan12001 = now.getTime() - jan12001.getTime();
    const secondsSinceJan12001 = Math.round(msecondsSinceJan12001 / 1000);
    const random = Math.floor((Math.random() * 1000));
    return {
    'Url': 'https://ugc.agilitycms.com/Agility-UGC-API-JSONP.svc',
    'AccessKey': UGCCREDENTIALS.key, //the website API Access Key provided to you
    'Seconds': secondsSinceJan12001, //is the number of seconds that have elapsed since Jan 1/2001.
    'RandomNumber': random, //just a random number between 1-1000
    'ProfileRecordID': '-1', //the profile record ID of the logged-in website user, -1 is anonymous
    'AccessHash': sha1(`${secondsSinceJan12001}.-1.${UGCCREDENTIALS.pass}.${UGCCREDENTIALS.key}.${random}`) //The SHA hash of all the above variables (Seconds.ProfileID.SecretKey.AccessKey.Random)
    };
    }

     

     

    0
    Comment actions Permalink
  • How would I use this in a static site like Gatsby? Would I create new environment variables like I do for the normal Agility information? And put those in the hosting sites?

    0
    Comment actions Permalink
  • Yep! You would set them as environment variables and then read them during the build process to compute the hash and pass that to to your page.

    0
    Comment actions Permalink

Please sign in to leave a comment.

Didn't find what you were looking for?

New post