Related
I am very new to Javascript and Apps Script. I want to create a function that updates another sheet based on a date in a certain range of the active sheet. I run and no error but it doesn't transfer value from active sheet to sheet named "January", in different target url google sheet
data master post
output 1
output 2
output 3
function myFunction4() {
const spreadsheetIds = [
{ id: "1ShPxDW9qhz4aWgaQ1G9oz7w1yh0-Wfe2VItet95UYks", sheetNames:
["cab1"] },
{ id: "13Dx3ZOpV7baSTadSApIrVVccN-bHrPlHu240Aux0fo0", sheetNames:
["cab2"] },
{ id: "14EVlqaP1ilXwopgi0ESvp_IKkSyROSF22WzWAcNAJWc", sheetNames:
["cab3", "cab4"] }
];
const srcSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const srcSheet = srcSpreadsheet.getSheetByName("January");
if (!srcSheet) return;
const date = new Date();
const ssName = srcSpreadsheet.getName();
const range = srcSheet.getRange("A2:C" + srcSheet.getLastRow());
let values = range.getValues();
if (values.filter(r => r.join("")).length == 0) return;
values = values.map(r => [...r, date, ssName]);
range.clearContent();
for (let i = 0; i < spreadsheetIds.length; i++) {
const dstSpreadsheet = SpreadsheetApp.openById(spreadsheetIds[i].id);
for (let j = 0; j < spreadsheetIds[i].sheetNames.length; j++) {
const targetSheet =
dstSpreadsheet.getSheetByName(spreadsheetIds[i].sheetNames[j]);
if (targetSheet) {
targetSheet.getRange(targetSheet.getLastRow() + 1, 1, values.length,
values[0].length).setValues(values);
}
}
}
}
copyto Google Sheets Script adding date and source data in the next column
sample video
which is desired
I want when I click the send button.
can add Date and source in the next column
Date = date when sent
source = the name of the workbook that sent it
otherSheetName.getRange(1,getJumlahKolom+1).setValue("Date").setFontWeight("bold").setHorizontalAlignment("center");
otherSheetName.getRange(1,getJumlahKolom+2).setValue("source").setFontWeight("bold").setHorizontalAlignment("center");
Date & source, the function you want to join to myFunction3()
I believe your goal is as follows.
You want to achieve your goal by modifying your showing script.
In this case, how about the following modification?
Modified script:
function myFunction3() {
const spreadsheetIds = [
{ id: "1aUoRKCnztsZAvbMwSa8Zk-gB-zfn1KeQnJgWIGRVu24", sheetNames: ["cab1"] },
{ id: "1Eme5Rb9_5kbGaT-HTy5ThR1WYUeR1fkQbn2-8wY-uUY", sheetNames: ["cab2"] },
{ id: "150DduDdhFJLC0LL7iOihYa6V1vaZckvtRxJUqCFFV9Q", sheetNames: ["cab3", "cab4"] }
];
const srcSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const srcSheet = srcSpreadsheet.getSheetByName("input");
if (!srcSheet) return;
const date = new Date();
const ssName = srcSpreadsheet.getName();
const range = srcSheet.getRange("A2:C" + srcSheet.getLastRow());
let values = range.getValues();
if (values.filter(r => r.join("")).length == 0) return;
values = values.map(r => [...r, date, ssName]);
range.clearContent();
for (let i = 0; i < spreadsheetIds.length; i++) {
const dstSpreadsheet = SpreadsheetApp.openById(spreadsheetIds[i].id);
for (let j = 0; j < spreadsheetIds[i].sheetNames.length; j++) {
const targetSheet = dstSpreadsheet.getSheetByName(spreadsheetIds[i].sheetNames[j]);
if (targetSheet) {
targetSheet.getRange(targetSheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
}
}
}
}
When this script is run, the values are retrieved from the source sheet, and the retrieved values are put to the destination sheet by adding 2 columns of date and source.
Note:
Please confirm the source and destination sheet names again. Because it seems that the sheet names of your showing script are different from your sample.
Goal: Convert multiple JavaScript arrays to CSV columns.
Context: Using an API to scan through a fleet of assets with GPS units and comprising a list of which assets are inactive, among other metrics.
Problem: The part I need assistance with is the algorithm in my nested for-loop that determines the index to insert the cell at the correct row/column.
I have looked at many packages on NPM but it doesn't appear that I was able to easily convert multiple arrays to CSV columns.
I haven't needed to ask a question here yet so it's certainly possibly I am going about this the wrong way or missed a solution to this; so any help or hints to achieve the goal are highly appreciated.
The running code is below, but the desired output would be:
All Assets,Idle Assets
1000,2001,
1001,2002,
1002,2003,
1003,2004,
1004,,
Rather than:
All Assets,Idle Assets
1000,2001,
2002,
2003,
2004,
,
1001,1002,1003,1004,
//Sample Array Data (can handle more arrays/columns)
const allAssets = ['1000', '1001', '1002', '1003', '1004'];
const idleAssets = ['2001', '2002', '2003', '2004'];
////Arrays to CSV columns
const headers = ['All Assets', 'Idle Assets'];
const columns = [allAssets, idleAssets];
//Identify columnLen (# of arrays) & rowLen (length of longest array)
let longestArr = 0;
columns.forEach((arr) =>
arr.length > longestArr
? longestArr = arr.length
: undefined);
const rowLen = longestArr;
const columnLen = columns.length;
const heading = (headers.join(',')).concat('\n');
const csvConstructor = () => {
const data = [];
for (let column = 0; column < columnLen; column++) {
const columnArray = columns[column];
for (let row = 0; row < rowLen; row++) {
let cell;
//Account for different sized arrays
columnArray[row] === undefined
? cell = ','
: cell = columnArray[row].concat(',');
//New line for cells in last column
(columns.length - 1) === column
? cell = cell.concat('\n')
: undefined;
//Dynamically insert cell into appropriate data index
const insertAt = column + row; //<--- Need help here
data.splice(insertAt, 0, cell);
}
}
const result = heading.concat(data.join(''));
return result
}
const csv = csvConstructor();
console.log(csv);
Your csvConstructor() is iterating columns individually, but for a table, you need to iterate columns in parallel. Also, it is trying to juggle two different concerns at the same time (transforming the data, and constructing the csv string), so it is a bit difficult to follow.
The problem is logically split into two phases. In the first phase, you want a well-formatted array that reflects the CSV's tabular structure (or if it's a large data set, then you probably want an iterator that yields rows one by one, but I won't do that version here).
The output should be in this format:
const result = [
[header A, header B],
[value A, value B],
[value A, value B],
// ...etc...
]
Once we have that structure, we can transform it into CSV.
So to get that based on your data:
function toCsvRows(headers, columns) {
const output = [headers]
const numRows = columns.map(col => col.length)
.reduce((a, b) => Math.max(a, b))
for (let row = 0; row < numRows; row++) {
output.push(columns.map(c => c[row] || ''))
}
return output
}
function toCsvString(data) {
let output = ''
data.forEach(row => output += row.join(',') + '\n')
return output
}
function csvConstructor(headers, columns) {
return toCsvString(toCsvRows(headers, columns))
}
Here's a working fiddle:
https://jsfiddle.net/foxbunny/fdxp3bL5/
EDIT: Actually, let me do the memory-efficient version as well:
function *toCsvRows(headers, columns) {
yield headers
const numRows = columns.map(col => col.length)
.reduce((a, b) => Math.max(a, b))
for (let row = 0; row < numRows; row++) {
yield columns.map(c => c[row] || '')
}
}
function toCsvString(data) {
let output = ''
for (let row of data) {
output += row.join(',') + '\n'
}
return output
}
function csvConstructor(headers, columns) {
return toCsvString(toCsvRows(headers, columns))
}
If you want a simple way using zip two arrays with comma. then join header and zipped rows with \n.
const allAssets = ['1000', '1001', '1002', '1003', '1004'],
idleAssets = ['2001', '2002', '2003', '2004'],
headers = ['All Assets', 'Idle Assets'],
zip = (a, b) => a.map((k, i) => `${k}, ${b[i]||''}`),
zipLonger = (a, b) => a.length > b.length ? zip(a, b) : zip(b, a);
console.log(headers.join(', ')+'\n'+
zipLonger(allAssets, idleAssets).join('\n'))
Result:
All Assets, Idle Assets
1000, 2001
1001, 2002
1002, 2003
1003, 2004
1004,
I'm trying to update the data source of plot 2 based on the hovered overvalues of plot 1, so that an accompanying viz is created in plot 2 based on the data from plot 1.
Problem is I can get the data out of rect inside of the callback and format it into x and y for a line graph, but it does not update the plot only removes the placeholder values from it.
The data object contains the x and y values that need to be displayed in p2 (the line graph) and are equal length and formatted, not sure why its not changing the sourceLine ColumnDataSource
#hover tool for additional plots
p2 = figure(plot_width = 900, plot_height = 350)
#initial data source
sourceLine = ColumnDataSource(data = {'x': [2, 3, 5, 6, 8, 7], 'y': [6, 4, 3, 8, 7, 5]})
#line to be updated
ln = p2.line(x = 'x', y = 'y', line_width = 2, source = sourceLine.data)
#callback
callback = CustomJS(args={'rect': rc.data_source.data, 'line': ln.data_source}, code="""
var rdataYear = rect.YEAR;
var rdataAgeGroup = rect.AGE_GROUP;
var rdataDeaths = rect.DEATHS;
var data = {'x': [], 'y': []};
var deaths = [];
var years = [1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017
]
var indices = cb_data.index['1d'].indices;
var newarr = rect.AGE_GROUP.map((e,i) => e === rdataAgeGroup[indices] ? i : undefined).filter(x => x);
for (var i = 0; i < newarr.length; i++){
var index = newarr[i];
deaths.push(rdataDeaths[index])
}
//data that needs to be inside p2 line
data['x'].push(years)
data['y'].push(deaths)
line.data = data;
line.change.emit();
""")
#hover tool with the callback
p1.add_tools(HoverTool(tooltips=[('Year', '#YEAR'), ('Age Group', '#AGE_GROUP'),('Deaths', '#DEATHS'),('Rate', '#RATE')], callback=callback))
I want to have the output from the callback (data) update x and y of p2.line, im kinda new to python and bokeh is a bit funky so I would really appreciate some help :)
This line is incorrect:
ln = p2.line(x = 'x', y = 'y', line_width = 2, source = sourceLine.data)
The value of source should be the ColumnDataSource itself:
ln = p2.line(x = 'x', y = 'y', line_width = 2, source = sourceLine)
When you pass a dict like .data, Bokeh will create an internal CDS for you automatically, as a convenience, but now this new CDS that the glyph uses has nothing to do with the one you update in the callback.
Also, don't use the ['1d'] syntax for selection indices. It has has been deprecated now for a very long time, and will guaranteed be removed for good entirely before the end of this year. Instead:
var indices = cb_data.index.indices
Additionally, your JS is not actually updating the data source correctly. The operation at the end data['y'].push(deaths) actually makes the list of deaths a sublist of the existing empty list, which is not the correct format. Here is an updated version of the JS code:
const rdataYear = rect.YEAR
const rdataAgeGroup = rect.AGE_GROUP
const rdataDeaths = rect.DEATHS
const years = [1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017]
const indices = cb_data.index.indices;
const newarr = rect.AGE_GROUP.map((e,i) => e === rdataAgeGroup[indices] ? i : undefined).filter(x => x);
const deaths = []
for (var i = 0; i < newarr.length; i++){
deaths.push(rdataDeaths[newarr[i]])
}
line.data = {x: years, y: deaths}
Note if you actually assign to .data then Bokeh can detect that automatically, no need for an explicit signal.
I'm having trouble printing more than one row to my google sheet with this loop.
The first row appends fine, but I want the function to append all objects from the data var.
The data object is properly pulling from Firebase when I verify with a Logger.
var firebaseUrl = "https://test.firebaseio.com/alerts";
var secret = "sssssssssssssssssssss";
var base = FirebaseApp.getDatabaseByUrl(firebaseUrl, secret);
var data = base.getData();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("feed");
var selection = sheet.getActiveRange();
var range = sheet.getActiveRange();
var values = range.getValues();
var columns = selection.getNumColumns();
var rows = selection.getNumRows();
var num = 2;
function writeToSheets() {
for(var i in data) {
var values = [
[ data[i].id, data[i].two, data[i].three, data[i].four ]
];
var keys = Object.keys(values[0]);
var sheetRow = [];
var entryKeys;
for (j in keys) {
sheetRow = [];
entryKeys = Object.keys(values[keys[j]])
for (k in entryKeys) {
sheetRow.push(values[keys[j]][entryKeys[k]]);
}
sheet.appendRow(sheetRow);
}
}
}
I've just tried this code (assuming that I guessed the data structure correctly):
function myFunction() {
var data = [
{'id': 1, 'two': 'test2', 'three': 'test3', 'four': 'test4'},
{'id': 2, 'two': 'test2-2', 'three': 'test3-2', 'four': 'test4-2'}
]
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("feed");
var selection = sheet.getActiveRange();
var range = sheet.getActiveRange();
var values = range.getValues();
var columns = selection.getNumColumns();
var rows = selection.getNumRows();
var num = 2;
function writeToSheets() {
for(var i in data) {
var values = [
[ data[i].id, data[i].two, data[i].three, data[i].four ]
];
var keys = Object.keys(values[0]);
var sheetRow = [];
var entryKeys;
for (j in keys) {
sheetRow = [];
entryKeys = Object.keys(values[keys[j]])
for (k in entryKeys) {
sheetRow.push(values[keys[j]][entryKeys[k]]);
}
sheet.appendRow(sheetRow);
}
}
}
writeToSheets();
}
When I run it, it fails after printing the first line with an error TypeError: Expected argument of type object, but instead had type undefined. (line 26, file "Code").
And it is easy to see what exactly happens if you run it in debug mode:
You have values array with one element (line 18)
The var keys = Object.keys(values[0]); becomes [0,1,2,3] (we have 4 values inside the first element of values array)
Then, having j from 0 to 3 we get entryKeys = Object.keys(values[keys[j])
When j = 0, values[keys[j]] = values[0] - we get the first element from values
When j = 1, values[keys[j]] = values[1] - here we fail, because there is only 1 element in values
I am not really sure what you are trying to do here with all these keys, but if you just want to print the data, it can be done simpler:
function writeToSheets() {
for(var i in data) {
var item = data[i];
sheetRow = [];
for (key in item) {
sheetRow.push(item[key]);
}
sheet.appendRow(sheetRow);
}
}
I have a table 10 rows, 10 columns. I want to define an array where I can place a value at e.g. pos. row 5, column 3.
The value itself is an array with more entries. And the entry of this array is also an array.
Example:
Row 1, column 1:
My text 1, Link to text 1
My text 2, Link to text 2
Row 4, column 5:
My text 3, Link to text 3
Row 6, column 2:
My text 1, Link to text 1
My text 2, Link to text 2
My text 3, Link to text 3
My text 4, Link to text 4
Not every table entry needs to be defined. A table element entry can have multiple entries. An entry consists of two values. A text and the link for the text.
The html-table is already defined. Now I want to fill it with the values (links) above.
My problem is, how to create an efficient data structure so that I easily can find table-positions that have entries (maybe without looping 10 rows 10 columns). For each entry I want to get the list of texts + links.
And how to access/read each entry of my definition. (I have no problem placing the value to my html-table.)
I'd really appreciate if someone could give me some code-example how to set up such a data structure.
var multiArray = [ ['element 0, 0', 'element 0, 1', 'element 0, 2'], ['element 1, 0', 'element 1, 1']];
and so on...
EDIT
every single notation in [] is an array, so you just have to combine them into an another array
Just use an array of array if the memory is not the problem;
var table = [];
table.length = 10; // 10 rows;
for (var i = 0; i < array.length; i++) {
table[i] = [];
table[i].length = 20; // 20 columns for each row.
}
If the table is big but only a few cells are used, you can also use a hash of hash:
var table = {};
table.rowCount = 10; // there're 10 rows
table[1] = {}
table[1].columnCount = 20 // 20 cells for row 1
table[1][3] = "hello world";
// visit all cells
for (var row in table) {
for (var column in table[row] {
console.log(table[row][column]);
}
}
You can even mix hash and array.
You could create a simple wrapper to make calling convenient: http://jsfiddle.net/QRRXG/2/.
A multidimensional array is just an array in another. So you can build an array with 10 arrays which in turn have 10 arrays in each. Then get one with arr[i][j].
Items can be represented as an object:
{ name: "foo", link: "bar" }
then such an item can be parsed like obj.name and obj.link.
var multi = (function() {
var data = [];
// initialize
for(var i = 0; i < 10; i++) {
data[i] = [];
for(var j = 0; j < 10; j++) {
data[i][j] = [];
}
}
return {
get: function(i, j) { // will return an array of items
return data[i][j];
},
push: function(i, j, v) { // will add an item
data[i][j].push(v);
},
clear: function(i, j) { // will remove all items
data[i][j] = [];
},
iterateDefined: function(f) {
for(var i = 0; i < 10; i++) {
for(var j = 0; j < 10; j++) {
if(data[i][j].length > 0) {
f(data[i][j], i, j);
}
}
}
}
};
})();
You can the use it like:
multi.push(2, 3, { name: "foo", link: "test1" });
multi.push(2, 3, { name: "bar", link: "test2" });
multi.push(1, 4, { name: "haz", link: "test3" });
multi.push(5, 7, { name: "baz", link: "test4" });
multi.clear(5, 7);
console.log(multi.get(2, 3)); // logs an array of 2 items
console.log(multi.get(1, 4)); // logs an array of 1 item
console.log(multi.get(5, 7)); // logs an array of 0 items
console.log(multi.get(2, 3)[0].name); // logs "foo"
console.log(multi.get(2, 3)[1].link); // logs "test2"
multi.iterateDefined(function(items, i, j) {
console.log(items, i, j); // will log two times
});
Create a utility Object:
var DataTable = {
source: [],
setEntry: function(i,j,e) {
var o ;
if( !!! ( o = this.source[i] ) ) o = this.source[i] = [] ;
o[j] = e ;
return this ;
},
getEntry: function(i,j) {
var o, e = null ;
if( !! ( o = this.source[i] ) ) e = o[j] || null ;
return e ;
}
} ;
The other answers seem to suggest placing dummy Arrays as placeholders for coordinates that are unused. This -- while it is not wrong -- is unnecessary: if you set an entry on an Array in JavaScript whose index exceeds the current range the Array is essentially padded with undefined values.
var a = [ ] ; // a length is 0
a[1024] = 1 // a length is now 1025, a[1] is undefined
Then add the values you require:
DataTable.setEntry( 1, 1, ["My text 1","Link to text 1","My text 2","Link to text 2"] )
.setEntry( 4, 5, ["My text 3","Link to text 3"] )
//..
;
The following control statements will return the value of the Arrays of the coordinates or null (if DataTable.source does not contain a nested Array for the given coordinates):
console.log("(!!) d.source: " + DataTable.getEntry(4,5) ) ;
console.log("(!!) d.source: " + DataTable.getEntry(1,1) ) ;
console.log("(!!) d.source: " + DataTable.getEntry(0,0) ) ;
Try it here:
link to JSFiddle
UPDATE:
This is a pretty old post, but since I received a comment to explain the snippet, here's an update with class syntax and a few more comments:
class DataTable {
data = [];
constructor() {
// bind methods to this instance
this.setEntry = this.setEntry.bind(this);
this.getEntry = this.getEntry.bind(this);
}
// set an entry at the given coordinates (row and column index pair)
setEntry(rowIndex, columnIndex, value) {
let row = this.data[rowIndex];
// create the row lazily if it does not exist yet
if(typeof row === 'undefined') {
this.data[rowIndex] = [];
row = this.data[rowIndex];
}
// set the value
row[columnIndex] = value;
}
// get the entry at the given coordinates (row and column index pair)
getEntry(rowIndex, columnIndex) {
const row = this.data[rowIndex];
// test if the row is defined; if not return null.
if(typeof row === 'undefined') { return null; }
else {
// return the value or fall back to null
return row[columnIndex] || null;
}
}
}
const d = new DataTable();
d.setEntry(1, 1, ["My text 1","Link to text 1","My text 2","Link to text 2"]);
d.setEntry(4, 5, ["My text 3","Link to text 3"]);
console.log(`d.getEntry(4, 5) = ${d.getEntry(4, 5)}`);
console.log(`d.getEntry(1, 1) = ${d.getEntry(1, 1)}`);
console.log(`d.getEntry(0, 0) = ${d.getEntry(0, 0)}`);