简体   繁体   中英

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.

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'

How about using eval rather than traversal?

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

   }
};

var x = "settings.another";
eval(x).deep = false;
alert(settings.another.deep);

Would the built in jQuery $().extend() not be exactly what you need?

http://api.jquery.com/jQuery.extend/

*note the second method signature with the first agument of true performs a deep merge...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM