My HTML has a container element with many sibling div
elements, each of which contains a contenteditable
p
. These sibling div
are "interrupted", however, by other div
which do not contain an editable element.
What is challenging me at the moment is how to "hop over" these interrupting div
when using the left and right arrow keys to move from C to D or from D back to C (see snippet). Navigation stops when it encounters these div
lacking an editable element. How can I correct this?
$('#foo p[contenteditable = "true"]').bind("keydown", function(e) {
switch (e.key) {
case "ArrowLeft":
var $P = $(this).parent().prev().children('p[contenteditable = "true"]');
setTimeout(() => $P.focus(), 0);
e.stopImmediatePropagation();
e.preventDefault();
break;
case "ArrowRight":
var $P = $(this).parent().next().children('p[contenteditable = "true"]');
setTimeout(() => $P.focus(), 0);
e.stopImmediatePropagation();
e.preventDefault();
break;
}
});
#foo p[contenteditable="true"] {
/* font-size: 22px;*/
height: 22px;
width: 22px;
color: cyan;
font-weight: bold;
background-color: cyan;
}
#foo p.const {
background-color: inherit;
border: none;
height: 22px;
width: 22px;
}
#foo div {
text-align: center;
display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="foo">
<div>
<p contenteditable="true" tabindex="0"></p>
<p>A</p>
</div>
<div>
<p contenteditable="true" tabindex="1"></p>
<p>B</p>
</div>
<div>
<p contenteditable="true" tabindex="2"></p>
<p>C</p>
</div>
<div>
<p class="const"> </p>
<p> </p>
</div>
<div>
<p class="const">"</p>
<p>"</p>
</div>
<div>
<p contenteditable="true" tabindex="3"></p>
<p>D</p>
</div>
<div>
<p contenteditable="true" tabindex="4"></p>
<p>E</p>
</div>
</div>
Instead of prev()
or next()
, use .prevAll(":has(p[contenteditable])").first()
and .nextAll(":has(p[contenteditable])").first()
:
$('#foo p[contenteditable = "true"]').bind("keydown", function(e) {
switch (e.key) {
case "ArrowLeft":
var $P = $(this).parent().prevAll(":has(p[contenteditable])").first().children('p[contenteditable = "true"]');
setTimeout(() => $P.focus(), 0);
e.stopImmediatePropagation();
e.preventDefault();
break;
case "ArrowRight":
var $P = $(this).parent().nextAll(":has(p[contenteditable])").first().children('p[contenteditable = "true"]');
setTimeout(() => $P.focus(), 0);
e.stopImmediatePropagation();
e.preventDefault();
break;
}
});
#foo p[contenteditable="true"] {
/* font-size: 22px;*/
height: 22px;
width: 22px;
color: cyan;
font-weight: bold;
background-color: cyan;
}
#foo p.const {
background-color: inherit;
border: none;
height: 22px;
width: 22px;
}
#foo div {
text-align: center;
display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="foo">
<div>
<p contenteditable="true" tabindex="0"></p>
<p>A</p>
</div>
<div>
<p contenteditable="true" tabindex="1"></p>
<p>B</p>
</div>
<div>
<p contenteditable="true" tabindex="2"></p>
<p>C</p>
</div>
<div>
<p class="const"> </p>
<p>
<p>
</div>
<div>
<p class="const">"</p>
<p>"
<p>
</div>
<div>
<p contenteditable="true" tabindex="3"></p>
<p>D</p>
</div>
<div>
<p contenteditable="true" tabindex="4"></p>
<p>E</p>
</div>
</div>
prevAll
/nextAll
match all previous/following siblings that match the selector, returning a jQuery object in order (so the nearest sibling is first, then the second nearest, etc.); using .first()
then reduces the set to just the nearest sibling.
I've used the :has
pseudo-selector in that, but you might be better off giving the relevant elements a class instead, since :has
is a jQuery-specific addition to CSS. Here I've used the class stop
so it's .xxxxAll(".stop").first()
:
$('#foo p[contenteditable = "true"]').bind("keydown", function(e) {
switch (e.key) {
case "ArrowLeft":
var $P = $(this).parent().prevAll(".stop").first().children('p[contenteditable = "true"]');
setTimeout(() => $P.focus(), 0);
e.stopImmediatePropagation();
e.preventDefault();
break;
case "ArrowRight":
var $P = $(this).parent().nextAll(".stop").first().children('p[contenteditable = "true"]');
setTimeout(() => $P.focus(), 0);
e.stopImmediatePropagation();
e.preventDefault();
break;
}
});
#foo p[contenteditable="true"] {
/* font-size: 22px;*/
height: 22px;
width: 22px;
color: cyan;
font-weight: bold;
background-color: cyan;
}
#foo p.const {
background-color: inherit;
border: none;
height: 22px;
width: 22px;
}
#foo div {
text-align: center;
display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="foo">
<div class="stop">
<p contenteditable="true" tabindex="0"></p>
<p>A</p>
</div>
<div class="stop">
<p contenteditable="true" tabindex="1"></p>
<p>B</p>
</div>
<div class="stop">
<p contenteditable="true" tabindex="2"></p>
<p>C</p>
</div>
<div>
<p class="const"> </p>
<p>
<p>
</div>
<div>
<p class="const">"</p>
<p>"
<p>
</div>
<div class="stop">
<p contenteditable="true" tabindex="3"></p>
<p>D</p>
</div>
<div class="stop">
<p contenteditable="true" tabindex="4"></p>
<p>E</p>
</div>
</div>