I am writing a Greasemonkey script to interact with orders on Shipstation.com.
The script will select certain values in <select> and <input> elements of the order modal based on certain criteria.
Thus, the script must be able to interact with these elements.
However, I cannot figure out how to do so.
So far, I have tried to do the following and have been unable to trigger a click on the element:
Set the value of the element using JS .value
Set the value of the element using jQuery .val
Trigger a click event on the element using JS .click()
Trigger a click event on the element using this code:
function triggerMostButtons (jNode) {
triggerMouseEvent (jNode, "mouseover");
triggerMouseEvent (jNode, "mousedown");
triggerMouseEvent (jNode, "mouseup");
triggerMouseEvent (jNode, "click");
}
function triggerMouseEvent (node, eventType) {
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent (eventType, true, true);
node.dispatchEvent (clickEvent);
}
triggerMostButtons(jNode);
It appears that Shipstation is locking the value of <select> and <input> values.
Here are examples of the SO questions I have read to try and figure this out. I haven't been able to trigger a click on these elements using any of these approaches:
"Normal" button-clicking approaches are not working in Greasemonkey script?
How to simulate click in react app using tampermonkey?
Greasemonkey script to automatically select, and click, a specific button with non-English characters in the value/selector?
Choosing and activating the right controls on an AJAX-driven site
Simulating a mousedown, click, mouseup sequence in Tampermonkey?
javascript click a value in dropdown list
How to change a <select> value from JavaScript
How do I programmatically set the value of a select box element using JavaScript?
Set the value of an input field
How else can I trigger a click on these elements?
Alternatively, how can I set the value of these fields using JS? Do I have to find the data model in the JS and edit the value directly? Or find a way to hijack the functions that are triggered when a user clicks on these elements?
Maybe the values are loaded dynamically from the server, so you need to wait until the full page is loaded by adding a load event.
Then grab the select elements by a document.getElementById (or querySelector) and set your desired value (If I understood correctly, you don't need a click event).
You didn't provide an example I can work with, but I tried it with this https://www.shipstation.com/step1/
Notice how after the page loads, the country will be set to Canada:
// ==UserScript==
// #name Unnamed Script 933923
// #version 1
// #grant none
// #match *://*.shipstation.com/*
// ==/UserScript==
window.addEventListener('load', (event) => {
document.getElementById('country').value = "CA";
document.getElementById('howdidyouhearaboutus').value = "Banner Ad";
});
you can select an option by doing this
function selectItemInDropdownList(selectElement, ov) {
const optionToSelect = '' + ov
const options = selectElement.getElementsByTagName('option')
for (const optionEle of options) {
if (optionToSelect === optionEle.innerText || optionToSelect === optionEle.value) {
optionEle.selected = true // selects this option
return true
}
}
return false // failed
}
/*
const dropdownEle = document.getElementById('whatever')
const status = selectItemInDropdownList(dropdownEle, "WHAT")
console.log(status ? 'success' : 'failed')
*/
I think main issue is its a popup form and the select element 'may' not be available in DOM tree at first. so make sure to call this function when its available.
This solution works well in 2022. No jQuery is used.
Auto expand select options in element cb:
cb.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))
Input focus on element cb (tested on Safari), watch the compatibility note on MDN:
cb.focus()
Below are the working solution for you to copy and run on a browser. (Since input focus feature is failed to run in the code snippet here, while the feature auto expand the select options still works.)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<select id="select-1">
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
<input id="input-1" type="text"/>
<script>
window.onload = function() {
const select = document.getElementById('select-1');
const input = document.getElementById('input-1');
input.focus();
setTimeout(function() {
select.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
}, 1000);
}
</script>
</body>
</html>
Related
When 'click' event option works fine: .addEventListener('click', listenerHandler); then event.stopPropagation(); and event.preventDefault(); does its job. As expected the file picker doesn't appear, it has been holded.
But when I switch to the 'change' event option things goes wrong... the file picker window immediately appears.
.addEventListener('change', listenerHandler);
Unlike the 'click' event, when using 'change' event as soon as I click the button the file picker is shown!
In order to test some conditions before the file picker is shown, is there any way to hold the file picker window from appear when using the 'change' event option?
I need to hold the file picker using the 'change' event option not the 'click' option.
I will appreciate your advice to resolve this issue. Thanks!
var teacher = student = "";
let fileSelector = document.getElementById('dataUpload');
// When the event option is 'change' the file picker is shown,
// when the event option is 'click' the file picker gets hold!
fileSelector.addEventListener('change', (event) => {
event.preventDefault();
event.stopPropagation();
listenerHandler(event);
});
// when using 'change' option the event.stopPropagation and
// event.preventDefault looks like does not do its job!
function listenerHandler(event) {
if (teacher == "" || student == "") {
fileSelector.removeEventListener('change', listenerHandler);
// remove.EventListener does not do its job when using the 'change' event option.
window.alert("Select a Teacher or Student.");
return;
}
// If conditions are okay invoke the next function...
handleDataUpload(event);
/* another function to handle the file upload, always
works okay whether 'click' or 'change' event options running. */
}
function handleDataUpload(event) {
window.alert("Okay!");
}
<!DOCTYPE html>
<html>
<head>
<title>My First Upload!</title>
</head>
<body>
<h3>My First Upload!</h3>
<input type="file" id="dataUpload" accept=".pdf, .PDF, .jpg, .gif" />
</body>
</html>
I was developing an HTML form and realised that the behaviour of the select field was quite different on iOS Safari to other browsers (including several android based browsers).
Safari mobile was ignoring the hidden attribute of my option elements, allowing the user to select placeholder values. While it's possible to set the disabled attribute which prevents the user from selecting them on iOS, this still shows those fields to the user, and it has to be handled with media queries as then the option elements are not shown at all (even as placeholders) in other browsers.
I wanted a seamless universal solution. A bit of reading and experimentation indicated that Safari was actually stripping the hidden attribute and generating a popover from the options.
Of course, if the option tag isn't there, it can't generate a popover containing them...
My solution was to add event listeners to:
touchstart & touchend to momentarily remove the option which should be hidden (note that it appears to be possible to trigger a tap without triggering touch end, if you swipe off the element while tapping)
change input listener prevents the code from running more than once, and,
blur focusout touchcancel listeners make doubly sure to reinstate the option if a user cancels selecting an option.
This code looks relatively long for what it is, but I've written it that way for readability. It could be significantly compressed, and the performance hit is negligible.
I have implemented it using jQuery, but I see no reason it couldn't be done with native JS and querySelector().
This approach is quite robust to being accidentally triggered by other touch input (swiping, scrolling etc.), and is able to respond long before the click event occurs (which seems to occur after the popover has been generated).
It's not perfect, but it does bring the behaviour of Safari well into line with all other browsers in this small aspect.
Hope this helps someone else!
$(document).ready(function() {
//Workaround - Safari/Ios - does not respect the <option hidden> attribute
//Momentarily remove the hidden option element when the user clicks a select element.
//Re-Add it after iOS has generated its selection popover
var hiddenOption; //Holds the option element we want to be non-selectable extra option)
var timers = []; //Collect setTimout timers
function returnOptionToSelect(element) {
if ($(element).children('[data-ioshidden]').length > 0) {
//It's already there. Do nothing.
} else {
$(element).prepend(hiddenOption); //Put it back
}
}
$('#jQueryWorkaroundForm select').on('touchstart touchend', function(e) {
if ($(e.target).data('has-been-changed')) {
//don't do anything
} else {
if($(e.target).children('[data-ioshidden]').length > 0){
hiddenOption = $(e.target).children('[data-ioshidden]');
$(hiddenOption).remove();
}
timers.push(setTimeout(returnOptionToSelect, 35, $(e.target))); //Nice short interval that's largely imperceptible, but long enough for the popover to generate
}
});
$('#jQueryWorkaroundForm select').on('input', function(e) {
if ($(e.target).data('has-been-changed')) {
//do nothing
} else {
$(e.target).data('has-been-changed', true);
if (navigator.maxTouchPoints && navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')){
$(e.target).prop('selectedIndex', (e.target.selectedIndex+1)); //Need to bump the index +1 on ios
}
//make sure the option is gone
for (var x in timers) {
clearTimeout(x);
}
timers = [];
if ($(e.target).children('[data-ioshidden]').length > 0) {
$(e.target).children('[data-ioshidden]').remove();
}
hiddenOption = undefined; //throw away the option so it can't get put back
}
});
$('#jQueryWorkaroundForm select').on('blur focusout touchcancel', function(e) {
if ($(e.target).data('has-been-changed')) {
//The user selected an option. Don't put the element back.
} else {
//The user did not select an option.
if ($(e.target).children('[data-ioshidden]').length > 0) {
//It's already there. Do nothing.
} else {
$(e.target).prepend(hiddenOption); //Put it back
}
}
});
});
select{
width:300px;
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<p>jQuery Workaround</p>
<form id="jQueryWorkaroundForm">
<select>
<option hidden data-ioshidden value="">This option not selectable in any browser</option>
<option>Option 1</option>
<option>Option 2</option>
</select>
</form>
<p>Standard HTML</p>
<form id="standardHTMLForm">
<select>
<option value="" hidden>This option selectable in iOS Safari</option>
<option>Option 1</option>
<option>Option 2</option>
</select>
</form>
</body>
</html>
And here it is as a JS fiddle: https://jsfiddle.net/wjcprbyg/97/
Is there a way to determine if a select element's change event was performed by user versus via code (dom manipulation)?
I have a timer that auto sets some select values based on some condition and then formats the select color to signify "success" (green select box).
Because I have the form set to fire off an ajax request when the select change event is fired, it does this for both the timer-based changes to the selects, as well as user-based select changes.
I'd like to only fire that ajax event when it's a user-based select change.
Is this possible?
EDIT:
When I output the value of e.isTrusted, it says Undefined in the log.
Here is my code:
$(document).on('change', 'select', function (e) {
e.preventDefault();
console.log(e.isTrusted); // This outputs "Undefined"
if (e.isTrusted) {
save_review();
console.log("Point status changed by user - Saved");
} else {
console.log("Point status changed programmatically - Not Saved");
}
});
Managed to solve this based on a solution I found googling and with the help of the community-provided answers above.
When the timer-based event triggers, I now pass it a boolean value (auto_selected):
$("#pt" + value + " option:nth-child(2)").attr('selected', true).trigger('change', [true]);
In my event listener, I am then able to check against the value of that parameter:
$(document).on('change', 'select', function (e, auto_selected) {
e.preventDefault();
console.log("Auto-selected: " + auto_selected);
if (typeof auto_selected == 'undefined' || !auto_selected) {
save_review();
console.log("Point status changed by user - Saved");
} else {
console.log("Point status changed programmatically - Not Saved");
}
});
There is a property called isTrusted on the event object that you can use to check whether the event is triggered by user action or by script.
that is true when the event was generated by a user action, and false when the event was created or modified by a script or dispatched via EventTarget.dispatchEvent()
if (e.isTrusted) {
/* The event is trusted */
} else {
/* The event is not trusted */
}
if you use jQuery, it's inside event.originalEvent
if (e.originalEvent && e.originalEvent.isTrusted) {
/* The event is trusted */
} else {
/* The event is not trusted */
}
If you use select2, listen to the event select2:select, and check for event.params.originalEvent.originalEvent (two originalEvent here, seems like a bad implementation inside the library itself)
$('#your-selector').on('select2:select', function(e) {
if(e?.params?.originalEvent?.originalEvent) {
// user action
} else {
// programatically trigger
}
});
I looked a bit through the documentation of select2 because I was not really happy with the initial solution in my answer. And they actually provide a solution for that using a namespaced events.
Limiting the scope of the change event:
It's common for other components to be listening to the change event, or for custom event handlers to be attached that may have side effects. To limit the scope to only notify Select2 of the change, use the .select2 event namespace
I'm wondering why they don't suggest that one as default in the documentation. I still think that their suggested way (using the not namespace verion) to sync the native select element with the HTML representation of select2 is odd. Because why should the default behavior of programmatically changing an input element trigger a change event.
So if you use $("select option:nth-child(2)").attr('selected', true).trigger('change.select2'); it won't trigger the change event.
$('select').select2();
function commonTaskOnChange() {
console.log('commonTask')
}
$(document).on('change', 'select', function (e) {
console.log('selection changed by user')
// in case you want to perform some common task for timer and change event
commonTaskOnChange();
})
setTimeout(() => {
console.log('timer will change selection')
// trigger the namespaced change event
$("select option:nth-child(2)").attr('selected', true).trigger('change.select2');
// you could still perform some action that the change event has in common by calling a function here
commonTaskOnChange();
}, 1000)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/select2#4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/select2#4.1.0-rc.0/dist/js/select2.min.js"></script>
<select>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
I'm currently using Angularjs (version 1.5.8) and I'm developing an application where a series of inputs are generated dynamically, and I want that when you focus an input and you take 5 seconds or more to start writing, appears a tooltip (the tooltip is the same for all inputs), currently I have a tooltip directive that supports trigger events ('click', 'mouseenter', 'focus').
I made a first version of what I want using the click trigger, but sometimes there are unwanted behaviors, any suggestions?
Use the mouseenter event (or focus) and set a timeout to add the title attribute to the input (or create an explicit tooltip component to appear). If the user has any keypress or just plain model input, then cancel the timeout ?
let input = document.getElementById('input');
input.onfocus = function(){
let a = 0;
setTimeout(function(){alert('Please, write something');}, 5000);
};
/* Replaces the function alert('Please ...') with the function that manages your tooltip */
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<input id="input" />
</body>
</html>
`
$scope.focus = function() {
setTimeout(function(){
alert("test"); }, 5000);`
you write tooltip code ...inside of function.
you write custom directive called element.on("focus")
We are developing a web application with GUI customized to use on iPhone. A page on the app has 3 subsequent SELECT dropdowns, where selection of an option from 1st dropdown derives the options for 2nd dropdown, and so forth. The subsequent dropdown options are populated by javascript based on onchange event on previous dropdown.
The problem is that, on iPhone the options for a SELECT appears with 'prev' and 'next' links to move to previous and next control. When the 'next' link is clicked, then the control moves to next SELECT and displays the options. The javascript is triggered by onchange event for previous SELECT and populates the options for next SELECT. But on dropdown for 2nd SELECT displays the previous options before it is repopulated by the javascript.
Is there a workaround or event that can sequence execution of javascript and then selection of next control? Is there any event that can be triggered when a SELECT option is selected and before control leaves the SELECT element? Is there a handle for Next and Previous links for SELECT dropdown option display?
Maybe you could use the focus event, in jQuery this would be:
$('#select2').focus(function () {
// update select2's elements
});
Although the real question is when the iPhone overload comes in, and when the event get fired. And also can the select options be changed whilst in this view.
I had the same problem on my site. I was able to fix it by manually polling the selectedIndex property on the select control. That way it fires as soon as you "check" the item in the list. Here's a jQuery plugin I wrote to do this:
$.fn.quickChange = function(handler) {
return this.each(function() {
var self = this;
self.qcindex = self.selectedIndex;
var interval;
function handleChange() {
if (self.selectedIndex != self.qcindex) {
self.qcindex = self.selectedIndex;
handler.apply(self);
}
}
$(self).focus(function() {
interval = setInterval(handleChange, 100);
}).blur(function() { window.clearInterval(interval); })
.change(handleChange); //also wire the change event in case the interval technique isn't supported (chrome on android)
});
};
You use it just like you would use the "change" event. For instance:
$("#mySelect1").quickChange(function() {
var currVal = $(this).val();
//populate mySelect2
});
Edit: Android does not focus the select when you tap it to select a new value, but it also does not have the same problem that iphone does. So fix it by also wiring the old change event.