First class JavaScript functions (with workaround)

It should be possible to create JavaScript functions that take inputs and return an output

They should be part of an app so they are versioned with the rest of the code.

They should accept inputs so the logic can be reused with multiple input sources instead of being tied to a specific query, table, or form.

I know similar requests have been pasted But I’m posting anyway to share the workaround I’ve come up with.

If this is added, the name “Transformers (JS functions)” should probably be changed. Perhaps to “Transformers (JS snippets)” as they are already called in the docs “Retool supports longer snippets of JS via transformers”.

Usage example

I want to update a timestamp before saving a row to the database I want this to affect all of my tables. I’d want to create the following function:

function updateTimestamps(recordUpdates) {
  recordUpdates.forEach(record => {
    record.updated_at = new Date().toISOString()
  })
  return recordUpdates
}

Then in my bulk update query I’d fill in the “Array of record to update” field as follows:

{{ updateTimestamps( table_1.recordUpdates ) }}

This way I can reuse the logic for all the tables.

Workaround

Defining the function on the window from within a compnenet that can be relyed opon to render before the function will be needed

For example, If you’re using a Tabbed Container for navigation you can define your functions in one of its fields that you don’t need to use I’ll place this in the “Disable this container?” field.

{{ (function () {
  // Using an Immediately invoked function expression here to break
  // out of the limitation of only using expressions and not statements.

  // Define functions on the window.
  window.updateTimestamps = function (recordUpdates) {
    recordUpdates.forEach(record => {
      record.updated_at = new Date().toISOString()
    })
    return recordUpdates
  }

  // Define as many functions as you need.
  window.someOtherFunction = function (param1, param2) {
     // ...
     // return ...
  }

  // I never meant to disable the container so I'll always return false
  // If you place this in another field (like "show the tab bar?") you may
  // want this to always be true.
  return false
} )() }}

Somethings to be aware of:

  • If you call this function in a place that isn’t guaranteed to run after this snippet is run, you won’t see the error until the page is refreshed and the window won’t have the function loaded yet. try to place this at the topmost container to avoid this issue.

  • When calling this function it will show a red underline with a warning that this function is not defined. just ignore it, It’s a bug. The same thing happens when calling a function from an external library.

Other workarounds that may work for some use-cases

Using transformers (hardcoded input sources)

Being that transformers need to hard code their inputs to a specific query or component they can only be reused when the output location changes but not when the input source changes.
Attempting to return a function from a transformer doesn’t work either.

I’d need to repeat this for every table unless there was a way to tell what table is currently being submitted from within a transformer. I can try to use the selected tab and create an object to map between each tab and the table on that tab. this seems a bit hacky and assumes that there isn’t a window of time for another tab to be selected in the interim (perhaps due to async behavior in the browser, retool, or in react).

Preloading JavaScript on the advanced settings page (not versioned and global to all apps)

The page is at https://your_username.retool.com/settings/code.

In the Preloaded JavaScript, section define your functions on the window

window.updateTimestamps = function (recordUpdates) {
  recordUpdates.forEach(record => {
    record.updated_at = new Date().toISOString()
  })
  return recordUpdates
}

I can never rename this function as that will break all older versions of my app.

This will affect all of my apps. if another app uses a different name for the last updated timestamp I’d need to either namespace the function names or have it handle both cases via another input parameter or by parsing the URL. In either case, I may break all older versions of the app, as these are not versioned.

4 Likes

Very interesting. Would per-app preloaded JS (files and also code) be another way to solve this? That would be a feature I’d definitely appreciate.

@byron yes. If there was the equivalent of the advanced settings page on a per app basis that would work great.

It would need to be included in the app versioning to convince me to switch from my current workaround.

You can do this with JS queries using additional scope.

https://docs.retool.com/docs/scripting-retool

Hi Alex,
I was trying to do achieve what is thread is about today (have a global function that can be used in multiple transformers within the app) and I was trying to use JS queries but could not really figure out out to have a function that takes some input and returns some values.

@anconam

Two ways. First is you can now add javascript to the app as was hoped for by @arye-eidelman over a year ago. Retool v2.66.91 release notes. If you are doing work that is completely ignorant of Retool and unique to that app then that is probably a good place for it.

Second you can use a query as @alex-godin suggests like this:

// jsQuery 
 myQuery.trigger({
      additionalScope: {
        myProp: "Hello "
      },
      onSuccess: function(data) {
    	  console.log(data) // Hello Fred
      }
 })

// myQuery
 return myProp + "Fred"
1 Like

link for release notes is not found

Hey @tpbook! Great callout. Is this the link you're looking for?

https://docs.retool.com/docs/retool-v2-66

is there an updated link?

Hey @bobthebear! Are you looking for our release notes? https://docs.retool.com/changelog

Hi all,

Related to this thread -- we just launched a closed alpha for Sync Functions. Let me know if you'd like to be added!

Best,
Erin