I have a normal HTML input box: <input id="ip">.
And this input box has focused. I know this by looking at the screen and the fact document.activeElement tells me so.
Now I want to replace this input node. And I do so with: var the_new_node = document.createElement("input"); the_new_node.id="ip"; the_input_node.replaceWith(the_new_node);
When I do this, however, the input box loses focus. document.activeElement now points to the body. Is there anyway to prevent this?
Edit: I realise I can call .focus(). Yet in my code I won't necessarily know if the node to be replaced will have an input within it.
For instance, the in various 'virtual dom' implementations they replace segments of the dom tree while retaining focus. How do they do it?
If you want to only focus the new input if the element you're replacing had a focused element inside, you can code exactly that using .contains(document.activeElement):
function runReplace(idx) {
document.querySelectorAll("input")[0].focus();
setTimeout(() => {
let p = document.getElementById("par").children[idx];
let wasFocused = p.contains(document.activeElement);
let newNode = document.createElement("input");
newNode.type = "text";
newNode.value = "replacement for child #" + idx;
p.replaceWith(newNode);
if (wasFocused)
newNode.focus();
console.log("Replaced ", p, " with an input; wasFocused=", wasFocused);
}, 3000);
}
<div id="par">
<p>This is a paragraph with <b>an <input type="text" id="inp" value="<input>"> inside</b></p>
<p>This is a paragraph with no input inside</p>
</div>
<button onclick="runReplace(0)">Replace first paragraph in 3 seconds</button>
<button onclick="runReplace(1)">Replace second paragraph in 3 seconds</button>
There's no magic way the browser can "preserve" focus in face of replacing an arbitrary DOM sub-tree with another arbitrary sub-tree, so you have to do it manually.
Related
I looked at this question where it is asked for a way to simply copy text as plain text. I want to do exactly that but with one additional thing - not lose focus on the current element.
I need this for a Chrome extension, so I'm not bothered with cross-browser support. When the user types in an input (or contenteditable), a dropdown with choices appears. If he chooses one of them, it is copied to his clipboard. I don't want the element to lose focus because some sites might have implemented logic to run on the element's blur event.
Here's what I've tried:
Solution 1
Create an <input> element and use its select() method:
function clipWithInput(text) {
var input = document.createElement("input");
document.body.appendChild(input);
input.addEventListener("focus", function (e) {
e.preventDefault();
e.stopPropagation();
});
input.value = text;
input.select();
document.execCommand("copy");
document.body.removeChild(input);
}
document.getElementById("choice").onmousedown = function (e) {
e.preventDefault(); // prevents loss of focus when clicked
clipWithInput("Hello");
};
#main {background: #eee;}
#choice {background: #fac;}
<div id="main" contenteditable="true">Focus this, click the div below and then paste here.</div>
<div id="choice">Click to add "Hello" to clipboard</div>
As you can see, this works. The text is copied. However, when you focus the contenteditable and click on the "choice", the focus is lost. The choice element has preventDefault() on its mousedown event which causes it to not break focus. The dummy <input> element is the problem here, even though it has preventDefault() on its focus event. I guess the problem here is that it's too late - the initial element has already fired its blur, so my dummy input's focus is irrelevant.
Solution 2
Use a dummy text node and the Selection API:
function clipWithSelection(text) {
var node = document.createTextNode(text),
selection = window.getSelection(),
range = document.createRange(),
clone = null;
if (selection.rangeCount > 0) {
clone = selection.getRangeAt(selection.rangeCount - 1).cloneRange();
}
document.body.appendChild(node);
selection.removeAllRanges();
range.selectNodeContents(node);
selection.addRange(range);
document.execCommand("copy");
selection.removeAllRanges();
document.body.removeChild(node);
if (clone !== null) {
selection.addRange(clone);
}
}
document.getElementById("choice").onmousedown = function (e) {
e.preventDefault(); // prevents loss of focus when clicked
clipWithSelection("Hello");
};
#main {background: #eee;}
#choice {background: #fac;}
<div id="main" contenteditable="true">Focus this, click the div below and then paste here.</div>
<div id="choice">Click to add "Hello" to clipboard</div>
This works perfectly at first glance. The text is copied, no focus is lost, the caret stays at the same position. No drama. However, when you paste the text in a contenteditable (like Gmail's email composer), this is the result:
<span style="color: rgb(0, 0, 0); font-family: "Times New Roman"; font-size: medium;">Hello</span>
Not plain text.
I tried appending the element in the <head> where there are no styles - nope. Text isn't selected and nothing is copied.
I tried appending the text node in a <span> and set stuff like style.fontFamily to inherit, as well as fontSize and color. Still doesn't work. I logged the dummy element and it correctly had my inherit styles. However, the pasted text didn't.
Recap
I want to programmatically copy plain text with no styles while preserving focus on the currently active element.
Your solution (especially 2) was okay. When you paste in a contenteditable, it needs to be expected that there are span codes inserted, many use that in insertHTML. You are not to expect plain text programmatically. Some would suggest not using a contenteditable at all (though I understand you're talking about some extension). But your solution is more compatible with mobiles than MDN or such.
So, you programmatically copy plain with no style added (if no contenteditable) while preserving focus on the current element.
This question already has answers here:
How can I change an element's text without changing its child elements?
(16 answers)
Closed 1 year ago.
Is there a simple way to change the text of an element only using vanilla javascript? In the code below, I thought that using .textContent, rather than .innerHTML would change the text and leave the image behind.
<head>
<script>
function change_stuff() {
var div = document.getElementById('to_change');
div.textContent = "OMG...it's an image!";
}
</script>
</head>
<body>
<div id="to_change">
This is a huge block of text that I want to replace while leaving the image in place
<img src="./the_image.jpg">
</div>
<button onclick="change_stuff();">
ThE dOER!!
</button>
</body>
I've also tried but had little to no success with many variations of this:
function change_stuff() {
var div = document.getElementById('to_change');
var text = div.textContent;
div.textContent = text.replace(text, "");
}
Any help would be appreciated
Get the first textNode by firstChild property and update the content.
function change_stuff() {
// get the first child node, in your code which is the text node
var t = document.getElementById('to_change').firstChild;
// update the text contents in the node
t.nodeValue = "";
// or t.textContent = "";
// or remove the node itself
// t.parentNode.removeChild(t)
}
<div id="to_change">
This is a huge block of text that I want to replace while leaving the image in place
<img src="./the_image.jpg">
</div>
<button onclick="change_stuff();">
ThE dOER!!
</button>
In the W3C DOM (Document Object Model), everything is a "node". Nodes come in different types (comment nodes, element nodes, attribute nodes and even text nodes). It may seem counter-intuitive that an element like div that doesn't have any nested elements that can contain text inside it actually does implicitly have a child element within it that contains the raw text and that element is a text node.
In order to access that (which will be separate from other elements within the div, you can navigate to the div and look for (in this case, it's firstChild because the text comes first and the image is second.
Also, when it comes to replacing the original text with something else...You were trying to call the .replace() string function on the div and not the text within the div. You can isolate just the text of the div by navigating to the text node within it and working just on that.
function change_stuff() {
// Get a reference to the div element's text node which is a child node
// of the div.
var divText = document.getElementById('to_change').firstChild;
// Get the current text within the element:
var text = divText.textContent;
// You can do whatever you want with the text (in this case replace)
// but you must assign the result back to the element
divText.textContent = text.replace(text, "");
}
<div id="to_change">
This is a huge block of text that I want to replace while leaving the image in place
<img src="./the_image.jpg">
</div>
<button onclick="change_stuff();">
ThE dOER!!
</button>
Or the pragmatic:
function change_stuff() {
var div = document.getElementById('to_change'),
img = div.getElementsByTagName('img')[0];
div.innerHTML = "OMG...it's an image!";
div.appendChild(img);
}
<div id="to_change">
This is a huge block of text that I want to replace while leaving the image in place
<img src="./the_image.jpg">
</div>
<button type="button" onclick="change_stuff();">
ThE dOER!!
</button>
You need to use innerText to set the text within the div (i.e.: div.innerText = replacement).
See Node.textContent - Differences from innerText.
This question already has answers here:
InnerHTML append instead of replacing
(3 answers)
Closed 6 years ago.
i've a div container and a button. Whenever i click the button, an empty textbox is added to the div. Now, my problem is whenever i click the button, the textbox is added, but the values of all others are removed.
The function is made like this:
function addTextBox() {
document.getElementById("txtList").innerHTML += "<input type='text'>";
}
I think it help you:
var child = document.createElement('input')
document.getElementById("txtList").appendChild(child);
You could achieve the same thing as the snippet below:
function addTextBox() {
var input = document.createElement("input");
input.type = "text"
document.getElementById("txtList").appendChild(input);
}
document.getElementById("addTxtBoxBtn").addEventListener("click",addTextBox);
<input type="button" id="addTxtBoxBtn" value="add TextBox"/>
<div id="txtList">
</div>
Why you can't achieve the same thing with innerHTML?
This happens because:
The Element.innerHTML property sets or gets the HTML syntax describing the element's descendants.
While the valueof an ipunt element is not an attribute of the element but a property (please have a look here).
If you want to check it in action, please try the following snippet:
function addTextBox() {
var txtList = document.getElementById("txtList");
console.log(txtList.innerHTML);
txtList.innerHTML += "<input type='text'/>" ;
}
document.getElementById("addTxtBoxBtn").addEventListener("click",addTextBox);
<input type="button" id="addTxtBoxBtn" value="add TextBox"/>
<div id="txtList">
</div>
What is happening under the hood here is that when you append the DOM as text using innerHTML you are simply rewriting that section of HTML. Editing your textList innerHTML will execute a new paint of that element and all information will be parsed again. This means you loose your user interaction.
To update your DOM elements successfully there are methods which enable you to do that. namely document.createElement and document.appendChild.
By appending the DOM element as opposed to concatenating the innerHTML(text) your are forcing a limited paint of the specific area. This leaves the rest of the DOM in tact.
Your code here
function addTextBox() {
document.getElementById("txtList").innerHTML += "<input type='text'>";
}
Becomes more like the following
function addTextBox() {
var textEl = document.getElementById("txtList");
var input = document.createElement("input");
input.type = 'text';
textEl.appendChild(input);
}
When you change append to innerHTML as a string, another string gets created (they are immutable). Browser than has to re-render the whole thing.
The other answers show appendChild, but since in your original question you used a string, maybe you want to keep doing so. If that's the case, you can use insertAdjacentHTML with 'beforeend' as first argument.
document
.getElementById('button')
.addEventListener('click', () => {
document.getElementById('txtList')
.insertAdjacentHTML('beforeend', '<input type="text">');
});
JSBin link is here.
I'm trying to make a page that has some editabable fields, but I only want them to display as input boxes once the user clicks on them (the rest of the time showing as plain text). Is there a simple way to do this in Javascript?
Introduction
Fairly simple, yes. I can think of two basic approaches:
Using the contenteditable attribute
Using an input you add on-the-fly
Handy references for both of the below:
DOM2 Core
DOM2 HTML
DOM3 Core
HTML5 spec - "user interaction" section
Using the contenteditable attribute
The contentEditable attribute (W3C, MDC, MSDN) can be "true" indicating that the element can be edited directly. This has the advantage of not requiring any JavaScript at all (live example):
<p id="container">The <span contenteditable="true">colored items</span> in this paragraph
are <span contenteditable="true">editable</span>.</p>
Lest you think this is some l33t new thing, IE has supported it since IE 5.5 and other major browsers for very nearly that long. (In fact, this was one of many Microsoft innovations from the IE5.5 / IE6 timeframe; they also gave us innerHTML and Ajax.)
If you want to grab the (edited) content, you just grab innerHTML from the elements you've made editable. Here's an example of some JavaScript that will flag up when contenteditable spans blur (live copy):
var spans = document.getElementsByTagName("span"),
index,
span;
for (index = 0; index < spans.length; ++index) {
span = spans[index];
if (span.contentEditable) {
span.onblur = function() {
var text = this.innerHTML;
text = text.replace(/&/g, "&").replace(/</g, "<");
console.log("Content committed, span " +
(this.id || "anonymous") +
": '" +
text + "'");
};
}
}
#container span {
background-color: #ff6;
}
<p id="container">The <span id="span1" contenteditable="true">colored items</span> in this paragraph
are <span contenteditable="true">editable</span>.</p>
Using an input you add on-the-fly
You need to get a reference to the element that you're using for display (a span, perhaps) and then hook its click event (or hook the click event on a parent of the desired element(s)). In the click event, hide the span and insert a input[type=text] alongside it.
Here's a very simple example of using an input:
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.value = 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 == "" ? " " : input.value;
// Show the span again
span.style.display = "";
};
}
};
};
#container span {
background-color: #ff6;
}
<p id="container">The <span>colored items</span> in this paragraph
are <span>editable</span>.</p>
There I'm hooking the click on the parent p element, not the individual spans, because I wanted to have more than one and it's easier to do that. (It's called "event delegation.") You can find the various functions used above in the references I gave at the beginning of the answer.
In this case I used blur to take the edit down again, but you may wish to have an OK button and/or other triggers (like the Enter key).
Off-topic: You may have noticed in the JavaScript code above that I had to handle a couple of "MS differences" (e.g., things that IE does differently from other browsers), and I've used the old "DOM0" style of event handler where you just assign a function to a property, which isn't ideal, but it avoids my having to handle yet another difference where some versions of IE don't have the DOM2 addEventListener and so you have to fall back to attachEvent.
My point here is: You can smooth over browser differences and get a lot of utility functions as well by using a decent JavaScript library like jQuery, Prototype, YUI, Closure, or any of several others. You didn't say you were using any libraries, so I didn't in the above, but there are compelling reasons to use them so you don't have to worry about all the little browser niggles and can just get on with addressing your actual business need.
A trivial example using plain JavaScript would be along the lines of: http://jsfiddle.net/vzxW4/.
document.getElementById('test').onclick = function() {
document.body.removeChild(this);
var input = document.createElement('input');
input.id = 'test';
input.value = this.innerHTML;
document.body.appendChild(input);
input.select();
}
Using a library would save you time and headaches, though. For example, using jQuery: http://jsfiddle.net/vzxW4/1/.
$("#test").click(function() {
var input = $("<input>", { val: $(this).text(),
type: "text" });
$(this).replaceWith(input);
input.select();
});
Can we do it simple guys?
Just keep textbox with readonly property true and some CSS which makes text box looks like span with border.
Then as soon as user clicks on text box remove readonly attribute.
On blur restore the CSS and readonly attributes.
I have an element like this:
<td>
<a>anchor</a>
[ some text ]
</td>
And i need to set it's text in jQuery, without removing the anchor.
The element's contents could vary in order (text before or after), and the actual text is unknown.
Thanks
New Update
This is what i came up using, assumes only a single text node:
function setTextContents($elem, text) {
$elem.contents().filter(function() {
if (this.nodeType == Node.TEXT_NODE) {
this.nodeValue = text;
}
});
}
setTextContents( $('td'), "new text");
Neal's answer is my suggestion. jQuery doesn't have a way to select text nodes How do I select text nodes with jQuery?.
Changing your HTML structure will make for the simplest code. If you can't do it, you can just use the childNodes property looking for nodes of type 3 (TEXT_NODE)
Here's some sample code that assumes the last node is the node you want to edit. This is a better approach than replacing the entire contents of the td because you could lose event handlers when you recreate the HTML
$('a').click(() => console.log('<a> was clicked'))
$('#btn').click(() =>
$('.someClass').get(0).lastChild.nodeValue = " New Value");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='someClass'>
anchor [ some text ]
</div>
<button id='btn'> Change text without losing a tag's handler</button>
If it is possible to put the text in a span:
<td id='someID'>
<a>anchor</a>
<span>[ some text ]</span>
</td>
You can do:
$('td#someID span').text('new text')
Without changing markup:
Live Demo
var anchor = $('td').find('a').clone();
$('td').text('{ some other text }').prepend(anchor);