I help maintain a Google spreadsheet where new data is added via a HTML form.
When it comes to add new data the insertion point of the new data depends on one of the form fields (Application Received date).
The script finds where in the sheet the data should be inserted and does 3 things:
Inserts a blank row at the correct location
Copies the row above (so formulas and conditional formatting are copied)
Replaces the data in the cells from the copy with the values entered into the form
The issue is cells A to I are value based (populated from the form) and so are cells M to O, but cells J,K,L are calculations based on some cells in A to I.
This means I have to make 2 calls to getRange/setValues and sometimes the second call (the call to set cells M,N,O does not work. The result is a new row created with the correct data in cells A to I (and thus J,K,L) but cells M,N,O stay as whatever is in those cells in the row above.
Here is the relevant code.
// Assign object data for cells A to I
var newvalues = [
[ username, applyDate, maritalStatus, sponsorApprovalDate, processingOffice, inProcessDate, extraDocsRequestedDate, nonVisaExempt, decisionMadeDate ]
];
// Set cells A to I with data from form
sheet.getRange('A' + startingRowIndex + ':I' + startingRowIndex).setValues(newvalues);
// Now assign object data for cells M to O
newvalues = [
[ coprReceivedDate, location, notes ]
];
// Set cells M to O with data from form
sheet.getRange('M' + startingRowIndex + ':O' + startingRowIndex).setValues(newvalues);
As stated above the second sheet.getRange('...').SetValues() call fails to set the values.
Any ideas?
Instead of completely recalculating the locations of your output ranges, you could get an "anchor" point at the start of the row, then use the Range.offset() method to define additional ranges relative to the anchor.
// Assign object data for cells A to I
var newvalues = [
[ username, applyDate, maritalStatus, sponsorApprovalDate, processingOffice, inProcessDate, extraDocsRequestedDate, nonVisaExempt, decisionMadeDate ]
];
// Get range "anchor" for data from form
var newRow = sheet.getRange('A' + startingRowIndex );
// Set cells A to I with data from form
newRow.offset(0,0,newvalues.length,newvalues[0].length).setValues(newvalues);
// Now assign object data for cells M to O
newvalues = [
[ coprReceivedDate, location, notes ]
];
// Set cells M to O with data from form
newRow.offset(0,13,newvalues.length,newvalues[0].length).setValues(newvalues);
Related
I have two sheets. Test Data has 3-4k entries of many columns of data and Order Changes has no data at all. I would like to search two specific columns on Test Data, a column of names and a column of yes or no. If column two of Test Data contains a 'yes' in the cell then the name of that person would be placed into a cell on order changes.
This is what I have so far:
function isThreshold(){
var data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Test Data");
var cdata = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Order Changes");
var lc = data.getLastColumn();
var lookUp = data.getRange(1,6,3,2).getValues();
lookUp.forEach(var info in lookUp){
}
Logger.log(lookUp);
}
I probably shouldn't loop through that many entries but I don't know of any other way. Should I combine the forEach loop with an if loop to get the desired result or use some other method?
I believe your goal as follows.
You want to retrieve the values from the cells "F1:G" of sheet "Test Data".
You want to search yes from the column "G" and when the column "G" is yes, you want to put the value of the column "F" to the sheet "Order Changes".
Modification points:
SpreadsheetApp.getActiveSpreadsheet() can be declared one time.
In this case, you can retrieve the values from the range of "F1:G" + data.getLastRow() of "Test Data", and create the array for putting to the sheet "Order Changes", and put it.
When above points are reflected to your script, it becomes as follows.
Modified script:
function isThreshold(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getSheetByName("Test Data");
var cdata = ss.getSheetByName("Order Changes");
var valuesOfTestData = data.getRange("F1:G" + data.getLastRow()).getValues();
var valuesForOrderChanges = valuesOfTestData.reduce((ar, [f, g]) => {
if (g.toLowerCase() == "yes") ar.push([f]);
return ar;
}, []);
if (valuesForOrderChanges.length > 0) {
cdata.getRange(1, 1, valuesForOrderChanges.length, valuesForOrderChanges[0].length).setValues(valuesForOrderChanges);
// or cdata.getRange(cdata.getLastRow() + 1, 1, valuesForOrderChanges.length, valuesForOrderChanges[0].length).setValues(valuesForOrderChanges);
}
}
In this modified script, from your question, it supposes that the columns "F" and "G" are the value of name and yes or no.
References:
getRange(a1Notation) of Class Sheet
reduce()
I get the following string returned by the CBOE options api:
{u'inputs': {u'stock_price_max': 50.0, u'high_low_stock_max': None, u'time_frame': u'middle', u'hv30_max': None, u'high_low_stock_min': None, u'symbols': None, u'hv30_min': None, u'low_strike': 3.0, u'high_strike': 4.0, u'industry_codes': None, u'spread_ask_price_max': None, u'stock_price_min': 10.0}, u'output': [{u'stock_price': 43.2, u'stock_hi_lo_percent': 72.9651, u'symbol': u'EWZ', u'industry_code': 55501010, u'max_gain': 0.52, u'high_strike_otm_percent': 0.463, u'low_strike_otm_percent': 2.7778, u'spread_ask': 0.48, u'spread': u'43/42 Put', u'expiry': u'2019-04-18', u'max_gain_to_spread_ask_percent': 108.3333, u'hv30': 27.3836}, {u'stock_price': 41.37, u'stock_hi_lo_percent': 21.7957, u'symbol': u'FXI', u'industry_code': 55501010, u'max_gain': 0.26, u'high_strike_otm_percent': 0.8944, u'low_strike_otm_percent': 2.103, u'spread_ask': 0.24, u'spread': u'41/40.5 Put', u'expiry': u'2019-05-17', u'max_gain_to_spread_ask_percent': 108.3333, u'hv30': 20.2925}
I want to loop through it and place elements into cells in a Google spreadsheet. I have this code:
function myFunction() {
var response = UrlFetchApp.fetch(endpoint);
var data = response.getContentText();
sheet.getRange("A8").setValue(data);
}
This puts the entire string into cell A8.
I have tried to loop through the string with
for (i = 0; i < jsonlen; i++) {
sheet.getRange("A:A").setValaue(data['output']['symbol']);
}
This returns "undefined". So problems are:
1) how can I extract the elements I need form the "output" part of the string
2) put the symbols into A3, A4 etc then stock_price into B3, B4 etc
3) how to identify the length of the string in order to make the loop work correctly
until the string has been entirely looped over?
Many thanks!
You want to retrieve the values of symbol and stock_price the property of output and want to put them to the columns "A" and "B" of the active Spreadsheet, respectively.
You want to put the values from the row 3.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification points:
the property of output has an array. And symbol and stock_price are in the array.
So at first, it is required to prepare the values for putting to Spreadsheet.
Modified script:
function myFunction() {
var response = UrlFetchApp.fetch(endpoint);
var data = JSON.parse(response.getContentText());
var values = data.output.map(function(e) {return [e.symbol, e.stock_price]});
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange(3, 1, values.length, values[0].length).setValues(values);
}
In this case, the values are put to the row 3 of column "A" and "B" on the active sheet.
Note:
In your case, if data is actually the string value of {u'inputs': {###}, u'outputs': [###]} which has the unicode literal of python 2, u is required to be replaced. So in this case, please put data = JSON.parse(data.replace(/u\'|\'/g, "\"").replace(/None/g, "null")); before var values = data.output.map(function(e) {return [e.symbol, e.stock_price]});. Please be careful this.
References:
map()
getRange(row, column, numRows, numColumns)
setValues(values)
If I misunderstood your question and this was not the result you want, I apologize.
Im trying to get the parent object of a nested field that I'm editing.
Lets assume my column definition is like this:
columns:[
{title:"Name" + i, field:"user" + i + ".name", cellEdited : cellEditDone},
],
And my cellEditDone looks like this:
cellEditDone: function (cell) {
var rowData = cell.getRow().getData();
var value = cell.getValue();
}
Now as you can see the column definitions(s) may be dynamically generated based on how many manes should appear in a row.
So, when I finish editing the cell, I need to get the specific object (in this case the user object) that has been edited, and not just the name field.
Can I somehow get the parent of the cells field or can I somehow get additional custom data to the cellEditDone function for each row/cell as to identify the specific user having the name edited?
Well.
Been at it for the whole evening and 5 minutes after I post a question here, I find a solution..
It turns out that you can add custom data to the column definition;
columns:[
{title:"Name" + i, field:"user" + i + ".name", cellEdited : cellEditDone, userIndex : i},
],
and retrieve it like this in the editDone event :
var definition = cell.getColumn().getDefinition();
var theIndex = definition.userIndex;
I need to synchronize the contents of 2 spreadsheets that reference each other, keeping their rows in sync if a new row is added in one of the sheets.
I've got 2 spreadsheets in Google Sheets (although if there is a cross spreadsheet solution, both Excel and GS that would be great):
Spreadsheet1 has data in A:F and party1 (a set of users) writes their data in it.
Spreadsheet2 is and import range of A:F from spreadsheet1 and then has further details written in G:M, the data is written in by party2.
The way it works is party1 writes in their data in rows A1-F10 then party2 writes their additional data in spreadsheet2 based on what party1 has written in.
For example if Spreadsheet1 A1:F10 was a name, price, est delivery time, qty etc. of an item, Spreadsheet2 G1:M10 might be a bunch of data on order date, delivered (yes / no) etc.
The issue I'm currently having is that when the spreadsheets are setup they read across fine i.e. 1-10 in spreadsheet1 lines up with 1-10 in spreadsheet2, but after a while some new rows get added into spreadsheet1 between the old rows 2-5. This throws out the order in spreadsheet2 (now row 4 in spreadsheet1 doesn't line up with the row 4 in spreadsheet2 and the data becomes out of line). Is there away around this so that even if someone adds additional rows in the middle of existing rows both spreadsheets will update?
This is a classic problem in database design; how to associate information in two tables. The usual solution is to use key data; one or more columns that exist in both tables and provide a unique identifier, or key, to associate rows.
We can adapt that idea to your situation, with a script that will adjust the location of rows in Spreadsheet 2 to synchronize with Spreadsheet 1. To do that, we need to identify a key - say the Name column - which must exist in both spreadsheets.
This entails a small change in spreadsheet 2, where a Name column will now appear in column G, following the imported range in columns A-F.
A B C D E F G H I J
| Name | Price | est delivery time | qty | etc. of | an item | Name | order date | delivered | blah blah |
< - - - - - - - - - - - - Imported - - - - - - - - - - - > *KEY* < - - - - - - sheet 2 - - - - - >
Demo
Here's how that would look in action! This example is using two sheets in the same spreadsheet, just for convenience. In the demo, a new "Item" row is added in the middle of sheet 1, which automatically appears on sheet 2 thanks to the =IMPORTRANGE() function. The synchronizing function is running on a 1-minute timed Trigger, and you'll see it move things around about 20 seconds in.
You can grab a copy of the spreadsheet + embedded script here.
Code
/**
* Call syncTables() with the name of a key column.
*/
function doSyncTables() {
syncTables( "Name" );
}
/*
* Sync "Orders" spreadsheet with imported rows from "Items" spreadsheet.
*
* From: http://stackoverflow.com/a/33172975/1677912
*
* #param {String} keyName Column header used as key colum, appears
* at start of "Orders" data, following
* "Items" data.
*/
function syncTables( keyName ) {
var sheet2 = SpreadsheetApp.openById( sheetId2 ).getSheetByName('Orders');
// Get data
var lastCol = sheet2.getLastColumn();
var lastRow = sheet2.getLastRow(); // Includes all rows, even blank, because of =importRange()
var headers = sheet2.getRange(1, 1, 1, lastCol).getValues()[0];
var keyCol = headers.lastIndexOf( keyName ) + 1;
var itemKeys = sheet2.getSheetValues(1, 1, lastRow, 1).map(function(row) {return row[0]});
var itemData = sheet2.getSheetValues(1, 1, lastRow, keyCol-1);
var orderData = sheet2.getSheetValues(1, keyCol, lastRow, lastCol-keyCol+1);
var ordersByKey = []; // To keep track of orders by key
// Scan keys in orderData
for (var row=1; row<orderData.length; row++) {
// break loop if we've run out of data.
var orderKey = orderData[row][0];
if (orderKey === '') break;
ordersByKey[ orderKey ] = orderData.slice(row, row+1)[0];
var orderKey = orderData[row][0];
}
var newOrderData = []; // To store reordered rows
// Reconcile with Items, fill out array of matching orders
for (row = 1; row<itemData.length; row++) {
// break loop if we've run out of data.
var itemKey = itemData[row][0];
if (itemKey === '') break;
// With each item row, match existing order data, or add new
if (ordersByKey.hasOwnProperty(itemKey)) {
// There is a matching order row for this item
newOrderData.push(ordersByKey[itemKey]);
}
else {
// This is a new item, create a new order row with same key
var newRow = [itemKey];
// Pad out all columns for the new row
for (var col=1; col<orderData[0].length; col++) newRow.push('');
newOrderData.push(newRow);
}
}
// Update spreadsheet with reorganized order data
sheet2.getRange(2, keyCol, newOrderData.length, newOrderData[0].length).setValues(newOrderData);
}
the current answer by mogsdad is great as always. i just wanted to point out a less complex alternative:
if you can live with preventing spreadsheet1 from allowing insertions or deletion of rows, you will avoid the issue. instead of removing rows you could use a column to mark "deleted" for example (and use filters to remove from view).
to prevent row insertions and deletions in spreadsheet1, simply select an entire unused column to the right, and create a protected range so none of the editors have permission. that prevents modifying at the row level up to the last existing row (but new rows can still be inserted below the range)
it also doesnt prevent users from swapping two row's data. but its still good to know about this simpler alternative.
I want to do is to store all the data in the table to a localstorage so that if i refresh or closed the browser and reopened again the contents are still their before it was refresh or closed. And if i delete a specific row it should be deleted also in the localstorage so that it wont comeback if i refresh the page.
My problem is, it only store 1 row of the table in the localstorage the other rows is not saved.
current output: http://jsfiddle.net/4GP2h/57/
In a rush? Here's the fiddle :
Right now, you're setting the "dataSet" localStorage item to the last row added by the user:
var data = [
$('#name').val(),
$('#age').val(),
$("[name='gender']:checked").val(),
"<button class='delete'>Delete</button>"
];
oTable.row.add(data).draw();
//Assignment operator!
var dataset = JSON.stringify(data);
localStorage.setItem('dataSet', dataset);
Thus, only one row gets saved. Instead, you need to accommodate for multiple rows using a multi-dimensional array.
Outputting the data from the "dataSet" localStorage item:
//Use a try/catch loop to make sure that no errors come up while parsing the JSON of our localStorage
var dataSet;
try{
dataSet = JSON.parse(localStorage.getItem('dataSet')) || [];
} catch (err) {
dataSet = [];
}
/*
I did not add this in here, but you might want to since if our item returns an empty array, it either has an empty array in it, in which case the following code won't affect anything, or it has a localStorage item we don't want in it, in which case this will fix that.
if (!dataSet.length) localStorage.setItem('dataSet', '[]')
*/
$('#myTable').dataTable({
"data": [],
[...]
});
oTable = $('#myTable').DataTable();
//Loop through dataSet to add _all_ of the rows.
for (var i = 0; i < dataSet.length; i++) {
oTable.row.add(dataSet[i]).draw();
}
Adding data to the "dataSet" localStorage item:
var data = [
$('#name').val(),
$('#age').val(),
$("[name='gender']:checked").val(),
"<button class='delete'>Delete</button>"
];
oTable.row.add(data).draw();
//Push the new data into dataSet. DO NOT assign dataSet to data.
dataSet.push(data);
//Update the localStorage item.
localStorage.setItem('dataSet', JSON.stringify(dataSet));
Removing data from the "dataSet" localStorage item:
var row = $(this).closest('tr');
//Find the index of the row...
var index = $("tbody").children().index(row);
oTable.row(row).remove().draw();
//...and remove it from dataSet.
dataSet.splice(index, 1);
localStorage.setItem('dataSet', JSON.stringify(dataSet));
Well, your problem is that you're saving the dataset as the current row every time you hit the save button:
var data = [
$('#name').val(),
$('#age').val(),
$("[name='gender']:checked").val(),
"<button class='delete'>Delete</button>"];
oTable.row.add(data).draw();
var dataset =JSON.stringify(data);
localStorage.setItem('dataSet', dataset);
Try appending the latest row to the total dataset and saving that instead,
I save it to localStorage like this:
var tableTotal = $('#orders_table').DataTable().rows().data();
var jsonTable = JSON.stringify(tableTotal);
localStorage.setItem("key", jsonTable);