There is a main content editable div that displays certain templates with placeholders (underscores) - inside of which there may be other divs, also editable. The inside divs have a class - lets call it highlight.
I'd like to be able to tab through to the divs inside and type away.
I've given the inside divs a tabIndex which makes them tab-able but I'm not able to type unless I explicitly click them. I'd like to be able to do this using the keyboard only.
PS I need a JS solution, not Jquery
Replacing the inside divs with an input/ or text-area works perfectly, except, I'd like to use divs or spans
From another stackoverflow page, I attached an event listener, checking if the div had the class I was looking for, I called .blur() and .focus() on it explicitly - but that doesn't work
// partial code relevant to the problem
function contentEdit(text) {
....
// replace placeholder text (underscores) with div
// and maintain line breaks
text = text.replace(/(\r\n|\n|\r)/gm, '<br/>')
.replace(/[_]+/gm, '<div class="highlight" tabindex="0"> </div>');
vm.content = '<div contenteditable>' + text + '</div>';
var ce = document.getElementById('contenteditable-div');
// tried this approach from stackoverflow
ce.addEventListener('keydown', function(e) {
if (e.keyCode == 9) {
if (e.target.className == 'highlight') {
e.target.blur();
e.target.focus();
}
}
});
.......
}
The blur/ focus block only fires on the second time that condition is true - so, not on the first time a div of that condition is encountered.
Because of the tabIndex, the browser seems as if it's putting the focus on the div when tabbing through (there is a border highlight on it) but the input events don't fire unless you click the div. This is not the case with an input or a textarea - for some reason, they too come into focus but also take key inputs.
demo: codepen.io/anon/pen/bPEJjo
Any ideas/ suggestions why the inner div can't accept keyboard events or how to get them firing?
Listen keyup (better timing than with keydown) on the parent only, it'll bubble up from the child elements. Then create a range to the focused element and set the cursor to its position. Something like below:
function moveCursor(e) {
if (e.key !== 'Tab') {return;}
var element = document.activeElement,
range = document.createRange(),
selection = window.getSelection();
range.setStart(element, 0);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
element.focus();
}
document.getElementById('pad').addEventListener('keyup', moveCursor);
.ce {
position: relative;
width: 300px;
height: 50px;
border: 1px solid #000;
margin-bottom: 1em;
}
<div id="pad" contenteditable="true">
<div class="ce" tabindex="1"></div>
<div class="ce" tabindex="2"></div>
<div class="ce" tabindex="3"></div>
<div class="ce" tabindex="4"></div>
</div>
Related
I have an app that needs to be fully navigable by keyboard. When I click on the header of a div (generated by a javascript/jquery function), an event listener is triggered. I was able to highlight the headers with tab by adding the attributes role="button" and tabindex="0", but it's still only clickable by mouse. The ARIA documentation is kind of hard to follow and I don't have much time left.
HTML
<section id="js-item-root">
<div class="item">
<h2 class="item-header js-item-header" id="1" role="button" tabindex="1">Title of the First Div</div>
<p>This is a paragraph</p>
</div>
<div class="item">
<h2 class="item-header js-item-header" id="2" role="button" tabindex="2" >Title of the Second Div</div>
<p>This is also a paragraph</p>
</div>
</section>
CSS
.item {
border: solid black 2px;
margin: 15px;
padding: 15px;
}
.item-header {
border-bottom: solid black 1px;
background-color: lightgrey;
padding: 5px 0;
}
Javascript/Jquery:
function handleHeaderClick() {
$('#js-item-root').on('click', '.js-item-header', function(event) {
event.preventDefault();
console.log(this.id)
}
}
how to I get the console.log to work when I highlight the header with the tab key and press enter?
//Progress update detailed below
I was able to get my code working right by trading my <div>s for <button>s and setting the width to width: 100%, but I still want to learn a way to make a div ACT like a button. I tried creating a new function that sawpped the 'click' for a 'keypress', but that didn't work. Is there something else I'm missing?
Sometimes using a button element isn't possible - e.g. when you need to use block level elements such as headings and divs inside the button. For those times I use the following snippet to get back what you lose from using a real button:
// --------------------------------------------
// Allow role=button to behave like a real <button> el.
// by triggering the click on spacebar/enter keydown
// --------------------------------------------
document.querySelectorAll('div[role="button"]').forEach(el => {
el.addEventListener('keydown', e => {
const keyDown = e.key !== undefined ? e.key : e.keyCode;
if ( (keyDown === 'Enter' || keyDown === 13) || (['Spacebar', ' '].indexOf(keyDown) >= 0 || keyDown === 32)) {
// (prevent default so the page doesn't scroll when pressing space)
e.preventDefault();
el.click();
}
});
});
Is it possible to focus on a <div> using JavaScript focus() function?
I have a <div> tag
<div id="tries">You have 3 tries left</div>
I am trying to focus on the above <div> using :
document.getElementById('tries').focus();
But it doesn't work. Could someone suggest something....?
Yes - this is possible. In order to do it, you need to assign a tabindex...
<div tabindex="0">Hello World</div>
A tabindex of 0 will put the tag "in the natural tab order of the page". A higher number will give it a specific order of priority, where 1 will be the first, 2 second and so on.
You can also give a tabindex of -1, which will make the div only focus-able by script, not the user.
document.getElementById('test').onclick = function () {
document.getElementById('scripted').focus();
};
div:focus {
background-color: Aqua;
}
<div>Element X (not focusable)</div>
<div tabindex="0">Element Y (user or script focusable)</div>
<div tabindex="-1" id="scripted">Element Z (script-only focusable)</div>
<div id="test">Set Focus To Element Z</div>
Obviously, it is a shame to have an element you can focus by script that you can't focus by other input method (especially if a user is keyboard only or similarly constrained). There are also a whole bunch of standard elements that are focusable by default and have semantic information baked in to assist users. Use this knowledge wisely.
window.location.hash = '#tries';
This will scroll to the element in question, essentially "focus"ing it.
document.getElementById('tries').scrollIntoView() works. This works better than window.location.hash when you have fixed positioning.
You can use tabindex
<div tabindex="-1" id="tries"></div>
The tabindex value can allow for some interesting behaviour.
If given a value of "-1", the element can't be tabbed to but focus
can be given to the element programmatically (using element.focus()).
If given a value of 0, the element can be focused via the keyboard
and falls into the tabbing flow of the document. Values greater than
0 create a priority level with 1 being the most important.
<div id="inner" tabindex="0">
this div can now have focus and receive keyboard events
</div>
document.getElementById('test').onclick = function () {
document.getElementById('scripted').focus();
};
div:focus {
background-color: Aqua;
}
<div>Element X (not focusable)</div>
<div tabindex="0">Element Y (user or script focusable)</div>
<div tabindex="-1" id="scripted">Element Z (script-only focusable)</div>
<div id="test">Set Focus To Element Z</div>
I wanted to suggest something like Michael Shimmin's but without hardcoding things like the element, or the CSS that is applied to it.
I'm only using jQuery for add/remove class, if you don't want to use jquery, you just need a replacement for add/removeClass
--Javascript
function highlight(el, durationMs) {
el = $(el);
el.addClass('highlighted');
setTimeout(function() {
el.removeClass('highlighted')
}, durationMs || 1000);
}
highlight(document.getElementById('tries'));
--CSS
#tries {
border: 1px solid gray;
}
#tries.highlighted {
border: 3px solid red;
}
To make the border flash you can do this:
function focusTries() {
document.getElementById('tries').style.border = 'solid 1px #ff0000;'
setTimeout ( clearBorder(), 1000 );
}
function clearBorder() {
document.getElementById('tries').style.border = '';
}
This will make the border solid red for 1 second then remove it again.
I'm trying to make a toggle which works, but every element I click on creates a stack of these showed elements. Instead I'm trying to hide everything and display only element that I clicked on. Now I can only hide it when I click on the same element twice, which is not what I want. I want to click on one and hide previous ones that were showing.
.totalpoll-choice-image-2 is a bunch of images that always has to be shown. They are what the user clicks on to display hidden description under each image. That description shows up when I click on .totalpoll-choice-image-2. There are 5 images with that class. The next image I click on, I want to hide the previous description box.
My code:
jQuery(document).ready(function() {
var element = document.getElementsByClassName("totalpoll-choice-image-2");
var elements = Array.prototype.slice.call(Array.from( element ) );
console.log(elements);
jQuery(element).each(function(item) {
jQuery(this).unbind('click').click(function(e) {
e.stopPropagation();
var id = jQuery(this).attr("data-id");
console.log(this);
//jQuery("#" + id).css({"display": 'block !important'});
//document.getElementById(id).style.setProperty( 'display', 'block', 'important' );
var descriptionContainer = document.getElementById(id);
var thiss = jQuery(this);
console.log(thiss);
console.log(jQuery(descriptionContainer).not(thiss).hide());
jQuery(descriptionContainer).toggleClass("show");
});
})
})
You can attach event handlers to a group of DOM elements at once with jQuery. So in this case, mixing vanilla JS with jQuery isn't doing you any favors - though it is possible.
I threw together this little example of what it sounds like you're going for.
The script itself is very simple (shown below). The classes and IDs are different, but the idea should be the same:
// Assign click handlers to all items at once
$('.img').click(function(e){
// Turn off all the texts
$('.stuff').hide();
// Show the one you want
$('#' + $(e.target).data('id')).show();
})
https://codepen.io/meltingchocolate/pen/NyzKMp
You may also note that I extracted the ID from the data-id attribute using the .data() method, and attached the event listener with the .click() method. This is the typical way to apply event handlers across a group of jQuery objects.
From what I understood based on your comments you want to show only description of image that has been clicked.
Here is my solution
$('.container').on('click', 'img', function() {
$(this).closest('.container').find('.image-description').addClass('hidden');
$(this).siblings('p').removeClass('hidden');
});
https://jsfiddle.net/rtsj6r41/
Also please mind your jquery version, because unbind() is deprecated since 3.0
You can use event delegation so that you only add your event handler once to the parent of your images. This is usually the best method for keeping work the browser has to do down. Adding and removing classes is a clean method for show and hide, because you can see what is happening by looking at your html along with other benefits like being easily able to check if an item is visible with .hasClass().
jsfiddle: https://jsfiddle.net/0yL5zuab/17/
EXAMPLE HTML
< div class="main" >
<div class="image-parent">
<div class="image">
</div>
<div class="image-descr">
Some text. Some text. Some text.
</div>
</div>
<div class="image-parent">
<div class="image">
</div>
<div class="image-descr">
Some text. Some text. Some text.
</div>
</div>
<div class="image-parent">
<div class="image">
</div>
<div class="image-descr">
Some text. Some text. Some text.
</div>
</div>
<div class="clear">
</div>
</div>
EXAMPLE CSS
.image-parent{
height: 100px;
width: 200px;
float: left;
margin: 5px;
}
.image-parent .image{
background: blue;
height: 50%;
width: 100%;
}
.image-descr{
display: none;
height: 50%;
width: 100%;
}
.show-descr{
display: block;
}
.clear{
clear: both;
}
EXAMPLE JQUERY
$(".main").on("click", ".image-parent", ShowDescription);
function ShowDescription(e) {
var $parent = $(e.target).parent(".image-parent");
var $desc = $parent.find(".image-descr");
$(".image-descr").removeClass("show-descr");
$desc.addClass("show-descr");
}
I'm trying to implement a dropdown which you can click outside to close. The dropdown is part of a custom date input and is encapsulated inside the input's shadow DOM.
I want to write something like:
window.addEventListener('mousedown', function (evt) {
if (!componentNode.contains(evt.target)) {
closeDropdown();
}
});
however, the event is retargeted, so evt.target is always the outside the element. There are multiple shadow boundaries that the event will cross before reaching the window, so there seems to be no way of actually knowing if the user clicked inside my component or not.
Note: I'm not using polymer anywhere -- I need an answer which applies to generic shadow DOM, not a polymer specific hack.
You can try using the path property of the event object. Haven't found a actual reference for it and MDN doesn't yet have a page for it. HTML5Rocks has a small section about it in there shadow dom tutorials though. As such I do not know the compatibility of this across browsers.
Found the W3 Spec about event paths, not sure if this is meant exactly for the Event.path property or not, but it is the closest reference I could find.
If anyone knows an actual spec reference to Event.path (if the linked spec page isn't already it) feel free to edit it in.
It holds the path the event went through. It will contain elements that are in a shadow dom. The first element in the list ( path[0] ) should be the element that was actually clicked on. Note you will need to call contains from the shadow dom reference, eg shadowRoot.contains(e.path[0]) or some sub element within your shadow dom.
Demo: Click menu to expand, clicking anywhere except on the menu items will close menu.
var host = document.querySelector('#host');
var root = host.createShadowRoot();
d = document.createElement("div");
d.id = "shadowdiv";
d.innerHTML = `
<div id="menu">
<div class="menu-item menu-toggle">Menu</div>
<div class="menu-item">Item 1</div>
<div class="menu-item">Item 2</div>
<div class="menu-item">Item 3</div>
</div>
<div id="other">Other shadow element</div>
`;
var menuToggle = d.querySelector(".menu-toggle");
var menu = d.querySelector("#menu");
menuToggle.addEventListener("click",function(e){
menu.classList.toggle("active");
});
root.appendChild(d)
//Use document instead of window
document.addEventListener("click",function(e){
if(!menu.contains(e.path[0])){
menu.classList.remove("active");
}
});
#host::shadow #menu{
height:24px;
width:150px;
transition:height 1s;
overflow:hidden;
background:black;
color:white;
}
#host::shadow #menu.active {
height:300px;
}
#host::shadow #menu .menu-item {
height:24px;
text-align:center;
line-height:24px;
}
#host::shadow #other {
position:absolute;
right:100px;
top:0px;
background:yellow;
width:100px;
height:32px;
font-size:12px;
padding:4px;
}
<div id="host"></div>
Can't comment because of reputation, but wanted to share how it should look using composedPath. See Determine if user clicked outside shadow dom
document.addEventListener("click",function(e){
if(!e.composedPath().includes(menu)){
menu.classList.remove("active");
}
});
The event.target of the shadowRoot would be the host element. To close a <select> element within shadowDOM if event.target is not host element you can use if (evt.target !== hostElement), then call .blur() on hostElement
var input = document.querySelector("input");
var shadow = input.createShadowRoot();
var template = document.querySelector("template");
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);
window.addEventListener("mousedown", function (evt) {
if (evt.target !== input) {
input.blur();
}
});
<input type="date" />
<template>
<select>
<option value="1999">1999</option>
<option value="2000">2000</option>
</select>
</template>
Another option is to check the event cursor offsets against the target element:
listener(event) {
const { top, right, bottom, left } = targetElement.getBoundingClientRect();
const { pageX, pageY } = event;
const isInside = pageX >= left && pageX <= right && pageY >= top && pageY <= bottom;
}
I'm trying to create a list where users can type text in a form field, and the next form field appears underneath it everytime user presses enter. This is my code:
HTML:
<body>
<form class="list">
<input type="text" name="list">
</form>
</body>
CSS:
.list {
margin-left: auto;
margin-right: auto;
margin-top: 230px;
width: 305px;
border-radius: 2px;
}
form input {
height: 40px;
width: 300px;
background-color: blue;
color: white;
font-size: 15px;
font-family: helvetica;
}
JS:
$(document).ready(function(){
$('form input').keydown(function(e){
if(e.keyCode == 13) {
var store = $(this).val();
$(this).parents().append($('input'));
};
});
})
This gives me 2 new unformatted boxes, instead of one formatted box underneath it. Pressing "enter" on the new unformatted boxes gives me newer unformatted boxes and messes up the page. What's wrong with the code?
You have a few issues.
You are using parents which will select all parents of the element. I believe you really just want the direct parent, so use just parent. (This is why 2 elements are created instead of one as the element has 2 parents in addition to the form, body and html).
By doing append($('input')) you are appending your existing input(s) and not creating a new input. You should instead do append($('<input/>')) to create a new input and append that.
With these changes made, you now need to use on for event delegation in order to have the keydown handler affect your dynamically created inputs. (more information on event delegation in the on documentation.)
With those changes you will end up with this:
$('form').on('keydown', 'input', function(e){
if(e.keyCode == 13) {
var store = $(this).val();
$(this).parent().append($('<input/>'));
};
});
http://jsfiddle.net/XjpG7/
Instead of appending, why don't you try having the box you want to show up already in your HTML, but just hidden by default, then use jQuery to show it, something like:
$(document).ready(function(){
$('.someOtherForm').hide();
$('form input').keydown(function(e){
if(e.keyCode == 13) {
var store = $(this).val();
$('.someOtherForm').show();
};
});
})
Just be sure to also include whatever other form you want in your html file to hide and show and you should be good to go.