WTFunction.png

Dynamic Function References in Javascript: How to Let the User Call a Function by Name

Intro

Have you ever wanted to dynamically call a function by its name? Or rather, have you ever wanted to build an app where the user can choose which function to run by name? Like most things in JavaScript, this problem can be solved in multiple ways. In this post, we'll take a look at 3 different approaches in Appsmith, and I'll show you why the least obvious way is also the easiest and takes the least code! 

Just want to see the working example? Click here to test (and fork) the app from this blog post.

The Problem

Say you have a dataset that you want to find different values from, like the min, max, and average. So you write different functions, and add separate buttons on the UI to run each one. There's only a single output, and you want to update it based on the user's selection. 

static function call

This doesn't scale well if you have a lot of functions because you run out of space on the UI. What if you want the user to just be able to select a function name from a Select widget, or type in a name? In other words, a single input, and a single output, with several different helper functions called based on the input. 

The Setup

For this example, I'll be using the following sample data of super-villains, and their super-accurate, exact age, as provided by ChatGPT. 

    sampleData: [
        {
            "name": "Darth Vader",
            "age": 45
        },
        {
            "name": "Sauron",
            "age": 7030
        },
        {
            "name": "Joker",
            "age": 35
        },
        {
            "name": "Loki",
            "age": 1025
        },
        {
            "name": "Magneto",
            "age": 80
        }
    ]

And I'll be referencing these three helper functions:

    findMin: (data=this.sampleData) => Math.min(...data.map(villain => villain.age)),
    findMax: (data=this.sampleData) => Math.max(...data.map(villain => villain.age)),
    findAvg: (data=this.sampleData) => data.reduce((sum, villain) => sum + villain.age, 0) / data.length

Dynamic Input

So let's say you have a LOT more functions you want the user to be able to run, and you want to simplify the UI to a single input and output. 

dynamic function call, single input

The IF/ELSE IF Approach

The most common approach is to test the input in a series of IF, ELSE IF blocks, and run the corresponding function:

    //run function by name, using IFs
    fnByName_ifs(fnName){
        if(fnName==='findMin'){
            return this.findMin()
        }
        else if(fnName==='findMax') {
            return this.findMax()
        }        
        else if(fnName==='findAvg') {
            return this.findAvg()
        }
    }

This works ok, but there's a lot of repetition, and you have to add a new ELSE IF block for each new function. 

The SWITCH Approach

A better approach is to use a JavaScript SWITCH, which takes a single input, and runs different code based on the value. 

    //run function by name, using SWITCH
    fnByName_sw(fnName){
        switch(fnName){
            case 'findMin':
                return this.findMin()
                break;
            case 'findMax':
                return this.findMax()
                break;
            case 'findAvg':
                return this.findAvg()
                break;
               default:
			return 'no matching function'
        }

This method has a few advantages:

  • It's DRYER (don't repeat yourself): The repeating fnName=== and extra (){} blocks are avoided
  • Multiple Effects: Switches can match multiple conditions, and run the corresponding code from each matching section.  
    (kind of an AND IF, instead of ELSE IF).  
    Just leave out the break, and all matching conditions will run
  • Fallback Value: You can add a default, in case none of the cases are matched! 

But it still seems a bit verbose, just to 'run the function called X'. 

There has to be a better way!

 

Bracket Notation for Object Properties

In JavaScript, object properties can be referenced using dot notation, or bracket notation. With dot notation, you have to use the exact JSON path, like objectName.property1, objectName.property2, etc. 

But with bracket notation, you can use strings, like objectName["property1"], objectName["property2"],... AND you can use variables!

bracket notation to run function
Cat WTF?

BUT HOW! ?

JSObject1 is... well, an object! So every function that you name in the object can be referenced by this[functionName] , and functionName can be a variable. Adding the () tells JavaScript to run that function. So you can just pass in the value from the user input, and run the function directly by name. It's that easy! 

    //run function by name, using bracket notation to access object properties
    funByName_brkt(fnName=Input1.text){
        return this[fnName]()
    }
//returns 35, when Input1.text = 'findMin'

FAQ

What if the function name the user inputs doesn't exist?

If a user tries to call a function by a name that doesn't exist, JavaScript will typically throw an error because it tries to execute something that's not there. To handle this gracefully, you can check if the function exists before trying to call it. If it doesn't, you can provide a friendly message to the user or execute a default function. 

Can I pass arguments to functions called dynamically by name?

Yes, you can pass arguments to functions even when calling them dynamically by their names. Once you've determined the function to call using the user's input, you can include the arguments right after the function name, just like you would with a regular function call. 

How can I make sure the user input is safe and not executing harmful code?

Ensuring user input is safe is crucial, especially when dynamically executing functions based on that input. To safeguard your application, you should validate the input against a predefined list of allowed function names or patterns. This acts as a filter, ensuring only safe and expected commands are executed.

Conclusion

Running a function by name (or a dynamic function reference), is a common requirement in more complex apps. It's a great way to save space on the UI, and it makes the code more efficient. Appsmith's JSObjects provide a simple and elegant solution to dynamically referencing functions, allowing developers to build complex UIs where the user can call a function by name. This is possible by leveraging bracket notation to access object properties, where that property is a function name. 

Just want to see the working example? Click here to test (and fork) the app from this blog post.

Kevin Blanco Verified userVerified user staff View kevinblanco's profile
Tue, 09/05/2023 - 00:10

Great article Joseph! Thanks for sharing 

John Passmore public View dwba's profile
Fri, 10/13/2023 - 10:13

Much needed blog and associated app - been trying to find out for ages how to use variables for  e.g. Query and modal names! Many thanks 

Joseph Petty Verified userVerified user staff View joseph_appsmith's profile
Sat, 10/14/2023 - 13:15

Thanks John! Glad you found it helpful. It's crazy how much work this saves and how easy it is to use with any number of functions. No editing the switch or if/else block with each new function. It just works! 

Joseph Petty Verified userVerified user staff View joseph_appsmith's profile
Sat, 10/14/2023 - 14:18

Want to list all the functions from a JS Object progromatically? 

Object.keys(JSObject1).filter(f=>typeof JSObject1[f]==='function').map(f=>({function:f}))