Creating a new type

This page follows on from Getting started with plugin development and will show you how to finish the "angles-type" plug-in which adds an angular degrees data type to Saltcorn.

To define the functionality in a Saltcorn module, you add to the object that is exported from the main JavaScript file in the NPM package. The types key in this object should hold a list of definitions of new datatypes. We start by adding the types key to the module:

module.exports = { sc_plugin_api_version: 1, types: [] };

This is still a blank module! To add our type definition in the types array, I recommend introducing defining the type as a separate object before including in the array:

const angle= {
   // ...
}

module.exports = { sc_plugin_api_version: 1, types: [angle] };

A valid object defining a new type should have the following keys:

  • name: a string giving a short name to distinguish the new type
  • sql_name: a string indicating the data type by which the value will be stored in an SQL database.
  • fieldviews: an object of objects, each of which define a fieldview, that is a way for the user to see or edit a value of the new type
  • read: a function which takes as an argument a JavaScript value of a variety of JavaScript types and outputs a valid JavaScript representation for a value of this type, or undefined if the argument cannot be converted to a valid representation.

The type object can have the following keys, but these are not necessary:

  • attributes: an array of objects specifying the attributes of the type
  • validate: a function from the attributes to a function from the JavaScript representation of the value to a Boolean. Indicates whether this value is valid within the parameters set by the attributes, if any.
  • validate_attributes: a function from the attributes as an object to a Boolean indicating whether these attributes are consistent and valid. Sometimes attributes can be invalid: for instance, number types have min and max attributes. A set of attributes are not valid if min > max
  • presets: a type can specify presets with an object of functions that return a JavaScript representation of the value. For instance, for instance, the presets for the date type includes a function that returns the current time. These presets can be used to auto populate form values.
  • readFromFormRecord: some datatypes can only be read from form values by reading the entire form. This is necessary to read Boolean values from form data, because an un-ticked checkbox is absent from the form results.
  • readFromDB: if the data needs to be transformed from the database representation to the JavaScript representation, specify the transformation here. This can be used to resolve inconsistencies between different database backends.

In the fieldviews object, every key/value pair gives a new fieldview. The key is the name of the fieldview, and the value is an object containing two properties:

  • isEdit: a Boolean (true or false). If true, this field view is for editing values; if false, this field view is for displaying values
  • run: a function that returns a string containing the HTML required to display the fieldview of the value in a browser. The run function takes these arguments:
    • if isEdit==false: a single argument, the JavaScript representation of the value in the new data type. This will be the output of the read function.
    • if isEdit==true: up to four arguments containing the information required to render a form input for the given data type. The arguments are:
      • 1st argument: the field name as a string. This should be the name of the form entry
      • 2nd argument: the current value as a JavaScript representation. If there is no current value (for instance, this is a form creating a new row), the 2nd argument will be undefined
      • 3rd argument: an array of values for the attributes of this field.
      • 4th argument: a class we are required to add as the class for the HTML element.

Based on these definitions, we can start to build our angle object describing our new type. It's always good to look at previous definitions. In our case, the Angle type will be very similar to the built-in type definition for floating-point numbers. This is defined in the main Saltcorn repository in the types.js file.

const angle = {
  name: "Angle",
  sql_name: "double precision",
  fieldviews: {
    show: { isEdit: false, run: (s) => `${s}°` },
    edit: {
      isEdit: true,
      run: (nm, v, attrs, cls) =>
        `<input type="number" class="form-control ${cls}" name="${nm}" id="input${nm}" ${
          v || v === 0 ? `value="${v}"` : ""
        }>°`,
    },
  },
  read: (v) => {
    switch (typeof v) {
      case "number":
        return v;
      case "string":
        const parsed = parseFloat(v);
        return isNaN(parsed) ? undefined : parsed;
      default:
        return undefined;
    }
  },
};

module.exports = { sc_plugin_api_version: 1, types: [angle] };

This code provides the name, SQL name, field views and the read function needed to define the angle data type. In the run methods, we use JavaScript string interpolation to generate the HTML in order to reduce the number of concepts introducced in this tutorial. This is perfectly acceptable, but in other plug-ins we use the HTML combinator is from the @saltcorn/markup library. You can also use handlebars or any other form of templating system you are familiar with, as long as in the end you return a string containing the HTML. If the value contains strings provided by the user, you may also want to consider cross site scripting (XSS) protection.

Since you have now added something useful to the plug-in, you may want to go ahead and create a new release. If you want to upload to the NPM repository again, you must increase the version number specified in the package.json file. You cannot upload the same version twice.