I created an array in a separate GS file using the code provided below. I tried calling it in my HTML file. My goal is to compare the contents the array to the parameter email. However, the value returned by google.script.run.withSuccessHandler() is undefined
//in GS
function mailGetter()
{
//open sheet
var sheet = SpreadsheetApp.openByUrl("https://sheet.url").getSheetByName("Email Sheet").activate();
//get size of given row range
var row_data_email = sheet.getRange("C2:C").getValues();
var emailArray = row_data_email.join().split(',').filter(Boolean);
Logger.log(emailArray);
return emailArray;
}
//in HTML
function checkEmail(email)
{
var reg1 = /^[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,4}$/;
var arraySize = google.script.run.withSuccessHandler(misc).sizeGetter();
console.log(arraySize);
var emailArray = new Array(arraySize);
emailArray = google.script.run.withSuccessHandler(misc).mailGetter();
console.log(emailArray);
if (reg1.test(email) == false)
{
emails.style.border = "1px solid red";
document.getElementById('submitBtn').disabled = true;
}
else if (reg1.test(email) == true)
{
emails.style.border = "1px solid green";
document.getElementById('submitBtn').disabled = false;
}
for (var row = 0; row < arraySize; row++)
{
if (emailArray[row][0] == email)
{
emails.style.border = "1px solid green";
document.getElementById('submitBtn').disabled = false;
break;
}
else if (emailArray[row][0] != email)
{
emails.style.border = "1px solid red";
document.getElementById('submitBtn').disabled = true;
}
}
}
function misc()
{
console.log("Pass");
}
Issue:
Using a asynchronous function's(google.script.run) return value, which will always be undefined.
Solution:
Use successHandler as mentioned in another answer or We can use promises with async/await.
Snippet:
/*Create a promise around old callback api*/
const p = func =>
new Promise(resolve=>
google.script.run.withSuccessHandler(resolve)[func]()
);
async function checkEmail(email) //modified
{
var arraySize = await p('sizeGetter');//Wait to resolve
console.log(arraySize);
//var emailArray = new Array(arraySize);
var emailArray = await p('mailGetter');//Wait to resolve
console.log(emailArray);
//....
}
Note:
It's better to reduce the number of calls to the server. If you can combine both Getters to a single server function, it'll be better.
The above is a snippet showing how to use async/await. But if you wait for each response from the server as shown above, your front end/UI will be slow. Wait only if absolutely necessary. Calls to server should be non-blocking/asynchronous.
References:
Promises
async
await
Issue is in these lines:
emailArray = google.script.run.withSuccessHandler(misc).mailGetter();
console.log(emailArray);
You're trying to execute mailGetter() and expecting it to return value which you're storing in emailArray but this method is asynchronous and does not return directly
Rather you'll get the value in callback which you have defined as SuccessHandler
Suggested solutions :
Calling Apps Script functions from a template : https://developers.google.com/apps-script/guides/html/templates#apps_script_code_in_scriptlets
Calling Apps Script APIs directly : https://developers.google.com/apps-script/guides/html/templates#calling_apps_script_apis_directly
Pushing variables to templates : https://developers.google.com/apps-script/guides/html/templates#pushing_variables_to_templates
Reference : https://developers.google.com/apps-script/guides/html/reference/run#myFunction(...)
I am new to react-native and calling a function inside a fucntion.
I have done as below so far :
Step 1 : Created a function _snapshotToArray to convert the firebase snapshot to Arrray.
_snapshotToArray(snapshot) {
var returnArr = [];
snapshot.forEach(function(childSnapshot) {
var item = childSnapshot.val();
item.key = childSnapshot.key;
returnArr.push(item);
});
return returnArr;
}
Step 2 : Created another function as below and calling _snapshotToArray inside it.
_readUserDataFromFirebaseConsole() {//once and on
firebase.database().ref('Users/').on('value', function (snapshot) {
console.log(this._snapshotToArray(snapshot));
Toast.show(this._snapshotToArray(snapshot),Toast.LONG);
});
}
Talking about this call :
console.log(this._snapshotToArray(snapshot));
When I press CTRL+CLick, it not letting me to navigate to body of the fuction _snapshotToArray.
In Device am getting below error :
_snapshotToArray is not defined
What might be the issue ?
I'm not at my PC right now, so I cannot test it, but from looking at your code, you need to use a different function notation to allow the varibale access of/from parent methods and parent class.
_snapshotToArray = snapshot => {
var returnArr = [];
snapshot.forEach(function(childSnapshot) {
var item = childSnapshot.val();
item.key = childSnapshot.key;
returnArr.push(item);
});
return returnArr;
}
and
_readUserDataFromFirebaseConsole = () => {
firebase.database().ref('Users/').on('value', snapshot => {
console.log(this._snapshotToArray(snapshot));
Toast.show(this._snapshotToArray(snapshot),Toast.LONG);
});
}
Im confused on how to make a RESTFUL API call with 'PUT'. I'm basically trying to save an edited profile but I'm confused on how to make the API call for it. This is what I have so far ...
var edit = angular.module('edit', ['ui.bootstrap','ngResource'])
.factory('editable', function($resource) {
return {
// get JSON helper function
getJSON : function(apicall) {
if(sessionStorage["EditUserId"] == undefined) {
// get the user id
var userid = sessionStorage["cerestiuserid"];
}
else {
var userid = sessionStorage["EditUserId"];
}
// json we get from server
var apicall = sessionStorage["cerestihome"];
// new api
return $resource(apicall + "/api/profiles/", {Userid:userid}, {'PUT': {method: 'Put'}});
}
};
});
This is the controller ...
//editable object
var object = editable.getJSON();
var edit = new object();
edit.UserName = "Hello World";
edit.$save();
Use restagular to invoke put service.
For example
admin.factory('AdminService', ['Restangular', 'AppConstants', 'AdminRestangular', 'WorkFlowRestangular', 'localStorageService',
function(Restangular, AppConstants, AdminRestangular, WorkFlowRestangular, localStorageService) {
var service = {}
service.updateAgency = function(data) {
return AdminRestangular.all(AppConstants.serviceUrls.agency).doPUT(data);
};
return service
}]);
I am currently exploring possible methods to handle application-wide exceptions in AngularJS.
One of the things we really wanted to avoid was wrapping multiple parts of the application in nested try/catch blocks, but handle things cleanly - i.e throw an exception in response to a promise.
Has anyone covered this issue before and have any recommendations?
Any suggestions on how to pick up exceptions in services as well as controllers/directives. (See below - broadcast works ok, but only if you can attach a listener to a scope).
Progress so far
A few short design goals:
Allow exceptions from one part of the application to be handled elsewhere - or possibly multiple places (i.e. 'display error notification to user', 'disable widget').
Provide central management of common error conditions - i.e. log to server, display notification to user, redirect to login.
Allow exceptions to be thrown from controllers, directives, services etc.
Eventually allow localized messages.
The current leaning of my team is to write a service to handle exceptions, which would expose a range of simple calls:
exceptionService.warn('exception_token');
exceptionService.crit('another_exception_token');
This service would then format an 'exception' object and broadcast this from the rootscope. This would allow a default handler to watch for any broadcasts and apply default actions, as well as allow custom listeners to be set in others scopes, which could handle more specific conditions - i.e. disable a part of the UI.
var exception = {
token: 'exception_token',
severity': 'crit'
};
// broadcast exception
$rootScope.$broadcast(
'application_exception',
exception
);
I was thinking about the same recently, and it occurred to me that when it comes to a good error handling in javascript, it is irrelevant which framework you are using, Angular on something else. I wrote one such error handler recently for an AngularJS project, but I did it in a way it can be used in any framework.
Here's the complete code. You can either use it directly, or modify to your needs...
/*
Factory errorFact is to simplify error handling and reporting in other objects.
It supports detailed error output as a text string and into the browser's console.
Usage example:
A function that supports return of an error object would have the following declaration
as its very first line:
var e = errorFact.create("objectName.funcName", arguments);
- in this declaration we specify the full object + method name as the first string parameter,
- and as the second parameter we pass javascript's reserved variable called arguments, which
provides reference to all of the function's parameters for logging.
When an error occurs, the function would return:
return e.error("Error description text");
- this line will create and return a complete error context.
When a function that supports return of an error object makes a call into another
function that also supports the error context, then it can return the nested error
result by passing the embedded error to the current error object instead of the error
text.
Example:
var e = errorFact.create("objectName.funcName", arguments);
var data = callAnotherFunc(...); // calling a function that support an error object;
if(data.isError){ // If an error was triggered;
return e.error(data); // return that error from the current context;
}
The top-level code that calls an error-returning function would do verification
and if an error occurred, log all its details into console (typically).
Example:
var data = getData(...);
if(data.isError){
data.log(); // Output all the error details into the browser's console;
}
*/
"use strict";
app.factory("errorFact", function(){
return {
// creates a new error context;
create: function(method, args){
var result = {
// initiates and returns the error context;
error: function(msg){
this.info.isError = true;
if(msg.isError){
this.info.details.caller = msg;
}else{
this.info.details.msg = msg;
}
return this.info;
},
info:
{
isError: false,
details: {},
log: function(){
if(this.isError){
console.error(this.format());
}
},
// formats complete error details into a text string;
format: function(){
if(this.details.caller){
var txt = this.details.caller.format();
txt += "\nCALLER: " + this.details.method + "(" + this.formatArguments() + ")";
return txt;
}
if(this.details.method){
return "Error calling " + this.details.method + "(" + this.formatArguments() + "): " + this.details.msg;
}else{
return this.details.msg;
}
return "";
},
// formats function argument details into a text string;
formatArguments: function(){
if(!this.details.args){
return "";
}
var params = "";
for(var i = 0;i < this.details.args.length;i ++){
if(params.length > 0){
params += ",";
}
var p = this.details.args[i];
if(p === undefined){
params += "undefined";
}else{
if(p === null){
params += "null";
}else{
if(typeof(p) == "object"){
params += "Object";
}else{
params += p;
}
}
}
}
return params;
}
}
};
if(method){
result.info.details.method = method;
}
if(args){
result.info.details.args = args;
}
return result;
}
}
});
Below is a factory that shows how it is used:
"use strict";
app.factory('moduleFact', ['errorFact', function(errorFact){
return {
// Locates existing module and expands its key Id references
// into corresponding object references:
// - If 'hintGroupId' is present, property 'hints' is added from
// the corresponding hint group.
// - If 'repModules' is present, properties 'question' and 'refs'
// are added.
// On success, return the expanded module object.
// On failure, returns an error object.
//
// NOTE: Currently supports only the first value in repModules.
expandModule: function(moduleData, moduleId){
var e = errorFact.create("moduleFact.expandModule", arguments);
if(!moduleData || !moduleData.modules || !moduleId){
return e.error("Invalid parameters passed");
}
var mod = this.findModule(moduleData, moduleId);
if(mod.isError){
return e.error(mod);
}
var src = mod;
if(mod.repModules){
var repId = mod.repModules[0];
if(!repId){
return e.error("Invalid repModules encountered");
}
///////////////////////////////////////
// temporary check to throw a warning:
if(mod.repModules.length > 1){
console.warn("Multiple values in property repModules: " + JSON.stringify(mod.repModules) +
", which is not supported yet (only the first value is used)");
}
///////////////////////////////////////
src = this.findModule(moduleData, repId);
if(src.isError){
return e.error(src);
}
}
if(src.question){
mod.question = src.question;
}else{
return e.error("Question not specified");
}
if(src.refs){
mod.refs = src.refs;
}
if(src.hintGroupId){
var hg = this.findHintGroup(moduleData, src.hintGroupId);
if(hg.isError){
return e.error(hg);
}
mod.hints = hg.hints;
}
return mod; // needed extra: expand attribute repModules
},
// Expands all the modules and returns the data;
expandAllModules: function(moduleData){
var e = errorFact.create("moduleFact.expandAllModules", arguments);
if(!moduleData || !moduleData.modules){
return e.error("Invalid parameters passed");
}
for(var i = 0;i < moduleData.modules.length;i ++){
var result = this.expandModule(moduleData, moduleData.modules[i].id);
if(result.isError){
return e.error(result);
}
}
return moduleData;
},
// Locates and returns module by its Id;
findModule: function(moduleData, moduleId){
var e = errorFact.create("moduleFact.findModule", arguments);
if(!moduleData || !moduleData.modules || !moduleId){
return e.error("Invalid parameters passed");
}
for(var i = 0;i < moduleData.modules.length;i ++){
if(moduleData.modules[i].id == moduleId){
return moduleData.modules[i];
}
}
return e.error("Module with Id = " + moduleId + " not found");
},
// Locates and returns Hint Group by its Id;
findHintGroup: function(moduleData, hintGroupId){
var e = errorFact.create("moduleFact.findHintGroup", arguments);
if(!moduleData || !moduleData.hintGroups || !hintGroupId){
return e.error("Invalid parameters passed");
}
for(var i = 0;i < moduleData.hintGroups.length;i ++){
if(moduleData.hintGroups[i].id == hintGroupId){
return moduleData.hintGroups[i];
}
}
return e.error("Hint Group with Id = " + hintGroupId + " not found");
}
}
}]);
So, when you have such factory in place, your high-level code, such as in a controller would just log any issues as shown in the example below:
"use strict";
app.controller('standardsCtrl', ['$scope', 'moduleFact', function($scope, moduleFact){
var data = ...//getting data;
var mod = moduleFact.expandAllModules(data);
if(mod.isError){
mod.log(); // log all error details into the console;
}else{
// use the data
}
});
}]);
You can override the $exceptionHandler in order to pass the exceptions to your own central service for exceptions, but the $exceptionHandler seems to only receive the exceptions thrown from your controllers, directives, etc... but not for the exceptions originated from ajax calls. For those exceptions you can implement an interceptor like the one described in this page:
EDITED: Link is dead permanently.
Archive.org link
whats your opinion to create a centralized error handling function for your app
so whenever an error happened with your frontend tear (angular, API calls,...) it executed, so no need to write your error handling every time
so here is my code
(function () {
'use strict';
angular
.module('app')
.factory('$exceptionHandler', ExceptionHandler);
ExceptionHandler.$inject = ['$injector']; //for minification
function ExceptionHandler($injector) {
var $log, sweetAlert, $translate;
return function exceptionHandler(exception, cause) {
// Add DI here to prevent circular dependency
$log = $log || $injector.get('$log');
sweetAlert = sweetAlert || $injector.get('sweetAlert'); //19degrees.ngSweetAlert2
$translate = $translate || $injector.get('$translate');
// $loggerService = $loggerService || $injector.get('$loggerService');
var title, message;
title = $translate.instant('General error title');
message = $translate.instant('General error message', { exceptionMessage: exception.message });
sweetAlert.error(title, message);
$log.error(exception, cause);
// loggerService.logErrorsToBackend(exception, cause);
};
}
})();
I'm not sure if this approach considered to be a best practice but hope it helps you.
function fileSelected() {
// get selected file element
var files = document.getElementById('files[]').files;
for (var i = 0; i < files.length; i++) //for multiple files
{
(function (file) {
var fileObj = {
Size: bytesToSize(file.size),
Type: file.type,
Name: file.name,
Data: null
};
var reader = new window.FileReader();
reader.onload = function (e) {
fileObj.Data = e.target.result;
};
// read selected file as DataURL
reader.readAsDataURL(file);
//Create Item
CreateFileUploadItem(fileObj);
})(files[i]);
}
}
function CreateFileUploadItem (item) {
console.log(item);
$('<li>', {
"class": item.Type,
"data-file": item.Data,
"html": item.Name + ' ' + item.Size
}).appendTo($('#filesForUpload'));
}
So when console.log(item) gets run in the CreateFileUploadItem function it shows the item.Data. YET it won't add it to the data-file of the LI. Why is that?
The call to readAsDataURL is asynchronous. Thus, the function call is likely returning prior to the onload function being called. So, the value of fileObj.Data is still null when you are attempting to use it in CreateFileUploadItem.
To fix it, you should move the call to CreateFileUploadItem into your onload function. As for the console logging the proper value, you can't rely on that being synchronous either. I think using a breakpoint during debugging at that line instead will likely show the true null value.