Error when trying to store an array in ScriptDb - google-apps-script

I have an array of objects that is created by my script and I am trying to copy that array into a new array and then store it in scriptDb using the following function:
function copyAndStore (currentArray) {
var db = ScriptDb.getMyDb();
var copyArray = [];
for (var i in currentArray) {
copyArray.push(currentArray[i]);
}
var id = db.save(copyArray);
return id;
}
It copies everything properly but when it gets to var id = db.save(copyArray); I get the error: Invalid argument. Expected a javascript map object.
Does ScriptDb have issues with storing arrays? Thanks in advance for the help.

As #Thomas said, you can save an array in a map object.
You don't need to perform a copy operation before putting an object into the ScriptDB, either. You could save your array by simply db.save({myArray}), and remember the ID.
Here's some minimalist code to demonstrate. I'm showing two ways to retrieve your saved array - one by ID, which seems to be the way you were planning to, but also a second way using a "key" value for a query. If you expect to retrieve the contents of ScriptDB in a later run of your code, this approach eliminates the need to somehow remember the ID of the stored array.
function saveArray (currentArray) {
var db = ScriptDb.getMyDb();
return db.save({type: "savedArray", data:currentArray}).getId();
}
function loadArrayById (id) {
var db = ScriptDb.getMyDb();
return db.load(id).data;
}
function loadArrayByType () {
var db = ScriptDb.getMyDb();
var result = db.query({type: "savedArray"});
if (result.hasNext()) {
return result.next().data;
}
else {
return [];
}
}
function test() {
var arr = ['this','is','a','test'];
var savedId = saveArray( arr );
var loaded1 = loadArrayById( savedId );
var loaded2 = loadArrayByType();
debugger; // pause if running debugger
}
Here's what you'll see at the debugger pause:
Note that by using the map tag data to pull the array from the saved object, both loaded1 and loaded2 are identical to the source array arr.

ScriptDb only stores map objects. You could however store a map that contains an array!
You can use arrays to save several objects in a single call using db.saveBatch.

Related

how to add to an array in appscript without resetting the array

I'm trying to add to an array when a dropdown button is pressed but app-script seems to redeclare the array everytime an event is triggered. is there a way to stop this?
the code in question looks like this:
let by = [];
function addToArray (name = 'hello') {
by.push(name.replace(/\s[\s\S]*/, '').toLowerCase());
console.log(by.join(', '))
}
// ...
// function called every dropdown buttonclick
function handelAdd () {
const response = DocumentApp.getUi().prompt('Name:'); // prompt that gets name of item added
if (response.getSelectedButton() == DocumentApp.getUi().Button.OK) {
add(response.getResponseText());
}
}
I am going to assume that the following line in the question, here:
add(response.getResponseText()); // incorrect?
Should actually be this:
addToArray(response.getResponseText()); // correct?
Whenever your script finishes execution, any state it had saved (for example the value of the array in let by = [];) is lost.
This is to be expected. The script has finished. It exits. The document is still open, but the script has completed its work. It will run again the next time there is a relevant button click event.
To save state between multiple runs of the script, you can use Properties Services. This allows you to store a variable associated with the specific document (and user of that document). You can save your list to this storage, and retrieve it when you need to add a new item to it.
But you also need to store your array of data as a string in this storage - so in my example below I will convert the [...] array to a JSON string representation of the array, using JSON.stringify(). And I will convert back from JSON to an array using JSON.parse():
let by = [];
let byList = 'BY_LIST';
function addToArray (name = 'hello') {
let userProps = PropertiesService.getUserProperties();
// retrieve stored list and convert back from JSON to array:
by = JSON.parse(userProps.getProperty(byList));
console.log(by);
by.push(name.replace(/\s[\s\S]*/, '').toLowerCase());
userProps.setProperty(byList, JSON.stringify(by));
console.log(by.join(', '))
}
// ...
// function called every dropdown buttonclick
function handelAdd() {
const response = DocumentApp.getUi().prompt('Name:'); // prompt that gets name of item added
if (response.getSelectedButton() == DocumentApp.getUi().Button.OK) {
addToArray(response.getResponseText());
}
}
// used for my testing:
//function testMe() {
// handelAdd();
//}
function onOpen(e) {
// whenever the doc is re-opened, set the list to be
// empty, and store it in userProperties as a JSON string:
let userProps = PropertiesService.getUserProperties();
userProps.setProperty(byList, JSON.stringify( [] ));
}
The onOpen() trigger ensures that each time the document is opened, we start with a new empty list.

What alternative to ScriptDB I could use to store a big array of arrays? (without using external DB)

I was a user of the deprecated ScriptDB. The use I made of ScriptDB was fairly simple: to store a certain amount of information contained on a panel options, this way:
var db = ScriptDb.getMyDb();
function showList(folderID) {
var folder = DocsList.getFolderById(folderID);
var files = folder.getFiles();
var arrayList = [];
for (var file in files) {
file = files[file];
var thesesName = file.getName();
var thesesId = file.getId();
var thesesDoc = DocumentApp.openById(thesesId);
for (var child = 0; child < thesesDoc.getNumChildren(); child++){
var thesesFirstParagraph = thesesDoc.getChild(child);
var thesesType = thesesFirstParagraph.getText();
if (thesesType != ''){
var newArray = [thesesName, thesesType, thesesId];
arrayList.push(newArray);
break;
}
}
}
arrayList.sort();
var result = db.query({arrayName: 'savedArray'});
if (result.hasNext()) {
var savedArray = result.next();
savedArray.arrayValue = arrayList;
db.save(savedArray);
}
else {
var record = db.save({arrayName: "savedArray", arrayValue:arrayList});
}
var mydoc = SpreadsheetApp.getActiveSpreadsheet();
var app = UiApp.createApplication().setWidth(550).setHeight(450);
var panel = app.createVerticalPanel()
.setId('panel');
var label = app.createLabel("Choose the options").setStyleAttribute("fontSize", 18);
app.add(label);
panel.add(app.createHidden('checkbox_total', arrayList.length));
for(var i = 0; i < arrayList.length; i++){
var checkbox = app.createCheckBox().setName('checkbox_isChecked_'+i).setText(arrayList[i][0]);
panel.add(checkbox);
}
var handler = app.createServerHandler('submit').addCallbackElement(panel);
panel.add(app.createButton('Submit', handler));
var scroll = app.createScrollPanel().setPixelSize(500, 400);
scroll.add(panel);
app.add(scroll);
mydoc.show(app);
}
function include(arr, obj) {
for(var i=0; i<arr.length; i++) {
if (arr[i] == obj) // if we find a match, return true
return true; }
return false; // if we got here, there was no match, so return false
}
function submit(e){
var scriptDbObject = db.query({arrayName: "savedArray"});
var result = scriptDbObject.next();
var arrayList = result.arrayValue;
db.remove(result);
// continues...
}
I thought I could simply replace the ScriptDB by userProperties (using JSON to turn the array into string). However, an error warns me that my piece of information is too large to be stored in userProperties.
I did not want to use external databases (parse or MongoDB), because I think it isn't necessary for my (simple) purpose.
So, what solution I could use as a replacement to ScriptDB?
You could store a string using the HtmlOutput Class.
var output = HtmlService.createHtmlOutput('<b>Hello, world!</b>');
output.append('<p>Hello again, world.</p>');
Logger.log(output.getContent());
Google Documentation - HtmlOutput
There are methods to append, clear and get the content out of the HtmlOutput object.
OR
Maybe create a Blob:
Google Documentation - Utilities Class - newBlob Method
Then you can get the data out of the blob as a string.
getDataAsString
Then if you need to you can convert the string to an object if it's in the right JSON format.
Firstly, if you're hitting the limits on the Properties service, I would recommend you look at an alternative external store, as you're manipulating a large amount of data, and any workaround given here is possibly going to be slower and less efficient then simply using a dedicated service.
Alternatively of course, you could look at making your data come under the limits for the properties service by splitting it up and using multiple properties etc.
One other alternative would be to use a Google Doc or Sheet to store the string. When you're required to pull the data again, you can simply access the sheet and get the string, but this might be slow depending on the size of the string. At a glance it looks like you're just pulling Data on the folders in your drive, so you could consider writing it to a sheet, which would allow you to even display the information in a user friendly way. Given your use of arrays already, you can write them to a sheet easily using .setValues() if you convert them to a 2D array.
Bruce McPherson has done a lot of work on abstracting databases. Take a look at his cDbAbstraction library then you could easily chop and change which DB you use and compare performance. Maybe even create a cDbAbstraction library to use HTMLOutput (I like that idea Sandy, Bruce does some funky stuff with parallel processes via HTMLService)

What is the best way to split a variable containing a json from an API and then to put it into a specific cell?

Here is my code:
var pUrl = "http://api.perk.com/api/user/id/";
var tk = "/token/";
var options = {
"method" : "GET",
"contentType": 'application/text'
}
function perkTvApi1() {
var response = UrlFetchApp.fetch("http://api.perk.com/api/user/id/515098/token/01133528575d15554742e5bb9bc1fc484fd95ac2/", options);
Logger.log(response.getContentText());
}
I'm trying to figure out how to split the response variable so I can then put it into a spreadsheet row which corresponds to a time stamp.
I haven't been able to find any kind of split function, like I would use in javascript, in Script Services. I'm running out of ideas and approaches.
The easiest way is to use the JSON library: the two most useful functions are JSON.parse() and JSON.stringify().
Both are native to Google Apps Script, so you'd call something like
var object = JSON.parse(response)
in perkTvApi1. Now, the object contains an actual object, which is exactly what you want.
Then, it's just a matter of setting the right cell, so if you wanted cell A1 to be the first name:
SpreadsheetApp.getActiveSheet().getRange('A1').setValue(object["firstName"]);
JSON.parse() is the method you want. Check out this thread
function myFunction() {
var options = {
"method" : "GET",
"contentType": 'application/text'
}
var response = UrlFetchApp.fetch("http://api.perk.com/api/user/id/515098/token/01133528575d15554742e5bb9bc1fc484fd95ac2/", options);
var jsonResponse = JSON.parse(response)
Logger.log(jsonResponse.firstName);
Logger.log("Key length: " + Object.keys(jsonResponse).length);
var keys = Object.keys(jsonResponse);
//Loop through the keys array to push the json object into an array
//The array can then be used to set values in spreadsheet range
//May need to create 2d array depending on how you want your rows/columns arranged.
var ssData = [];
for (var i in keys){
ssData.push(jsonResponse[keys[i]]);
}
Logger.log(ssData);
}

How to create document from data array

I have FlexTable with chekBoxes in first cell of each row, when checkBox is true data from FlexTable's row is collected in variable. Now I need to create document with table that contains table with data from variable. I tried to store string's value in Hidden but it doesn't work and can't figure out how to realise it.
All my (although the code is not really my, code is almost half #Sergeinsas's) code is avaliable here: http://pastebin.com/aYmyA7N2, thankyou in advance.
There are a few errors in your code... widgets like hidden can only have string values and they can only return string values when you retrieve their values.
One possible and easy way to convert arrays to string (and back) is to use a combination of join() and split() , here is the modified code (relevant part only) that works.
// Storing checked rows
function check(e) {
var checkedArray = [];
var data = sh.getRange(1,1,lastrow,lastcol).getValues();
for(var n=0; n < data.length;++n){
if(e.parameter['check'+n]=='true'){
checkedArray.push(data[n].join(','));// convert data row array to string with comma separator
}
}
var hidden = app.getElementById('hidden');
hidden.setValue(checkedArray.join('|'));// convert array to string with | separator
return app;
}
function click(e) {
var hiddenVal = e.parameter.hidden.split('|');// e.parameter.hidden is a string, split back in an array of strings, each string should be splitted too to get the original array of arrays
var d = new Date();
var time = d.toLocaleTimeString();
var table = []
for(var n in hiddenVal){
table.push(hiddenVal[n].split(','));// reconstruction of a 2D array
}
DocumentApp.create('doc '+time).getBody().appendTable(table);// the table is in the document
}
Full code available here
EDIT : suggestion : if you put your headers in your spreadsheet you could retrieve them in your final table quite easily like this :
function check(e) {
var checkedArray = [];
var data = sh.getRange(1,1,lastrow,lastcol).getValues();
checkedArray.push(data[0].join(','));// if you have headers in your spreadsheet, you could add headers by default
for(var n=0; n < data.length;++n){
if(e.parameter['check'+n]=='true'){
checkedArray.push(data[n].join(','));
}
}
You could also use data[0] in the doGet function to build the header of your UI, I think this would make your code more easy to maintain without hardcoding of data.... but this is only a suggestion ;-)

How do you query ScriptDb for partial matches?

I tried using RegEx and it did not return any results:
function findRecord() {
var db = ScriptDb.getMyDb();
var toFind = /Quality/i;
var results = db.query({companyName: toFind});
while (results.hasNext()) {
var result = results.next();
Logger.log(Utilities.jsonStringify(result));
}
}
From what I can see, ScriptDb's query() will only return exact matches for strings.
The only way I can see is to return the entire database and then iterate through it. I really hope there is a way to query partial matches.
Try iterating over the results using the match method
function testQuery() {
var db = ScriptDb.getMyDb();
var results = db.query({});
var start = new Date();
while (results.hasNext()) {
var result = results.next();
if (result.companyName.match(/qual.*/i)){
Logger.log(Utilities.jsonStringify(result));
}
}
var endTime = new Date();
Logger.log("time is " + (endTime.getTime() - start.getTime()) + "ms");
}
ScriptDb currently doesn't support partial matches in strings. Depending on the data you may be able to use the anyOf method:
var results = db.query({
companyName: db.anyOf(['Quality', 'quality'])
});
I don't think that is possible. You may open an "enhancement request" on the issue tracker.
But depending on your usage, it may be possible to achieve your goal if you structured your database differently, probably creating some kind of "tag" category properties for your objects, that you set beforehand, i.e. when adding the object to the database, so you can query on it later.