So I was wondering how I could place a svg toggle button inside the input['password'] like this
and when this is clicked, it would be displayed as this:
I've made a simple version, with a checkbox, but I don't know how to do this with a svg, and make it toggle.
The svg images I use is posted in the code.
function togglePass() {
var x = document.getElementById("login-form-password");
if (x.type === "password") {
x.type = "text";
} else {
x.type = "password";
}
}
<p class="signin_title">Sign in</p>
<input type="text" id="login-form-username" name="os_username" placeholder="Username" required><br><br>
<!-- Password -->
<input type="password" id="login-form-password" name="os_password" placeholder="Password" required><br>
<!-- An element to toggle between password visibility -->
<input type="checkbox" onclick="togglePass()">Show Password
<br><p>When password is hidden</p>
<svg id="Layer_1" data-name="Layer 1" width="25" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>eye-glyph</title><path d="M320,256a64,64,0,1,1-64-64A64.07,64.07,0,0,1,320,256Zm189.81,9.42C460.86,364.89,363.6,426.67,256,426.67S51.14,364.89,2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.14,147.11,148.4,85.33,256,85.33s204.86,61.78,253.81,161.25A21.33,21.33,0,0,1,509.81,265.42ZM362.67,256A106.67,106.67,0,1,0,256,362.67,106.79,106.79,0,0,0,362.67,256Z"/></svg><br>
<p>When password is shown </p>
<svg id="Layer_1" data-name="Layer 1" width="25" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>eye-disabled-glyph</title><path d="M409.84,132.33l95.91-95.91A21.33,21.33,0,1,0,475.58,6.25L6.25,475.58a21.33,21.33,0,1,0,30.17,30.17L140.77,401.4A275.84,275.84,0,0,0,256,426.67c107.6,0,204.85-61.78,253.81-161.25a21.33,21.33,0,0,0,0-18.83A291,291,0,0,0,409.84,132.33ZM256,362.67a105.78,105.78,0,0,1-58.7-17.8l31.21-31.21A63.29,63.29,0,0,0,256,320a64.07,64.07,0,0,0,64-64,63.28,63.28,0,0,0-6.34-27.49l31.21-31.21A106.45,106.45,0,0,1,256,362.67ZM2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.15,147.11,148.4,85.33,256,85.33a277,277,0,0,1,70.4,9.22l-55.88,55.88A105.9,105.9,0,0,0,150.44,270.52L67.88,353.08A295.2,295.2,0,0,1,2.19,265.42Z"/></svg>
So how exactly do I create this using svg?
Thought about maybe if its possible to use the checkbox, and with some js look if its clicked or not, and change the svg depending on that? and then with some css, try to move it inside the input['password']?
`
Something like this but with a svg
input[type="text"] {
width: 200px;
height: 20px;
padding-right: 50px;
}
input[type="submit"] {
margin-left: -50px;
height: 25px;
width: 50px;
background: blue;
color: white;
border: 0;
-webkit-appearance: none;
}
<input type="text"><input type="submit" value="SVG">
First of all, make sure an id is only used once! (Second svg changed)
You can hide and show the desired <svg> onclick
Wrap the input and svg's into a container for styling
I've moved the onclick to the <svg>
var x = document.getElementById("login-form-password"); // Input
var s = document.getElementById("Layer_1"); // Show pass
var h = document.getElementById("Layer_2"); // Hide pass
function togglePass() {
if (x.type === "password") {
x.type = 'text';
s.style.display = 'none';
h.style.display = 'inline';
} else {
x.type = 'password';
s.style.display = 'inline';
h.style.display = 'none';
}
}
#inputcontainer {
display: flex;
}
#inputcontainer > svg {
margin-left: 5px;
}
<p class="signin_title">Sign in</p>
<input type="text" id="login-form-username" name="os_username" placeholder="Username" required><br><br>
<div id='inputcontainer'>
<input type="password" id="login-form-password" name="os_password" placeholder="Password" required></input>
<svg id="Layer_1" onclick="togglePass()" data-name="Layer 1" width="25" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>eye-glyph</title><path d="M320,256a64,64,0,1,1-64-64A64.07,64.07,0,0,1,320,256Zm189.81,9.42C460.86,364.89,363.6,426.67,256,426.67S51.14,364.89,2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.14,147.11,148.4,85.33,256,85.33s204.86,61.78,253.81,161.25A21.33,21.33,0,0,1,509.81,265.42ZM362.67,256A106.67,106.67,0,1,0,256,362.67,106.79,106.79,0,0,0,362.67,256Z"/></svg>
<svg id="Layer_2" onclick="togglePass()" data-name="Layer 2" width="25" xmlns="http://www.w3.org/2000/svg" style='display: none' viewBox="0 0 512 512"><title>eye-disabled-glyph</title><path d="M409.84,132.33l95.91-95.91A21.33,21.33,0,1,0,475.58,6.25L6.25,475.58a21.33,21.33,0,1,0,30.17,30.17L140.77,401.4A275.84,275.84,0,0,0,256,426.67c107.6,0,204.85-61.78,253.81-161.25a21.33,21.33,0,0,0,0-18.83A291,291,0,0,0,409.84,132.33ZM256,362.67a105.78,105.78,0,0,1-58.7-17.8l31.21-31.21A63.29,63.29,0,0,0,256,320a64.07,64.07,0,0,0,64-64,63.28,63.28,0,0,0-6.34-27.49l31.21-31.21A106.45,106.45,0,0,1,256,362.67ZM2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.15,147.11,148.4,85.33,256,85.33a277,277,0,0,1,70.4,9.22l-55.88,55.88A105.9,105.9,0,0,0,150.44,270.52L67.88,353.08A295.2,295.2,0,0,1,2.19,265.42Z"/></svg>
</div>
You have to get svg and input into the same div. And little bit Css magic!
For example,
HTML:
<div class="inputCover">
<input type="password" class="input">
<svg class="icon"></svg>
</div>
CSS:
.inputCover{
position: relative; //so, when do you make parent div position relative, then absolute items inside is not goes outside.
}
.input{
padding: 5px;
padding-right: 25px; //you can limit input's inside, so text not goes to under of icon
}
.icon{
position: absolute; //you can make icon on the input like this.
top: 50%; //icon will be center of the input from top to bottom.
right: 10px; //right position.
transform: translateY(-50%); //this is important to make icon perfectly centered.
}
I think this is the right answer for you.
You can try to place SVG inside input using absolute position, and then to add onclick event on SVG itself. Something like this:
function togglePass() {
var x = document.getElementById("login-form-password");
var l1 = document.getElementById("Layer_1");
var l2 = document.getElementById("Layer_2");
if (x.type === "password") {
x.type = "text";
l1.setAttribute('hidden', true);
l2.removeAttribute('hidden');
} else {
x.type = "password";
l1.removeAttribute('hidden');
l2.setAttribute('hidden', true);
}
}
[hidden] { display: none; }
.form-group {
position: relative;
width: 170px;
}
.form-group svg {
position: absolute;
right: 10px;
top: 2px;
width: 16px;
height: auto;
}
<p class="signin_title">Sign in</p>
<input type="text" id="login-form-username" name="os_username" placeholder="Username" required><br><br>
<!-- Password -->
<div class="form-group">
<input type="password" id="login-form-password" name="os_password" placeholder="Password" required>
<svg onclick="togglePass()" id="Layer_1" data-name="Layer 1" width="25" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>eye-glyph</title><path d="M320,256a64,64,0,1,1-64-64A64.07,64.07,0,0,1,320,256Zm189.81,9.42C460.86,364.89,363.6,426.67,256,426.67S51.14,364.89,2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.14,147.11,148.4,85.33,256,85.33s204.86,61.78,253.81,161.25A21.33,21.33,0,0,1,509.81,265.42ZM362.67,256A106.67,106.67,0,1,0,256,362.67,106.79,106.79,0,0,0,362.67,256Z"/></svg>
<svg onclick="togglePass()" hidden id="Layer_2" data-name="Layer 2" width="25" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>eye-disabled-glyph</title><path d="M409.84,132.33l95.91-95.91A21.33,21.33,0,1,0,475.58,6.25L6.25,475.58a21.33,21.33,0,1,0,30.17,30.17L140.77,401.4A275.84,275.84,0,0,0,256,426.67c107.6,0,204.85-61.78,253.81-161.25a21.33,21.33,0,0,0,0-18.83A291,291,0,0,0,409.84,132.33ZM256,362.67a105.78,105.78,0,0,1-58.7-17.8l31.21-31.21A63.29,63.29,0,0,0,256,320a64.07,64.07,0,0,0,64-64,63.28,63.28,0,0,0-6.34-27.49l31.21-31.21A106.45,106.45,0,0,1,256,362.67ZM2.19,265.42a21.33,21.33,0,0,1,0-18.83C51.15,147.11,148.4,85.33,256,85.33a277,277,0,0,1,70.4,9.22l-55.88,55.88A105.9,105.9,0,0,0,150.44,270.52L67.88,353.08A295.2,295.2,0,0,1,2.19,265.42Z"/></svg>
</div>
Related
Currently I have some code that when selecting a value from a dropdown list will paint a specific path of a svg image.
var paths1 = {
"https://www.google.com": {
color: "#eb8c00",
selector: "#path1"
},
"https://www.yahoo.com": {
color: "#eb8c00",
selector: "#path2"
},
}
$('#path').on("change", function() {
$('#path1, #path2').css({
fill: "#FFFFFF"
});
var value = $(this).val()
if (!value) {
return;
}
var {
color,
selector
} = paths1[value]
$(selector).css({
fill: color
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div name="svg">
<svg id="Layer_1" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" width="500px" height="500px"
viewBox="0 0 500 500"
enable-background="new 0 0 500 500"
xml:space="preserve">
<path id="path1"
fill="#FFFFFF"
stroke="#231F20"
stroke-miterlimit="10"
d="M291.451,51.919v202.54c0,0,164.521,119.846,140.146,0 C407.227,134.613,291.451,51.919,291.451,51.919z"/>
<path id="path2"
fill="#FFFFFF"
stroke="#231F20"
stroke-miterlimit="10"
d="M169.595,150.844c0,0-76.24,69.615-40.606,128.309 c35.634,58.695,155.798-51.867,151.654-85.993C276.498,159.034,177.054,89.42,169.595,150.844z"/>
</svg>
</div>
<select id="path" name="path" class="pathSelector">
<option value="">Select Path</option>
<option value="https://www.google.com">Google</option>
<option value="https://www.yahoo.com">Yahoo</option>
</select>
<div>
<input
type="button"
class="selectbutton"
value="Select"
onClick="window.open(path.value,'newtab'+path.value)">
</div>
It works fine, as the button will also redirect to a different page.Now I need to be able to the reverse. I want to click on a path and the dropdown selector will also change. Clicking on path1 will select Google and clicking on path2 will select Yahoo from my dropdown list.
How can I achieve this?
To achieve this, you could define a click event handler:
$('#Layer_1 path').on("click", function() {...});
and use a for in loop to compare the selector values in the paths1 array with the ID of the clicked path:
for (key in paths1) {
if (paths1[key].selector == '#' + this.id) {...}
}
If you found a match, you can use the key to select the option tag and assign the selected property to it:
$('option[value="' + key + '"]').prop('selected', true);
All together:
$('#Layer_1 path').on("click", function() {
for (key in paths1) {
if (paths1[key].selector == '#' + this.id) {
$('option[value="' + key + '"]').prop('selected', true);
}
}
});
You could also change the colors similar to the other event handler:
$('#Layer_1 path').on("click", function() {
for (key in paths1) {
if (paths1[key].selector == '#' + this.id) {
$('option[value="' + key + '"]').prop('selected', true);
$('#path1, #path2').css({
fill: "#FFFFFF"
});
$(this).css({fill: paths1[key].color});
}
}
});
Working example:
var paths1 = {
"https://www.google.com": {
color: "#eb8c00",
selector: "#path1"
},
"https://www.yahoo.com": {
color: "#eb8c00",
selector: "#path2"
},
}
$('#path').on("change", function() {
$('#path1, #path2').css({
fill: "#FFFFFF"
});
var value = $(this).val()
if (!value) {
return;
}
var {
color,
selector
} = paths1[value]
$(selector).css({
fill: color
});
});
$('#Layer_1 path').on("click", function() {
for (key in paths1) {
if (paths1[key].selector == '#' + this.id) {
$('option[value="' + key + '"]').prop('selected', true);
$('#path1, #path2').css({
fill: "#FFFFFF"
});
$(this).css({fill: paths1[key].color});
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div name="svg">
<svg id="Layer_1" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" width="500px" height="500px"
viewBox="0 0 500 500"
enable-background="new 0 0 500 500"
xml:space="preserve">
<path id="path1"
fill="#FFFFFF"
stroke="#231F20"
stroke-miterlimit="10"
d="M291.451,51.919v202.54c0,0,164.521,119.846,140.146,0 C407.227,134.613,291.451,51.919,291.451,51.919z"/>
<path id="path2"
fill="#FFFFFF"
stroke="#231F20"
stroke-miterlimit="10"
d="M169.595,150.844c0,0-76.24,69.615-40.606,128.309 c35.634,58.695,155.798-51.867,151.654-85.993C276.498,159.034,177.054,89.42,169.595,150.844z"/>
</svg>
</div>
<select id="path" name="path" class="pathSelector">
<option value="">Select Path</option>
<option value="https://www.google.com">Google</option>
<option value="https://www.yahoo.com">Yahoo</option>
</select>
<div>
<input
type="button"
class="selectbutton"
value="Select"
onClick="window.open(path.value,'newtab'+path.value)">
</div>
here is a code that you can check out as an example. It must help. Good luck.
function SelectADropdownItem(id, val) {
var d = document.getElementById(id);
for (var i = 0; i < d.length; i++) {
if (d[i].value == val) {
d[i].selected = true;
} else {
d[i].selected = false;
}
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<img
onclick="SelectADropdownItem('design-dropdown','Fleur')"
src="//www.willmaster.com/library/images/ImageClickSelects/flower.jpg"
style="
border: none;
height: 120px;
width: 94px;
cursor: pointer;
margin: 0 5px 0 5px;
"
alt="Flower design"
/>
<img
onclick="SelectADropdownItem('design-dropdown','Nine Patch')"
src="//www.willmaster.com/library/images/ImageClickSelects/ninepatch.jpg"
style="
border: none;
height: 120px;
width: 94px;
cursor: pointer;
margin: 0 5px 0 5px;
"
alt="Nine patch design"
/>
<img
onclick="SelectADropdownItem('design-dropdown','Pink Gate')"
src="//www.willmaster.com/library/images/ImageClickSelects/pinkgate.jpg"
style="
border: none;
height: 120px;
width: 94px;
cursor: pointer;
margin: 0 5px 0 5px;
"
alt="Pink gate design"
/>
<br />
<img
onclick="SelectADropdownItem('design-dropdown','Sand Dollar')"
src="//www.willmaster.com/library/images/ImageClickSelects/sanddollar.jpg"
style="
border: none;
height: 120px;
width: 94px;
cursor: pointer;
margin: 0 5px 0 5px;
"
alt="Sand dollar design"
/>
<img
onclick="SelectADropdownItem('design-dropdown','Sandria')"
src="//www.willmaster.com/library/images/ImageClickSelects/sandria.jpg"
style="
border: none;
height: 120px;
width: 94px;
cursor: pointer;
margin: 0 5px 0 5px;
"
alt="Sandria design"
/>
<img
onclick="SelectADropdownItem('design-dropdown','Blue Wheel')"
src="//www.willmaster.com/library/images/ImageClickSelects/wheel.jpg"
style="
border: none;
height: 120px;
width: 94px;
cursor: pointer;
margin: 0 5px 0 5px;
"
alt="Blue wheel design"
/>
<select id="design-dropdown">
<option>Select here or click a pattern above.</option>
<option value="Fleur">Fleur</option>
<option value="Nine Patch">Nine Patch</option>
<option value="Pink Gate">Pink Gate</option>
<option value="Sand Dollar">Sand Dollar</option>
<option value="Sandria">Sandria</option>
<option value="Blue Wheel">Blue Wheel</option>
</select>
</body>
</html>
I'm trying to simulate a search on web version of Whatsapp (https://web.whatsapp.com). My goal is to do this task using pure JS, for study purposes. By looking the source code, i can see the search field is actually an editable DIV element :
With this source :
<div role="textbox" class="_13NKt copyable-text selectable-text" contenteditable="true" data-tab="3" dir="ltr"></div>
Here is what i tried :
1 - I first locate the element on page :
var node = document.getElementsByClassName('_13NKt copyable-text selectable-text')[0];
2 - I then set innertext :
node.innerText = 'test';
3 - The div is filled (although the placeholder is still there) , but the event that makes the search is not triggered :
4 - So i try to dispatch events that could trigger the 'search' event of the div :
node.dispatchEvent(new Event('input', { bubbles: true }));
node.dispatchEvent(new Event('change', { bubbles: true }));
node.dispatchEvent(new Event('keydown', { bubbles: true }));
Nothing really helped. At this point, the only way to make the page really search for 'test' string, is to manually click on the div and hit space bar.
What am i missing ?
I've tried to replicated web.whatsapp.com. As mentioned above by myf for the solution need those attributes <div role="textbox" contenteditable=true> and the change event not fire in that case.
The input event works and for intercept the string we can to use event.target.textContent instead event.target.value.
document.addEventListener('DOMContentLoaded', function () {
const searchBar = document.querySelector('.search-bar');
const input = document.querySelector('.search-input');
const clearButton = document.querySelector('.clear-button');
const focusedField = () => {
searchBar.classList.add('focused');
input.focus();
};
const outSideClick = ev => {
ev.stopPropagation();
const isSearchBar = ev.target.closest('.search-bar');
const isEmptyField = input.textContent.length;
if (!isSearchBar && isEmptyField === 0) {
searchBar.classList.remove('focused');
document.removeEventListener('click', outSideClick);
}
};
const showClearBtn = ev => {
const isEmptyFiled = ev.target.textContent.length;
console.log(ev.target.textContent);
if (isEmptyFiled === 0) {
clearButton.classList.remove('active');
return;
}
clearButton.classList.add('active');
};
const clearText = () => {
input.textContent = '';
clearButton.classList.remove('active');
};
clearButton.addEventListener('click', clearText);
input.addEventListener('input', showClearBtn);
searchBar.addEventListener('mouseup', focusedField);
document.addEventListener('mouseup', outSideClick);
});
*,
::after,
::before {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
'Open Sans', 'Helvetica Neue', sans-serif;
}
:root {
--bg: hsl(201, 27%, 10%);
--input-field: hsl(197, 7%, 21%);
--font-color: hsl(206, 3%, 52%);
--search-bar-height: 48px;
}
html {
height: 100%;
}
body {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--bg);
}
.search-bar {
height: var(--search-bar-height);
width: 350px;
display: flex;
align-items: center;
position: relative;
padding-inline: 1em 1.5em;
overflow: hidden;
background-color: var(--input-field);
border-radius: 50px;
z-index: 10;
}
.search-bar.focused .icon[data-icon='search'] {
opacity: 0;
transform: rotate(45deg);
}
.search-bar.focused .icon[data-icon='back'] {
opacity: 1;
transform: rotate(0deg);
}
.search-bar.focused .search-placeholder {
display: none;
}
.search-button,
.clear-button {
width: calc(var(--search-bar-height) / 2);
height: calc(var(--search-bar-height) / 2);
display: flex;
position: relative;
background-color: transparent;
border: none;
outline: none;
transition: opacity 0.3s ease-in-out;
}
.icon {
position: absolute;
inset: 0;
transition: all 0.3s ease-in-out;
cursor: pointer;
}
.icon path {
fill: var(--font-color);
}
.icon[data-icon='back'] {
transform: rotate(-45deg);
opacity: 0;
}
.clear-button {
opacity: 0;
pointer-events: none;
}
.clear-button.active {
opacity: 1;
pointer-events: initial;
}
.search-field {
height: 2em;
display: flex;
margin-inline-start: 1em;
flex-grow: 1;
position: relative;
overflow: hidden;
}
.search-placeholder,
.search-input {
height: inherit;
position: absolute;
top: 3px;
font-size: 1rem;
color: var(--font-color);
transition: all 0.3s ease-in-out;
white-space: nowrap;
}
.search-placeholder {
text-overflow: ellipsis;
pointer-events: none;
user-select: none;
}
.search-input {
width: 100%;
outline: none;
border: 1px solid transparent;
/* border transparent for caret visibility */
}
<div class="search-bar" tabindex="1">
<button class="search-button">
<span class="icon" data-icon="search">
<svg viewBox="0 0 24 24" width="24" height="24">
<path
fill="currentColor"
d="M15.009 13.805h-.636l-.22-.219a5.184 5.184 0 0 0 1.256-3.386 5.207 5.207 0 1 0-5.207 5.208 5.183 5.183 0 0 0 3.385-1.255l.221.22v.635l4.004 3.999 1.194-1.195-3.997-4.007zm-4.808 0a3.605 3.605 0 1 1 0-7.21 3.605 3.605 0 0 1 0 7.21z"
></path>
</svg>
</span>
<span class="icon" data-icon="back">
<svg viewBox="0 0 24 24" width="24" height="24">
<path
fill="currentColor"
d="M12 4l1.4 1.4L7.8 11H20v2H7.8l5.6 5.6L12 20l-8-8 8-8z"
></path>
</svg>
</span>
</button>
<div class="search-field">
<div class="search-input" role="textbox" dir="ltr" tabindex="1" contenteditable="true"></div>
<div class="search-placeholder">Search or start new chat</div>
</div>
<button class="clear-button">
<span class="icon" data-icon="clear">
<svg viewBox="0 0 24 24" width="24" height="24">
<path
fill="currentColor"
d="M17.25 7.8L16.2 6.75l-4.2 4.2-4.2-4.2L6.75 7.8l4.2 4.2-4.2 4.2 1.05 1.05 4.2-4.2 4.2 4.2 1.05-1.05-4.2-4.2 4.2-4.2z"
></path>
</svg>
</span>
</button>
</div>
onChange event triggers only for these supported elements:
<input type="checkbox">, <input type="color">, <input type="date">,
<input type="datetime">, <input type="email">, <input type="file">,
<input type="month">, <input type="number">, <input type="password">,
<input type="radio">, <input type="range">, <input type="search">,
<input type="tel">, <input type="text">, <input type="time">,
<input type="url">, <input type="week">, <select> and <textarea>
source: https://www.w3schools.com/jsref/event_onchange.asp
WhatsApp is built by ReactJS for web and ReactNative for mobile apps.
React has a event binder that reacts to certain changes and feeds back to the DOM.
If you're trying to recreate it using vanilla javascript, use the input tag
i'm also working on extension for Whatsapp and this works for me.
const input = document.querySelector('footer ._13NKt.copyable-text.selectable-text[contenteditable="true"]');
input.innerText = `${value}`;
const event = new Event('input', {bubbles: true});
input.dispatchEvent(event);
If you don't need any rich text formatting in the "value", then <div role="textbox" contenteditable> is probably overkill which will bring more complications than usage of simple native <input> with similar semantic and native convenient properties like dispatching of events when it's value is changed by user (the input event).
Sadly, even native input elements do no fire change nor input events when it's value is changed by JS "from the outside". Complex workarounds for this would involve similar techniques as for the "fake input / conteneditable div": for example using mutationobserver for watching the input's value / div's innerHTML and/or listening to all keyboard, mouse and clipboard events in relevant parts of the document), but…
…there's stupidly simple workaround for this: just fire the "changed" handler yourself when you know it is necessary.
Using these pieces of information can give us such simple POC with all event handlers attached as (and invoked from) inline attributes ("DOM0", what sometimes even makes quite sense being nice "vanilla" declarative markup):
<input placeholder="type, paste or change text here" id="i" size="40"
oninput="
// native inline 'input event handler'
// of native 'text input element' ('this' refers to it)
console.log(this.value);
">
<button
onclick="
// 'do something' with the content in a 'programmatic' way:
i.value += '!!';
// then invoke the native inline 'input' event handler directly:
i.oninput();
">append '!!'</button>
Same could be used in higher DOM event handlers, but it would not be as self-explanatory as this POC.
Following your example:
var node = document.getElementsByClassName('_13NKt copyable-text selectable-text')[0];
node.value = 'test'
const clickEvent = new KeyboardEvent('keydown', {
bubbles: true, cancelable: true, keyCode: 13
});
node.dispatchEvent(clickEvent);
I don't know about whatsapp being based on React but assuming it is, you could hook this after mounting the app
I have a chevron SVG at the bottom of my page that I want to hide once the user scrolls down. I can't figure out why my JS isn't working and the chevron is never hidden:
NOTE: the console.log statements do run and the css stylesheet does work.
chevron = document.getElementById("down-accelerate-path");
window.addEventListener("scroll", e => {
const y = window.scrollY;
if (y >= 1) {
console.log(y + " " + chevron)
chevron.className = ""
chevron.hidden = true;
} else {
chevron.className = "hide"
chevron.hidden = false;
}
});
body {
height: 300vh;
}
.hide {
display: none;
}
#down-accelerate {
position: fixed;
top: 88%;
transform: translateX(-50%);
}
<span class="center">
<svg height=88 width=88 viewBox="0 0 444.819 444.819" xmlns="http://www.w3.org/2000/svg" id = "down-accelerate">
<path id="down-accelerate-path" style="" fill="white" stroke="none"
d="M434.252,114.203l-21.409-21.416c-7.419-7.04-16.084-10.561-25.975-10.561c-10.095,0-18.657,3.521-25.7,10.561
L222.41,231.549L83.653,92.791c-7.042-7.04-15.606-10.561-25.697-10.561c-9.896,0-18.559,3.521-25.979,10.561l-21.128,21.416
C3.615,121.436,0,130.099,0,140.188c0,10.277,3.619,18.842,10.848,25.693l185.864,185.865c6.855,7.23,15.416,10.848,25.697,10.848
c10.088,0,18.75-3.617,25.977-10.848l185.865-185.865c7.043-7.044,10.567-15.608,10.567-25.693
C444.819,130.287,441.295,121.629,434.252,114.203z"/>
</svg>
</span>
Your code appears to be working. But I couldn't see the chevron. So I made it black and changed its position for testing. I also cleaned up the code a bit.
chevron = document.getElementById("chevron");
window.addEventListener("scroll", e => {
const y = window.scrollY;
if (y >= 1) {
chevron.className = 'hide'
} else {
chevron.className = ''
}
});
body {
height: 300vh;
}
.hide {
display: none;
}
#down-accelerate {
position: fixed;
right: 50%;
}
<span class="center" id="chevron">
<svg height=88 width=88 viewBox="0 0 444.819 444.819" xmlns="http://www.w3.org/2000/svg" id = "down-accelerate">
<path id="down-accelerate-path" style="" fill="black" stroke="none"
d="M434.252,114.203l-21.409-21.416c-7.419-7.04-16.084-10.561-25.975-10.561c-10.095,0-18.657,3.521-25.7,10.561
L222.41,231.549L83.653,92.791c-7.042-7.04-15.606-10.561-25.697-10.561c-9.896,0-18.559,3.521-25.979,10.561l-21.128,21.416
C3.615,121.436,0,130.099,0,140.188c0,10.277,3.619,18.842,10.848,25.693l185.864,185.865c6.855,7.23,15.416,10.848,25.697,10.848
c10.088,0,18.75-3.617,25.977-10.848l185.865-185.865c7.043-7.044,10.567-15.608,10.567-25.693
C444.819,130.287,441.295,121.629,434.252,114.203z"/>
</svg>
</span>
I have an accordion component which is working correctly using the 'tab' to navigate through the controls, and on 'enter' and 'spacebar' the accordion expands. I am trying to figure out a way to navigate to the next accordion using the 'up' and 'down' arrow keys. I am familiar with Javascript but I have not been able to achieve this with my existing code. Any help I can get is greatly appreciated.
Here's a CodePen to my accordion component. https://codepen.io/ozman2182/pen/vYgvGOd
(function () {
const headings = document.querySelectorAll(".unr-accordion--heading");
Array.prototype.forEach.call(headings, (h) => {
let btn = h.querySelector("button");
let target = h.nextElementSibling;
btn.onclick = () => {
let expanded = btn.getAttribute("aria-expanded") === "true";
btn.setAttribute("aria-expanded", !expanded);
target.hidden = expanded;
};
});
})();
In the example at the end of this answer I have added the code required to make the arrow keys cycle up and down through the list (and loop around).
I have also added Home and End keys to go to the start and end of the list (as that is expected behaviour).
In summary we:
grab all the buttons with .querySelectorAll('.unr-accordion--heading>button');
add an event listener for "keydown"
see if the keyCode is 38 (up) or 40 (down), if it is we apply a "direction" of -1 (to go up one item in our list of buttons) or +1 (to go down).
if the keyCode is 36 ("Home") - we set a direction of -999 so we can check it later
if the keyCode is 35 ("End") - we set a direction of 999 so we can check it later
if a direction is set (up or down arrow was pressed or home / end) we then loop through all the buttons.
if the button in the current stage of the loop equals document.activeElement (the currently focused item) then we know we are both in the accordion and on a button and the arrow keys should function.
We then check if the direction is up and we are focused on the first item in the buttons list (direction == -1 && x == 0) or if the "direction" is -999 (the home key), so we can loop to the bottom of the list of buttons and focus that one. We exit the loop if so with break;
If not we then check if the direction is down and we are focused on the last item in the buttons list (direction == 1 && x == max) or if the "direction" is +999 (the end key), so we can loop to the top of the list of buttons and focus that one. We exit the loop if so with break;
finally if neither of the above are true we just move focus by the direction (-1 for up, +1 for down) and then exit the loop.
(function () {
const headings = document.querySelectorAll(".unr-accordion--heading");
Array.prototype.forEach.call(headings, (h) => {
let btn = h.querySelector("button");
let target = h.nextElementSibling;
btn.onclick = () => {
let expanded = btn.getAttribute("aria-expanded") === "true";
btn.setAttribute("aria-expanded", !expanded);
target.hidden = expanded;
};
});
var btns = document.querySelectorAll('.unr-accordion--heading>button');
document.addEventListener('keydown', function(e){
var direction = 0;
var max = btns.length - 1;
direction = (e.keyCode == 38) ? -1 : direction;
direction = (e.keyCode == 40) ? 1 : direction;
direction = (e.keyCode == 35) ? -999 : direction;
direction = (e.keyCode == 36) ? 999 : direction;
if(direction != ""){
e.preventDefault();
for(x = 0; x <= max; x++){
if(document.activeElement == btns[x]){
if(direction == -1 && x == 0 || direction == -999){
btns[max].focus();
break;
}
if(direction == 1 && x == max || direction == 999){
btns[0].focus();
break;
}
btns[x + direction].focus();
break;
}
}
}
})
})();
:root {
--blue-10: #E6E9EC;
--blue-20: #CDD2D9;
--blue-50: #828FA1;
--blue-80: #364B68;
--blue-100: #041E42;
}
html {
font-family: Helvetica, sans-serif;
color: var(--blue-100);
}
section {
max-width: 920px;
margin-top: 3em;
margin-right: auto;
margin-left: auto;
}
.unr-accordion--expandall {
margin-bottom: 1em;
border:2px solid var(--blue-20);
border-radius: 5px;
padding: 0.5em 1em;
background-color: white;
}
.unr-accordion--expandall:hover,
.unr-accordion--expandall:focus {
border:2px solid var(--blue-10);
background-color: var(--blue-10);
}
.unr-accordion--wrapper {
border: 2px solid var(--blue-20);
border-radius: 5px;
margin-bottom: 0.5em;
}
.unr-accordion--wrapper:last-child {
margin-bottom: 0;
}
.unr-accordion--wrapper > h2 {
display: flex;
margin: 0;
border-radius: 5px;
}
.unr-accordion--wrapper > h2 button {
all: inherit;
border: 0;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin: 0;
padding: 0.5em;
font-size: 1.5rem;
line-height: 1.5;
}
.unr-accordion--wrapper > h2 button:hover {
background-color: var(--blue-10);
}
.unr-accordion--wrapper > h2 button svg {
font-size: 1rem;
margin-left: 0.5em;
flex-shrink: 0;
}
.unr-accordion--wrapper > h2 button:focus svg {
outline: 2px solid;
}
.unr-accordion--wrapper > h2 button[aria-expanded="true"] {
background-color: var(--blue-10);
}
.unr-accordion--wrapper > h2 button[aria-expanded="true"] .vert {
display: none;
}
.unr-accordion--wrapper > h2 button[aria-expanded] rect {
fill: currentColor;
}
.unr-accordion--panel {
margin-top: -1em;
padding-top: 1em;
padding-right: 1em;
padding-bottom: 1em;
padding-left: 1em;
background-color: var(--blue-10);
}
<section>
<h1>Edgar Allan Poe was an American writer, poet, editor, and literary critic.</h1>
<p>Poe is best known for his poetry and short stories, particularly his tales of mystery and the macabre. He is widely regarded as a central figure of Romanticism in the United States and of American literature as a whole, and he was one of the country's earliest practitioners of the short story.</p>
<!-- <button class="unr-accordion--expandall" href="#">Expand All</button> -->
<!-- accordion items -->
<div class="unr-accordions">
<div class="unr-accordion--wrapper">
<h2 class="unr-accordion--heading">
<button aria-expanded="false">
The Black Cat (short story)
<svg viewbox="0 0 10 10" width="24px" height="24px" aria-hidden="true" focusable="false">
<rect class="vert" height="8" width="2" y="1" x="4" />
<rect height="2" width="8" y="4" x="1" />
</svg>
</button>
</h2>
<div class="unr-accordion--panel" hidden>
<p>It was first published in the August 19, 1843, edition of The Saturday Evening Post. In the story, an unnamed narrator has a strong affection for pets until he perversely turns to abusing them.</p>
</div>
</div>
<div class="unr-accordion--wrapper">
<h2 class="unr-accordion--heading">
<button aria-expanded="false">
The Cask of Amontillado
<svg viewbox="0 0 10 10" width="24px" height="24px" aria-hidden="true" focusable="false">
<rect class="vert" height="8" width="2" y="1" x="4" />
<rect height="2" width="8" y="4" x="1" />
</svg>
</button>
</h2>
<div class="unr-accordion--panel" hidden>
<p>First published in the November 1846 issue of Godey's Lady's Book. The story, set in an unnamed Italian city at carnival time in an unspecified year, is about a man taking fatal revenge on a friend who, he believes, has insulted him. Like several of Poe's stories, and in keeping with the 19th-century fascination with the subject, the narrative revolves around a person being buried alive – in this case, by immurement. As in "The Black Cat" and "The Tell-Tale Heart", Poe conveys the story from the murderer's perspective.</p>
</div>
</div>
<div class="unr-accordion--wrapper">
<h2 class="unr-accordion--heading">
<button aria-expanded="false">
The Gold-Bug
<svg viewbox="0 0 10 10" width="24px" height="24px" aria-hidden="true" focusable="false">
<rect class="vert" height="8" width="2" y="1" x="4" />
<rect height="2" width="8" y="4" x="1" />
</svg>
</button>
</h2>
<div class="unr-accordion--panel" hidden>
<p>The plot follows William Legrand, who was bitten by a gold-colored bug. His servant Jupiter fears that Legrand is going insane and goes to Legrand's friend, an unnamed narrator, who agrees to visit his old friend. Legrand pulls the other two into an adventure after deciphering a secret message that will lead to a buried treasure. </p>
</div>
</div>
</div>
</div>
<!-- end: accordion component -->
</section>
I've been trying to center a collection of svg paths/text vertically in CSS / JS for quite a while with no luck. None of the solutions I've found here have worked so far. I have multiple < text > , < circle >, < rect >, although I don't think the type of content really matters.
The height is variable, so I have thought of 2 methods:
1) Transform via 50%. This is not working because you cannot transform a sub-set of content from a svg, from what I have found so far. The < g > tag was promising but I had no luck.
2) My second idea was to adjust the mask down via javascript once content is loaded. This, likewise, is not working.
All code is on code-pen and copied here as well: https://codepen.io/anon/pen/MmdWXB
HTML:
<div class="mask_group" id="mask_container">
<div class="text">
<svg>
<defs>
<mask id="mask" x="0" y="0" width="100%" height="100%" >
<rect class="cutout label" x="0" y="0" width="100%" height="100%"/>
<text class="label normal_font_weight" y="1em" dy="0em" x="50%">A</text>
<text class="label normal_font_weight" y="3em" dy="0em" x="50%">B</text>
<line class="label" x1="47%" y1="4.6em" x2="53%" y2="4.6em" style="stroke:rgb(255,0,0);stroke-width:1.5" />
<text class="label" y="7em" dy="0em" x="50%">C</text>
<text class="label" y="9em" id="final_text" dy="0em" x="50%">D</text>
</mask>
</defs>
<rect id="base" x="0" y="0" width="100%" height="100%"/>
</svg>
</div>
</div>
<div style="background-color:blue; width: 100%; height: 100%">
</div>
CSS:
html,body {
height: 100%;
}
.text {
position: relative;
/*top: 0;
left: 0;*/
width: 100%;
height: 100%;
margin-left: 0%;
/*z-index: 11; */
}
.mask_group {
position: fixed;
width: 100%;
height: 100%;
z-index: 10;
}
svg {
position: absolute;
width: 100%;
height: inherit;
}
svg text {
text-anchor: middle;
}
.cutout {
fill: white;
}
svg #base {
fill: #051E2A; /*#1F5773;*/
-webkit-mask: url(#mask);
mask: url(#mask);
}
/*Unused*/
.vert_center {
position: relative;
margin-top: 50%;
transform: translateY(-50%);
}
JS:
function body_loaded() {
var final_text = document.getElementById("final_text");
var position = final_text.getBoundingClientRect();
var bottom = position.bottom;
var mask_container = document.getElementById("mask_container");
var mask_position = mask_container.getBoundingClientRect();
var mask_height = mask_position.height;
var origin_y = (mask_height - bottom) / 2;
alert(origin_y);
var mask = document.getElementById("mask");
mask.style.y = origin_y;
}
Thanks ahead of time!
I'm not sure what you are attempting to do with the <mask>, so I am going to ignore it for now.
To vertically centre the SVG, just give it a fixed height and use the standard translate(-50%) trick. The only wrinkle is that you can't put the CSS transform on the <svg> element because transform has a different behavior in SVGs than it does in CSS. SVG transforms pre-date CSS transforms. So you just need to wrap the SVG in an HTML container and apply the transform to that instead.
I've set the height of the <svg> to 10em. Obviously you'll need to adjust that if you have more text. You could do that with JS using
svgElem.setAttribute("height", "10em");
But take it out of the CSS first obviously :)
html,body {
height: 100%;
}
.svg-wrapper {
position: absolute;
width: 100%;
top: 50%;
transform: translate(0, -50%);
}
.mask_group {
position: fixed;
width: 100%;
height: 100%;
z-index: 10;
}
svg {
width: 100%;
height: 10em;
fill: blue; /* default fill colour for contents */
}
svg text {
text-anchor: middle;
}
svg #base {
fill: #051E2A; /*#1F5773;*/
}
<div class="mask_group" id="mask_container">
<div class="svg-wrapper">
<svg>
<rect id="base" x="0" y="0" width="100%" height="100%"/>
<text class="label normal_font_weight" y="1em" dy="0em" x="50%">A</text>
<text class="label normal_font_weight" y="3em" dy="0em" x="50%">B</text>
<line class="label" x1="47%" y1="4.6em" x2="53%" y2="4.6em" style="stroke:rgb(255,0,0);stroke-width:1.5" />
<text class="label" y="7em" dy="0em" x="50%">C</text>
<text class="label" y="9em" id="final_text" dy="0em" x="50%">D</text>
</svg>
</div>
</div>
<div style="background-color:blue; width: 100%; height: 100%">
</div>