I want to implement a simple menu just by using Vanilla JS. So I have a working onclick function, where I just twist visibility property on click of the menu item. CSS is by default set on visibility:hidden
function getContentP() {
var div = document.getElementById("menu1Content");
if (div.style.visibility === "hidden") {
div.style.visibility = "visible";
} else {
div.style.visibility = "hidden";
}
};
<a href="#menu1" onclick="getContentP()">
<h2>title</h2>
</a>
<!-- CONTENT BOX, show on clicks-->
<div id="menu1Content" style="background: #fefefe">
Some content to make it visible
</div>
This works as expected, but really slowly, and with poor results.
Any good suggestion to improve the code? And maybe add some nice transitions like fadeIn effect without using jQuery?
Thanks a lot.
Im not sure what slowly means in this case or what poor results you are seeing, but in general the thing that jumps out at me is the usage of onclick. When passing code like that to a dom element the runtime will essentially eval the snippet which is bad for a number of reasons, and certainly not great for performance (also likely can never be optimized by the vm).
element.addEventListener('click', function() { 'your stuff here' }, false);
may give you better performance but id be shocked if you can even tell the difference unless this is called thousands or maybe millions of times.
You could also cache the dom element since you seem be doing a lookup by id, then you dont have to do a potentially expensive dom search every time the thing is clicked.
I'm not css guru but you can probably get something cool with this without too much effort.
[from comments] I usually need two clicks on the link to get it to show in the first place, which is really strange
No, that is anything but strange.
element.style.property only allows you to access CSS values that where set either directly via a style attribute on the element, or by assigning a value to element.style.property via script.
Both is not the case here, and your class="hidden" that you had in your code initially (before editing it out) was likely to set the element’s visibility hidden from the stylesheet, right?
Therefor, when you check the current visibility of the element here for the first time,
if (div.style.visibility === "hidden") {
that condition is not true, so you set the visibility to hidden in your else-branch – and therefor the element stays hidden.
Now, on your second click, the value hidden was set via script, so now you can read it back. Now that if condition results in true, and your script sets the element to visible.
Easy way to work around this – turn the logic around:
Check div.style.visibility === "visible", and set the element to hidden if that’s true, and visible if it is false.
The element is hidden (via the stylesheet) when your page first loads. Because of what I explained before, div.style.visibility can’t read the value on the first click, so the condition will be false, and the element will be made visible.
The HTML (note the added id):
<a href="#" id="menu1Toggle" style="visibility: hidden;">
<h2>title</h2>
</a>
The handler (note the added ev and preventDefault()):
function getContentP(ev)
{
var div = document.getElementById("menu1Content");
div.style.visibility = div.style.visibility === "hidden" ? "visible" : "hidden";
ev.preventDefault();
};
Attach the event with:
document.getElementById("menu1Toggle").onclick = getContentP;
You could use opacity if you want to fadeIn/Out. Just add a CSS transition. An easy toggle method would be:
elem.style.opacity = +!parseInt(elem.style.opacity);
Not sure if this would perform better/worse, but here is a Fiddle
Related
I am new to Javascript and trying to create a Javascript click function that will display the input element when the button is clicked and hide it when the button is clicked again. My console shows no error messages but when I click the button the input element does not hide. Again I am very new to Javascript, I appreciate any feedback!
document.addEventListener("DOMContentLoaded", load);
function load() {
let button = document.querySelector("button");
button.addEventListener("click", clickMe);
}
// Click Function to change the display value of the input
function clickMe() {
let input = document.getElementById("popup");
if (input.style.display == "none") {
input.style.display = "block";
} else {
input.style.display = "none";
}
}
<!-- <form> I commented out the form element because it does not work to use .addEventListener inside form -->
<label for="button"></label>
<fieldset>
<ol>
<li><button type="button" onclick="clickMe()">Click me!</button></li>
<li><input type="text" name="popup" id="popup" placeholder="placeholder"></li>
</ol>
</fieldset>
<!-- </form> -->
You are adding the click listener twice, in HTML with onclick and in JavaScript with addEventListener(). Therefore it is executed twice for each click.
Since your listener toggles between two states, calling it twice behaves the same as calling it none at all. This is the issue you are observing.
Adding the listener only once solves this issue. Prefer to keep addEventListener(); reasons are stated later on.
Since you mentioned that you would "appreciate any feedback", I collected a few points that may be of interest:
Good parts
JavaScript
Deferring code execution with "DOMContentLoaded"
In StackOverflow snippets, the script is always added as <body>'s last element, however in other scenarios this can not be ensured.
Scripts can be added in many different ways to pages:
Inline scripts:
Regular scripts.
type="module" scripts.
External scripts:
With defer.
With async.
Regular scripts.
type="module" scripts.
Additionally, for regular non-deferred scripts, position of the script element in the document is also relevant.
But by using "DOMContentLoaded", you ensure that all relevant elements have been loaded regardless of how the script is added to the page, which is great!
Here it is also more preferable to use "DOMContentLoaded" over "load", since we only want to wait until the DOM has loaded; the loading of resources is irrelevant for our implementation.
Use of getElementById()
In this case, using getElementById() is more expressive than using querySelector() with an ID selector, because it conveys our intent more clearly: Getting an element by its ID. The approach of using querySelector() with an ID selector would be less clear at a glance.
Note that you can practically add IDs to every element, but reserving them for when it makes sense keeps your code complexity maintainable.
However, if your document structure allows for (subjectively) simple selectors to uniquely select elements, it may be preferable to use querySelector() instead of adding an ID solely to use getElementById().
Use of modern declarator: let
The declarator var has been de facto deprecated in favour of let/const for various reasons, so usually the new declarators are preferred.
While let may be used in all places where const is used, its usage conveys a different intent:
const is used for a constant reference, whereas the reference of let may change during runtime.
Sidenote: Referenced values may still be mutated, including values referenced by const. Primitive values are immutable, but e.g. objects are mutable.
Regardless, learning JavaScript with let/const is usually easier and more preferable than learning it with var.
Comments!
Useful comments are always appreciated. Since your function name is not expressive, your comment provides the missing description.
However, if your function name would be expressive enough (see section "Naming conventions" below), the comment would be obsolete.
HTML
Use of type="button"
By default, buttons have type="submit", which submits the form on click. But you want to use your button as an actual button.
This could be realized by calling event.preventDefault() in its listener, but using type="button" instead makes for semantic and more expressive HTML code and keeps your JS code simpler.
Unfortunately, it ambiguates whether the button-click would submit the form when only inspecting the JS code, but this is a trade-off for keeping the JS code simple(r).
Use of uncommon elements
Some elements (such as <fieldset>) are more rare on typical (commercial?) pages but more common on document-like pages. I personally enjoy seeing these uncommon elements being used.
Note that <fieldset> is usually used together with <legend> to provide a descriptive heading for it.
Labelling
Your attempt of providing a label for the button is great!
A label's for attribute is used to reference another element by its ID. Unfortunately, your button doesn't have an ID, nor does the referenced ID "button" exist.
Note that a button's content may act as that button's label, so an additional label may actually not be necessary.
Your placeholder attribute currently only holds placeholder text, presumably because this is just a StackOverflow snippet. But providing instructions is great and improves the user experience.
Feedback
TL;DR
Prefer addEventListener() over onevent attributes/properties.
Prefer reverting styles over overriding with assumed values.
Follow a naming convention, your own or an established one. This keeps names meaningful.
Prefer browser-provided features (checkbox) over custom implementations (custom toggle button).
Keep accessibility in mind.
Separation of concerns
Nowadays web technologies (HTML, CSS and JS) allow for Separation of concerns: You can separate structural code (HTML), presentational code (CSS) and functional code (JS). Separating your code into these "concerns" allows for easier maintenance in the future.
That said, you are adding functionality to your page in two ways:
In HTML with onclick.
In JS with addEventListener().
As mentioned before, functionality is more related to JS than HTML, so we should prefer addEventListener() to keep our concerns organized.
Sidenote: Adding the listener twice actually cancels its effect since it would be called twice per interaction, toggling back and forth between two states. Adding only one listener conveniently fixes this bug.
Using addEventListener() is also more preferred than assigning to the onclick property (or similar), because addEventListener() ...
Allows adding multiple listeners.
Allows more configurations for the listener.
Exists on many different objects, e.g. elements, window, document, etc.
Inline styles
The HTMLElement.style property reflects that element's inline style declarations. You hide the element by setting style.display = "none", which is fine. But to unhide you assume that the previous value was block.
If the element's display value was not block, then your toggling behaviour may appear buggy. To fix this we should remove our assumption and just revert to the actual previous value.
We can revert to the previous value in multiple ways, but the easiest is to just remove our inline declaration:
const [revertingButton, overridingButton] = document.querySelectorAll("button");
revertingButton.addEventListener("click", evt => {
const button = evt.target.closest("button");
const div = button.nextElementSibling;
if (div.style.display === "none") {
// Remove inline declaration
div.style.display = "";
} else {
div.style.display = "none";
}
});
overridingButton.addEventListener("click", evt => {
const button = evt.target.closest("button");
const div = button.nextElementSibling;
if (div.style.display === "none") {
// Override style declaration with assumption
div.style.display = "block";
} else {
div.style.display = "none";
}
});
div {display: flex} /* Notice this declaration! */
/* Ignore; for presentational purposes */
body {font-family: sans-serif}
div::before, div::after {
content: "";
width: 50px;
height: 50px;
display: block;
}
div::before {background-color: lightpink}
div::after {background-color: lightblue}
section {margin-block: .8rem}
header {
font-size: large;
font-weight: bold;
}
<section>
<header>Removing inline declaration</header>
<button>Toggle visibility</button>
<div></div>
</section>
<section>
<header>Assuming previous value</header>
<button>Toggle visibility</button>
<div></div>
</section>
Naming conventions
Some of your names are either confusing or not meaningful (e.g. function name clickMe).
This can be fixed by following a naming convention. You can either decide on one by yourself, or choose (from) established naming conventions. Any naming convention is better than none. Here are some naming conventions:
Google's style guide: Simple and loose convention.
No abbreviations.
Meaningful over short names.
MDN's style guide: Stricter but still quite loose convention.
No abbreviations.
Short names.
No articles or possessives.
No type information in names (no "list", "array", "map") and no Hungarian Notation.
By following the point "meaningful names" we may implement these changes:
Change function name clickMe to toggleInputVisibility.
Change function name load to loadHandler, or use an anonymous function since the surrounding code (the context) provides enough meaning.
Accessibility
It is good to practice implementing behaviours such as toggle buttons yourself, but for conciseness, uniformity and accessibility you should prefer the browser-provided features in any real-world projects. That said, let's focus on the accessibility aspect:
Controls (e.g. your toggling button) should indicate their state and what element they control. Referencing the controlled element is done with aria-controls.
Indicating the control's state can in our case be done with aria-checked since our button is a 2-state control, or by using an <input type="checkbox"> which natively indicates its own state.
Apart from indicating their state, checkboxes also indicate the correct role: Our button is of role="button" by default, but we effectively use it as a checkbox. Therefore this button's role should be checkbox.
Labels
Controls should also provide meaningful descriptions regarding their actions: The text "Click me!" is not descriptive. Instead, "Toggle input visiblity" (or just "Toggle visibility") would be better. If we used a checkbox instead of a button, its description should be provided in a label that references the checkbox with its for attribute.
Labels come with the additional benefit of increasing the interactable region for their referenced input element. For example, clicking a textfield's label focuses the textfield, or clicking a checkbox's label toggles the checkbox.
Also important to note is that "placeholder text is not a replacement for labels". When using input elements, always provide a label:
With aria-labelledby, or
With aria-label, or
With <label>s and the for attribute.
Nesting an input element inside a label element is technically enough according to the HTML specification, but "generally, explicit labels are better supported".
Examples
Here is an accessible example of using a button as a visibility toggle:
const button = document.querySelector("button");
button.addEventListener("click", () => {
// ariaChecked is a string, but we want to invert its boolean equivalent
button.ariaChecked = !(button.ariaChecked === "true");
const input = document.getElementById(button.getAttribute("aria-controls"));
input.style.display = button.ariaChecked === "true" ? "" : "none";
});
/* Add visual indicator */
button::before {display: inline}
button[aria-checked=true]::before { content: "\2705 "}
button[aria-checked=false]::before { content: "\274E "}
<div>
<button role="checkbox" aria-checked="true" aria-controls="button-input">Toggle visibility</button>
<input id="button-input">
</div>
And here is an equivalent example but using a checkbox:
const checkbox = document.querySelector("input[type=checkbox]"); // or #checkbox
// Prefer to use "change" over "click" for semantics, but both work
checkbox.addEventListener("change", () => {
// Toggling checkedness is default behaviour
const input = document.getElementById(checkbox.getAttribute("aria-controls"));
input.style.display = checkbox.checked ? "" : "none";
});
<div>
<label for="checkbox">
<input id="checkbox" type="checkbox" checked aria-controls="checkbox-input">Toggle visibility
</label>
<input id="checkbox-input">
</div>
Things to notice:
In the button-example we repurpose an element to behave like another. This makes the code less readable and more confusing. The HTML in the checkbox-example is therefore more understandable.
Toggling the checkedness comes built-in in the checkbox-example.
Using the checkedness of a checkbox is easier than using the checkedness of an ARIA-enhanced element, because .checked is boolean whereas .ariaChecked is a string.
Semantically, we want to react to a state change (event type change), not to the cause of a state change (event type click) which may have been done manually.
The input fields in both examples are generic for simplicity's sake, therefore they do not have an associated label. As mentioned before, input elements should always be associated with a label in real-world scenarios.
Two things:
You don't need to add both the onclick attribute and an event listener. Just use .addEventListener, it is the preferred way.
Don't use tag selectors unless you really want to affect every <button> element. In your case, you should use an ID, such as "clickMeBtn".
document.addEventListener("DOMContentLoaded", load);
function load() {
let button = document.querySelector("#clickMeBtn");
button.addEventListener("click", clickMe);
}
// Click Function to change the display value of the input
function clickMe() {
let popup = document.getElementById("popup");
if (popup.style.display == "none") {
popup.style.display = "block";
} else {
popup.style.display = "none";
}
}
<form>
<label for="button"></label>
<fieldset>
<ol>
<li><button type="button" id="clickMeBtn">Click me!</button></li>
<li><input type="text" id="popup" name="popup" placeholder="placeholder"></li>
</ol>
</fieldset>
</form>
JS will execute code from top to bottom, in your code, you define function ClickMe() after your function load() so it can not call.
Just move it on.
There also other solution, that you dont need to add addEventListener with event click because in button tag already have default event click, just remove that event can solve this problem
Best way to achieve this is to create a CSS class to hide and unhide the element.
document.querySelector("button").addEventListener("click", function (evt) {
document.querySelector("input").classList.toggle("hidden");
});
.hidden{
display:none
}
<button>click</button>
<input type=text></input>
Some interesting JavaScript I hadn't seen before:
var html = $( 'html' );
if( html.className !== '' )
html.className = '';
I’m not sure how it works, but it seems like that assignment has the effect of changing the CSS display value of every element on the page which has the className selector from block to none.
Is anybody familiar with this behavior? Am I seeing it right?
EDIT:
OK, in response to those who say it's not valid jQuery, you're right. It's a shorthand way of describing the HTML element that was passed in by another function. And I didn't write it, just trying to understand it. It works, I want to know why.
This is actually a jQuery to select the html node and change the value of css class to it.
lets says you want to change the padding of the html document using a click event.
your onclick event would call that function to assign the css class with the desired padding.
I need to determine the height of a DOM element using javascript - specifically, in my case, a div containing some text. Due to the way that HTML works, I can only reliably do this if the element is visible. The general purpose solution is to show the element, get it's height, and then hide it - simple enough in the single element case.
However, in the general case, the element in question may be a child of other elements that are hidden, thus preventing the above solution from working - calling jQuery's show() function on the element in question doesn't actually cause it to be shown due to the hidden parent, so you still can't get the height.
How can I make an element visible long enough to get its height, taking into account any parent elements that need to be made visible to make it work?
Use case: I'm trying to write some code that I can apply to any table element, that creates some other elements whose height should match the height of the table header. I want to keep the code generic enough that it will work regardless of where in the DOM the table is located, or if it is currently visible. An alternate solution would be to have some javascript that simply adjusts the size of the created elements when the size of the table header changes (such as when it is shown), but conceptually that seems less efficient. Still, if it is easier, I will accept that as an answer.
Edit: To give an example, while keeping in mind I am going for a general solution that is not tied to this specific HTML layout, consider the following HTML:
<div style="display:none; line-height:22px; font-size:18px;">
...Some random text or other content...
<div id="desired_size">
I want to find the height of this div when visible
</div>
...Possibly some more content/other stuff...
</div>
The goal is to get the height of that inner div, but I can't do that because it isn't displayed - it is hidden due to the parent div being hidden. If all I know about the HTML is the desired_size div, how would I go about making it visible enough to get the height? Granted, this example is trivial, but I'm trying to generalize it.
Edit 2: One suggestion was to clone the element and move it to somewhere that is visible. This works, but with a caveat: any inherited CSS properties that would affect the size are lost.
Edit 3: I'm trying to write a block of code that I can re-use in a variety of web pages, not just coding to a specific layout. As such, I can't make any assumptions about or changes to the parent HTML. The example above shows one case where this can cause difficulties.
Edit 4: As has been pointed out, it would be trivial to change the HTML such that the visual appearance is the same, but the issue doesn't exist. However, I am trying to find a solution that works with the HTML as written, regardless of how the HTML is written.
demo - http://jsbin.com/tosusanowu/edit?html,js,output
Assuming you know that desired_size div has always a parent that is hidden.
$(function(){
var desired_size = getDesiredSize('#desired_size');
});
function getDesiredSize(el) {
var $el = $(el), $parent = $el.parent(), desired_size = 0;
$parent.attr('style', 'opacity:0;position:absolute');
desired_size = $el.height();
$parent.attr('style', 'display:none');
return desired_size;
}
<div style="display:none;">
...Some random text or other content...
<div id="desired_size">
I want to find the height of this div when visible
</div>
...Possibly some more content/other stuff...
</div>
The following javascript/jQuery function should work in the general case where the HTML structure is unknown, as requested:
function getHeight(objectID){
var object=$('#'+objectID);
var nextObject=object;
var changedObjects=[];
var counter=0; //to prevent infinite looping
while(!object.is(':visible') && counter<100){
counter+=1;
var curObject=nextObject; //store a reference for use in loop
nextObject=curObject.parent();
var curStyle=curObject.css('display') //see if current object is hidden
if(curStyle!='none')
continue; //don't mess with non-hidden objects
//see if the display style is inline, or from a CSS file
var inlineStyles=curObject.attr("style");
if(typeof(inlineStyles)!=='undefined'){
inlineStyles.split(";").forEach(function(element){
var style = element.split(":");
if ($.trim(style[0]) === 'display') {
//Record the fact that the display properly is an inline style
curObject.data('floatinghead_inline_style',true);
return false; //break out of the forEach loop
}
});
}
curObject.show(); //if object is hidden, show it for now
visitedObjects.push(curObject); //save a reference so we can put it back later
}
var height=object.height(); //this should work properly, since object should now be visible
visitedObjects.forEach(function(item){
if(item.data('floatinghead_inline_style')===true)
item.hide();
else
item.css('display','');
})
}
The above code makes no assumptions about the HTML structure, particularly the depth of the object in question from the hidden parent element. Also, unlike the "clone item to a different, visible, location in the DOM" option, it should properly take into account any inherited attributes that affect the height.
I have bunch of images in a wrapper div. I need to hide all of them except the one user clicks on.
What is the most performance concise way of doing that?
Should I apply class on all of them to hide them all and than filter out the one that was clicked and show only that one or should I loop over them all hiding them as loop progresses with exception of the one that was clicked or something else?
Tnx
In modern desktop browsers you won't see any difference. Browsers are tuned so that they are blazing fast in rendering any changes is DOM three. Guts tell me that it might be sligtly faster to loop through all images and set visibility depending on item clicked using style attribute and not using class. In that way you have to process only N elements and no external CSS files are involved. If you hide all and show the element with was clicked, you process N + 1 elements.
In your situation I would use solution that is fastest, more managable and clean from the developers standpoint as there won't be much difference in the final result if you use one method or another.
PS: If you're using jquery, you can use the following:
Lets say, your div has id='test-div', and there are several images in it. All these images can be accessed as:
$('#test-div img')
Now, lets assume you know the id of image which got clicked. Lets assume id='my-image'.
You can execute the following to hide all other images (except 'my-image'):
$('#test-div img').not('#my-image').addClass('hide')
One of the most performant ways would be to let CSS do the visibility. It sounds to me like you're only displaying one at a time, in which case you can do it with two DOM operations by using classes;
// scope above
var lastClicked = null;
// then in click listener, 1st param `e`
if (lastClicked) lastClicked.className = ''; // remove visible class
lastClicked = e.target; // get clicked node
lastClicked.className = 'visible'; // add visible class
I'm assuming event.target but depending how the listener is attached, you might want to use this or some other logic. Further, if you expect element.classList support, you can use add and remove from that.
Example CSS of how to show only nodes with class token visible.
selector:not(.visible) {
display: none;
}
There are a couple of ways I could do this (That I'm aware of).
Test css display
if ($('#foo').css('display') == 'none')
Test the visibility
if ($('#foo').is(':visible'))
In the visibility I can check if the element is there.
Elements are considered visible if they consume space in the document.
Visible elements have a width or height that is greater than zero.
Elements with visibility: hidden or opacity: 0 are considered visible,
since they still consume space in the layout.
Source
But, note that in both I can't test the visibility (by the user) because:
Hiding an element can be done by setting the display property to
"none" or the visibility property to "hidden". However, notice that
these two methods produce different results:
visibility: hidden hides an element, but it will still take up the same
space as before. The element will be hidden, but still affect the
layout.
display: none hides an element, and it will not take up any space. The
element will be hidden, and the page will be displayed as if the
element is not there:
Source
So in neither of the examples I test if the element is visible in all senses for the user.
So my question is:
What're the differences between the two if's codes from above?
What's the best way to test if an element is visible to the user:
Should I have to use something like:
if ($('#foo').is(':visible') &&
$('#foo').css('opacity') > 0 &&
$('#foo').css('visibility') != 'hidden')
I think your best bet is to implement a custom function like below and test/improve as new things come up,
$.fn.isReallyVisible = function () { //rename function name as you like..
return (this.css('display') != 'none' &&
this.css('visibility') != 'hidden' &&
this.css('opacity') > 0);
}
The above should be cross browser proof as we are using jQuery .css function (specifically for opacity).
DEMO
The difference between the two is that being hidden using "visible" attribute leaves the element on the page, just not actually displayed. So, it's spacing will be taken into account when the page renders the rest of the display.
It seems that doing it the other way actually stops the element from being put onto the page, which can change how other elements on the page are laid out.
usually testing the visible part is enough from my experience, but if you are wanting to be more complete, then yeah you would have to check using "&&" conditions on multiple attributes.
It really all depends on how clean the code you are using is, and how well tested the rest of the UI aspect of the system is.
The other thing to consider is what is the purpose of the test. Are you testing code that you wrote, or how the browser is using Javascript to render the page? You want to be testing the code that you are creating, and rely on the fact that the browser works (because if the browser stops working, then the whole thing is unreliable anyway). So if your code tells the element to set some attribute, then checking for that attribute is all the testing you need to do. Anything on top of that can only really be proven by testing outside of the code itself (as in manualy looking at the page and other things like that).
If you want to see if an element exists in the DOM you could just do this:
$.fn.exists = function () {
return this.length > 0;
}
Usage:
$('#myid').exists();