MLK.DEV
MLK.DEV

Reputation: 453

How to Use JS Array Elements to Define Calls to Functions Using Arguments

For this particular application I am plotting points on a line graph, and these are the numbers used by my function to place the points at the correct locations along the line.

var CustomData = [
  [168,36,10,1,"#FFFFFF","#f0e58d"],
  [282,31,10,1,"#FFFFFF","#559ab5"],
  [338,47,10,1,"#FFFFFF","#f0e58d"],
  [448,55,10,1,"#FFFFFF","#559ab5"],
  [540,49,10,1,"#FFFFFF","#559ab5"],
  [674,27,10,1,"#FFFFFF","#f0e58d"],
  [718,24,10,1,"#FFFFFF","#559ab5"],
  [846,24,10,1,"#FFFFFF","#f0e58d"],
  [1008,35,10,1,"#FFFFFF","#f0e58d"]
];

A description of these values:

CustomData[index][x, y, radius, lineWeight, lineColor, fillColor];

What I'm doing is using the CreateJS library to draw these shapes, and using simple event listeners to detect click events (lines broken for clarity):

for(i = 0; i < this.CustomData.length; i ++) {
  var _i = this.CustomData[i];
  this.NewShape = new cjs.Shape();
  this.NewShape.set({cursor:"pointer"});
  this.NewShape.graphics.ss(_i[3]);
  this.NewShape.graphics.s(_i[4]);
  this.NewShape.graphics.f(_i[5]);
  this.NewShape.graphics.dc(_i[0],_i[1],_i[2]);
  (function(i,shape,_i){
    shape.on("click", function() { DoStuff(i,_i); });
  })(i,this.NewShape,_i);
  this.addChild(this.NewShape);
}

What I would like to do is expand this routine so that I can use a custom function on each shape, I just don't know how to define a reference to a function inside the array. I also need to be able to pass the iteration and the CustomData element into the function as well.

In theory what I'm imagining is something like:

var CustomData = [
  [168,36,10,1,"#FFFFFF","#f0e58d", "DataFunc_A" ],
  [282,31,10,1,"#FFFFFF","#559ab5", "DataFunc_B" ],
  [338,47,10,1,"#FFFFFF","#f0e58d", "DataFunc_A" ],
  [448,55,10,1,"#FFFFFF","#559ab5", "DataFunc_B" ],
  [540,49,10,1,"#FFFFFF","#559ab5", "DataFunc_B" ],
  [674,27,10,1,"#FFFFFF","#f0e58d", "DataFunc_A" ],
  [718,24,10,1,"#FFFFFF","#559ab5", "DataFunc_B" ],
  [846,24,10,1,"#FFFFFF","#f0e58d", "DataFunc_A" ],
  [1008,35,10,1,"#FFFFFF","#f0e58d", "DataFunc_A" ]
];

And changing the anonymous function within the loop to:

(function(i,shape,_i){
  shape.on("click", function() {
    window[_i[6]](i,_i);
  });
})(i,this.NewShape,_i);

My question: Is this the best most scalable way to achieve this? Are there any "gotchas" that I'll need to keep in mind? Thanks in advance.

Upvotes: 1

Views: 36

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074198

Is this the best most scalable way to achieve this?

I don't know about scalable, but it's not the best way to achieve this.

Are there any "gotchas" that I'll need to keep in mind?

By storing strings in your arrays and then looking up those strings later on the window object, you're forcing yourself to make all of your functions globals. The global namespace is already really, really crowded, leading to the possibility of conflicts. The more you put in the global namespace, the more possible conflicts you run into.

Instead:

You can place a function in an array just like any other value, since functions can be created by expressions:

var CustomData = [
  [168,36,10,1,"#FFFFFF","#f0e58d", function() { /* do something */ }],
  [282,31,10,1,"#FFFFFF","#559ab5", function() { /* do something else */ }],
  [338,47,10,1,"#FFFFFF","#f0e58d", function() { /* do yet another thing */ }],
  // ...
];

If you need to use the same function on more than one entry, or if you run into readability or maintenance issues with long functions defined inline like that, you may find it more useful to define the functions separately and then just refer to them in the array:

var CustomData = [
  [168,36,10,1,"#FFFFFF","#f0e58d", doSomething],
  [282,31,10,1,"#FFFFFF","#559ab5", doSomethingElse],
  [338,47,10,1,"#FFFFFF","#f0e58d", doYetAnotherThing],
  // ...
];

function doSomething() {
    // ...
}

function doSomethingElse() {
    // ...
}

function doYetAnotherThing() {
    // ...
}

Even in the second case, if you have your code in a scoping function or a module (whether some form of library AMD, or ES2015 modules), those functions won't be globals. (They will be if you just have them out at global scope, not in a scping function or module.)

You can call those functions just like any other:

_i[6](arg1, arg2, arg3);

....where I'm using 6 there because in my example above, the functions are at index 6 in each of those arrays.


Having said all of that, I'd suggest you consider using objects rather than arrays for the entries in your CustomData array.

Consider this code from your question:

this.NewShape.set({cursor:"pointer"});
this.NewShape.graphics.ss(_i[3]);
this.NewShape.graphics.s(_i[4]);
this.NewShape.graphics.f(_i[5]);
this.NewShape.graphics.dc(_i[0],_i[1],_i[2]);

vs., say, this:

this.NewShape.set({cursor:"pointer"});
this.NewShape.graphics.ss(_i.ss);
this.NewShape.graphics.s(_i.s);
this.NewShape.graphics.f(_i.f);
this.NewShape.graphics.dc(_i.dc[0],_i.dc[1],_i.dc[2]);

You can do that like this:

var CustomData = [
  {dc: [168,36,10], ss: 1, s: "#FFFFFF", f: "#f0e58d", func: doSomething},
  {dc: [282,31,10], ss: 1, s: "#FFFFFF", f: "#559ab5", func: doSomethingElse},
  {dc: [338,47,10], ss: 1, s: "#FFFFFF", f: "#f0e58d", func: doYetAnotherThing},
  // ...
];

Using symbols (ss,s,f,dc`) rather than indexes usually makes things easier to maintain over time. Longer, more meaningful names can be even more useful.


Regarding your question about creating objects with methods on them, and avoiding using globals, here's a fairly standard thing one might do:

(function() {
    var stuff = {
        data: "Some value",

        method1: function() {
            // Do something, can reference data as `stuff.data`, e.g.:
            console.log(stuff.data);
            // If method1 is called via `stuff` (e.g., `stuff.method1()`),
            // it can also use `this.data`:
            console.log(this.data);
        },

        method2: function() {
            // Do something else...
        }
    };

    var otherStuff = {
        data: "Some other value",

        method3: function() {
            // Do something entirely different, possibly call `stuff.method2`:
            stuff.method2();
        }
    };

    // A standalone utility function
    function utilityFunction(arg) {
        return arg.toUpperCase();
    }

    stuff.method1();
    stuff.method2();

    otherStuff.method3();
})();

The outermost anonymous function there is a scoping function, keeping the things declared within it private. stuff, moreStuff, and utilityFunction are all in-scope within that function, but not outside it.

Sometimes you may do that, but want to expose one symbol (this is usually called the "revealing module pattern" because you only reveal the things you want exposed:

var stuff = (function() {
    // ...all that stuff above...

    return stuff;
})();

ES2015 makes those method declarations more concise:

// ES2015 (aka ES6) and higher
var stuff = {
    method1() {
        // Do things here...
    }
};

...and so on.

Upvotes: 2

Related Questions