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'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With