Damian Chrzanowski
Damian Chrzanowski

Reputation: 479

Use an array of objects to construct a new object that will call functions declared in the original array

I am using a JS router for my new website project. The router accepts an object: the Key is the URL address and the Value is a function that will get called when the address is reached.

It gets messy pretty quickly with the amount pages and sub pages growing bigger and bigger, especially considering that different pages need to call different initialization functions (initAccordion or initDataTables, etc, etc).

Just to keep it neat I thought of creating an array of objects that will hold the page names and the init functions that need to be called after the page is loaded. However the solution I use uses eval:

function initFoo() {
    console.log("initFoo says hi");
}

function initBar() {
    console.log("initBar says hi");
}

var pages = [
    {
        name: "home",
        init: ["initFoo", "initBar"]
    },
    {
        name: "people",
        init: ["initBar"]
    }
];

var routerPaths = {};

for (var page in pages) {
    var url = pages[page].name;
    var init = pages[page].init;
    // construct the string for eval()
    var objStr = "routerPaths['" + url + "'] = function() {";
    for(var item in init) {
        objStr += init[item] + "();";
    }
    objStr += "};";

    eval(objStr);
}

routerPaths.home();  // as expected: initFoo says hi initBar says hi
routerPaths.people();  // as expected: initBar says hi

This all fine, but is there a way to perhaps have the pages.init array without the quotes and the object creator NOT using eval? So that the values would look like this init: [initFoo, initBar].

So my questions really are: Is there a way of creating the new routerPaths object without constructing a string and then running eval on it? Should I just stick with what I have?

Keep in mind that routerPaths.home, routerPaths.people need to be functions that call the functions from pages.init.

Upvotes: 0

Views: 41

Answers (2)

georg
georg

Reputation: 214949

There's no need to reference functions by their string names. Just use a function name like any other variable. var pages = [ { name: "home", init: [initFoo, initBar] }, { name: "people", init: [initBar] } ];

and write your init stubs as ordinary closures (note: it's imperative to use let and not var in the loop).

var routerPaths = {};

for (var page of pages) {
    let init = page.init;

    routerPaths[page.name] = function() {
        init.forEach(function(fn) { fn() })
    }
}

If you can't use ES6 for...of and let, replace them with `forEach:

pages.forEach(function(page) {
    var init = page.init;
    routerPaths[page.name] = function() {
        init.forEach(function(fn) { fn() })
    }
})

Upvotes: 1

yaswanthsvist
yaswanthsvist

Reputation: 104

const initFoo=()=>{
    console.log("initFoo says hi");
}

const initBar=()=> {
    console.log("initBar says hi");
}

let pages = [
    {
        name: "home",
        init: [initFoo, initBar]
    },
    {
        name: "people",
        init: [initBar]
    }
];

let routerPaths = {
};

for (let page in pages) {
    let url = pages[page].name;
    let init = pages[page].init;
    Object.defineProperty( routerPaths ,  url , {
        value: ()=>{
            for(var item in init) {
                init[item]();
            }
        },  
        enumerable: true, // if enumerable is true then only you can see the people,home properties if you console.log
                          //if enumerable set to false you can not see the the property but you can still call routerPaths.people and it just works fine
    });
}
console.log(routerPaths);

routerPaths.home();  // as expected: initFoo says hi initBar says hi
routerPaths.people();  // as expected: initBar says hi

Upvotes: 0

Related Questions