It's been some years since I have had to do DOM manipulation with CSS and vanilla JavaScript.
I have an input element some default css that is being added to its wrapping div and not the input element itself like so:
<div class="field animated-label text required">
<label class="control-label" for="answer">Answer</label>
<input class="form-control" />
</div>
So the default css for this input element is dictated by this selector:
.rds-form .animated-label {
border: 2px solid #ccc;
border-radius: 2px;
color: #767676
display: block;
min-height: 40px;
position: relative;
}
However when the user clicks out of the input element not having typed anything in, this selector gets appended on to give a red error border around the input element:
.rds-form .field-group.error, .rds-form
.field.text.error, .rds-form
.field.select.error, .rds-form .field.textarea.error {
border: 2px solid #cc2233;
position: relative;
}
How would this be handled in JavaScript? I am assuming this is handled by some JavaScript logic.
Add two handlers to the <input> field:
// When the input field has focus, remove the 'error' class from .animated-label:
document.querySelector('input').addEventListener('focus', function(e) {
document.querySelector('.animated-label').classList.remove('error');
});
// When the input field loses focus, determine whether to add or remove the 'error' class:
document.querySelector('input').addEventListener('blur', function(e) {
if (e.target.value.match(/^\s*$/)) { // Input is empty
document.querySelector('.animated-label').classList.add('error');
} else {
document.querySelector('.animated-label').classList.remove('error');
}
});
Related
i'm working on a small project where textarea are used.
And the problem with textarea is that the text always start at the top left corner...
And what i want is to have the content inside of that textarea always centered. And then if I add a line it'll expend from the middle, something like this :
I looked for thing like vertical align and thing like that, but i also saw things about inserting a div... But i was wondering if there was a solution with textarea
Thanks !
I don't think you can align the text in textarea vertically, but you can fake it!
HTML and CSS:
Create a resizable div
Nest a contenteditable p in that div
Add a placeholder for the p when it's empty
Use flexbox to set the p in the middle: align-items: center;
Add p {white-space: pre-line;} to prevent the overflow when typing
Add p {outline: 0px solid transparent;} to remove the border on focus
JavaScript:
Get the target (p)
Add an event on keypress
Check for pressing Enter
Prevent the default event and insert a line break (new line) instead
Add an event on click to the div to focus the p (textarea behavior)
// Get the target
let p = document.querySelector('p');
let div = document.querySelector('div');
// Add event onkeypress
p.addEventListener('keypress', (e) => {
let keyCode = e.code || e.key;
// Check for pressing 'Enter'
if(keyCode === 'Enter') {
// Prevent the default event and insert line break (new line) instead
document.execCommand('insertLineBreak');
e.preventDefault();
}
});
// Add event onclick
div.addEventListener('click', () => {
// Focus on p to get the textarea behavior
p.focus();
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
div {
resize: both;
width: 200px;
height: 150px;
border: 1px solid;
overflow: auto;
margin: 30px auto;
display: flex;
align-items: center;
cursor: text;
}
p {
white-space: pre-line;
width: 100%;
height: fit-content;
outline: 0px solid transparent;
}
p:empty::before {
content: attr(placeholder);
color: #666666;
}
<div>
<p contenteditable="true" placeholder="Type here..."></p>
</div>
JS:
document.getElementsByTagName("input, select, textarea, option, optgroup, fieldset, label").onchange = function () {
var updateRate = document.querySelector('.updateRate');
updateRate.style.display = "block";
};
mark-up & styles:
<div class="updateRate">Update Rate</div>
<style>
.updateRate {
display:none;
top: 0px;
position: fixed;
width: 100%;
left: 0px;
z-index: 11111;
}
#rate, .updateRate {
background: #354563;
color: #ffffff;
cursor: pointer;
text-align: center;
padding: 10px;
}
</style>
The above is my attempt; but the banner is still not displaying after any form elements state change.
Update: So both SO answers below seem to be correct; but perhaps I didn't explain the context enough - now the 'banner' displays as soon as the form is beginning to be filled out the first time; the goal was for the banner to show after a user has gone back in and updated a form element (second time, or changing it from initial).
Context:
It is an inline quote tool; a quote will generate inline after form elements are filled out. I was trying to create a 'banner' that would que if a user has adjusted any web form element a second time. Sorry for the confusion.
You can't attach the event this way since the getElementsByTagName method accept one single tag name you could use the querySelectorAll() method instead to get the elements then loop through them using foreach and attach the event using addEventListener():
var fields = document.querySelectorAll("input, select, textarea, option, optgroup, fieldset, label");
[].forEach.call(fields, function(el) {
el.addEventListener('change', showUpdateRate, false);
});
function showUpdateRate() {
if (document.querySelector('.quote').textContent != "") {
document.querySelector('.updateRate').style.display = "block";
}
}
document.querySelector('.generate').addEventListener('click', function() {
var quote = document.querySelector('[name="type"]:checked').value;
document.querySelector('.quote').textContent = quote + " Quote";
});
.updateRate {
display: none;
top: 0px;
position: fixed;
width: 100%;
left: 0px;
z-index: 11111;
}
#rate,
.updateRate {
background: #354563;
color: #ffffff;
cursor: pointer;
text-align: center;
padding: 10px;
}
<div class="updateRate">Update Rate</div>
<br><br><br>
<form>
<input type="radio" name="type" value="Success">Success
<input type="radio" name="type" value="Motivation">Motivation
<input type="radio" name="type" value="Work">Work
<br><br>
<input type="button" class="generate" value="Generate">
</form>
<br>
<span class="quote"></span>
.getElementsByTagName() returns a node list (an array-like object), not a single element. As such, it doesn't have an onchange property to work with. After getting the node list, you'll need to loop over all the items in the list and set up the event handler for each, one at a time.
Also, .getElementsByTagName() only allows for a single tag name to be passed in, not a comma separated list. Additionally, it returns a "live node list", which has performance implications, so if you aren't dynamically adding/removing elements, you should avoid it and use .querySelectorAll() instead.
Now, option, optgroup, and label elements can only be changed via code, and don't emit or recieve a change event in the first place, so you actually don't want/need those included in your node list.
More comments about how to make your code more modern and organized inline below:
// Get this reference just once and cache it in a variable
var updateRate = document.querySelector('.updateRate');
// Gather up all the relevant elements into a node list
let elements = document.querySelectorAll("input, select, textarea, fieldset");
// Convert the node list into an Array so that .forEach()
// can safely be used to loop in all modern browsers
Array.prototype.slice.call(elements).forEach(function(element){
// Add event listeners the modern way, not with .onXyz properties
element.addEventListener("change", function () {
// Just remove the hidden class already applied to the element
// instead of working with inline styles
updateRate.classList.remove("hidden");
});
});
/*
This is applied to the "Update Rate" element in HTML by default.
It can be removed by the JavaScript when appropriate.
*/
.hidden { display:none; }
.updateRate {
top: 0px;
position: fixed;
width: 100%;
left: 0px;
z-index: 11111;
}
#rate, .updateRate {
background: #354563;
color: #ffffff;
cursor: pointer;
text-align: center;
padding: 10px;
}
<form>
<fieldset>
<legend>This is the legend</legend>
<input>
<select>
<option>choice 1</option>
<option>choice 2</option>
<option>choice 3</option>
</select>
</fieldset>
<textarea></textarea>
</form>
<!-- Set this element to be hidden by default -->
<div class="updateRate" class="hidden">Update Rate</div>
I have an <input type="text"> field and I need to clear it when this field loses focus (whiech means that user clicked somewhere on the page). But there is one exception. Input text field should't be cleared when user clicks on a specific element.
I tried to use event.relatedTarget to detect if user clicked not just somewhere but on my specific <div>.
However as you can see in snippet below, it simply doesn't work. event.relatedTarget is always returning null!
function lose_focus(event) {
if(event.relatedTarget === null) {
document.getElementById('text-field').value = '';
}
}
.special {
width: 200px;
height: 100px;
background: #ccc;
border: 1px solid #000;
margin: 25px 0;
padding: 15px;
}
.special:hover {
cursor: pointer;
}
<input id="text-field" type="text" onblur="lose_focus(event)" placeholder="Type something...">
<div class="special">Clicking here should not cause clearing!</div>
Short answer: add tabindex="0" attribute to an element that should appear in event.relatedTarget.
Explanation: event.relatedTarget contains an element that gained focus. And the problem is that your specific div can't gain a focus because browser thinks that this element is not a button/field or some kind of a control element.
Here are the elements that can gain focus by default:
<a> elements with href attribute specified
<link> elements with href attribute specified
<button> elements
<input> elements that are not hidden
<select> elements
<textarea> elements
<menuitem> elements
elements with attribue draggable
<audio> and <video> elements with controls attribute specified
So event.relatedTarget will contain above elements when onblur happens. All other elements will are not counted and clicking on them will put null in event.relatedTarget.
But it is possible to change this behaviour. You can 'mark' DOM element as element that can gain focus with tabindex attribute. Here is what standart says:
The tabindex content attribute allows authors to indicate that an element is supposed to be focusable, whether it is supposed to be reachable using sequential focus navigation and, optionally, to suggest where in the sequential focus navigation order the element appears.
So here is the corrected code snippet:
function lose_focus(event) {
if(event.relatedTarget === null) {
document.getElementById('text-field').value = '';
}
}
.special {
width: 200px;
height: 100px;
background: #ccc;
border: 1px solid #000;
margin: 25px 0;
padding: 15px;
}
.special:hover {
cursor: pointer;
}
<input id="text-field" type="text" onblur="lose_focus(event)" placeholder="Type something...">
<div tabindex="0" class="special">Clicking here should not cause clearing!</div>
I know this is just a simple question for you but here I'm having a hard time trying to solve my own problem.
In my case, I have several text field that changes color of the field's border and label's text when the field itself is focused, and will revert changes when it is not focused. So then I used the following code: (see my demo)
$(function() {
$(".field").focus(function() { /* ... */ });
$(".field").blur(function() { /* ... */ });
});
But since I'm pointing to the class .field, all elements that have this class will be affected so I thought I have to set the current active element with the class .field so the other elements will be excluded. I used the below code but it doesn't work (and I don't even know if I'm right about the idea):
var current = $(document.activeElement).hasClass(".field");
$(current).focus(function() { /* ... */ });
$(current).blur(function() { /* ... */ });
If there's another way to settle this please tell me how.
Hope you could help me.
Thanks.
You can update your $(".field") selector to $(this) to select the field and $(this).next() to select the field's label inside both your blur and focus functions. Only the selected field and the next label will be selected, see this snippet:
$(function() {
$(".field").focus(function() {
/* when field with or without value is focused, add these classes */
if ($(this).val().length || $(".field").val().length == "") {
$(this).addClass("field-is-focused");
$(this).next().addClass("label-is-focused");
}
});
$(".field").blur(function() {
/* when field with or without value is not focused, remove added classes */
if ($(this).val().length || $(".field").val().length == "") {
$(this).removeClass("field-is-focused");
$(this).next().removeClass("label-is-focused");
}
});
});
fieldset {
border: 2px solid #ccc;
padding-top: 20px;
padding-bottom: 20px;
}
.label {
float: left;
margin-right: 5px;
}
.field {
border: 2px solid #ccc;
padding: 0 5px;
height: 22px;
margin-bottom: 10px;
}
.field:focus {
outline: 0;
outline-offset: 0;
}
/*---------------------------------*/
.field-is-focused {
border-color: blue;
}
.label-is-focused {
color: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width"/>
<title>jQuery - Get current focused element</title>
<script src="https://code.jquery.com/jquery.min.js"></script>
</head>
<body>
<form>
<fieldset>
<!-- input email type field -->
<input type="email" class="field">
<label class="label">Email</label>
<!-- input password type field -->
<input type="password" class="field">
<label class="label">Password</label>
<!-- textarea field -->
<textarea class="field"></textarea>
<label class="label">Comment</label>
</fieldset>
</form>
</body>
</html>
Try using the focus CSS propert for the class .field.
.field :focus{
/*your style change*/
}
This might eliminate the focus/blur event functions.Hope this helps!
I followed a SO question on how to create a placeholder for div[contenteditable].
My code looks like: http://jsfiddle.net/sdxjgkzm/
$('div[data-placeholder]').on 'keydown input', ->
if (this.textContent)
this.dataset.divPlaceholderContent = 'true'
else
delete(this.dataset.divPlaceholderContent)
Unfortunately, the problem is that as you can see the standard input's placeholder stays until you begin typing, while the contenteditable's goes away as soon as you click inside.
How do I fix this?
change your html a bit then use the below css:use placeholder instead of data-placeholder i.e. without data attribute.
input,div {
border: 1px black solid;
margin-top: 20px;
}
[contenteditable=true]:empty:before {
content: attr(placeholder);
}
<input placeholder="test"/>
<div contenteditable='true' placeholder="test"></div>
Check this out, CSS only :)
Placeholder support for contentEditable elements, without JavaScript
Updated Fiddle: enter link description here
All you need is to add the following CSS:
[contenteditable=true]:empty:before {
content: attr(placeholder);
display: block; /* For Firefox */
}
/* General Styling for Demo only */
div[contenteditable=true] {
border: 1px dashed #AAA;
width: 290px;
padding: 5px;
}
pre {
background: #EEE;
padding: 5px;
width: 290px;
}
<h3>Placeholder support for contentEditable elements,<br>without JavaScript!</h3>
<h5>Demo:</h5>
<div contenteditable="true" placeholder="Enter text here..."></div>
<p>All you need is to add the following CSS:</p>
<pre>
[contenteditable=true]:empty:before {
content: attr(placeholder);
display: block; /* For Firefox */
}
</pre>
<h5>Notes</h5>
<ul>
<li>Can add a different style than actual text like opacity, italic, etc</li>
<li>If your html needs to be 100% compliant, you can replace "placeholder" for "data-placeholder" on both files</li>
<li>Chrome will add <br />'s inside contentEditable elements in some cases, breaking the :empty check. Can be fixed with a bit of JavaScript.</li>
</ul>
<i>By Ariel Flesler</i>