Google Apps Script - Sheet - Menu Trigger not working - javascript

So I am building a mailmerge tool, and it works fine.
Testing the trigger with a hard coded input works fine:
function test(){
sendEmails("TEST MAILMERGE FROM DRAFT")
}
It also works fine if I prompt an input box (relevant section of the code shown).
function sendEmails(subjectLine,sheet=SpreadsheetApp.getActiveSheet()) {
if (!subjectLine) {
subjectLine = Browser.inputBox(
"Mail Merge",
"Type or copy/paste the subject line of the Gmail " +
"draft message you would like to use:",
Browser.Buttons.OK_CANCEL
);
if (subjectLine === "cancel" || subjectLine == "") {
// if no subject line finish up
return;
}
}
However, trying to be smarty pants and have the menu dynamically populated with Subject lines like this:
function onOpen() {
// get the UI for the Spreadsheet
const ui = SpreadsheetApp.getUi();
// add the menu
const menu = ui.createMenu("TEST");
// get the drafts from Gmail
let drafts = GmailApp.getDraftMessages();
// for each draft, create a new menu item
drafts.forEach((draft) => {
// add the drafts to be triggered using the following: addItem(caption: string, functionName: string)
menu
.addItem(
draft.getSubject().toString(),
'sendEmails("' + draft.getSubject().toString() + '")'
)
.addToUi();
});
}
However, this doesn't work. It comes up with the following error:
Error Script function not found: sendEmails(TEST MAILMERGE FROM DRAFT)
Which to me looks like it should work. As the testing trigger that is hardcoded above works.
Am I being daft here? As far as I can see, this should work? But it's not.
When / if I get it working, I will put a check in to account for 'trash' drafts that don't have a subject. Just trying to get it to actually work for now though.

Menu.addItem(caption, functionName)
Parameters:
caption: The label for the menu item, with only the first word capitalized.
functionName: The name of the function to invoke when the user selects the item. You can use functions from included libraries, such as Library.libFunction1.
Menu.addItem() expects a function name without arguments. It doesn't allow to pass arguments in the function.
Workaround:
Based on my understanding, your goal is to have a different menu item that could send emails on each draft messages available in your email account.
You might want to consider using Custom dialogs or Custom sidebars where in you can select your draft message subject that you want to pass as argument when you call your sendEmails() function. You can refer to the sample code as reference.
Sample Code:
(Code.gs)
function onOpen() {
// get the UI for the Spreadsheet
const ui = SpreadsheetApp.getUi();
// add the menu
const menu = ui.createMenu("TEST")
.addItem('Send Email', 'selectDraft')
.addToUi();
}
function selectDraft() {
var html = HtmlService.createHtmlOutputFromFile('draft');
SpreadsheetApp.getUi() // Or DocumentApp or SlidesApp or FormApp.
.showModalDialog(html, 'Select Draft Message');
}
function getDraftSubject(){
// get the drafts from Gmail
let drafts = GmailApp.getDraftMessages();
var subjects = [];
// for each draft, create a new menu item
drafts.forEach((draft) => {
subjects.push(draft.getSubject().toString());
});
Logger.log(subjects);
return subjects;
}
function sendEmails(subjectLine,sheet=SpreadsheetApp.getActiveSheet()) {
Logger.log(subjectLine);
}
(draft.html)
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h2>Select Draft Subject</h2>
<form id="myForm" onsubmit="handleFormSubmit()">
<div class="form-group">
<label for="subject">Draft Subject</label>
<select class="form-control form-control-sm" id="subject" name="subject" required>
<option value="" selected>Choose...</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<script>
google.script.run.withSuccessHandler(function(subj) {
let select = document.getElementById("subject");
subj.forEach(function(e, i) {
let option = document.createElement("option");
option.value = e;
option.text = e;
select.appendChild(option);
});
}).getDraftSubject();
function handleFormSubmit() {
var value = document.getElementById("subject").value;
google.script.run.sendEmails(value);
document.getElementById("myForm").reset();
}
</script>
</body>
What it does?
Create a custom menu that will show a dialog box based on the draft.html
In draft.html, We used google.script.run.withSuccessHandler(function) to call getDraftSubject() in our server side (apps script) which returned an array of draft message subjects. We then update the form's select class and add options based on each draft message subjects obtained.
When the form was submitted, it will call handleFormSubmit(), we get the selected draft message subject and pass that value using google.script.run.myFunction(...) (any server-side function).
google.script.run.sendEmails(value);
Output:

Related

Change Google Sheet data based on user selection in Google Site (Web App)

I am making a google site which shows a drop down based on data coming from a Google Sheet. It works.
As next step, I want that when the user select a drop down choice from the list, the value which is selected is written in Google Sheet cell (in the example below, the selected value will be written in Sheet: "Dashboard", cell: B92).
For example, assume that the drop down list has the following values coming from the Sheet: "Item 1", "Item 2", "Item 3".
When the user select "Item 1" from the web site, the script should write "Item 1" in Google sheet cell B92. Similarly, if the user select "Item 2", the script will set "Item 2" in cell B92.
I tried with the code below, but I think something is wrong with:
fetch in the HTML file
doPost(e) because I am not sure how to pass the parameter
(I removed the entire code to focus on the pieces which are incorrect. I can add it back if needed)
function doPost(e) {
var ss=SpreadsheetApp.openById('XXXXXXXX');
var sh=ss.getSheetByName('Dashboard');
sh.getRange(92,2).setValues(e.parameter);
}
HTML file:
<script type="text/javascript">
var lastIndex = "";
const url = "https://script.google.com/a/google.com/macros/s/AKfycbxHX7cthji076OrqfY9ZpGa7jNDxKHUMf_ib7Ekmoo0Ir5DQF1Y/exec";
function listQ(){
var e = document.getElementById("sel1");
if(e.selectedIndex > 0){
lastIndex = e.selectedIndex;
console.log(lastIndex);
fetch(url, {
method: "POST"
, body: lastIndex
}).then(function (response) {
return response.json()
}).then(function (data) {
console.log(data);
})
}
}
document.getElementById("sel1").addEventListener("click",listQ);
I believe your goal as follows.
You want to put the selected value at select tab in HTML to the cell "B92" in the sheet Dashboard.
You want to send the value to Web Apps with the POST method.
For this, how about this answer?
Modification points:
At Google Apps Script side,
When you want to use the POST method, the request body is included in e.postData.contents.
sh.getRange(92,2).setValues(e.parameter); is not correct. In this case, please use setValue/
In your doPost, no values are returned. In this case, an error occurs at Javascript side when the value is sent.
At Javascript side,
lastIndex is returned. In the case of When the user select "Item 1" from the web site, the script should write "Item 1" in Google sheet cell B92. Similarly, if the user select "Item 2", the script will set "Item 2" in cell B92., the selected value is required to be retrieved and returned.
When above modification is reflected to your script, it becomes as follows.
Modified script:
Google Apps Script side:
function doPost(e) {
var value = JSON.parse(e.postData.contents).value;
var ss = SpreadsheetApp.openById('XXXXXXXX');
var sh = ss.getSheetByName('Dashboard');
sh.getRange(92, 2).setValue(value);
return ContentService.createTextOutput(JSON.stringify({message: "ok"})).setMimeType(ContentService.MimeType.JSON);
}
HTML and Javascript side:
From your question, I cannot understand about your options. So I used a sample options like below. Please replace this for your actual situation.
<select id="sel1">
<option value="sample1">sample1</option>
<option value="sample2">sample2</option>
<option value="sample3">sample3</option>
</select>
<script>
function listQ() {
const index = this.selectedIndex;
if (index > 0) {
const e = document.getElementById("sel1");
const value = e.options[index].value;
const url = "https://script.google.com/macros/s/###/exec"; // Please replace this for your Web Apps.
fetch(url, {
method: "POST",
body: JSON.stringify({index: index, value: value}),
})
.then(function (response) {
return response.json();
})
.then(function (data) {
console.log(data);
})
}
}
document.getElementById("sel1").addEventListener("change", listQ);
</script>
In this modification, when the options of sample2 and sample3 are selected, the value is sent to Web Apps. And then, at the Web Apps, the retrieved value is put to the cell "B92".
Note:
When you modified the script of Web Apps, please redeploy it as new version. By this, the latest script is reflected to Web Apps. Please be careful this.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
In addition to Tanaike answer, I am posting an alternative using google.script.run which avoid the CORS issue which some users may experience.
The complete explanation is here: CORS block: WebApp POST to Sheet
in gs file:
function yourServerSideFunc(body) {
var value = body["value"];
var ss = SpreadsheetApp.openById('1ROvDcIQ9JCGxzLvCvTKIqSor576Uj9ZJv-n6hQ762XB');
var sh = ss.getSheetByName('Dashboard');
sh.getRange(92, 2).setValue(value);
return ContentService.createTextOutput(JSON.stringify({message: "ok"})).setMimeType(ContentService.MimeType.JSON);
}
and in HTML:
function listQ() {
const index = this.selectedIndex;
if (index > 0) {
const e = document.getElementById("sel1");
const value = e.options[index].value;
const body = { index: index, value: value };
google.script.run.withSuccessHandler(yourCallBack).yourServerSideFunc(body);
}
}
document.getElementById("sel1").addEventListener("change",listQ);
function yourCallBack(response) {
}

How to do update in listing for web by using firebase

I am creating admin panel in website and I am using firebase as a database in backend.I am able to display listing but when I click on the particular listing there status should change from 'pending' to 'accept' but it doesnt.I dont know where I did mistake.Please give suggestion and I attach js file and database screenshot
pl.js
var firebaseheadingRef = firebase.database().ref().child("user");
firebaseheadingRef.on('child_added',datasnapshot=>{
var title= datasnapshot.child("listing").child("title").val();
var userid= datasnapshot.child("username").val();
var type= datasnapshot.child("listing").child("title").val();
var publisheddate= datasnapshot.child("listing").child("publish").val();
var expirydate= datasnapshot.child("listing").child("expire").val();
$("#tablebody").append("<tr><td>"+title+"</td><td>"+userid+"</td><td>"+type+"</td><td>"+publisheddate+"</td><td><button type=button id=accept onclick=accept()>Accept</button><button type=button>Reject</button></td></tr>");
});
function accept()
{
firebaseheadingRef.on('child_changed',datasnapshot=>{
datasnapshot.child("listing").child("status").update({"status":"accept"});
setCommentValues(postElement, data.key, data.val().text, data.val().author);
});
}
database
listing display picture where I click on accept button then update of status should done
There are two places where you need to change your code:
First, in the code that generates the table, you have to pass the id of the node to the function call, as follows. You get the node id with the key property of the DataSnapshot.
.....
$("#tablebody").append("<tr><td>"+title+"</td><td>"+userid+"</td><td>"+type+"</td><td>"+publisheddate+"</td><td><button type=button id=accept onclick=accept('" + datasnapshot.key + "')>Accept</button><button type=button>Reject</button></td></tr>");
...
And secondly you have to write your accept() function in such a way it updates the database value, with the set() method. Like the following
function accept(userId) {
var nodeRef = firebase.database().ref("/user/" + userId + "/listing/status");
return nodeRef.set('accept');
}

How do I populate a list field in a model from javascript?

I have a Kendo.MVC project. The view has a model with a field of type List<>. I want to populate the List from a Javascript function. I've tried several ways, but can't get it working. Can someone explain what I'm doing wrong?
So here is my model:
public class Dashboard
{
public List<Note> ListNotes { get; set; }
}
I use the ListNotes on the view like this:
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
}
This works if I populate Model.ListNotes in the controller when the view starts...
public ActionResult DashBoard(string xsr, string vst)
{
var notes = rep.GetNotesByCompanyID(user.ResID, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
Dashboard employee = new Dashboard
{
ResID = intUser,
Type = intType,
FirstName = user.FirstName,
LastName = user.LastName,
ListNotes = listNotes
};
return View(employee);
}
... but I need to populate ListNotes in a Javascript after a user action.
Here is my javascript to make an ajax call to populate ListNotes:
function getReminders(e)
{
var userID = '#ViewBag.CurrUser';
$.ajax({
url: "/api/WoApi/GetReminders/" + userID,
dataType: "json",
type: "GET",
success: function (notes)
{
// Need to assign notes to Model.ListNotes here
}
});
}
Here's the method it calls with the ajax call. I've confirmed ListNotes does have the values I want; it is not empty.
public List<Koorsen.Models.Note> GetReminders(int id)
{
var notes = rep.GetNotesByCompanyID(id, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
foreach (Koorsen.OpenAccess.Note note in notes)
{
Koorsen.Models.Note newNote = new Koorsen.Models.Note()
{
NoteID = note.NoteID,
CompanyID = note.CompanyID,
LocationID = note.LocationID,
NoteText = note.NoteText,
NoteType = note.NoteType,
InternalNote = note.InternalNote,
NoteDate = note.NoteDate,
Active = note.Active,
AddBy = note.AddBy,
AddDate = note.AddDate,
ModBy = note.ModBy,
ModDate = note.ModDate
};
listNotes.Add(newNote);
}
return listNotes;
}
If ListNotes was a string, I would have added a hidden field and populated it in Javascript. But that didn't work for ListNotes. I didn't get an error, but the text on the screen didn't change.
#Html.HiddenFor(x => x.ListNotes)
...
...
$("#ListNotes").val(notes);
I also tried
#Model.ListNotes = notes; // This threw an unterminated template literal error
document.getElementById('ListNotes').value = notes;
I've even tried refreshing the page after assigning the value:
window.location.reload();
and refreshing the panel bar the code is in
var panelBar = $("#IntroPanelBar").data("kendoPanelBar");
panelBar.reload();
Can someone explain how to get this to work?
I don't know if this will cloud the issue, but the reason I need to populate the model in javascript with an ajax call is because Model.ListNotes is being used in a Kendo Panel Bar control and I don't want Model.ListNotes to have a value until the user expands the panel bar.
Here's the code for the panel bar:
#{
#(Html.Kendo().PanelBar().Name("IntroPanelBar")
.Items(items =>
{
items
.Add()
.Text("View Important Notes and Messages")
.Expanded(false)
.Content(
#<text>
#RenderReminders()
</text>
);
}
)
.Events(e => e
.Expand("getReminders")
)
)
}
Here's the helper than renders the contents:
#helper RenderReminders()
{
if (Model.ListNotes.Count <= 0)
{
#Html.Raw("No Current Messages");
}
else
{
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
<br />
}
}
}
The panel bar and the helpers work fine if I populate Model.ListNotes in the controller and pass Model to the view. I just can't get it to populate in the javascript after the user expands the panel bar.
Perhaps this will do it for you. I will provide a small working example I believe you can easily extend to meet your needs. I would recommend writing the html by hand instead of using the helper methods such as #html.raw since #html.raw is just a tool to generate html in the end anyways. You can write html manually accomplish what the helper methods do anyway and I think it will be easier for you in this situation. If you write the html correctly it should bind to the model correctly (which means it won't be empty on your post request model) So if you modify that html using javascript correctly, it will bind to your model correctly as well.
Take a look at some of these examples to get a better idea of what I am talking about:
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
So to answer your question...
You could build a hidden container to hold your list values like this (make sure this container is inside the form):
<div id="ListValues" style="display:none">
</div>
Then put the results your ajax post into a javascript variable (not shown).
Then in javascript do something like this:
$('form').off('submit'); //i do this to prevent duplicate bindings depending on how this page may be rendered futuristically as a safety precaution.
$('form').on('submit', function (e) { //on submit, modify the form data to include the information you want inside of your ListNotes
var data = getAjaxResults(); //data represents your ajax results. You can acquire and format that how you'd like I will use the following as an example format for how you could save the results as JSON data: [{NoteID ="1",CompanyID ="2"}]
let listLength = data.length;
for (let i = 0; i < listLength; i++) {
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].NoteID " value="' + data.NoteID +'" />')
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].CompanyID " value="' + data.CompanyID +'" />')
//for your ajax results, do this for each field on the note object
}
})
That should do it! After you submit your form, it should automatically model bind to you ListNotes! You will be able to inpsect this in your debugger on your post controller action.

use jquery variable in # block razor

I'm strugling with a jquery script inside a cshtml page. For short my question is how to use a var inside a # statement in a cshtml page?
below an example of what I'm trying:
<select id="DefaultText">
<option value="-1">-- select --</option>
#foreach( var d in Model.DefaultTexts )
{
<option value="#d.Id" >#d.Name</option>
}
</select>
<script type="text/javascript">
$('#DefaultText').change(function () {
var id = parseInt($('#DefaultText :selected').val());
var text = #Model.DefaultTexts.First( t => t.Id == id );
$('#CustomProductText').val(text);
});
</script>
I can't reach the var id. It's out of scope. I've also tryed it with a for loop and a if statement. But in the if statement I get the same error: out of scope.
The full story is this:
On my page I've a dropdown list. The items to select are short names for default text parts. Based on the id or name, I want to show the default text part in a textbox.
#CustomProductText is my textbox where the content should be placed (code not posted).
I've also tryed it with #: and statement but that did not work.
What am I doing wrong or maybe its not even possible what I'm trying to do.
As an alternative I've added a action to my controller to get the text form there. Below the code:
<script type="text/javascript">
$('#DefaultText').change(function () {
var id = parseInt($('#DefaultText :selected').val());
$.post("Categories/GetDefaultText", { Id: id }, function (data) {
alert(data);
});
//$('#CustomProductText').val(text);
});
</script>
controller code:
[HttpPost]
public ActionResult GetDefaultText(int id)
{
using( var context = new MyContext() )
{
var text = context.DefaultText.First( d => d.Id == id ).Text;
return this.Content( text );
}
}
This doesn't work. The action doesn't get hit in debug mode.
regards,
Daniel
The $.post that is not working for you, you should prefix the url with / sign and it will be hit as expected:
$.post("/Categories/GetDefaultText", { Id: id }, function (data) {
alert(data);
});
As for the razor solution, you can't use javascript variables in the razor code as it's not a scripting language. What razor does is simply rendering the strings (be it html or javascript or anything) into the page.
To do what you want you either need to request the server to pass the text to your page or render all the texts you have in the page and then access this rendered content in your javascript.

How to submit data from new page to older page

I got a Button on HTML form (let's call it Form_A) which when clicked, opens a new window (let's call it Form_B).
When user fill in some information in Form_B form and hit submit (or just a button), I need to send some processed information back to 'Form_A' and close 'Form_B.'
How can I accomplish it?
This is best illustrated with an example. In the code of Form_A:
<div id="target"></div> <button onclick="window.open('form_b.html'); return false">Open Form B</button>
function receive_data (value)
{
$("#target").text(value);
}
In the code of Form_B:
<input type="button" onclick="window.opener.receive_data('hello'); window.close();">
You can do it using localStorage, like:
<!--FormB-->
<input type="submit" onclick="processInfo();" />
and in your javascript code:
function processInfo(){
//process your information then
//let's day you have 2 variables as a result of your process
var info1 = "My Information 1";
var info2 = "My Information 2";
localStorage.setItem("info1", info1);
localStorage.setItem("info2", info2);
}
then in your code on the next page get your variables whenever you wanted like:
function getInfo1(){
return localStorage.getItem("info1");
}
function getInfo2(){
return localStorage.getItem("info2");
}
and the other solution for ancient browsers is using window.opener, you can use it in your close function like this:
<!--FormB-->
<input type="button" onclick="closeWindow();" />
javascript code:
function closeWindow(){
//process your information then
//let's day you have 2 variables as a result of your process
var info1 = "My Information 1";
var info2 = "My Information 2";
window.opener._info1 = info1;
window.opener._info2 = info2;
window.close();
}
then you can get them in the main page like:
function getInfo1(){
return window._info1;
}
function getInfo2(){
return window._info2;
}
BTW, we usually use _ when we want to simulate private variable, whereas they are not really private.

Categories