Tuesday, 4 October 2016

Eval really evil?

I've recently had a situation where I needed to parse the options within a select, and it's been causing me to do some little head-scratching… so I thought I'd solve my solutions. The options used non-breaking spaces to indicate their position within a hierarchy, so I needed a way of counting the number of non-breaking spaces a string had within it. This function worked a treat with this:

function howManyRepeated(str, search){
    try{ 
        return str.toLowerCase().split("").sort().join("").match(search).length; 
    }catch(e){ 
        return 0;
    }
}

That meant that I needed a way of generating a regex for non-breaking spaces, this did the job for me:

var nbsp = new RegExp(String.fromCharCode(160), "g");

I also needed a way of removing those non-breaking spaces, so this does that:

function replaceNbsps(str, search) {
    return str.replace(search, "");
}

The main meat of the program involved iterating over the options and generating an array of the names, I wanted each option to hold a further object, which could be empty if it wasn't empty it needed to know whereabouts in the hierarchy it was. After spending some little time adding more and more if statements I clocked that I was adding to a the selectObject and that I was just adding strings to it within square brackets. Eval was the obvious solution to this problem, so this is what I came up with:

var selectObject = {};
$(function() {
    var parent = [];
    var options = $("#select option:not(.default)");
    options.each(function(k, v) {
        var level = howManyRepeated($(v).text(), nbsp) / 2;
        if (level) {
            if (level === 1) {
                parent = [replaceNbsps($(v).text(), nbsp)];
                selectObject[parent[0]] = {};
            }
            if (level > 1) {
                while (parent.length > level - 1) {
                    parent.pop();
                }
                parent.push(replaceNbsps($(v).text(), nbsp));
                var evalString = "selectObject";
                for (var i = 0; i < parent.length; i++) {
                    evalString += '["' + parent[i] + '"]';
                }
                evalString += " = {};";
                eval(evalString);

            }
        }
    });
    console.log(selectObject);
});

It's mainly here so I don't forget how to do it and because I've not blogged in a while. Working JSFiddle is here.

This is what the HTML looked like:

<select id="select">
    <option class="default" selected="selected">All</option>
    <option>&nbsp;&nbsp;One</option>
    <option>&nbsp;&nbsp;Two</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Two point one</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Two point two</option>
    <option>&nbsp;&nbsp;Three</option>
    <option>&nbsp;&nbsp;Four</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Four point one</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Four point one point one</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Four point two</option>
    <option>&nbsp;&nbsp;Five</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Five point one</option>
    <option>&nbsp;&nbsp;&nbsp;&nbsp;Five point two</option>
    <option>&nbsp;&nbsp;Six</option>
</select>

And this is the resulting object:

{
    "One": {},
    "Two": {
        "Two point one": {},
        "Two point two": {}
    },
    "Three": {},
    "Four": {
        "Four point one": {
            "Four point one point one": {}
        },
        "Four point two": {}
    },
    "Five": {
        "Five point one": {},
        "Five point two": {}
    },
    "Six": {}
}