Installation parameters, as the name suggests, allows us to obtain certain values during the time of App Installation. Conceptually, these values are configuration details that the app requires to function properly. This is similar to how cab-hailing apps (E.g. Uber, Ola) require user's contact & location details or how social media apps (E.g. Instagram, Twitter) require us to choose topics that we are interested in before we start using them. Installation parameters (iparams) can range from simple text inputs to complex UI components.

Freshworks Developer Platform offers simplicity and sophistication when it comes to installation pages.

Dynamic Installation pages offer the best of both worlds. Retaining the simplicity of iparams.json and the customizability of iparams.html, Freshworks App developers can make use of iparam callbacks to build top-notch Installation pages without breaking a sweat.

What will we be learning:

How to,

  1. Write event callbacks for iparam fields
  2. Get / Set the properties of iparam fields
  3. Dynamically validate fields by making third-party API calls

Before we dive-in:

Ensure to,

  1. Have Freshworks Developer Kit (FDK) installed
  2. Clone the marketplace sample apps repo
git clone https://github.com/freshworks/marketplace-sample-apps
  1. Switch to the app directory
cd marketplace-sample-apps/Freshworks-Samples/App-Development-Features/Configuration-Features/dynamic_iparams

Much of the dynamicity can be owed to event callbacks in iparams.json. We can subscribe to specific event(s) via callbacks associated with a particular iparam field. As a part of the iparam field definition, one can include events property that will contain a list of events and their listeners.

For instance, consider the following snippet. As you can notice, for the following iparams - twitter_id & contact_methods, we can notice an additional property called events. It contains an array of event callbacks designated for iparam events. At the time of writing this document, there was only one event,`change`, associated with iparam fields. As the name implies, the change event is triggered every time the value of the associated iparam field changes.

config/iparams.json

{
   "twitter_id": {
       "display_name": "Twitter ID",
       "description": "Please enter your Acc. ID",
       "type": "text",
       "required": true,
       "events": [{
           "change": "checkAccountID"
       }]
   },
   "mobile": {
       "display_name": "Phone Number",
       "description": "Please enter your phone number with the country code",
       "type": "phone_number",
       "required": true
   },
   "domain_name": {
       "display_name": "Domain Name",
       "description": "Please enter your domain name",
       "type": "domain",
       "type_attributes": {
           "product": "freshdesk"
       },
       "required": true
   },
   "api_key": {
       "display_name": "API Key",
       "description": "Please enter your api_key",
       "type": "api_key",
       "secure": true,
       "required": true,
       "type_attributes": {
           "product": "freshdesk"
       }
   },
   "contact_methods": {
       "display_name": "Preferred Contact Method",
       "description": "Please select the preferred contact methods",
       "type": "multiselect",
       "options": [
           "Mobile",
           "Twitter ID"
       ],
       "events": [{
           "change": "contactMethodChanged"
       }],
       "default_value": [
           "Mobile",
           "Twitter ID"
       ]
   }
}

If you are wondering where the event listeners associated with the iparam fields are situated, Look no further than the config/assets/iparams.js file!

Inside the iparams.js file, we can define the event callback methods that are associated with the iparam fields. The logic inside the method gets executed when a particular event is triggered. In our case, we listen to the `change` event. Whenever the field value is modified, the callback will be fired. Validations and field manipulations can be done by returning appropriate promises and making use of utility methods.

For instance, let's take a look at the `checkAccountID` callback method. This method gets executed every time a value is entered in the twitter_id iparam field

config/assets/iparams.js

/**
* Using this iparam callback function, we are validating the details using a third-party API.
* 
* @param {string} newValue The new value of the iparam field
*/
function checkAccountID(newValue) {
 //Input type validation
 if (!isNaN(newValue)) {
   return Promise.reject("Account ID has to be a string");
 }
 return validateWithAPI(newValue);
}
/**
* In this case, for example, we are making use of `httpbin.org` to return 200 OK status.
* In real-world, this could be a valid third-party API that can return an appropriate status code indicating the status of validation
* Payload and other options can be specified using `options`
* Notice the presence of the debounce logic to avoid rate-limiting issues.
*
* @param {string} value
*/
function validateWithAPI(value) {
 //Assume it is the validation/resource endpoint
 var url = "https://httpbin.org/status/200";
 var options = {
   body: JSON.stringify({
     param: value
   })
 };
 var p = new Promise(function (resolve, reject) {
   // Do not hit the validation API immediately upon change.
   // Wait for 500ms and if the user hasn't typed anything during that time, make a call
   clearTimeout(timeout);
   timeout = setTimeout(function () {
     client.request.post(url, options).then(
       function (data) {
         // Upon success, just resolve
         resolve();
       },
       function (error) {
         // Upon failure - send an appropriate validation error message
         reject("This Account ID does not exist. Please enter the right one");
       }
     );
   }, 500);
 });
 return p;
}


In the above snippet, we see that by returning a rejected promise, we symbolize errors during validation. The rejection reason should be a string that will eventually appear as an error message under the field. For instance, during input validation.

On the other hand, we can return a resolved promise to symbolize successful validation.

Also, one can also make use of platform features (Request method, in this case) by obtaining the client object.

config/assets/iparams.js

app.initialized().then(
   function (_client) {
       //If successful, register the app activated and deactivated event callback.
       window.client = _client;
   },
   function (error) {
       //If unsuccessful
       console.log(error);
   }
);

Now that we have the client object, we can proceed to make use of the Request method. We have successfully made use of the Request method inside `validateWithAPI` to call a third party API based on the new value that was entered in the twitter_id field. Note that this is not an actual verification with Twitter. We are calling a dummy API that echoes back the status code that we have intended to return. In a real-world scenario, this could be an actual third party API with its own endpoint and payload structure.

Note that firing API calls on every change could burn out the rate-limit. Consider using a debounce logic with an appropriate timeout in such situations. Shown below is the difference between using and not using debounce logic.

Without debounce logic

With debounce logic

Based on the promise resolution or rejection, an appropriate response can be sent as the iparams validation message. In our sample code, as the API returns with status code `200 OK`, it will return a resolved promise indicating successful validation.

Utility methods allow us to perform two important things

As a handy reference, here is an exhaustive list of attributes that can be used along with utility methods to either get or set them.

General syntax:

// For setting the properties
utils.set(‘<iparam_key>', {<attribute>: <value>});
// For getting the properties
utils.get(‘<iparam_key>', {<attribute>: <value>});

Attribute Reference:

Attribute

Data Type

Description

value

string

array of strings (for iparams of the multiselect type)

Enables you to set a value for the iparam.

For a multiselect iparam, enables you to set certain values as selected options. On the installation page, the selected options are populated in the iparam's input field.

label

string

Enables you to modify the display_name attribute value of the iparam.

visible

boolean

Enables you to hide the iparam from the installation page.

disabled

boolean

Enables you to disable the iparam on the installation page.

required

boolean

Enables you to modify the required attribute value of the iparam.

hint

string

Enables you to modify the description attribute value of the iparam.

values

Valid only for iparams of the radio, multiselect, and dropdown types.

array of strings

Enables you to modify the options attribute value of the iparam.

min

Valid only for iparams of the number type.

number

Enables you to set a validation for the minimum value that can be entered for the iparam.

max

Valid only for iparams of the number type.

number

Enables you to set a validation for the maximum value that can be entered for the iparam.

Utility methods can be handy when we need to perform ad hoc field manipulations based on field values. For instance, let us take a look at `contactMethodChanged()` method.

config/assets/iparams.js

/**
* When the contact method changes, just display the options
* To set the list of possible options to choose from, use -> utils.set("<iparam_field>", { values: ['Opt1', 'Opt2', 'Opt3'] });
*/
function contactMethodChanged() {
 //Let us get the selected options for contact methods
 const cm = utils.get("contact_methods");
 console.info(cm);
}

The `contactMethodChanged()` function is invoked every time we select or deselect an option from the "Contact Methods" multi-select field.

Based on the values that are selected, we can toggle the visibility of other fields like so:

config/assets/iparams.js

/**
*  Dynamically enable / disable standard iparam fields using the utility methods
* @param {object} cm
*/
function toggleFieldsVisibility(cm) {
   if (!Array.isArray(cm))
       return console.error("Something went wrong while toggling field visibility");
   utils.set("twitter_id", {
       visible: cm.includes("Twitter ID")
   });
   utils.set("twitter_tags", {
       visible: cm.includes("Twitter ID")
   });
   utils.set("tags", {
       visible: cm.includes("Twitter ID")
   });
   utils.set("mobile", {
       visible: cm.includes("Mobile")
   });
}

For simplicity, we just print the options to understand how it works. However, feel free to build your logic on top of this ⚡️

We learned to

Kudos for following the tutorial until the end. If you have any queries, feel free to get in touch with us.

The Right Balance