I am making a web app for a Spreadsheet for a sidebar.
Trying to handle an event listener that implements these conditions:
when checking checkboxes (which have correspondent names and Calendar links for that names)
and picking 2 dates
and then clicking the button
After it's all done a function is called from backend which gets events for that names and records them to a sheet.
I can't get the values for names and calendars in a loop. And not sure what's the good way to put them. I tried to handle it in different ways - no luck.
I eventually narrowed all issues to these 2 parts:
1) how to load data to a sidebar in a best way.
2) how to loop through that data after user interacted with these elements and to get values (depends on the part 1).
I would really appreciate if someone help me a bit more on it (some rather simple solutions).
Here's the variant with scriptlets for using GAS in an html file:
<? for (var i = 0; i < loopNamesForSidebar().names.length; i++) { ?>
<label>
<input type="checkbox" class="filled-in check" checked="checked" />
<span>
<div class="collection">
<a href=" <?= loopNamesForSidebar().calendars[i] ?>" class="collection-item" >
<?= loopNamesForSidebar().names[i] ?>
</a>
</div>
</span>
<? } ?>
</label>
loopNamesForSidebar() is a backend function which loops names and calendars that go to the sidebar. Every time I open the sidebar this data refreshes. I don't have it used in my front-end part.
Here's a Javascript code in an html file:
//import jobs from calendars to sheet
function importJobs() {
//getting all checkboxes
var allCheckboxes = document.getElementsByClassName("check")
//getting inputs of start and end dates
var startDate = document.getElementById("startDate").value
var endDate = document.getElementById("endDate").value
var chosenNames = []
var calendars = []
//looping all checkboxes
for (var i = 0; i < allCheckboxes.length; i++) {
//getting value of each checkbox
var checkbox = allCheckboxes[i].checked;
//if checkbox is checked
if (checkbox == true) {
//getting correspondant employee name
var name = loopNamesForSidebar().names[i]
//and push it to an array
chosenNames.push(name)
//getting correspondant employee calendar
var cal = loopNamesForSidebar().calendars[i]
calendars.push(cal)
} else {
continue;
}
};
//push names and cals to object
var employees = {
names: chosenNames,
cals: calendars
}
//call function to get calendar events
google.script.run.getEvents(employees, startDate, endDate)
};
Per Best Practices, you should load any API / ___ Service call output asynchronously. I would only use template evaluation for trivially computed / retrieved data, e.g. from PropertiesService, CacheService, static definitions, or simple lookups based on input parameters (i.e. querystring / payload data for doGet and doPost trigger functions). If it takes longer than a quarter second on average, it's too slow for synchronous usage.
so:
function templateOK(param) {
const val = PropertiesService.getScriptProperties().getProperty(param);
return val ? JSON.parse(val) : [
"name 1", "name 2", "name 3"
];
}
function shouldRunAsync(param) {
const sheet = param ? SpreadsheetApp.getActive().getSheetByName(param) : null;
return sheet ? sheet.getDataRange().getValues() : [];
}
Assuming you've set up the other parts of your GS and HTML files appropriately, one of the .html's <script> tags may look something like this:
$(document).ready(() => loadServerData()); // jQuery
function loadServerData() {
const TASK = google.script.run.withSuccessHandler(useNames); // has implicit failure handler of `console`
// Schedule this to be run every so often
const intervalMS = 10 * 60 * 1000; // 10 minutes
setInterval(sheetName => TASK.shouldRunAsync(sheetName), intervalMS, "Names");
// Invoke promptly too.
TASK.shouldRunAsync("Names");
console.log(new Date(), "Set schedule & invoked server function");
}
function useNames(serverValue, userObject) {
console.log(new Date(), "Got Value from server", serverValue);
// use the return value to do stuff, e.g.
const cbDiv = $("id of div containing checkboxes"); // jQuery
/** could add code here to read existing checkbox data, and
use that to maintain checked/unchecked state throughout loads */
cbDiv.innerHTML = serverValue.map((name, idx) => `<p id="${name}">${idx + 1}: ${name}</p>`).join("");
}
As always, make sure you are intimately familiar with the serializable data types and the Client-Server communication model: https://developers.google.com/apps-script/guides/html/communication
Other refs
Element#innerHTML
Array#map
setInterval
Arrow functions
Template literals
Related
Hi following the question in this thread Google Script to pull certain information from email and put in a Sheet
I am really interested to understand if is it possible to extract multiple parts of the email.
For example, if I have two different blocks inside my email like
Item#: SS10MM
Product Description: 10mm SOCKET
Vendor: Store
Vendor Item Code: 10MSS
Complaint: Lost another one, really wish you could put a GPS chip in these!
may I extract both?
thanks a lot
var LABEL_PENDING = "pending";
var LABEL_DONE = "done";
// processPending(sheet)
// Process any pending emails and then move them to done
function processPending_(sheet) {
// Get out labels by name
var label_pending = GmailApp.getUserLabelByName(LABEL_PENDING);
var label_done = GmailApp.getUserLabelByName(LABEL_DONE);
// The threads currently assigned to the 'pending' label
var threads = label_pending.getThreads();
// Process each one in turn, assuming there's only a single
// message in each thread
for (var t in threads) {
var thread = threads[t];
// Gets the message body
var message = thread.getMessages()[0].getPlainBody();
Logger.log(message);
// Process the messages here
message = message.substr(message.search("Item#:")); //Get the beginning of the important part + cut off the beginning
Logger.log(message);
message = message.split("\n");
Logger.log(message[0].split(": ")[1]);
var data = [message[0].split(": ")[1], //Item#
message[1].split(": ")[1], //Prod. Desc.
message[2].split(": ")[1], //Vendor
message[3].split(": ")[1], //Ven. Item Code
message[4].split(": ")[1] //Complaint
];
// Add message to sheet
sheet.appendRow(data);
// Set to 'done' by exchanging labels
thread.removeLabel(label_pending);
thread.addLabel(label_done);
}
}
I am trying to link my google sheet to a calendar to automatically create calendar events and update them when they are updated in the google sheet. My google sheet tracks new building opening dates and new building construction start dates so for each row I need it to create two calendar events when applicable (sometimes only one of the dates have been filled out).
The Headings of the sheet are "Loc. #", "Location", "Cons Start", and "Whse Open."
The values for each of these headings are populated from a reference to a different sheet and updates automatically from that sheet due to being a reference.
I am not the most inclined in javascript, but so far the code I have for the google apps script is as follows:
// Calendar ID can be found in the "Calendar Address" section of the Calendar Settings.
var calendarId = 'costco.com_19rlkujqr1v5rfvjjj8p1g8n8c#group.calendar.google.com';
// Configure the year range you want to synchronize, e.g.: [2006, 2017]
var years = [2017,2020];
// Date format to use in the spreadsheet.
var dateFormat = 'M/d/yyyy H:mm';
var titleRowMap = {
'loc#': 'Loc. #',
'location': 'Location',
'conStart': 'Cons Start',
'whseOpen': 'Whse Open',
};
var titleRowKeys = ['loc#', 'location', 'conStart', 'WhseOpen'];
var requiredFields = ['loc#', 'location', 'conStart', 'WhseOpen'];
// This controls whether email invites are sent to guests when the event is created in the
// calendar. Note that any changes to the event will cause email invites to be resent.
var SEND_EMAIL_INVITES = false;
// Setting this to true will silently skip rows that have a blank start and end time
// instead of popping up an error dialog.
var SKIP_BLANK_ROWS = false;
// Updating too many events in a short time period triggers an error. These values
// were tested for updating 40 events. Modify these values if you're still seeing errors.
var THROTTLE_THRESHOLD = 10;
var THROTTLE_SLEEP_TIME = 75;
// Adds the custom menu to the active spreadsheet.
function onOpen() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [
{
name: "Update from Calendar",
functionName: "syncFromCalendar"
}, {
name: "Update to Calendar",
functionName: "syncToCalendar"
}
];
spreadsheet.addMenu('Calendar Sync', menuEntries);
}
// Creates a mapping array between spreadsheet column and event field name
function createIdxMap(row) {
var idxMap = [];
for (var idx = 0; idx < row.length; idx++) {
var fieldFromHdr = row[idx];
for (var titleKey in titleRowMap) {
if (titleRowMap[titleKey] == fieldFromHdr) {
idxMap.push(titleKey);
break;
}
}
if (idxMap.length <= idx) {
// Header field not in map, so add null
idxMap.push(null);
}
}
return idxMap;
}
// Converts a spreadsheet row into an object containing event-related fields
function reformatEvent(row, idxMap, keysToAdd) {
var reformatted = row.reduce(function(event, value, idx) {
if (idxMap[idx] != null) {
event[idxMap[idx]] = value;
}
return event;
}, {});
for (var k in keysToAdd) {
reformatted[keysToAdd[k]] = '';
}
return reformatted;
}
Not really sure what to do next to make this happen. Any suggestions on how to implement this?
You can try to check these tutorials and SO questions on how to use Google Apps Script in creating a calendar events. These links includes some sample code that you can copy of follow.
Create Google Calendar Events from Spreadsheet but prevent duplicates
Tutorial: Using Google Spreadsheets to Update a Google Calendar – Our New Vacation Management System
For more informationor more ideas, you can also check this tutorials on how to use Google Apps Script in creating a calendar events from Google Form.
A JSON encoded array is passed from PHP to an HTML document. It is not at all clear how to deconstruct that array into javascript-usable pieces. For example, consider the following HTML:
<div id="options">{"foo":[{"id":1},{"id":3}], "bar":[{"id":2},{"id":4}]}</div>
The only a priori known element of this array is that the key id exists. The indices, I know, can be found with
var data = JSON.parse($("#options").text());
$.each(data, function(index) {
// index will be foo & bar
});
The use case is to use the index and id to add an attribute to elements in a document. I have not yet stumbled upon the technique to return the ids associated with each index. How best can that be done?
Edit - a clarification of the use case - the long story
I want to re-enable some options on a form based on properties of an entity (in a Symfony application). Disabled options cannot be modified, but are also not not persisted - their values are set to null. I've built a service to determine the option elements that are disabled and send those elements to the form document as a JSON object. I'm assuming for now that the specific options are not known until the form is created. In the example above, foo & bar represent possible options, and the ids correspond to the option. For example, a Household entity might have Reason options selected but disabled of "Low wages" (id = 3). This would show up in as ...id="options">{"reasons":[{"id":3}]}<.... I would the use this information to remove the disabled="disabled" attribute from the set of checkboxes for the Reason, id=3 (i.e., id="household_reasons_3") field. I hope this makes sense.
Edit #2, by request - the PHP code creating the object.
The result of getMetatData() appears in the document at #options. From the above edit, the Household entity is $object.
public function getMetaData($object) {
$data = array();
$className = get_class($object);
$metaData = $this->em->getClassMetadata($className);
foreach ($metaData->associationMappings as $field => $mapping) {
if (8 === $mapping['type']) {
$data[$field] = $this->extractOptions($object, $field);
}
}
return json_encode($data);
}
private function extractOptions($object, $field) {
$data = [];
$method = 'get' . ucfirst($field);
$itemName = substr($field, 0, -1);
$getter = 'get' . ucfirst($itemName);
$entity = $object->$method();
foreach ($entity as $item) {
if (method_exists($item, 'getEnabled') && false === $item->getEnabled()) {
$data[] = ['id' => $item->getId()];
}
}
return $data;
}
Long before the infinite monkey limit was reached I stumbled on a method to create the results I was looking for. My thanks go out to all who pushed for clarifications. So, for the object
{"foo":[{"id":1},{"id":3}], "bar":[{"id":2},{"id":4}]}
the script
var data = JSON.parse($("#options").text());
var i = 0
var output = [];
$.each(data, function(index, item) {
$.each(item, function(k, v) {
output[i] = "household_" + index + "_" + v.id;
i++;
});
});
output;
produces this:
["household_foo_1", "household_foo_3", "household_bar_2", "household_bar_4"]
I get the strings I need; I can take it from here.
When I design interfaces, I can't be bothered to create believable dummy text. At the same time, however, I don't want to just copy and paste the same exact data over and over, because then the interface doesn't look terribly realistic.
I have generated a Master JSON Schema that contains the most common types of data I use. I'd like to be able to do something like this when I'm writing HTML:
<ul>
<li>{first_name}</li>
<li>{first_name}</li>
...
<li>{first_name}</li>
</ul>
OR
<ul>
<li data="{first_name}"></li>
<li data="{first_name}"></li>
...
<li data="{first_name}"></li>
</ul>
...whereby every instance of {first_name} is replaced with a random first name from my JSON file. Likewise for any other variable I have in there ( last_name, email, address, country, sentence, etc... )
Is there a way to do this without PHP in something like jQuery? I imagine it'd have to be something like:
foreach(var) {
return randomData(var);
}
Ideally I'd have a very simple, generalized function that would comb through the UI looking for any and all tags and replace each one with a random piece of data from the master schema JSON file.
Click below to see solution I came up with thanks to Billy's help:http://jsfiddle.net/JonMoore/5Lcfz838/2
between http://chancejs.com/ and http://handlebarsjs.com/ you can generate lots of repeatable, seeded random data structures...
// get references to DOM elements
var tplElm = document.getElementById('template');
var tgtElm = document.getElementById('target');
// compile handlebars template
var template = Handlebars.compile(tplElm.innerText);
// initialise chance with seed
// change the seed to change the output data
var chance = new Chance(1234567890);
// create array of random data
var people = [];
for(var i=0;i<10;i++){
people.push({
first_name: chance.name()
});
}
// apply data to template, and insert into page
tgtElm.innerHTML = template({
people: people
});
<!-- load dependencies -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chance/0.5.6/chance.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.3/handlebars.min.js"></script>
<!-- define template -->
<script id="template" type="text/template">
<ul>
{{#people}}
<li>{{first_name}}</li>
{{/people}}
</ul>
</script>
<!-- placeholder for output -->
<div id="target"></div>
Something like this will give you what you want:
var json = [{ "first_name": "bob"}, {"first_name": "sam"}, {"first_name": "bill"}];
var randomnum = Math.floor((Math.random() * 3));
console.log(json[randomnum].first_name);
You can access this data using AJAX and then get elements using Math.random.
Then, with the help of jQuery you can dynamically generate li items.
Let's suppose you have a div element like <div id="anyId"></div> where you will put your ul and lis.
function getElementsFromMasterSchema(count, callback) {
var items = [];
var usedIds = {};
$("h3").text("Request sent...Loading...");
timer = setInterval(function() {
$("h3").text("Loading..." + (time++) + " seconds passed.");
}, 1000);
$.ajax({
url: "https://www.mockaroo.com/37dcc3b0/download?count=100&key=37b5a7c0",
method: "GET"
}).done(function(dt) {
var length = dt.length;
while (items.length < count) {
var randomItem = dt[Math.round(Math.random() * length)];
if (usedIds[randomItem.id] !== true) {
usedIds[randomItem.id] = true;
items.push(randomItem);
}
}
callback(items);
});
}
getElementsFromMasterSchema(10, function(result) {
var ul = $("<ul/>");
for (var i = 0; i < result.length; i++) {
$("<li/>").text(result.first_name).appendTo(ul);
}
$("#anyId").append(ul);
});
Note that you need to make requests from the same domain or set Access-Control-Allow-Origin header in order to make cross-domain requests.
However, this method is working slowly. It takes from 5 to 20 seconds to load this file. Loading a large file from a server and using only some of data is a bad approach.
You definitely need to implement this algorithm on a server side. Something like SQL SELECT TOP n FROM x ORDER BY newid() (SELECT * FROM x ORDER BY RAND() LIMIT n).
I have data being sent to a custom data list from the following code:
// Get the site name and dataLists
var site = siteService.getSite("Testing");
var dataLists = site.getContainer("dataLists");
// Check for data list existence
if (!dataLists) {
var dataLists = site.createNode("dataLists", "cm:folder");
var dataListProps = new Array(1);
dataListProps["st:componentId"] = "dataLists";
dataLists.addAspect("st:siteContainer", dataListProps);
dataLists.save();
}
// Create new data list variable
var orpList = dataLists.childByNamePath("orplist1");
// If the data list hasn't been created yet, create it
if (!orpList) {
var orpList = dataLists.createNode("orplist1","dl:dataList");
// Tells Alfresco share which type of items to create
orpList.properties["dl:dataListItemType"] = "orpdl:orpList";
orpList.save();
var orpListProps = [];
orpListProps["cm:title"] = "Opportunity Registrations: In Progress";
orpListProps["cm:description"] = "Opportunity registrations that are out for review.";
orpList.addAspect("cm:titled", orpListProps);
}
// Create new item in the data list and populate it
var opportunity = orpList.createNode(execution.getVariable("orpWorkflow_nodeName"), "orpdl:orpList");
opportunity.properties["orpdl:nodeName"] = orpWorkflow_nodeName;
opportunity.properties["orpdl:dateSubmitted"] = Date().toString();
opportunity.properties["orpdl:submissionStatus"] = "Requires Revisions";
opportunity.save();
This correctly creates data list items, however, at other steps of the workflow require these items to be updated. I have thought of the following options:
Remove the data list item and add another with the updated information
Simply update the data list item
Unfortunately I have not found adequate solutions elsewhere to either of these options. I attempted to use orpWorkflow_nodeName, which is a unique identifier generated at another step, to identify a node to find it. This does not seem to work. I am also aware that nodes have unique identifiers generated by Alfresco itself, but documentation doesn't give adequate information on how to obtain and use this.
My question:
Instead of var opportunity = orpList.createNode(), what must I use in
place of createNode() to identify an existing node so I can update its
properties?
You can use this to check existing datalist item.
var opportunity = orpList .childByNamePath(execution.getVariable("orpWorkflow_nodeName"));
// If the data list Item is not been created yet, create it
if (!opportunity ) {
var orpList = orpList .createNode(execution.getVariable("orpWorkflow_nodeName"),"dl:dataList");}