Changing local variable in JavaScript affects original global with different name - javascript

I have a global declared at top of script:
var g_nutrition_summary = null;
When the user enters the page, I return network data and give this variable a value.
g_nutrition_summary = json.data;
This line is the ONLY assignment of the variable and is never called again (tested with alerts).
I later use that json.data variable to populate a Bar Chart with the plugin Chart.js. The global assignment is for later use.
Underneath the chart, the user can filter certain types of data it displays with a series of checkboxes. So my goal is, to keep an original value of what comes in from the network, and then make a LOCAL COPY of it and alter the COPY (not the global original) and repopulate the chart. Everytime the user checks/unchecks a checkbox, it will call this function and grab the ORIGINAL global (g_nutrition_summary) and re-filter that.
Here is how I do it:
function filter_nutrition_log()
{
alert("1: " + JSON.stringify(g_nutrition_summary));
// reassign object to tmp variable
var tmp_object = g_nutrition_summary;
var food_array = new Array("Grains", "Vegetables", "Fruits", "Oils");
var checked_array = new Array();
// Make an array of all choices that are checked
$(".input-range-filter").each(function()
{
var type = $(this).val();
if ($(this).is(':checked'))
{
checked_array.push(type);
}
});
alert("2: " + JSON.stringify(g_nutrition_summary));
// Loop thru all the 7 choices we chart
$.each(food_array, function(i, val)
{
// find out if choice is in array of selected checkboxes
if ($.inArray(val, checked_array) === -1)
{
// it's not, so delete it from out tmp obj we
// will use to repopulate the chart with
// (we do not want to overwrite the original array!)
delete tmp_object["datasets"][val];
}
});
// Resert graph
alert("3: " + JSON.stringify(g_nutrition_summary));
getNutritionChart(null, tmp_object, null, false);
}
Somehow, between alert "1" and alert "2". The global gets changed. Then when the user clicks a checkbox again and it calls this function, the very first alert shows that the original, global object contains the altered data to the tmp_object variable.
As you can see, I call a third party function I have created when this happens originally. Doing a search for the global there is absolutely nowhere else it is used in the instances described above.
Am I not understanding something about JavaScript variable scope?

Both objects and arrays in javascript are treated as references, so when trying to pass them to functions or to "copy" them, you are just cloning the reference
To have a "real copy", you would need to traverse the object and copy its content to another object. This can be done recursively, but fortunately jquery already comes with a function that does this: $.extend
So the solution would be:
var tmp_object = $.extend({},g_nutrition_summary);
If you have a nested object, you need to set an extra parameter:
var tmp_object = $.extend(true,{},g_nutrition_summary); // now it will do it recursively
For arrays, an easy way to make a "real copy" is, as #Branden Keck pointed out,
var arrCopy = arrOriginal.slice(0)
More on jquery extend: https://api.jquery.com/jquery.extend/

Going along with juvian's comment. To create the new array as somewhat of a "copy" and not just a reference, use:
var tmp_object= g_nutrition_summary.slice(0);
However, .slice() is only works for arrays and will not work on JSON, so to used this method you would have to create an array from the JSON
Another method that I found (although not the cleanest) suggested creating a string from the JSON and re-parsing it:
var tmp_object= JSON.parse(JSON.stringify(g_nutrition_summary));

Related

indexOf running within Map function relying on Variable (array) from parent function (Google Apps Script)

I'm new to Google Apps Script/Javascript and I'm in the midst of working on the second iteration of my original code in order to make it faster. My first code made a lot of gets and sets to the underlying Google Sheet, while now I'm trying to do everything in arrays before setting the revised values. The current block of code I'm stuck on is my attempt to run an indexOf while running a map function.
The below script is about to run through every row of an array full of timesheet data and make
various amendments to the columns and calculate new column values.
var = amendedTimesheetData = timesheetdata.map(function(item){
item.splice(2, 0, item[0] + " " + item[1]);
item.splice(4, 0, item[0].slice(0, 2) + item[1].slice(0, 2) + date(item[3]));
item.splice(0, 2);
item.splice(3, 0, "Open");
item.splice(4, 0, accountingCode); //SEE ISSUE BELOW
return item
})
}
The accounting code variable relies on having performed a Javascript version of a vlookup based on a value in one of the array columns. The looked-up value exists in another array pulled from a different tab in the sheet. I know the code to perform the lookup looks like this:
var timeTrackingCode = item[6]; //this is the location in the mapped array of the value to be looked up
var timeTrackingCodeRow = codeMappingData.map(function(r){ return r[0]; }).indexOf(timeTrackingCode);
var accountingCode = codeMappingData[timeTrackingCodeRow][1]
The formula in the code above relies on the codeMappingData variable which was made earlier in the parent function before the map function started running.
var codeMappingSheet = ss.getSheetByName('CodeMapping');
var codeMappingRange = codeMappingSheet.getDataRange();
var codeMappingData = codeMappingRange.getValues();
The Question: How do I reference the codeMappingData array while in the map function?
Other options to get around this would be to run a For loop separate from the map, but while trying to learn all the possibilities I'm stuck on trying to understand how I could pass the variable through and achieve all of the column manipulations in as little code as possible. Any guidance on how I could achieve the variable pass through, or tips on how the code could be more efficient would all be very valuable. I've only copied a bit of the code, so it might lack context, but I think the question is clear.
Note that I've assumed that it would be ill-advised to establish the codeMappingData variable within the Map function because then every time it iterates through a row it would be performing a get from the sheet? If I'm incorrect in assuming this, then perhaps the simplest approach is to establish the variable within the map.
Thank you,
The inner or child scope always has access to variables in the outer or parent scope.
function outerFunction(){
const outerScopeVar = 5;
const arr = [1,2,3];
arr.map(num => {
console.info(`I have access to both inner and outer scope variable: ${num} and ${outerScopeVar}`);
})
}
outerFunction();
References:
Scope
You can use the thisArg parameter as described in the Array.prototype.map() documentation. Just add it after your closing curly bracket and refer to it as this within your map function, not codeMappingData.
var timeTrackingCodeRow = codeMappingData.map(function(r) {
Logger.log(this[0]); // logs the first element of codeMappingData
return r[0];
}, codeMappingData).indexOf(timeTrackingCode);

How do I prevent my program from overwriting localStorage every time a button is clicked?

document.getElementById("submit").addEventListener("click", getElements)
function getElements() {
var a = document.getElementById("sample").value;
var x = new obj(a);
function store() {
localStorage.setItem('todays-values', Object.values(x));
}
store();
}
In a separate js file I then call
localStorage.getItem('todays-values');
I get the values, but if I put new inputs into my html file and click the submit button, the previous values get overwritten and replaced by the new ones. How do I store all the values that are submitted and prevent the old ones from getting replaced?
I'm very new to Javascript so I would prefer to solve this problem without the use of any additional libraries if possible.
First: it seems that you are mixing JavaScript a class with a function (here is an example: What techniques can be used to define a class in JavaScript, and what are their trade-offs?)
For example this is the class equivalent in JavaScript:
function ClassName() {
var privateVar;
this.publicVar;
function privateFunction() {}
this.publicFunction = function() {};
}
You shouldn't wrap a function in a function unless it has a meaning (beacuse it is confusing for other people otherwise), but in the example given you don't need that. Also I can't see the reason why you are creating a new object x - if you create the object right before you save it you could just save the value because the object will only contain the value from sample, so you could write something like this:
document.getElementById("submit").addEventListener("click", getElements);
function storeElements() {
var sampleValue = document.getElementById("sample").value;
localStorage.setItem('todays-values', sampleValue);
}
Back to your question:
As Kalamarico mentioned: if you write new values into todays-values you will overwrite your old values, you could simply load all old values from the localStorage append the new ones and write them back to the localStorage.
You should also note that the localStorage only takes strings, so you should stringify objects (see localStorage.setItem).
function appendValueToStorage(key, value) {
var values = JSON.parse(localStorage.getItem(key));
if (values === null) {
values = [];
}
values.push(value);
localStorage.setItem(key, JSON.stringify(values));
console.log(localStorage.getItem(key));
}
appendValueToStorage('todays-values', document.getElementById("sample").value);
The function will let you append some value for a key, you could even wrap this function again to be able to use it in your click function:
function onSubmitClick() {
appendValueToStorage('todays-values', document.getElementById("sample").value);
}
document.getElementById("submit").addEventListener("click", onSubmitClick);
With the console.log command you can see the current content of the localStorage (you could also check with the developer tools - I find the ones for chrome work the best, under the Application -> Local Storage tab you can check the localStorage of your page).
You need read more about localStorage, this is a new feature introduced with HTML5, you can take a look here and see all features.
localStorage stores your data like a JSON object, if you don't know what is JSON, you need to find info. In javascript think in objects in this way:
var myData = {
myName: 'Kalamarico',
myAge: undefined
};
This is a Javascript object, and JSON is very similar and it is a representation of objects.
localStorage API stores your data as this way, when you do:
localStorage.setItem('todays-values', Object.values(x))
localStorage saves a new entry, one key 'todays-values' and its value is an object, so, your localStorage seems:
{
"todays-values": { ... }
}
Every time you set a "todays-values" you will overwrite the key, as you are seeing, so, if you can keep old values, you need to do this manage, first you can get items in localstorage (if there are), and after you can "merge" your old value and the new value. Or you can set a new key, for example: "todays-values1" depends on your need.
If you need to store exactly one key-value pair per day, then you could add the date in the key string.
Else how about numbering the keys ("yourKey_0", "yourKey_1", ...) and also storing the current (biggest) index ("currentIndex")in local storage:
function store(value) {
newIndex = localStorage.getItem("currentIndex") + 1;
localStorage.setItem("yourKey_" + newIndex, value);
localStorage.setItem("currentIndex", newIndex);
}
If you run into problems storing integer values, convert to strings.

find and replace property value in json object using javascript/underscore.js

I have one list as follows
var data = [{id:1, name:'user1', img:'img1.jpg' },{id:2, 'user2', img:'img2.jpg' }]
Now I have to replace img property with new value (e.g. 'New_Image_2.jpg') for id=2 using underscore.js or in javascript using less effort. right now I have created one small function as follows.
var imgpath = 'New_Image_2.jpg';
var tmpdata = _.findWhere(data,{id:2});
if (tmpdata) {
tmpdata.img = imgpath;
}
Now, in above method, my problem is, How to marge new value with original data?
Well your code should already work because _.findWhere returns the first object corresponding to your constraint or undefined if it does not exist, it means that modifying tmpdata.img actually modifies the value you want to modify.

How to adjust a variable based on the id of the object that is clicked?

I am making a shop system for my HTML game, and I want to make it so that when you click on an item, it gets the id, and lowers the variable for that item by 1.
if(e.shiftKey && inShop[this.id] === 0) {
coins = coins+price[this.id]
coinUpdate();
[this.id]--;
}
var fish1 = 1
<html>
<img class="item cookable" id="fish1" src="source">
</html>
For example, when I click on a fish, I want it to lower the variable for how many fishes you have in your inventory. So I need to change the [this.id] in a variable with the same name.
Do not use the window method. It only works with global variables.
Global variables are defined as some_var = 10; as opposed to var some_var = 10; If you're from a desktop programming background, you'll find globals in JS hella awkward.
Instead, use namespaces or an object (this method).
Define your inventory like this:
var inventory = {
goldfish: 10,
seahorse: 10,
jellyfish: 10
}
As for the HTML, the ID method is OK, but why not use the data attribute? It's made to hold metadata so perfect for this scenario.
<img src="//placehold.it/32x32" class="item cookable" data-type="fish">
Access for this is built into jQuery via the .data method so when you need to decrement or increment the quantity based on what is clicked, simply use the following where necessary:
// Fetch the "type" of fish from the data attribute
var type = $(this).data("type");
// And update the inventory
inventory[type]--;
Use the data attribute for other metadata so data-foo="hello" data-bar="world"
And these can be fetched as an object using jQuery .data() and it will return {foo: "hello", bar: "world"} or fetch them individually by passing the data name.data("foo")`
Personally, I'd use this as opposed to ID.
[this.id]-- is not going to work. This makes an array with a single element (the string that is referenced by this.id), and tries to decrement that array. And decrementing an array doesn't make much sense.
You can't access local variable dynamically like this (well you can in some cases, but really you shouldn't). However, you can do it with properties, so you have to rejigger things a bit.
How about storing all the counts of things you have in an object, maybe call it inventory.
var inventory = {
fish1: 10,
fish2: 5,
worms: 3
};
Now you use you decrement method with only a slight tweak:
if(e.shiftKey && inShop[this.id] === 0) {
coins = coins+price[this.id]
coinUpdate();
inventory[this.id]--; // decrement an item count in your inventory
}
All global variables in JS are created on the window object. If you have a variable per id, then you just need to do window[this.id]-- or window[this.id]++. Anyway, in JavaScript the Window object acts as the global namespace and in general it is bad practice to clutter the global namespace with variables. You should create a new object (for example items) containing all the item counters and for each item that is added you can do items[this.id]++ or items[this.id]-- when they are removed

Need to duplicate code, but rather instantiate several objects and keep the code in one place

I've developed some javascript which generates a dynamic table from an array, then allows the user to edit the data. This works perfect.
Now I want to do use this code 3 times on the same HTML page. At the moment it depends on two variables: metadata, which describes the header format and is read only and the same for all three occasions, and 'data' which actually holds the data for the table.
Rather than having three copies of the code for each instance, I'm thinking it would be better to have an object which keeps everything internal and simply has 'setData' and 'getData' methods.
Existing code dynamically creates onclick events - how to get these to reference the objects function, rather than a global function? (ie, there is currently functions like 'RemoveRow(index)', which I assume will be 'obj.RemoveRow(index)'. I would love to do this in jquery but alas, I wouldn't know the rowindex to pass in then, unless someone has a solution. Perhaps store it somewhere inside the DOM and access it somehow to determine the row being clicked on?
Is it possible to pass a reference to an array? It would be much better if the object could manipulate the array passed in rather than making a copy to work on and then the caller having to copy that back to its own array.
So this is very raw code and only one of multiple options, but maybe you get the idea:
Create an object that handles a single table
function TableControl () {
// dom elements
this.table = null;
this.rows = null;
// values
this.id = null;
// create new row
this.insertRow = function() {
// some logic
this.table.append( 'new row goes here' );
},
// load table object and rows and stuff
this._initiate = function() {
//create object with all passed arguments
var args = arguments[0] || {};
// set internal values
this.id = args.id;
// get dom elements
this.table = $('#' + this.id);
this.rows = this.table.find('tr');
},
this._initiate( arguments[0] );
}
Create instances of that object using the id of each table
var table_1 = new TableConrol({ id: 'table_1' });
var table_2 = new TableConrol({ id: 'table_2' });
var table_3 = new TableConrol({ id: 'table_3' });
So each table handles only stuff within it's own "scope", like adding new rows for example:
table_1.insertRow();

Categories

Resources