I'd come to a situation where I had to get the object value dynamically from an array having object keys coming from an api. I came to this approach by using eval.
class App extends React.Component {
constructor() {
super();
this.state = {
title: 'Developers',
descp: 'They are just amazing! JK',
names: ['title', 'descp']
}
}
getVal(objKey) {
let { title, descp } = this.state;
return eval(objKey);
}
render() {
let {names} = this.state;
return (
<div>
<h2>{this.getVal(names[0])}</h2>
<div>{this.getVal(names[1])}</div>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
The above code works correctly.
Another approach I found later:
render() {
let {names} = this.state;
return (
<div>
<h2>{this.state[names[0]]}</h2>
<div>{this.state[names[1]]}</div>
</div>
)
}
Outputs the same result. But my question is that if I use eval with the following case, is it a good approach to do so?
Eval should be avoided as it can be very dangerous. You can safely replace your eval call with accessing property via bracket notation.
getVal(objKey) {
if(this.state.hasOwnProperty(objKey)){
return this.state[objKey];
} else {
// handle missing property
}
}
Eval is generally avoided as it allows the client to insert and evaluate their own expressions in your code.
That being said, JavaScript being a client side language already allows full access to the user, so there isn't really a good reason not to use it.
As long as the user can only mess with their own session, i wouldn't worry. Security should be handled server side anyway so: Beware but don't simply ignore Eval.
EDIT 1 - Defending Eval
The comments pointed out some issues, mainly Performance/Optimization impact, which this answer explains in depth. Basically, since it's Just-In-Time compiling anyway, you don't really lose that much in terms of performance.
As for an example on a use case, here is a template example i whipped up, which also uses the controversial with statement:
var Template = /** #class */ (function () {
function Template(html) {
this.html = html;
}
Template.prototype.apply = function (params, returnDOMObject) {
if (params === void 0) { params = {}; }
if (returnDOMObject === void 0) { returnDOMObject = false; }
with (params) {
var html = eval('`' + this.html.replace(Template.regexes.encapsulated, function (n) {
return n
.replace(Template.regexes.start, '${')
.replace(Template.regexes.end, '}');
}) + '`');
}
if (returnDOMObject) {
return document.createRange().createContextualFragment(html);
}
return html;
};
Template.regexes = {
encapsulated: new RegExp('{{.*?}}', 'igm'),
start: new RegExp('\{{2,}', 'igm'),
end: new RegExp('\}{2,}', 'igm')
};
return Template;
}());
//TEST
var persons = [
{ name: "Peter", age: 25 },
{ name: "Ole", age: 55 },
];
var templates = [];
var container = document.body.appendChild(document.createElement("div"));
var leftBox = container.appendChild(document.createElement("div"));
var rightBox = container.appendChild(document.createElement("div"));
leftBox.style.width = rightBox.style.width = "50%";
leftBox.style.height = rightBox.style.height = "500px";
leftBox.style.cssFloat = rightBox.style.cssFloat = "left";
var leftList = leftBox.appendChild(document.createElement("select"));
leftBox.appendChild(document.createElement("br"));
var leftText = leftBox.appendChild(document.createElement("textarea"));
leftText.style.width = "100%";
leftText.style.resize = "vertical";
var rightOutput = rightBox.appendChild(document.createElement("div"));
function updateLists() {
leftList.innerHTML = '';
for (var i = 0; i < templates.length; i++) {
var template = templates[i];
var option = document.createElement("option");
option.value = option.innerHTML = template.name;
leftList.appendChild(option);
}
}
var h1Template = new Template("<h1>{{name}}</h1>");
var h2Template = new Template("<h2>{{age}} is no age!</h2>");
var pTemplate = new Template("<p>{{name}} may be {{age}}, but is still going strong!</p>\n<p>(When he's {{age*2}} though...)</p>");
var personTemplate = new Template("<p>\n{{ h1Template.apply(params) }}\n{{ h2Template.apply(params) }}\n{{ pTemplate.apply(params) }}\n</p>");
templates.push({ name: "personTemplate", template: personTemplate });
templates.push({ name: "h1Template", template: h1Template });
templates.push({ name: "h2Template", template: h2Template });
templates.push({ name: "pTemplate", template: pTemplate });
function updateOutput() {
rightOutput.innerHTML = '';
for (var pi = 0; pi < persons.length; pi++) {
var person = persons[pi];
rightOutput.appendChild(personTemplate.apply(person, true));
}
}
function leftTextChange() {
templates.find(function (val) { return val.name === leftList.value; }).template.html = leftText.value;
updateOutput();
}
function leftListChange() {
leftText.value = templates.find(function (val) { return val.name === leftList.value; }).template.html;
}
updateLists();
leftList.onchange = leftList.onkeyup = leftListChange;
leftText.onchange = leftText.onkeyup = leftTextChange;
leftListChange();
updateOutput();
Here the users input text is being interpreted live, while the user is watching. No security concerns, since it's all client side.
Related
I'm trying to come up with some very reusable code that will look up and perform variable substitutions within a string.
The example string below contains a $$ reference to a variable. Format is varname.key.
I want the subText() function to be reusable. The issue I'm having is repvars themselves can require substitution. The code hasn't finished substituting the example text and I'm asking it to substitute the repvars.cr by calling the same function. This seems to through it off. I'm saying that because if I do it separately in works.
var exampleText = "A string of unlimited length with various variable substitutions included $$repvars.cr$$";
var repvars = {
cr: 'Copyright for this is $$repvars.year$$',
year: '2019'
}
function subText(text) {
var subVars = findSubs(text);
return makeSubs(text, subVars);
}
function findSubs(theText) {
var subarr = [];
while (theText.indexOf('$$') > -1) {
theText = theText.substring(theText.indexOf('$$') + 2);
subarr.push(theText.substring(0, theText.indexOf('$$')));
theText = theText.substring(theText.indexOf('$$') + 2);
}
return subarr;
}
function makeSubs(text, subs) {
for (var s = 0; s < subs.length; s++) {
var subst = getSubVal(subs[s]);
text = text.split("$$" + subs[s] + "$$").join(subst);
}
return text;
}
function getSubVal(subvar) {
var subspl = subvar.split('.');
switch (subspl[0]) {
default:
return processRepVar(subspl[1]);
}
}
function processRepVar(rvName) {
var data = getRepVarData(rvName);
if(data.indexOf('$$') > -1) {
subText(data);
} else {
return data;
}
}
function getRepVars() {
return repvars;
}
function getRepVarData(key) {
return getRepVars()[key];
}
subText(exampleText);
Aren't you just missing a return here?
function processRepVar(rvName) {
var data = getRepVarData(rvName);
if(data.indexOf('$$') > -1) {
subText(data);
} else {
return data;
}
}
Changing subText(data) to return subText(data); makes your code work for me.
Working jsfiddle: https://jsfiddle.net/uzxno754/
Have you tried regular expressions for this?
function replace(str, data) {
let re = /\$\$(\w+)\$\$/g;
while (re.test(str))
str = str.replace(re, (_, w) => data[w]);
return str;
}
//
var exampleText = "A string with variables $$cr$$";
var repvars = {
cr: 'Copyright for this is $$year$$',
year: '2019'
}
console.log(replace(exampleText, repvars))
Basically, this repeatedly replaces $$...$$ things in a string until there are no more.
CONTEXT
I have one Base class called Entity and User and Group are both derived from this object; If I instantiate a user with the ID of 12 for example, the next time I try to do that it returns the same object. I store these in a prototype items variable. To keep these item variables separate I have to declare the User And Group functions separately although they contain the same code.
CODE
Application.prototype.Entity = function() {
};
Application.prototype.Entity.prototype.print = function() {
var container = $("<div class='user-tag'></div>");
container.append("<a class='friend_list ellipsis_overflow' style='background-image:url(\"" + this.entity.pic.icon + "\");'"
+ "href ='user?id=" + this.entity.id + "'>" + this.entity.name + "</a>");
return container;
};
//HOW TO GET RID OF THIS "REPETITION"
Application.prototype.User = function(entity) {
this.entity = entity;
this.entity.pic = this.entity.pic || Application.prototype.default.pic;
if (this.items[this.entity.id]) {
return this.items[this.entity.id];
} else {
this.items[this.entity.id] = this;
}
};
Application.prototype.Group = function(entity) {
this.entity = entity;
this.entity.pic = this.entity.pic || Application.prototype.default.pic;
if (this.items[this.entity.id]) {
return this.items[this.entity.id];
} else {
this.items[this.entity.id] = this;
}
};
// END REPEAT
Application.prototype.Group.prototype = new Application.prototype.Entity();
Application.prototype.User.prototype = new Application.prototype.Entity();
//Application.prototype.User.prototype.constructor = Application.prototype.Entity;
//Application.prototype.Group.prototype.constructor = Application.prototype.Entity; - these don't seem to work
//THESE items VARIABLES HAVE TO REMAIN SEPARATE
Application.prototype.Group.prototype.items = {};
Application.prototype.User.prototype.items = {};
QUESTION
I specifically would like to rid my code of the repetition mentioned above, but if you see any other unnecessary code, please comment. Thanks!
Something like this?
function userAndGroupConstructor(entity) {
this.entity = entity;
this.entity.pic = this.entity.pic || Application.prototype.default.pic;
if (this.items[this.entity.id]) {
return this.items[this.entity.id];
} else {
this.items[this.entity.id] = this;
}
}
Application.prototype.User = function() {
return userAndGroupConstructor.apply(this, arguments)
}
Application.prototype.Group = function() {
return userAndGroupConstructor.apply(this, arguments)
}
You get distinct constructors with distinct prototypes, but avoid duplication.
You can do this:
Application.prototype.Group = Application.prototype.User;
Since Application.prototype.User contains the function reference, you can just assign it to Application.prototype.Group.
Including the declaration of the 2d Array, how can I make the following recursive? I would like it to iterate until there are no "children()".
self.buildRelationships = function () {
relationships = [];
relationships[0] = [];
relationships[1] = [];
relationships[2] = [];
relationships[3] = [];
relationships[0][0] = self.name();
$.each(self.children(), function(i, child) {
relationships[1][i] = child.name();
$.each(child.children(), function (j, grandchild) {
relationships[2][j] = grandchild.name();
$.each(grandchild.children(), function (k, greatgrandchild) {
relationships[3][k] = greatgrandchild.name();
})
})
})
}
Visualization of objective
Given this data:
Me!
Bob
James
Dale
Steve
Bill
Fred
Owen
Patrick
.children() returns immediate children only:
Bob.children() would return "James" and "Steve".
Me!.children() would return "Bob" and "Bill".
The accepted answer would create data that looked like this:
relationships[0] = "Me!" //this will always have a length of only 1
relationships[1] = "Bob", "Bill"
relationships[2] = "James", "Steve", "Fred"
relationships[3] = "Dale", "Owen", "Patrick"
self.buildRelationships = function () {
relationships = [];
relationships[0] = [];
relationships[1] = [];
relationships[2] = [];
relationships[3] = [];
relationships[0][0] = self.name();
var recursive = function(level) {
return function(i, child) {
relationships[level] = relationships[level] || [];
relationships[level].push(child.name());
$.each(child.children(), recursive(level + 1));
}
}
$.each(self.children(), recursive(1));
}
Here's how I would approach it:
self.buildRelationships = function () {
var relationships = [];
addToLevel(self, 0);
return relationships;
function addToLevel(node, levelIndex) {
$.each(node.children(), function (i, child) {
if (relationships.length <= levelIndex) { relationships.push([]) }
relationships[levelIndex].push(child.name());
addToLevel(child, levelIndex + 1);
});
}
}
(I prefer using Underscore/Lodash for data processing, and reserving jQuery for DOM stuff. But jQuery only was assumed.)
&for fun, here's a recursive jQuery plugin that can be invoked on a container that may or may not be a singleton "eve" creature (dependent upon whether or not the container element defines a name attribute) :
jsfiddle
$.fn.getImps=function(imps, i)
{
if (!$.isArray(imps))
throw new Error("oook: getImps requires an empty array!");
i=(typeof i !== "undefined" ? i : 0);
if (this.is("[name]"))
(imps[i] || (imps[i]=[])).push(this.attr("name")) && i++;
this.children().each(function(){ $(this).getImps(imps,i) });
}
btw: since this is a breadth-first traversal, & typically implemented using non-recursive queue-based algos..:
jsfiddle
//expects a singleton container id
var getImps=function(id)
{
var imps=[];
for(var o=$(id), a; (o=o.children("[name]")).length && imps.push(a=[]);)
o.each(function() { a.push($(this).attr("name")) });
return imps;
}
(= but only saying.. =)
Oook: um your creatures appear to be undergoing some kind of asexual reproduction ~are they maybe.. aphids?
I'm starting a new Backbone app, and I've been quite fond of the javascript class inheritance system of Sencha Touch 2.0, basically i want to be able to do this :
Helium.define('Application', {
namespace: 'Helium',
constructor: function() {
console.warn('Helium');
}
});
Helium.define('Application', {
namespace: 'MyApp',
extend: 'Backbone.Events',
routers: ['Cards'],
constructor: function() {
console.warn(this);
this.callParent();
console.warn('MyApp');
//console.warn(Helium.getDisplayName(arguments.callee));
}
});
I'm stuck on the prototype chain definition (to make the this.callParent() work) :
var p = Object.create(extend);
var o = _.extend(p, data);
/*function() {
var _o = _.extend(p, data);
_o.constructor.apply(o, arguments);
return _o;
};*/
o.prototype.superclass = p;
o.prototype.callParent = function() {
console.warn(p);
};
Here is the complete micro-implementation so far :
So far, i have done this :
_.mixin({
resolve : function(path, base, separator) {
var parts = path.split('.' || separator),
key = parts.pop();
base = base || window;
while (parts.length) {
part = parts.shift();
base = base[part] = base[part] || {};
}
base[key] = base[key] || {};
return base[key];
}});
Helium = {
define: function(className, data) {
var base = window,
extend = data.extend || {constructor : function() {}},
namespace = data.namespace || null;
if(namespace) {
window[namespace] = window[namespace] || {};
base = window[namespace];
}
if(_.isString(extend)) extend = _.resolve(extend);
if(!extend) throw 'Extend error';
var parts = className.split('.'),
key = parts.pop();
while (parts.length) {
part = parts.shift();
base = base[part] = base[part] || {};
}
delete data.extend;
//delete data.namespace;
data.$name = key;
data.$className = className;
var p = Object.create(extend);
var o = _.extend(p, data);
/*function() {
var _o = _.extend(p, data);
_o.constructor.apply(o, arguments);
return _o;
};*/
o.prototype.superclass = p;
o.prototype.callParent = function() {
console.warn(p);
};
},
getDisplayName: function(callee) {
console.warn('getDisplayName', [this, arguments]);
console.warn(callee.toString());
}
};
I see what you're going for here but if your end game is really being able to call the parent constructor I think it's a little convoluted to try and impose Sencha's inheritance model on Backbone because it is so simple and lightweight and ultimately it has its own inheritance implementation.
Since Backbone's extend() only exists on Backbone classes I think this is a good resource. Unless I'm misunderstanding you I think something along the lines of MyApp.__super__.initialize() in Backbone would be roughly equivalent to this.callParent() in Sencha Touch.
i am using an iframe ipage in my parentpage. I would like to get the querystring in javascript of the parentpage?
I suggest to you to use my favourite function:
function getQueryString() {
var queryStringKeyValue = window.parent.location.search.replace('?', '').split('&');
var qsJsonObject = {};
if (queryStringKeyValue != '') {
for (i = 0; i < queryStringKeyValue.length; i++) {
qsJsonObject[queryStringKeyValue[i].split('=')[0]] = queryStringKeyValue[i].split('=')[1];
}
}
return qsJsonObject;
}
Just call it from the child window like this and act with the query string as an object.
For example if you have the query string ?name=stack and you want to get it, try:
getQueryString().name
This will return stack.
nice answer from #Marawan. - if it helps anyone...
I extended this to choose the target as a parameter (self / parent)
function getQueryString(target) {
if ( target == 'parent' ) {
var queryStringKeyValue = window.parent.location.search.replace('?', '').split('&');
}
else {
var queryStringKeyValue = window.location.search.replace('?', '').split('&');
}
var qsJsonObject = {};
if (queryStringKeyValue != '') {
for (i = 0; i < queryStringKeyValue.length; i++) {
qsJsonObject[queryStringKeyValue[i].split('=')[0]] = queryStringKeyValue[i].split('=')[1];
}
}
return qsJsonObject;
}
eg.
getQueryString('parent').id; // get iframe parent url ?id=foo
getQueryString().id; // get this url ?id=foo
ES6 implementation:
export const getQueryParameters = () => {
const queryStringKeyValue = window.parent.location.search.replace('?', '').split('&');
return queryStringKeyValue.reduce((acc, curr) => {
const [key,value] = curr.split('=')
return {
...acc,
[key]: value
}
}, {})
}
Usage:
getQueryParameters().name