Targeting specific elements in JavaScript? - javascript

I tried asking this before, but I guess I wasn't specific enough. Suppose I have HTML code that looks like this. How do I ONLY target the tags within the the horizontalNAV using pure JavaScript? Okay I know I could do this using jQuery like this...
<script type="text/javascript">
$(document).ready(function(){
$('#horizontalNAV li a').click(function(){
//jQuery code here...
});
});
</script>
However I do NOT want a jQuery answer, because I want to know how you target ('#horizontalNAV li a') using pure javaScript.
or you can tell me how to do it for the verticalNav portion, either way I'll get it, if I see an example or if its explained to me. If I'm not mistaken you would have to use the document.querySelectorAll method, if so, how does that work in the above example.
<div id="wrapper">
<div id="horizontalNav">
<ul>
<li>item1</li>
<li>item2</li>
<li>item3</li>
</ul>
</div>
<div class="sideBar">
<div class="verticalNav">
<ul>
<li>item1</li>
<li>item2</li>
<li>item3</li>
</ul>
</div>
</div>
</div>

Without jQuery it would look like this
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
var elems = document.querySelectorAll('#horizontalNav li a');
for (var i = elems.length; i--;)
elems[i].addEventListener("click", handler, false);
}, false);
function handler(event) {
//javascript code here...
this.style.color = 'red';
}
</script>
FIDDLE

If #horizontalNAV is a UL or OL element, then it can only have LI element children so you can skip that part of the selector. The following doesn't use querySelectorAll so will work in browsers, that don't support it:
<script>
window.onload = function() {
var list = document.getElementById('#horizontalNAV');
var links = list && list.getElementsByTagName(‘a’);
if (links) {
for (var i=0, iLen=links.length; i<iLen; i++) {
links[i].onclick = listener;
}
}
}
function listener() {
// do stuff
}
</script>
If you want to include more than one listener for an event, you’ll need to use addEventListener or some other strategy instead of assigning the function directly to the element, but in most cases only one listener is required per event type and keeping things simple has its benefits.
The listener function is declared outside the function doing the assignment to avoid a closure and circular reference, so it should have less chance of creating a memory leak.

Related

Prevent click after a certain child when event handler is attached to parent

I have the following structure:
<ul id="list-items">
<li class="item" data-id="123" data-title="Some Title">
<div class="block">
<img src="#"/>
Link
<p>Some excerpt</p>
</div>
</li>
</ul>
There are more than 1 <li> item
All data- attributes are on the <li> elements
Using jQuery, I would usually make use of event delegation instead of attaching event handlers on every <li> item:
$( document ).on( 'click', '.item', function() {
var id = $( this ).data( 'id' );
var title = $( this ).data( 'title' );
});
However, I am not able to replicate this using Pure JavaScript.
I want to be able to click on an <li> item without clicking any of its child elements.
I am also not at the liberty of using closest() since we have to provide support for IE11. Is there a simpler way to implement it?
EDIT:
I am avoiding attaching event listeners to each <li> item as it won't work for dynamically created <li> elements, and also for performance reasons.
You can disable the li's content from getting any mouse event by setting a pointer-events: none to it.
<li class="item" data-id="123" data-title="Some Title">
<div class="block" style="pointer-events: none">
<img src="#"/>
Link
<p>Some excerpt</p>
</div>
</li>
You can guarantee now that the event.target will always be the li
If you don't want to attach event handler on all the items directly. you can attach only one event handler on the parent like this
var element = document.querySelector("#vanilla-parent")
element.addEventListener("click", function(event){
event.composedPath().forEach(function(elm){
if (elm.tagName === 'LI') {
// do something
}
});
});
$("#jquery-parent").on("click", "li", function(event){
// do something
});
Pen demonstrating the same: https://codepen.io/kireeti-tiki/pen/EzPpqZ?editors=1010
I used composedPath on the event object to get to li, I wouldn't recommend this as this is a bit of hacky way to get to the solution. Also, it is not supported on IE and Edge. So stay away from that solution if you need support for those browsers. More on that subject here https://developer.mozilla.org/en-US/docs/Web/API/Event
If using jQuery is not a problem, Then I would recommend that approach.
Will something like get all li and attach an eventlistener do?
<script>
var li_list = document.getElementsByTagName('li');
for(var i = 0; i < li_list.length; i++) {
(function(index) {
li_list[index].addEventListener("click", function() {
event.preventDefault();
alert('clicked')
})
})(i);
}
</script>
Here's an example using Element.closest(), for which there is a polyfill.
function attachClickHandler() {
const list = document.querySelector('.list');
list.addEventListener('click', (item) => {
// console.log('Item:', item);
const closest = item.target.closest('li');
console.log('Closest li:', closest);
console.log('Data on closest li:', closest.tagName, closest.dataset && closest.dataset.id || 'no data');
// alternative
console.log(' === alt ===');
let elem = item.target;
while (elem.tagName.toLowerCase() !== 'li') {
elem = elem.parentNode;
if (elem.tagName.toLowerCase() === 'ul') {
break;
}
}
console.log('Manual version:', elem.tagName, elem.dataset && elem.dataset.id || 'no data');
});
console.log('Listening.');
}
attachClickHandler();
<h1>My list</h1>
<ul class="list">
<li data-id="1">Item 1</li>
<li data-id="2"><button>Item 2</button></li>
<li>Empty item</li>
</ul>
Edit: This won't work on IE as stackoverflow likely doesn't load the polyfill. But if you test it standalone (should be 3 seconds work) you can verify if it's good enough for you.

jquery custom function call not working

I'm new to jquery, ajax and jstree. I'm using jstree to have my <ul> elements look like a tree structure.
I have the <ul> under a div tag of id = "container". When I execute the html file, the div (id = "container") is passed to jstree function as follows:
$(function() {
$('#container').jstree();
});
My html snippet is as follows:
<div id="container">
<ul id = "treeNodes">
<li>Parent
<ul>
<li>Child1
<ul>
<li>child2-1</li>
<li>child2-2</li>
<li>child2-3</li>
<li>child2-4</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
The tree structure is being displayed fine.
I'm trying to write a jquery function that gets the li element's name as an argument.
For example: when I click Parent, the function should recieve "Parent" as an argument or when I click child2-3, the function should get "child2-3" as the argument.
I attempted to create that function, but it doesn't seem to work. Here's my attempt -
$("#treeNodes li").click(function() {
console.log("hello");
console.log(this.innerHTML);
});
The control seems to go to the function calling the jstree(), but the other function doesn't seem to work.
Any help or tips would be appreciated. Thanks in advance.
Making your structure into a JSTree causes new HTML to be created and those new elements have custom classes and new APIs. So, you have to read the documentation to understand how to use the new structure.
If you look at this documentation page, you'll see an example of what you are after, which hinges on a custom changed event. I've reproduced that example, customizing it for your HTML and console output.
$(function() {
$('#container')
// listen for the custom "changed" event on any jstree
.on('changed.jstree', function (e, data) {
// When you click on a JSTree the event appears to fire on the entire tree
// You'll have to iterate the tree nodes for the selected one.
var i, j, r = [];
for(i = 0, j = data.selected.length; i < j; i++) {
r.push(data.instance.get_node(data.selected[i]).text);
}
console.clear();
console.log('Selected: ' + r.join(', '));
})
// create the instance
.jstree();
});
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.5/jstree.min.js"></script>
<div id="container">
<ul id = "treeNodes">
<li>Parent
<ul>
<li>Child1
<ul>
<li>child2-1</li>
<li>child2-2</li>
<li>child2-3</li>
<li>child2-4</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
I have read the documentation from the jstree page: https://www.jstree.com/
There is a class from the jstree called jstree-children, from that class you can obtain the value from the list, like this:
$(document).on('click', '.jstree-children ul li', function (e) {
console.log($(this).html());
});
Try this: https://jsfiddle.net/Mantixd/0jwpz2r1/

Hiding an unordered list when it has no elements

I have a problem that seems at first like a total no-brainer and an easy task.
I have a JavaScript plugin on my page that generates a Table Of Contents list to the sidebar of my Wordpress pages. My purpose is to hide the text widget element of the #toc when the list within it has no elements. I'm trying to solve it using jQuery but no luck.
The HTML:
<div class="textwidget">
<div id="toc">
<ul></ul>
</div>
</div>
The JavaScript:
jQuery(document).ready(function ($) {
if (!$('#toc').children('ul').has('li')) {
$('#toc').parent().hide();
}
});
My script should hide this specific #toc's parent, because it has no child <li> elements, but it doesn't. Instead, when I remove the ! from my if sentence, the script hides my list, as if it had something in it. It then also hides the lists that actually have elements in them. Am I totally missing something here?
Simply use this:
$(document).ready(function () {
if ($('#toc ul li').length < 1) {
$('#toc').parent().hide();
}
});
<script>
$(document).ready(function(){
$("#YourListID").hide();
var a = $("#YourListID li").length();
if (a > 0 ) {
$("#YourListID").show();
}
else {
$("#YourListID").hide();
}
});
</script>
You can try one of the following:
1) Give the UL an ID and check it's HTML. Ex:
var ulHtml = $("#myULElement").html();
if(ulHtml == ''){
$('#toc').parent.hide();
}
2) Do the same as above but with a relative path, like:
var ulHtml = $("#toc ul").html();
//etc...
See if that works. If it does, we can elaborate on it further.
This works:
if(!$('#toc').has('ul li').length) {
$('#toc').closest('.textwidget').hide();
}
Working jsfiddle here
What you need to do is add .length to your testing statement.
jQuery(document).ready(function ($) {
if (!$('#toc').children('ul').has('li').length) {
$('#toc').parent().hide();
}
});
Working Fiddle
Source: http://api.jquery.com/has/
Use length to evaluate if there are <li> or not.
DEMO
<div class="textwidget">
<div id="toc">
<ul></ul>
</div>
</div>
if ($('#toc ul li').length<1) {
$('#toc').parent().hide();
}
inside $(document).ready check for this
if($.trim($('#toc ul').html()).length == 0){
$('#toc').parent().hide();
}
as case may arise
<ul></ul>
<ul>
</ul>

Copy div class from the clicked item and insert into another div

I currently have a list of <li>'s. Each <li> will have a color class defined, example: .color-blue, .color-red, .color-green - like so:
<ul id="listings">
<li class="layer block color-blue" id="item-1"></li>
<li class="layer block color-red" id="item-2"></li>
<li class="layer block color-green" id="item-3"></li>
</ul>
How do I copy/get the color class of the specific <li> item that is clicked?
I have my click listener in place and also know how to get the <li "id"> however not sure on the specific class though.
/* Click listener */
document.getElementById("listings").addEventListener("click", function(e) {
//console.log(e.target.id + " was clicked");
});
Something like this:
document.getElementById("listings").addEventListener("click", function(e) {
var el = e.target;
if (el.tagName == "LI") { // Use only li tags
for (i=0; i < el.classList.length; i++) {
if (~el.classList[i].indexOf('color')) {
var color = el.classList[i];
console.log('color class found: '+color);
break;
}
}
}
});
http://jsfiddle.net/bHJ3n/
You can use (jQuery):
$('ul').find('li.layer block color-blue')
Or
$('ul#listings').find('li.layer block color-blue')
Or... you can not use jQuery as that wasn't in the original question and would be wasteful to include unnecessarily.
Here's a solution that works in vanilla JS:
jsFiddle Example
Essentially because you're lumping the colour among the other classes you have to split them into an array and iterate over them until you find the one that starts 'color-'. I would recommend you use a custom attribute instead, like data-color="blue" as that would mean you could just retrieve it with:
e.target.getAttribute('data-color');
Try
document.getElementById("listings").addEventListener("click", function(e) {
alert(e.srcElement.className);
});
DEMO
UPDATE(since it is not working in Firefox as pointed from Sai):
To work also in Firefox try this:
document.getElementById("listings").addEventListener("click", function(e) {
var target = e.target || e.srcElement;
alert(target.className);
});
DEMO2

Undefined functions

I'm learning javascript and I'm having trouble figuring out why my script is not working. I'm guessing its because the imageIn and imageOut functions don't have access to the counter variable. How would I go about fixing this? Both imageIn and imageOut have errors in my error console 'undefined'.
<style type="text/css">
ul {
list-style-type:none;
}
</style>
<body>
<div id="slideShow">
<ul>
<li>
<img src="stockboat.png" alt="Steam Boat" id="boat" />
</li>
</ul>
</div>
<script type="text/javascript" src="getElementsByClassName.js"></script>
<script type="text/javascript">
var image = document.getElementsByTagName('img');
for (i = 0, ii = image.length; i < ii; i++) {
image[i].style.opacity = "0.5";
image[i].addEventListener('mouseover', imageIn, 'false');
image[i].addEventListener('mouseout', imageOut, 'false');
}
function imageIn() {
image[i].style.opacity = "1";
}
function imageOut() {
image[i].style.opacity = "0.5";
}
</script>
</body>
</html>
I think you're right about it not recognizing your iteration variable, you'll have to do:
Use reference object:
function imageIn() {
this.style.opacity = "1"; // use "this" instead of image array
}
function imageOut() {
this.style.opacity = "0.5"; // use "this" instead of image array
}
-or-
Closure Approach:
image[i].addEventListener('mouseover'
, (function(obj){return function(){imageIn(obj)};})(image[i])
, 'false');
image[i].addEventListener('mouseout'
, (function(obj){return function(){imageOut(obj)};})(image[i])
, 'false');
Need to change function definition:
function imageIn(obj) { // added parameters "obj"
obj.style.opacity = "1";
}
This method allows you to reference the loop variable, which is nice to have as an example for when using setTimeOut in a loop - so you can reuse code later :)
You may try jsfiddle and put the 2 functions imagineIn and imageOut before the code that use them.
You need to wait for the dom to finish loading before accessing any elements.
Wrap your code in with the following:
window.addEventListener('load', function () {
. . . // Your code
});
Additionally, your i variable is wrapped in the closure of both imageIn and imageOut. This means that whenever any image is receives a mouseover or mouseout event, the opacity will always change for the last image in your list of images.
To fix this, you can bind a scope to the functions:
image[i].addEventListener('mouseover', imageIn.bind(image[i]), false);
Then in your imageIn function you would do:
this.style.opacity = "1";
One last point: you are passing the string 'false' as the third argument to addEventListener. In JavaScript, any non-empty string will evaluate to true, so you should pass the boolean value false instead to prevent event bubbling.
I find jQuery incredibly time-saving and intuitive. You just have to get used to throwaway functions. Check it out! http://jsfiddle.net/wgxZu/1/
<style type="text/css">
ul {
list-style-type:none;
}
</style>
<body>
<div id="slideShow">
<ul>
<li>
<img src="http://placekitten.com/100/100" alt="Steam Boat" id="boat" />
</li>
<li>
<img src="http://placekitten.com/200/100" alt="Steam Boat" id="boat" />
</li>
<li>
<img src="http://placekitten.com/300/60" alt="Steam Boat" id="boat" />
</li>
</ul>
</div>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
var image = document.getElementsByTagName('img');
for (i = 0, ii = image.length; i < ii; i++) {
image[i].style.opacity = "0.5";
$(image[i]).hover(function(){$(this).css({"opacity":"1.0"})},
function(){$(this).css({"opacity":"0.5"})});
}
</script>
</body>
</html>
There could be errors in your <script type="text/javascript" src="getElementsByClassName.js"></script> which is causing the rest of your script to stop executing.
In addition to the answers mentioned here I would offer some other advice:
Define your counter variables: var i = 0, ii = image.length. Otherwise you might end up using some global i and ii variables that are already set....
Use an inspector like Chrome Developer Tools to find issues in your JS code.
Add and remove CSS classes rather than changing the elements styles. This will allow you to make multiple style changes with little effort.
Learn to make use of Event Delegation in your code, it will help you in the long run especially when you want to start making use of dynamic content.
Good luck with your learning.
Also, as far as getting the code to work you can take a look at this jsFiddle which uses your code with a few modifications: http://jsfiddle.net/b9Fua/

Categories