Code Pages - AddEditRecordForm.html

This is a 100% fully dynamic HTML record form. Simply pass in the Table ID, Record ID (if viewing/updating a record), and a list of Field IDs to display on the form.

Once opened the page queries the specified record/table to get the field properties and record values. Once queried the form is dynamically generated based on the fields object in the response

Verified compatibility with the following field types: text, rich text, multi-line text, numeric, date, date/time, time of day, checkbox, phone and email
 

*This leverages EOTI by not passing in a Temp Token. In a production environment you should use Temporary Tokens. 
**There's inline comments throughout the code page which provide instructions on how to convert this page to take advantage of Temp Tokens.
Also see GetTempToken.html for an additional example of how to get Temp Tokens.

// 2 Examples below. When copying into a formula field, remove/uncomment one of the examples

// Example 1
URLRoot() & "db/" & AppID() & "?a=dbpage&pageid=14"  // Open code page 14
& "&dbid=" & Dbid()  // Pass is the Table ID
& "&rid=" & [Record ID#]  // Pass in the Record ID to update an existing record. Remove this to add a new record
& "&fids=6.7.8.19.9"  // Pass in the Field IDs of the fields that should appear on the form. The order of this list determines the field order on the form
& "&apptoken=" & [App Token]  // Pass in the App Token


// Example 2
URLRoot() & "db/" & AppID() & "?a=dbpage&pageid=14"  // Open code page 14
& "&dbid=" & [_DBID_CHILDREN]  // Pass is the Table ID
& "&fids=6"  // Pass in the Field IDs of the fields that should appear on the form. The order of this list determines the field order on the form
& "&refID=7"  // Field ID of the Reference field
& "&refVal=" & [Record ID#]  // Record ID of the parent record
& "&embed=1"  // Removes help text
AddEditRecordForm.html
14
<!DOCTYPE HTML>
<html lang="en">
<head>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <script src = 'https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js'> </script>
    <style>
        .loader {
            border: 16px solid #f3f3f3;
            border-radius: 50%;
            border-top: 16px solid #3498db;
            width: 120px;
            height: 120px;
            -webkit-animation: spin 2s linear infinite; /* Safari */
            animation: spin 2s linear infinite;
            display: block;
            margin-left: auto;
            margin-right: auto;
        }

        /* Safari */
        @-webkit-keyframes spin {
            0% { -webkit-transform: rotate(0deg); }
            100% { -webkit-transform: rotate(360deg); }
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
    <script>
        function buildForm() {
            const urlParams = new URLSearchParams(window.location.search);
            const dbid = urlParams.get('dbid');
            let rid = urlParams.get('rid');
            const fids = urlParams.get('fids');
            const embed = urlParams.get('embed');
            const appToken = urlParams.get('apptoken');
            const realm = window.location.hostname.substring(0, window.location.hostname.indexOf("."));
            const headers = new Map;

            // Alert to missing parameters and go back to previous page
            if (!dbid || !fids) {
                alert(`Missing either the DBID or FIDs parameter.\nYou'll now be redirected.`);
                errRdr();
            }

            if (embed) { // Embed within record to add child records
                document.getElementById("test").textContent = 'Add Child';
                $('.helpText').hide();
            } else if (!rid) { // Add a new record
                rid = 0;
                document.getElementById("test").textContent = 'Add Record';
                $('#instructions').show();
            } else { // Update an existing record
                document.getElementById("test").textContent = `Edit Record # ${rid}`;
            }

            // Temporary Auth doesn't work with EOTI since the user isn't logged into the realm. For this reason the getTempAuth() call is commented out.
            // For production use cases, delete the EOTI Auth section and uncomment the Temp Auth section immediately below that
            // Begin EOTI Auth
            headers.set(dbid, {
                'QB-Realm-Hostname': realm,
                'userAgent': 'QB APIGateway',
                'Authorization': `QB-TEMP-TOKEN ` // EOTI access doesn't require a token
            });
            query();
            // End EOTI Auth

            // Begin Temp Auth
            // getTempAuth(realm, dbid, appToken).then((header) => {
            //     headers.set(dbid, header);
            //     query();
            // });
            // End Temp Auth

            function query() {
                const fields = fids.split('.');
                const params = {
                    "from": dbid,
                    "where": `{3.EX.${rid}}`,
                    "select": fields
                };
                $.ajax({
                    url: `https://api.quickbase.com/v1/records/query`,
                    method: 'POST',
                    headers: headers.get(dbid),
                    dataType: "json",
                    contentType: "application/json; charset=utf-8",
                    data: JSON.stringify(params),
                    success: function (response) {
                        // console.log(response);

                        // Generate Form Dynamically
                        $(document).ready(function () {
                            let wrapper = $("#inputForm");
                            for (let i = 0; i < response.fields.length; i++) {
                                const fid = response.fields[i].id;
                                const fLabel = response.fields[i].label;
                                const fType = response.fields[i].type;
                                let type, value = "", focus;
                                if (response.data[0]) {
                                    if (fType === "timestamp") {
                                        value = `value="${response.data[0][fid].value.slice(0, -1)}"`; // Removes the Z from the end of the returned timestamp
                                    } else if (fType === "checkbox" && response.data[0][fid].value === true) {
                                        value = `value="${response.data[0][fid].value}" checked`; // Marks checkbox as true if necessary
                                    } else {
                                        value = `value="${response.data[0][fid].value}"`;
                                    }
                                }
                                // Dynamically determine the type of input field
                                switch (fType) {
                                    case "date":
                                        type = "date"
                                        break;
                                    case "timestamp":
                                        type = "datetime-local"
                                        break;
                                    case "timeofday":
                                        type = "time"
                                        break;
                                    case "checkbox":
                                        type = "checkbox"
                                        break;
                                    case "phone":
                                        type = "tel"
                                        break;
                                    case "numeric":
                                        type = "number"
                                        break;
                                    default:
                                        type = "text"

                                }

                                if(i===0){
                                    focus = 'autofocus';
                                }
                                // This is the styling of input fields which are dynamically generated
                                const input = `<label for="${fid}">${fLabel}</label><input type="${type}" class="form-control" id="${fid}" ${value} ${focus}/><br>`;

                                $(wrapper).append(input);
                            }
                        });
                    }
                })
            }
        }
        function run(){

            $('#form').hide();
            $('#status').show();

            const urlParams = new URLSearchParams(window.location.search);
            const dbid = urlParams.get('dbid');
            let rid = urlParams.get('rid');
            const fids = urlParams.get('fids');
            const refID = urlParams.get('refID');
            const refVal = urlParams.get('refVal');
            const appToken = urlParams.get('apptoken');
            const realm = window.location.hostname.substring(0,window.location.hostname.indexOf("."));
            const fields = fids.split('.');
            const headers = new Map;

            // Temporary Auth doesn't work with EOTI since the user isn't logged into the realm. For this reason the getTempAuth() call is commented out.
            // For production use cases, delete the EOTI Auth section and uncomment the Temp Auth section immediately below that
            // Begin EOTI Auth
            headers.set(dbid, {
                'QB-Realm-Hostname': realm,
                'userAgent': 'QB APIGateway',
                'Authorization': `QB-TEMP-TOKEN ` // EOTI access doesn't require a token
            });
            save();
            // End EOTI Auth

            // Begin Temp Auth
            // getTempAuth(realm, dbid, appToken).then((header) => {
            //     headers.set(dbid, header);
            //     save();
            // });
            // End Temp Auth

            function save(){
                // Create the upsert payload based on the values within the input elements
                const payload = {
                    "to": dbid,
                    "mergeFieldId": 3,
                    "data": [
                        {}
                    ]
                };
                if(rid){
                    payload.data[0][3] = {"value": rid};
                }
                if(refID && refVal){
                    payload.data[0][refID] = {"value": refVal};
                }

                for(i=0; i<fields.length; i++){
                    const input = $(`#${fields[i]}`);
                    const type = input.attr('type');
                    let val;
                    let add = true;

                    if(type==="checkbox"){
                        val = input.prop('checked');
                    }else if(type==="number"){
                        if(input.val()) {
                            val = Number(input.val());
                        }else{
                            add = false;
                        }
                    }else if(type==="datetime-local" && input.val() && input.val().length === 16) {
                        val = `${input.val()}:00`
                    }else if(type==="time" && input.val() && input.val().length === 5) {
                        val = `${input.val()}:00`
                    }else{
                        val = input.val();
                    }

                    if(add) {
                        payload.data[0][fields[i]] = {"value": val};
                    }
                }
                jsonImport(payload, "Add Record");
            }

            function jsonImport(payload, udata) {
                return new Promise((resolve) =>{
                    // console.log(JSON.stringify(payload));
                    if(payload.data.length > 0) {

                        $.ajax({
                            url: 'https://api.quickbase.com/v1/records',
                            method: 'POST',
                            headers: headers.get(payload.to),
                            dataType: "json",
                            contentType: "application/json; charset=utf-8",
                            data: JSON.stringify(payload),
                            retryLimit: 10,
                            success: function (response) {
                                // console.log(JSON.stringify(response));
                                if(!rid){
                                    rid = response.metadata.createdRecordIds[0];
                                }
                                if (refID && refVal) {
                                    // console.log('refresh page');
                                    window.location.reload(true);
                                } else {
                                    // console.log('view record');
                                    window.location.href = `https://${realm}.quickbase.com/db/${dbid}?a=dr&rid=${rid}`;
                                }
                            },
                            error: function (error) {
                                alert(`An error occurred. Click OK to be redirected back to the previous page.`);
                                errRdr();
                            }
                        });
                    }
                });
            }

        }
        function getTempAuth(realm, dbid, appToken) {
            return new Promise(function(resolve) {
                $.ajax({
                    url: `https://api.quickbase.com/v1/auth/temporary/${dbid}`,
                    method: 'GET',
                    headers: {
                        'QB-Realm-Hostname': realm,
                        'userAgent': 'QB APIGateway',
                        'QB-App-Token': appToken
                    },
                    xhrFields: {
                        withCredentials: true
                    },
                    success: function (data) {
                        // console.log(`Getting token for import realm: ${dbid}`);
                        // console.log(data);
                        // headers.set(dbid, );
                        resolve({
                            'QB-Realm-Hostname': realm,
                            'userAgent': 'QB APIGateway',
                            'Authorization': `QB-TEMP-TOKEN ${data.temporaryAuthorization}`
                        });
                    }
                });
            })
        }
        function errRdr(){
            // Redirects to the previous page, if this page was the previous page as well, then redirect to the app home page
            if(document.referrer && document.referrer !== window.location.href) {
                window.location.href = document.referrer;
            }else{
                window.location.href = window.location.origin + window.location.pathname;
            }
        }
    </script>
    <title>Add, Edit, View Record</title>
</head>
<body onload="buildForm()">
    <div class="container" id="form">
        <h2 id="test"></h2>
        <h4 class="helpText">This form is dynamically generated based on the FIDs (Field IDs) passed in the URL. It also is able to handle both adds and edits by detecting if a RID (Record ID#, FID 3) is passed in the URL.  If no RID is provided, a new record will be created.</h4>
        <h5 class="helpText" style="color:red">For demo purposes data entered here will be viewable by Everyone on the Internet. Don't enter any sensitive data</h5>
        <form>
            <div class="form-group">
                <div id="inputForm"></div>
                <a class="btn btn-primary" onclick="run()" role="button">Submit</a>
                <a class="btn" onclick="window.location.href = document.referrer;" role="button">Cancel</a>
                <h4 class="helpText" id="instructions" hidden>Once submitted you'll be directed to the <b>view form</b> of the newly created record.
                    <br>Within the record there's buttons which will allow you to edit the record in this same format.</h4>
            </div>
        </form>
    </div>
    <br>
    <div class="container" id="status" style="text-align:center" hidden>
        <h2>Processing...</h2>
        <div class="loader" id="loader"></div></div>
    </div>

</body>
</html>

<!--
These code samples are provided "AS IS" without any warranties of any kind and is not supported by Quickbase teams.
You are responsible for the security and maintenance of any third-party code inside of your application.
Any reliance on Quickbase functions, HTML, CSS or other document-object-model (DOM) elements should be considered unstable and may change at any time, without notice.

Builders should not reference or rely on common libraries hosted by Quickbase (such as jQuery or Angular) as these may change at any time.
Customers should reference their own hosted libraries or from 3rd-party resources that they can trust and maintain
-->
Show fields from Show fields from Show fields from a related table
Report Name *
Description
Reports and Charts Panel
Each table has a panel listing its reports and charts, organized in groups.
Please wait while your new report is saved...
Field label
Column heading override
Justification
What does auto mean?
Fields in:

Fields to Extract:

Name for the new table:
Items in the new table are called:

When you bring additional fields into a conversion, Quickbase often finds inconsistencies. For example, say you're converting your Companies column into its own table. One company, Acme Corporation, has offices in New York, Dallas and Portland. So, when you add the City column to the conversion, Quickbase finds three different locations for Acme. A single value in the column you're converting can only match one value in any additional field. Quickbase needs you to clean up the extra cities before it can create your new table. To do so, you have one of two choices:

  • If you want to create three separate Acme records (Acme-New York, Acme-Dallas and Acme-Portland) click the Conform link at the top of the column.
  • If the dissimilar entries are mistakes (say Acme only has one office in New York and the other locations are data-entry errors) go back into your table and correct the inconsistencies—in this case, changing all locations to New York. Then try the conversion again.

Read more about converting a column into a table.

We're glad you're interested in doing more with Quickbase!

Now we need to make you official before you share apps or manage your account.

Verifying your email lets you share Quickbase with others in your company.

Your work email
Your company