Tutorial

Scripting

Sonadier Forms is an online database application platform that uses forms to represent database tables. We provide an interface, validation, accounts, and permissions. You can start writing custom scripts for your business logic, without reinventing the wheel. This tutorial is intended for first-time users, and more detailed documentation is available. Scripting is included with our Development Plan.

For Developers

When you create a form, we'll provide pages for common database operations like creating, reading, updating, and deleting records. Your users can, with permission, start recording data immediately. You can use the same API to write client-side scripts (run in the user's browser), server-side validations (run after a submission is saved), or server-side procedures (triggered with a POST request or at scheduled intervals).

For clarity, a "submission" or "response" refers to a database row, and we'll use the terms interchangeably below. "Forms" are equivalent to database tables, and "fields" are table columns. Loading a submission means loading its data on to the current page with JavaScript.

The Submission Object

Client-side scripts are run once for each loaded submission, and you can access the current submission's data with the submission variable. Submissions open in modal windows by default, so there can be more than one per page.

A Submission object represents a new or edited submission on the page. You can load, edit, and save submissions behind the scenes with AJAX, and submissions can be hidden from the user. The submission variable is set to the Submission object for the current submission, but you can also load in related submissions and use the same methods.

A submission's "permalink" is it's globally-unique ID. It'll be undefined if the submission is unsaved. You can check whether a submission is saved using the persisted method.

submission.permalink(); // "ABCDEFGHIJ"
submission.persisted(); // true

You can get and set field values easily - we'll go over these methods more later in the tutorial.

submission.fields.field_name.value("Hello World!"); // Sets the value of the "Field Name" field to "Hello World!".
submission.fields.field_name.value(); // Returns the value of "Field Name", "Hello World!".
submission.fields.first(); // Returns the first field in the submission.
submission.fields.any_errors(); // Returns true if the submission has any validation errors.

You can open a field's object with it's variable name. By default this is a snake_cased version of the field's name: fully lowercased with spaces replaced by underscores. If you'd like to quickly check names and values, you can use the values method to return a simple object.

submission.values(); // Returns: { field_name: "Hello World!" }

Saving and Validating Submissions

Your users can save submissions with the "Save" button, and scripts can call the submit method. You can add callbacks to be run at various stages of the submission process. The Field and Error APIs are useful here, and we'll go over them below.

The validate callback runs after submission. Submission will be halted if any of the methods passed return false, or add errors to a field.

submission.validate(function() {
    submission.fields.field_name.error("This field has an error.");
});

The before_submit callback runs before submission. Unlike validate, it cannot stop the submission. before_create and before_update are also available and will run before creating new submissions and updating existing submissions, respectively.

submission.before_submit(function() {
    submission.fields.reporter.value(current_user.username);
});

The after_submit callback runs after the submit function completes successfully. after_create and after_update are also available and will run after creating new submissions and updating existing submissions, respectively.

submission.after_submit(function() {
    console.log("Submitted " + submission.permalink() + " successfully.");
});

The failed_submit callback runs when the submit function fails. It's triggered by the jQuery ajax fail method, and the request's status and error code are sent as parameters. failed_create and failed_update are also available for their respective actions.

submission.failed_submit(function(status, error) {
    console.log("This response could not be submitted. Status code: " + status);
});

Getting and Setting Fields

The submission.fields object handles finding and managing fields.

/* Returns an array of the submission's fields. */
submission.fields.all();

/* Returns a field object by its variable name. */
submission.fields.field_name;

Individual field objects handle getting and setting values, as well as displaying field errors.

var field = submission.fields.field_name;

/* Returns the field's value. This can be a string, object, or array. */
field.value();

/* Sets the field's value. This method works even when setting sub-fields like street address. */
field.value("Hello!");
field.value([1,2,3]);
field.value({"street_address": "1600 Pennsylvania Avenue"});

/* Sets a default value for the field. */
field.default("Default Value!");

/* Show or hide the field's container element. This can be used to toggle fields or to hide internal fields that are set with scripts. */
field.show();
field.hide();

/* Checks if the field's value generated any errors. Errors are generated when a submission attempt is made, and this method only checks if the last attempt added any errors. */
field.valid();

Creating and Reading Field Errors

You can add errors to a field to make the user aware of custom requirements. New errors should usually be added from a validation callback. You can use the same syntax to add errors from server-side Validations, which can be enforced more securely.

var field = submission.fields.field_name;

/* Checks for any errors generated after a failed submission attempt. */
field.valid();

/* Returns an array of strings containing all errors for this field. */
field.errors.all();

/* Adds a string to the array of errors, which appear in red above the field. */
field.errors.add("There was an error!");
field.error("This is also an error!");

The Form Object

Just like submissions have Submission objects, forms have Form objects. You can access the submission's form directly, or initialize new forms by permalink. This can be used to reference, open, or submit other submissions.

/* Returns the submission's form object. */
submission.form;

/* Returns the form's permalink. */
submission.form.permalink();

/* Returns a form object for the form permalink given. */
forms("ABCDEFGHIJ");

Managing a Form's Submissions

From a Form object, you can open, search, and remove submissions belonging to the form.

var form = submission.form;

/* Returns all submission objects belonging to the form */
form.submissions.all();

/* Opens a submission for the user by permalink. If no permalink is given, a new submission is initialized. If the submission is not currently loaded, it will be requested over AJAX. Returns a jQuery Deferred (promise) object. */
form.submissions.open("ABCDEFGHIJ").done(function(new_submission) {
    new_submission.modal.hide();
});

/* Loads a submission from the server in the background. It will not appear to the user unless you use the modal.show() method. Returns a jQuery Deferred object. */
form.submissions.load("ABCDEFGHIJ").done(function(new_submission) {
    new_submission.modal.show();
});

/* Finds an initialized submission object. If the submission is not loaded on to the page, this will return undefined, and you can request it with the load method. */
new_submission = form.submissions.find_locally("ABCDEFGHIJ");

Getting the Current User

Sonadied comes with a built-in user management and permissions service. You can access the current user from anywhere with the current_user variable.

/* Returns the logged-in user's object. If the current user is not logged in, this will be undefined. */
current_user

/* Returns the logged-in user's username */
current_user.username;

/* Returns an array of the names of groups the user belongs to. */
current_user.group_names; // ["All Users", "Staff Members", "Management"]

Showing and Hiding Modals

Submissions can be loaded full-screen or embedded, but they are usually opened as modal windows. Modals have a standard Modal class to handle visibility and display. You can use the display method to see whether a submission is full-screen or a modal, or check if the modal property is undefined.

/* Returns "page" if the submission is full-screen, and "modal" if it's in a modal. */
submission.display();

if (submission.modal != undefined) {
    /* Makes a modal visible to the user. */
    submission.modal.show();

    /* Makes a modal invisible to the user. */
    submission.modal.hide();

    /* Returns a boolean indicating whether the modal is visible or not. */
    submission.modal.visible();
}

Adding Custom Menu Options

Scripts can add custom options to the navigation menu that appears at the top of the page. Since scripts can run multiple times on a page, the Navigation system has several automatic checks to prevent duplicate options.

/* Returns an object of options, with ID keys. */
navbar.options();

/* Returns an array of elements for the custom options. */
navbar.elements();

/* Adds an option to the navbar. This won't be added if another option has the same ID. The option is formatted as a string here for convenience, but it can also be passed as a jQuery DOM element constructor */
navbar.add("<div id='...' class='option'><a href='#'>...</a></div>");

/* Options can also be strings. The ID will be set automatically based on the value. "My Option Name" will have the ID "my_option_name". */
navbar.add("My Option Name");

/* Remove a custom option by ID */
navbar.remove("my_option_name");

Creating Your Own Modals

Your scripts can create custom modals using a standard modal method. The modal function returns a Modal object which you can use to show or hide it dynamically. All parameters are optional except for the title.

/* Returns a Modal object. The title attribute takes a string, while the body and actions attributes take either a string or a DOM element */
modal(title, body = "", actions = "", options = {});

/* The actions parameter accepts a set of <div/> elements. It's formatted as a string here for convenience, but can also be passed as a jQuery DOM element constructor. */
modal("Title", "Body", "<div class='option'>Option Name</div>");

/* Option elements accept additional classes to change their display. The "primary" and "secondary" classes will display the option as a teal-background button, or gray text, respectively. The left and right classes will set the alignment of the option. */
modal("Title", "Body", "<div class='option primary right'>Submit</div><div class='option secondary right'>Cancel</div>");

/* The fourth parameter, options, allows you to pass style options. The size option controls the modal's width, and takes the following values: ["x-small", "small", "medium", "large", "x-large"]. The id option sets a custom id for the modal element. */
modal("Title", "Body", "", { size: "x-large", id: "test" })

Server-Side Validations

Validations are custom scripts that are run server-side for extra security. You can use them to add custom error checking to your forms, or trigger third-party services. They are run automatically when a submission is created or updated. If the validation returns any errors, the submission will not be saved and the errors will be displayed next to fields in the same format as standard errors.

Validations use the same general API structure as client-side scripts, with the same submission object and field handling. The following script will add an error to the "Field Name" field, and block saving.

if (submission.fields.field_name.value() < 100) {
    submission.fields.field_name.error("can't be less than 100");
}

Since validations support asynchronous objects like $.Deferred and third-party API calls, you're required to resolve your script with a call to the finish method.

Integrations.salesforce.get("/services/data/v35.0").done(function(response) {
    console.log(response);
    finish(); // Note the finish method call when the deferred has finished.
});

By default, console logs are not saved anywhere. If you set the DEVELOPMENT variable to true, they will be returned with the submission's request client-side.

DEVELOPMENT = true;

Procedures

Like Validations, Procedures are run server-side. However, they are triggered by making a POST request to a specific URL. Procedures can access the same API that validations and client-side scripts use. Procedures can be used to encode business logic in self-contained scripts, called by client-side scripts, validations, or third-party APIs. Procedures can be triggered by making a POST request to the URL:

https://{{organization}}.sonadier.io/api/rest/v1/forms/{{form}}/procedures/{{procedure_endpoint}}?submission=ABCDEFGHIJ

The submission parameter is optional: if it's set to a valid and permitted submission's permalink, the procedure will be provided with a standard submission object. This allows you to make both procedures that deal with individual submissions, and procedures that aren't tied to a specific record. Procedures and validations can query submissions and forms in the same way client-side scripts can:

Submission.find_by_permalink("ABCDEFGHIJ").done(function(new_submission) {
    new_submission.fields.field_name.value("Submitted!");
    new_submission.submit();
});

In the same way that Validations are required to call finish, Procedures are required to call render when they have completed, to render a response to the triggering request. The parameters you pass will be returned as the response's status and body, so you can control the output of your procedure's endpoint. By default, render will return an empty response with the HTTP code 200. Note that render must be called when your script is finished, even if it's called with no parameters.

/* Returns a HTTP status 200 OK */
render();
render({ status: "ok" });

/* Returns a HTTP Status 400 Bad Request */
render({ status: 400 });
render({ status: "bad_request", json: { error: "We couldn't process that input"} });

The response's body and type is set by passing the type as a key and body as value. The response types supported are "html", "json", "xml", and "csv".

Form Preferences

The "Preferences" form system lets you attach a secondary form to each of your forms to save user-specific data. It allows only one submission per user, and you can access the current user's submission programmatically from a Form object. You can use it to let users store custom preferences for a form, or save user-specific data that will be shared between a form's submissions.

/* Loads a user's preference submission in the background. Returns a Deferred object. */
submission.form.preferences.load().done(function(preferences) {
    if (preferences.fields.sign_submissions_automatically.value()) {
        submission.fields.signature.value(true);
    }
});

/* Open a preference form modal for the user, loading it if it's not already loaded. */
submission.form.preferences.open();

Integrating with External Services

Forms has an OAuth Integration service to help you call external APIs with custom scripts. Your users can set up OAuth accounts for services we've integrated with from their Settings page, and you can programmatically call API actions. We'll store their sensitive OAuth details, and provide an access token to your API calls automatically. We're currently integrated with Salesforce and the Force.com platform, and you can email us at support@sonadier.com to request another service.

/* Makes a GET request to the integration's base URL and with path given. POST, PATCH, PUT, DELETE, and OPTIONS are also available. The params argument is passed to the $.ajax data option, while the request_params are merged with the $.ajax's options. Both params and request_params are optional, and the request has sane defaults. */
Integrations.salesforce.get("/services/data/v35.0", params = {}, request_params = {});

/* Request methods return a jQuery $.Deferred object. */
Integrations.salesforce.get("/services/data/v35.0").done(function(response) {
    console.log(response);
});

Miscellaneous Helper Methods

There are several small helper methods available for scripts to use. jQuery is also used on every page and is available, although we do not guarantee a specific version at this time.

/* Returns the URL query parameters as a hash. ?foo=bar outputs { foo: "bar" }. */
params();

/* Returns a UUID generated based on the current time. */
uuid();

/* Returns the current time in seconds. */
unix_time();

Contact us with any Questions

Feel free to email us at support@sonadier.com if you have suggestions, requests, or comments on the scripting system. Additionally, if you have an edge-case and are having difficulty using existing methods to work with it, we are always willing to add useful functions and new objects.