Beginner google app maker calendar sample - google-calendar-api

I'm really a beginner with Appmaker.
How can I get events from a specific calendar instead of the default calendar?
I've try several thing but I need little help.
I've try to call CalendarApp.getCalendarsByName(""); instead of CalendarApp.getDefaultCalendar()
But I'm stuck
Any hints?
/**
* Gets Calendar events.
* #param {Query} query - data query with parameters.
* #return {Array<Event>} events from Calendar.
*/
function getEvents_(query) {
var startDate = query.parameters.StartDate;
startDate.setHours(0, 0, 0, 0);
var endDate = query.parameters.EndDate;
endDate.setDate(endDate.getDate() + 1);
endDate.setHours(0, 0, 0, 0);
if (startDate.getTime() > endDate.getTime()) {
return [];
}
var results = [];
var events = CalendarApp.getDefaultCalendar().getEvents(startDate,endDate);
events.forEach(function(item) {
var event = app.models.Events.newRecord();
event.StartDate = item.getStartTime();
event.EndDate = item.getEndTime();
event.Title = item.getTitle();
event.Color = item.getColor();
results.push(event);
});
return results;
}

I think you need to use Advanced Calendar API for it. You can plug it in in App Maker settings:
Once you turn it on try this script:
var events = Calendar.Events.list(calendarId, {
timeMin: startDate.toISOString(),
timeMax: endDate.toISOStrng(),
singleEvents: true,
orderBy: 'startTime',
maxResults: 10
});
You can find more info about list method parameters in this docs:
https://developers.google.com/calendar/v3/reference/events/list

Thanks for the hint!
The Calendar API Service was not added.
Work great now with:
var id = 'mycalendarID#group.calendar.google.com';
CalendarApp.getCalendarById(id).getEvents(startDate, endDate);

Related

Sending multiple products in a order data to Google sheets only sends the first product. Why?

I must admit that I donĀ“t have much experience so I tried my best so far-
I made a webhook on my woocommerce store and made a script on google apps to export new order data directly to spreadsheet.
The script partly works but the problem is that when there are more products in order it only exports one row and ignores the rest - so the order ID is correct the name of customer is exported but just 1 of his products from his order is exported to spreadsheet, second problem is that product ID is wrong.
The script is below:
//this is a function that fires when the webapp receives a GET request
function doGet(e) {
return HtmlService.createHtmlOutput("request received");
}
//this is a function that fires when the webapp receives a POST request
function doPost(e) {
var myData = JSON.parse([e.postData.contents]);
var order_number = myData.number;
var shipping_company = myData.shipping.company;
var shipping_method = myData.shipping_lines[0].method_title;
var customer_note = myData.customer_note;
var shipping_lastname = myData.shipping.last_name;
var lineitems=""
for (i in myData.line_items)
{
var product_id = myData.line_items[i].product_id;
var quantity = myData.line_items[i].quantity;
}
var sheet = SpreadsheetApp.getActive().getSheetByName("export");
sheet.appendRow([order_number,product_id,customer_note,shipping_method,shipping_lastname,shipping_company,quantity,lineitems]);
}
on the sheet there is one order - which actually has 3 products in the order but only one was exported, also product ID is 4135 and correct should be 4137.
If anyone has any feedback for me I would be very happy for any help, thanks.
Guys thanks for the helpful tips - I changed the looping and I also realized that I was exporting product_id but in fact what I was looking for variation_id.
Here is the finished code I hope it will help someone:
//this is a function that fires when the webapp receives a GET request
function doGet(e) {
return HtmlService.createHtmlOutput("request received");
}
//this is a function that fires when the webapp receives a POST request
function doPost(e) {
var myData = JSON.parse([e.postData.contents]);
var order_number = myData.number;
var shipping_company = myData.shipping.company;
var shipping_method = myData.shipping_lines[0].method_title;
var customer_note = myData.customer_note;
var shipping_lastname = myData.shipping.last_name;
var lineitems=""
for (i in myData.line_items)
{
var variation_id = myData.line_items[i].variation_id;
var quantity = myData.line_items[i].quantity;
var product_items = variation_id;
var lineitems =lineitems+product_items;
var sheet = SpreadsheetApp.getActive().getSheetByName("export");
sheet.appendRow([order_number,variation_id,customer_note,shipping_method,shipping_lastname,shipping_company,quantity]);
}
}

How to browse to the next page in a datasource that is loaded into table in Google AppMaker

I'm working on a requirement where I have a datasource named 'emailSearchResults' where I search for email messages metadata and load the results in the datasource.
The fields in the datasource are not relevant, however I set the datasource to have 50 records per page as per the below screenshot:
The script I used to load the datasource is shown in the query field, that call the following script:
function getMessageDetails(userId, msgID)
{
var messageDetails = [];
var messageData;
var msgID_,subject_,from_,date_;
messageData=Gmail.Users.Messages.get(userId,msgID,{format:"metadata", metadataHeaders:["Message-ID", "Subject", "From", "Date"]});
console.log(messageData.payload.headers);
//console.log(msgID);
//console.log(messageData.payload.headers[3].value);
date_="<na>";
from_="<na>";
subject_="<na>";
msgID_="<na>";
for (var counter =0;counter<4;counter++)
{
if (messageData.payload.headers[counter].name=="Message-ID")
{
msgID_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="Subject")
{
subject_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="From")
{
from_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="Date")
{
date_=messageData.payload.headers[counter].value;
}
}
messageDetails.push(date_);
messageDetails.push(from_);
messageDetails.push(subject_);
messageDetails.push(msgID_);
return messageDetails;
}
function searchMessages(userId,condition)
{
//
// first we build the conditions
// we can make it fixed
// or we can make it dynamic
var searchResult;
var deleteResult;
var currentMessage;
var results = [];
var pageToken;
var params = {};
var _stat;
var options = {
includeSpamTrash: "true",
pageToken: pageToken
};
var msgRecord = [];
do
{
searchResult=Gmail.Users.Messages.list(userId,options);
for (var i = 0; i < searchResult.messages.length; i++)
{
var record=app.models.emailSearchResults.newRecord();
msgRecord=getMessageDetails(userId,searchResult.messages[i].id);
record.msgMainID=searchResult.messages[i].id;
record.msgID=msgRecord[3];
record.subject=msgRecord[2];
record.senderAddress=msgRecord[1];
record.msgDate=msgRecord[0];
/*console.log(searchResult.messages[i].id);
console.log(msgRecord[3]);
console.log(msgRecord[2]);
console.log(msgRecord[1]);
console.log(msgRecord[0]);
return;*/
results.push(record);
msgRecord=null;
}
if (searchResult.nextPageToken) {
options.pageToken = searchResult.nextPageToken;
}
} while (searchResult.pageToken);
searchResult=null;
return results;
}
On the main page I put a table and linked it to the datasource, and I enabled pagination on the table, so I get the pager buttons at the bottom of the table as below:
When I execute the app and the datasource is filled, I see the first page results in a correct way, however when I want to move to the next page, I click the next page button and once the loading is complete I find out that I still see the same results from the first page on the table.
I am not familiar with how to make the table show the results of the second page then the third page, and I am going in circles on this...
Hope the explanation is clear and addresses the issue..
I would really appreciate any help on this!
Regards
Currently pagination isn't working as expected with calculated datasources. You can, however, build your own. There are several changes you'll need to make to accomplish this. First you'll want to refactor your searchMessages function to something like this:
function searchMessages(userId, pageToken){
var results = [];
var options = {
includeSpamTrash: "true",
pageToken: pageToken,
maxResults: 50
};
var searchResult = Gmail.Users.Messages.list(userId, options);
for (var i = 0; i < searchResult.messages.length; i++){
var record = app.models.emailSearchResults.newRecord();
var msgRecord = getMessageDetails(userId,searchResult.messages[i].id);
record.msgMainID = searchResult.messages[i].id;
record.msgID = msgRecord[3];
record.subject = msgRecord[2];
record.senderAddress = msgRecord[1];
record.msgDate = msgRecord[0];
results.push(record);
}
return {records: results, nextPageToken: searchResult.nextPageToken};
}
Then you'll want to change your datasource query. You'll need to add a number parameter called page.
var cache = CacheService.getUserCache();
var page = query.parameters.page || 1;
var pageToken;
if(page > 1){
pageToken = cache.get('pageToken' + page.toString());
}
var results = searchMessages('me', pageToken);
var nextPage = (page + 1).toString();
cache.put('pageToken' + nextPage, results.nextPageToken);
return results.records;
You'll need to modify the pagination widget's various attributes. Here are the previous/next click functions:
Previous:
widget.datasource.query.pageIndex--;
widget.datasource.query.parameters.page = widget.datasource.query.pageIndex;
widget.datasource.load();
Next:
widget.datasource.query.pageIndex++;
widget.datasource.query.parameters.page = widget.datasource.query.pageIndex;
widget.datasource.load();
You should be able to take it from there.

Issues DocumentMerge in Google AppMaker

As I would like to create documents by merging the entries in a list into a Google Docs template. I have therefore integrated the DocumentMerge method from my previous question into a printButton in a list widget.
Clicking on the printButton should produce a document that merges the contents of the current row into the document template. But when I click on the printButton the method fails due to a circular reference. How can I fix that? The print method goes like this ...
function printReview(widget) {
var review = app.models.Review.getRecord(widget.datasource.item._key);
var templateId = 'templateId';
var filename = 'Review for ...' + new Date();
var copyFile = DriveApp.getFileById(templateId).makeCopy(filename);
var copyDoc = DocumentApp.openById(copyFile.getId());
var copyBody = copyDoc.getBody();
var fields = app.metadata.models.Review.fields;
for (var i in fields) {
var text = '$$' + fields[i].name + '$$';
var data = review[fields[i].name];
copyBody.replaceText(text, data);
}
copyDoc.saveAndClose();
}
As Morfinismo noticed you are getting the error because you are trying to pass complex object from client to server and serializer fails to handle it. In order to fix that you need to adjust your code:
// onClick button's event handler (client script)
function onPrintClick(button) {
var reviewKey = button.datasource.item._key;
google.script.run
.withSuccessHandler(function() { /* TODO */ })
.withFailureHandler(function() { /* TODO */ })
.printReview(reviewKey);
}
// server script
function printReview(reviewKey) {
var review = app.models.Review.getRecord(reviewKey);
...
}

places api Unable to get property 'address_components' of undefined or null reference

working with the google places api and cannot figure why autocomplete is returning undefined here on call to get places.
what developer tools shows is.
address_components is what should be returned on a call to autocomplete.getPlace
Unable to get property 'address_components' of undefined or null reference
function initAutoCompleteDynamic() {
var slideID = 99;
var idx = 99 - slideID;
var propcount = 5;
for (var i = 0; i < propcount; i++) {
var propaddress = "prop1address" + i;
var autocomplete = autocomplete + i;
autocomplete = new google.maps.places.Autocomplete(
document.getElementById(propaddress)),
{ types: ['geocode'] };
autocomplete.addListener('place_changed', fillinAddressDynamic);
}
}
and in fillinAddressDynamic
var place=autocomplete.getPlace():
for (var i = 0; i < place.address_components.length; i++) {
alert("i am in the loop");
var addressType = place.address_components[i].types[0];
var field = addressType;
var completeaddress1 = '';
var propaddress = 'prop1address' + i;
var strnum = 'streetnumber' + i;
CR(i);//calling component resolver.
if (componentFormProduction[addressType]) {
var val = place.address_components[i][componentFormProduction[addressType]];
document.getElementById(CR[addressType]).value = val;
if (field == "street_number") {
var streetnum = document.getElementById(strnum).value = val;
}
if (field == "route") {
if (streetnum) {
completeaddress1 = streetnum + ' ' + val;
}
else {
completeaddress1 = val;
}
document.getElementById('prop1address0').value = completeaddress1;
}
}
}
This would happen if the user (or you) hits Enter without clicking on a suggestion.
Typically the sequence of event is like this:
user enters input
JavaScript queries Autocomplete for suggestions
user clicks on a suggestion
JavaScript queries Details, replaces user input with Details responses' fields (incl. address_components) and fires the places_changed event
handler for places_changed will obtain the Place object from Details response by calling getPlace()
However, it may also be like this:
user enters input
JavaScript queries Autocomplete for suggestions
user disregards suggestions and hits Enter without clicking on one
JavaScript fires the places_changed event without querying Details or modifying user input
handler for places_changed calls getPlace() and gets a nearly empty Place object, with only the name field containing the raw user input.
It is for you to decide what to do with raw user input, here are some examples:
This tool uses the JavaScript Geocoding service to search for that input:
https://google-developers.appspot.com/maps/documentation/utils/geocoder/
This example (address form) does nothing with it:
https://google-developers.appspot.com/maps/documentation/javascript/examples/places-autocomplete-addressform
This (very basic) example will show an error message reporting no details:
https://google-developers.appspot.com/maps/documentation/javascript/examples/full/places-autocomplete

Can I Implement Slide Bar Mark

Hi I am fairly new to HTML5 development. I am currently doing a school project using video-js. The project asks for dynamic marks in the video player slide bar to indicate specific position so that video viewer would know where to jump at. I am not sure about the following questions:
Is there a way to implement this?
Could I achieve that via changing skins simply of the videojs player (video-js.css)?
If I must modify source files to have this function, where should I start from?
Is it possible of adding additional elements (say buttons, images) on to the videoJS player or to its slide bar?
Thanks for the help.
Yes - video js has a great plugin framework that can modify the player's look and functionality.
You can't simply change skins to get what you're asking for, since the those marks don't exist without you creating them explicitly.
Yes - take a look at the example below (which uses the latest 4.1 videojs api)
Definitely! See an example in an answer posted here: VideoJS 4.0 Plugin: How to properly add items to the controlBar?
Example plugin for creating marks in the control bar:
/**
* Utility function to find the percent a given time represents in
* the overall duration.
*/
var getPercent = function(value, total) {
var raw = value * 1.0 / total;
var bounded = Math.min(1, Math.max(0, raw));
return Math.round(bounded * 100, 2);
};
/**
* Draws a single highlighted section on the control bar. Assumes all
* sections require a start value, but not an end value (defaults to a
* 1 second duration).
*/
var drawSection = function(player, section, duration) {
if (!section.start) return;
if (!section.end) section.end = section.start + 1;
var seekBar = vid.controlBar.progressControl.seekBar;
var sectionDiv = seekBar.createEl('div');
// You'd probably want to have a css class for these styles.
sectionDiv.style.backgroundColor = 'yellow';
sectionDiv.style.position = 'absolute';
sectionDiv.style.height = '100%';
var startPercent = getPercent(section.start, duration);
var endPercent = getPercent(section.end, duration);
sectionDiv.style.left = startPercent + '%';
sectionDiv.style.width = (endPercent - startPercent) + '%';
var seekHandle = seekBar.seekHandle;
seekBar.el().insertBefore(sectionDiv, seekHandle.el());
};
var drawSections = function(player, sections) {
var duration = player.duration();
if (duration === undefined || isNaN(duration)) return;
for (var i = 0, l = sections.length; i < l; i++) {
drawSection(player, sections[i], duration);
}
};
var highlightedSectionsPlugin = function(options) {
var player = this;
player.on('durationchange',
function() { drawSections(player, options.sections); });
};
videojs.plugin('highlightedSectionsPlugin', highlightedSectionsPlugin);
var vid = videojs("video", {
plugins : {
highlightedSectionsPlugin : {
sections : [ {start:10, end:20}, {start:25, end:30}, {start: 40} ]
}
}
});

Resources