WTFunction.png

Sr. Developer Advocate

Appsmith

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'

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 staff View kevinblanco's profile
Tue, 09/05/2023 - 04:10

Great article Joseph! Thanks for sharing