Issue with creating an "old-fashioned" mail merge with Google Apps Script - javascript

This is a followup to a question I asked yesterday on the Google Apps Script Office Hours Hangout.
The goal of my final script is to create an election process for student elections at the high school where I work using Google Forms. The script has three parts: 1) Create Unique "Voting IDs" (a random 6-digit code) 2) Merge the student data (Name, Homeroom, & Voting ID) on with a template document that will create specific voting instruction for each student. (i.e. an old-fashioned mail merge) 3) Verify the results by checking Voting ID's and removing duplicate votes.
The part of the script that I am having trouble with is the student data merge (step 2). The first dataset is the only one that works. The rest show up as "DocumentBodySection". I have a feeling it is either how I am copying the text from the Document Template or how I am adding the text to the new document.
Spreadsheet w/ Data: https://docs.google.com/spreadsheet/ccc?key=0AierVcXWELCudFI1LU10RnlIVHNsUm11a0dDWEV6M1E
Document Template: (see followup comment for url)
Document Created by Script: https://docs.google.com/document/d/12r2D9SpIVmQYVaasMyMWKjHz6q-ZZyIMEBGHTwlQct8/edit
//Get Settings & Data
ss = SpreadsheetApp.getActiveSpreadsheet();
source_sheet = ss.getSheetByName("Student Data");
settings_sheet = ss.getSheetByName("SETTINGS");
results_column = settings_sheet.getRange("B19").getValue();
source_column = settings_sheet.getRange("B18").getValue();
source_lastrow = source_sheet.getLastRow();
docTemplateID = settings_sheet.getRange("B13").getValue();
docCopyName = settings_sheet.getRange("B14").getValue();
//Merge Student Data with Document
function SendDataMerge () {
// Open docTemplate and Copy Contents to entryTemplate
var docTemplate = DocumentApp.openById(docTemplateID);
var entryTemplate = docTemplate.getActiveSection();
docTemplate.saveAndClose();
// Make a NEW copy of docTemplate
var docTemplate = DocsList.getFileById(docTemplateID);
var docCopy = DocsList.copy(docTemplate, docCopyName);
var docCopyID = docCopy.getId();
// Create Array of Student Data (First, Last, Grouping, VID)
var data = source_sheet.getRange("A2:D"+source_lastrow).getValues();
// Open docCopy for Editing & Clear Contents
var doc = DocumentApp.openById(docCopyID);
var docText = doc.editAsText();
// Run through Student Data
for(var i=0; i<5 /*data.length*/; i++) { //For testing, limit this to 5 entries
var lastName = data[i][0];
var firstName = data[i][1];
var grouping = data[i][2];
var vid = data[i][3];
docText.replaceText('keyLastName', lastName);
docText.replaceText('keyFirstName', firstName);
docText.replaceText('keyGrouping', grouping);
docText.replaceText('keyVID', vid);
docText.appendText('\n*** Appended Text (End of entry) ***');
docText.appendText(entryTemplate);
}
// Save and Close
doc.saveAndClose();
}

I worked around this issue by creating a copy of the template, doing the text replacement and then appending the template elements from the original document into the copy. In particular, I used: var copyTables = templateDoc.getTables(); to fetch and store the tables (as all of my template data was contained in a table) and copyDoc.appendTable(copyTables[0].copy() ); to append the copy (the .copy() at the end seems to work the real magic). This provides the flexibility of updating the template in the friendly Documents interface without having to see a programmer.

I think the problem is with this line:
docText.appendText(entryTemplate);
The variable entryTemplate holds a DocumentBodySection, which is why you are seeing that in the output. If you are trying to append another copy of the original template text you'll need to store that before you enter the loop.

I agree with Eric that appendText(entryTemplate) isn't going to do what you want it to do.
Since you're trying to create one large document with all the students, using a "template" and replacing the text isn't going to work well. I'd suggest instead, creating the "template" in code using the api calls that produce the formatting you want. Then it makes it simple to keep appending new pages of student instructions. Although I think you may run into slowness when the document gets large... I don't know how many students you have.

Related

Dynamically Generate Questions in Google Forms Without Creating New Columns

I'm trying to create a Google Form where a question is dynamically generated by a script.
The form is designed to capture the results of product inspections, which can either result in a pass or a fail. The inspections are raised as the result of a product issue, and continue on for a certain period of time until the issue is resolved - at which point they are closed.
I currently have the form set up as a web app, which will generate a multiple choice grid item upon opening based on the inspections that are currently required.
The form pulls in all of the currently active inspections from the linked spreadsheet, and generates the question options based on this data. However, the script is creating a new set of columns every time the form is opened, even if many of them are the same inspection.
Is there a way to only generate a new column if the question added is new? And if the question is not new, can the responses for this question be funneled into the original column on the spreadsheet?
Thanks.
function doGet(){
var form = FormApp.openById("###");
var workBook = SpreadsheetApp.openById("###");
var sheet = workBook.getSheetByName("###");
var range = "A:B";
var options2DArr = sheet.getRange(range).getValues();
var options = [];
for (var i = 0; i < options2DArr.length; i++)
{
if (options2DArr[i][0] != "")
{
options.push(options2DArr[i][1] + " (" + options2DArr[i][0] + ")");
}
}
myCheckBoxItem = form.getItemById("###").asGridItem();
currentRows = myCheckBoxItem.getRows();
myCheckBoxItem.setTitle('Inspection Results')
.setRows(options)
.setColumns(['Pass', 'Fail']);
var URL = form.getPublishedUrl();
// redirect to the prefilled form URL dynamically created above
return HtmlService.createHtmlOutput("<script>window.top.location.href=\"" + URL + "\"</script>");
}

How to send multiple attachments via email

I have a project to create PDF out from google form and send multiple PDF through email in Google Script,
The relevant CODE segment is as follows ,
if(TF_Samples!= null){
//Generating Lab Reports for TAPE LIFT
for (var Sample_Number in TF_Samples) {
//Creating Report File - Word Version
Template_Doc = Tape_Template_Doc ;
Report_File_Word = Template_Doc.makeCopy(Report_Folder_Word);
Report_File_Word.setName(data[0]+"-TAPE LIFT");
Opened_Report_Doc = DocumentApp.openById(Report_File_Word.getId());
//Getting the Content of the Report & Replacing the Fields
Report_Body = Opened_Report_Doc.getBody();
Report_Body.replaceText("{{ Client Address }}",data[2]);
Report_Body.replaceText("{{ Sample Date }}",data[1]);
Report_Body.replaceText("{{ Sample Number }}",TF_Samples[Sample_Number]);
//Saving & Closing the Report
Opened_Report_Doc.saveAndClose();
//Creating PDF version of Report
Blob_PDF = Report_File_Word.getAs(MimeType.PDF);
Report_PDF_Version = Report_Folder_PDF.createFile(Blob_PDF).setName(data[0]+"-SPORE TRAPE");
}
}
I managed to send email with single attachment but since here I am looping through a for loop I don't know how to do that.
Create a global array and push within each loop iteration the file into the array
Sample modification:
...
var files = [];
for (var Sample_Number in TF_Samples) {
...
Report_PDF_Version = Report_Folder_PDF.createFile(Blob_PDF).setName(data[0]+"-SPORE TRAPE");
files.push(Report_PDF_Version);
}
GmailApp.sendEmail(recipient, subject, body, {attachments:files});
...
as specified in the documentation, the attachment argument must be an array of blobs.
So, instead of converting the blob to a drive file just send the blobs like this :
Blob_PDF = Report_File_Word.getAs(MimeType.PDF);
and push these blobs in an array one by one.
The argument will then be valid.

How to make a web app retain information?

Ok... so a user enters info into the journal field and hits submit, and a function gets called on the information they have submitted, which I call changeTextComment(). That function calls another function and so on as the info is formatted and placed in the cue, as in a Facebook commentary.
I need this information saved so it can be recalled later, in local storage, making the app not refresh every time I restart it. So...
<script>
function appCache() {
// Check browser support
// this is experimental for local storage...
more here: http://www.w3schools.com/html/html5_webstorage.asp
if (typeof(Storage) != "undefined") {
localStorage.setItem(para);
// Retrieve
document.getElementById("journal").innerHTML = localStorage.getItem(para);
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support Web Storage...";
};
</script>
So looking at the appCache function it seems like it might work except I need to identify what the key variable is that will be stored and then retrieved.
I think this is the variable userInput, as any time the user hits the 'add to journal' button this variable is used to store the user input and then put into the changeTextComment() function.
I am wondering if this is the simplest way to deal with this whole thing... I do not yet understand databases, but wondering if that would be easier to learn.
In this case, I would add the function Appcache() to the function changeText() such that it caches the variable and then how would I set it up to then feed the value of the variable's cached info into changeText() upon launch of the app?
Every submission to the journal will have a unique value.
Heres the ChangeTextComment() Function... still sorting out how to use classes in css to simplify these functions:
function changeTextComment(){
// to be modified from the above to change the location of the dump
var userInput = document.getElementById('userInput').value;
// get the input from the user
var panel = document.createElement("div"); // create a parent divider for everything
// assignment of attributes
panel.setAttribute("class","panel panel-default panel-body");
panel.setAttribute("id","panelBody");
var para = document.createElement("P");
var t = document.createTextNode(userInput);
para.appendChild(t);
// add comment area
var c = document.createElement("INPUT");
c.setAttribute("type", "text");
c.setAttribute("id", "comment");
c.setAttribute("placeholder", "comment here");
c.setAttribute("class", "form-control input-lg");
// add comment button attempt -- success <> now try to put it in the textarea
var d = document.createElement("INPUT");
d.setAttribute("type","button");
d.setAttribute("class","btn btn-info active pull-right");
d.setAttribute("onclick","commentThis()");
d.setAttribute("value","Add Comment");
panel.appendChild(para); // this is where a comments piece would go
// place the item
var destination = document.getElementById("journal")
//destination.insertBefore(Commentaryarea, destination.firstChild);
//destination.insertBefore(panel, destination.firstChild);
destination.insertBefore(panel, destination.firstChild);
panel.appendChild(c);
panel.appendChild(d);
document.getElementById("userInput").value = "";
document.getElementById("userInput").focus();}
</script>
<script>
function setText(a){
document.getElementById("userInput").value = a
document.getElementById("userInput").focus();
}
</script>
When you say "stored locally", do you mean stored in the visitors browser, i.e. with only themselves being able to see it and retrieve it? Or do you want other users to be able to see the data, and/or the commenter to be able to see it if they log in later from another location?
If it's the latter then you need to store it on the server side, and you're going to need a database for that. If all you need is to store journal entries then mongoDB is easy to set up with javascript, but if you have a lot of relations (journal entries being associated with users, comments on entries being associated with users and entries, ect) then perhaps an SQL database would suit you better.

Getting form's parent data with ajax / javascript

In CRM DYNAMICS 2013, I'm trying to get the contents of a few text fields of a parent form, after a user opens a child form.
On the OnLoad event of the child form, I have this code:
var objvsdsassignedto = document.parentWindow.parent.parent.opener.Xrm.Page.data.entity.attributes.get("address1_line1");
The error I get is:
I have also tried:
var objvsdassignedto = window.top.opener.Xrm.Page.getAttribute("address1_line1").getValue();
The error I get is:
How do I get the contents of fields using the child form?
here's a larger version of the screenshot above: http://screencast.com/t/KHrtREYn3tL
Just wanted to update this question with more of the things I've tried:
var objvsdsassignedto = window.parent.Xrm.Page.data.entity.attributes.get("address1_line1").getValue();
var objvsdsassignedto = document.parentWindow.parent.parent.opener.Xrm.Page.data.entity.attributes.get("address1_line1");
var objvsdsassignedto = window.top.opener.Xrm.Page.getAttribute("address1_line1").getValue();
var objvsdsassignedto = parent.window.Xrm.Page.data.entity.attributes.get("address1_line1").getValue();
var objvsdsassignedto = window.parent.Xrm.Page.getAttribute("address1_composite");
var parentWindow = window.dialogArguments;
alert( parentWindow.Xrm.Page.data.entity.attributes('address1_composite').getValue());
var parentForm = window.top.opener.parent.Xrm.Page.getAttribute("address1_composite").getValue();
alert(parent.window.Xrm.Page.getAttribute('address1_line1').getValue());
var title = window.parent.opener.Xrm.page.ui.controls.getAttribute('address1_composite').getValue();
alert(title);
Are you sure that you can't do that with a simple mapping in the relationship tab? At the end of the day you are creating a lead from an account, so if you want to move a field you should be able to do that via data mapping. Open the solution go into account open the N:1 relationship node, select the relationship to lead and add the fields you want to map, It should be fairly easy without using any js.
After around 10 hours of googling, I've decided to go with cookies: http://www.quirksmode.org/js/cookies.html

Reload div with same content

I've a div which contains a list of clients pulled from Mysql.
On the same page I have a jquery dialog box which pops up to allow a user to add a new client.
What I want to happen is that when the user adds a new client the containing the list reloads so the new client is available.
Is there a way to simply reload the div without using the Load() function as this is causing errors when I redeclare my classes that populate the list ?
Of course. Without looking at your code, your confusion here suggests that you don't understand "Separation of Concerns". Separate the process of getting information from the process of displaying that information. When the user enters new information, add that to javascript array or object of information you got from the server and also send that off to the server to be updated in the database. Then run the display function again using the updated information to include the new information. Ideally, the display process will use existing markup if it can, rather than deleting it all and recreating it all just to add one item. Here's a very basic example (click here). Ideally, you would take this concept and expand on it to make it optimally efficient and organized.
Here's the sample code from my jsbin. Please keep in mind this is just to get you started.
var info = [1,2,3,4,5]; //this is what you got from your ajax call to the server
function render(element, info) {
//this is a lazy system that recreates/replaces all the markup each time. I suggest doing this more efficiently, but that is work for you to do :)
var frag = document.createDocumentFragment();
var len = info.length;
for (var i=0; i<len; ++i) {
var p = document.createElement('p');
p.textContent = info[i];
frag.appendChild(p);
}
element.innerHTML = '';
element.appendChild(frag);
}
var targetElem = document.getElementById('targetElem');
render(targetElem, info);
var addButton = document.getElementById('add');
var input = document.getElementById('infoInput');
addButton.addEventListener('click', function() {
info.push(input.value); //update the information
render(targetElem, info); //render the updated information
});
Either of these should give you success.
success: function(userinfos) {
$("#users").append($('<tr><td>'+userinfos+'</td></tr>'))
display_message("user added");
}
$.ajax({
type: "GET",
url: "ht.tp://127.0.0.1:8000/result/?age="+ ageData +"&occasion="+
occasionData +"&relationship="+ forData +"#",
success: function (response) {
$("#testDIV").html(response);
}
});

Categories