Determing which Item was Clicked in a Custom Menu - javascript

I've created a custom menu with multiple items in it, however I'm having difficulties determining which item was clicked by the user. I want to use the same function for every item in this menu but I can't figure out how to pass on the info of which item was pressed into my function. Multiple ideas that I've had but haven't been able to implement are: trying to pass a parameter when pressing a button (the parameter could be the name of the button or its index), or trying to somehow determine which item was clicked by index (i.e. "item 3 was clicked") and passing that info on to the function.
var ui = SpreadsheetApp.getUi(); //shortcut to access ui methods
var ps = PropertiesService.getScriptProperties(); //shortcut to access properties methods
var ss = SpreadsheetApp.getActiveSpreadsheet() //shortcut to access spreadsheet methods
function onOpen() {
var menu = ui.createMenu('Scripts') //create a menu with this name
var subMenu = ui.createMenu('Timestamps')
for (var n = 0; n < ss.getNumSheets(); n++){
var sheets = ss.getSheets();
var sheetName = sheets[n].getName();
Logger.log(sheetName)
subMenu.addItem(sheetName, 'sheets')
}
menu.addSubMenu(subMenu).addToUi(); //add it to the UI
}
function sheets(sheet){
var response = ui.alert(sheet, 'Add to timestamps?', ui.ButtonSet.YES_NO_CANCEL) //create a button and store the user value in response
if(response == ui.Button.YES){ //if the user pressed YES (add this item to timestamp list)
if(sheets.indexOf(sheet) != -1){ //check if item is already in the array. If it is, do nothing
//item is aleady in array
}else if(sheets.indexOf(sheet) == -1){ //check if it is NOT in the array. If it isn't, add it
//item isn't in array, but needs to be added
sheets.push(sheet) //add the item to the array
}
}else if(response == ui.Button.NO){ //if the user pressed NO (remove item from the list)
if(sheets.indexOf(sheet) != -1){ //if the item already exists but needs to be removed)
//item exists in array, but needs to be removed
var index = sheets.indexOf(sheet); //find where the item is stored
sheets.splice(index, 1); //splice that item out of the array
}else if(sheets.indexOf(sheet) == -1){ //if the item already doesn't exist in the array, do nothing
//item already isn't in array
}
}
ps.setProperty('updatedSheets', JSON.stringify(sheets)) //storing the new value of sheets so that we can view it in the properties screen (only for debugging purposes)
}
What this code currently does is when the spreadsheet is opened, a menu is created named Scripts with a sub-menu inside of it named Timestamps. Inside of the sub-menu timestamps, I have one item for each sheet. The purpose is that when the user clicks on one of the items, a pop-up appears with 3 buttons: Yes, No, and Cancel. If they press Yes, that item is supposed to be added to the array sheets. If they press No, that item is supposed to be removed. If they press Cancel, nothing happens. I have it working so far to add and remove the items if they specify in the code a specific sheet, but how could I get it so that I could use the same function for every item and pass the parameter sheet (depending on which item was clicked) into the function sheets.
Example of the functionality of the code if I hard-code the sheet name without passing a parameter to the function:
function sheets(){
var response = ui.alert('Sheet1', 'Add to timestamps?', ui.ButtonSet.YES_NO_CANCEL) //create a button and store the user value in response
if(response == ui.Button.YES){ //if the user pressed YES (add this item to timestamp list)
if(sheets.indexOf('Sheet1') != -1){ //check if item is already in the array. If it is, do nothing
//item is aleady in array
}else if(sheets.indexOf('Sheet1') == -1){ //check if it is NOT in the array. If it isn't, add it
//item isn't in array, but needs to be added
sheets.push('Sheet1') //add the item to the array
}
}else if(response == ui.Button.NO){ //if the user pressed NO (remove item from the list)
if(sheets.indexOf('Sheet1') != -1){ //if the item already exists but needs to be removed)
//item exists in array, but needs to be removed
var index = sheets.indexOf('Sheet1'); //find where the item is stored
sheets.splice(index, 1); //splice that item out of the array
}else if(sheets.indexOf('Sheet1') == -1){ //if the item already doesn't exist in the array, do nothing
//item already isn't in array
}
}
ps.setProperty('updatedSheets', JSON.stringify(sheets)) //storing the new value of sheets so that we can view it in the properties screen (only for debugging purposes)
}

I know eval is evil but I cannot help using it. Yes, if you make a bunch of functions dynamically via eval then the rest is trivial.
var FUNC_STR = 'sheets'; //the real function name (global constant)
function onOpen() {
//...
for(var n = 0; n < ss.getNumSheets(); n++){
var sheets = ss.getSheets();
var sheetName = sheets[n].getName();
subMenu.addItem(sheetName, FUNC_STR + n); //note here
}
menu.addSubMenu(subMenu).addToUi();
}
//dynamically make functions
var evalString = '';
for(var n = 0; n < ss.getNumSheets(); n++) {
evalString += 'function ' + FUNC_STR + n + '() { ' +
FUNC_STR + '(' + n + ') }';
}
eval(evalString);
//now you can take a argument.
//function name should be the same as FUNC_STR.
function sheets(sheet) {
SpreadsheetApp.getUi().alert(sheet);
//...
}

Related

Apps Script doesn't compare 2 values in if-statement

I've created a new project that should compare a name from Sheet1 with a list of names in Sheet2 and check if the name is already in that list. For that I chose a for-loop to get through the list in Sheet2 and compare every list entry with the name from Sheet1. Only if the name already exists in the list stuff should happen.
function myFunction() {
var tabSheet1 = 'Sheet1';
var tabSheet2 = 'Sheet2';
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName(tabSheet1);
var sheet2 = ss.getSheetByName(tabSheet2);
var lastRow1 = sheet2.getLastRow() + 1;
var playerNameSheet1 = sheet1.getRange(1, 1).getValue();
for (var j = 1; j < lastRow1; j++) {
var playerNameSheet2 = sheet2.getRange(j, 1).getValue();
if (playerNameSheet2 == playerNameSheet1) {
...stuff...
}
}
}
Now my problem is that it seems like the script isn't able to identify that a name already exists in the list. Both values (playerNameSheet1 and playerNameSheet2) are completely identical (no space or other hidden obstacles), however the script would never continue with stuff in the if-statement. My example name to test my script was "Oliver Baumann".
I'm a bit confused about it - even more, because another comparison a bit later in the script code works just fine.
I've already tried to change the operator into === but that wouldn't work either.
if (playerNameSheet2 === playerNameSheet1) {
...stuff...
}
I've also observed that if I put a dot behind both variables I'm only able to choose further functions with playerNameSheet2, but not with playerNameSheet1. Maybe I did a typing error and am just too blind to see it? I don't know. Anyone an idea how to resolve the issue?
The complete project can be found here. However, a lot of stuff is in german and very rudimental. I just started it and haven't got time to clean it up. Just so you don't wonder.
You will likely benefit from a change to your inspection routine - currently what you have is not scalable due to the slow, repeated calls to the Spreadsheet Service. Use a batch method - getValues() - to return a Javascript Array that contains all the content you could want from your 'master list' of names:
// Create an N x 1 array of arrays, e.g. [ [r1c1], [r2c1], [r3c1], ... [rNc1] ],
// of data in column A in sheet2. There will be blanks at the end if other columns have more data.
var allNames = sheet2.getRange(1, 1, sheet2.getLastRow(), 1).getValues();
To check if the name from the first sheet is present, we can replace this code:
for (var j = 1; j < lastRow1; j++) {
var playerNameSheet2 = sheet2.getRange(j, 1).getValue();
if (playerNameSheet2 == playerNameSheet1) {
/* do stuff */
with this code (note j now starts at 0):
for (var j = 0; j < allNames.length; ++j) {
if (playerNameSheet1 === allNames[j][0]) {
/* do stuff */
If you only need to do stuff on a name once in the function call (e.g. you don't need to execute the loop body twenty times when the sheet 1 name is "Bob" and there are twenty instances of "Bob" on sheet 2), you can simplify checking allNames for a value with the Array#indexOf method. First, one must collapse the "2D" array of arrays of values into an array of values. We want to apply a function to every element of the outer array and construct an array of its outputs, so we choose to call Array#map on it:
var db = allNames.map(function (row) { return row[0]; });
The function we use simply returns the first element of the passed element - i.e. the value in the first column, resulting in an output like [ r1c1, r2c1, r3c1, ... rNc1 ].
The replacement code is then:
if (db.indexOf(playerNameSheet1) === -1) {
console.log({
message: "Did not find '" + playerNameSheet1 + "' in database.",
database: db, original: allNames, searched: playerNameSheet1
});
return;
}
/* do stuff */
Which says "if the name is not on sheet 2, log the failed lookup and then quit running the function." To promote actual logging, the log is sent to Stackdriver, which will keep it for much longer than the native Logger class would.
If your do stuff bits use the j index, you can still obtain that index and use the associated row in sheet 2:
var index = db.indexOf(playerNameSheet1);
if (index === -1) {
console.log({
message: "Did not find '" + playerNameSheet1 + "' in database.",
database: db, original: allNames, searched: playerNameSheet1
});
return;
}
/* do stuff with the user's existing row of data, e.g.
var userDataRow = sheet2.getRange(index + 1, 1, 1, sheet2.getLastColumn()).getValues();
var userData = userDataRow[0];
...
*/
A possible improvement to the indexOf modification, which I leave for you to investigate and/or implement, would be to use an Object to hold the names as "keys" (object properties) and the index of the associated sheet data (or even the data directly) as the associated value of the key-value pair.
you can try to convert data in array and compare in for-loop:
var dataRangeSpieler = sheetSpieler.getDataRange().getValues();
var dataRangeDBSpiele = sheetDBSpieler.getDataRange().getValues();
for (i in dataRangeSpieler ) {
for (j in dataRangeDBSpiele) {
if (dataRangeSpieler[i][1] == dataRangeDBSpiele[j][0]) {
Logger.log(dataRangeSpieler[i][1]); //Oliver Baumann
}
}
}

JavaScript: If statement not working inside Loop

I'm writing the following code (a test as of now) using Google Scripts to pass data from one spreadsheet to another. The passing of the code is working just fine, however my second For loop – which I intend to use to detect duplicate values and avoid passing those rows over – is not working.
Checking the logs I see that even though the "i" and "j" values are correctly being passed inside the If block, the "if(sheetsIDHome[i] == sheetsIDTarget[j])" statement is never triggering, even when I confirm that both values are the same.
Any help would be greatly appreciated, thank you in advance!
function move(){
var homeBook = SpreadsheetApp.getActiveSpreadsheet();
var sheet = homeBook.getSheets()[0];//Sheet where my Home data is stored
var limit = sheet.getLastRow(); //number of rows with content in them
var evento = sheet.getRange(2, 1, limit-1).getValues(); //Even titles array
var descript = sheet.getRange(2,2,limit-1).getValues(); //Event Descriptions array
var tags = sheet.getRange(2,3,limit-1).getValues(); //Tags array
var sheetsIDHome = sheet.getRange(2,4,limit-1).getValues(); //ID's array
var targetBook = SpreadsheetApp.openById("1t3qMTu2opYffLmFfTuIbV6BrwsDe9iLHZJ_ZT89kHr8"); // Traget Workbook
var target = targetBook.getSheets()[0]; //Sheet1, this is my Target sheet
if (target.getLastRow() > 1){
var sheetsIDTarget = target.getRange(2, 4,target.getLastRow()-1).getValues();}
else{
var sheetsIDTarget = target.getRange(2, 4, 1).getValues();}
var targetRow = target.getLastRow()+1; //Target row to start pasting content
for (var i = 0; i < evento.length; i++) { //Loops throught every value from my Home sheet in order to pass it to my Target Sheet
var isKlar = 1; //This works as a switch, data passing will not activate if isKlar set to 0
Logger.log("Switch is: "+isKlar);
for(var j = 0; j < sheetsIDTarget.length; j++){ //While having a certain "i" value in place, will loop though all my values in my target array using the counter "j"
if(sheetsIDHome[i] == sheetsIDTarget[j]){ //If the ID of my curent row from Home matches any of the values in my target sheet, my "isKlar" switch should turn off and the break loop will be exited.
Logger.log("If Activated");
isKlar = 0;
break;}
else{Logger.log("ID's: "+sheetsIDHome[i] + " vs " + sheetsIDTarget[j]);}
}
if(isKlar === 1){ //data passing will not activate if isKlar set to 0
//pass data to the Target sheet
target.getRange(targetRow,1).setValue(evento[i]);
target.getRange(targetRow,2).setValue(descript[i]);
target.getRange(targetRow,3).setValue(tags[i]);
target.getRange(targetRow,4).setValue(sheetsIDHome[i]);
targetRow++; //select the next available row in ny Target sheet
}
}
}
Edit. - Right now I'm testing both ID arrays with the same numbers (e.g. 1, 2, 3, 4). The log inside my else statement does show the correct values being read for both arrays... I thought it was a scope issue, but now I'm not sure where the problem is.
the issue is a sheet range.getValues() returns an array of arrays, not an array of values.
values[0] is the first row, and values[0][0] is the first value in that first row. rework your code knowing this.

JQuery to update the HTML of a div without reloading page

I have a div that is displaying the contents of an order. Currently this is how I am implementing adding items to this div using JQuery
$(document).ready(function(){
//if cookie exists, show the panel
if($.cookie('order_cookie') != undefined){
productArray = JSON.parse($.cookie('order_cookie'));
$(".order-alert").show();
for(var i = 0; i < productArray.length; i++){
console.log(productArray[i]);
var obj = productArray[i];
$(".order-alert").append("<p> StockCode: " + obj.stockCode + " Qty: " + obj.quantity + "</p>");
console.log("Object code: " + obj.stockCode + " Qty: " + obj.quantity);
}
$('#order_counter').html(productArray.length);
}
});
I have it working so that when the user adds an item to the order the counter increments without reloading the browser window.
$('#order_counter').html(productArray.length);
I'm just wondering how I could implement the same thing with my loop to output the items in the order as the user adds them to the array
Any help is greatly appreciated.
This script adds items to array, the cookie is also set in the script
var productArray = []; // Will hold order Items
$(".orderBtn").click(function(event){
//Check to ensure quantity > 0
if(quantity == 0){
console.log("Quantity must be greater than 0")
}else{//It is so continue
//Show the order Box
$(".order-alert").show();
event.preventDefault();
//Get reference to the product clicked
var stockCode = $(this).closest('li').find('.stock_code').html();
//Get reference to the quantity selected
var quantity = $(this).closest('li').find('.order_amount').val();
//Order Item (contains stockCode and Quantity) - Can add whatever data I like here
var orderItem = {
'stockCode' : stockCode,
'quantity' : quantity
};
//Check if cookie exists
if($.cookie('order_cookie') === undefined){
console.log("Creating new cookie");
//Add object to the Array
productArray.push(orderItem);
}else{//Already exists
console.log("Updating the cookie")
productArray = JSON.parse($.cookie('order_cookie'));
//Check if the item already exists in the Cookie and update qty
var found = false;
for(var i =0; i < productArray.length; i++){
if(productArray[i].stockCode == stockCode){
console.log("OBJECT EXISTS")
var index = i;
found = true;
}
}
//If it exists update the old quantity
if(found){
//update
console.log("Match found at: " + index);
var oldQty = (productArray[index].quantity)
var newQty = parseInt(quantity) + parseInt(oldQty);
productArray[index].quantity = newQty;
}else{// It doesn't exist so add new item to array
productArray.push(orderItem);
}
}
}
//Update the Cookie
$.cookie('order_cookie', JSON.stringify(productArray), { expires: 1, path: '/' });
//Testing output of Cookie
console.log($.cookie('order_cookie'));
//Update the order tracker
$('#order_counter').html(productArray.length);
});
I can think of two options:
1) Create another field for the order to print them in which you update when a user adds something to their order.
2) Implement the move to front heuristic on your product array so that when a product is incremented it's moved to the front of the array and the products originally in front of it are pushed back one space. As an example if you start with
"orange" => 1
"pear" => 1
and then the user adds a pear followed by an apple, the result would be:
"apple" => 1
"pear" => 2
"orange" => 1
Both have issues when the array gets massive, but if you won't be getting orders containing hundreds of unique products, it shouldn't be an issue.
Regardless of which method you use, you can update the list presented to the user by just using $('.order-alert').prepend()

how to remove concatenated data in localStorage

I'm using localStorage to store some data and all the data are concatenated separated by \n. I want to remove specific data in localStorage and i'm using listbox to display all the data.
example {"rowdata":"data1\ndata2\ndata3"} // the three data are stored in localStorage, the key of rowdata in the localStorage is storedata and the rowdata is the value of storedata that have three data concatenated.
is there an easy way to remove the selected data, example i want to remove data3. i'm using google chrome browser..
code for display:
function populate(){
for(i=0; i<rowdata.length; i++){
var select = document.getElementById("test"); // id of the listbox
var splitRow = rowdata.split("\n");
var row = splitRow[i];
if(row != undefined)
select.options[select.options.length] = new Option(row);
}
}
code for remove:
function removeSelectedItem(){
var htmlSelect=document.getElementById('test'); // id of the listbox
if(htmlSelect.options.length == 0)
{
alert('You have already removed all list items');
return false;
{
var optionToRemove = htmlSelect.options.selectedIndex;
htmlSelect.remove(optionToRemove);
if(htmlSelect.options.length > 0)
{
htmlSelect.options[0].selected=true;
{
alert('The selected data has been removed successfully');
return true;
}
Thanks...
Not sure if I clearly understood the question, but if you just need to update state if rowdata variable then try put the code below before removing an option from select in RemoveSelectedItem (man, start function names with lowercase!):
rowdata = ("\n" + rowdata + "\n").replace("\n" + htmlSelect.options[htmlSelect.options.selectedIndex].innerHTML + "\n", "").trim()

marking a item "done" in localStorage

I have an application where I am adding li elements to the web page. I need to change the class name of the element to "done" inside of local storage when I mark it as "done" on the webpage. (It should say done: true). With my current code I am unintentionally making two items in local storage, one which is done: true and the other which is done: false. I'll show my code here:
function updateDone(e) {
var spanClicked = e.target;
var id = spanClicked.parentElement.id;
var done = spanClicked.parentElement.className;
spanClicked.innerHTML = " ✔ ";
spanClicked.setAttribute("class", "done");
var key = "todos" + id;
for(var i = 0; i < todos.length; i++) {
if(todos[i].id == id) {
var mark = todos[i];
mark.done = true;
console.log(mark);
spanClicked.setAttribute("class", "done");
var newKey = JSON.stringify(mark);
localStorage.setItem(key, newKey);
if(mark.done == false) {
spanClicked.setAttribute("class", "not done");
spanClicked.innerHTML = "not done";
}
}
}
}
They are both labeled with the same id which is how I keep track of each item, yet there are two of them. Also, when i refresh the page there are two list items shown, one which is marked done. My question is how do I prevent another item from being created and instead mark just one item as done in localStorage?
You need a way to uniquely identify each item, so you can ensure your marks are being set on the items you intend and are not overwriting because you might have, say, two items with the same key. Since you are looping through a list, maybe you can change your keys to be composed of two parts.
var parent_key = "todos" + parent_id;
And then, in the loop :
var store_key = parent_key + ":" + i;
...
localStorage.set(store_key,newKey);
This way (so long as the order is going to be consistent), you can separate multiple list elements from the same parent.
As commented, a live example in jsFiddle or something would help better address your requirement.
However if this solution is insufficient you could try the following idea, effectively setting a "table" within localstorage.
var parent_key = "todos" + id;
var parent_table = {};
// for loop
parent_table[i] = newKey;
// end of for loop
localStorage.set(parent_key,parent_table);
So you have a table inside of local storage, to give you finer granularity.

Categories