Prompt message box through custom menu on Google Sheets/App Script - if-statement

I have created a custom menu on Google Sheets with two options:
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu('Custom')
menu.addItem('Add new store', 'openForm')
menu.addItem('Update Database', 'replacebackenddatabase') }
When a user selects "Update Database" I would like a message box to appear and ask for confirmation with "Do you want to proceed" and Yes/No basis. IF the user selects "Yes", I would like the function 'replacebackenddatabase' to run. If not, I would just like the message box to close and nothing to happen.
How can I do this?
Thank you!

Check out Prompt boxes here.
function replacebackenddatabase() {
var ui = SpreadsheetApp.getUi();
var result = ui.prompt(
'Ask a question...',
ui.ButtonSet.OK_CANCEL);
// Get the response...
var button = result.getSelectedButton();
var text = result.getResponseText();
if (button == ui.Button.OK) {
//If they clicked OK do something with 'text' variable
} else if (button == ui.Button.CANCEL) {
// If they clicked Cancel.
} else if (button == ui.Button.CLOSE) {
// If they closed the prompt
}
}

function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu('Custom')
menu.addItem('Add new store', 'openForm')
menu.addItem('Update Database', 'checkResponse')
//.addSeparator()
//.addSubMenu(SpreadsheetApp.getUi().createMenu('Sub Menu')
//.addItem('One sub-menu item', 'subFunction1')
//.addItem('Another sub-menu item', 'subFunction2'))
.addToUi();
// do not set a variable to any chain of methods ending in .addToUi()
// after the Menu is created, the value of menu will be undefined
// the .addToUi() method does not return anything.
}
function checkResponse() {
var ui = SpreadsheetApp.getUi();
var response = ui.alert('Are you sure you want to proceed?', ui.ButtonSet.YES_NO);
if (response == ui.Button.YES) {
replacebackenddatabase();
} else {
Logger.log("The user wasn't sure.");
}
}

Related

Google Apps Script to Move Data from Form Entry to Log w/ UI Input Driven If Statements

I have limited experience with Google's Apps Script but have managed to piece together functional code, until now. Ultimately, what I'm trying to do is to create several Apps Script connected buttons: "New", "Save", "Download" and "Close". The most difficult for me, so far, has been the "Close" button. When the user clicks "Close," the system will first prompt the user to confirm that they wish to "Close" out the data form. After clicking "YES," Apps Script should check for an existing matching CAR Number (CAR numbers are unique) in the CAR Log and, if found, prompt the user with a dialog box to confirm that the data in the matching "CAR Log" row should be overwritten with the data in the "Entry Form". If the user clicks "YES", the data from "Entry Form" is copied and pasted to the row in the "Car Log" with the matching CAR No. If the user clicks "NO", cells containing data on the "Entry Form" will be cleared so that new data can be entered.
Sample Google Sheet w/ Entry Form and CAR Log:
https://docs.google.com/spreadsheets/d/1XyOldXz7FoZeAAeToWFbNXJlbHWlgwRkVlo9ifnUZy8/edit?usp=sharing
The intended flow is:
Here's the code I've written so far:
function FormClose() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var uiConfirmClose = SpreadsheetApp.getUi();
var responseConfirmClose = uiConfirmClose.alert('Close & Clear all Form Data', 'Are you sure?', uiConfirmClose.ButtonSet.YES_NO);
var destination = ss.getSheetByName('CAR Log');
var CurrentCARNo = ss.getRange("D5").getValue();
var UsedCARNosRange = SpreadsheetApp.openById("MYSPREADSHEETID").getSheetByName("CAR Log").getRange("A:A");
var UsedCARNos = UsedCARNosRange.getValues();
var uiOverwrite = SpreadsheetApp.getUi();
var responseOverwrite = uiOverwrite.alert('CAR No. '+CurrentCARNo+' Already Exists in Log', 'Would you like to overwrite the log with the data in this form?', uiOverwrite.ButtonSet.YES_NO);
// Confirm Close: YES
if (responseConfirmClose == uiConfirmClose.Button.YES) {
Logger.log('The user clicked "Yes."');
// Check for Existing CAR No.
for (var i in UsedCARNos){
// FOUND
if (UsedCARNos[i][0].match(CurrentCARNo)!=null){
// Overwrite?
if (responseOverwrite == uiOverwrite.Button.YES) {
Logger.log('The user clicked "Yes."');
ss.getRange('D5:E5').copyTo(destination.getRange(destination.getLastRow()+1,1,1,1),SpreadsheetApp.CopyPasteType.PASTE_VALUES,true);
}
else {
Logger.log('The user clicked "No" or the close button in the dialog\'s title bar.');
}
}
// NOT FOUND
else {
Logger.log('Data not found. Copy data to log.');
ss.getRange('D5:E5').copyTo(destination.getRange(destination.getLastRow()+1,1,1,1),SpreadsheetApp.CopyPasteType.PASTE_VALUES,true);
}
}
}
// Confirm Close: NO
if (responseConfirmClose == uiConfirmClose.Button.NO) {
Logger.log('The user clicked "No" or the close button in the dialog\'s title bar.');
return;
};
}
I will be very grateful for any direction.
If I understand your question clearly, your main goal is to stop your script from running when No button gets clicked on your first dialog prompt (responseConfirmClose) :
FINDINGS
On your script, responseOverwrite runs right away after the first
dialog responseConfirmClose prompt, even after clicking No, because it is being initialized & run at the beginning of your code.
SUGGESTION
On your logic, I would suggest to place your
responseOverwrite variable inside of your first if conditional statement (if
YES button was selected on the responseConfirmClose dialog) as seen on the tweaked script below:
[UPDATED]
Tweaked Script
function runClose() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var uiConfirmClose = SpreadsheetApp.getUi();
var destination = ss.getSheetByName('CAR Log');
var carLogData = destination.getDataRange().getDisplayValues();
var uiOverwrite = SpreadsheetApp.getUi();
var entryFormData = ss.getSheetByName('Entry Form').getDataRange().getDisplayValues();
var responseConfirmClose = uiConfirmClose.alert('Close & Clear all Form Data', 'Are you sure?', uiConfirmClose.ButtonSet.YES_NO);
if (responseConfirmClose == uiConfirmClose.Button.YES) {
Logger.log('The user clicked "Yes."');
for(var i in carLogData){
var row = parseInt(i) +1;
if(carLogData[i][0] == entryFormData[4][3]){
Logger.log("Found a match for \""+"CAR NO. "+entryFormData[4][3]+"\" in row #"+row+ " on the \"CAR Log\" sheet");
var responseOverwrite = uiOverwrite.alert('CAR No. '+entryFormData[4][3]+' Already Exists in Log', 'Would you like to overwrite the log with the data in this form?', uiOverwrite.ButtonSet.YES_NO);
// Overwrite?
if (responseOverwrite == uiOverwrite.Button.YES) {
Logger.log('The user clicked "Yes."');
var copyData = [[entryFormData[4][3], //CAR No
entryFormData[8][3], //Date Opened
entryFormData[8][9], //Originator
entryFormData[8][15], //NCP # (if applicable)
entryFormData[10][2], //Action Type
entryFormData[10][10], //Source
entryFormData[12][1], //Issue to
entryFormData[12][10], //QMS Reference & Clause
entryFormData[17][0], //Description of Nonconformance / Opportunity for Improvement
entryFormData[19][0], //Root Cause
entryFormData[21][0], //Corrective Action
entryFormData[23][5], //Corrective Action Discussed with and Agreed Upon b
entryFormData[23][15], //Corrective Action Planned Completion Date
entryFormData[28][3], //Approved by
entryFormData[28][11], //Date Approve
entryFormData[33][3], //Followed up by
entryFormData[33][11], //Date of Follow up
entryFormData[35][5], //Corrective Action Effective?
entryFormData[38][0], //Evidence
entryFormData[40][3], //Close Out by
entryFormData[40][11]]]; //Date Closed
destination.getRange("A"+row+":U"+row).setValues(copyData);
}
else {
Logger.log('The user clicked "No" or the close button in the dialog\'s title bar.');
}
}
}
}else{
Logger.log('The user clicked "No" or the close button in the dialog\'s title bar.');
}
//Code here to empty the fields on "Entry Form" e.q.:
ss.getRange("D9:E9").clearContent();
ss.getRange("J9:K9").clearContent();
ss.getRange("P9:Q9").clearContent();
ss.getRange("C11:I11").clearContent();
ss.getRange("K11:O11").clearContent();
ss.getRange("B13:F13").clearContent();
ss.getRange("K13:P14").clearContent();
//continue here for the rest of the fields
}
Sample Demonstration:
Sample Entry Form with data
Sample CAR Log with data
After clicking YES on the dialog prompt, since there's already a matching CAR No on row #4 on the CAR Log, it will then be overridden with new data:
Entry Form will be cleared after that Or it will be cleared when No is clicked from the prompt:

Mac OS Menu Bar Add Right Click To Show Another View

I have a Mac OS menu bar which when clicked opens a popup view, I want to add upon this by incorporating a quit function. When the icon is right clicked another popup menu will be presented.
I am stuck on trying to get the another popup menu to present, I am able to detect a right click on the icon but I unable to show the popup menu view.
Preview when i left click
POPUP MENU VIEW FOR BOTH POPUPS
#main
struct MenuNoteApp: App {
let persistenceController = PersistenceController.shared
#Environment(\.scenePhase) var scenePhase
#NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
.onChange(of: scenePhase) { _ in
persistenceController.save()
}
}
}
extension NSStatusBarButton {
open override func rightMouseUp(with event: NSEvent) {
// Detect right click and prints right click
print("right click")
}
}
class AppDelegate: NSObject,NSApplicationDelegate{
let persistenceController = PersistenceController.shared
// Status Item
var statusItem: NSStatusItem?
// PopOver
var popOver = NSPopover()
var popOverOptions = NSPopover()
func applicationDidFinishLaunching( _ notification: Notification){
// Menu View
let menuView = MenuView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
// Creating PopOver
popOver.behavior = .transient
popOver.animates = true
// Setting Empty View Controller And Setting View as SwiftUI View with help of hosting controller
popOver.contentViewController = NSViewController()
popOver.contentViewController?.view = NSHostingView(rootView: menuView)
popOverOptions.contentViewController = NSViewController()
popOverOptions.contentViewController?.view = NSHostingView(rootView: OptionsView())
// Creating Status Bar Button
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
// Check if status button is available
if let MenuButton = statusItem?.button {
MenuButton.image = NSImage(systemSymbolName: "note", accessibilityDescription: nil)
MenuButton.action = #selector(MenuButtonToggle)
}
}
// Button Action
#objc func MenuButtonToggle(sender: AnyObject){
if popOver.isShown{
popOver.performClose(sender)
}
else if popOverOptions.isShown{
popOverOptions.performClose(sender)
}
else{
//Present Pop Over
if let menuButton = statusItem?.button{
// popOver is the main view while popOverOpitions is the second view
self.popOver.show(relativeTo: menuButton.bounds, of: menuButton, preferredEdge: NSRectEdge.minY)
}
}
}
}
Instead of overriding functions in NSStatusBarButton, you should detect the left and right click behavior within the MenuButtonToggle function.
First, you should register the right-click action, inside your the if-let inside applicationDidFinishLaunching, like so:
if let menuButton = statusItem?.button {
menuButton.image = NSImage(systemSymbolName: "note", accessibilityDescription: nil)
menuButton.action = #selector(MenuButtonToggle)
menuButton.sendAction(on: [.leftMouseUp, .rightMouseUp]) // register action on right click too
}
Then, you want to update the MenuButtonToggle function to act differently depending on left and right clicks.
#objc func menuButtonToggle(sender: AnyObject){
// ... previous logic here
if let event = NSApp.currentEvent {
if event.type == NSEventType.rightMouseUp {
// Right button click
self.popOver.show(...)
} else {
// Left button click
self.popOverOptions.show(...)
}
}
}

APEX row selector

I'm displaying my results on an interactive grid. I'd like to be able to select multiple rows and click an edit button that will open up an “edit” form. I am having a number of problems:
Retrieve the car IDs of the rows selected. (I am having trouble accessing column values, I can access item values)
Pass a collection or array of ids to the edit form.
Save the collection.
Added more code in answer box by accident...……..
I made some progress but I am a little stuck. I followed the oracle blog and it was vey helpful. So on the attribute of the region I added the following code:
function (config) {
var $ = apex.jQuery,
toolbarData = $.apex.interactiveGrid.copyDefaultToolbar(),
toolbarGroup = toolbarData.toolbarFind("actions3");
toolbarGroup.controls.push(
{
type: "BUTTON",
action: "updateCar",
label: "Edit Selected Cars",
hot: true,
});
config.toolbarData = toolbarData;
config.initActions = function (actions)
{
// Defining the action for activate button
actions.add(
{
name: "updateCar",
label: "Edit Selected Cars",
action: updateCar
});
}
function updateCar(event, focusElement)
{
var i, records, model, record,
view = apex.region("ig_car").widget().interactiveGrid("getCurrentView");
var vid = "";
model = view.model;
records = view.getSelectedRecords();
if (records.length > 0)
{
for (i = 0; i < records.length; i++)
{
record = records[i];
//alert("Under Development " + record[1]);
vid = vid + record[1] + "||";
apex.item("P18_CAR").setValue(vid);// This is not needed just to test
//the output
// call next page
// pass array as sql source or directly on page
}
}
}
return config;
}
This works. A button is displayed and when selected it gets the values from the interactive grid. The part I am stuck is how to call the next page and pass the multiple values (2 columns) to the page to be displayed and in a query to do an update.
Thank you if you can help me accomplish this!

Persistent AdminLTE sidebar state

I've inherited some work on an AdminLTE sidebar on a Django website. The page in question uses an "extends" block to load AdminLTE's index.html page right off the bat. Links on our treeview sidebar cause the entire page to reload, including the sidebar, so the state of any expanded treeview menus is lost whenever someone clicks a link.
I'm guessing there's some well-known way of making the sidebar keep its treeview menus open, but I've yet to find it. There are some working examples on the AdminLTE site, but I can't figure out how they work.
Can someone point me to the right piece of documentation to help me make my sidebar persistent across page loads?
Treeview css class works in an unordered list so any child links only show up when the parent list is clicked. An example of this is if you have "home" and then "About" "About-Locations". When you click About it is a tree-view class and on the sidebar it will show locations underneath it. When you click on home the locations sidebar link will not be displayed as this is how the css is written for the list.
The code can be found in the "AdminLTE.css" file.
I'm not working on django, I work on a MVC Razor app.
For the same problem, I use this solution :
I store the link clicked on the menu (ajax send to the server and session storage, but you can use cookie or what you want).
The link clicked is inserted in the java script below :
$(" ul.treeview-menu > li > a").on("click", function ()
{
if (this.href == "#")
return;
$.ajax({
type: "POST",
url: '/Outils/SetActiveMenu',
data: { url: this.href },
dataType: "json"
});
})
$(document).ready(function () {
var v = "#Html.Raw(Session["ActiveMenu"] == null?"": Session["ActiveMenu"].ToString())";
if(v == "") return;
var a = $('a[href="' + v + '"]');
openParentMenu(a);
a.css("background-color", "#E3E6E5");
});
function openParentMenu(item)
{
var parent = item.parent().closest("li.treeview");
if (parent.length != 0) {
openParentMenu(parent);
parent[0].children.item("a").click();
}
}
Here is the code for reference.
/* Tree()
* ======
* Converts the sidebar into a multilevel
* tree view menu.
*
* #type Function
* #Usage: $.AdminLTE.tree('.sidebar')
*/
$.AdminLTE.tree = function (menu) {
var _this = this;
var animationSpeed = $.AdminLTE.options.animationSpeed;
$(menu).on('click', 'li a', function (e) {
//Get the clicked link and the next element
var $this = $(this);
var checkElement = $this.next();
//Check if the next element is a menu and is visible
if ((checkElement.is('.treeview-menu')) && (checkElement.is(':visible')) && (!$('body').hasClass('sidebar-collapse'))) {
//Close the menu
checkElement.slideUp(animationSpeed, function () {
checkElement.removeClass('menu-open');
//Fix the layout in case the sidebar stretches over the height of the window
//_this.layout.fix();
});
checkElement.parent("li").removeClass("active");
}
//If the menu is not visible
else if ((checkElement.is('.treeview-menu')) && (!checkElement.is(':visible'))) {
//Get the parent menu
var parent = $this.parents('ul').first();
//Close all open menus within the parent
var ul = parent.find('ul:visible').slideUp(animationSpeed);
//Remove the menu-open class from the parent
ul.removeClass('menu-open');
//Get the parent li
var parent_li = $this.parent("li");
//Open the target menu and add the menu-open class
checkElement.slideDown(animationSpeed, function () {
//Add the class active to the parent li
checkElement.addClass('menu-open');
parent.find('li.active').removeClass('active');
parent_li.addClass('active');
//Fix the layout in case the sidebar stretches over the height of the window
_this.layout.fix();
});
}
//if this isn't a link, prevent the page from being redirected
if (checkElement.is('.treeview-menu')) {
e.preventDefault();
}
});
};
I have used the inbuilt functionality mentioned by #MDT and have created a function:
function toggleCollapsibleList(){
//Get the clicked link and the next element
var $this = $('#parent-list-item-id');
var checkElement = $('#an-id-for-collapsible-li-with-treeview-class');
//Check if the next element is a menu and is visible
if ((checkElement.is('.treeview-menu')) && (checkElement.is(':visible')) && (!$('body').hasClass('sidebar-collapse'))) {
//Close the menu
checkElement.slideUp(500, function () {
checkElement.removeClass('menu-open');
//Fix the layout in case the sidebar stretches over the height of the window
//_this.layout.fix();
});
checkElement.parent("li").removeClass("active");
}
//If the menu is not visible
else if ((checkElement.is('.treeview-menu')) && (!checkElement.is(':visible'))) {
//Get the parent menu
var parent = $this.parents('ul').first();
//Close all open menus within the parent
var ul = parent.find('ul:visible').slideUp(500);
//Remove the menu-open class from the parent
ul.removeClass('menu-open');
//Get the parent li
var parent_li = $this.parent("li");
//Open the target menu and add the menu-open class
checkElement.slideDown(500, function () {
//Add the class active to the parent li
checkElement.addClass('menu-open');
parent.find('li.active').removeClass('active');
parent_li.addClass('active');
//Fix the layout in case the sidebar stretches over the height of the window
});
}}
This worked for me :)

Adding a custom Authors category to Gtk::AboutDialog class

I was wondering if there was a way to set a custom Authors category in a Gtk::AboutDialog class via gtkmm. I know there are the following methods:
set_artists()
set_authors()
set_documenters()
set_translator_credits()
But I wanted to add a custom category. Right now I have a program that accepts a bunch of plugins, so on startup when it scans for plugins I would like to populate a "Plugins" page on the about screen once you click credits that shows all of the plugin authors' names (removing duplicates of course). The logic is already there, but it looks quite odd adding them to the artists or documenters categories where they certainly do not belong.
Is there an easy way to add a new category besides rolling my own?
Nice question! In GTK 3, this is fairly easy. You have to do some manipulation of the About dialog's internal children, which may change in future releases, so be warned!
I've written a quick-n-dirty example in Vala that does what you want. That was faster for me because I almost never use Gtkmm. It shouldn't be too hard to translate though.
using Gtk;
int main(string[] args)
{
Gtk.init(ref args);
var dialog = new AboutDialog();
// Fetch internal children, using trickery
var box = dialog.get_child() as Box;
Box? box2 = null;
ButtonBox? buttons = null;
Notebook? notebook = null;
box.forall( (child) => {
if(child.name == "GtkBox")
box2 = child as Box;
else if(child.name == "GtkButtonBox")
buttons = child as ButtonBox;
});
box2.forall( (child) => {
if(child.name == "GtkNotebook")
notebook = child as Notebook;
});
// Add a new page to the notebook (put whatever widgets you want in it)
var plugin_page_index = notebook.append_page(new Label("Plugin 1\nPlugin 2"),
new Label("Plugins"));
// Add a button that toggles whether the page is visible
var button = new ToggleButton.with_label("Plugins");
button.clicked.connect( (button) => {
notebook.page = (button as ToggleButton).active? plugin_page_index : 0;
});
buttons.pack_start(button);
buttons.set_child_secondary(button, true);
// Set some other parameters
dialog.program_name = "Test Program";
dialog.logo_icon_name = Gtk.Stock.ABOUT;
dialog.version = "0.1";
dialog.authors = { "Author 1", "Author 2" };
dialog.show_all(); // otherwise the new widgets are invisible
dialog.run();
return 0;
}
In GTK 2, this is much more difficult, although probably not impossible. You have to connect to the Credits button's clicked signal, with a handler that runs after the normal handler, and then get a list of toplevel windows and look for the new window that opens. Then you can add another page to that window's GtkNotebook.
I would suggest doing it a little differently: add a Plugins button to the action area which opens its own window. Then you don't have to go messing around with internal children. Here's another Vala sample:
using Gtk;
class PluginsAboutDialog : AboutDialog {
private Dialog _plugins_window;
private Widget _plugins_widget;
public Widget plugins_widget { get {
return _plugins_widget;
}
set {
var content_area = _plugins_window.get_content_area() as VBox;
if(_plugins_widget != null)
content_area.remove(_plugins_widget);
_plugins_widget = value;
content_area.pack_start(value);
}}
public PluginsAboutDialog() {
_plugins_window = new Dialog();
_plugins_window.title = "Plugins";
_plugins_window.add_buttons(Stock.CLOSE, ResponseType.CLOSE, null);
_plugins_window.response.connect((widget, response) => { widget.hide(); });
var buttons = get_action_area() as HButtonBox;
// Add a button that opens a plugins window
var button = new Button.with_label("Plugins");
button.clicked.connect( (button) => {
_plugins_window.show_all();
_plugins_window.run();
});
button.show();
buttons.pack_start(button);
buttons.set_child_secondary(button, true);
}
public static int main(string[] args) {
Gtk.init(ref args);
var dialog = new PluginsAboutDialog();
// Make a widget for the plugins window
var can_be_any_widget = new Label("Plugin 1\nPlugin 2");
dialog.plugins_widget = can_be_any_widget;
// Set some other parameters
dialog.program_name = "Test Program";
dialog.logo_icon_name = Gtk.Stock.ABOUT;
dialog.version = "0.1";
dialog.authors = { "Author 1", "Author 2" };
dialog.run();
return 0;
}
}