contentEditable set caret inside a particular element - javascript

I am unable to set caret into the first <p> in the contentEditable <div>.
I have seen this solution but its for before or after the element. How do I get it into an element?
Here is what I have so far:
$('#content').on('click', function(){
if($('#placeholder').length > 0)
$('#placeholder').removeAttr('id').text('').focus();
});
#content{
border: 1px solid black;
min-height: 100px;
padding: 20px;
}
#placeholder{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='content' contentEditable=true>
<p id='placeholder'>placeholder</p>
</div>
It clears the placeholder and doesnt set the caret at all.
How do I fix this?

The problem you have is that you p element has no content and so has 0px height. Watching the console you can see that contenteditable often add a <br> when clicking on it, you can do the same here and you'll have your caret.
$('#content').on('click', function(){
if($('#placeholder').length > 0)
$('#placeholder').removeAttr('id').html('<br>').focus();
});
#content{
border: 1px solid black;
min-height: 100px;
padding: 20px;
}
#placeholder{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='content' contentEditable=true>
<p id='placeholder'>placeholder</p>
</div>

Before you read any further please address to Range (MDN) and getSelection (MDN).
This is a working fiddle.
Code inside the fiddle is very self-explainatory, but what I found interesting was: let's say we have
<p id='paragraph'>I want to focus on this text</p>
then we do (as expected):
var p = $('#paragraph');
range.selectNode(p[0]);
This is actually going to put caret (roughly speaking) before <p>, but we need to get inside <p>.
Surprisingly...
var p = $('#paragraph');
var textInsideP = p.childNodes[0]; // the text inside p counts as a ChildNode
range.selectNode(textInsideP);
Boom.

Related

Affecting whole div block with ondblclick function

I have recently started learning DOM and I have seen some examples of it, however, I'm trying to make a function (getting id) which would trigger after being double clicked.
This is the CSS, HTML and JavaScript codes I'm using.
function getID() {
var x = document.getElementsByClassName("blueblock")[0].id;
document.getElementById("xx").innerHTML = x;
.blueblock {
width: 30%;
height: 50vh;
float: left;
background-color: lightblue;
text-align: justify;
overflow: auto;
}
<p id="xx" ondblclick="getID()">
<div class="blueblock" id="bluebl">
<p>Just some text inside of the block.</p>
</div>
How should I change my code so that clicking on any part of the blueblock would trigger the function and output the id value?
This happens because the <p> tag you have does not have a content. If you would add text to the <p> and double click the text it will work.
The solution for this is to use div instead of p:
function getID() {
var x = document.getElementsByClassName("blueblock")[0].id;
document.getElementById("xx").innerText = x;
}
.blueblock {
width: 30%;
height: 50vh;
float: left;
background-color: lightblue;
text-align: justify;
overflow: auto;
}
<div id="xx" ondblclick="getID()">
<div class="blueblock" id="bluebl">
<p>Just some text inside of the block.</p>
</div>
</div>
You need to have valid html element nesting, and you should probably accomodate for more than one of these sections. Here is an example.
function getID(el) {
var x = el.getElementsByClassName("blueblock")[0].id;
document.getElementById(el.id).innerHTML = x;
}
.blueblock {
width:30%;
height:50vh;
float:left;
background-color:lightblue;
text-align:justify;
overflow:auto;
}
<div id="xx" ondblclick="getID(this)">
<div class="blueblock" id="bluebl">
<p>Just some text inside of the block.</p>
</div>
</div>
<div id="xx2" ondblclick="getID(this)">
<div class="blueblock" id="bluebl2">
<p>Just some more text inside of the block.</p>
</div>
</div>
The first p element is basically terminated by the next div element because a p (paragraph equivalent) cannot contain divs. Hence the double click code is not seen because effectively the first p element has no content.
Replacing that p element by a div, and terminating correctly, means that anything within the div will lead to the double click being seen.
However, note that ondblclick is not supported by all browsers (see https://caniuse.com/?search=ondblclick) so we replace that by adding an event listener to the element using Javascript.
Here is the complete snippet. Note that when you have double clicked the innerHTML gets replaced and therefore if you doubleclick again you will see an error in your browser's console as the element cannot be found - it is no longer there.
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script>
function getID() {
var x = document.getElementsByClassName("blueblock")[0].id;
document.getElementById("xx").innerHTML = x;
}
</script>
<style>
.blueblock {
width: 30%;
height: 50vh;
float: left;
background-color: lightblue;
text-align: justify;
overflow: auto;
}
</style>
</head>
<body>
<div id="xx">
<div class="blueblock" id="bluebl">
<p>Just some text inside of the block.</p>
</div>
</div>
<script>
document.getElementById('xx').addEventListener('dblclick',getID);
</script>
</body>
</html>

Is there a way to make SVG className backward compatible?

Many old libraries rely on className to identify their context.
So a click handler can look like this:
function click(event)
{
if (event.target.className.indexOf('something')!= -1){
// Do something
}
}
But this will fail if the target element is an svg element.
The reason is that svg className is an object of type SVGAnimatedString
Is there any temporary workaround to avoid issues with old code?
Change the handler is not an option as it is unclear how many libraries have this code and changing library code could be impossible.
Changing the SVG to some other element is not an option as the SVG is a part of a 3rd party control.
Update:
"Is there any temporary workaround to avoid issues with old code?"
Seems unclear based on the comments. My goal is to see if there is any polyfill or any other technique that I can use to temporarily make SVG elements have their className as string until 3rd party libraries catch up. Then I will update the 3rd party libraries and revert this code.
As of now - simply overwriting the className doesn't seem to be possible as it only seems to have getter and no setter for SVG elements.
function oldModuleHandler(e) {
if (e.className.indexOf("initial") > -1 || e.className.indexOf("fixed") > -1) {
alert('Behaves as expected.');
}
}
function theFix(e) {
e.className = "fixed";
}
<html>
<head>
</head>
<body>
<div style="border:2px solid silver;">
<h2>Demo:</h2>
Click on the left square, it is a div and it will have it's color changed to green because .className.indexOf will go through just fine. Same does not apply for SVG.<br/> <br/><br/>
<div onclick="theFix(this); oldModuleHandler(this)" class="initial">
</div>
<svg class="initial" onclick="theFix(this); oldModuleHandler(this)"></svg>
<div style="clear:both;"></div>
</div>
<style>
.fixed {
background: green !important;
width: 80px;
height: 80px;
float: left;
}
.initial {
background: transparent;
border: 1px solid black;
width: 80px;
height: 80px;
float: left;
}
</style>
</body>
</html>
Update 2
I added a small code to demonstrate the issue. If you click on the left square (it is a div) - it will work just fine. If you click on the right square - the SVG - it will not work because .className.indexOf() will throw an error.
You might use getAttribute and setAttribute OR 'classList methods :
function oldModuleHandler(e) {
var c = e.getAttribute('class');
if (~c.indexOf("initial") || ~c.indexOf("fixed")) {
alert('Behaves as expected.');
}
}
function theFix(e) {
e.className = "fixed";
e.setAttribute('class', 'fixed')
// OR
e.classList.add('fixed')
}
.fixed {
background: green !important;
width: 80px;
height: 80px;
float: left;
}
.initial {
background: transparent;
border: 1px solid black;
width: 80px;
height: 80px;
float: left;
}
<div style="border:2px solid silver;">
<h2>Demo:</h2>
Click on the left square, it is a div and it will have it's color changed to green because .className.indexOf will go through just fine. Same does not apply for SVG.<br/> <br/><br/>
<div onclick="theFix(this); oldModuleHandler(this)" class="initial">
</div>
<svg class="initial" onclick="theFix(this); oldModuleHandler(this)"></svg>
<div style="clear:both;"></div>
</div>
And/Or look at Proxy API.

Input in textarea does not display as typed in

I have an inputarea for user to type some text. However, when the text gets updated it does not shows as the user has type in. For example, the new lines are not showing up.
Here is what the user typed in:
And here is how it shows up after update
(Everything goes into one line.):
Can someone please advise how to fix this?
You're going to need to convert the new lines to br's or you can put the text in a pre element (or an element with white-space: pre* CSS).
$('textarea').on('keyup',function() {
var text = $(this).val();
$('pre').html(text);
$('article').html(text);
$('div').html(text.replace(/(?:\r\n|\r|\n)/g, '<br />'))
})
* {margin:0;padding:0;}
pre {
border: 1px solid blue;
}
div {
border: 1px solid red;
}
article {
border: 1px solid green;
white-space: pre-line;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea></textarea>
<pre></pre>
<div></div>
<article></article>

Edit cursor not displayed on Chrome in contenteditable

When you open this page (see Live demo) with Chrome :
<span id="myspan" contenteditable=true></span>
CSS :
#myspan { border: 0; outline: 0;}
JS :
$(myspan).focus();
the contenteditable span has focus (you can start to write things and you will see that it already had focus), but we don't see the "I" edit cursor.
How to make that this cursor is displayed ? (Remark : outline:0 is needed, as well as the fact that the span is empty even with no white space).
Note : With Firefox, the cursor is displayed.
The problem is that spans are inline elements. Just add display:block; to your CSS and it will fix the problem.
$(myspan).focus();
#myspan {
border: 0;
outline: 0;
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<span id="myspan" contenteditable=true></span>
I added padding to the left and the cursor appears.
#myspan
{
border: 0;
outline: 0;
min-width: 100px;
height: 30px;
padding-left: 1px;
}
Demo in jsFiddle
.cont_edit {
outline: 1px solid transparent;
}
This just has to do with the way an empty ContentEditable area is rendered. To prove it's not about the focus, add some text to your editable div, and then delete it. When the last character is gone, the cursor will disappear
From the question Setting the caret position to an empty node inside a contentEditable element
The selection/range model is based around indexes into text content, disregarding element boundaries. I believe it may be impossible to set the input focus inside an inline element with no text in it. Certainly with your example I cannot set focus inside the last element by clicking or arrow keys.
It almost works if you set each span to display: block, though there's still some highly strange behaviour, dependent on the existence of whitespace in the parent. Hacking the display to look inline with tricks like float, inline-block and absolute position make IE treat each element as a separate editing box. Relative-positioned block elements next to each other work, but that's probably impractical.
You could also try adding a zero-width character like ​
document.getElementById('myspan').focus();
#myspan {
border: 0;
outline: 0;
}
<span id="myspan" contenteditable="true">​</span>
The solution was to change <span> to <div> (I've seen that this solves many contenteditable problems in other questions here and there) + to add a min-width.
Indeed, with the following code, the size of the <div> would be 0px x 18px ! That explains why the caret (edit cursor) would be hidden !
HTML
<div id="blah" contenteditable=true></div>
CSS
#blah {
outline: 0;
position: absolute;
top:10px;
left:10px;
}
JS
$("#blah").focus();
Then, adding
min-width: 2px;
in the CSS will allow the caret to be displayed, even with Chrome : http://jsfiddle.net/38e9mkf4/2/
The issue I faced on Chrome v89.0.4389.90 was that contenteditable fields would sometimes show the blinking caret on focusin and sometimes not. I noticed it always blinks when there's already content in the field before focusing. It's when there's no content that the sometimes will/won't behavior occurs.
At first, I thought there must be some conflicting event handler that's erratically taking focus away. I disabled all my event binds and timers. Still the same erratic behavior. Then I thought it might be some conflicting CSS, so I disabled all stylesheets. At least now the behavior was consistent: the caret blinks 100% of the time when the field has content; the caret does not blink 100% of the time when the field has no content.
I enabled binds and stylesheets again. My div was already set to display: block; with min-width, min-height, and padding set in the final computed style set. None of the other answers here worked. I do have a placeholder on :empty:before that was a possible culprit. I commented that out. Now the behavior was consistent again, same as if the stylesheet was off. Oddly enough, the runnable snippet on SO works with the same computed CSS stack. I want to keep the placeholder, so it requires further research with my actual codebase...
The only solution I could get to work 100% of the time with my current issue involved forcibly placing the caret inside empty fields by creating a blank space and removing it immediately afterwards. Best I can do for a workaround until debugging the root cause.
//force caret to blink inside masks
let force_caret = function() {
if (!this.textContent) {
this.textContent = ' ';
let r = document.createRange(),
s = window.getSelection();
r.setStart(this.childNodes[0], 0);
r.collapse(true);
s.removeAllRanges();
s.addRange(r);
this.textContent = '';
}
}
//binds
let els = document.querySelectorAll("[contenteditable]");
for (let i = 0; i < els.length; i++) {
els[i].addEventListener('focusin', force_caret, false);
}
/* styles irrelevant to the issue, added for visual assist */
:root {
--b-soft: 1px solid silver;
--bs-in: inset 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px rgba(255, 255, 255, 0.1);
--c-soft: gray;
--lg-warm: linear-gradient(30deg, rgb(254, 250, 250), #eedddd);
}
body {
font-family: system-ui, -apple-system, -apple-system-font, 'Segoe UI', 'Roboto', sans-serif;
}
[contenteditable] {
outline: initial;
}
[contenteditable][placeholder]:empty:before {
content: attr(placeholder);
color: var(--c-soft);
background-color: transparent;
font-style: italic;
opacity: .5;
font-size: .9em;
}
.input {
border-bottom: var(--b-soft);
padding: .2em .5em;
}
.input_mask {
display: flex;
align-items: baseline;
color: var(--c-soft);
}
.mask {
box-shadow: var(--bs-in);
border-radius: .2em;
background: var(--lg-warm);
font-weight: 500;
border: 1px solid transparent;
text-transform: uppercase;
/* styles possibly relevant to the issue according to other proposed solutions */
margin: 0 .4em .1em .4em;
padding: .2em .4em;
min-width: 3em;
min-height: 1em;
text-align: center;
}
<div data-type="tel" data-id="phone" class="input input_mask">
<span>+1 (</span>
<div maxlength="3" contenteditable="true" placeholder="111" class="mask"></div>
<span>)</span>
<div maxlength="3" contenteditable="true" placeholder="111" class="mask"></div>
<span>-</span>
<div maxlength="4" contenteditable="true" placeholder="1111" class="mask"></div>
<span>x</span>
<div maxlength="5" contenteditable="true" class="mask"></div>
</div>
Add a CSS style of
min-height: 15px;
you may also need
display: block;
to your contenteditable="true" tag
For me setting it content of contenteditable div to <br> works. I tried setting it to nbsp; but that creates extra character space in the div before i start editing. So, i choose this:
<div id="blah" contenteditable=true><br></div>
over:
<div id="blah" contenteditable=true>nbsp;</div>
Hope this helps.
I use Chrome and your Code works fine.
Try to use cursor: text; in your CSS. See here

I need these buttons to control which div is showing or "on top"

I have these buttons on the side of my page, and a main content area taking up the better part of the page.
What I am trying to do is get the button I click to change the main content to a div containing the corresponding information. This is very hard to find, perhaps because I am searching by the wrong terms, and I have covered a good portion of stackoverflow without much luck.
I have though about absolutely positioning the divs and using a script to change the z-index of the the divs to the highest amount using a "=+1" type situation, but I could see that getting messy.
I have considered adapting a script I have that replaces part of an image file name in order to change a main picture on a page to a larger version of the image corresponding to a thumb name, though this script targets file names so it isn't going well.
I have also tried something along the lines of:
"id of button" onclick function = "main content class" change id to "corresponding div"
only in javascript talk, and this isn't working at all so I can only assume that I am either looking at it wrong or I have some messed up in the code.
$('#tabhead1').click(function() {
document.getElementByClassName("maintab").id = "tabs1";
});
This is driving me crazy and I would really appreciate some ideas. I tried to leave it free formed so that noone gets hung up on anyone solution.
**** Just to clarify, I have 5 divs id'd at #tabhead1, #tabhead2, #tabhead3, etc. and 5 content divs classed as .maintab, and id'd as tabs1, tabs2, tabs3, etc. I need the first content div to show automatically, and for that div to change based on the button clicked. at the moment all content divs are set to display: none; except the first one.
For each button, add a data attribute related to the corresponding <div>
for example
<button id="tabhead1" data-content="tabs1" >first Tab</button>
apply a common class for the tabs, for example .tab
Then you can do the following
$('button').click(function(){
var contentId = $(this).data('content'); // get the id of corresponding tab
$('.tab').hide(); // hide all tabs
$('#'+contentId).show(); //show the corresponding tab
});
You are using getElementbyClassName which does not exists. Use:
document.getElementsByClassName("maintab")[0].id = "tabs1";
// Get all elements to match classname + get first element from array
And for the rest, I don't know why you want to add id with JS? Why not just add them to your HTML?
Try this
$('#tabhead1').click(function() {
// get element with class 'maintab' and replace its content with that of another tab
$(".maintab").html($(".tabs1").html());
});
To expand a little on the demo I posted in the comments earlier:
This uses a method very similar to #tilwin-joy, so I guess we were of like mindedness. There are a couple of small differences that I would point out:
jQuery:
$('button').on('click', function () {
var button = $(this);
var target = button.data('target');
button.prop('disabled', true).siblings().prop('disabled', false);
$(target).show('slow').siblings().hide();
});
This uses siblings to hide the other content (one less pass at the DOM).
I suggest just setting your data value with the id hash in the markup, I think it's a bit clearer to read and follow (IMHO) in both the script and markup.
This script also sets the current button to be disabled when clicked. The benefit of this is that you can use the disabled property to style up your buttons, and even if you don't style them it gives a visual cue to the user as to which tab content is currently displayed. Check out the demo to see how this can be used for styling purposes.
HTML: (I stripped some of the unneeded ids from what you described as your markup).
<div class="tabhead">
<button data-target="#tabs1" disabled="true">Content 1</button>
<button data-target="#tabs2">Content 2</button>
<button data-target="#tabs3">Content 3</button>
<button data-target="#tabs4">Content 4</button>
<button data-target="#tabs5">Content 5</button>
</div>
<div class="maintab">
<div id="tabs1">
<img src="http://placehold.it/350/e8117f/fff&text=Image+1" alt="Image 1" />
<p>This is the content of tabs1.</p>
</div>
<div id="tabs2">
<img src="http://placehold.it/350/9acd32/fff&text=Image+2" alt="Image 2" />
<p>This is the content of tabs2.</p>
</div>
<div id="tabs3">
<img src="http://placehold.it/350/9400d3/fff&text=Image+3" alt="Image 3" />
<p>This is the content of tabs3.</p>
</div>
<div id="tabs4">
<img src="http://placehold.it/350/ffd700/fff&text=Image+4" alt="Image 4" />
<p>This is the content of tabs4.</p>
</div>
<div id="tabs5">
<img src="http://placehold.it/350/1e90ff/fff&text=Image+5" alt="Image 5" />
<p>This is the content of tabs5.</p>
</div>
</div>
CSS: Not needed - just to give you an idea of how you can style the elements to look like tabs.
/*This sets all but the first tab to hidden when the page is loaded*/
.maintab>div:not(:first-child) {
display: none;
}
/*The rest is just to style the elements to look like tabs*/
body {
background-color: #eaeaea;
}
.maintab, .tabhead {
text-align: center;
margin:0 20px;
font-family: sans-serif;
}
.maintab {
border: 1px solid #1e90ff;
border-top: none;
padding-top: 20px;
background-color: #fff;
}
.tabhead {
border-bottom: 1px solid #1e90ff;
position: relative;
margin-top: 20px;
}
button {
background-color: #ccc;
padding: 10px;
border: 1px solid #999;
border-bottom: none;
-webkit-border-top-left-radius: 4px;
-webkit-border-top-right-radius: 4px;
-moz-border-radius-topleft: 4px;
-moz-border-radius-topright: 4px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
color: #999;
font-size: 14px;
cursor: pointer;
position: relative;
top: 2px;
}
button:disabled {
background-color: #fff;
border-color: #1e90ff;
color: #1e90ff;
top: 3px;
padding-top: 11px;
cursor: not-allowed;
z-index: 10;
}

Categories