Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery Plugin - Deep option modification

I am currently working a plugin with a settings variable that is fairly deep (3-4 levels in some places). Following the generally accepted jQuery Plugin pattern I have implemented a simple way for users to modify settings on the fly using the following notation:

$('#element').plugin('option', 'option_name', 'new_value');

Here is the code similar to what I am using now for the options method.

option: function (option, value) {
    if (typeof (option) === 'string') {
        if (value === undefined) return settings[option];

        if(typeof(value) === 'object')
            $.extend(true, settings[option], value);
        else
            settings[option] = value;
    }

    return this;
}

Now consider that I have a settings variable like so:

var settings = {
    opt: false,
    another: {
        deep: true
    }
 };

If I want to change the deep settings I have to use the following notation:

$('#element').plugin('option', 'another', { deep: false });

However, since in practice my settings can be 3-4 levels deep I feel the following notation would be more useful:

$('#element').plugin('option', 'another.deep', false);

However I'm not sure how feasible this is, nor how to go about doing it. As a first attempt I tried to "traverse" to the option in question and set it, but if I set my traversing variable it doesn't set what it references in the original settings variable.

option: function (option, value) {
    if (typeof (option) === 'string') {
        if (value === undefined) return settings[option];

        var levels = option.split('.'),
            opt = settings[levels[0]];
        for(var i = 1; i < levels.length; ++i)
            opt = opt[levels[i]];

        if(typeof(value) === 'object')
            $.extend(true, opt, value);
        else
            opt = value;
    }

    return this;
}

To say that another way: By setting opt after traversing, the setting it actually refers to in the settings variable is unchanged after this code runs.

I apologize for the long question, any help is appreciated. Thanks!


EDIT

As a second attempt I can do it using eval() like so:

option: function (option, value) {
    if (typeof (option) === 'string') {
        var levels = option.split('.'),
            last = levels[levels.length - 1];

        levels.length -= 1;

        if (value === undefined) return eval('settings.' + levels.join('.'))[last];

        if(typeof(value) === 'object')
            $.extend(true, eval('settings.' + levels.join('.'))[last], value);
        else 
            eval('settings.' + levels.join('.'))[last] = value;
    }

    return this;
}

But I really would like to see if anyone can show me a way to not use eval. Since it is a user input string I would rather not run eval() on it because it could be anything. Or let me know if I am being paranoid, and it shouldn't cause a problem at all.

like image 537
Chad Avatar asked Dec 07 '11 17:12

Chad


1 Answers

The issue you're running into here comes down to the difference between variables pointing to Objects and variables for other types like Strings. 2 variables can point to the same Object, but not to the same String:

  var a = { foo: 'bar' };
  var b = 'bar';
  var a2 = a;
  var b2 = b;
  a2.foo = 'hello world';
  b2 = 'hello world';
  console.log(a.foo); // 'hello world'
  console.log(b); // 'bar'

Your traversal code works great until the last iteration of the loop, at which point opt is a variable containing the same value as deep inside the object settings.opt.another. Instead, cut your loop short and use the last element of levels as a key, like

  var settings = {
    another: {
      deep: true
    }
  };

  var levels = 'another.deep'.split('.')
    , opt = settings;

  // leave the last element
  var i = levels.length-1;

  while(i--){
    opt = opt[levels.shift()];
  }
  // save the last element in the array and use it as a key
  var k = levels.shift();
  opt[k] = 'foobar'; // settings.another.deep is also 'foobar'

At this stage opt is a pointer to the same Object as settings.another and k is a String with the value 'deep'

like image 88
Daniel Mendel Avatar answered Sep 28 '22 10:09

Daniel Mendel