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
Related
I am animating an element in SVG so that it travels around a circular track. It runs around the track 3x and stops. I am using the GSAP MotionPath plugin to do the animation. I want to control the motion with start and stop buttons. The stop button can either pause the motion or stop it completely and have the element return to its place - whichever involves the simpler method.
I managed to get the animation to start by clicking the "START" button. But I can't get it to stop by clicking the "STOP" button.
I show below the animation with the start button.
function myFunction(){
gsap.registerPlugin(MotionPathPlugin);
gsap.to("#comet-horizontal", {
duration: 5,
repeat: 2,
repeatDelay: 0,
yoyo: false,
ease: "none",
motionPath:{
path: "#racetrack",
align: "#racetrack",
autoRotate: true,
alignOrigin: [0.5, 0.5]
}
});
}
html, body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
body {
/*background-color: black;*/
min-height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
svg {
overflow: visible;
height: 100%;
background-color: orange;
/* Fix Safari rendering bug */
transform: translateZ(0);
}
circle {
fill: pink;
}
#button{
width: 60px;
height: 30px;
background-color: orange;
position: relative;
margin-top: 5px;
margin-bottom: 5px;
}
#button2{
width: 60px;
height: 30px;
background-color: yellow;
position: relative;
margin-top: 5px;
margin-bottom: 5px;
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.3/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.3/MotionPathPlugin.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="snap.svg-min.js"></script>
</head>
<body>
<button id="button" onclick="myFunction()">START</button>
<button id="button2">STOP</button>
<svg
width="100%"
height="100%"
viewBox="0 0 338.66667 190.5">
<path
id="racetrack"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffaaaa;stroke-width:2.32673;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;stop-color:#000000;stop-opacity:1"
d="M 184.04496,79.375006 A 51.753304,51.753307 0 0 1 132.29166,131.1283 51.753304,51.753307 0 0 1 80.538365,79.375006 51.753304,51.753307 0 0 1 132.29166,27.621698 a 51.753304,51.753307 0 0 1 51.7533,51.753308 z" />
<path
id="comet-horizontal"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#00d3ff;fill-opacity:1;fill-rule:evenodd;stroke-width:2.82278;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
d="m 241.75652,61.794127 v 2.645839 l -13.22916,-0.661459 v -0.66146 -0.66146 z"
sodipodi:nodetypes="cccccc" />
</svg>
</body>
I browsed StackOverflow but could not find the answer. For example, How to control the execution of javascript functions? is a similar question to mine but the asker wants a new function to start after the first one is stopped.
I cannot use typical animation functions such as animation-play-state since the animation is using a GreenSock script.
I also tried out the top solution from How to stop a function during its execution - JavaScript but it didn't work for me (maybe because I implemented it wrongly). I am having trouble writing the "if" condition for when the Stop button is clicked. I tried if(document.getElementById('button').clicked == true) but it didn't work.
Below is the solution mentioned above in the last link.
function foo1(){
console.log("Foo started...");
if(prompt("Type 1 to terminate right now or anything else to continue...") == "1"){
return; // Function will terminate here if this is encountered
}
console.log("Foo ending..."); // This will only be run if something other than 1 was entered
}
foo1();
I would prefer to stick with vanilla JS solutions if possible rather than JQuery because I am not familiar with JQuery, but if a JQuery solution is the easiest way of doing it, I am open to it.
This is the attempt to implement the "foo1" solution that failed:
function myFunction(){
gsap.registerPlugin(MotionPathPlugin);
gsap.to("#comet-horizontal", {
duration: 5,
repeat: 2,
repeatDelay: 0,
yoyo: false,
ease: "none",
motionPath:{
path: "#racetrack",
align: "#racetrack",
autoRotate: true,
alignOrigin: [0.5, 0.5]
}
});
if (document.getElementById("button2").clicked == true)
return;
}
myFunction();
The gsap.to method returns a Tween object with which you can control the animation. It has pause, resume, restart and several other useful methods.
Here I adapted your script:
Renamed the HTML buttons with more telling names.
Attached the click handlers in code, not via HTML attribute
Added a global variable to allow each event handler to access the above mentioned object
Added the logic to pause and resume the animation
Change the repeat: 2 parameter to repeat: -1, so the animation has no end.
let tween; // global so both handlers can access it
document.getElementById("buttonStart").addEventListener("click", function () {
if (tween) { // Not first time:
tween.resume(); // Continue from where it was paused
return;
}
gsap.registerPlugin(MotionPathPlugin);
tween = gsap.to("#comet-horizontal", {
duration: 5,
repeat: -1,
repeatDelay: 0,
yoyo: false,
ease: "none",
motionPath:{
path: "#racetrack",
align: "#racetrack",
autoRotate: true,
alignOrigin: [0.5, 0.5]
}
});
});
document.getElementById("buttonStop").addEventListener("click", function () {
tween?.pause();
});
html, body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
body {
/*background-color: black;*/
min-height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
svg {
overflow: visible;
height: 100%;
background-color: orange;
/* Fix Safari rendering bug */
transform: translateZ(0);
}
circle {
fill: pink;
}
#button{
width: 60px;
height: 30px;
background-color: orange;
position: relative;
margin-top: 5px;
margin-bottom: 5px;
}
#button2{
width: 60px;
height: 30px;
background-color: yellow;
position: relative;
margin-top: 5px;
margin-bottom: 5px;
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.3/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.3/MotionPathPlugin.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="snap.svg-min.js"></script>
</head>
<body>
<button id="buttonStart">START</button>
<button id="buttonStop">STOP</button>
<svg
width="100%"
height="100%"
viewBox="0 0 338.66667 190.5">
<path
id="racetrack"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffaaaa;stroke-width:2.32673;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;stop-color:#000000;stop-opacity:1"
d="M 184.04496,79.375006 A 51.753304,51.753307 0 0 1 132.29166,131.1283 51.753304,51.753307 0 0 1 80.538365,79.375006 51.753304,51.753307 0 0 1 132.29166,27.621698 a 51.753304,51.753307 0 0 1 51.7533,51.753308 z" />
<path
id="comet-horizontal"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#00d3ff;fill-opacity:1;fill-rule:evenodd;stroke-width:2.82278;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
d="m 241.75652,61.794127 v 2.645839 l -13.22916,-0.661459 v -0.66146 -0.66146 z"
sodipodi:nodetypes="cccccc" />
</svg>
</body>
There are 5 boxes, which can be changed from 'white'<->'yellow' colors by mouse events (mouseover, mouseout and click). There is also a blue area with text displaying the level of the clicked box.
After clicking into the third box, I got 'hard level' text in blue area and 3 boxes color in yellow.
What I need is to return it to the default level ('easy level' and first box in yellow only) by clicking the reset button.
I have been trying do this like this , but it isn't working:
resetBtn = document.querySelector('#update');
and eventlistener:
resetBtn.addEventListener('click', highlightStars(`#star1`), true)
Here is an example:
window.addEventListener('DOMContentLoaded', changeStars, false);
const resetBtn = document.querySelector('#update');
/* Change level of the game depending on user choice */
function changeStars() {
/* Displaying level text inside blue box */
const updateAltText = currentLevelIndex => {
let levelText = document.querySelector('#level-text');
/* 'currentLevelIndex + 1' replaces event 'currentElement' */
levelText.textContent = document.querySelector(`#star${currentLevelIndex + 1}`).alt;
}
/* Captcha level number - default is 1 */
const getNumber = str => Number(str.match(/\d+/)[0]) || 1;
/* Star index is always one number lower than level number (indexing rules) */
const getStarIndex = event => getNumber(event.target.id) - 1;
let stars = document.querySelectorAll('.star');
const handleStarClick = event => {
/* FIRST - blocking possibility to change star behaviour by mouse events */
gameLevel.removeEventListener('mouseover', highlightStars);
gameLevel.removeEventListener('mouseout', highlightStars);
/* SECOND - making all needed star with yellow color */
const stars = document.querySelectorAll('.star');
for (let i = 0; i <= getStarIndex(event); i++) {
stars[i].classList.add('yellow');
}
};
const highlightStars = event => {
const starIndex = getStarIndex(event);
updateAltText(starIndex);
for (let i = 1; i <= starIndex; i++) {
const star = document.querySelector(`#star${i + 1}`);
star.classList.toggle('yellow');
}
};
// resetBtn.addEventListener('click', highlightStars(`#star1`), true);
resetBtn.addEventListener('click', updateAltText(0), true);
const gameLevel = document.querySelector('.game-level');
gameLevel.addEventListener("mouseover", highlightStars);
gameLevel.addEventListener("mouseout", highlightStars);
gameLevel.addEventListener('click', handleStarClick, {once: true});
}
.stars {
display: flex;
margin: 10px auto;
width: 500px;
}
input[type='image'] {
width: 60px;
height: 60px;
border: thin solid black;
}
.yellow {
background-color: yellow;
}
.game-level {
display: flex;
width: 300px;
height: 100%;
}
.level-block {
display: flex;
width: 200px;
margin-left: 10px;
justify-content: center;
align-items: center;
border: 1px solid hsl(217, 86%, 50%);
border-radius: 25px;
background-color: hsl(212, 29%, 80%);
}
.level-block > span {
font-size: 18px;
}
.reset {
width: 80px;
height: 80px;
}
<div class="stars">
<div class="game-level">
<input type="image" class="star yellow" id="star1" src="" width="60" alt="easy level">
<input type="image" class="star" id="star2" src="" width="60" alt="normal level">
<input type="image" class="star" id="star3" src="" width="60" alt="hard level">
<input type="image" class="star" id="star4" src="" width="60" alt="very hard level">
<input type="image" class="star" id="star5" src="" width="60" alt="impossible level">
</div>
<div class="level-block">
<span id="level-text">Easy level</span>
</div>
</div>
<input type="button" class="reset" id="update" value="RESET">
The following demo uses JavaScript for click events only, all mouse events (ie hover) are pure CSS. The reset behavior simply removes .active class on all buttons then adds .active class to the first button. Instead of the first button title being displayed after a reset -- the reset button title: "Game Reset" is displayed, it might be a little confusing for users if there's no confirmation of a reset. Other behavior is included in demo that is logical and consistent such as toggling, hovering to a temporary state and clicking for a persistent state etc. Details are commented in demo.
// Reference the form
const stars = document.forms.stars;
/*
Register the form to the click event -- when a click occurs anywhere on or within the form, callback function twinkle() is
called
*/
stars.onclick = twinkle;
/**
//A -- twinkle passes a reference to the Event Object... (e)
//B1 - Two Event Object properties are used to reference:
The tag the was clicked by user: event.target
The tag registered to the event: event.currentTarget
//B2 - The HTMLFormElement property: .elements collects all form
controls into a Live HTML Collection (aka NodeList)
//C -- ui.star is a Collection of form controls with [name=star]
The brackets [] and spread operator ... converts the
NodeList into an Array
//D -- Reference the message tag. If the clicked tag was the reset
button -- for...of loop iterates through each [name=star]
and removes the class .active from all [name=star]
//E1 - Next add .active class to the default button
//E2 - Set the legend.message text to the value of clicked button
[title] attribute...
~~~~~~~
//F -- ...But if a button.star was clicked, a check to verify if
clicked tag has the .active class -- then a for...of
loop identical to the one described in line D is used to
remove any .active class.
//G -- After there are no .active, the Boolean declared in line F
determines whether the clicked tag gets the .active class
and its [title] attribute displayed or not
*/
function twinkle(e) {
const active = e.target;
const ui = e.currentTarget.elements;
const starZ = [...ui.star];
const msg = document.querySelector(".message");
if (active.matches("#clear")) {
for (let star of starZ) {
star.classList.remove("active");
}
ui.star1.classList.add('active');
msg.textContent = active.title;
} else if (active.matches(".star")) {
let status = active.classList.contains("active");
for (let star of starZ) {
star.classList.remove("active");
}
if (!status) {
active.classList.add("active");
msg.textContent = active.title;
} else {
active.classList.remove("active");
msg.textContent = "";
}
}
return false;
}
:root {
font: 400 small-caps 2.5vw/1 Arial
}
.levels {
display: table;
width: 96%;
height: auto;
border: 1px solid hsl(217, 86%, 50%);
border-radius:4px;
}
.message {
display: table-caption;
width: 40vw;
height: 6vh;
margin: 0 auto 2vh;
padding: 0.5vh 0;
border: 1px solid hsl(217, 86%, 50%);
border-radius: 1.5rem;
background-color: hsla(212, 29%, 80%, 25%);
text-align: center;
font-size: 1.5rem;
color: #0078D7;
}
#clear {
float: right;
transform: rotate(45deg);
padding: 0;
border: none;
background: none;
font-size: 3.5rem;
cursor: pointer;
}
#clear:focus {
outline: 0;
}
/*
Flex is applied to the button.star'S parent tag so the order
property can be utilized.
*/
.flex {
display: flex;
justify-content: space-evenly;
align-items: center;
width: 70vw;
}
.star {
display: table-cell;
position: relative;
width: 16vw;
height: 24vh;
border: thin solid black;
background: #DDD;
font-size: 3.75rem;
text-align: center;
vertical-align: middle;
cursor: pointer;
}
/*
GSC (General Sibling Combinator: ~ ) provides highlighting across
multiple buttons.
Exp. 5 buttons: [-] [-] [X] ~ [>] ~ [>]
*/
.star.active,
.star:hover,
.star.active ~ .star,
.star:hover ~ .star {
background: gold;
}
/*
HTML layout has button.star in reverse order. Applying order to
each button rectifies the order by APPEARING in order while the
HTML structure remains reversed.
*/
#star1 {
order: 1;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
#star2 {
order: 2;
}
#star3 {
order: 3;
}
#star4 {
order: 4;
}
#star5 {
order: 5;
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
#star1:hover,
#star1.active {
color: #5BC0DE;
}
#star2:hover,
#star2.active {
color: #FF1C8D;
}
#star3:hover,
#star3.active {
color: #00D800;
}
#star4:hover,
#star4.active {
color: #0000D5;
}
#star5:hover,
#star5.active {
color: #D50000;
}
<form id="stars" action="">
<fieldset name="levels" class="levels">
<legend class="message">Novice</legend>
<button id="clear" type="reset" title="Game Reset">π</button>
<section class="flex">
<button id="star5" name='star' class="star" title="Master">π</button>
<button id="star4" name='star' class="star" title="Expert">π</button>
<button id="star3" name='star' class="star" title="Advanced">π</button>
<button id="star2" name='star' class="star" title="Intermediate">π</button>
<button id="star1" name='star' class="star active" title="Novice">π</button>
</section>
</fieldset>
</form>
I'm pretty new with Javascript and jQuery, and can't seem to indentify the reason why my code acts like it does.
I have created two seemingly identical functions to change the background color of an input field.
Their goal is to turn the background color of the given input field to the color #00FF7F if anything is typed in the field. And if not, the field should be transparent.
Code JS:
$(document).ready(function () {
var $input1 = $("#logindata1");
var $input2 = $("#logindata2");
function onChangeInput1() {
$input1.css("background-color", "#00FF7F");
var value = $.trim($(".form-control").val());
if (value.length === 0) {
$input1.css("background-color", "transparent");
}
}
function onChangeInput2() {
$input2.css("background-color", "#00FF7F");
var value = $.trim($(".form-control").val());
if (value.length === 0) {
$input2.css("#background-color", "transparent");
}
}
$input1.on("keyup", onChangeInput1);
$input2.on("keyup", onChangeInput2);
});
css:
#loginbox {
width: 400px;
height: 200px;
margin-left: auto;
margin-right: auto;
margin-top: 25%;
}
.logindata {
margin-left: auto;
margin-right: auto;
margin-top: 20px;
height: 60px;
width: 290px;
transition: 0.25s ease;
}
.form-control {
margin-left: auto;
margin-right: auto;
height: 55px;
width: 288px;
border-style: none;
background-color: transparent;
text-align: center;
border: solid 2px #00FF7F;
transition: 0.25s ease;
font-size: 25px;
font-family: "Trebuchet MS";
}
.form-control:hover {
box-shadow: 0px 0px 30px #2E8B57;
}
::-webkit-input-placeholder {
color: #00FF7F;
}
Simple HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Test</title>
<!-- Stylesheet link -->
<link rel="stylesheet" type="text/css" href="assets/style.css">
<!-- jQuery link -->
<script type="text/javascript" src="assets/vendor/jquery-3.1.0.min.js"></script>
</head>
<body>
<div id="loginbox">
<div class="logindata" id="logindata1">
<input type="text" class="form-control" placeholder="Username">
</div>
<div class="logindata" id="logindata2">
<input type="password" class="form-control" placeholder="Password">
</div>
</div>
<!-- Javascript link-->
<script type="text/javascript" src="assets/js/javascript.js"></script>
</body>
On the jsbin above, try typing in both the Username and Password field to see how they react differently.
Images of what happens. Didn't want to include all images here:
http://imgur.com/a/qgubP
I realize there probably is a way to compromise my js/jquery into 1 function that each input field calls instead of have a function for each.
If both of these fields are required, here's a much simpler solution using CSS only.
Add the attribute required to your <input> tags and then use the pseudo-class :valid.
.form-control:valid {
background-color: #00FF7F;
}
Code snippet:
#loginbox {
width: 400px;
height: 200px;
margin-left: auto;
margin-right: auto;
margin-top: 25%;
}
.logindata {
margin-left: auto;
margin-right: auto;
margin-top: 20px;
height: 60px;
width: 290px;
transition: 0.25s ease;
}
.form-control {
margin-left: auto;
margin-right: auto;
height: 55px;
width: 288px;
border-style: none;
background-color: transparent;
text-align: center;
border: solid 2px #00FF7F;
transition: 0.25s ease;
font-size: 25px;
font-family: "Trebuchet MS";
}
.form-control:hover {
box-shadow: 0px 0px 30px #2E8B57;
}
::-webkit-input-placeholder {
color: #00FF7F;
}
.form-control:valid {
background-color: #00FF7F;
}
<div id="loginbox">
<div class="logindata" id="logindata1">
<input type="text" class="form-control" placeholder="Username" required>
</div>
<div class="logindata" id="logindata2">
<input type="password" class="form-control" placeholder="Password" required>
</div>
</div>
jsFiddle : https://jsfiddle.net/7vzjz2u5/3/
jQuery
$(document).ready(function() {
$('.change-background').on('change', function() {
var $this = $(this);
var value = $.trim($this.val());
// toggleClass can be provided a bool value,
// If we provide true we add class, if false we remove class
$this.toggleClass('filled-background', value.length !== 0);
}).change();
// We also want to call a 'change' event on
// all inputs with the change-background class just incase the page has
// pre-filled in values
});
Instead of listening for the keyup event and then running a function, just create a listener on the change event, also if we just apply one class to all inputs we want the background colour to change on, we can just create one listener which will do it for any input with the class change-background.
Html
<div id="loginbox">
<div class="logindata" id="logindata1">
<input type="text" class="change-background form-control" placeholder="Username">
</div>
<div class="logindata" id="logindata2">
<input type="password" class="change-background form-control" placeholder="Password">
</div>
</div>
Css (the extra class for background color)
.filled-background {
background-color: #00FF7F;
}
Also side note
listening for keyup is back, someone may want to copy and paste their username and password and if they do this it won't trigger an keyup event if they use right click and paste.
Your code clears the background color when the length is 0. The way it checks the length is with this snippet of code:
var value = $.trim($(".form-control").val());
The selector $(".form-control") will select all elements with the CSS class of .form-control. This is a problem because there is more than one of them; in this case, it will always return the value from the first element found.
You should change the code to check for the specific control by searching by ID, like so:
var value = $.trim($("#logindata1 input").val()); //get user ID
var value = $.trim($("#logindata2 input").val()); //get password
You have some minor mistakes, but no worry. We can fix it.
First Problem
Other answers are pointing something important: you are trying to get the value selecting all elements with form-control class.
var value = $.trim($(".form-control").val());
You can do it, replacing your selector by your already declared variables $input1 and $input2. This way:
var value = $.trim($input1.val());
var value = $.trim($input2.val());
Second
Ok. First problem solved. The second problem is in your second function. You trying to set an invalid css: $input2.css("#background-color", "transparent");
When should be: $input2.css("background-color", "transparent"); (without #).
Next One
Nice. Next one. The id's you are setting logindata1 and logindata2 are on your divs. So, you are wrongly trying to get the value of the div instead the value of the input. you can fix your selector by appending input, this way:
var $input1 = $("#logindata1 input");
var $input2 = $("#logindata2 input");
Finally
So, finally, it should work:
$(document).ready(function () {
var $input1 = $("#logindata1 input");
var $input2 = $("#logindata2 input");
function onChangeInput1() {
$input1.css("background-color", "#00007F");
var value = $.trim($input1.val());
if (value.length === 0) {
$input1.css("background-color", "transparent");
}
}
function onChangeInput2() {
$input2.css("background-color", "#00007F");
var value = $.trim($input2.val());
if (value.length === 0) {
$input2.css("background-color", "transparent");
}
}
$input1.on("keyup", onChangeInput1);
$input2.on("keyup", onChangeInput2);
});
Your value check is not right. With your jQuery, you are checking the value of both inputs every time.
Try checking the single inputs that you are interested in instead.
$(document).ready(function () {
var $input1 = $("#logindata1");
var $input2 = $("#logindata2");
function onChangeInput1() {
$input1.css("background-color", "#00FF7F");
var value = $.trim($input1.val());
if (value.length === 0) {
$input1.css("background-color", "transparent");
}
}
function onChangeInput2() {
$input2.css("background-color", "#00FF7F");
var value = $.trim($input2.val());
if (value.length === 0) {
$input2.css("#background-color", "transparent");
}
}
$input1.on("keyup", onChangeInput1);
$input2.on("keyup", onChangeInput2);
});
I'm running into a problem binding both a .blur and .click event handler to the same clickable element. The UX I'm going for is as follows:
a user clicks on the search icon and the search field appears; when a user clicks again on the search icon, they can collapse and hide the search field. If they click away, the search field should hide.
So far, I'm able to achieve most of what I want, except that I can't get the .click binding to toggle the class after it's been clicked. I'm thinking it's possibly because after being toggled the $ selector doesn't have any results to select? I'm relatively new to JavaScript so I'm a little unclear on exactly how JavaScript or jQuery would handle something like this.
Jsfiddle here: http://jsfiddle.net/TpXJe/1/
Edit: including code here so future Stack Overflow users can see it:
html:
<form class="hidden-xs search-container navbar-right search-form" method="get">
<div class="input-group">
<input id="search-box" type="search" class="search-box" name="s" class="search-field form-control" />
<label class="hide">Search for something</label>
<label for="search-box"><span id="searchDivider">|</span>
<div class="icon-continer"> <span class="glyphicon glyphicon-search search-icon"></span>
</div>
</label>
</div>
</form>
css:
.search-container {
right: 0px;
}
#searchDivider {
position: absolute;
right: 0px;
top:31px;
z-index: 1;
color: #brand-success;
cursor: pointer;
font-weight: 200;
}
// Style the search box
$tl: .3s; // transition length
.search-box {
outline-width: 0;
transition: width $tl, border-radius $tl, background $tl;
position: absolute;
right:-37px;
top: 20px;
z-index: 100;
width: 40px;
height: 40px;
border-radius: 20px;
border: none;
cursor: pointer;
background: transparent;
& + label .search-icon {
color: black }
&:hover {
color: white;
background: transparent;
box-shadow: 0 0 0 1px transparent;
& + label .search-icon {
color: white }
}
&.focused {
transition: width $tl cubic-bezier(.18,.57,.25,.94), border-radius $tl;
border: none;
outline: none;
box-shadow: none;
padding-left: 15px;
cursor: text;
width: 600px;
border: 1px solid black;
border-radius: auto;
background: white;
color: black;
& + label .search-icon {
color: black; }
}
&:not(:focus) {
text-indent:-5000px; } // for more-graceful falling back (:not browsers likely support indent)
}
#search-submit {
position: relative;
left: -5000px;
}
.search-icon {
position: absolute;
right: -45px;
top: 14px;
z-index: 1000;
color: black;
cursor: pointer;
width: 60px;
height: 60px;
padding: 23px;
}
js:
$('.search-icon').click(
function () {
$('.search-box ').toggleClass(' .search-box focused');
});
$('.search-box').blur(
function () {
$('.search-box').removeClass('focused');
});
As Ehsan said in the comments, it appears that clicking the icon while it has already been clicked once fires both click() and blur() events. I just added in a variable to represent the state of clicked/unclicked and it works for me! Also in the updated fiddle below is what I believe you intended to do that Jacob mentioned in the comments-the class name is 'focused' not '.search-box focused'.
Updated JSFiddle Link
Here is the JS Code:
var clicked = false;
$('.search-icon').click(
function () {
if (clicked) {
$('.search-box ').removeClass('focused');
clicked = false;
} else {
$('.search-box ').addClass('focused');
clicked = true;
}
});
$('.search-box').blur(
function () {
$('.search-box').removeClass('focused');
What I have stated to do recently is set up all my event listeners as a variable (object). It makes being able to assign multiple events to a single object very easy.
/**
* Event listeners.
* Params: element, event : method
* NOTE: element is passed in as a string, not jQuery object!
* This so we have the possibility of multiple events for a single element (eg. focus & change for the same element).
*
*/
eventHandlers: {
'a click': 'onAnchorClick',
'a hover': 'onAnchorHover'
},
/**
* Registers all event listeners/handlers.
* #returns {void}
*
*/
registerEventHandlers: function() {
var that = this;
$.each(this.eventHandlers, function(key, val) {
var split = key.split(" ");
var element = split[0];
var event = split[1];
$(document).on(event, element, that[val]);
});
}
...
/**
* Event listener for all anchor click events.
* #param {object} event | The click event.
* #returns {void}
*
*/
onAnchorClick: function(event) {
console.log('click event');
// For example purposes:
var ga_eventData = {
'category': 'Text Link',
'action': 'Click',
'label': event.currentTarget.innerText,
'optionalData': {
'page': window.location.protocol + '//' + window.location.host + window.location.pathname
}
}
GA.registerEvent(ga_eventData); // GA is my google analytics module in this example
},
/**
* Event listener for all anchor hover events.
* #param {object} event | The hover event.
* #returns {void}
*
*/
onAnchorHover: function(event) {
console.log('hover event');
}
...
Then in my initialization method, I simply call:
/**
* Initialization method.
* #returns {void}
*
*/
initialize: function() {
var that = this;
that.registerEventHandlers();
}
I wanted to implement a slider control that changes the brightness of the image, much like the one shown at this link :
http://camanjs.com/examples/
I am fairly new to javascript and this is proving to be rather difficult. So right now, I am using the CamanJS library but unfortunately am not able to replicate that. I tried reverse engineering the example, but gosh the example is very complicated and not at all readable! Anyways, heres the problem with my implementation :
//this is the event handler called when the slider value changes
function brightnessControl(e, ui) {
//mainImage is the id of the canvas that holds the image
Caman("#mainImage", function() {
this.brightness(ui.value);
this.render();
});
}
the original image is overwritten with a new image with the brightness settings. So eventually, I end up with just a plain white or black image. Any help would be greatly appreciated! Thanks in advance!
You can achieve the desired effect with a canvas element, CSS3 filter and pure JavaScript:
HTML
<input id="bri" type="text" value="1"/>
<canvas id="img"></canvas>
JavaScript
window.onload = function () {
var context = document.getElementById('img').getContext('2d');
/* Loading the image at first */
var base_image = new Image();
base_image.src = 'http://images.google.com/intl/fr_ALL/images/logos/images_logo_lg.gif';
context.drawImage(base_image, 0, 0);
/* Function trigerred when we leave the input */
document.getElementById('bri').onblur = function () {
var amount = this.value;
var img = document.getElementById('img');
/* We change the brightness of the canvas itself */
img.setAttribute('style', 'filter:brightness(' + amount + '); -webkit-filter:brightness(' + amount + '); -moz-filter:brightness(' + amount + ')');
}
};
Live Demo
To read full implementation check this website How to Create an Image Brightness Control Slider
rangeInput = document.getElementById('range');
container = document.getElementsByClassName('container')[0];
rangeInput.addEventListener("mousemove",function(){
container.style.filter = "brightness(" + rangeInput.value + "%)";
});
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container{
background: url(https://codingdebugging.com/wp-content/uploads/2020/07/include-how-to-create-an-image-brightness-control-slider.jpg) no-repeat center;
min-height: 100vh;
background-size: cover;
display: flex;
align-items: center;
justify-content: center;
}
.brightness-box{
width: 400px;
height: 60px;
background: #f9f9f9;
border-radius: 8px;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
}
.brightness-box i{
margin: 0 10px;
}
#range{
width: 100%;
-webkit-appearance: none;
background: #0a85ff;
height: 3px;
outline: none;
}
#range::-webkit-slider-thumb{
-webkit-appearance: none;
width: 22px;
height: 22px;
background: #333;
border-radius: 50%;
cursor: pointer;
}
<!DOCTYPE html>
<html>
<head>
<title>Brightness Control - Coding Debugging</title>
<!-- Font Awesome Icon -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css">
</head>
<body>
<div class="container">
<div class="brightness-box">
<i class="far fa-sun"></i>
<input type="range" id="range" min="10" max="100" value="100">
<i class="fas fa-sun"></i>
</div>
</div>
</body>
</html>
//this is the event handler called when the slider value changes
function brightnessControl(e, ui) {
//mainImage is the id of the canvas that holds the image
Caman("#mainImage", function() {
this.style.filter='brightness(ui.value)';//like this: filter: brightness(0.4)
//this.brightness(ui.value);
// this.render(); i don/t know this is useful? you judge it by yourself
});
}
the brightness is from 0 to 1.
The following will work in CSS 2.1+. Please note that I have used HTML5 input type="range" only for ease of use in this example. Javascript fallback code is also implemented in this example for browsers that do not support this (input type will default to text).
Optimally a custom slider would be implemented, but I believe this question is about brightness control and not so much about the slider.
The way this works is by overlapping the image with some element of equal proportions and with opacity depending on the slider/text input value. The background color of this element will be white for values > 50, and black for values < 50.
Link to JS Fiddle
#HTML
<div id="container">
<div id="brightness"></div>
<img src="http://placehold.it/400x400" />
</div>
Brightness (0 - 100):<br />
<input type="range" id="controls" value="50" min="0" max="100" maxlength="3">
#Javascript
window.onload = function()
{
var brightness = document.getElementById('brightness');
controls = document.getElementById('controls');
controls.onkeyup = controls.onchange = function()
{
var brightness = document.getElementById('brightness'),
val = parseInt(this.value) - 50;
if (val > 50 || val < -50)
return false;
brightness.style.backgroundColor = val > 0 ? 'white' : 'black';
brightness.style.opacity = Math.abs(val/100) * 2;
}
}
#CSS
#container{
width:400px;
height:400px;
margin-bottom:10px;
border:1px solid rgb(127, 127, 127);
}
#brightness{
width:400px;
height:400px;
background:white;
position:absolute;
opacity:0;
}
#controls{
width:400px;
height:22px;
padding:0 5px;
}