GAS: key:value array to menu - javascript

So i am attempting to turn a key:value array into a menu using Google Apps Script. I am using SpreadsheetApp currently but want to make this for use in other apps.
My issue is using context_menu() method gives an error of Exception: Custom menus must contain at least one item. on line m.addToUi() in context_menu()
I have tested this using simple_menu() to make sure i understand how it works (and that works perfectly) so I'm not sure what the issue is.
var f = 'my_function'
var m_contex = {"Item1":f, "Item2":f, "SubMenu":{"subItem1":f, "subItem2":f}, "Item3":f}
function my_function(){
Logger.log("triggered");
}
function cm_rec(ui, m, name, items){
var sm = ui.createMenu(name);
m.addSubMenu(sm);
for(var key in items){
if(typeof items[key] === "string"){
Logger.log("adding " + key + " to "+name);
sm.addItem(key, items[key]);
}else{
cm_rec(ui, sm, key, items[key]);
}
}
}
function context_menu(ui, title, items){
var m = ui.createMenu(title);
for(var key in items){
if(typeof items[key] === "string"){
Logger.log("adding " + key + " to "+title);
m.addItem(key, items[key]);
}else{
cm_rec(ui, m, key, items[key]);
}
}
m.addToUi();
}
function simple_submenu(ui, m, title){
var sm = ui.createMenu(title);
sm.addItem("SubItem1", 'my_function');
sm.addItem("SubItem2", 'my_function');
m.addSubMenu(sm);
}
function simple_menu(ui){
var m = ui.createMenu("My Menu");
m.addItem("Item1", 'my_function');
m.addItem("Item2", 'my_function');
simple_submenu(ui, m, "SubMenu");
m.addItem("Item3", 'my_function');
m.addToUi();
}
function tester1(){ //runs the simple_menu
var ui = SpreadsheetApp.getUi();
Logger.log("Creating Simple Menu");
simple_menu(ui);
}
function tester2(){ // runs the context_menu
var ui = SpreadsheetApp.getUi();
Logger.log(m_contex);
context_menu(ui, "Custom Menu", m_contex);
}
The output for tester2 is correct except from the error:
2:44:27 PM Info {Item3=my_function, Item1=my_function, SubMenu={subItem2=my_function, subItem1=my_function}, Item2=my_function}
2:44:27 PM Info adding Item1 to Custom Menu
2:44:27 PM Info adding Item2 to Custom Menu
2:44:27 PM Info adding subItem1 to SubMenu
2:44:27 PM Info adding subItem2 to SubMenu
2:44:27 PM Info adding Item3 to Custom Menu

In your script, I thought that the following part might be required to be modified.
var sm = ui.createMenu(name);
m.addSubMenu(sm);
Because, in this case, sm is added with no items. I thought that this might be the reason for your current issue of Exception: Custom menus must contain at least one item.. In this case, how about the following modification?
Modified script:
In this modification, please modify the function cm_rec as follows.
function cm_rec(ui, m, name, items) {
var sm = ui.createMenu(name);
for (var key in items) {
if (typeof items[key] === "string") {
Logger.log("adding " + key + " to " + name);
sm.addItem(key, items[key]);
} else {
cm_rec(ui, sm, key, items[key]);
}
}
m.addSubMenu(sm); // This line was moved.
}

Related

Search for value and return row number

I'm struggle with this script. I need to search for today date, once match it return column number (done!). I need a script to do the same with row number. The script I have is works fine if I keep only one table on that sheet, but I have more tables and if I use values.length -1 will return last row from the sheet. Also, every table might not have a fixed row numbers, so need to be dynamic.
This is the script I have so far:
function getTodaysTotal() {
function toDateFormat(date) {
try {return date.setHours(0,0,0,0);}
catch(e) {return;}
}
var values = SpreadsheetApp
.openById("id")
.getSheetByName("Q3 - W27 - 39")
.getDataRange()
.getValues();
var today = toDateFormat(new Date());
var todaysColumn = values[5].map(toDateFormat).map(Number).indexOf(+today);
var output = values[values.length - 1][todaysColumn];
var emailDate = Utilities.formatDate(new Date(today),"GMT+1", "dd/MM/yyyy");
And that's a screen shoot of my table.
table
Hope it make sense. I have the column number and I need to find row number that contain Total.
Thank you!
Kind regards
You have to loop through an array of data which is probably the fastest.
Have a look at the below script and implement in your existing.
Not sure if it is only one column where you are looking but for example column C:
for (i in values){
if (values[i][2]=='Total'){
Logger.log(i);
var nr = i
}
}
var nr now contains i as the row number it found the word "Total" in column C (the #2 after [i]).
you can then use the variable in anything that you would like after the first closing bracket.
I add this to my script, but I get undefined on output. I get the right value if I use logger log, but I also get an empty value (undefined). So I need only the first value.
function getTodaysTotal() {
function toDateFormat(date) {
try {return date.setHours(0,0,0,0);}
catch(e) {return;}
}
var values = SpreadsheetApp
.openById("id")
.getSheetByName("Q3 - W27 - 39")
.getDataRange()
.getValues();
for (i in values){
if (values[i][0]=='Total'){
var nr = i;
var newNr = parseInt(nr);
// Logger.log(nr);
var today = toDateFormat(new Date());
var todaysColumn =
values[5].map(toDateFormat).map(Number).indexOf(+today);
var output = values[newNr][todaysColumn];
Logger.log(output);
var emailDate = Utilities.formatDate(new Date(today),"GMT+1", "dd/MM/yyyy");
// Logger.log(todaysColumn);
// Logger.log(output);
// return values[values.length - 1][todaysColumn];
}
}
if (output == undefined) {
GmailApp.sendEmail("email#company.com", "test data", "Today, " +emailDate +" we had no calls made or no values are inputed.");
}
else if (output == "") {
GmailApp.sendEmail("email#company.com", "test data", "Today, " +emailDate +" we had no calls made or no values are inputed.");
}
else {
GmailApp.sendEmail("email#company.com", "test data", "Today, " +emailDate +" we had " +output + " calls made.");
}
}

jQuery traverse json recursive

I am trying to traverse a JSON object with jQuery recursive.. normally it worked , but not on the following JSON.
I want to traverse this JSON, here I uploaded an image:
For my json objects, i had this jquery function:
var construct_id = "#ecommerce_form_";
// function to traverse json objects given back from Serializer class
function process(callback, id) {
var key;
for (key in callback) {
// Handle the arrays
if ('length' in callback[key]) {
// Handle the end - we found a string
if (typeof callback[key][0] == "string") {
var field_id = construct_id + id + key;
var err_msg = callback[key][0];
$(field_id).tooltip('destroy');
$(field_id).tooltip({'title': err_msg});
$(field_id).closest('div[class="form-group"]').addClass('has-error');
console.log(field_id, ":", err_msg);
}
// Else we found something else, so recurse.
else {
var i = 0;
while (i < callback[key].length) {
process(callback[key][i], key + "_" + i + "_");
i++;
}
}
}
// Handle the objects by recursing.
else {
process(callback[key], key + "_");
}
}
}
But that functions fails when trying to build the contact > addresses id with the error message:
"Uncaught TypeError: Cannot use 'in' operator to search for 'length'
in This value should not be blank."
Hope you guys can help me enhancing the jQuery function, it is not 100% successfull as you can see on this json example.
Regards
You are trying to search for the property "length" in a string, which can't be done. In the erroneous iteration: callback = obj.contacts.addresses, key = cities and then callback[key][0] = "This value should not be blank".
What you should do is check if you have reached a string before looking for the "length" property, and only then if you haven't found a string, begin the recursion check.
see jsfiddle example here:
http://jsfiddle.net/38d15z4o/
var construct_id = "#ecommerce_form_";
// function to traverse json objects given back from Serializer class
function process(callback, id) {
var key;
for (key in callback) {
// Handle the end - we found a string
if (typeof callback[key] == "string") {
var field_id = construct_id + id + key;
var err_msg = callback[key][0];
$(field_id).tooltip('destroy');
$(field_id).tooltip({'title': err_msg});
$(field_id).closest('div[class="form-group"]').addClass('has-error');
console.log(field_id, ":", err_msg);
}
// Handle the objects and arrays by recursing.
else {
process(callback[key], id + key + "_");
}
}
}
NOTE: for the error message, you are only showing the first letter of the string, I think you meant to put: err_msg = callback[key] not err_msg = callback[key][0].
Why don't you check for
typeof callback[key] === 'array'
Instead checking the length property?

jqGrid and metadata

I have a jqGrid which loads very nice (thanks Oleg), however I load flat data from a database table which also contains id's which I want to merge on the interface with the metadata there available
So I have a bunch of variables like:
var strMeta = [{id:35,label:"Russia",labelLocal:"Россия",pid:"33"}, {id:36,label:"Moldavia",labelLocal:"Молдавия",pid:"33"}]
In the jqGrid are id's like 35 and 36.
Now what I want to do is when the jqGrid page loads (so only for the page and not the entire data) it will match with that specific column so I get something like "[35] Россия" instead of "35", anybody ever tried to do this or have any idea how to do this?
for a form object I do this already however I cannot get this to work on the jqGrid
$.each(_Columns, function (x, it) {
if (i == it.name) {
var dataid = it.dataid;
if (dataid !== "") {
$.each(strMeta, function (y, arr) {
if (arr.id == item) {
alert(arr.label);
var _item = "[" + item + "] " + arr.labelLocal;
if (arr.labelLocal === "") {
_item = "[" + item + "] " + arr.label;
$("#" + i).val(_item);
}
return;
}
});
}
}
I know here the textbox names, however this is in a jqGrid a little different as I understood the rowid is being added to the name of the column
Thanks,

chrome.storage.local.get results in "Undefined" when called

I'm building a chrome extension, and I needed to save some data locally; so I used the Storage API . I got to run the simple example and save the data, but when I integrated it with my application, it couldn't find the data and is giving me "Undefined" result.
Here is my Code:
function saveResults(newsId, resultsArray) {
//Save the result
for(var i = 0; i < resultsArray.length; i++) {
id = newsId.toString() + '-' + i.toString();
chrome.storage.local.set({ id : resultsArray[i] });
}
//Read and delete the saved results
for(var i = 0; i < resultsArray.length; i++) {
id = newsId.toString() + '-' + i.toString();
chrome.storage.local.get(id, function(value){
alert(value.id);
});
chrome.storage.local.remove(id);
}
}
I am not certain what type of data you are saving or how much, but it seems to me that there may be more than one newsId and a resultsArray of varying length for each one. Instead of creating keys for each element of resultsArarry have you considered just storing the entire thing as is. An example of this would be:
chrome.storage.local.set({'results':[]});
function saveResults(newsId, resultsArray) {
// first combine the data into one object
var result = {'newsId':newsId, 'resultsArray':resultsArray};
// next we will push each individual results object into an array
chrome.storage.get('results',function(item){
item.results.push(result);
chrome.storage.set({'results':item.results});
});
}
function getResults(newsId){
chrome.storage.get('results', function(item){
item.results.forEach(function(v,i,a){
if(v.newsId == newsId){
// here v.resultsArray is the array we stored
// we can remove any part of it such as
v.resultsArray.splice(0,1);
// or
a.splice(i,1);
// to remove the whole object, then simply set it again
chrome.storage.local.set({'results':a});
}
});
});
}
This way you don't need to worry about dynamically naming any fields or keys.
First of All thanks to Rob and BreadFist and all you guys. I found out why my code wasn't working.
Storage.Set doesn't accept the key to be an 'integer' and even if you try to convert that key to be a 'string' it won't work too. So I've added a constant character before each key and it worked. Here's my code.
function saveResults(Id, resultsArray) {
var key = Id.toString();
key = 'a'.key;
chrome.storage.local.set({key : resultsArray});
}
function Load(Id) {
var key = Id.toString();
key = 'a'.key;
chrome.storage.local.get(key, function(result){
console.debug('result: ', result.key);
});
}

Writing google Javascript similar to vlookup

ColumnA ColumnB
jhinz 115
tom 116
The idea behind this code is someone enters a number (lets say 116), the computer looks it up in column B and returns the name in column A (tom)
The only part I need help on for the code is the computer looking up the value in column 116.
I was trying to do a for loop with a nested if statement but it wasn't working.
Could someone help me?
in its simplest form and to see the working principle you could try this :
function findinB() {
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var last=ss.getLastRow();
var data=sh.getRange(1,1,last,2).getValues();// create an array of data from columns A and B
var valB=Browser.inputBox('Enter value to search in B')
for(nn=0;nn<data.length;++nn){
if (data[nn][1]==valB){break} ;// if a match in column B is found, break the loop
}
Browser.msgBox(data[nn][0]);// show column A
}
I figured that #Serge's function can be made slightly more modular and might be worth sharing.
/*
Imitates the Vlookup function. Receives:
1. sheet - A reference to the sheet you would like to run Vlookup on
2. column - The number of the column the lookup should begin from
3. index - The number of columns the lookup should cover.
4. value - The desired value to look for in the column.
Once the cell of the [value] has been found, the returned parameter would be the value of the cell which is [index] cells to the right of the found cell.
*/
function vlookup(sheet, column, index, value) {
var lastRow=sheet.getLastRow();
var data=sheet.getRange(1,column,lastRow,column+index).getValues();
for(i=0;i<data.length;++i){
if (data[i][0]==value){
return data[i][index];
}
}
}
Any suggestions or improvements are appreciated.
This could also be a good opportunity to start a repo for much needed Google Sheet API functions that are missing. I started a new repo which might someday turn into something more useful, if you're up to contributing your own custom made functions, please don't hesitate to PR.
Cheers!
//~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`
//--//Dependent on isEmpty_()
// Script Look-up
/*
Benefit of this script is:
-That google sheets will not continually do lookups on data that is not changing with using this function as it is set with hard values until script is kicked off again.
-Unlike Vlookup you can have it look at for reference data at any Column in the row. Does not have to be in the first column for it to work like Vlookup.
-You can return the Lookup to Memory for further processing by other functions
Useage:
var LocNum = SpreadsheetApp.openById(SheetID).getSheetByName('Sheet1').getRange('J2:J').getValues();
Lookup_(Sheetinfo,"Sheet1!A:B",0,[1],"Sheet1!I1","n","y");
//or
Lookup_(Sheetinfo,"Sheet1!A:B",0,[1],"return","n","n");
//or
Lookup_(Sheetinfo,"Sheet1!A:B",0,[0,1],"return","n","n");
//or
Lookup_(Sheetinfo,"Sheet1!A:B",1,[0],"return","y","n");
//or
Lookup_(Sheetinfo,"Sheet1!A:G",4,[0],"Database!A1","y","y");
//or
Lookup_(Sheetinfo,LocationsArr,4,[0],"return","y","y");
*/
function Lookup_(Search_Key,RefSheetRange,SearchKey_Ref_IndexOffSet,IndexOffSetForReturn,SetSheetRange,ReturnMultiResults,Add_Note)
{
if(Object.prototype.toString.call(Search_Key) === '[object String]')
{
var Search_Key = new Array(Search_Key);
}
if(Object.prototype.toString.call(IndexOffSetForReturn) === '[object Number]')
{
var IndexOffSetForReturn = new Array(IndexOffSetForReturn.toString());
}
if(Object.prototype.toString.call(RefSheetRange) === '[object String]')
{
var RefSheetRangeArr = RefSheetRange.split("!");
var Ref_Sheet = RefSheetRangeArr[0];
var Ref_Range = RefSheetRangeArr[1];
var data = SpreadsheetApp.getActive().getSheetByName(Ref_Sheet).getRange(Ref_Range).getValues(); //Syncs sheet by name and range into var
}
if(Object.prototype.toString.call(RefSheetRange) === '[object Array]')
{
var data = RefSheetRange;
}
if(!/^return$/i.test(SetSheetRange))
{
var SetSheetRangeArr = SetSheetRange.split("!");
var Set_Sheet = SetSheetRangeArr[0];
var Set_Range = SetSheetRangeArr[1];
var RowVal = SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(Set_Range).getRow();
var ColVal = SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(Set_Range).getColumn();
}
var twoDimensionalArray = [];
for (var i = 0, Il=Search_Key.length; i<Il; i++) // i = number of rows to index and search
{
var Sending = []; //Making a Blank Array
var newArray = []; //Making a Blank Array
var Found ="";
for (var nn=0, NNL=data.length; nn<NNL; nn++) //nn = will be the number of row that the data is found at
{
if(Found==1 && /^n$/i.test(ReturnMultiResults)) //if statement for found if found = 1 it will to stop all other logic in nn loop from running
{
break; //Breaking nn loop once found
}
if (data[nn][SearchKey_Ref_IndexOffSet]==Search_Key[i]) //if statement is triggered when the search_key is found.
{
var newArray = [];
for (var cc=0, CCL=IndexOffSetForReturn.length; cc<CCL; cc++) //cc = numbers of columns to referance
{
var iosr = IndexOffSetForReturn[cc]; //Loading the value of current cc
var Sending = data[nn][iosr]; //Loading data of Level nn offset by value of cc
if(isEmpty_(Sending)) //if statement for if one of the returned Column level cells are blank
{
var Sending = "#N/A"; //Sets #N/A on all column levels that are blank
}
if (CCL>1) //if statement for multi-Column returns
{
newArray.push(Sending);
if(CCL-1 == cc) //if statement for pulling all columns into larger array
{
twoDimensionalArray.push(newArray);
var Found = 1; //Modifying found to 1 if found to stop all other logic in nn loop
break; //Breaking cc loop once found
}
}
else if (CCL<=1) //if statement for single-Column returns
{
twoDimensionalArray.push(Sending);
var Found = 1; //Modifying found to 1 if found to stop all other logic in nn loop
break; //Breaking cc loop once found
}
}
}
if(NNL-1==nn && isEmpty_(Sending)) //following if statement is for if the current item in lookup array is not found. Nessessary for data structure.
{
for(var na=0,NAL=IndexOffSetForReturn.length;na<NAL;na++) //looping for the number of columns to place "#N/A" in to preserve data structure
{
if (NAL<=1) //checks to see if it's a single column return
{
var Sending = "#N/A";
twoDimensionalArray.push(Sending);
}
else if (NAL>1) //checks to see if it's a Multi column return
{
var Sending = "#N/A";
newArray.push(Sending);
}
}
if (NAL>1) //checks to see if it's a Multi column return
{
twoDimensionalArray.push(newArray);
}
}
}
}
if(!/^return$/i.test(SetSheetRange))
{
if (CCL<=1) //checks to see if it's a single column return for running setValue
{
var singleArrayForm = [];
for (var l = 0,lL=twoDimensionalArray.length; l<lL; l++) //Builds 2d Looping-Array to allow choosing of columns at a future point
{
singleArrayForm.push([twoDimensionalArray[l]]);
}
SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,singleArrayForm.length,singleArrayForm[0].length).setValues(singleArrayForm);
}
if (CCL>1) //checks to see if it's a multi column return for running setValues
{
SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,twoDimensionalArray.length,twoDimensionalArray[0].length).setValues(twoDimensionalArray);
}
if(/^y$/i.test(Add_Note))
{
if(Object.prototype.toString.call(RefSheetRange) === '[object Array]')
{
SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,1,1).setNote("VLookup Script Ran On: " + Utilities.formatDate(new Date(), "PST", "MM-dd-yyyy hh:mm a") + "\nRange: Origin Variable" );
}
if(Object.prototype.toString.call(RefSheetRange) === '[object String]')
{
SpreadsheetApp.getActive().getSheetByName(Set_Sheet).getRange(RowVal,ColVal,1,1).setNote("VLookup Script Ran On: " + Utilities.formatDate(new Date(), "PST", "MM-dd-yyyy hh:mm a") + "\nRange: " + RefSheetRange);
}
}
SpreadsheetApp.flush();
}
if(/^return$/i.test(SetSheetRange))
{
return twoDimensionalArray
}
}
//~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`
//~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`
// Empty String Check
function isEmpty_(string)
{
if(Object.prototype.toString.call(string) === '[object Boolean]') return false;
if(!string) return true;
if(string == '') return true;
if(string === false) return true;
if(string === null) return true;
if(string == undefined) return true;
string = string+' '; // check for a bunch of whitespace
if('' == (string.replace(/^\s\s*/, '').replace(/\s\s*$/, ''))) return true;
return false;
}
//~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`~,~`
I'm still new to JavaScript and Google Script but this seems to work.
And I'm sure there's a better way to limit the for-loop than data.length, but I don't know it.
function vlookup(row, col) {
var x=1, y=1;
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
for(x=1; x<data.length; x++){
while(data[x][0]===row){
for(y=1; y<data.length; y++){
while(data[0][y]===col){
var result = data[x][y]
return result;
}
}
}
}
}
I know I'm late to the party, but I built this script a while back. As expected, it's slow, but it performs vlookup as a script function. Range should be passed as a multidimensional array (array[row][col]). In Google Sheets you can place the cell range in the attributes and it will work:
function vlookupscript(search_key,range,index){
var returnVal = null;
for(var i in range) {
if(range[i][0] == search_key){
returnVal = range[i][(index-1)];
break;
}
}
return returnVal;
}

Categories

Resources