GAS Click Event Handler causing "Unexpected Error" in UI Web App - javascript

I've written a Google apps script which first creates and displays a line chart within the UI app and then re-draws the graph based on selected list box values using a Click-button event handler to trigger when the graph "refreshes".
The issue I'm trying to resolve is that when published as a web application when triggering the click event I receive:
Error Encountered: An unexpected error occurred
The code runs perfectly from a spreadsheet (except for some pesky permissions issues where other users are asked to download the panel object; I'll get to that later), and I'm really stuck as to why it won't translate.
Here's the code for what it's worth:
function doGet(){
var app = UiApp.createApplication().setWidth(800).setHeight(650).setTitle('FLEET Chart');
var e = 'undefined';
lb(app);
return click(e, app);
}
function lb(app){
var lb = app.createListBox(true).setId('myId').setName('myLbName');// add items to ListBox
var lb2 = app.createListBox(true).setId('myId2').setName('myLbName2');
var lb3 = app.createListBox(true).setId('myId3').setName('myLbName3');
var bttn = app.createButton('Refresh').setId('myBttn');
lb3.addItem('2011').addItem('2012');
lb2.addItem('January')
.addItem('February')
.addItem('March')
.addItem('April')
.addItem('May')
.addItem('June')
.addItem('July')
.addItem('August')
.addItem('September')
.addItem('October')
.addItem('November')
.addItem('December');
lb.addItem('Drug1')
.addItem('Drug2')
.addItem('Drug3')
.addItem('Drug4');
var mypanel = app.createHorizontalPanel().setVisible(true);
mypanel.add(lb).add(lb2).add(lb3).add(bttn);
app.add(mypanel);
var handler = app.createServerChangeHandler('click').addCallbackElement(mypanel);
handler.addCallbackElement(bttn);
bttn.addClickHandler(handler);
}
function click(e, app) {
var valuelb = new Array('Drug1','Drug2','Drug3','Drug4');
var valuelb2 = new Array ('January','February','March','April','May','June','July','August','September','October','November','December');
var valuelb3 = new Array('2011','2012');
var SS = SpreadsheetApp.openById('0Am8DRqAEaxH7dF9NWFJLSTdWZGRxeEFfMkFjNlFCT2c');
var sheeteff = SS.getSheetByName('Efficiency');
var data = sheeteff.getDataRange().getValues();
if (String(e) != 'undefined') {
app.setWidth(800).setHeight(650).setTitle('FLEET Chart');
var valuelb = String(e.parameter.myLbName);
if (valuelb.indexOf(',') != -1) {
var valuelb = e.parameter.myLbName.split(',');
}
var valuelb2 = String(e.parameter.myLbName2);
if (valuelb2.indexOf(',') != -1) {
var valuelb2 = e.parameter.myLbName2.split(',');
}
var valuelb3 = String(e.parameter.myLbName3);
if (valuelb3.indexOf(',') != -1) {
var valuelb3 = e.parameter.myLbName3.split(',');
}
SS.getSheetByName('Sheet2').getRange(1,1).setValue(String(valuelb2) + ' ' + valuelb.indexOf(','));//for unformation purposes
lb(app);
}else{
SS.getSheetByName('Sheet2').getRange(1,1).setValue(String(e));//for information purposes
}
var usedata = ArrayLib.filterByText(ArrayLib.filterByText(ArrayLib.filterByText(data, 3, valuelb), 1, valuelb2), 0, valuelb3);
//Build data table
var dataTable = Charts.newDataTable();
//Add Column types
dataTable.addColumn(Charts.ColumnType.DATE, data[0][2]);
dataTable.addColumn(Charts.ColumnType.NUMBER, data[0][9]);
dataTable.addColumn(Charts.ColumnType.NUMBER, data[0][8]);
//Add rows
for(var j=0; j<usedata.length; j++){
dataTable.setValue(j, 0, usedata[j][2]);
dataTable.setValue(j, 1, usedata[j][9]);
dataTable.setValue(j, 2, usedata[j][8]);
}
//Create and build chart
var chart = Charts.newLineChart()
.setDataTable(dataTable)
.setTitle('Efficiency over Time \nConfiguration: [' + valuelb + ']\n' + 'Month: [' + valuelb2 + ']\n' + 'Year: [' + valuelb3 + ']')
.setXAxisTitle('Time')
.setYAxisTitle('Percentage')
.setDimensions(750, 600)
.setPointStyle(Charts.PointStyle.MEDIUM)
.build();
app.add(chart);
return app;
}

I've not gone through your entire code but definitely see this wrong - you have set doGet as the function to be called on a handler. No problems with that except you try to call UiApp.createApplication() for the second time which isn't allowed.
I suggest you change the handler to call another function (lb, maybe) instead of doGet.

I also have "Unexpected Errors" in my code. To fix them, I wrote a simple library which helps to find problems in server handlers. Here is an example demonstrating how to use the library. The example source code is here. To add the library to a script is necessary to use the Mr6TKHuVEJGjYr-NnLUNbEkFO7CoOZA03 project key in the Resources->Manage Libraries dialog. The library uses the ScriptProperties Service to store own internal variables.

Related

Spreadsheet tracker

Im having trouble with an script that posts the outcome twice, I have reviewed the script but I cant find the issue.
The script gets the "Timestamp", "Cell address", "Column label" and "Value entered" and post it on the sheet named "Tracker" but it gets posted twice
function onEdit() {
var sheetsToWatch = ['Responses'];
// name of the sheet where the changelog is stored
var changelogSheetName = "Tracker";
var timestamp = new Date();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = sheet.getActiveCell();
var sheetName = sheet.getName();
// if it is the changelog sheet that is being edited, do not record the change
if (sheetName == changelogSheetName) return;
// if the sheet name does not appear in sheetsToWatch, do not record the change
var matchFound = false;
for (var i = 0; i < sheetsToWatch.length; i++) {
if (sheetName.match(sheetsToWatch[i])) matchFound = true;
}
if (!matchFound) return;
var columnLabel = sheet.getRange(/* row 1 */ 1, cell.getColumn()).getValue();
var rowLabel = sheet.getRange(cell.getRow(), /* column A */ 1).getValue();
var changelogSheet = ss.getSheetByName(changelogSheetName);
if (!changelogSheet) {
// no changelog sheet found, create it as the last sheet in the spreadsheet
changelogSheet = ss.insertSheet(changelogSheetName, ss.getNumSheets());
// Utilities.sleep(2000); // give time for the new sheet to render before going back
// ss.setActiveSheet(sheet);
changelogSheet.appendRow(["Timestamp", "Cell address", "Column label", "Value entered"]);
changelogSheet.setFrozenRows(1);
}
changelogSheet.appendRow([timestamp, cell.getA1Notation(), columnLabel, cell.getValue()]);
}
Google's method documentation makes it seem like you can programmatically check for the existence of some other user's trigger with a function like this:
function triggerLogger() {
// Read installed triggers for the project.
var triggers = ScriptApp.getProjectTriggers();
var installedReport = {};
triggers.forEach(function (t) { installedReport[t.getUniqueId()] = {
event: t.getEventType(),
calledFunction: t.getHandlerFunction(),
source: t.getTriggerSource(),
source_id: t.getTriggerSourceId() || "Time-based triggers have no source id."
}});
// Read "simple" triggers for the project by checking for globals that start with "on".
var simpleReport = {};
for (var thing in this)
if (thing.indexOf("on") === 0 && thing.length > 2)
simpleReport[String(thing)] = {def: this[thing]};
var possibleSimple = Object.keys(simpleReport).length,
message = "Trigger report: " + triggers.length + " installed";
if (possibleSimple) message += ", " + possibleSimple + " possible simple triggers";
message += ".";
// Log to Stackdriver (so the report can be viewed sensibly).
console.log({
message: message,
installed: Object.keys(installedReport).length ?
installedReport : "No detected installed triggers.",
simple: possibleSimple ?
simpleReport : "No simple triggers used",
reportRunAs: Session.getActiveUser().getEmail()
});
}
But the getProjectTriggers() method, despite claiming to get all of the current project's installed triggers, will only obtain your installed triggers for the document, even if you are the owner of the document.
Note that this behavior is accepted as a bug (meaning someone, someday will fix it). If you would like to feel that you have done your part to accelerate that timeline, please star that issue:

Cannot read property 'enumNodeFragments' of undefined

I'm trying to change the color of elements in 3D Viewer using the Autodesk-forge platform, and for this I'm using this API https://forge.autodesk.com/cloud_and_mobile/2015/12/change-color-of-elements-with-view-and-data-api.html by Daniel Du.
But the problem is when running I got this
The error Pict
And this the function :
Autodesk.Viewing.Viewer3D.prototype.setColorMaterial = function(objectIds, color) {
var material = addMaterial(color);
for (var i=0; i<objectIds.length; i++) {
var dbid = objectIds[i];
//from dbid to node, to fragid
viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, function () {
var it = viewer.model.getData().instanceTree;
console.log(it);
it.enumNodeFragments(dbid, function (fragId) {
var renderProxy = viewer.impl.getRenderProxy(viewer.model, fragId);
console.log("r prox : " + renderProxy);
renderProxy.meshProxy = new THREE.Mesh(renderProxy.geometry, renderProxy.material);
renderProxy.meshProxy.matrix.copy(renderProxy.matrixWorld);
renderProxy.meshProxy.matrixWorldNeedsUpdate = true;
renderProxy.meshProxy.matrixAutoUpdate = false;
renderProxy.meshProxy.frustumCulled = false;
viewer.impl.addOverlay(overlayName, renderProxy.meshProxy);
viewer.impl.invalidate(true);
}, false);
});
}
}
Hopefully, anyone has the solution to this problem...
Most likely you are running this code before the instance tree has been loaded, which provokes the error Cannot read property 'enumNodeFragments' of undefined on it variable. You would need to wait for the Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT before running that code.
Take also a look at previous question about modifying materials in the viewer.

Javascript sharedworker inside for loops

I'm trying to create 7 Javascript shared workers inside a for loop.
My IDE (Pycharm) is giving me a warning for these two variables container-frame and worker:
"Mutable variable is accessible from closure"
Each of these shared worker is communicating with network_worker.js
Below is my JS code:
socket = io.connect('http://' + document.domain + ':4999/layout');
// Set up CSS
for (var i=0; i<player_dict.length; i++) {
var _id = player_dict[i]['_id'];
var container_frame = document.getElementById("container-frame-" + _id);
container_frame.style.display = "none";
setup_communication();
console.log(_id)
var client_id = _id;
var alarm_flag= "";
var alarm_metric="";
if (client_id != null && client_id == 0 && parseInt(location.port) == 4999) { // player_0_hack (for now). Remove and have work properly.
label = "";
} else if (client_id != null){
label = "Service " + (parseInt(location.port) - 5000 + 1);
} else {
label = "Invalid player id";
}
var worker = new SharedWorker('../static/js/network_worker.js', client_id);
console.log(worker);
worker.port.addEventListener('message', worker_callback, false);
window.addEventListener("beforeunload", function () {
worker.port.postMessage('label_close');
});
worker.port.start();
function worker_callback(e) {
console.log(e.data)
if(e.data.type == "update_label") {
console.log(container_frame)
container_frame.style.animationDuration = Math.random() + "s";
}
}
worker.port.postMessage({type: "label_connection", payload: {domain: document.domain, port: location.port, client_id: client_id, label: label}, alarm_flag: alarm_flag, alarm_rate: 1, alarm_metric: alarm_metric});
}
Ultimately what I'm trying to do is animating the container-frame for 7 HTML elements. I'm iterating over these elements using the for loop. These animations happen when an event is triggered from a JS script (update label).
Currently, I have 7 HTML elements. The last element only is having the animation working properly.
My doubt is that when I have put the worker_callback function inside the for loop, the JS compiler became confused about the scope of container_frame but I'm not sure.
Any suggestions please ?
The problem was the two variables container_frame and worker where defined as var not const. Thanks to #Edmund Lee

The collection has not been initialized - Sharepoint Javascript

I'm getting the following error when attempting to get an enumerator for a collection of lists: "Uncaught Error: The collection has not been initialized. It has not been requested or the request has not been executed. It may need to be explicitly requested."
It happens on the line var listEnumerator = lists.getEnumerator(); it seems to me that there is an issue in my attempt to load lists into the client object with context.load(lists);
Here's the portion of my code that's causing the problem. I've marked the place just before the error is thrown.
//____________________________Required function for accessing the host site's info.___________________________________
function getQueryStringParameter(param) {
var params = document.URL.split("?")[1].split("&");
for (var i = 0; i < params.length; i = i + 1) {
var singleParam = params[i].split("=");
if (singleParam[0] == param) {
return singleParam[1];
}
}
}
//____________________________Begin checking for list_________________________
function checkForList(listToFind, typeOfListToCreateIfTheListIsMissing)
{
var hostUrl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
var hostcontext = new SP.AppContextSite(context, hostUrl);
var hostweb = hostcontext.get_web();
var lists = hostweb.get_lists();
context.load(lists);
context.executeQueryAsync(checkIfListExistsUsingEnumerator(listToFind, lists, hostweb, typeOfListToCreateIfTheListIsMissing), onQueryFailed);
}
//Failed to get lists for some reason
function onQueryFailed(sender, args) {
alert('We failed to retrieve lists. \n' + args.get_message() + '\n' + args.get_stackTrace());
}
//____________________________Does list exist?____________________________
function checkIfListExistsUsingEnumerator(listToFind, lists, hostweb, typeOfList)
{
var listExists = false;
//!!!!!!!!!!!!!!! ERROR HERE !!!!!!!!!!!!!!!!
var listEnumerator = lists.getEnumerator();
var title;
while (listEnumerator.moveNext())
{
title = listEnumerator.get_current().get_title();
if (title == listToFind)
{
listExists = true;
}
}
if (!listExists)
{
alert("It appears that a required list does not already exist. \nClick ok, and we'll automatically create one for you.");
//Create a new list
createList(listToFind, hostweb, typeOfList);
}
else if (listExists)
{
//Do nothing.
}
}
//____________________________If it doesn't, create one on the local site____________________________
function createList(nameOfNewList, hostweb, typeOfList) {
var listCreationInfo = new SP.ListCreationInformation();
listCreationInfo.set_title(nameOfNewList);
if (typeOfList === "events")
{
listCreationInfo.set_templateType(SP.ListTemplateType.events);
}
else if (typeOfList === "contacts")
{
listCreationInfo.set_templateType(SP.ListTemplateType.contacts);
}
var lists = hostweb.get_lists();
var newList = lists.add(listCreationInfo);
context.load(newList);
context.executeQueryAsync(onListCreationSuccess, onListCreationFail);
}
function onListCreationSuccess() {
alert('List created successfully!');
}
function onListCreationFail(sender, args) {
alert('Failed to create the list. ' + args.get_message());
}
I've looked at this question sharepoint javascript collection not initialized error which seems to be fairly similar to mine, but I'm having trouble implementing the solution provided there, making me think my error may be have a different cause.
I've also tried querying for the lists inside of the function that is throwing the error, but that doesn't seem to solve anything.
For a little background, these functions are attempting to read all lists from the app's host site, check to see if a specified list exists, and create a list if no matching list exists. If there's a better way of doing that than what I'm attempting, I'd be open to that too.
Any pointers?
Some things I've tried that don't seem to work:
Changing the Asynchronous query
context.executeQueryAsync(checkIfListExists(listToFind, hostweb, typeOfListToCreateIfTheListIsMissing), onQueryFailed);
to a Synchronous one.
context.executeQuery(checkIfListExists(listToFind, hostweb, typeOfListToCreateIfTheListIsMissing), onQueryFailed);
I've figured out an alternate, and shorter way to method of achieving the same goal I was trying to achieve before.
Instead of checking to see if a list does not already exist, I just try to create a list, and the Query fails to create a list if one is already there. (That's good because I don't want to overwrite the list if it is already there.)
I'm not totally sure if there are any undesired side effects of what I'm doing here, but in my tests it produced the desired behavior.
//____________________________Required function for accessing the host site's info.___________________________________
function getQueryStringParameter(param) {
var params = document.URL.split("?")[1].split("&");
for (var i = 0; i < params.length; i = i + 1) {
var singleParam = params[i].split("=");
if (singleParam[0] == param) {
return singleParam[1];
}
}
}
//____________________________Create a list if one does not already exist_________________________
function createList(listToCreate, typeOfList)
{
// Create an announcement SharePoint list with the name that the user specifies.
var hostUrl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
var hostContext = new SP.AppContextSite(currentContext, hostUrl);
var hostweb = hostContext.get_web();
var listCreationInfo = new SP.ListCreationInformation();
listCreationInfo.set_title(listToCreate);
if (typeOfList === "events")
{
listCreationInfo.set_templateType(SP.ListTemplateType.events);
}
else if (typeOfList === "contacts")
{
listCreationInfo.set_templateType(SP.ListTemplateType.contacts);
}
var lists = hostweb.get_lists();
var newList = lists.add(listCreationInfo);
currentContext.load(newList);
currentContext.executeQueryAsync(onListCreationSuccess, onListCreationFail);
}
function onListCreationSuccess() {
alert("We've created a list since one doesn't exist yet." );
}
function onListCreationFail(sender, args) {
alert("We didn't create the list. Here's why: " + args.get_message());
}

how do you add a custom callback javascript param to a bing api callback?

The bing V2 javascript api requires a callback to work. Using jQuery to add the script block dynamically (ignoring pollution of global namespace):
function translate(text) {
var txt = "text=" + text;
var lang = "&to=fr";
var appId = "&appid=apikey"; // Add your AppId here
var func = "&oncomplete=window.translated";
$("<script><\/script>")
.attr("src", "http://api.microsofttranslator.com/V2/ajax.svc/Translate?" + txt + lang + appId + func)
.appendTo("HEAD");
}
and then using a click event on multiple elements to trigger the translation:
$(document).ready(function () {
$('a').click(function () {
var tr = $(this).parent().parent();
var txtin = tr.find('.in').text();
var out = tr.find('.out'); // would like translation inserted here
translate(txtin);
return false;
});
});
and finally the callback required by the api:
function translated(text) {
$("#translation").text(text);
}
I want to specify different elements to received the translated text, depending on what element was clicked to kick the translation of - but using the above approach I can't pass any extra params to bing, to then be returned in the callback.
How should I rewrite this to allow a click on el in row1 to put the translation in row1 and a click on an el in row2 to put the translation in row2? i.e. using the element assigned to 'out' in my click event.
The callback method does not support a state object, so you need to keep track of your objects in some global place. I've implemented a queue model to help you make it
Add the queue definition in the global variables are
var queue = new Array();
Add your 'out' object to it just before calling the service
$('a').click(function () {
var tr = $(this).parent().parent();
var txtin = tr.find('.in').text();
var out = tr.find('.out'); // would like translation inserted here
//Here it goes
queue.push(out);
////////////////
translate(txtin);
return false;
});
Append the index of your object to the text and it will be returned back to you as the service does not translate numbers. You can skip adding the index if you are not making more than one translation at a time, this is only to grant that you get the correct object in case of having some service calls slower than others.
function translate(text) {
//Here it goes
var txt = "text=" + text + " ___" + (queue.length - 1);
////////////////
var lang = "&to=fr";
//...no more changes here
}
Finally extract your object in the callback method and remove the appended index and the splitter from the translated text.
function translated(text) {
if (queue.length > 0) {
var splts = text.split(' ___')
var indx = splts[splts.length - 1];
var out = queue[indx];
//remove the out object from the queue
queue.slice(indx, indx + 1);
//remove the index number from the end of the word
text = text.substr(0, text.lastIndexOf(indx) - 4);
out.text(text);
}
}

Categories

Resources