How can I avoid using eval() for the base object - javascript

As I understand it, eval() can be harmful. And it's annoying seeing all the warnings in my JSLint.
I've got a number of functions that are identical for my Wishlist / Shopping Cart. So I'd like to make it dynamic and only have one function of each.
Instead of cart.addItem() and wish.addItem(), I want cartWish.addItem(type).
Inside of cartWish.addItem() I need to access cart.data or wish.data, depending on the type argument.
How can I do this without resorting to eval(type).data?
I tried this[type].data and it didn't seem to work right.

What's the difference between
cart.addItem(...);
wish.addItem(...)
and
cartWish.addItem("cart", ...);
cartWish.addItem("wish", ...);
Seems like the same number of lines of code, and then all you've done is obfuscate what you are really doing. Maybe create a function that takes either a cart or wish object and assume they have the same interface:
function addItem(x, data) {
x.addItem(data);
}
var cart = ...
var wish = ...
addItem(cart, {...});
addItem(wish, {...});
Another option is to create a class:
function Item(type) {
this.type = type;
}
Item.prototype.add = function add(...) {
// ...
};
var cart = new Item("cart");
var wish = new Item("wish");
cart.add(...)
wish.add(...)

It was poor programming on my part. I didn't know that JavaScript tended to make references of objects instead of copies.
So doing...
var myReference=(type == "cart") ? cart.data : wish.data;
myReference[0].name="Bob Dole's Grill";
... will actually change cart.data[0].name outside of the function. And it will do so without making a copy of the cart object in memory.
Note: You could also just pass in the object by reference into the function, but I'm not sure if I can, because I'm sometimes invoking this function from a KnockoutJS click event.

Related

Assign HTML elements ID to document.queryselector shorthand variables in a loop

I am new to Javascript development.
I am trying to assign HTML elements IDs stored in an array to shorthands to be used in my function later.
So that instead of writing :
let addprop = document.querySelector(`#addprop`);
let readprop = document.querySelector(`#readprop`);
let editprop = document.querySelector(`#editprop`);
let footer = document.querySelector(`#footer`);
let association = document.querySelector(`#association`);
I can attribute elements ids that i store in an array like this :
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"] ;
arrayElements.forEach(el => { return(new Function (`${el} = document.querySelector("#${el}");`)()); });
Now, this bit of code works but from what I read here :
Execute JavaScript code stored as a string
This is probably not a good way to do it and also declares global variables.
One problem I encountered is that if I try to directly execute the assignment like this :
el = document.querySelector(`#${el}`);
Then the el value takes the value of the named access ID element (https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object) and breaks the code.
So I resorted to generate a string first then execute it.
I could simply assign each shorthand manually but I spent way too much time trying to make this work and am now left curious as to what would be a good solution or approach for this.
And would the scope limitations for loops simply forbid me to do this without using global variables ?
edit : switched the working code in one line
Possible answer :
1 - does it matter to declare global variables like that ? As these variables already exist globally because of browsers named access for elements IDs.
2 - By kiranvj's answer, a solution can be to store in an object structured as keys being the shortcuts and the full strings being the values, and calling the shortcuts with the object[key] method ; or using destructuring to assign the values to variable directly with :
const {addprop, readprop, editprop, idfooter, assocpatients} = elements;
I feel like I am missing something on this last one but it also seems to work.
In the end I will stick with my first code as condensing the function in one line seems to negate the risks of cross site scripting (?), and global values for the variables assigned though this method anyway already exist because of named access.
You can create a dictionary with all the elements with ID and then destroy it into your variables, ignoring the unused ones.
function getAllElementsWithId() {
let elements = {}
for (let el of document.querySelectorAll('[id]')) {
if (!(el.id in elements)) {
elements[el.id] = el
}
}
return elements
}
let { addprop, readprop, editprop, footer, association } = getAllElementsWithId()
This uses document.querySelectorAll (link to MDN) to get all elements with an ID. Notice that for big pages this could be a performance issue.
Also, what you would usually do is to add them into a container, in this case it seems like a dictionary.
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"]
let elementsId = Object.fromEntries(arrayElements.map(id => [id, document.getElementById(id)]))
This uses Object.fromEntries (link to MDN) to generate the dictionary. Also I'm using document.getElementById (link to MDN) instead of document.querySelector so you don't need to add the hashtag before the id.
If you are concerned about global scope, you can try something like below. Use forEach instead of map . map also work but since you are not handling the return of map, forEach would be a better choice.
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"];
let elements = {};
arrayElements.forEach(el => elements[el] = document.querySelector(`#${el}`));
// access variables like elements.ID-NAME
console.log(elements);
<div id="addprop"></div>
<div id="readprop"></div>
Object destructing can be used if you know the object key name.
example : let {addprop} = element;
Another thing which you might be interested is Automatic global variables
This means a new variable (scoped to window) with the name of element id is created for all the elements in page. See the html5 spec. I would not recommend using it though.
So you don't have to call like document.querySelector('addprop')
addprop variable will have the DOM object.
See this example
// these works due to automatic global varaibles binding
alert(addprop);
console.log(addprop);
<div id="addprop">Some contents</div>

Passing variables into Meteor helpers

I am struggling with what I know is a very basic question related to variable declaration. I've read everything I can find on variables but I don't know if my problem is related to 1) how I am declaring variables or 2) how I am setting the scope of the variables.
To start, my understanding of variables in Meteor is that if I use var, then I am setting file-scope, which would make that variable available to every helper for that particular template. If I do not use var, it will be global and therefore available to the helpers in every template. Is that correct?
The following block of code works fine, returning the correct value in the client:
Template.CompanyFinancials.helpers({
priceEarningsFy1: function () {
var compTicker = this.ticker
var price = Companies.findOne({ticker: compTicker}).capTable.lastClose;
var epsFy1 = Companies.findOne({ticker: compTicker}).fy1.eps;
return (price / epsFy1).toFixed(1)
});
I have dozens of similar calculations throughout this app and many which rely on more variables than this example, so I have been trying to factor out the variables and reuse them in the template, like so:
var compTicker = function() {
return this.ticker;
};
console.log(compTicker);
var price = function(compTicker) {
Companies.findOne({ticker: compTicker}).capTable.lastClose;
};
console.log(price);
var epsFy1 = function(compTicker) {
Companies.findOne({ticker: compTicker}).fy1.eps;
};
console.log(epsFy1);
Template.CompanyFinancials.helpers({
priceEarningsFy1: function (price, epsFy1) {
return (price / epsFy1).toFixed(1)
}
});
With this code, console.log() actually returns the text within each function (e.g., return this.ticker) for each variable, not the value. If I declare the variables without functions, like I’ve done within the helper, it returns undefined for compTicker.
I tried to follow this answer which explains reusable code, but not clear if same use case applies. My variables point to specific fields in the database, not necessarily calculations.
Can anyone help me repair my syntax? I'm writing multiples more code than I need to with my current understanding. Thank you.
EDIT
I also tried declaring the variables the same way they are declared in the helper, but these return undefined.
var compTicker = this.ticker;
console.log(compTicker);
var price = CompaniesFeed.findOne({ticker: this.ticker}).capTable.lastClose;
console.log(price);
var epsFy1 = CompaniesFeed.findOne({ticker: this.ticker}).fy1.eps;
console.log(epsFy1);
RESOLUTION:
Using global helpers and returning multiple values, then using dot notation to access in the template HTML:
Template.registerHelper('priceEarnings',function(){
var ticker = this.ticker;
var company = CompaniesFeed.findOne({ticker: ticker});
return {
peFy1: (company.capTable.lastClose / company.financial.fy1.eps).toFixed(1),
peFy2: (company.capTable.lastClose / company.financial.fy2.eps).toFixed(1)
};
});
<td>{{priceEarnings.peFy1}}x</td>
You might be looking for global helpers. These are helpers which can be reused across all templates.
For your priceEarningsFy1 function for example:
Template.registerHelper('priceEarningsFy1',ticker => {
const company = Companies.findOne({ticker: ticker});
return ( company.capTable.lastClose / company.fy1.eps ).toFixed(1);
});
In this case I've specified that ticker is to be provided as an argument. From a blaze template you would use {{priceEarningsFy1 this.ticker}} for example. To refer to this function from js code use UI._globalHelpers.priceEarningsFy1(ticker)
Note that any local functions you define inside a given file are available to any other functions inside the same file. My pattern is to put all my global helpers in one file sorted by name and then at the bottom add various utility functions for use by the global helpers. This keeps things relatively dehydrated.

pushing json arrays in javascript

I am customizing some jQuery plugin, and I have an error message I can't understand
var totHistory=0;
var positions = new Array();
$('.someclass').each(function(index){
var tmp = $(this).val();
addHistory({id:tmp});
});
function addHistory(obj)
{
/* Gets called on page load for each comment, and on comment submit */
totHistory++;
positions.push(obj.id);
}
At the very first iteration through .someClass, I get this message
Cannot call method 'push' of undefined
Could someone explain why ?
You should either send positions as a parameter or declare it in a scope accesible for addHistory. You should not declare it without the var keyword as that is considered a bad practice.
Try my first suggestion as that one is the only one I can help you with without knowing the structure of your other js code.

How to use parameters from another function [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Get actual HTML values using javascript
so i have two problems here. let me explain what i am trying to do first. I have a page that has values that change on it, however i want to grab the values before they change, keep them, and then once a button is pushed, change the html to the original html. Now first of all my biggest problem is that when i try to uncomment the initial2 function, it just doesnt work. it brings me to the webpage then for some reason the html url tries to change and it says it can not find the page. the second, and more understandable problem for me, is that the function previousaccept i cant get to use the values from the previousnames function.
function previousnames()
{
name= document.getElementById('name').innerHTML;
imagetitle= document.getElementById('imagetitle').innerHTML;
location=document.getElementById('location').innerHTML;
similarities = document.getElementById('similarities').innerHTML;
type = document.getElementById('type').innerHTML;
cost = document.getElementById('cost').innerHTML;
date = document.getElementById('date').innerHTML;
pictureid = document.getElementById('pictureid').src;
}
function previousaccept(name,imagetitle,location,similarities,value,type,cost,date,pictureid)
{
document.getElementById('name').innerHTML = name;
document.getElementById('location').innerHTML = location;
document.getElementById('similarities').innerHTML = similarities;
document.getElementById('type').innerHTML = type;
document.getElementById('cost').innerHTML = cost;
document.getElementById('date').innerHTML = date;
window.alert(pictureid);
document.getElementById('pictureid').src = pictureid;
}
window.onload=initial();
function initial()
{
myvalues;
previousnames;
}
/*
function initial2()
{
myvalues;
previousnames();
}*/
If you set the location (which is window.location), then the browser will go to a new web page. That's what you're doing in the previousnames() function with this line:
location=document.getElementById('location').innerHTML;
If you're trying to have a global variable named location, then give it a different name that isn't already used by the browser.
Also, you should explicitly declare any global variables you intend to use outside of your functions rather than use implicitly declared variables like you are which makes your code very prone to errors.
I think this will do what you want. The key is to make sure that the scope of the variables you are trying to store is such that the functions have access to them all. I do this by defining an empty object dataStore at the start of the onload function, and also defining the 2 other functions within the onload function. Putting all the stored data in a single object is convenient and avoids naming problems (such as the window.location problem noted by the previous answer.)
window.onload = function() {
var dataStore = {};
function getInitialData() {
dataStore = {
name: document.getElementById('name').innerHTML,
imagetitle: document.getElementById('imagetitle').innerHTML,
// and so on...
}
}
function resetData() {
document.getElementById('name').innerHTML = dataStore.name;
document.getElementById('imagetitle').innerHTML = dataStore.imagetitle;
// and so on...
}
getInitialData();
//... then later when you want to reset all the values
resetData();
}​

Moving inline code into function, with object name generation

I am customizing Denis Gritcyuk's Popup date picker.
This pop-up script uses inline Javascript in a href link, to set the selected date into the input field, in the parent window, that is was called for. An example URL looks like:
<a href="javascript:window.opener.document.formname.field.value='03-10-2011';
window.close();">3</a>
The input field name, (e.g. document.formname.field), is passed to the script as a string parameter.
I would like to add things done when that link is clicked (e.g. change background color of field, set flag, etc.). So while this DOES work, it's getting ugly fast.
<a href="javascript:window.opener.document.formname.field.value='03-10-2011';
window.opener.document.formname.field.style.backgroundColor='#FFB6C1';
window.close();">3</a>
How would I move these inline commands into a JS function? This would give me much cleaner URLs and code. The URL would now look something like
3
with a function like (this example obviously does NOT work):
function updateField (str_target, str_datetime) {
var fieldName = "window.opener" + str_target;
[fieldName].value = str_datetime;
[fieldName].style.backgroundColor = '#FFB6C1';
// Set flag, etc.
window.close();
}
So any suggestions on how this can be done, please?
I'd prefer to hide the dom path tracing back from the current window back to the opener. It's appropriate to bake that into the function since the function will always be used in the context of that child popup. Then your function call is cleaner and more readable. Obviously, replace "myField" with the ID of the field you're intending to update.
3
function updateField ( str_date, str_fieldname ) {
var fieldToUpdate = document.getElementById( str_fieldname );
fieldToUpdate.value = str_date;
fieldToUpdate.style.backgroundColor = '#FFB6C1';
// Set flag, etc.
window.close();
}
You're acessing the property incorrectly. Try:
function updateField (str_target, str_datetime) {
var fieldName = window.opener;
str_target = str_target.split('.');
for (var i = 0; i < str_target.length; i++)
fieldName = fieldName[str_target[i]];
fieldName.value = str_datetime;
fieldName.style.backgroundColor = '#FFB6C1';
// Set flag, etc.
window.close();
}
The bracket notation ([]) is only used for properties of objects, not objects themselves. If you found my post helpful, please vote for it.
You can build a string and evaluate it as code using the eval function, but I would recommend against it.
There are a couple of things wrong with your code:
You cannot use the [] operator in a global context, you have to suffix it on an object, so you can say window["opener"] and this will be equivalent to window.opener, but there is no such thing as simply ["window"]
When navigating nested properties, as in window.opener.document you cannot navigate multiple levels using the [] operator. I.e. window["opener.document"] is not allowed. You must use window["opener"]["document"] instead.

Categories

Resources