How to click UI element using JXA - macos

Please tell me how do I click in point coordinates in application window?
I trying to UI automate my application on OSX 10.10 using JXA technology.
In documentation I found that it's possible using click at event. By I'am beginner of JXA and cant find how make a call.
Code snippet which I tried in Script Editor:
var app = Application('my_application_path')
app.window.click.at('{100,100}')
Thank you for help

You can interact with an application's user interface using the System Events application. Here is a script that clicks at certain coordinates in Safari:
// Activate Safari, so you will be able to click like a user
Application("Safari").activate()
// Access the Safari process of System Events
var SystemEvents = Application("System Events")
var Safari = SystemEvents.processes["Safari"]
// Call the click command, sending an array of coordinates [x, y]
Safari.click({ at: [300, 100] })
If you want to click a specific button (or other element of the user interface), it is more appropriate to click that specific element. For example:
// Click the third button of Safari's first window to minimize it
Safari.windows[0].buttons[2].click()
To learn what user interface elements can be interacted with and how, check out the Processes Suite in System Events' scripting dictionary. To open the dictionary, in Script Editor's menu bar, choose Window > Library, then select System Events in the Library window.

See https://github.com/dtinth/JXA-Cookbook/wiki/System-Events#clicking-menu-items
For example:
var fileMenu = proc.menuBars[0].menuBarItems.byName('File');

Below is an example of a portion of a script I wrote that automates creating mailboxes (aka folders) in Mail. I ended up using the UI file menus and click because using make() in the Mail DOM had issues for me. Hope it helps someone.
(() => {}
//this is part of a script that automates creating mailboxes (ie folders) in Apple Mail
//I used the file menu UI because when I tried the Mail library and make() method
//there was strange behavior when trying to interact with the new mailbox.
//However, when creating the new mailboxes thru the file menu, all seems to work fine
const Mail = Application('Mail');
const strId = Mail.accounts.byName('Exchange').id();
const exchange = Mail.accounts.byId(strId);
const activeClientFolder = exchange.mailboxes.byName('ActiveClient');
const SysEvents = Application('System Events');
const mail = SysEvents.processes.byName('Mail');
//next two lines insure Mail will be open and in front
mail.windows[0].actions.byName('AXRaise').perform();
mail.frontmost = true;
const mailboxMenu = mail.menuBars[0].menus.byName('Mailbox');
//below shows EXAMPLES of using .click(), keystroke(), and keyCode()
let newFolder = function (parentFolder, newFolderName, addTrailingDelay = true) {
//next line will select the parent mailbox (aka folder) where the new mailbox will be inserted
Mail.messageViewers[0].selectedMailboxes = parentFolder;
mailboxMenu.click();
delay(.2);
mailboxMenu.menuItems.byName('New Mailbox…').click();
delay(.2);
SysEvents.keystroke(newFolderName);
SysEvents.keyCode(36);
//delay is needed when creating multiple mailboxes with a loop
if (addTrailingDelay == true){
delay(1);
}
}
//now the payoff
const count = newActiveClients.length;
for(let i=0;i<count;i++){
/* Client Root Mailbox */
newFolder(activeClientFolder, newActiveClients[i], true);
/* Client Email Folders */
newFolder(activeClientFolder.mailboxes.byName(newActiveClients[i]), 'Client', true);
newFolder(activeClientFolder.mailboxes.byName(newActiveClients[i]).mailboxes.byName('Client'), 'Client_FYI_Sent');
newFolder(activeClientFolder.mailboxes.byName(newActiveClients[i]).mailboxes.byName('Client'), 'Client_FYI_Inbox');
newFolder(activeClientFolder.mailboxes.byName(newActiveClients[i]).mailboxes.byName('Client'), 'Client_FYI_Client_To');
newFolder(activeClientFolder.mailboxes.byName(newActiveClients[i]).mailboxes.byName('Client'), 'Client_From', false);
}
})()

Related

Showing more than one line of the notification description in the notification tray using an extension

I am currently designing an extension to make the notifications in the notification section of the calendar expendable. The goal is to make the noficiation expand like the initial notification on the desktop does. I have changed the type of notification added to the noficiation tray to class NotificationBanner from class NotificationMessage. I am currently using a work-around to make this work, this is what my expand function looks like:
expand(animate) {
this.expanded = true;
this._actionBin.visible = this._actionBin.get_n_children() > 0;
if (this._bodyStack.get_n_children() < 2) {
this._expandedLabel = new MessageList.URLHighlighter(this._bodyText,
true, this._useBodyMarkup);
this.setExpandedBody(this._expandedLabel);
}
if (animate) {
if (!this.clickedByButton && !this.forceExpansion) {
// This is the usual way notifications are expanded, using the layout manager
this._bodyStack.ease_property('#layout.expansion', 1, {
progress_mode: Clutter.AnimationMode.EASE_OUT_QUAD,
duration: MessageTray.ANIMATION_TIME,
});
}
else if (this.forceExpansion || this.clickedByButton) {
// When auto expanding or clicked by button, change height of body
oldHeight = this.bodyLabel.get_height();
const lines = Math.ceil(this._bodyText.length / 54);
this.bodyLabel.set_height(lines * this.bodyLabel.get_height());
}
this._actionBin.scale_y = 0;
this._actionBin.ease({
scale_y: 1,
duration: MessageTray.ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
} else {
this._bodyStack.layout_manager.expansion = 1;
this._actionBin.scale_y = 1;
}
this.emit('expanded');
}
As you can see, I have 2 options for this extension: Force expand all notifications or make the user use a button to expand. The current solution is not elegant, it simply changes the height of the notification label which manages the body. Furhermore, the notification body still shows the three dots, implying that the body is still not expanded. I believe this to be an issue with the layout manager, since the proper way to expand is to set message._bodyStack.layout_manager.expansion to 1. That does not work in the case of expanding a message in the notification tray. Is anyone familiar with the layout manager or can help me find a different solution? Here is an image of what my current solution looks like:
Image of an automatically expanded notification in the notification tray due to the extension (note the three dots at the end of the first line being still there)
Okay I have found a solution, it is not related to the layout manager. The value of the message message.bodyLabel.clutter_text.ellipsize is set to 3, which is the main cause of the dots appearing on the notification. Setting this value to 0 solves this problem. I would have still loved to find a more elegant approach to displaying the body, but this will do.

nativescript - change variable on exitEvent

I need to be able to change some variable's value, when app is closed.
I'm using exitEvent described here:
https://docs.nativescript.org/core-concepts/application-lifecycle
Also, i'm using local-storage plugin that works similar to javasctip's localstorage.
https://github.com/NathanaelA/nativescript-localstorage
My simple code looks like this:
var Observable = require("data/observable").Observable;
require("nativescript-dom");
var LS = require("nativescript-localstorage");
const application = require("tns-core-modules/application");
var page = require("ui/page");
exports.load = function (args) {
var page = args.object;
var model = new Observable();
var frame = require('ui/frame');
var ff = frame.getFrameById("dashboard");
var topmost = frame.topmost();
// This is exit event
application.on(application.exitEvent, (args) => {
LS.setItem('XX','111')
});
// What i will exit app, and run app again this will newer become 111,
it's null all the time
console.log(LS.getItem('XX'));
}
So, my question is - is possible to set any flag on app exit - it do not have to be localstorage (i've tested global variables to), to detect if exit was made, and based on this i can make a decisions that will help me ?
One of scenarios may be - i'm holding some flag in Localstorage that is TURE is user tapped "rememebr me" on the login screen.
So on exit i can check if he want's to be rememebered, if not i want to send user to login page and not dashboard when app is lauching....
Thank you.
EDIT:
I've tried applications-settings too, it will not work.
application.on(application.exitEvent, (args) => {
applicationSettings.setString("Name", "John Doe");
});
console.log(applicationSettings.getString("Name")); // not working
I suspect it's the issue with the nativescript-localstorage plugin. It writes the changes to file after a 250ms delay. At exit event you will give you very limited amount of time before your app is completely killed by system.
May be the Author had a reason for setting up this delay but I think its too much of time at least in this particular scenario so the changes are never written to file. You may raise a issue at the plugin's Github repo.
If you are looking for an immediate workaround, copy localstorage.js to your app and export internalSaveData from the file, so you could directly call it right after you finish setting your values.

Make Close Button Session Persistent

I'm using Foundation 6 to make my websites frontend. Above the header of my website I want to show a promotionbar, which should be closeable / dismisable.
Therefore I'm using a close-button, coming out of the box with Foundation, to hide / dismis the promotionbar.
Now I want, that if the user closes / hides the promotionbar, that (for the rest of the session) the bar will not be shown anymore.
As the closebutton just "hides" the element, the bar will be shown again if I go to another subpage (for example, coming from a blogpost -> going back to the frontpage).
Is there a simple way to keep the bar hidden, after I've clicked on the closebutton for the rest of the session?
If the user stops by another day, the bar should be shown again.
https://foundation.zurb.com/sites/docs/close-button.html
Thank you in advance!
Thank you #daniel-ruf - I've solved it the following way if anybody is looking for a solution
Give the promotionbar an unique ID (id="promotionbar")
Add a class to the promotionbar (class="hider")
Add display:none, to the class via CSS File (.hider {display:none;})
Add onClick action the closebutton
button class="close-button" data-close="" onclick="promotionhide()">×
in App.js add
var cacheKeyNoPromo = 'promotionhide';
var nopromo = readFromCache(cacheKeyNoPromo);
if (!nopromo) {
document.getElementById("promotionbar").classList.remove("hider");
}
var promotionhide = function() {
writeToCache(cacheKeyNoPromo, true, 24 * 60 * 60 * 1000 /* 1D */);
document.getElementById("promotionbar").classList.add("hider");
};
works for me.

InDesign CC 2017 ExtendScript - Can't overwrite text in TextArea

At this point, I'm sure this is something simple that I'm missing but I can't for the life of me figure it out.
Working on an InDesign script that takes text passed into the script and writes it into the currently selected text area.
insertButton.onClick = function(){
var postIndex = postList.selection.index;
var postContent = posts[postIndex].content.rendered;
$.writeln(app.selection[0].parentStory.contents);
app.selection[0].parentStory.contents = postContent;
$.writeln(app.selection[0].parentStory.contents);
myWindow.close();
}
I've confirmed that the function is getting called correctly, that postContent exists and is what I expect it to be and that the first writeln call dumps out the current value of the TextArea. The second $.writeln never fires, so I know the error is on
app.selection[0].parentStory.contents = postContent;
Is there an updated way to set TextArea contents that I haven't found in documentation?
Thanks in advance!
I think your problem is that your window is modal thus preventing any interaction with inDesign objects.
You have to quit the dialog first in order to modify objects:
var w = new Window('dialog');
var btn = w.add('button');
btn.onClick = function() {
w.close(1);
}
if ( w.show()==1){
//"InDesign is no longer in modal state. So you can modify objects…")
}
...postContent exists and is what I expect it to be...
Indesign expects a string here --> is it what you expect as well?
What is an input selection? text? textFrame?
You could
alert(postContent.construction.name)
to ensure what you've got
Jarek
When debugging was enabled in ExtendScript Toolkit, I was able to find the error being thrown:
"cannot handle the request because a modal dialog or alert is active"
This was referring to the dialog I opened when I initiated the script.
Delaying the text insertion until the dialog has actually been closed fixed the issue.
insertButton.onClick = function(){
var postIndex = postList.selection.index;
postContent = posts[postIndex].content.rendered;
postContent = sanitizePostContent(postContent);
// The 1 here is the result that tells our code down below that
// the window has been closed by clicking the 'Insert' button
myWindow.close(1);
}
var result = myWindow.show();
// If the window has been closed by the insert button, insert the content
if (result == 1) {
app.selection[0].parentStory.contents = postContent;
}

What does window.close() do to the contents of said window?

I've got the below code:
// Initiate the font choosing lists.
var headerListWindow = Titanium.UI.createWindow();
var headerFontListData = [{title:"Row 1"},{title:"Row 2"},{title:"Row 3"}];
var headerFontList = Titanium.UI.createTableView({data:headerFontListData});
chooseHeader.addEventListener('click', function(e) {
headerListWindow.left=win.width;
var a = Titanium.UI.createAnimation();
a.left = 0;
a.duration = 1000;
headerListWindow.add(headerFontList);
headerListWindow.open(a);
});
headerFontList.addEventListener('click', function(e) {
var a = Titanium.UI.createAnimation();
a.left = win.width;
a.duration = 1000;
headerListWindow.close(a);
});
Which slides a window in nicely. When I open headerListWindow for the first time, everything appears, the list works, and the window slides in nicely. When I close() the window, however, all the list elements' names revert to "R.". Then, upon reopening the window, the list elements are completely gone. Why is this?
.close() kills your window. it's passed on. this window is no more. It has ceased to be. It's expired and gone to meet it's maker. It's a stiff. Bereft of life. It rests in peace... etc.
when your window is closed, then your are destroying it. You might like to try .hide() instead which will not null the window, but just not show it on the screen.
blissapp, I am using the Alloy framework, and if I do a window.close() it does not kill the window. It closes the window, but if I log the window after it's still defined, and its definitely not dead.
I am not sure wether this applies to developing without alloy or not, but the object remains. You do have to re-open the window in order to render it, but it is my experience that the window is not deceased as you put it. I am definitely not destroying it.
An example:
welcome.addEventListener('open', function() {
console.log("welcome is open");
win1.close();
Ti.API.debug(win1);
});
Outputs:
[INFO] : welcome is open
[DEBUG] : [object welcome]
Ofcause Nulling the variable is a solution, but why doesn't .close() work as it is suposed to according the documentation using Alloy? Am I missing something when working in Alloy?

Resources