How do I select the one of many input elements that had the cursor in it a split second before clicking a button to add something to the (previously) active input text box? Is there any method document.hadFocus? Can I get the id of the input using onFocusOut and pass it to my function?
I've seen many solutions in JQuery but can anyone do it in vanilla JS?
function insertAtCursor(newText, el = document.activeElement) {
//getElementsByTagName("input")[0].focus();
const start = el.selectionStart;
const end = el.selectionEnd;
const text = el.value;
const before = text.substring(0, start);
const after = text.substring(end, text.length);
el.value = (before + newText + after);
el.selectionStart = el.selectionEnd = start + newText.length;
el.focus();
}
I resolved this by replacing the onclick eventListener of my button (for adding text to an input box...)
with a focusout eventlistener on the previous element (the input box).
That way the event was tied to the input box and had everything I needed.
e.relatedTarget is the opposite of what I needed ("hadFocus"). It is "what element (if any) is getting the new focus of this focusout event?" The if statement uses this relatedTarget to make sure that the focus was lost to the button.
this is tied to the inputBox that "hadFocus" when the button was pressed.
inputBox.addEventListener( "focusout", insertAtCursor )
function insertAtCursor( event ){
if ( e.relatedTarget?.parentElement?.classList?.value == "buttonSet" ){
const text = this.value;
const start = this.selectionStart;
const end = this.selectionEnd;
const wordId = this.id;
const newTextToAddFromButton = e.relatedTarget.innerText;
...
}
}
Related
Explanation
I have a textarea and a button. When I click the button, I want to insert text into the textarea. However, the text that I insert depends upon the current focus of the texteara. Here are some cases:
Cases
(As of the time that the button is clicked)
Textarea focused
Insert text where the cursor is, as-is
Textarea unfocused
Insert text at the end of the textarea (ie add a newline to the inserted text)
Example / Attempt
Here is a fiddle with my example implementation:
https://jsfiddle.net/reL9ro6L/1/
$(document).ready(function() {
$('button').click(function() {
var $text = $('textarea');
var currentValue = $text.val(),
len = currentValue.length,
isTextAreaFocused = $text.is(':focus'),
optionalNewline = isTextAreaFocused ? '' : '\n';
var start = $text[0].selectionStart,
end = $text[0].selectionEnd,
beforeText = isTextAreaFocused ? currentValue.substring(0, start) : len,
afterText = isTextAreaFocused ? currentValue.substring(end) : len;
var insertedText = 'foo',
newValue = beforeText + insertedText + afterText + optionalNewline;
$text.val(newValue);
});
})
Problem
I believe that the button focuses before it has a chance to know if the textarea is focused. Is there a hook or way to handle the click event on the button such that I'll know (before it is focused) what is focused?
Off point: I'm using Ember as my framework. I'd really love to see a pure JS / jQuery solution, but I just wanted to place Ember on the table as well.
You'd have to use the mousedown event on the button, as it fires before the textarea loses focus.
By the time a click event fires, the mouse has been pressed down, and released, and the focus will have shifted to the button instead.
$(document).ready(function() {
$('button').on({
mousedown: function() {
var text = $('textarea').get(0),
currentValue = text.value,
isTextAreaFocused = text === document.activeElement,
insertedText = 'foo',
start = text.selectionStart,
end = text.selectionEnd,
beforeText = currentValue.substring(0, start) || "",
afterText = currentValue.substring(end) || "",
newValue = beforeText + insertedText + afterText;
text.value = isTextAreaFocused ? newValue : currentValue + insertedText + '\n';
$(this).data({'focus' : isTextAreaFocused, 'end' : end + insertedText.length});
},
mouseup: function() {
if ( $(this).data('focus') ) {
$('textarea').focus().get(0).setSelectionRange($(this).data('end'), $(this).data('end'));
}
}
});
});
textarea {
width: 20em;
height: 10em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea></textarea>
<button>Insert text</button>
I created a textarea and a button. When the button is clicked, I want to add the letter 'a' at the current position of the cursor in the textarea. Below is my current code:
$('button.buttonA').click(function(){
var cursorPos = $('textarea.formInsideMenu').prop('selectionStart');
var textCurrent = $('textarea.formInsideMenu').val();
var textBefore = textCurrent.substring(0, cursorPos);
var textAfter = textCurrent.substring(cursorPos, textCurrent.length);
$('textarea.formInsideMenu').val(textBefore + 'a' + textAfter);
});
The above code works fine, (inserts an 'a' at the correct position), when the focus is on the textarea; but as soon as I click on the button, I lose focus of the textarea and the cursor is no longer showing. If I click on the button again after this, 'a' is appended at the very end of the text, (it seems like the cursor is moved to the end of the text). Is there anyway to keep track of where the cursor is inside the textarea even when something else has been clicked on and the textarea has lost focus?
Once you're done with the insert, you need to focus the textarea and set the caret position back:
$('button.buttonA').click(function() {
var area = $('textarea.formInsideMenu'),
curPos = area.prop('selectionEnd');// at the caret **or after selected text**
area.val( area.val().substring(0, curPos) + 'a' + area.val().substring(curPos) )
.focus()
.prop({'selectionStart': curPos+1, 'selectionEnd': curPos+1});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button class="buttonA">Add an a</button> <br>
<textarea class="formInsideMenu"></textarea>
This version uses javascript only after returning the dom object from jQuery:
$('button.buttonA').click(function(){
var text = $('textarea.formInsideMenu').get(0);
var start = text.selectionStart;
text.value = text.value.slice(0,start) + 'a' + text.value.slice(start);
text.setSelectionRange(start+1,start+1);
text.focus();
});
Fiddle here
Use setSelectionRange after inserting the 'a'.
$('button.buttonA').click(function(){
var cursorPos = $('textarea.formInsideMenu').prop('selectionStart');
var textCurrent = $('textarea.formInsideMenu').val();
var textBefore = textCurrent.substring(0, cursorPos);
var textAfter = textCurrent.substring(cursorPos, textCurrent.length);
$('textarea.formInsideMenu').val(textBefore + 'a' + textAfter);
var elem = document.getElementsByClassName("formInsideMenu")[0];
elem.setSelectionRange(cursorPos, cursorPos + 1);
});
https://jsfiddle.net/ny82n5kn/
You can make a change event on the textarea, where you sore the position of the cursor, like this:
var cursorPos;
$('textarea.formInsideMenu').on('change', function(){
cursorPos = $(this)).prop('selectionStart');
};
Now it will be avaiable in the clickhandler.
I am attempting to remove an entire element and recreate it on an event:
I cannot seem to get this right despite several variations of the same code:
For example on event, I need to remove the element and then recreate the same element. I do not want to remove the text:
This is what I have tried (experimental): The result is inconsistent and the code is repetitive.
function removeCreate(){
var input = document.getElementById('display');
var body = document.getElementsByTagName('body')[0];
if(!!input){
input.parentNode.removeChild(input);
input = document.createElement('input');
input.id = 'display';
input.setAttribute('type',"text");
body.appendChild(input);
} else {
input.parentNode.removeChild(input);
input = document.createElement('input');
input.id = 'display';
input.setAttribute('type',"text");
body.appendChild(input);
}
}
Your reason for removing your input element and re-creating it is quite unclear, but let's say it gets modified somehow and you want to "reset" its state.
When you say "I do not want to remove the text", the most probable thing I understand is that you want to keep the current value that the user has typed into your input.
If this fits your situation, then you could simply hold a "template" of your input element in memory, so that you can clone it when needed and use the clone to replace the one in DOM. When doing so, retrieve first the current input value, and inject it back into the cloned input.
Result:
var inputTemplate = document.createElement('input');
inputTemplate.setAttribute('type', 'text');
function cloneInput() {
var newInput = inputTemplate.cloneNode(true);
newInput.id = 'display';
return newInput;
}
var input = document.getElementById('display');
var body = document.getElementsByTagName('body')[0];
if(!!input){
// First retrieve the current value (what the user has typed / default value)
var value = input.value;
input.parentNode.removeChild(input);
input = cloneInput();
input.value = value; // Re-inject the value.
body.appendChild(input); // Note that this would put your input at the bottom of the page.
} else {
//input.parentNode.removeChild(input); // useless?
input = cloneInput();
body.appendChild(input);
}
I cannot seem to get event.target to work for an element that is nested in any sort of tag in a body element. I'm capturing JavaScript keystrokes and updating them into an array, like so:
$(document).keyup(function(event) {
// Check for human
if (event.originalEvent !== undefined)
updateEvents("keyup", event.target, $(event.target).val());
});
function updateEvents(targetEvent, targetElement, targetValue)
{
if (canUpdate)
{
var index = events.length - 1;
if (events[index].targetEvent !== undefined)
{
events[index].targetEvent = targetEvent;
events[index].targetElement = targetElement;
events[index].targetValue = targetValue;
}
}
}
Then after a button is clicked that signals the playback to begin, I update the text field to simulate live typing:
var count = 0;
$.each(events, function() {
window.setTimeout(action, 100 * count, count);
count++;
});
function action(index)
{
var targetEvent = events[index].targetEvent;
var targetElement = events[index].targetElement;
var targetValue = events[index].targetValue;
if (typeof targetEvent != "undefined")
{
if (targetEvent == "keyup")
{
// Simulate live typing.
$(targetElement).val(targetValue);
// Retain cursor position and user viewpoint in text fields and textareas.
var length = $(targetElement).val().length;
targetElement.setSelectionRange(length, length);
targetElement.scrollLeft = targetElement.scrollWidth;
targetElement.scrollTop = targetElement.scrollHeight;
$(targetElement).keyup();
}
}
}
In the action() function, the targetValue is the actual value of the text field, and the targetElement is the actual text field element. This code works beautifully when the text field is just in the body tag, but nesting it within any sort of tag such as div, it doesn't work at all.
I fixed it. As it turns out there was a call where I was changing the HTML of the page, which messed up my element ID's.
I have got this working with the start point as a span, but I want to have the form still function if javascript is disabled in the browser this is how I had it working originally. I'm still very new to javascript, can someone lend a hand please.
window.onload = function() {
document.getElementById('container').onclick = function(event) {
var span, input, text;
// Get the event (handle MS difference)
event = event || window.event;
// Get the root element of the event (handle MS difference)
span = event.target || event.srcElement;
// If it's a span...
if (span && span.tagName.toUpperCase() === "SPAN") {
// Hide it
span.style.display = "none";
// Get its text
text = span.innerHTML;
// Create an input
input = document.createElement("input");
input.type = "text";
input.size = Math.max(text.length / 4 * 3, 4);
span.parentNode.insertBefore(input, span);
// Focus it, hook blur to undo
input.focus();
input.onblur = function() {
// Remove the input
span.parentNode.removeChild(input);
// Update the span
span.innerHTML = input.value;
// Show the span again
span.style.display = "";
};
}
};
};
Best way to do this would be to show the input first, then quickly swap it out when the page loads, then swap it back when the user clicks.
You might also consider using the form element the whole time, but just changing CSS classes on it to make it look like normal text. This would make your UI cleaner and easier to maintain in the future.
Then just put the input fields there from the start, and hide them with a script that runs when the form has loaded. That way all the fields will be visible if Javascript is not supported.
I think your best option would be to wrap a form with noscript tags which will fire when Javascript is disabled in a browser. If they display even while in the noscript tags then just set them as not visible with Javascript.
if you have jQuery, something like this should work.
function makeElementIntoClickableText(elm){
$(elm).parent().append("<div onClick='switchToInput(this);'>"+ elm.value +"</div>");
$(elm).hide();
}
function switchToInput(elm){
$(elm).prev().prev().show();
$(elm).hide();
}
makeElementIntoClickableText($("input")[0]);
use the readonly attribute in the input elements:
<input type="text" readonly />
And then remove that attribute with JavaScript in the onclick event handler, reassigning it on blur:
var inputs = document.getElementsByTagName('input');
for (i=0; i < inputs.length; i++) {
inputs[i].setAttribute('readonly',true);
inputs[i].onclick = function(){
this.removeAttribute('readonly');
};
inputs[i].onblur = function(){
this.setAttribute('readonly',true);
};
}
JS Fiddle demo.