I am developing a WebExtension, and one feature is to have a context menu item on editable fields, which when selected opens a confirmation window and then pastes a value into the editable field.
At the moment I have the context menu item, which opens the window, but getting some text inserted into the editable field is proving tricky. The closest I have managed so far is:
let code = 'setTimeout(function() {document.designMode = "on";document.execCommand("insertText", false, "apple");document.designMode = "off";}, 1000);';
browser.tabs.executeScript(parent_tab_id, {"code": code});
window.close()
The whole designMode thing seems it bit weird, and the code does not work very reliably. Is there a better way to do this? The root of the problem, is that I don't see any way to find the editable field that was clicked on.
I would do it like this:
let code = 'document.activeElement.value = "apple";';
browser.tabs.executeScript(parent_tab_id, {"code": code});
By the way, window.close inside a background script is browser.tabs.remove(currentTabId). You can get the current tab id by querying the tabs API (example 2): https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/query#examples
Related
Scenario
I've been handed a custom work queue tool that runs in a browser. The interactions with the site are very repetitive and I'd like to see if I can trim down some of the interactions. Right now, I'm focused on using Chrome's DevTools console to try this.
I've used Inspect to gather the js paths of all of the form controls I want to change. These are all drop-down boxes.
There is one drop-down box, control3, who's contents is populated dependent on selections in two other drop down boxes, control1 and control2. If control1 or control2 are blank, control3 is also blank.
Problem
If I fire something like this in the console to set the values of control1 and control2:
document.querySelector("#control1").value = 'My Team'
document.querySelector("#control2").value = 'In Progress'
... control3's drop down is not populated.
My base understanding is that this .value change skirts around the functions/listeners that are attached to these controls.
I can see that control1 has an attribute ng-change="setServiceGroupDirty()", and if I follow that in the Sources tab, it's a function in a .js file that just changes a bool.
$scope.setServiceGroupDirty = function(){
$scope.activeTab.isServiceGroupDirty = true ;
} ;
If I just run setServiceGroupDirty() in the console, I get: setServiceGroupDirty is not defined
Question
Is there a way to use the DevTools console to simulate the user using a mouse/keyboard to select options in control1 and control2 so that the appropriate listener/functions trigger to populate control3?
Background:
I am using the setSelection() method in an google marketplace add-on for google documents.
The text is selected as expected when clicking the relevant button on the add-on's sidebar. However, this selection is not active - i.e. the selected text is highlighted in light grey instead of light blue (see example below).
Now:
What I need:
This is because the last active portion of the browser tab is the sidebar (after clicking the button), not the actual document.
Question:
Is there a way to make the button click select the text and keep the document the active portion?
Goal:
The whole purpose of this selection is to copy the selected text by Ctrl + C on the keyboard, which is not possible when the selection is not active.
Right now the user needs to use the right-click on the mouse and select Copy from the menu...
On your client-side code use google.script.host.editor.focus() to make the selection on the editor an active selection.
function showSidebar(){
var ui = DocumentApp.getUi();
var html = '<div>Hello world!</div>'
html += '<div><button onclick="google.script.host.editor.focus()">Click me!</button></div>';
ui.showSidebar(HtmlService.createHtmlOutput(html));
}
From https://developers.google.com/apps-script/guides/html/communication#moving_browser_focus_in
Moving browser focus in Google Workspace
To switch focus in the user's browser from a dialog or sidebar back to the Google Docs, Sheets, or Forms editor, simply call the method google.script.host.editor.focus(). This method is particularly useful in combination with the Document service methods Document.setCursor(position) and Document.setSelection(range).
Solution
Since your goal is to copy the selected text I would like to propose an alternative solution:
The task now will include the copying feature directly without additional user input than the button click. And it will be developed this way:
Text Selection
Get your selected text with your Apps Script function triggered by a button click:
//... Your custom logic to get the text selection
var text-to-copy = doc.setSelection(x)
.getSelection()
.getRangeElements()
.map(re => re.getElement()
.asText()
.getText())
.join(" ");
return text-to-copy;
We cannot access the user clipboard from Apps Script but with a successHandler we can pass the text-to-copy variable to the Client-Side interface.
Handling the Server-Side return value
In the following way we can pass the text back to the HTML sidebar.
<!-- HTML Interface Index.html -->
<button onclick="google.script.run.withSuccessHandler(copyToClipboard).setSelection()">
Click Here
</button>
<script>
function copyToClipboard(text) {
const elem = document.createElement('textarea');
elem.value = text;
document.body.appendChild(elem);
elem.select();
document.execCommand('copy');
document.body.removeChild(elem);
}
</script>
We can now leverage the native client-side functionality to copy that text directly to the user clipboard without having her/him to Ctrl+C once your script finished.
A good practice in this case would be to provide a visual feedback to your user once the copy procedure has finished.
Reference
withSuccessHandler(function)
I'm trying to interact with the default Mail app using Javascript for Automation (JXA). I want to click on the first email in the list so that it opens in its own window. but clicking does nothing. The solution suggested here is what I tried and it does not work.
My code:
var se = Application("System Events");
var mail = se.applicationProcesses.byName("Mail");
mail.windows[0]
.splitterGroups[0]
.splitterGroups[0]
.groups[0]
.scrollAreas[0]
.tables[0]
.rows[0].click()
If I remove the click() and use .select() it will select the email from the menu, but clicking on the email list item itself just returns null in the repl. I tried clicking on each of the inner uiElements but this doesn't work either.
Here's an AppleScript that shows how to open a message in a new window.
You should be able to readily translate that to JXA.
https://stackoverflow.com/a/390549/915019
It is better to use the app's object model than UI scripting.
I am trying to fill forms on certain webpages. I am having success with most
pages however certain ones are giving me trouble.
Some pages have a format where the form is not initially visible on the page itself, instead you need to click a button which then opens a (what seems to be JavaScript) pop up within the page which then needs to be filled.
I have looked around and have seen examples which address how to fill forms which get created in a new pop up window and which get created in alert windows. However I can not seem to find any examples which can solve this particular case.
This is the code I have so far.
driver = webdriver.Chrome()
driver.implicitly_wait(10)
mouse = webdriver.ActionChains(driver)
driver.get(url)
driver.maximize_window()
value = 'fill form'
span_xpath = '//span[contains(text(), "' + value + '")]'
span_element = driver.find_element_by_xpath(span_xpath)
mouse.move_to_element(span_element).click().perform()
time.sleep(5)
Everything works till here, the fill form button gets clicked which opens the form in the browser page.
But when the following is execute an error always arises: "no such element: unable to locate element"
n =driver.find_element_by_id('name')
n.send_keys('john smith')
I have tried to locate the element by, name, id, xpath and so on but no matter what try, it can not locate any of the form elements.
I would greatly appreciate any help on this matter.
First switch using driver.switch_to_frame(0) (parent iFrame)
then switch using driver.switch_to_frame(1) (chiled iFrame)
and then find the element.
Note: instead of using index you can identify the frame using id, name, xpath etc. like any other Web Element and pass that element to switch_to_frame as an argument. This gives more consistency.
Apologies for the long question, but I've tried a lot of things and done some research and havent found much of a solution. I have a content editable div.
<div contentEditable=true onkeyup='showResult(this.lastChild.textContent)'></div>
When a user types something into this div, the showResult javascript runs which is basically an ajax request which returns a list of items that match. When a user clicks on one of the suggestions, say the name "John", a span with the suggestion is added into this contentEditable div like so:
<div contentEditable=true onkeyup='showResult(this.lastChild.textContent)'>
<span id='uniqueId1' class='SpanClass' contentEditable='false'>John</span>
</div>
Having selected one Name, the user may want to search for another name. It HTML terms, that means that they would be typing the following:
<div contentEditable=true onkeyup='showResult(this.lastChild.textContent)'>
<span id='uniqueId1' class='SpanClass' contentEditable='false'>John</span>
New User Text Goes Here
</div>
On Chrome, the right behaviour happens when the user continues tries typing in the div - the showResult function runs on the new text that the user types in and ignores the span elements. For Example, if the user types in "Fr" having already selected John, it ignores the first children (John), and sends what the user typed off via ajax and returns suggestions like Fred and Frankie.
However, in IE the span is still content editable and the user can't add any text other than within the span, which seems to make no sense as it is clearly contentEditable=false The ajax request is therefore run on the "John" text plus whatever the user types in next, which is not what I'm trying to achieve.
Finally, in Firefox, the span is not contentEditable BUT the lastChild bit only picks up text within the span, and ignores the text the user puts in.
I've console logged the results of showResult(this.lastChild.textContent) to see what is being sent to the ajax request.
In Chrome, typing in "Fr" in the box after the "John" span sends "Fr" to the ajax and returns the right result.
In IE, typing in "Fr" in the box sends "JohnFr"
In Firefox, typing in "Fr" just sends "John".
As the issue is with this lastChild and the span, I've also included the Javascript that creates the span element. This only activates after a successful result is return and the result is clicked on. (please excuse the very messy Javascript/Jquery)
$('body').on("click", '.TagHints', function(){
//Once you click on the suggestion
var ThisData = $(this).data("id");
var ThisId = $(this).attr("id");
var ThisTag = $(this).data("tag");
//delete the text that the user typed in
elementToRemove = document.getElementById("FakeInput").lastChild;
document.getElementById("FakeInput").removeChild(elementToRemove);
var TagDiv = document.createElement('span');
TagDiv.className = 'SpanClass';
TagDiv.id = ThisId;
TagDiv.innerHTML = ThisTag;
TagDiv.contentEditable=false;
//append the Span to the contentEditable div
document.getElementById("FakeInput").appendChild(TagDiv);
var TagHints = document.getElementsByClassName("TagHints");
while(TagHints.length > 0){
TagHints[0].parentNode.removeChild(TagHints[0]);
}
});
Why are the three browsers behaving completely differently and how do I get them all to behave like Chrome is? Is there a better way of getting the text not in the spans?
I read on another answer that firefox likes inputs and IE likes breaks in this context but both do not seem to work for me. :-(.
One big stopper to good solutions is that jQuery stops working after about line 6, which has also completely baffled me. If anyone can explain why its not working, that would also be cool. Maybe something to do with it being an ajax query and content being created after jquery is loaded?
Thanks for your help!
So, although I haven't had any responses I've come up with a workaround. However, lastChild is still behaving differently between the three browsers so would appreciate any further input.
The answer was to have a function in the document ready section which cloned the original div, stripped it of its children and grabbed the text on key up using Jquery. This then passed the result to the showResult function like so:
$('#FakeInput').keyup(function(){
var ThisClone = $('#FakeInput').clone()
.children()
.remove()
.end()
.text();
showResult(ThisClone);
});
Equally, rather than trying to remove the text that the user typed in from the divs when the user makes a selection I simply cloned the items in the span, using their class, then emptied the contents of the div and reappended these cloned divs.
var TagItems = $('.TagItems').clone();
$('#FakeInput').empty();
$('#FakeInput').append(TagItems);
This solution works across the three browsers.