Input click can't be triggered programanticly on iOS <12 - javascript

I'm having an issue with triggering the opening of a file dialogue on older iOS devices, specifically, the issue seems to be on devices that still run iOS 12.
I'm using the React-Dropzone package to create a dropzone for files, but also adding a way to tab the area to open the file dialogue to select files.
I then use Hammerjs to detect onTab events.
What I can establish, by adding an alert to be fired when onTab is called, is that the onTab event is firing and that it is the functions that are meant to open the dialogue that is not triggering the file dialogue to open on older iOS devices.
const FileUploadDropzone = () => {
...
const {getRootProps, getInputProps, open, inputRef} = useDropzone({
// Disable click and keydown behavior
noClick: true,
noKeyboard: true,
});
const handleTap = useCallback(() => {
// specific function created by React-Dropzone to open the dialog
open();
// also tried to trigger the input click directly using a ref (have confirmted that the input is correctly referenced)
inputRef.current.click();
}, [allowInteract, uploading, open]);
return (
<Hammer onTap={handleTap}>
<div {...getRootProps()}>
<input {...getInputProps()}/>
{children}
</div>
</Hammer>
);
};
From what I have read, the input cannot have styles set to display:none, if it does then you cannot programmatically trigger opening the file dialogue.
So I have set the styles as following:
input {
visibility: hidden;
height: 0px;
width: 0px;
}
What I've also tried to do is to wrap the input and the child element passed to the component in a label, hoping that clicking on a label to open the dialogue will be better supported, but even that is not working.
So now I am at a loss, I don't understand how to get this to work on these older iOS < 12 devices.

The issue seems to be related to an issue with fact that Safari on iOS 12 does not like it when there is any delay between the custom click event and until the input click event is called programmatically. Since the input in my case is deeply nested into the DOM, it causes an issue when the bubbling of the event takes to long.
To get around this issue, I had to create a hack, to append the file input into the DOM to make it as shallow of a grandchild as I could of the <body>.
I followed this article to utilise React.createPortal to append the input outside of the component. https://www.jayfreestone.com/writing/react-portals-with-hooks
I created a function to create an empty div at the bottom of the top <div> off the React application.
function usePortal(cssSelector) {
const rootElemRef = React.useRef(document.createElement('div'));
useEffect(
function setupElement() {
const {current} = rootElemRef;
const parentElem = document.querySelector(cssSelector);
parentElem?.appendChild(current);
return function removeElement() {
current.remove();
};
},
[id]
);
return rootElemRef.current;
}
Then created a component that would append its child a custom div, created by the function above
const Modal = ({children}) => {
const target = usePortal('#react-root-div');
return ReactDOM.createPortal(children, target);
};
And then simply wrap the input element with the Modal component and it will be appended (almost) to the bottom of the DOM tree.
<Modal>
<input {...getInputProps()} />
</Modal>

Related

How to execute function when anchor tag was opened?

I want to send a post request (with a payload) to my server when a user opens a link in my React app.
My current approach is to use the onClick and onAuxClick callbacks, and then filter out right-clicks (because those don't directly open links).
<a href={link} target="_blank" rel="noopener noreferrer"
onClick={onLinkClicked} onAuxClick={onLinkClicked}>
<FaExternalLinkAlt className={styles.headerButton} />
</a>
function onLinkClicked(event: MouseEvent) {
if ([0,1].includes(event.button)) {
alert('track click')
}
}
However, right clicks followed by opening the link via the context menu are also ignored.
Another option seems to be to use the ping callback. However, there are two problems with this: Ping doesn't seem to contain a payload/body, which I need. And browser support seems to be flaky.
How can I reliably track link clicks without false positives and without ignoring opening the link via the context menu (and any other ways of opening a link)?
You can force the link to be opened even if the user clicks the wrong button and with that you ensure the actual click count
Edit 1:
For some reason, the contextmenu event fires twice so I advise that you handle that issue I tried removing the event propagation and investigating on it and found nothing
const a = document.querySelector('a');
// left, right, middle Clicks
const events = ['click', 'contextmenu', 'auxclick'] // You can add more events if you feel the need to
const cb = (clickType, elem) => {
console.log(clickType);
console.log(elem.href);
// Open link and do the post request here
}
events.forEach(eve => {
a.addEventListener(eve, (e) => {
e.preventDefault();
cb(e.which, a);
})
})
click

Content script class not being added to injected element

I'm building a gmail extension that extends the compose view with an extra toolbar, using inboxSDK and content-scripts. Everything is working fine, I've injected everything, and I'm using gmail's style sheets and classes to make it flow more with the gmail UI. There is a class called "J-Z-M-I-Kq" (default gmail stylesheet) which when applied to a button-like div, gives the button the appearance of being pressed.
The issue comes when I close one compose view, and open another. Because the html for the new compose view is generated when the compose button is clicked, I have to re-inject all the toolbar things again. Everything works perfectly fine, except this class won't add to my button. I've tried making my own class and applying it in the exact same way, using the exact same jQuery selectors, and that works. In the dev tools, when I click the button, the element flashes like it would if the class was about to be added, but it just doesn't. I'm so confused as to why this might be happening.
This is the click handler:
$(dropdownContainer).click(function (e) {
let senderElement = $(e.target);
let checkTarget = senderElement.is("form") || senderElement.is("input") || senderElement.is("p");
if (checkTarget) {
return;
}
let wasOpen = false;
if (!($(dropdownContent).is(":hidden"))) {
wasOpen = true;
}
// Remove all dropdown content and un-highlight all buttons
$(".dropdown-content").hide();
$(".dropdown-btn").removeClass("J-Z-M-I-Kq");
// If the clicked button was already open, then do nothing (it has been closed). Else
// open the dropdown
if (!wasOpen) {
console.log("it wasn't open");
forceShow($(dropdownContent));
console.log(button);
$(button).addClass("red");
}
});
The ID_NUMBER is so that I can have unique ids when re-generating the tool bar.
button is the button that is clicked, and is defined earlier - console.logging this shows that the selector isn't the issue.
If I were to do button.addClass("red"), where red is an injected stylesheet - it works perfectly. All the other classes (such as one which does a similar effect but on hover) work fine, even when a second compose view is open. If I run the code in console, the effect works. Removing the class by clicking off even works when manually adding the class in console! I'm really stumped here, any help would be appreciated.

How to prevent mobile keyboard from closing when I click button in website?

I have a simple chat view, and one button that when clicked sends the value of input field to api (send message)
After I insert text via mobile virtual keyboard, and click "Send" Button the keyboard closes.
I want to prevent that closing, so user can send more messages and only when they click outside keyboard it finally closes
My react code is like this:
component.tsx
<span className="input-group-btn">
<button
ref={submitMessageBtn}
className="btn"
onClick={React.useCallback((event) => {
event.nativeEvent.preventDefault();
sendMessage();
}, [])}
>
{i18n.sendMsgBtn}
</button>
</span>
// somewhere down the function sendMessage
function sendMessage() {
const messageContent = chatMessageInput.current!.value;
chatMessageInput.current!.value = '';
submitMessageBtn.current!.disabled = true;
p.sendMessage(user.id, messageContent).catch((err) => {
// not interesting for question
});
}
I tried in the button event handler to preventDefault() but doesn't work. I also tried event.nativeEvent.stopPropagation(); event.stopPropagation() still no success. I don't understand why the keyboard closes (maybe due to losing focus but I want to keep it open)
How to stop mobile (android) virtual keyboard from closing, when I click this button ?
This question ranked high on google, and here is how I managed to do this (with the composition api)
you will need first to get a ref to your textarea (or input), and when you are firing the click event, call this ref value and focus on it:
<template>
<textarea
ref="input"
placeholder="Message..."
/>
<button #click="sendMessage">Send</button>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
// if you are using JS, write only const input = ref(null);
const input = ref<null | { focus: () => null }>(null);
async function sendMessage() {
// keep focus back on the input
input.value?.focus();
// input.value.focus(); in JS
// then send the message
// ...
}
</script>
this was tested only on android though
I had the same Problem with a Button in Vue.js and .preventDefault() on the click-event didn't help. The solution was adding an EventListener on the Button for the "touchend" Event.
mounted() {
this.guestName = "";
const btn = document.querySelector(".input-button");
if (btn){
btn.addEventListener("touchend", (e)=>{
e.preventDefault();
//functions for the Button need to be called here
this.addGuestClicked(this.guestName);
})
}
I just tested it on iOS, but I guess Android will work too.
You can try the VirtualKeyboard API: https://developer.chrome.com/docs/web-platform/virtual-keyboard/
if ('virtualKeyboard' in navigator) {
// The VirtualKeyboard API is supported!
navigator.virtualKeyboard.overlaysContent = true;
navigator.virtualKeyboard.show();
// When you want to hide it
navigator.virtualKeyboard.hide();
}
Note this only works in a secure context i.e. localhost or https site. If you need to do testing and you want to host from your local device but test on your phone, use port forwarding as described here:
https://developer.chrome.com/docs/devtools/remote-debugging/local-server/
Also your browser MUST be focused on an element which is editable, as per the W3C spec. https://www.w3.org/TR/virtual-keyboard/#webidl-65390189

how to interact with ckeditor, by catching and handling events

I am new to ckeditor, I have hard time figuring this issue out.
due to my html design; if I try to use the link editor dialog while my ckeditor is maximized, it just doesn't show up, I understand that ckeditor is the top most object in my html page and the link dialog comes underneath it. if now I bring ckeditor to its normal state I will be able to see and use the link dialog.
my idea is to slightly override the link button click event as follows:
if the editor is in full screen mode, bring it back to the normal state. and keep a flag somewhere so that when I close the link dialog, I can decide whether to bring back the ckeditor to a maximized mode again. now this is easy logic except that I do not know how to override the click event of the link button and keep it work as expected.
here's what I have:
$().ready(function () {
var editor = $('#txa').ckeditor();
CKEDITOR.plugins.registered['link']=
{
init : function( editor )
{
var command = editor.addCommand( 'link',
{
modes : { wysiwyg:1, source:1 },
exec : function( editor ) {
if(editor.commands.maximize.state == 1 ){
alert("maximized");
//....here bring back the editor to UN-maximized state and let the link button event click do the default behavior
}
else
{
alert("normal state");
}
//2 is normal state
//1 is maximized
}
}
);
editor.ui.addButton( 'link',{label : 'YOUR LABEL',command : 'link'});
}
}
});
html part to make the exemple work:
<div>
<textarea id="txa">
</textarea>
</div>
TO BE SHORT:
http://jsfiddle.net/Q43QP/
if the editor is maximized, bring it to normal state then show the link dialog.
Use editor.execCommand('maximize') to toggle the state of the editor window.
http://jsfiddle.net/Q43QP/2/

CKEditor cannot get current instance from iframe

CKEditor 4 or above
I have form > textarea with CKEditor enabled and functioning.
I have iframe in modal dialog and inside iframe is button with insert_media() javascript function.
function insert_media( element ) {
// get element html decode
element = htmlspecialchars_decode( element, 'ENT_QUOTES' );
// htmlspecialchars_decode is external function.
// CKEditor insert element ---------------------------------------------
// use .insertElement()
var CKEDITOR = window.parent.CKEDITOR;
var element = CKEDITOR.dom.element.createFromHtml(element);
// body_value is name of textarea
// this code only works with specific textarea NOT current active textarea
//CKEDITOR.instances.body_value.insertElement(element);
var current_instance_ckeditor = window.parent.test_current();
// CKEditor insert element ---------------------------------------------
// close modal dialog at parent window
window.parent.close_dialog();
// done
return false;
}// insert_media
and this is javascript in main page html
function close_dialog() {
$('#media-modal').modal('hide');
}// close_dialog
function test_current() {
console.log( CKEDITOR.currentInstance.name );
}
The problem is i cannot get current active CKEditor to insert element with insertElement command.
CKEDITOR.currentInstance is undefined or null
window.parent.CKEDITOR.currentInstance is undefined or null
How to get current active CKEditor from iframe?
file for test: http://www.megafileupload.com/en/file/420060/test-ckeditor-zip.html
If CKEDITOR.currentInstance is null/undefined, then none editor instance is active. It means that you moved focus out of editor to the place which is not recognised as its part.
However, if you're using CKEditor's dialogs (do you?) editor instance should always be active, when this dialog is opened for it. If this is your case, then you need to provide us a working example, because it's hard to guess what can be wrong.
Second option is that you don't use CKEditor's dialogs and then you have to take care of registering that iframe to the CKEditor's focusManager, although this is tricky so you rather should not use 3rd party's dialogs with CKEditor.
Edit The test_current function works fine when I click "test" button, but editor has to be focused. But after 200ms from the moment you click button editor will be blurred and you won't be able to get it from currentInstance. To avoid blurring editor when clicking button you need to register it in focusManagers (of both editors if it will be used with both).
I'd do it like this
var ck_instance_name = false;
for ( var ck_instance in CKEDITOR.instances ){
if (CKEDITOR.instances[ck_instance].focusManager.hasFocus){
ck_instance_name = ck_instance;
return ck_instance_name;
}
}
CKEditor is null/undefined as Reinmar said.
Now i can find the way to work with current instance even if you clicked outside CKEditor.
Here is sample files for test.
http://www.megafileupload.com/en/file/448409/test-ckeditor-zip.html
What i do is ...
Add global variable for current instance in javascript (in html)
On click button for open modal dialog, get current instance and set to global variable in choice 1.
On click insert button in iframe in modal dialog, get window.parent.current_instance variable and use as texteditor id. (var parent_cke_current_id = window.parent.current_instance_id;)
Now do what ever you want with clicked CKEditor instance. for example (CKEDITOR.instances[parent_cke_current_id].insertElement(element);)
Thank you Reinmar.

Categories