Using Google App Script to get values from sheets and display them in text box - javascript

So, Google recently updated Google App Script API and added lots of nice features, however, in the process, they also depreciated LOTS of API. I have been working on a Library Database user interface for the place I work on my college campus, and when I wanted to update my app to the new API, a lot of things broke, and I can't figure out how to make them work again.
What I am trying to do is get a value from a Google Sheets file, and simply put that value in a text box on the web app. Currently I cannot get that work work. In addition, I discovered something that was troublesome, and that is, the debugger seems to not be correct. I know, bold accusation. Let me try to show you.
Code.gs
function doGet(e) {
var html = HtmlService.createHtmlOutputFromFile('index')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
return html;
}
function searchBooks(searchItem, searchType){
var sI = searchItem;
Logger.log(sI);
var sT = searchType;
Logger.log(sT);
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var ss = sheets[0];
var itemDataRange = ss.getRangeByName("itemInformation");
var selectedItem = null; //the item that will be returned
//var selectedSearch = searchItem;
var titles = sheet.getRange("K2:K9507").getValues(); //get the titles of the items
var authors = sheet.getRange("J2:J9507").getValues(); //get the authors in the sheet
var barcodes = sheet.getRange("B2:B9507").getValues(); //get the barcodes in the sheet
var itemsArray = new Array();
if (sT == '')
{
return null;
}
else if (sT.value == 'Please select type...')
{
var test = "this works";
Logger.log(test);
return selectedItem;
}
else if(sT == 'Barcode')
{
var selectedBarcode = sI;
for(var i = 0; i < barcodes.length; i++) //search for the barcode
{
if(barcodes[i] == selectedBarcode)
{
selectedItem = titles[i];
break; //break immediately because barcodes are not duplicated
}
}
if(selectedItem != null)
{
return selectedItem;
}
else
{
selectedItem = "No book(s) found";
return selectedItem;
}
return selectedItem;
}
}
...
index.html
<script>
function bookSearch()
{
var searchItem = String(document.getElementById('searchItem').value.toLowerCase());
var searchType = String(document.getElementById('searchType').value.toLowerCase());
google.script.run.withSuccessHandler(bookFound).searchBooks(searchItem, searchType);
}
...
function bookFound(selectedItem)
{
document.getElementById("bookResultBox").innHTML = selectedItem;
alert(selectedItem);
}
</script>
When I test this code, and put a search value with the category "Barcodes" selected, I successfully get console logs of the data being brought into the function searchBooks, however the debug console says that the variables sI, sT, searchItems, and searchType are all undefined.
I've also been having trouble trying to figure out the proper API calls to use to search through the spreadsheet (when dealing with stuff like getRangeByName). I think there might be a slightly different way to do this since the big update. I may have had it working before I changed some of the code, although I started changing a lot of it when I was trying to figure out WHY nothing was displaying. When I saw at the "undefined" debug console logs, it scared me a bit. I can't tell if I'm messing up, or the API is messing up.
Any help is much appreciated in advance :)

There's probably an error in your code. It's probably coming from line:
var itemDataRange = ss.getRangeByName("itemInformation");
Your variable ss is not a spreadsheet class, it's a sheet class. You can't get a RangeByName of a sheet class. There is no getRangeByName() method of the Sheet class.
I'd change your code to this:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var itemDataRange = ss.getRangeByName("itemInformation");
If you need to get the first sheet:
var theFirstSheet = ss.getSheets()[0];

Related

Take selected text, send it over to Scryfall API, then take the link and put it in the selected text

I've been able to sort out the middle bit (the API seems to be called to just fine) along with the submenu displaying. Originally I thought that just the end part wasn't working but I'm now thinking that the selection part isn't either.
What am I doing wrong with the getSelection() and what do I need to do to insert a link into said selection? (to clarify, not to replace the text with a link, but to insert a link into the text)
//Open trigger to get menu
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Scry', 'serumVisions')
.addToUi();
}
//Installation trigger
function onInstall(e) {
onOpen(e);
}
//I'm not sure if I need to do this but in case; declare var elements first
var elements
// Get selected text (not working)
function getSelectedText() {
const selection = DocumentApp.getActiveDocument().getSelection();
if (selection) {
var elements = selection.getRangeElements();
Logger.log(elements);
} else {
var elements = "Lack of selection"
Logger.log("Lack of selection");
}
}
//Test run
// insert here
// Search Function
function searchFunction(nameTag) {
// API call + inserted Value
let URL = "https://api.scryfall.com/cards/named?exact=" + nameTag;
// Grabbing response
let response = UrlFetchApp.fetch(URL, {muteHttpExceptions: true});
let json = response.getContentText();
// Translation
let data = JSON.parse(json);
// Jackpot
let link = data.scryfall_uri;
// Output
Logger.log(link);
}
// Test run
searchFunction("Lightning Bolt");
//Let's hope this works how I think it works
function serumVisions() {
const hostText = getSelectedText();
const linkage = searchFunction(hostText);
// Unsure what class I'm supposed to use, this doesn't
const insertLink = DocumentApp.getActiveDocument().getSelection().newRichTextValue()
.setLinkUrl(linkage);
Logger.log(linkage);
}
For the first part, I tried the getSelection() and getCursor() examples from the Google documentation but they don't seem to work, they all just keep returning null.
For the inserting link bit, I read all those classes from the Spreadsheet section of the documentation, at the time I was unaware but now knowing, I haven't been able to find a version of the same task for Google Docs. Maybe it works but I'm writing it wrong as well, idk.
Modification points:
In your script, the functions of getSelectedText() and searchFunction(nameTag) return no values. I think that this might be the reason for your current issue of they all just keep returning null..
elements of var elements = selection.getRangeElements(); is not text data.
DocumentApp.getActiveDocument().getSelection() has no method of newRichTextValue().
In the case of searchFunction("Lightning Bolt");, when the script is run, this function is always run. Please be careful about this.
When these points are reflected in your script, how about the following modification?
Modified script:
Please remove searchFunction("Lightning Bolt");. And, in this case, var elements is not used. Please be careful about this.
From your script, I guessed that in your situation, you might have wanted to run serumVisions(). And also, I thought that you might have wanted to run the individual function. So, I modified your script as follows.
function getSelectedText() {
const selection = DocumentApp.getActiveDocument().getSelection();
var text = "";
if (selection) {
text = selection.getRangeElements()[0].getElement().asText().getText().trim();
Logger.log(text);
} else {
text = "Lack of selection"
Logger.log("Lack of selection");
}
return text;
}
function searchFunction(nameTag) {
let URL = "https://api.scryfall.com/cards/named?exact=" + encodeURIComponent(nameTag);
let response = UrlFetchApp.fetch(URL, { muteHttpExceptions: true });
let json = response.getContentText();
let data = JSON.parse(json);
let link = data.scryfall_uri;
Logger.log(link);
return link;
}
// Please run this function.
function serumVisions() {
const hostText = getSelectedText();
const linkage = searchFunction(hostText);
if (linkage) {
Logger.log(linkage);
DocumentApp.getActiveDocument().getSelection().getRangeElements()[0].getElement().asText().editAsText().setLinkUrl(linkage);
}
}
When you select the text of "Lightning Bolt" in the Google Document and run the function serumVisions(), the text of Lightning Bolt is retrieved, and the URL like https://scryfall.com/card/2x2/117/lightning-bolt?utm_source=api is retrieved. And, this link is set to the selected text of "Lightning Bolt".
Reference:
getSelection()

Grab URL from element on page for Bitly URL shortening

I'm working in shopify - attempting to do this client-side
I have a URL being generated (based on what items are in the cart presently) that adds items to the cart based on their ID#.
I'm building this little thing for our sales team, so they can start an order for a customer and send that arrangement to someone through a URL - right now in shopify if you do it their way it will take the customer to the checkout window and they can't edit that order - This way we're just sending an arrangement in the cart that they can adjust before they actually check out.
So right now, that url gets very very long depending on how many items are in the cart, and I'd like to use bit.ly to create a short url based on that generated url - I have it now so that it can encode the URL so it won't have any strange characters in it - but looking at the bitly api documentation most of the examples seem generic and other cases on stack overflow seemed to be specific to their problem --
Perhaps it can't be done? Thanks for taking the time to read this, if anyone has any suggestions at all - or if you think I just missed a big chunk of something obvious please feel free to tell me so. I can provide code for what I have so far if that makes it easier to understand what I'm trying to do!
screen shot of what that page looks like
---- ADDING CODE BELOW ----
// get the cart
if (typeof Shopify === 'undefined') var Shopify = {};
Shopify.cart = {{ cart | json }};
Shopify.idsInCart = [];
Shopify.quanInCart = [];
//where we gonna put the url
var cartURL = document.getElementById('cart_url');
// for every item in Shopify Cart - push to idsInCart and print the IDs to the cart url
for (var i=0; i<Shopify.cart.items.length; i++) {
Shopify.idsInCart.push(Shopify.cart.items[i].id);
cartURL.innerHTML += 'id[]=' + Shopify.idsInCart[i] + '&';
}
// get the div with cartURLform as an id
var longUrlNode = document.getElementById('cartURLform'),
// grab the .textContent from that div
textContent = longUrlNode.textContent;
//
var uri = longUrlNode.textContent;
var res = encodeURI(uri);
// Copy to clipboard example
document.querySelector("#qlink").onclick = function() {
// Select the content
document.querySelector("#qlink").select();
// Copy to the clipboard
document.execCommand('copy');
};
(function(long_url,callback){
bi = new URL("https://api-ssl.bitly.com/v3/shorten?");
var params = [
"login=__obviously__",
"domain=bit.ly",
"apiKey=__obviously__",
"longUrl="+ encodeURIComponent(long_url)
]
bi.search = "?"+params.join('&')
var xhr = new XMLHttpRequest();
xhr.onreadystatechange=function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var res = JSON.parse(xhr.responseText);
callback(res["data"]["url"]);
// document.getElementById("qlink").value = JSON.parse(xhr.responseText);
} else {
alert('There was a problem with the request.');
}
}
}
xhr.open("GET",bi.toString());
xhr.send(null)
})(res,function(a){
// prompt("hello", a);
document.getElementById("qlink").value = a;
});
--- Edited to add code

Export data from Google AppMaker Datasource automatically

Does anyone know how we can generate report from data in datasource in Google AppMaker automatically (e.g generate report at 12a.m.) instead of manually click export data in deployments every time user need the report.
I have seen something similar on Exporting data out of Google AppMaker but also no one tried to answer that.
Really appreciate if there is anyone who know how to solve this :)
This can be achieved by using Installable Triggers.
Say for example, you have a model with students data that has three fields; name(string), age(number) and grade(number). On the server script you can write something like this:
//define function to do the data export
function dataExport() {
//create sheet to populate data
var fileName = "Students List " + new Date(); //define file name
var newExport = SpreadsheetApp.create(fileName); // create new spreadsheet
var header = ["Name", "Age", "Grade"]; //define header
newExport.appendRow(header); // append header to spreadsheet
//get all students records
var ds = app.models.students.newQuery();
var allStudents = ds.run();
for(var i=0; i< allStudents.length; i++) {
//get each student data
var student = allStudents[i];
var studentName = student.name;
var studentAge = student.age;
var studentGrade = student.grade;
var newRow = [studentName, studentAge, studentGrade]; //save studen data in a row
newExport.appendRow(newRow); //append student data row to spreadsheet
}
console.log("Finished Exporting Student Data");
}
//invoke function to set up the auto export
function exportData(){
//check if there is an existing trigger for this process
var existingTrigger = PropertiesService.getScriptProperties().getProperty("autoExportTrigger");
//if the trigger already exists, inform user about it
if(existingTrigger) {
return "Auto export is already set";
} else { // if the trigger does not exists, continue to set the trigger to auto export data
//runs the script every day at 1am on the time zone specified
var newTrigger = ScriptApp.newTrigger('dataExport')
.timeBased()
.atHour(1)
.everyDays(1)
.inTimezone("America/Chicago")
.create();
var triggerId = newTrigger.getUniqueId();
if(triggerId) {
PropertiesService.getScriptProperties().setProperty("autoExportTrigger", triggerId);
return "Auto export has been set successfully!";
} else {
return "Failed to set auto export. Try again please";
}
}
}
Then, to delete/stop the auto export, in case you need to, you can write the following on the server script too:
function deleteTrigger() {
//get the current auto export trigger id
var triggerId = PropertiesService.getScriptProperties().getProperty("autoExportTrigger");
//get all triggers
var allTriggers = ScriptApp.getProjectTriggers();
//loop over all triggers.
for (var i = 0; i < allTriggers.length; i++) {
// If the current trigger is the correct one, delete it.
if (allTriggers[i].getUniqueId() === triggerId) {
ScriptApp.deleteTrigger(allTriggers[i]);
break;
//else delete all the triggers found
} else {
ScriptApp.deleteTrigger(allTriggers[i]);
}
}
PropertiesService.getScriptProperties().deleteProperty("autoExportTrigger");
return "Auto export has been cancelled";
}
You can check the demo app right here.
The reference to the script properties service is here.
The reference to the Time Zones list is here.
I hope this helps!
It seems that you are looking for daily database backups. App Maker Team recommends migrating apps to Cloud SQL if you haven't done this so far. Once you start using Cloud SQL as your data backend you can configure backups through Google Cloud Console:
https://cloud.google.com/sql/docs/mysql/backup-recovery/backups

Sharepoint 2010 JSOM getEnumerator 'The collection has not been initialized. It has not been requested...'

I'm no stranger to this error or the various solutions, but this one has me scratching my head. I'm using JavaScript object model to get all a list of all the files in a given folder. I get the error on the getEnumerator in the code below. I've stripped the code down to the bare minimum:
function getFilesInFolder() {
var folderServerRelativeUrl = folderPath + ID;
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle(documentLibraryName);
var query = SP.CamlQuery.createAllItemsQuery();
query.set_folderServerRelativeUrl(folderServerRelativeUrl);
//Update "web part" link
$("#doclink").attr('href',folderServerRelativeUrl);
files = list.getItems(query)
context.load(files, 'Include(Id)');
context.executeQueryAsync(Function.createDelegate(this, this.OnSuccess), Function.createDelegate(this, this.OnFailure));
}
function OnSuccess()
{
//ERROR Next Line:
var listItemEnumerator = this.files.getEnumerator();
var table = $("#attachments");
while (listItemEnumerator.moveNext())
{
console.log("Found a file");
}
}
The code is called as such in the beginning of the file:
$(document).ready(function(){
//Other code...
ExecuteorDelayUntilScriptLoaded(getFilesInFolder,"sp.js");
});
I've tried a ton of variations on this AND it used to work (not sure what changed either server or client-side).
I tried your code, since I couldnt see any obvious errors, and it works.
I hardcoded the folderServerRelativeUrl as it works in Swedish:
"/sites/intranet/dokument" is my root web and the "Documents" folder.
You can try in the broswer "sitecollection/_api/web/getFolderByServerRelativeUrl('/path/to/folder/')"
To see if the url u are using is a correct one.
You could also set a breakpoint in your onsuccess and look in the console: files.get_count() to see if you have any results.
Your load is fine, so dont worry about that!
function getFilesInFolder() {
var folderServerRelativeUrl = "/sites/intranet/dokument";
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle("Dokument");
var query = SP.CamlQuery.createAllItemsQuery();
query.set_folderServerRelativeUrl(folderServerRelativeUrl);
//Update "web part" link
// $("#doclink").attr('href',folderServerRelativeUrl);
files = list.getItems(query)
context.load(files, 'Include(Id)');
context.executeQueryAsync(Function.createDelegate(this, this.OnSuccess), Function.createDelegate(this, this.OnFailure));
}
function OnSuccess()
{
//ERROR Next Line:
var listItemEnumerator = this.files.getEnumerator();
var table = $("#attachments");
while (listItemEnumerator.moveNext())
{
console.log("Found a file");
}
}
This error basically means that resulting object (this.files variable) has not been requested from the server once the iteration is performed. This error would be reproduced by executing simultaneously several queries (basically invoking getFilesInFolder several times).
Given the fact that result object (this.files) is stored in global scope (window object) i would strongly suggest against this approach of getting list items, instead you could consider to store the result in local scope as demonstrated below:
function getFilesInFolder() {
var folderServerRelativeUrl = "/Documents/Archive";
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle(documentLibraryName);
var query = SP.CamlQuery.createAllItemsQuery();
query.set_folderServerRelativeUrl(folderServerRelativeUrl);
var items = list.getItems(query)
context.load(items, 'Include(Id)');
context.executeQueryAsync(
function(){
if(items.get_count() > 0)
{
console.log('Found a file(s)');
}
},
function(sender,args){
console.log(args.get_message());
});
}
With such approach you could easily avoid conflicts with overriding resulting object and therefore getting this exception.

How to set the positions of dynamicaly loaded elements using Javascript?

I am working on an application that loads in "Apps" from a file on the server. At the moment i have to load the apps in a specific order otherwise they will have the wrong positions in the array of positions.
Here is the code to help explain what i mean.
function getFacebook() {
var appname = "facebooka1thd";
$.get("apps/"+appname+"/"+appname+".html", function(data){
$('.AppList').append(data);
$.cookie(appname, 1, { expires : 365 });
checkpositions();
});
};
function checkpositions() {
if ($.cookie('PosApps')){
var Poscookie = $.cookie('PosApps');
var Pos = JSON.parse(Poscookie);
$("#User").css({top:Pos[0].top, left:Pos[0].left});
$("#facebooka1thd").css({top:Pos[1].top, left:Pos[1].left});
$("#youtubea2thd").css({top:Pos[2].top, left:Pos[2].left});
};
};
function getAppPositions() {
var apps = $(".App"),
positions = [];
$.each(apps, function (index, app) {
var positionInfo = $(app).position();
positions.push(positionInfo);
console.log(positionInfo);
});
var setPositions = JSON.stringify(positions);
$.cookie("PosApps", setPositions, { expires : 365 });
};
I would like the code to adapt to the number off apps present and the order in which they are added or removed.
basically i dont have a clue how to get around this -_- I think that most of my code would have to change in-order for this to be possible because at the moment the positions are saved in the order in which the apps are added but that wont help when the user removes one of the apps and also the positions are being set relative to the order of the apps being added.
Any help with this would be great!
I managed to work it out for my self...
function checkpositions() {
if ($.cookie('PosApps')){
var Poscookie = $.cookie('PosApps');
var Pos = JSON.parse(Poscookie);
for (var i = 0; i < Pos.length; i++) {
$("#"+Pos[i][0]).css({top:Pos[i][1].top, left:Pos[i][1].left});
};
};
};
function getAppPositions() {
var apps = $(".App"),
positions = [];
$.each(apps, function (index, app) {
var id = $(app).attr('id');
var positionInfo = [id,$(app).position()];
positions.push(positionInfo);
console.log(positionInfo);
});
var setPositions = JSON.stringify(positions);
$.cookie("PosApps", setPositions, { expires : 365 });
};
All i had to do was make a 2D array with the id of the element before its position and set the CSS using Pos[i][0] for the id and Posi.top/left for the positions. Hope this helps anyone who gets stuck with something like this. Also if you do get stuck with a problem i would recommend going to this site. Their tutorials are really good and are what helped me figure this problem out.

Categories