Where is spreadsheet with data - google-app-maker

Is it possible to access the Google drive table as which contains the data for an app maker model? I mean - is it possible to open it as a Spreadsheet?

Yes. You can use a spreadsheet as a datasource. I used a simple spreadsheet just to collect 3 items. I was playing with the Email Sender tutorial and I wanted to collect a list of the emails I'd already sent. I created the fields and went into the datasources tab and added this code to the queryRecords() function.
var ss=SpreadsheetApp.openById('id');
var sh=ss.getSheetByName('RecentEmails');
var rg=sh.getRange(2,1,sh.getLastRow(),sh.getLastColumn());
var vA=rg.getValues();
var reRecords=[];
for(var i=0;i<vA.length;i++){
var reRecord=app.models.RecentEmails.newRecord();
reRecord.Recipient=vA[i][0];
reRecord.Date=vA[i][1].toString();
reRecord.Message=vA[i][2];
reRecords.push(reRecord);
}
return reRecords;
The above function loads the datasource.
Then I connected a table upto the datasource and the data will update whenever the page is loaded.
I loaded the data into the table with a function like this:
function archiveSentEmails(to,when,what)
{
var ss=SpreadsheetApp.openById('id');
var sh=ss.getSheetByName('RecentEmails');
sh.appendRow([to,when,what]);
}
It gets placed inside the serverside script where the MailApp.sendMail function is located.
When you push the Send EMail button it calls this clientside function which calls the serverside function via google.script.run.
function sendMessage(to, subject, msg){
var status = app.pages.Email.descendants.EmailStatus;
google.script.run
.withFailureHandler(function(error) {
// An error occurred, so display an error message.
status.text = error.message;
})
.withSuccessHandler(function(result) {
// Report that the email was sent.
status.text = 'Email sent...';
clearEmailForm();
loadDebugElements();
app.datasources.RecentEmails.load();//this lines refreshes the widgets attached to the datasource
})
.sendEmailMessage(to, subject, msg);
}
and I placed the command app.datasources.RecentEmails.load in the withSuccessHandler so that the table of recent emails will update everytime it sends an email and that way you don't have to have a button to initiate updating the table after every email is sent.

Related

Google Form make POST request on submission

Is there a way to call an external API Endpoint on Google Forms every time the form is filled out?
First:
you'll need to set up your App script project and you'll do that by:
Visit script.google.com to open the script editor. (You'll need to be signed in to your Google account.) If this is the first time you've been to script.google.com, you'll be redirected to a page that introduces Apps Script. Click Start Scripting to proceed to the script editor.
A welcome screen will ask what kind of script you want to create. Click Blank Project or Close.
Delete any code in the script editor and paste in the code below.
This video and the doc will help
Second
you'll need to create an installable trigger, you can add it to the form directly or to the spreadsheet that has the responses
function setUpTrigger(){
ScriptApp.newTrigger('sendPostRequest') /* this has the name of the function that will have the post request */
.forForm('formkey') // you'll find it in the url
.onFormSubmit()
.create();
}
Check the doc
Third
create the sendPostRequest function and add the UrlFetchApp to it
function sendPostRequest(e){
// Make a POST request with form data.
var resumeBlob = Utilities.newBlob('Hire me!', 'text/plain', 'resume.txt');
var formData = {
'name': 'Bob Smith',
'email': 'bob#example.com',
'resume': resumeBlob
};
// Because payload is a JavaScript object, it is interpreted as
// as form data. (No need to specify contentType; it automatically
// defaults to either 'application/x-www-form-urlencoded'
// or 'multipart/form-data')
var options = {
'method' : 'post',
'payload' : formData
};
UrlFetchApp.fetch('https://httpbin.org/post', options);
}
Check the doc
Try something like this in your app script:
var POST_URL = "enter your webhook URL";
function onSubmit(e) {
var form = FormApp.getActiveForm();
var allResponses = form.getResponses();
var latestResponse = allResponses[allResponses.length - 1];
var response = latestResponse.getItemResponses();
var payload = {};
for (var i = 0; i < response.length; i++) {
var question = response[i].getItem().getTitle();
var answer = response[i].getResponse();
payload[question] = answer;
}
var options = {
"method": "post",
"contentType": "application/json",
"payload": JSON.stringify(payload)
};
UrlFetchApp.fetch(POST_URL, options);
};
Be sure to replace the POST_URL variable with your webhook, you can use requestcatcher.com to test this out.
Add a trigger to the script by clicking "Triggers" in the side menu
Open the menu (top-right dots)
Click in Script Editor
Paste the above code (changing the POST_URL)
Click in the clock icon (left-side menu), which means Triggers.
On the right-bottom corner, click in the blue Add trigger button (a form will show as the image below).
It should show onSubmit under Choose which function to run.
Make sure Select event type is set as On form submit.
Click Save button.
After that, submit your form and watch for the request to come in.
This is pretty straightforward with Google Scripts.
Just create a new project bound to your spreadsheet and create 2 elements:
A function that will contain all relevant data to make the call (see docs for making a HTTP request from Google Apps Script)
A trigger linked to the spreadsheet. You can set it to run each time an edit occurs or form is submitted
VoilĂ , your sheet will call whatever endpoint you wish on submission. You can even parse the spreadsheet to return that data to your endpoint

Set email body content in google appmaker

I want to send an email with a content related to my data such as in following piece of code I found on Datasource script of Google AppMaker Project Tracker template. But I don't understand how it works. How that data.modifiedBy reflect to the record in my datasource?
Any help from the floors? Thanks ..
Look at the Notifications server side script in the template.
It has method notifyAboutItemChanges_ which is passing the data to this record.
function notifyAboutItemChanges_(changes) {
var settings = getAppSettingsRecord_()[0];
if (!settings.EnableEmailNotifications) {
return;
}
var data = {
appUrl: settings.AppUrl,
itemType: changes[0].Type,
itemKey: changes[0]._key,
itemName: changes[0].Name,
modifiedBy: changes[0].ModifiedBy,
changes: changes
};
// Email subject.
var subjectTemplate =
HtmlService.createTemplate(settings.NotificationEmailSubject);
}
This function is passing this data to your settings record.
So no magic here :) You need to pass the data to your record which will be replaced at run time with the values.
For more details on Email refer this sample app.

Google App Maker how to create Data Source from Google Contacts

Using GoogleAppMaker how to create a data source from google contacts. There is an employee HR example app but I want to similarly manage contacts (add, modify, delete) and use select criteria.
At this time this task is not trivial in App Maker and it is pretty much generic. We can change question wording to CRUD operations with 3rd party datasources. Let's break it into smaller parts and address them separately.
Read/list contacts
This task is relatively easy. You need to use Calculated Model to proxy Apps Scripts Contacts API response. Once you create model with subset of fields from the Contact response you can create datasource for the model and bind it to List or Table widget. You can also try to find some inspiration in Calculated Model Sample.
// Server side script
function getContacts_() {
var contacts = ContactsApp.getContacts();
var records = contacts.map(function(contact) {
var record = app.models.Contact.newRecord();
record.FirstName = contact.getGivenName();
record.LastName = contact.getFamilyName();
var companies = contact.getCompanies();
if (companies.length > 0) {
var company = companies[0];
record.Organization = company.getCompanyName();
record.Title = company.getJobTitle();
}
var emails = contact.getEmails();
if (emails.length > 0) {
record.Email = emails[0].getAddress();
}
var phones = contact.getPhones();
if (phones.length > 0) {
record.Phone = phones[0].getPhoneNumber();
}
return record;
});
return records;
}
Create/Update/Delete
Since Calculated Models have some limitations, we need to turn on our imagination to create, update and delete records from their datasources. The basic strategy will be calling server side scripts for CUD operations in response to user actions on client side. To get user's input from UI we will need to utilize page's Custom Properties, one property for each Contact field:
Here are some snippets that should explain the idea
Create
// Client script
function onSubmitContactClick(submitButton) {
var props = submitButton.root.properties;
var contact = {
FirstName: props.FirstName,
LastName: props.LastName,
Organization: props.Organization,
...
};
google.script.run
.withSuccessHandler(function() {
// Most likely we'll need to navigate user back to the
// page with contacts list and reload its datasource
// to reflect recent changes, because our `CUD` operations
// are fully detached from the list datasource
app.showPage(app.pages.Contacts);
app.datasources.Contacts.load();
})
.withFailureHandler(function() {
// TODO: Handle error
})
.createContact(contact);
}
// Server script
function createContact(contactDraft) {
var contact = ContactsApp.createContact(contactDraft.FirsName,
contactDraft.LastName,
contactDraft.Email);
contact.addCompany(contactDraft.Organization, contactDraft.Title);
contact.addPhone(ContactsApp.Field.WORK_PHONE, contactDraft.Phone);
}
Update
Idea to update contact records will be very similar to the new contact creation flow, so I skip it for now.
Delete
Assuming that delete button is located inside contacts table row.
// Client script
function onDeleteContactClick(deleteButton) {
var email = deleteButton.datasource.item.Email;
google.script.run
.withSuccessHandler(function() {
// To update contacts list we can either reload the entire
// datasource or explicitly remove deleted item on the client.
// Second option will work way faster.
var contactIndex = deleteButton.parent.childIndex;
app.datasources.Contacts.items.splice(contactIndex, 1);
})
.withFailureHandler(function() {
// TODO: Handle error
})
.deleteContact(contact);
}
// Server script
function deleteContact(email) {
var contact = ContactsApp.getContact(email);
ContactsApp.deleteContact(contact);
}

Form link send and later submit to sender in Meteor

A Meteor web app being used by a logged in user "merchant" who needs to create a link and sms/email it to his customer. The link opens a form. The customer fills up the form and submits it so that the data gets inserted with a property merchantId, since many merchants can send to many customers.
This single page app is not using a Router but able to sms/email. How can linking a form between a merchant and a customer be accomplished elegantly so that the data from the correct customer gets "linked" to the correct merchant? Thanks
Merchant Part
You can trigger after a successful send of the email/sms a meteor method, that stores a record of the sent email/sms in a collection (in this example named Record). This could be the schema for it:
Record Collection Schema (Server/Client)
{
merchantId:String, // the id of the sender
customer:String, //id or name or address or phone num of receiver
opened:Boolean, //when the link is opened can be set to true
link:String, // the link to be send,
type:Number, //0=sms, 1=email,
expires:Date, //in case the link is not opened
}
You can for example create a Meteor method to insert the record after send:
Insert Record (Server)
Meteor.methods({
insertRecord:function(recordData) {
//... check recordData by schmema and permissions here
return Records.insert(recordData);
}
})
Sending the Email/SMS
So the merchant part of the app sends the link via sms/email and calls the insertRecord method to store the record of the saved .
Save Record (Client or Server)
const callback=function(err, res) {
if (res) { // assume your sent was successful here
Meteor.call("insertRecord", {
//... your record credentials
});
}
}
// a little bit of pseudocode
if (sms)
sms.send(form, callback);
else
email.send(form, callback);
Customer Part
When the customer opens the link it triggers a templatethat will render your form. You can initially run a meteor method to check the Record collection for a document that matches the link url.
Get Record by Url Method (Server)
Meteor.methods({
getRecordByUrl:function(url) {
//... do your input checks here
return Records.findOne({link:url});
},
});
Template for Form (Client)
Template.customerForm.onCreated(function(){
const instance = this;
instance.state = new ReactiveDict();
instance.state.set("record", null);
instance.autorun(function(){
// if no record loaded yet
if (!instance.state.get("record")) {
Meteor.call("getRecordByUrl", window.location.href, function(err, res) {
if (err || !res) {
//...handle err
}
this.state.set("record", res);
}.bind(instance));
}
});
});
Template.customerForm.helpers({
getRecord() {
return Template.instance().state.get("record");
},
getMerchantId() {
const record = Template.instance().state.get("record");
return record.merchantId;
}
});
You can then use this document to add the merchantId to the form either as a hidden input or via html data attribute.
{{#if getRecord}}
<form id="...">
<input type="hidden" name="merchantId" value="{{getMerchantId}}" />
<!-- other inputs here -->
</form>
{{/if}}
The examples can of course be optimized but I think this way it clearer to understand.

How to retrieve only new data?

I'm trying to create simple notification system for my site admin, and I need to send only real-time messages to every admin user. But when I use firebase it loads old data on every page, and user see all messages from database. If I set limit(1) user will see last notification on every page reloading:
var eventsList = new Firebase('https://*****-messages.firebaseio.com/');
eventsList.on('child_added', function(message) {
var message = message.val();
$.notification(message.message);
});
How I can load only new messages, without old notification history?
This is by design, in a real-time system there is no concept of the "latest" data because it's always changing. However, if you want to only display items added to the list after the page has loaded, you can do the following:
var newItems = false;
var eventsList = new Firebase('https://*****-messages.firebaseio.com/');
eventsList.on('child_added', function(message) {
if (!newItems) return;
var message = message.val();
$.notification(message.message);
});
eventsList.once('value', function(messages) {
newItems = true;
});
I would comment on the above, but due to reputation I cannot, so hoping this is adequate and this is to address the last comment by Gruff McGruff.
Once fires after because you want to break out of the loop of grabbing all child items. Once that loops is broken, you'll set the newItems variable to true, and then it will be able to get all new children after that.
If you fired it before, it would defeat the purpose and grab all child items regardless because you'll set the newItems variable immediately.
Also, I've used this approach and it works well.

Resources