I got a data-attribute that got a string value, from that I would like to be able to get the specific json object (the value of the string). So something like this.
The example below is the stripped version (enough to make the point across), check this fiddle for full version of what I'm trying to do.
JSFiddle
<div class="js-carousel" data-conf='{"componentSettings": "CarouselSettings" }'>...</div>
or
<div class="js-carousel" data-conf='{"componentSettings": "CarouselConfig.hero" }'>...</div>
and I got this json objects that got the settings for my js module (in this case it's Slick carousel)
var CarouselSettings = {
accessibility: false,
autoplay: true
};
var CarouselConfig = {
hero: {
accessibility: false,
autoplay: true
},
product: {
accessibility: true,
autoplay: false
}
};
I'm able to have a setting with a string value and to trigger a function.
"mediaQuery": "isBase2Medium"
and that would trigger a mediaquery control function. So I thought maybe I could have a similar approach to this?
Oh my God,the code is too long,I finally find the point of entry to insert eval(...),You can try this solution by use eval(...):
function applySettings(settings) {
var componentSettings = settings.componentSettings;
componentSettings = typeof componentSettings == 'string' ? eval('(' + componentSettings + ')') : componentSettings;
settings.componentSettings = componentSettings;
return settings;
}
function getSettings($element, settings) {
settings = settings === undefined ? applySettings($element.data(elementConf)) : settings;
....
return settings;
}
I have updated your fiddle to do what (I think) you want to achieve:
https://jsfiddle.net/8a4x5y2m/3/
Please note the following changes I made:
The major change is in the getSettings() function. There I introduced a check to see if one particular setting (componentSettings) is declared as an object in the window (global) scope, and if it does, then it replaces the string parameter with the object. Which brings us to point (2)...
You are declaring your configuration objects in global scope using var, but in jsFiddle your whole script is put in a window.onload handler which makes these variables local to the handler. I removed the var keyword to make the parameters global, thus exposing them to the window object, but this is bad practice. Consider putting them either in a global configuration object, and then in the getSettings() method above, replace with window[] with myGlobalConf[].
Also, please note that I only test the componentSettings field. If you want to test every single parameter, then you'll have to iterate over them and do the same check for each.
Related
I avoid using eval() or Functions created from a string. But when I need to run some subset of Javascript that can be entered by a user I'm tempted to use it just because it will save me much work writing a lexer/parser and interpreter.
Say I'd like to run this code:
a.toLowerCase() === 'xyz' || b == 1 || /pqr/.test(c)
the native approach would be to pass it into eval() like this:
with({a: ..., b: ..., c: ...}) {
ret = eval(code);
}
I cannot be sure that code always contains uncritical code like the above. This opens the possibilities to run malicious code.
I thought of passing an object re-defining critical Browser objects to with besides the actual data like:
var obj = {
// list incomplete ;)
console: true, XMLHttpRequest: true, document: true, window: true, addEventListener: true, removeEventListener: true, parent: true, top: true, history: true, ...,
// actual data
a: ..., b: ..., c: ...
};
with (obj) {
...
}
When running code within with access to the objects/methods is not possibe.
I know that it's still possible to indirectly access those methods if they are indirectly accessed though another object/function that is not re-defined. Let's assume I re-define these too.
Is it secure to sandbox code with a suffient list of objects and functions as content object?
What would be remaining the attack vectors in this case?
Edit 1:
The code should run within Firefox, Chrome, IE (10+), Opera, Safari.
No, this is not secure.
No matter what you do with your code's execution environment using with, it is still possible to retrieve the "real" global object using the following trick:
var window = (function(){ return this }).call(undefined);
This works because Function.call will use the global object as the value of this if it is explicitly passed as undefined or null.
If the shadowed variables were deleted ...
alert([1, window, document]);
var obj = {
document: true, window: true
};
with (obj) {
alert([2, window, document]);
delete window;
delete document;
alert([3, window, document]); //restored
}
Additionally if you exposed any DOM elements the document/window objects could be reached via ownerDocument/defaultView.
You should make use of a global (and "static") function to avoid giving access to other unwanted/private variables (i.e. a sub-function has access to all the variables of the parent function).
Second you want to remove a few keywords from the string to be evaluated to avoid problems as described by duskwuff and Alex K. Something like this:
function exec(e)
{
e = e.replace(/new/, "new_", "g")
.replace(/delete/, "delete_", "g")
.replace(/function/, "function_", "g")
.replace(/throw/, "throw_", "g")
.replace(/this/, "this_", "g")
.replace(/var/, "var_", "g")
.replace(/eval/, "eval_", "g");
obj = { ... };
with(obj)
{
eval(e);
}
}
Note that may not work in strict mode. As mentioned by Bergi in a comment, you could also protect the variables in obj and make them non-deletable so you cannot replace them.
The replace() can include many more things... you may want to look closer at what you are trying to achieve. If the evaluation string is expected to just be an expression, then all keywords should be removed (Except true and false and null). You may also want to remove a few other functions. Here I only removed eval.
If you'd like to only match words so the word anew does not match new you can use the \b flag in the regex. I do not know how compatible this flag is across browsers.
e.replace(/\bnew\b/, "new_", "g");
This would match new but not anew.
I have an object that I am passing to a function, that I am trying to figure out if the property exists or not, and when it doesn't, ignore it.
The problem is I keep getting false even when the property is there. For sake of example, I will use an object I posted on another question earlier today...
var myObj = {
something1_max: 50,
something1_enabled: false,
something1_locked: true,
something2_max: 100,
something2_enabled: false,
something2_locked: true,
something3_max: 10,
something3_enabled: true,
something3_locked: true
}
which gets passed to a function like: buildRetentionPolicyStr('something2', myObj);
So far I’ve got everything I need with this function working perfectly. Until I tried it on live data and realized on the occasion, properties I thought were static and there with defaults otherwise aren't always actually there. So I need to do something I assume with hasOwnProperty() somehow. So in my function I can set a default of my own where if the property exists, use it..
I.e.:
function buildRetentionPolicyStr(theScope, obj)
{
var myVar = 0;
if(obj.hasOwnProperty(theScope + '_enabled'))
{
myVar = obj[theScope + '_enabled'];
}
}
In my current test case, the object does in fact exist, so I know that to be true. However, when I do (right above the if statement):
console.log(obj.hasOwnProperty(theScope + '_enabled'));
// Or
console.log(obj.hasOwnProperty([theScope + '_enabled']));
I get this output respective to the order above:
false
// Or
["something2_enabled"]
What is, if there is one, the proper way to check to see if the property exists in this fashion?
A simple way to do that is to run typeof against your property:
obj = { xxx: false }
typeof obj.xxx // 'boolean'
typeof obj.yyy // 'undefined'
I ended up doing a review of my code to figure out overall that I had some mix matched cases. While I was in all doing what I should have, I overwrote one of my variables and caused the object I was looking for to essentially to end up going missing. So in fact false was correct.
So to verify the how or which was proper for me in my case.
obj.hasOwnProperty([theScope+'_enabled']);
was the proper way.
I'm building a phonegap project using jQuery mobile.
I have a javascript object that I'm iterating through.
Currently the problem is this:
Below is a method in my model object. It is self recursing, and once called, will recurse through itself to the next level every time a user clicks on a list item generated by the previous level of the object.
What I am battling with is passing the iterated segment, b, into the method itself as an object. For some reason this is returned as a string called [Object], and not the object itself.
This function does work as it's displaying the first level, but something about the "firstString" string I am creating for each child seems to be turning my object into a string named object. I have removed the quotes, placed the object in braces, to no avail.
Would anyone have any idea why this is happening, I'm obviously missing something important regarding passing objects into methods whose call is generated as a string...
My code is below, and line causing the issue is firstString+="model.recurseAppTree('"+b+"');";
recurseAppTree: function(AppTree)
{
$.each(AppTree, function(a,b)
{
var firstString='<li data-role="list-divider" role="heading" data-theme="b">'+b.DisplayValue+'</li>';
if(b.Children != null)
{
$.each(b.Children, function(c,d)
{
firstString+="<li data-theme='c'><a data-transition='slide' id='id-"+d.IdValue+"' href='javascript:void(0);'>"+d.DisplayValue+"</a></li>";
firstString+="<script>";
firstString+="$('#id-"+d.IdValue+"').click(function(){";
firstString+="model.recurseAppTree('"+b+"');";
firstString+="});";
firstString+="</script>";
});
}
$("#selectview").html(firstString);
$("#selectview").listview('refresh', true);
});
},
It's just normal.
You use an object in a string context by the concatenation with +. This tells JS to implicitely cast the object to a string.
b = {}
alert(typeof b) // object
alert(typeof (''+b)) // string
You should use event delegation for your gui
1- Add a (common) class to your '' tags, e.g. unrollLink :
var firstString='<li ...><a class="unrollLink" ...></a></li>"
2- Choose a node in your html, which is a parent of all your "tree" nodes, and will always be present in your html. Delegate the click handler to this node :
$('#selectview').on('click', '.unrollLink', function(){
//this === clicked link - write a function which returns the node you want based on the "id" you set
var myNode = getNode( this.id );
model.recurseAppTree( myNode );
});
3- change your function to produce the adequate html. You don't need to add code for the click events :
recurseAppTree: function(AppTree)
{
$.each(AppTree, function(a,b)
{
var firstString='<li data-role="list-divider" role="heading" data-theme="b">'+b.DisplayValue+'</li>';
if(b.Children != null)
{
$.each(b.Children, function(c,d)
{
// add the class you chose to the clickable items :
firstString+='<li data-theme="c"><a class="unrollLink" data-transition="slide" id="id-'+d.IdValue+'" href="javascript:void(0);">'+d.DisplayValue+'</a></li>';
});
}
$("#selectview").html(firstString);
$("#selectview").listview('refresh', true);
});
},
I have made an interesting observation. When trying to update an array that is stored in the Meteor session storage, the following code will not propagate the changes:
var tags = Session.get("Tags");
tags.push("a");
Session.set("Tags", tags);
But if I change the first line to use Session.get("Tags").slice(), everything depending on the session will update accordingly. I guess this is due to the fact that Meteor tests some references for equality and therefore does not update anything.
Is there a better way to manage lists stored in the meteor session store?
If I now try to remove an element from the collection (using array.remove() from here), the behavior turns out to be a bit ... of ... I am doing this inside a Meteor template event, the code looks like this:
"click .taglist li" : function(e) {
var tags = Session.get("Tags").slice();
var index = cardTags.indexOf(this);
Meteor._debug(Session.get("Tags").slice().indexOf("a"));
Meteor._debug("Removing tag \"" + this + "\", index: " + index, ", typeof(this) = " + typeof(this).toString());
tags.remove(index);
Session.set("Tags", tags);
}
This outputs:
1
Removing tag "a", index: -1, typeof(this) = string
So somehow, the cardTags.indexOf(this); statement seems to return -1 for almost any case. I guess I am doing something fundamentally wrong, as I am quite now to javascript, but somehow I can not figure out whats going on here.
Why will those two calls to indexOf() behave different?
I believe this is the same as this situation in Backbone.js. In order for the change event to be triggered, Meteor needs to have a new reference for the array, not just an updated copy of the old one.
In brief, in order to have the 'correct' behaviour, you'll need to clone the array, make the changes you want, and then do Session.set('foo', myCopiedArray).
In short: Use var index = cardTags.indexOf(this.toString()); instead.
Long version:
When using strings in JavaScript, those are strings, whereas typeof 'test' returns string.
Let's take a look at the following code in order to get find out another way to represent strings in JavaScript:
var func = function () {
return this;
};
console.log(func.call('test'));
The console (at least FireBug) won't show us "test", but instead it shows String {0="t", 1="e", 2="s", 3="t" }. typeof would return "object".
The content of the this statement seems to need to be an object. In order to convert a string into a "String" object we can do console.log(new String('test'));, which is the same as the previously logged value.
To convert a string object into a string (data type), just use its prototype toString.
Because jQuery is a widely used and mature collaborative effort, I can't help but to look at its source for guidance in writing better Javascript. I use the jQuery library all the time along with my PHP applications, but when I look under the hood of this rather sophisticated library I realize just how much I still don't understand about Javascript. Lo, I have a few questions for the SO community. First of all, consider the following code...
$('#element').attr('alt', 'Ivan is SUPER hungry! lolz');
vs
$('#element').attr({'alt': 'Ivan is an ugly monster! omfgz'});
Now, is this to say that the attr() method was designed to accept EITHER an attribute name, an attribute name and a value, or a pair-value map? Can someone give me a short explanation of what a map actually is and the important ways that it differs from an array in Javascript?
Moving on, the whole library is wrapped in this business...
(function(window, undefined) { /* jQuery */ })(window);
I get that the wrapped parentheses cause a behavior similar to body onLoad="function();", but what is this practice called and is it any different than using the onLoad event handler? Also, I can't make heads or tails of the (window) bit there at the end. What exactly is happening with the window object here?
Am I wrong in the assessment that objects are no different than functions in Javascript? Please correct me if I'm wrong on this but $() is the all encompassing jQuery object, but it looks just like a method. Here's another quick question with a code example...
$('#element').attr('alt', 'Adopt a Phantom Cougar from Your Local ASPCA');
... Should look something like this on the inside (maybe I'm wrong about this)...
function $(var element = null) {
if (element != null) {
function attr(var attribute = null, var value = null) {
/* stuff that does things */
}
}
}
Is this the standing procedure for defining objects and their child methods and properties in Javascript? Comparing Javascript to PHP, do you use a period . the same way you would use -> to retrieve a method from an object?
I apologize for this being a bit lengthy, but answers to these questions will reveal a great deal to me about jQuery and Javascript in general. Thanks!
1. Method overloading
$('#element').attr('alt', 'Ivan is SUPER hungry! lolz');
vs
$('#element').attr({'alt': 'Ivan is an ugly monster! omfgz'});
var attr = function (key, value) {
// is first argument an object / map ?
if (typeof key === "object") {
// for each key value pair
for (var k in key) {
// recursively call it.
attr(k, key[k]);
}
} else {
// do magic with key and value
}
}
2. Closures
(function(window, undefined) { /* jQuery */ })(window);
Is not used as an onload handler. It's simply creating new scope inside a function.
This means that var foo is a local variable rather then a global one. It's also creating a real undefined variable to use since Parameters that are not specified passes in undefined
This gaurds againts window.undefined = true which is valid / allowed.
the (window) bit there at the end. What exactly is happening with the window object here?
It's micro optimising window access by making it local. Local variable access is about 0.01% faster then global variable access
Am I wrong in the assessment that objects are no different than functions in Javascript?
Yes and no. All functions are objects. $() just returns a new jQuery object because internally it calls return new jQuery.fn.init();
3. Your snippet
function $(var element = null) {
Javascript does not support default parameter values or optional parameters. Standard practice to emulate this is as follows
function f(o) {
o != null || (o = "default");
}
Comparing Javascript to PHP, do you use a period . the same way you would use -> to retrieve a method from an object?
You can access properties on an object using foo.property or foo["property"] a property can be any type including functions / methods.
4. Miscellanous Questions hidden in your question
Can someone give me a short explanation of what a map actually is and the important ways that it differs from an array in Javascript?
An array is created using var a = [] it simply contains a list of key value pairs where all the keys are positive numbers. It also has all the Array methods. Arrays are also objects.
A map is just an object. An object is simply a bag of key value pairs. You assign some data under a key on the object. This data can be of any type.
For attr, if you give an object instead of a key value pair it will loop on each property.
Look for attr: in jQuery's code, then you'll see it use access. Then look for access: and you will see there is a check on the type of key if it is an object, start a loop.
The wrapping in a function, is to prevent all the code inside to be accessed from outside, and cause unwanted problems. The only parameters that are passed are window that allow to set globals and access the DOM. The undefined I guess it is to make the check on this special value quicker.
I read sometimes jQuery but I didn't start with it, may be you should get some good books to make you an idea first of what some advanced features Javascript has, and then apply your knowledge to the specifics of jQuery.
1 - Yes attr can accept a attribute name for getting a value, a name and a value for setting one value or a map of attribute names and values for settings more than one attribute
2 - A map is basically a JavaScript object e.g:
var map = {
'key1' : 'value1',
'key2' : 'value2'
};
3 - (function(window, undefined) { /* jQuery */ })(window); is something called an anonymous function as it doesn't have a name. In this case it also executes straight away.
A simple example would be:
function test(){
...
}
test();
//As an anonymous function it would be:
(function(){
...
}();
//And it you wanted to pass variables:
function test(abc){
...
}
test(abc);
//As an anonymous function it would be:
(function(abc){
...
}(abc);
this would make it different to the load event, as it is a function not an event.
4 - window is passed as a variable, as it is used internally within jQuery
5 - Objects and functions the same, as everything in JavaScript is an object. jQuery does something like this:
var obj = {
"init" : function(){
}
}
6 - Yes you can use . to retrieve a value on an object but you can also use [] e.g:
var map = {
"test" : 1
}
map.test //1
map["test"] //1
I hope this answers your many questions, let me know if I've missed anything out.
jQuery 1.6.1
The test is typeof key === "object"
if that is true, then you passed a { .... }
jQuery.fn.extend({
attr: function( name, value ) {
return jQuery.access( this, name, value, true, jQuery.attr );
},
// Mutifunctional method to get and set values to a collection
// The value/s can be optionally by executed if its a function
access: function( elems, key, value, exec, fn, pass ) {
var length = elems.length;
// Setting many attributes
if ( typeof key === "object" ) {
for ( var k in key ) {
jQuery.access( elems, k, key[k], exec, fn, value );
}
return elems;
}
// Setting one attribute
if ( value !== undefined ) {
// Optionally, function values get executed if exec is true
exec = !pass && exec && jQuery.isFunction(value);
for ( var i = 0; i < length; i++ ) {
fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
}
return elems;
}
// Getting an attribute
return length ? fn( elems[0], key ) : undefined;
},